diff --git a/.github/workflows/ai_bot.yml b/.github/workflows/ai_bot.yml new file mode 100644 index 0000000..ea38744 --- /dev/null +++ b/.github/workflows/ai_bot.yml @@ -0,0 +1,172 @@ +name: AI Bot + +on: + issues: + types: [opened] + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + +jobs: + respond-to-commands: + runs-on: ubuntu-latest + if: | + (github.event_name == 'issues' && contains(github.event.issue.body, '/ai')) || + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/ai')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/ai')) + permissions: + contents: write + pull-requests: write + issues: write + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install dependencies + run: npm install openai@^4.0.0 @octokit/rest@^19.0.0 + + - name: Process command + id: process + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: | + node << 'EOF' + const OpenAI = require('openai'); + const { Octokit } = require('@octokit/rest'); + + async function main() { + const openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY + }); + + const octokit = new Octokit({ + auth: process.env.GITHUB_TOKEN + }); + + const eventName = process.env.GITHUB_EVENT_NAME; + const eventPath = process.env.GITHUB_EVENT_PATH; + const event = require(eventPath); + + // Get command and context + let command = ''; + let issueNumber = null; + + if (eventName === 'issues') { + command = event.issue.body; + issueNumber = event.issue.number; + } else if (eventName === 'issue_comment') { + command = event.comment.body; + issueNumber = event.issue.number; + } else if (eventName === 'pull_request_review_comment') { + command = event.comment.body; + issueNumber = event.pull_request.number; + } + + if (!command.startsWith('/ai')) { + return; + } + + // Extract the actual command after /ai + const aiCommand = command.substring(3).trim(); + + // Generate response using OpenAI + const completion = await openai.chat.completions.create({ + model: "gpt-3.5-turbo", + messages: [ + { + role: "system", + content: "You are a helpful AI assistant that helps with GitHub repositories. You can suggest code changes, fix issues, and improve code quality." + }, + { + role: "user", + content: aiCommand + } + ], + temperature: 0.7, + max_tokens: 2000 + }); + + const response = completion.choices[0].message.content; + + // If response contains code changes, create a new branch and PR + if (response.includes('```')) { + const branchName = `ai-bot/fix-${issueNumber}`; + + // Create new branch + const defaultBranch = event.repository.default_branch; + const ref = await octokit.git.getRef({ + owner: event.repository.owner.login, + repo: event.repository.name, + ref: `heads/${defaultBranch}` + }); + + await octokit.git.createRef({ + owner: event.repository.owner.login, + repo: event.repository.name, + ref: `refs/heads/${branchName}`, + sha: ref.data.object.sha + }); + + // Extract code changes and file paths from response + const codeBlocks = response.match(/```[\s\S]*?```/g); + for (const block of codeBlocks) { + const [_, filePath, ...codeLines] = block.split('\n'); + const content = Buffer.from(codeLines.join('\n')).toString('base64'); + + await octokit.repos.createOrUpdateFileContents({ + owner: event.repository.owner.login, + repo: event.repository.name, + path: filePath, + message: `AI Bot: Apply suggested changes for #${issueNumber}`, + content, + branch: branchName + }); + } + + // Create PR + await octokit.pulls.create({ + owner: event.repository.owner.login, + repo: event.repository.name, + title: `AI Bot: Fix for #${issueNumber}`, + body: `This PR was automatically generated in response to #${issueNumber}\n\nChanges proposed:\n${response}`, + head: branchName, + base: defaultBranch + }); + } + + // Add comment with response + await octokit.issues.createComment({ + owner: event.repository.owner.login, + repo: event.repository.name, + issue_number: issueNumber, + body: `AI Bot Response:\n\n${response}` + }); + } + + main().catch(error => { + console.error('Error:', error); + process.exit(1); + }); + EOF + + - name: Handle errors + if: failure() + uses: actions/github-script@v6 + with: + script: | + const issueNumber = context.issue.number || context.payload.pull_request.number; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body: '❌ Sorry, there was an error processing your command. Please try again or contact the repository maintainers.' + }); \ No newline at end of file diff --git a/.github/workflows/pr_title_improver.yml b/.github/workflows/pr_title_improver.yml deleted file mode 100644 index 95b710c..0000000 --- a/.github/workflows/pr_title_improver.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: Improve PR Title - -on: - pull_request: - types: [opened, edited, synchronize] - -jobs: - improve-title: - runs-on: ubuntu-latest - permissions: - pull-requests: write - - steps: - - uses: actions/checkout@v3 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: '18' - - - name: Install OpenAI SDK - run: npm install openai@^4.0.0 - - - name: Get PR Content - id: pr-content - uses: actions/github-script@v6 - with: - script: | - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number - }); - const content = `${pr.title}\n\n${pr.body}`; - core.setOutput('content', content); - - - name: Generate Better Title - id: generate-title - run: | - node << 'EOF' - const OpenAI = require('openai'); - - async function generateTitle() { - const openai = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY - }); - - const content = `${{ steps.pr-content.outputs.content }}`; - - const completion = await openai.chat.completions.create({ - model: "gpt-3.5-turbo", - messages: [ - { - role: "system", - content: "You are a helpful assistant that improves pull request titles. Make titles concise, descriptive, and following conventional commit message style. Return ONLY the new title, nothing else." - }, - { - role: "user", - content: `Based on this pull request content, generate a better title:\n\n${content}` - } - ], - temperature: 0.7, - max_tokens: 60, - top_p: 1.0 - }); - - const newTitle = completion.choices[0].message.content.trim(); - console.log(`::set-output name=title::${newTitle}`); - } - - generateTitle().catch(error => { - console.error('Error:', error); - process.exit(1); - }); - EOF - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - - - name: Update PR Title - if: steps.generate-title.outputs.title != '' - uses: actions/github-script@v6 - with: - script: | - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - title: '${{ steps.generate-title.outputs.title }}' - }); \ No newline at end of file