From 181a5458600c48381a3776c5c7526655ee13753f Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 16:57:27 +0200 Subject: [PATCH 01/13] fix: handle case when there are no changes detected --- lib/summarizePullRequest.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/summarizePullRequest.ts b/lib/summarizePullRequest.ts index 81984d1..a946643 100644 --- a/lib/summarizePullRequest.ts +++ b/lib/summarizePullRequest.ts @@ -36,7 +36,7 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { const codeDiff = joinStringsUntilMaxLength(parsedFiles, 10000) // If there are changes, trigger workflow - if (codeDiff.length != 0) { + if (codeDiff?.length != 0) { const systemPrompt = `You are a Git diff assistant. Always begin with "This PR". Given a code diff, you provide a simple description in prose, in less than 300 chars, which sums up the changes. Continue with "\n\n### Detailed summary\n" and make a comprehensive list of all changes, excluding any eventual skipped files. Be concise. Always wrap file names, functions, objects and similar in backticks (\`).${ skippedFiles.length != 0 ? ` After the list, conclude with "\n\n> " and mention that the following files were skipped due to too many changes: ${skippedFiles.join( @@ -98,6 +98,7 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { return summary } + throw new Error("No changes in PR") } const generateChatGpt = async (messages: ChatCompletionRequestMessage[]) => { From 39a899e75507391f4cadfda1b14fa27d8d7db3fb Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 17:16:35 +0200 Subject: [PATCH 02/13] fix: handle null descriptions and improved response formatting --- lib/summarizePullRequest.ts | 14 +++++++++----- utils/github/testPayload.ts | 4 +++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/summarizePullRequest.ts b/lib/summarizePullRequest.ts index a946643..a6308dc 100644 --- a/lib/summarizePullRequest.ts +++ b/lib/summarizePullRequest.ts @@ -4,7 +4,7 @@ import { yieldStream } from "yield-stream" import { parseDiff } from "../utils/parseDiff" import { joinStringsUntilMaxLength } from "./joinStringsUntilMaxLength" -export const startDescription = "" +export const startDescription = "\n\n" export const endDescription = "" export async function summarizePullRequest(payload: any, octokit: Octokit) { @@ -61,7 +61,7 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { // Check if the PR already has a comment from the bot const hasCodexCommented = payload.action == "synchronize" && - pr.body?.split("\n\n" + startDescription).length > 1 + pr.body?.split(startDescription).length > 1 // if (firstComment) { // // Edit pinned bot comment to the PR @@ -81,13 +81,17 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { // }) // } - const prCodexText = `\n\n${startDescription}\n\n---\n\n## PR-Codex overview\n${summary}\n\n${endDescription}` + const prCodexText = `${startDescription}\n\n${ + (hasCodexCommented ? pr.body.split(startDescription)[0].trim() : pr.body) + ? "---\n\n" + : "" + }## PR-Codex overview\n${summary}\n\n${endDescription}` const description = hasCodexCommented - ? pr.body.split("\n\n" + startDescription)[0] + + ? pr.body.split(startDescription)[0] + prCodexText + pr.body.split(endDescription)[1] - : pr.body + prCodexText + : (pr.body ?? "") + prCodexText await octokit.issues.update({ owner, diff --git a/utils/github/testPayload.ts b/utils/github/testPayload.ts index a2c753c..ca78c53 100644 --- a/utils/github/testPayload.ts +++ b/utils/github/testPayload.ts @@ -5,9 +5,11 @@ export const testPayload = { action: "synchronize", pull_request: { number: 4, + // body: null, + // body: "\n\n\n\n## PR-Codex overview\nThis PR adds a new feature to the project: a GitHub app that explains and summarizes PR code diffs. It includes a new `github/route.ts` file and updates several existing files, including `README.md`, `Homepage.tsx`, `DefaultHead.tsx`, `AppLayout.tsx`, `Footer.tsx`, and `Navbar.tsx`.\n\n> The following files were skipped due to too many changes: `package-lock.json`.\n\n", body: "\n\nthis is a test", // body: - // "\n\nthis is a test\n\n" + + // "\n\nthis is a test" + // startDescription + // "\n\n---\n\n## PR-Codex overview\nThis PR adds a new file `route.ts` and makes changes to several files. It adds a homepage with a button to install the app, changes the og_image and twitter_card image formats to png, removes a navbar, adds a `Made by dlabs` line in the footer, and removes the custom connect button and signed block from the navbar.\n\n" + // endDescription, From b3334bae3d76b003b8265e2369cd7247a089e1c8 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 17:40:53 +0200 Subject: [PATCH 03/13] refactor: rename testPayload --- scripts/summarize.ts | 8 ++++---- utils/github/{testPayload.ts => testPayloadSyncPr.ts} | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename utils/github/{testPayload.ts => testPayloadSyncPr.ts} (97%) diff --git a/scripts/summarize.ts b/scripts/summarize.ts index 7005ec4..b2e15a3 100755 --- a/scripts/summarize.ts +++ b/scripts/summarize.ts @@ -1,19 +1,19 @@ import dotenv from "dotenv" import { handleGithubAuth } from "../lib/handleGithubAuth" import { summarizePullRequest } from "../lib/summarizePullRequest" -import { testPayload } from "../utils/github/testPayload" +import { testPayloadSyncPr } from "../utils/github/testPayloadSyncPr" dotenv.config() -// Customize payload in `utils/testPayload` +// Customize payload in `utils/testPayloadSyncPr` async function main() { - const octokit = await handleGithubAuth(testPayload) + const octokit = await handleGithubAuth(testPayloadSyncPr) try { console.log("Generating summary...") - const summary = await summarizePullRequest(testPayload, octokit) + const summary = await summarizePullRequest(testPayloadSyncPr, octokit) console.log( "PR-Codex wrote:\n\n", diff --git a/utils/github/testPayload.ts b/utils/github/testPayloadSyncPr.ts similarity index 97% rename from utils/github/testPayload.ts rename to utils/github/testPayloadSyncPr.ts index ca78c53..12f302c 100644 --- a/utils/github/testPayload.ts +++ b/utils/github/testPayloadSyncPr.ts @@ -1,6 +1,6 @@ import { endDescription, startDescription } from "@lib/summarizePullRequest" -export const testPayload = { +export const testPayloadSyncPr = { installation: { id: 35293807 }, action: "synchronize", pull_request: { From 88c7cf4426399ee92e8c2fbbc89c3a17257f9aa9 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 17:41:05 +0200 Subject: [PATCH 04/13] feat: add testPayloadComment --- utils/github/testPayloadComment.ts | 305 +++++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 utils/github/testPayloadComment.ts diff --git a/utils/github/testPayloadComment.ts b/utils/github/testPayloadComment.ts new file mode 100644 index 0000000..e10af5a --- /dev/null +++ b/utils/github/testPayloadComment.ts @@ -0,0 +1,305 @@ +export const testPayloadComment = { + installation: { id: 35293807 }, + action: "created", + issue: { + url: "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4", + repository_url: "https://api.github.com/repos/decentralizedlabs/pr-codex", + labels_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4/labels{/name}", + comments_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4/comments", + events_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4/events", + html_url: "https://github.com/decentralizedlabs/pr-codex/pull/4", + id: 1627438684, + node_id: "PR_kwDOJKDDks5MNS9d", + number: 4, + title: "PR-Codex init", + user: { + login: "jjranalli", + id: 39241410, + node_id: "MDQ6VXNlcjM5MjQxNDEw", + avatar_url: "https://avatars.githubusercontent.com/u/39241410?v=4", + gravatar_id: "", + url: "https://api.github.com/users/jjranalli", + html_url: "https://github.com/jjranalli", + followers_url: "https://api.github.com/users/jjranalli/followers", + following_url: + "https://api.github.com/users/jjranalli/following{/other_user}", + gists_url: "https://api.github.com/users/jjranalli/gists{/gist_id}", + starred_url: + "https://api.github.com/users/jjranalli/starred{/owner}{/repo}", + subscriptions_url: "https://api.github.com/users/jjranalli/subscriptions", + organizations_url: "https://api.github.com/users/jjranalli/orgs", + repos_url: "https://api.github.com/users/jjranalli/repos", + events_url: "https://api.github.com/users/jjranalli/events{/privacy}", + received_events_url: + "https://api.github.com/users/jjranalli/received_events", + type: "User", + site_admin: false + }, + labels: [], + state: "open", + locked: false, + assignee: null, + assignees: [], + milestone: null, + comments: 2, + created_at: "2023-03-16T13:06:36Z", + updated_at: "2023-04-16T15:36:16Z", + closed_at: null, + author_association: "CONTRIBUTOR", + active_lock_reason: null, + draft: false, + pull_request: { + url: "https://api.github.com/repos/decentralizedlabs/pr-codex/pulls/4", + html_url: "https://github.com/decentralizedlabs/pr-codex/pull/4", + diff_url: "https://github.com/decentralizedlabs/pr-codex/pull/4.diff", + patch_url: "https://github.com/decentralizedlabs/pr-codex/pull/4.patch", + merged_at: null + }, + body: "\n\nthis is a test\n\n\n\n\n\n---\n\n## PR-Codex overview\nThis PR adds a new file `route.ts` for handling pull request events on Github, updates the `README.md` file with a test comment, and makes various changes to the UI. The `package-lock.json` file was skipped due to too many changes.\n\n> The following files were skipped due to too many changes: package-lock.json.\n\n", + reactions: { + url: "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4/reactions", + total_count: 0, + "+1": 0, + "-1": 0, + laugh: 0, + hooray: 0, + confused: 0, + heart: 0, + rocket: 0, + eyes: 0 + }, + timeline_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4/timeline", + performed_via_github_app: null, + state_reason: null + }, + comment: { + url: "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/comments/1510415128", + html_url: + "https://github.com/decentralizedlabs/pr-codex/pull/4#issuecomment-1510415128", + issue_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4", + id: 1510415128, + node_id: "IC_kwDOJKDDks5aBxsY", + user: { + login: "jjranalli", + id: 39241410, + node_id: "MDQ6VXNlcjM5MjQxNDEw", + avatar_url: "https://avatars.githubusercontent.com/u/39241410?v=4", + gravatar_id: "", + url: "https://api.github.com/users/jjranalli", + html_url: "https://github.com/jjranalli", + followers_url: "https://api.github.com/users/jjranalli/followers", + following_url: + "https://api.github.com/users/jjranalli/following{/other_user}", + gists_url: "https://api.github.com/users/jjranalli/gists{/gist_id}", + starred_url: + "https://api.github.com/users/jjranalli/starred{/owner}{/repo}", + subscriptions_url: "https://api.github.com/users/jjranalli/subscriptions", + organizations_url: "https://api.github.com/users/jjranalli/orgs", + repos_url: "https://api.github.com/users/jjranalli/repos", + events_url: "https://api.github.com/users/jjranalli/events{/privacy}", + received_events_url: + "https://api.github.com/users/jjranalli/received_events", + type: "User", + site_admin: false + }, + created_at: "2023-04-16T15:36:15Z", + updated_at: "2023-04-16T15:36:15Z", + author_association: "CONTRIBUTOR", + body: "/codex-ask test", + reactions: { + url: "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/comments/1510415128/reactions", + total_count: 0, + "+1": 0, + "-1": 0, + laugh: 0, + hooray: 0, + confused: 0, + heart: 0, + rocket: 0, + eyes: 0 + }, + performed_via_github_app: null + }, + repository: { + id: 614515602, + node_id: "R_kgDOJKDDkg", + name: "pr-codex", + full_name: "decentralizedlabs/pr-codex", + private: false, + owner: { + login: "decentralizedlabs", + id: 119909906, + node_id: "O_kgDOByWuEg", + avatar_url: "https://avatars.githubusercontent.com/u/119909906?v=4", + gravatar_id: "", + url: "https://api.github.com/users/decentralizedlabs", + html_url: "https://github.com/decentralizedlabs", + followers_url: "https://api.github.com/users/decentralizedlabs/followers", + following_url: + "https://api.github.com/users/decentralizedlabs/following{/other_user}", + gists_url: + "https://api.github.com/users/decentralizedlabs/gists{/gist_id}", + starred_url: + "https://api.github.com/users/decentralizedlabs/starred{/owner}{/repo}", + subscriptions_url: + "https://api.github.com/users/decentralizedlabs/subscriptions", + organizations_url: "https://api.github.com/users/decentralizedlabs/orgs", + repos_url: "https://api.github.com/users/decentralizedlabs/repos", + events_url: + "https://api.github.com/users/decentralizedlabs/events{/privacy}", + received_events_url: + "https://api.github.com/users/decentralizedlabs/received_events", + type: "Organization", + site_admin: false + }, + html_url: "https://github.com/decentralizedlabs/pr-codex", + description: "A ChatGPT-powered bot to summarize PR code diffs", + fork: false, + url: "https://api.github.com/repos/decentralizedlabs/pr-codex", + forks_url: "https://api.github.com/repos/decentralizedlabs/pr-codex/forks", + keys_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/keys{/key_id}", + collaborators_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/collaborators{/collaborator}", + teams_url: "https://api.github.com/repos/decentralizedlabs/pr-codex/teams", + hooks_url: "https://api.github.com/repos/decentralizedlabs/pr-codex/hooks", + issue_events_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/events{/number}", + events_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/events", + assignees_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/assignees{/user}", + branches_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/branches{/branch}", + tags_url: "https://api.github.com/repos/decentralizedlabs/pr-codex/tags", + blobs_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/git/blobs{/sha}", + git_tags_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/git/tags{/sha}", + git_refs_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/git/refs{/sha}", + trees_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/git/trees{/sha}", + statuses_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/statuses/{sha}", + languages_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/languages", + stargazers_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/stargazers", + contributors_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/contributors", + subscribers_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/subscribers", + subscription_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/subscription", + commits_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/commits{/sha}", + git_commits_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/git/commits{/sha}", + comments_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/comments{/number}", + issue_comment_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/comments{/number}", + contents_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/contents/{+path}", + compare_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/compare/{base}...{head}", + merges_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/merges", + archive_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/{archive_format}{/ref}", + downloads_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/downloads", + issues_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/issues{/number}", + pulls_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/pulls{/number}", + milestones_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/milestones{/number}", + notifications_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/notifications{?since,all,participating}", + labels_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/labels{/name}", + releases_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/releases{/id}", + deployments_url: + "https://api.github.com/repos/decentralizedlabs/pr-codex/deployments", + created_at: "2023-03-15T18:38:11Z", + updated_at: "2023-04-11T13:51:42Z", + pushed_at: "2023-04-16T12:54:04Z", + git_url: "git://github.com/decentralizedlabs/pr-codex.git", + ssh_url: "git@github.com:decentralizedlabs/pr-codex.git", + clone_url: "https://github.com/decentralizedlabs/pr-codex.git", + svn_url: "https://github.com/decentralizedlabs/pr-codex", + homepage: "https://codex.dlabs.app", + size: 17593, + stargazers_count: 44, + watchers_count: 44, + language: "TypeScript", + has_issues: true, + has_projects: true, + has_downloads: true, + has_wiki: true, + has_pages: false, + has_discussions: false, + forks_count: 1, + mirror_url: null, + archived: false, + disabled: false, + open_issues_count: 1, + license: null, + allow_forking: true, + is_template: false, + web_commit_signoff_required: false, + topics: ["chatgpt", "github", "github-app", "pull-requests"], + visibility: "public", + forks: 1, + open_issues: 1, + watchers: 44, + default_branch: "master" + }, + organization: { + login: "decentralizedlabs", + id: 119909906, + node_id: "O_kgDOByWuEg", + url: "https://api.github.com/orgs/decentralizedlabs", + repos_url: "https://api.github.com/orgs/decentralizedlabs/repos", + events_url: "https://api.github.com/orgs/decentralizedlabs/events", + hooks_url: "https://api.github.com/orgs/decentralizedlabs/hooks", + issues_url: "https://api.github.com/orgs/decentralizedlabs/issues", + members_url: + "https://api.github.com/orgs/decentralizedlabs/members{/member}", + public_members_url: + "https://api.github.com/orgs/decentralizedlabs/public_members{/member}", + avatar_url: "https://avatars.githubusercontent.com/u/119909906?v=4", + description: "" + }, + sender: { + login: "jjranalli", + id: 39241410, + node_id: "MDQ6VXNlcjM5MjQxNDEw", + avatar_url: "https://avatars.githubusercontent.com/u/39241410?v=4", + gravatar_id: "", + url: "https://api.github.com/users/jjranalli", + html_url: "https://github.com/jjranalli", + followers_url: "https://api.github.com/users/jjranalli/followers", + following_url: + "https://api.github.com/users/jjranalli/following{/other_user}", + gists_url: "https://api.github.com/users/jjranalli/gists{/gist_id}", + starred_url: + "https://api.github.com/users/jjranalli/starred{/owner}{/repo}", + subscriptions_url: "https://api.github.com/users/jjranalli/subscriptions", + organizations_url: "https://api.github.com/users/jjranalli/orgs", + repos_url: "https://api.github.com/users/jjranalli/repos", + events_url: "https://api.github.com/users/jjranalli/events{/privacy}", + received_events_url: + "https://api.github.com/users/jjranalli/received_events", + type: "User", + site_admin: false + } +} From c732a779e6c22c7b2f86af6e0f9e27147cb5040d Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 17:46:37 +0200 Subject: [PATCH 05/13] feat: add reply script --- package.json | 3 ++- scripts/reply.ts | 33 +++++++++++++++++++++++++++++++++ scripts/summarize.ts | 4 ++-- 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100755 scripts/reply.ts diff --git a/package.json b/package.json index 2d70b63..b9fbe80 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "start": "next start", "lint": "next lint", "prettier": "prettier --write . --ignore-path .gitignore", - "summarize": "npx ts-node scripts/summarize" + "summarize": "npx ts-node scripts/summarize", + "reply": "npx ts-node scripts/reply" }, "dependencies": { "@headlessui/react": "^1.7.14", diff --git a/scripts/reply.ts b/scripts/reply.ts new file mode 100755 index 0000000..3783ee5 --- /dev/null +++ b/scripts/reply.ts @@ -0,0 +1,33 @@ +import dotenv from "dotenv" +import { handleGithubAuth } from "../lib/handleGithubAuth" +import { replyIssueComment } from "../lib/replyIssueComment" +import { testPayloadComment } from "../utils/github/testPayloadComment" + +dotenv.config() + +// Customize payload in `utils/testPayloadComment` + +async function main() { + try { + const octokit = await handleGithubAuth(testPayloadComment) + + console.log("Generating comment...") + + const comment = await replyIssueComment(testPayloadComment, octokit) + + console.log( + "PR-Codex commented:\n\n", + comment, + "\n\nView on Github: https://github.com/decentralizedlabs/pr-codex/pull/4" + ) + } catch (error) { + console.log(error) + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/summarize.ts b/scripts/summarize.ts index b2e15a3..c5c0533 100755 --- a/scripts/summarize.ts +++ b/scripts/summarize.ts @@ -8,9 +8,9 @@ dotenv.config() // Customize payload in `utils/testPayloadSyncPr` async function main() { - const octokit = await handleGithubAuth(testPayloadSyncPr) - try { + const octokit = await handleGithubAuth(testPayloadSyncPr) + console.log("Generating summary...") const summary = await summarizePullRequest(testPayloadSyncPr, octokit) From ffdfc81c7d7e1542dba901607e02e0f7c873ead8 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 18:59:30 +0200 Subject: [PATCH 06/13] fix: refactor and optimize summarizePullRequest --- lib/summarizePullRequest.ts | 103 ++++++------------------------ utils/generateChatGpt.ts | 41 ++++++++++++ utils/getCodeDiff.ts | 32 ++++++++++ utils/github/testPayloadSyncPr.ts | 7 +- utils/parseDiff.ts | 2 +- 5 files changed, 95 insertions(+), 90 deletions(-) create mode 100644 utils/generateChatGpt.ts create mode 100644 utils/getCodeDiff.ts diff --git a/lib/summarizePullRequest.ts b/lib/summarizePullRequest.ts index a6308dc..003e168 100644 --- a/lib/summarizePullRequest.ts +++ b/lib/summarizePullRequest.ts @@ -1,11 +1,12 @@ import { Octokit } from "@octokit/rest" -import { ChatCompletionRequestMessage, OpenAI } from "openai-streams" -import { yieldStream } from "yield-stream" -import { parseDiff } from "../utils/parseDiff" -import { joinStringsUntilMaxLength } from "./joinStringsUntilMaxLength" +import { ChatCompletionRequestMessage } from "openai-streams" +import { generateChatGpt } from "../utils/generateChatGpt" +import { getCodeDiff } from "../utils/getCodeDiff" export const startDescription = "\n\n" export const endDescription = "" +const systemPrompt = + 'You are a Git diff assistant. Always begin with "This PR". Given a code diff, you provide a simple description in prose, in less than 300 chars, which sums up the changes. Continue with "\n\n### Detailed summary\n" and make a comprehensive list of all changes, excluding any eventual skipped files. Be concise. Always wrap file names, functions, objects and similar in backticks (`).' export async function summarizePullRequest(payload: any, octokit: Octokit) { // Get relevant PR information @@ -17,34 +18,15 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { } // Get the diff content using Octokit and GitHub API - const compareResponse = await octokit.rest.repos.compareCommits({ + const { codeDiff, skippedFiles } = await getCodeDiff( owner, repo, - base: pr.base.sha, - head: pr.head.sha, - mediaType: { - format: "diff" - } - }) - const diffContent = String(compareResponse.data) - - // Parses the diff content and returns the parsed files. - // If the number of changes in a file is greater than 1k changes, the file will be skipped. - // The codeDiff is the joined string of parsed files, up to a max length of 10k. - const maxChanges = 1000 - const { parsedFiles, skippedFiles } = parseDiff(diffContent, maxChanges) - const codeDiff = joinStringsUntilMaxLength(parsedFiles, 10000) + number, + octokit + ) // If there are changes, trigger workflow if (codeDiff?.length != 0) { - const systemPrompt = `You are a Git diff assistant. Always begin with "This PR". Given a code diff, you provide a simple description in prose, in less than 300 chars, which sums up the changes. Continue with "\n\n### Detailed summary\n" and make a comprehensive list of all changes, excluding any eventual skipped files. Be concise. Always wrap file names, functions, objects and similar in backticks (\`).${ - skippedFiles.length != 0 - ? ` After the list, conclude with "\n\n> " and mention that the following files were skipped due to too many changes: ${skippedFiles.join( - "," - )}.` - : "" - }` - const messages: ChatCompletionRequestMessage[] = [ { role: "system", @@ -56,36 +38,24 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { } ] - const summary = await generateChatGpt(messages) + const codexResponse = await generateChatGpt(messages) // Check if the PR already has a comment from the bot const hasCodexCommented = payload.action == "synchronize" && pr.body?.split(startDescription).length > 1 - // if (firstComment) { - // // Edit pinned bot comment to the PR - // await octokit.issues.updateComment({ - // owner, - // repo, - // comment_id: firstComment.id, - // body: summary - // }) - // } else { - // // Add a comment to the PR - // await octokit.issues.createComment({ - // owner, - // repo, - // issue_number: number, - // body: summary - // }) - // } - const prCodexText = `${startDescription}\n\n${ (hasCodexCommented ? pr.body.split(startDescription)[0].trim() : pr.body) ? "---\n\n" : "" - }## PR-Codex overview\n${summary}\n\n${endDescription}` + }## PR-Codex overview\n${codexResponse}${ + skippedFiles.length != 0 + ? `\n\n> The following files were skipped due to too many changes: ${skippedFiles.join( + ", " + )}` + : "" + }\n\n${endDescription}` const description = hasCodexCommented ? pr.body.split(startDescription)[0] + @@ -100,44 +70,7 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { body: description }) - return summary + return codexResponse } throw new Error("No changes in PR") } - -const generateChatGpt = async (messages: ChatCompletionRequestMessage[]) => { - const DECODER = new TextDecoder() - let text = "" - - try { - const stream = await OpenAI( - "chat", - { - model: "gpt-3.5-turbo", - temperature: 0.7, - messages - }, - { apiKey: process.env.OPENAI_API_KEY } - ) - - for await (const chunk of yieldStream(stream)) { - try { - const decoded: string = DECODER.decode(chunk) - - if (decoded === undefined) - throw new Error( - "No choices in response. Decoded response: " + - JSON.stringify(decoded) - ) - - text += decoded - } catch (err) { - console.error(err) - } - } - } catch (err) { - console.error(err) - } - - return text -} diff --git a/utils/generateChatGpt.ts b/utils/generateChatGpt.ts new file mode 100644 index 0000000..341f1ff --- /dev/null +++ b/utils/generateChatGpt.ts @@ -0,0 +1,41 @@ +import { ChatCompletionRequestMessage, OpenAI } from "openai-streams" +import { yieldStream } from "yield-stream" + +export const generateChatGpt = async ( + messages: ChatCompletionRequestMessage[] +) => { + const DECODER = new TextDecoder() + let text = "" + + try { + const stream = await OpenAI( + "chat", + { + model: "gpt-3.5-turbo", + temperature: 0.7, + messages + }, + { apiKey: process.env.OPENAI_API_KEY } + ) + + for await (const chunk of yieldStream(stream)) { + try { + const decoded: string = DECODER.decode(chunk) + + if (decoded === undefined) + throw new Error( + "No choices in response. Decoded response: " + + JSON.stringify(decoded) + ) + + text += decoded + } catch (err) { + console.error(err) + } + } + } catch (err) { + console.error(err) + } + + return text +} diff --git a/utils/getCodeDiff.ts b/utils/getCodeDiff.ts new file mode 100644 index 0000000..25e1c4f --- /dev/null +++ b/utils/getCodeDiff.ts @@ -0,0 +1,32 @@ +import { Octokit } from "@octokit/rest" +import { joinStringsUntilMaxLength } from "../lib/joinStringsUntilMaxLength" +import { parseDiff } from "./parseDiff" + +const maxChanges = 1000 +const maxCodeDiff = 10000 + +export const getCodeDiff = async ( + owner: string, + repo: string, + pull_number: number, + octokit: Octokit +) => { + const compareResponse = await octokit.pulls.get({ + owner, + repo, + pull_number, + mediaType: { + format: "diff" + } + }) + + const diffContent = String(compareResponse.data) + + // Parses the diff content and returns the parsed files. + // If the number of changes in a file is greater than 1k changes, the file will be skipped. + // The codeDiff is the joined string of parsed files, up to a max length of 10k. + const { parsedFiles, skippedFiles } = parseDiff(diffContent, maxChanges) + const codeDiff = joinStringsUntilMaxLength(parsedFiles, maxCodeDiff) + + return { codeDiff, skippedFiles } +} diff --git a/utils/github/testPayloadSyncPr.ts b/utils/github/testPayloadSyncPr.ts index 12f302c..00ca963 100644 --- a/utils/github/testPayloadSyncPr.ts +++ b/utils/github/testPayloadSyncPr.ts @@ -4,6 +4,7 @@ export const testPayloadSyncPr = { installation: { id: 35293807 }, action: "synchronize", pull_request: { + diff_url: "https://github.com/decentralizedlabs/pr-codex/pull/4.diff", number: 4, // body: null, // body: "\n\n\n\n## PR-Codex overview\nThis PR adds a new feature to the project: a GitHub app that explains and summarizes PR code diffs. It includes a new `github/route.ts` file and updates several existing files, including `README.md`, `Homepage.tsx`, `DefaultHead.tsx`, `AppLayout.tsx`, `Footer.tsx`, and `Navbar.tsx`.\n\n> The following files were skipped due to too many changes: `package-lock.json`.\n\n", @@ -14,9 +15,7 @@ export const testPayloadSyncPr = { // "\n\n---\n\n## PR-Codex overview\nThis PR adds a new file `route.ts` and makes changes to several files. It adds a homepage with a button to install the app, changes the og_image and twitter_card image formats to png, removes a navbar, adds a `Made by dlabs` line in the footer, and removes the custom connect button and signed block from the navbar.\n\n" + // endDescription, base: { - repo: { owner: { login: "decentralizedlabs" }, name: "pr-codex" }, - sha: "99dfdd96142d171546b59504fbba84cffe23ebd2" - }, - head: { sha: "51cfb7599b7eea7fd74ec5a0cc4c8c5c77150404" } + repo: { owner: { login: "decentralizedlabs" }, name: "pr-codex" } + } } } diff --git a/utils/parseDiff.ts b/utils/parseDiff.ts index 9dc226a..317a47c 100644 --- a/utils/parseDiff.ts +++ b/utils/parseDiff.ts @@ -14,7 +14,7 @@ export function parseDiff(diff: string, maxChanges: number): FileChange { if (changes <= maxChanges) { return file } - skippedFiles.push(filepath.slice(2)) + skippedFiles.push(`\`${filepath.slice(2)}\``) }) return { parsedFiles, skippedFiles } } From f96bb0b89d8cc39ff0cdd82a9a4b7a92242ad3c6 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 19:00:02 +0200 Subject: [PATCH 07/13] fix: comments --- utils/getCodeDiff.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/getCodeDiff.ts b/utils/getCodeDiff.ts index 25e1c4f..cacbf02 100644 --- a/utils/getCodeDiff.ts +++ b/utils/getCodeDiff.ts @@ -23,8 +23,8 @@ export const getCodeDiff = async ( const diffContent = String(compareResponse.data) // Parses the diff content and returns the parsed files. - // If the number of changes in a file is greater than 1k changes, the file will be skipped. - // The codeDiff is the joined string of parsed files, up to a max length of 10k. + // If the number of changes in a file is greater than `maxChanges` changes, the file will be skipped. + // The codeDiff is the joined string of parsed files, up to a max length of `maxCodeDiff`. const { parsedFiles, skippedFiles } = parseDiff(diffContent, maxChanges) const codeDiff = joinStringsUntilMaxLength(parsedFiles, maxCodeDiff) From 6031ad8c65bad043d3c970d62469abe8454e1fae Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 19:01:05 +0200 Subject: [PATCH 08/13] fix: rename --- lib/summarizePullRequest.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/summarizePullRequest.ts b/lib/summarizePullRequest.ts index 003e168..679a612 100644 --- a/lib/summarizePullRequest.ts +++ b/lib/summarizePullRequest.ts @@ -11,17 +11,17 @@ const systemPrompt = export async function summarizePullRequest(payload: any, octokit: Octokit) { // Get relevant PR information const pr = payload.pull_request - const { owner, repo, number } = { + const { owner, repo, pull_number } = { owner: pr.base.repo.owner.login, repo: pr.base.repo.name, - number: pr.number + pull_number: pr.number } // Get the diff content using Octokit and GitHub API const { codeDiff, skippedFiles } = await getCodeDiff( owner, repo, - number, + pull_number, octokit ) @@ -66,7 +66,7 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { await octokit.issues.update({ owner, repo, - issue_number: number, + issue_number: pull_number, body: description }) From 1d1832acaba8ae31587d4c3da3f1f7524bd53836 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 19:20:28 +0200 Subject: [PATCH 09/13] refactor: remove unneeded data from testPayloadComment --- utils/github/testPayloadComment.ts | 301 +---------------------------- 1 file changed, 8 insertions(+), 293 deletions(-) diff --git a/utils/github/testPayloadComment.ts b/utils/github/testPayloadComment.ts index e10af5a..32f0225 100644 --- a/utils/github/testPayloadComment.ts +++ b/utils/github/testPayloadComment.ts @@ -2,304 +2,19 @@ export const testPayloadComment = { installation: { id: 35293807 }, action: "created", issue: { - url: "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4", - repository_url: "https://api.github.com/repos/decentralizedlabs/pr-codex", - labels_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4/labels{/name}", - comments_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4/comments", - events_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4/events", - html_url: "https://github.com/decentralizedlabs/pr-codex/pull/4", - id: 1627438684, - node_id: "PR_kwDOJKDDks5MNS9d", - number: 4, - title: "PR-Codex init", - user: { - login: "jjranalli", - id: 39241410, - node_id: "MDQ6VXNlcjM5MjQxNDEw", - avatar_url: "https://avatars.githubusercontent.com/u/39241410?v=4", - gravatar_id: "", - url: "https://api.github.com/users/jjranalli", - html_url: "https://github.com/jjranalli", - followers_url: "https://api.github.com/users/jjranalli/followers", - following_url: - "https://api.github.com/users/jjranalli/following{/other_user}", - gists_url: "https://api.github.com/users/jjranalli/gists{/gist_id}", - starred_url: - "https://api.github.com/users/jjranalli/starred{/owner}{/repo}", - subscriptions_url: "https://api.github.com/users/jjranalli/subscriptions", - organizations_url: "https://api.github.com/users/jjranalli/orgs", - repos_url: "https://api.github.com/users/jjranalli/repos", - events_url: "https://api.github.com/users/jjranalli/events{/privacy}", - received_events_url: - "https://api.github.com/users/jjranalli/received_events", - type: "User", - site_admin: false - }, - labels: [], - state: "open", - locked: false, - assignee: null, - assignees: [], - milestone: null, - comments: 2, - created_at: "2023-03-16T13:06:36Z", - updated_at: "2023-04-16T15:36:16Z", - closed_at: null, - author_association: "CONTRIBUTOR", - active_lock_reason: null, - draft: false, - pull_request: { - url: "https://api.github.com/repos/decentralizedlabs/pr-codex/pulls/4", - html_url: "https://github.com/decentralizedlabs/pr-codex/pull/4", - diff_url: "https://github.com/decentralizedlabs/pr-codex/pull/4.diff", - patch_url: "https://github.com/decentralizedlabs/pr-codex/pull/4.patch", - merged_at: null - }, - body: "\n\nthis is a test\n\n\n\n\n\n---\n\n## PR-Codex overview\nThis PR adds a new file `route.ts` for handling pull request events on Github, updates the `README.md` file with a test comment, and makes various changes to the UI. The `package-lock.json` file was skipped due to too many changes.\n\n> The following files were skipped due to too many changes: package-lock.json.\n\n", - reactions: { - url: "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4/reactions", - total_count: 0, - "+1": 0, - "-1": 0, - laugh: 0, - hooray: 0, - confused: 0, - heart: 0, - rocket: 0, - eyes: 0 - }, - timeline_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4/timeline", - performed_via_github_app: null, - state_reason: null + number: 4 }, + comment: { - url: "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/comments/1510415128", - html_url: - "https://github.com/decentralizedlabs/pr-codex/pull/4#issuecomment-1510415128", - issue_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/4", - id: 1510415128, - node_id: "IC_kwDOJKDDks5aBxsY", - user: { - login: "jjranalli", - id: 39241410, - node_id: "MDQ6VXNlcjM5MjQxNDEw", - avatar_url: "https://avatars.githubusercontent.com/u/39241410?v=4", - gravatar_id: "", - url: "https://api.github.com/users/jjranalli", - html_url: "https://github.com/jjranalli", - followers_url: "https://api.github.com/users/jjranalli/followers", - following_url: - "https://api.github.com/users/jjranalli/following{/other_user}", - gists_url: "https://api.github.com/users/jjranalli/gists{/gist_id}", - starred_url: - "https://api.github.com/users/jjranalli/starred{/owner}{/repo}", - subscriptions_url: "https://api.github.com/users/jjranalli/subscriptions", - organizations_url: "https://api.github.com/users/jjranalli/orgs", - repos_url: "https://api.github.com/users/jjranalli/repos", - events_url: "https://api.github.com/users/jjranalli/events{/privacy}", - received_events_url: - "https://api.github.com/users/jjranalli/received_events", - type: "User", - site_admin: false - }, - created_at: "2023-04-16T15:36:15Z", - updated_at: "2023-04-16T15:36:15Z", - author_association: "CONTRIBUTOR", - body: "/codex-ask test", - reactions: { - url: "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/comments/1510415128/reactions", - total_count: 0, - "+1": 0, - "-1": 0, - laugh: 0, - hooray: 0, - confused: 0, - heart: 0, - rocket: 0, - eyes: 0 - }, - performed_via_github_app: null + body: "/codex-ask Describe the changes in the homepage UI" + }, + sender: { + login: "jjranalli" }, repository: { - id: 614515602, - node_id: "R_kgDOJKDDkg", name: "pr-codex", - full_name: "decentralizedlabs/pr-codex", - private: false, owner: { - login: "decentralizedlabs", - id: 119909906, - node_id: "O_kgDOByWuEg", - avatar_url: "https://avatars.githubusercontent.com/u/119909906?v=4", - gravatar_id: "", - url: "https://api.github.com/users/decentralizedlabs", - html_url: "https://github.com/decentralizedlabs", - followers_url: "https://api.github.com/users/decentralizedlabs/followers", - following_url: - "https://api.github.com/users/decentralizedlabs/following{/other_user}", - gists_url: - "https://api.github.com/users/decentralizedlabs/gists{/gist_id}", - starred_url: - "https://api.github.com/users/decentralizedlabs/starred{/owner}{/repo}", - subscriptions_url: - "https://api.github.com/users/decentralizedlabs/subscriptions", - organizations_url: "https://api.github.com/users/decentralizedlabs/orgs", - repos_url: "https://api.github.com/users/decentralizedlabs/repos", - events_url: - "https://api.github.com/users/decentralizedlabs/events{/privacy}", - received_events_url: - "https://api.github.com/users/decentralizedlabs/received_events", - type: "Organization", - site_admin: false - }, - html_url: "https://github.com/decentralizedlabs/pr-codex", - description: "A ChatGPT-powered bot to summarize PR code diffs", - fork: false, - url: "https://api.github.com/repos/decentralizedlabs/pr-codex", - forks_url: "https://api.github.com/repos/decentralizedlabs/pr-codex/forks", - keys_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/keys{/key_id}", - collaborators_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/collaborators{/collaborator}", - teams_url: "https://api.github.com/repos/decentralizedlabs/pr-codex/teams", - hooks_url: "https://api.github.com/repos/decentralizedlabs/pr-codex/hooks", - issue_events_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/events{/number}", - events_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/events", - assignees_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/assignees{/user}", - branches_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/branches{/branch}", - tags_url: "https://api.github.com/repos/decentralizedlabs/pr-codex/tags", - blobs_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/git/blobs{/sha}", - git_tags_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/git/tags{/sha}", - git_refs_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/git/refs{/sha}", - trees_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/git/trees{/sha}", - statuses_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/statuses/{sha}", - languages_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/languages", - stargazers_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/stargazers", - contributors_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/contributors", - subscribers_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/subscribers", - subscription_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/subscription", - commits_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/commits{/sha}", - git_commits_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/git/commits{/sha}", - comments_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/comments{/number}", - issue_comment_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/issues/comments{/number}", - contents_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/contents/{+path}", - compare_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/compare/{base}...{head}", - merges_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/merges", - archive_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/{archive_format}{/ref}", - downloads_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/downloads", - issues_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/issues{/number}", - pulls_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/pulls{/number}", - milestones_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/milestones{/number}", - notifications_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/notifications{?since,all,participating}", - labels_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/labels{/name}", - releases_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/releases{/id}", - deployments_url: - "https://api.github.com/repos/decentralizedlabs/pr-codex/deployments", - created_at: "2023-03-15T18:38:11Z", - updated_at: "2023-04-11T13:51:42Z", - pushed_at: "2023-04-16T12:54:04Z", - git_url: "git://github.com/decentralizedlabs/pr-codex.git", - ssh_url: "git@github.com:decentralizedlabs/pr-codex.git", - clone_url: "https://github.com/decentralizedlabs/pr-codex.git", - svn_url: "https://github.com/decentralizedlabs/pr-codex", - homepage: "https://codex.dlabs.app", - size: 17593, - stargazers_count: 44, - watchers_count: 44, - language: "TypeScript", - has_issues: true, - has_projects: true, - has_downloads: true, - has_wiki: true, - has_pages: false, - has_discussions: false, - forks_count: 1, - mirror_url: null, - archived: false, - disabled: false, - open_issues_count: 1, - license: null, - allow_forking: true, - is_template: false, - web_commit_signoff_required: false, - topics: ["chatgpt", "github", "github-app", "pull-requests"], - visibility: "public", - forks: 1, - open_issues: 1, - watchers: 44, - default_branch: "master" - }, - organization: { - login: "decentralizedlabs", - id: 119909906, - node_id: "O_kgDOByWuEg", - url: "https://api.github.com/orgs/decentralizedlabs", - repos_url: "https://api.github.com/orgs/decentralizedlabs/repos", - events_url: "https://api.github.com/orgs/decentralizedlabs/events", - hooks_url: "https://api.github.com/orgs/decentralizedlabs/hooks", - issues_url: "https://api.github.com/orgs/decentralizedlabs/issues", - members_url: - "https://api.github.com/orgs/decentralizedlabs/members{/member}", - public_members_url: - "https://api.github.com/orgs/decentralizedlabs/public_members{/member}", - avatar_url: "https://avatars.githubusercontent.com/u/119909906?v=4", - description: "" - }, - sender: { - login: "jjranalli", - id: 39241410, - node_id: "MDQ6VXNlcjM5MjQxNDEw", - avatar_url: "https://avatars.githubusercontent.com/u/39241410?v=4", - gravatar_id: "", - url: "https://api.github.com/users/jjranalli", - html_url: "https://github.com/jjranalli", - followers_url: "https://api.github.com/users/jjranalli/followers", - following_url: - "https://api.github.com/users/jjranalli/following{/other_user}", - gists_url: "https://api.github.com/users/jjranalli/gists{/gist_id}", - starred_url: - "https://api.github.com/users/jjranalli/starred{/owner}{/repo}", - subscriptions_url: "https://api.github.com/users/jjranalli/subscriptions", - organizations_url: "https://api.github.com/users/jjranalli/orgs", - repos_url: "https://api.github.com/users/jjranalli/repos", - events_url: "https://api.github.com/users/jjranalli/events{/privacy}", - received_events_url: - "https://api.github.com/users/jjranalli/received_events", - type: "User", - site_admin: false + login: "decentralizedlabs" + } } } From 18845242d0d6abf01acb8c2b0f1ca50781c152b9 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 19:20:35 +0200 Subject: [PATCH 10/13] feat: add replyIssueComment --- app/github/route.ts | 9 +++++++ lib/replyIssueComment.ts | 55 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 lib/replyIssueComment.ts diff --git a/app/github/route.ts b/app/github/route.ts index ccb20b2..96992b4 100644 --- a/app/github/route.ts +++ b/app/github/route.ts @@ -1,4 +1,5 @@ import { handleGithubAuth } from "@lib/handleGithubAuth" +import { replyIssueComment } from "@lib/replyIssueComment" import { summarizePullRequest } from "@lib/summarizePullRequest" import { NextRequest, NextResponse } from "next/server" @@ -9,9 +10,17 @@ export async function POST(req: NextRequest) { try { if (payload.action == "opened" || payload.action == "synchronize") { + // If a PR is opened or updated, summarize it const octokit = await handleGithubAuth(payload) await summarizePullRequest(payload, octokit) + } else if (payload.action == "created") { + if (payload.comment.body.includes("/codex-ask")) { + // If a comment is created, reply to it + const octokit = await handleGithubAuth(payload) + + await replyIssueComment(payload, octokit) + } } return NextResponse.json("ok") diff --git a/lib/replyIssueComment.ts b/lib/replyIssueComment.ts new file mode 100644 index 0000000..7b83e5c --- /dev/null +++ b/lib/replyIssueComment.ts @@ -0,0 +1,55 @@ +import { Octokit } from "@octokit/rest" +import { ChatCompletionRequestMessage } from "openai-streams" +import { generateChatGpt } from "../utils/generateChatGpt" +import { getCodeDiff } from "../utils/getCodeDiff" + +export const startDescription = "\n\n" +export const endDescription = "" +const systemPrompt = + "You are a Git diff assistant. Given a code diff, you answer any question related to it. Be concise. Always wrap file names, functions, objects and similar in backticks (`)." + +export async function replyIssueComment(payload: any, octokit: Octokit) { + // Get relevant PR information + const { repository, issue, sender, comment } = payload + + const question = comment.body.split("/codex-ask")[1].trim() + + if (question) { + const { owner, repo, issue_number } = { + owner: repository.owner.login, + repo: repository.name, + issue_number: issue.number + } + + // Get the diff content using Octokit and GitHub API + const { codeDiff } = await getCodeDiff(owner, repo, issue_number, octokit) + + // If there are changes, trigger workflow + if (codeDiff?.length != 0) { + const messages: ChatCompletionRequestMessage[] = [ + { + role: "system", + content: systemPrompt + }, + { + role: "user", + content: `${question}\n\nHere is the code diff:\n\n${codeDiff}` + } + ] + + const codexResponse = await generateChatGpt(messages) + + const description = `> ${question}\n\n@${sender.login} ${codexResponse}` + + await octokit.issues.createComment({ + owner, + repo, + issue_number, + body: description + }) + + return codexResponse + } + throw new Error("No changes in PR") + } +} From fbfc6e8e470d11810c605cbcc0ca33001b5ed323 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 19:28:21 +0200 Subject: [PATCH 11/13] nit: change codex-ask to ask-codex --- app/github/route.ts | 2 +- lib/replyIssueComment.ts | 2 +- utils/github/testPayloadComment.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/github/route.ts b/app/github/route.ts index 96992b4..8af196b 100644 --- a/app/github/route.ts +++ b/app/github/route.ts @@ -15,7 +15,7 @@ export async function POST(req: NextRequest) { await summarizePullRequest(payload, octokit) } else if (payload.action == "created") { - if (payload.comment.body.includes("/codex-ask")) { + if (payload.comment.body.includes("/ask-codex")) { // If a comment is created, reply to it const octokit = await handleGithubAuth(payload) diff --git a/lib/replyIssueComment.ts b/lib/replyIssueComment.ts index 7b83e5c..07386ad 100644 --- a/lib/replyIssueComment.ts +++ b/lib/replyIssueComment.ts @@ -12,7 +12,7 @@ export async function replyIssueComment(payload: any, octokit: Octokit) { // Get relevant PR information const { repository, issue, sender, comment } = payload - const question = comment.body.split("/codex-ask")[1].trim() + const question = comment.body.split("/ask-codex")[1].trim() if (question) { const { owner, repo, issue_number } = { diff --git a/utils/github/testPayloadComment.ts b/utils/github/testPayloadComment.ts index 32f0225..8adf62c 100644 --- a/utils/github/testPayloadComment.ts +++ b/utils/github/testPayloadComment.ts @@ -6,7 +6,7 @@ export const testPayloadComment = { }, comment: { - body: "/codex-ask Describe the changes in the homepage UI" + body: "/ask-codex Describe the changes in the homepage UI" }, sender: { login: "jjranalli" From 746e58415f882ccf09efae3e49f378315eafb17b Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 21:52:31 +0200 Subject: [PATCH 12/13] fix: optimize diff parse --- lib/joinStringsUntilMaxLength.ts | 12 +++++++----- utils/getCodeDiff.ts | 10 +++++++--- utils/parseDiff.ts | 19 ++++++++++++++++--- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/lib/joinStringsUntilMaxLength.ts b/lib/joinStringsUntilMaxLength.ts index d96277b..3206b06 100644 --- a/lib/joinStringsUntilMaxLength.ts +++ b/lib/joinStringsUntilMaxLength.ts @@ -1,22 +1,24 @@ export function joinStringsUntilMaxLength( parsedFiles: string[], maxLength: number -): string { - let combinedString = "" +) { + let codeDiff = "" let currentLength = 0 + let maxLengthExceeded = false for (const file of parsedFiles) { const fileLength = file.length if (currentLength + fileLength <= maxLength) { - combinedString += file + codeDiff += file currentLength += fileLength } else { + maxLengthExceeded = true const remainingLength = maxLength - currentLength - combinedString += file.slice(0, remainingLength) + codeDiff += file.slice(0, remainingLength) break } } - return combinedString + return { codeDiff, maxLengthExceeded } } diff --git a/utils/getCodeDiff.ts b/utils/getCodeDiff.ts index cacbf02..74396d7 100644 --- a/utils/getCodeDiff.ts +++ b/utils/getCodeDiff.ts @@ -3,7 +3,7 @@ import { joinStringsUntilMaxLength } from "../lib/joinStringsUntilMaxLength" import { parseDiff } from "./parseDiff" const maxChanges = 1000 -const maxCodeDiff = 10000 +const maxCodeDiff = 11500 export const getCodeDiff = async ( owner: string, @@ -26,7 +26,11 @@ export const getCodeDiff = async ( // If the number of changes in a file is greater than `maxChanges` changes, the file will be skipped. // The codeDiff is the joined string of parsed files, up to a max length of `maxCodeDiff`. const { parsedFiles, skippedFiles } = parseDiff(diffContent, maxChanges) - const codeDiff = joinStringsUntilMaxLength(parsedFiles, maxCodeDiff) - return { codeDiff, skippedFiles } + const { codeDiff, maxLengthExceeded } = joinStringsUntilMaxLength( + parsedFiles, + maxCodeDiff + ) + + return { codeDiff, skippedFiles, maxLengthExceeded } } diff --git a/utils/parseDiff.ts b/utils/parseDiff.ts index 317a47c..1a011a7 100644 --- a/utils/parseDiff.ts +++ b/utils/parseDiff.ts @@ -3,18 +3,31 @@ type FileChange = { parsedFiles: string[]; skippedFiles: string[] } export function parseDiff(diff: string, maxChanges: number): FileChange { let skippedFiles: string[] = [] const files = diff.split(/diff --git /).slice(1) - const parsedFiles = files.map((file) => { + + const parsedFiles = files.flatMap((file) => { const lines = file.split("\n") + const filepath = lines[0].split(" ")[1] - const mainContent = lines.slice(4) + + // Don't consider diff in deleted files + if (lines[1].startsWith("deleted")) return `deleted ${filepath}` + + const mainContent = lines.slice(6).map((line) => { + if (line.startsWith("+") || line.startsWith("-")) { + const trimContent = line.slice(1).trim() + return line[0] + trimContent + } else return line.trim() + }) const changes = mainContent.filter( (line) => line.startsWith("+") || line.startsWith("-") ).length if (changes <= maxChanges) { - return file + return `${filepath}\n${mainContent.join("\n")}` } skippedFiles.push(`\`${filepath.slice(2)}\``) + return [] }) + return { parsedFiles, skippedFiles } } From cde80595310f0bc4259e64f68597a25d7a4f2881 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 21:57:10 +0200 Subject: [PATCH 13/13] fix: prompts and logic --- lib/replyIssueComment.ts | 4 ++-- lib/summarizePullRequest.ts | 13 +++++++++---- utils/github/testPayloadComment.ts | 2 +- utils/github/testPayloadSyncPr.ts | 4 ++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/replyIssueComment.ts b/lib/replyIssueComment.ts index 07386ad..f4ca8cd 100644 --- a/lib/replyIssueComment.ts +++ b/lib/replyIssueComment.ts @@ -29,11 +29,11 @@ export async function replyIssueComment(payload: any, octokit: Octokit) { const messages: ChatCompletionRequestMessage[] = [ { role: "system", - content: systemPrompt + content: `${systemPrompt}\n\nHere is the code diff:\n\n${codeDiff}` }, { role: "user", - content: `${question}\n\nHere is the code diff:\n\n${codeDiff}` + content: `${question}` } ] diff --git a/lib/summarizePullRequest.ts b/lib/summarizePullRequest.ts index 679a612..43b7d9f 100644 --- a/lib/summarizePullRequest.ts +++ b/lib/summarizePullRequest.ts @@ -6,7 +6,7 @@ import { getCodeDiff } from "../utils/getCodeDiff" export const startDescription = "\n\n" export const endDescription = "" const systemPrompt = - 'You are a Git diff assistant. Always begin with "This PR". Given a code diff, you provide a simple description in prose, in less than 300 chars, which sums up the changes. Continue with "\n\n### Detailed summary\n" and make a comprehensive list of all changes, excluding any eventual skipped files. Be concise. Always wrap file names, functions, objects and similar in backticks (`).' + "You are a Git diff assistant. Given a code diff, you provide a clear and concise description of its content. Always wrap file names, functions, objects and similar in backticks (`)." export async function summarizePullRequest(payload: any, octokit: Octokit) { // Get relevant PR information @@ -18,7 +18,7 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { } // Get the diff content using Octokit and GitHub API - const { codeDiff, skippedFiles } = await getCodeDiff( + const { codeDiff, skippedFiles, maxLengthExceeded } = await getCodeDiff( owner, repo, pull_number, @@ -30,11 +30,12 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { const messages: ChatCompletionRequestMessage[] = [ { role: "system", - content: systemPrompt + content: `${systemPrompt}\n\nHere is the code diff:\n\n${codeDiff}` }, { role: "user", - content: `Here is the code diff:\n\n${codeDiff}` + content: + 'Starting with "This PR", clearly explain the focus of this PR in prose, in less than 300 characters. Then follow up with "\n\n### Detailed summary\n" and make a comprehensive list of all changes.' } ] @@ -55,6 +56,10 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { ", " )}` : "" + }${ + maxLengthExceeded + ? "\n\n> The code diff exceeds the max number of characters, so this overview may be incomplete." + : "" }\n\n${endDescription}` const description = hasCodexCommented diff --git a/utils/github/testPayloadComment.ts b/utils/github/testPayloadComment.ts index 8adf62c..0dfadb7 100644 --- a/utils/github/testPayloadComment.ts +++ b/utils/github/testPayloadComment.ts @@ -6,7 +6,7 @@ export const testPayloadComment = { }, comment: { - body: "/ask-codex Describe the changes in the homepage UI" + body: "/ask-codex what changes have been done in the homepage?" }, sender: { login: "jjranalli" diff --git a/utils/github/testPayloadSyncPr.ts b/utils/github/testPayloadSyncPr.ts index 00ca963..f1512cc 100644 --- a/utils/github/testPayloadSyncPr.ts +++ b/utils/github/testPayloadSyncPr.ts @@ -6,9 +6,9 @@ export const testPayloadSyncPr = { pull_request: { diff_url: "https://github.com/decentralizedlabs/pr-codex/pull/4.diff", number: 4, - // body: null, + body: null, // body: "\n\n\n\n## PR-Codex overview\nThis PR adds a new feature to the project: a GitHub app that explains and summarizes PR code diffs. It includes a new `github/route.ts` file and updates several existing files, including `README.md`, `Homepage.tsx`, `DefaultHead.tsx`, `AppLayout.tsx`, `Footer.tsx`, and `Navbar.tsx`.\n\n> The following files were skipped due to too many changes: `package-lock.json`.\n\n", - body: "\n\nthis is a test", + // body: "\n\nthis is a test", // body: // "\n\nthis is a test" + // startDescription +