From 47ca0a18b9f66ca135ef05f3c3c3540fa07df945 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Thu, 18 May 2023 23:21:58 +0530 Subject: [PATCH 01/12] feat: generate code suggestions via AI --- src/constants.ts | 2 + .../AICodeRefactor/ChangeSuggestorButton.ts | 75 +++++++++++++++++++ src/content-scripts/github.ts | 6 +- src/utils/aiprdescription/openai.ts | 53 ++++++++++++- src/utils/dom-utils/changeSuggestorButton.ts | 19 +++++ .../{prEditWatcher.ts => prWatcher.ts} | 13 ++++ src/utils/urlMatchers.ts | 17 +++-- 7 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts create mode 100644 src/utils/dom-utils/changeSuggestorButton.ts rename src/utils/dom-utils/{prEditWatcher.ts => prWatcher.ts} (50%) diff --git a/src/constants.ts b/src/constants.ts index 8fa3bc0f..671b31d5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -20,6 +20,8 @@ export const GITHUB_LOGGED_IN_USER_USERNAME_SELECTOR = "meta[name=\"user-login\" export const GITHUB_PR_COMMENT_HEADER_SELECTOR = "timeline-comment-header clearfix d-flex"; export const GITHUB_NEW_PR_COMMENT_EDITOR_SELECTOR = "flex-nowrap d-none d-md-inline-block mr-md-0 mr-3"; export const GITHUB_PR_COMMENT_EDITOR_SELECTOR = "flex-nowrap d-inline-block mr-3"; +export const GITHUB_REVIEW_SUGGESTION_SELECTOR = "js-suggestion-button-placeholder"; export const GITHUB_REPO_ACTIONS_SELECTOR = ".pagehead-actions"; export const GITHUB_PR_COMMENT_TEXT_AREA_SELECTOR = "pull_request[body]"; +export const GITHUB_PR_SUGGESTION_TEXT_AREA_SELECTOR = "comment[body]"; export const GITHUB_PR_BASE_BRANCH_SELECTOR = "css-truncate css-truncate-target"; diff --git a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts new file mode 100644 index 00000000..28389c16 --- /dev/null +++ b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts @@ -0,0 +1,75 @@ +import { createHtmlElement } from "../../../utils/createHtmlElement"; +import openSaucedLogoIcon from "../../../assets/opensauced-icon.svg"; +import { GITHUB_PR_SUGGESTION_TEXT_AREA_SELECTOR, OPEN_AI_COMPLETION_MODEL_NAME, SUPABASE_LOGIN_URL } from "../../../constants"; +import { generateCodeSuggestion } from "../../../utils/aiprdescription/openai"; +import { isContextWithinBounds } from "../../../utils/fetchGithubAPIData"; +import { insertAtCursorFromStream } from "../../../utils/aiprdescription/cursorPositionInsert"; +import { getAIDescriptionConfig } from "../../../utils/aiprdescription/descriptionconfig"; +import { isLoggedIn } from "../../../utils/checkAuthentication"; + +export const ChangeSuggestorButton = () => { + const changeSuggestoreButton = createHtmlElement("a", { + innerHTML: ` + + + Get Refactor Suggestions`, + onclick: handleSubmit, + }); + + return changeSuggestoreButton; + }; + +const handleSubmit = async () => { + try { + if (!(await isLoggedIn())) { + return window.open(SUPABASE_LOGIN_URL, "_blank"); + } + const logo = document.getElementById("ai-description-button-logo"); + + if (!logo) { + return; + } + + const descriptionConfig = await getAIDescriptionConfig(); + + if (!descriptionConfig) { + return; + } + if (!descriptionConfig.enabled) { + return alert("AI PR description is disabled!"); + } + + logo.classList.toggle("animate-spin"); + + const selectedLines = document.querySelectorAll(".code-review.selected-line"); + const selectedCode = Array.from(selectedLines).map(line => line.textContent) + .join("\n"); + + if (!isContextWithinBounds([selectedCode, [] ], descriptionConfig.config.maxInputLength)) { + logo.classList.toggle("animate-spin"); + return alert(`Max input length exceeded. Try reducing the number of selected lines to refactor.`); + } + const suggestionStream = await generateCodeSuggestion( + descriptionConfig.config.openai_api_key, + OPEN_AI_COMPLETION_MODEL_NAME, + descriptionConfig.config.language, + descriptionConfig.config.length, + descriptionConfig.config.temperature / 10, + descriptionConfig.config.tone, + selectedCode, + ); + + logo.classList.toggle("animate-spin"); + if (!suggestionStream) { + return console.error("No description was generated!"); + } + const textArea = document.getElementsByName(GITHUB_PR_SUGGESTION_TEXT_AREA_SELECTOR)[0] as HTMLTextAreaElement; + + void insertAtCursorFromStream(textArea, suggestionStream); + } catch (error: unknown) { + if (error instanceof Error) { + console.error("Description generation error:", error.message); + } + } + }; + diff --git a/src/content-scripts/github.ts b/src/content-scripts/github.ts index a54cc7bc..95c1dc9f 100644 --- a/src/content-scripts/github.ts +++ b/src/content-scripts/github.ts @@ -4,6 +4,7 @@ import { isGithubPullRequestPage, isGithubRepoPage, isPullRequestCreatePage, + isPullRequestFilesChangedPage, } from "../utils/urlMatchers"; import { isOpenSaucedUser } from "../utils/fetchOpenSaucedApiData"; import injectViewOnOpenSauced from "../utils/dom-utils/viewOnOpenSauced"; @@ -13,7 +14,8 @@ import injectAddPRToHighlightsButton from "../utils/dom-utils/addPRToHighlights" import injectRepoVotingButtons from "../utils/dom-utils/repoVotingButtons"; import domUpdateWatch from "../utils/dom-utils/domUpdateWatcher"; import injectDescriptionGeneratorButton from "../utils/dom-utils/addDescriptionGenerator"; -import prEditWatch from "../utils/dom-utils/prEditWatcher"; +import injectChangeSuggestorButton from "../utils/dom-utils/changeSuggestorButton"; +import prEditWatch, { prReviewWatch } from "../utils/dom-utils/prWatcher"; const processGithubPage = async () => { if (prefersDarkMode(document.cookie)) { @@ -21,6 +23,8 @@ const processGithubPage = async () => { } if (isPullRequestCreatePage(window.location.href)) { void injectDescriptionGeneratorButton(); + } else if (isPullRequestFilesChangedPage(window.location.href)) { + prReviewWatch(injectChangeSuggestorButton); } else if (isGithubPullRequestPage(window.location.href)) { prEditWatch(injectDescriptionGeneratorButton); void injectAddPRToHighlightsButton(); diff --git a/src/utils/aiprdescription/openai.ts b/src/utils/aiprdescription/openai.ts index 344cbe79..0c1508d5 100644 --- a/src/utils/aiprdescription/openai.ts +++ b/src/utils/aiprdescription/openai.ts @@ -1,7 +1,7 @@ import type { DescriptionTone } from "./descriptionconfig"; import { OpenAI, CreateChatCompletionRequest } from "openai-streams"; -const generatePrompt = ( +const generatePRDescriptionPrompt = ( locale: string, maxLength: number, tone: DescriptionTone, @@ -12,6 +12,16 @@ const generatePrompt = ( "Exclude anything unnecessary such as translation. Your entire response will be passed directly into a pull request description", ].join("\n"); +const generateCodeSuggestionPrompt = ( + locale: string, + maxLength: number, + tone: DescriptionTone, +) => [ + `Generate a code refactor suggestion for a given code snippet written in ${locale} with the specifications mentioned below`, + `The code snippet must be a maximum of ${maxLength} characters.`, + "Exclude anything unnecessary such as translation. Start with \"```suggestion\" and end with ``` to create a valid GitHub suggestion codeblock.", +].join("\n"); + const createChatCompletion = async ( apiKey: string, json: CreateChatCompletionRequest, @@ -44,7 +54,46 @@ export const generateDescription = async ( messages: [ { role: "system", - content: generatePrompt(locale, maxLength, tone), + content: generatePRDescriptionPrompt(locale, maxLength, tone), + }, + { + role: "user", + content, + }, + ], + temperature, + n: 1, + }, + ); + + return completion; + } catch (error: unknown) { + if (error instanceof Error) { + console.error("OpenAI error: ", error.message); +} + } +}; + +export const generateCodeSuggestion = async ( + apiKey: string, + model: "gpt-3.5-turbo" | "gpt-3.5-turbo-0301", + locale: string, + maxLength: number, + temperature: number, + tone: DescriptionTone, + code: string, +) => { + const content = `Code: ${code}`; + + try { + const completion = await createChatCompletion( + apiKey, + { + model, + messages: [ + { + role: "system", + content: generateCodeSuggestionPrompt(locale, maxLength, tone), }, { role: "user", diff --git a/src/utils/dom-utils/changeSuggestorButton.ts b/src/utils/dom-utils/changeSuggestorButton.ts new file mode 100644 index 00000000..19d05007 --- /dev/null +++ b/src/utils/dom-utils/changeSuggestorButton.ts @@ -0,0 +1,19 @@ +import { ChangeSuggestorButton } from "../../content-scripts/components/AICodeRefactor/ChangeSuggestorButton"; +import { GITHUB_REVIEW_SUGGESTION_SELECTOR } from "../../constants"; +import { isPublicRepository } from "../fetchGithubAPIData"; + +const injectChangeSuggestorButton = async () => { + if (!(await isPublicRepository(window.location.href))) { + return; + } + + const suggestChangesIcon = document.getElementsByClassName(GITHUB_REVIEW_SUGGESTION_SELECTOR)[0]; + const changeSuggestorButton = ChangeSuggestorButton(); + + if (suggestChangesIcon.firstChild?.isEqualNode(changeSuggestorButton)) { + return; + } + suggestChangesIcon.insertBefore(changeSuggestorButton, suggestChangesIcon.firstChild); +}; + +export default injectChangeSuggestorButton; diff --git a/src/utils/dom-utils/prEditWatcher.ts b/src/utils/dom-utils/prWatcher.ts similarity index 50% rename from src/utils/dom-utils/prEditWatcher.ts rename to src/utils/dom-utils/prWatcher.ts index 778c68e6..c0f838aa 100644 --- a/src/utils/dom-utils/prEditWatcher.ts +++ b/src/utils/dom-utils/prWatcher.ts @@ -11,4 +11,17 @@ const prEditWatch = (callback: () => void, delayInMs = 0) => { observer.observe(document.body, { attributes: true, subtree: true }); }; +export const prReviewWatch = (callback: () => void, delayInMs = 0) => { + const observer = new MutationObserver((mutationList: MutationRecord[], observer: MutationObserver) => { + mutationList.forEach(mutation => { + if (Array.from((mutation.target as HTMLElement).classList).includes("js-inline-comment-form-container")) { + setTimeout(callback, delayInMs); + observer.disconnect(); + } + }); + }); + + observer.observe(document.body, { attributes: true, subtree: true }); +}; + export default prEditWatch; diff --git a/src/utils/urlMatchers.ts b/src/utils/urlMatchers.ts index 73adda33..2ecb58e6 100644 --- a/src/utils/urlMatchers.ts +++ b/src/utils/urlMatchers.ts @@ -34,7 +34,6 @@ export const isGithubProfilePage = (url: string) => { return githubProfilePattern.test(url); }; - export const isGithubRepoPage = (url: string) => { const githubRepoPattern = /github\.com\/[^/]+\/[^/]+$/; @@ -47,6 +46,12 @@ export const isPullRequestCreatePage = (url: string) => { return githubPullRequestPattern.test(url); }; +export const isPullRequestFilesChangedPage = (url: string) => { + const githubPullRequestFilesChangedPattern = /github\.com\/[\w.-]+\/[^/]+\/pull\/\d+\/files/; + + return githubPullRequestFilesChangedPattern.test(url); +}; + export const getPullRequestAPIURL = (url: string) => { const apiURL = url.replace(/github\.com/, "api.github.com/repos"); @@ -54,10 +59,10 @@ export const getPullRequestAPIURL = (url: string) => { return apiURL.replace("pull", "pulls"); } - if (url.match(/compare\/.*\.\.\./)) { - return apiURL; -} - const baseBranch = document.getElementsByClassName(GITHUB_PR_BASE_BRANCH_SELECTOR)[1].textContent; + if (url.match(/compare\/.*\.\.\./)) { + return apiURL; + } + const baseBranch = document.getElementsByClassName(GITHUB_PR_BASE_BRANCH_SELECTOR)[1].textContent; - return apiURL.replace(/compare\//, `compare/${baseBranch}...`); + return apiURL.replace(/compare\//, `compare/${baseBranch}...`); }; From 3fcb332b919dc43c796dffbd78434faad0f713d1 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Fri, 19 May 2023 15:14:42 +0530 Subject: [PATCH 02/12] avoid multiple icons --- .../components/AICodeRefactor/ChangeSuggestorButton.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts index 28389c16..9544a339 100644 --- a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts +++ b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts @@ -12,7 +12,7 @@ export const ChangeSuggestorButton = () => { innerHTML: ` - Get Refactor Suggestions`, + Get Refactor Suggestions`, onclick: handleSubmit, }); From 6103242f17499e4363780a867454c9ceaf711f55 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Fri, 19 May 2023 15:36:08 +0530 Subject: [PATCH 03/12] multiple reviews --- src/constants.ts | 2 +- .../AICodeRefactor/ChangeSuggestorButton.ts | 10 +++++----- src/utils/dom-utils/changeSuggestorButton.ts | 7 ++++--- src/utils/dom-utils/prWatcher.ts | 15 ++++++++++----- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 671b31d5..1db769f7 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -23,5 +23,5 @@ export const GITHUB_PR_COMMENT_EDITOR_SELECTOR = "flex-nowrap d-inline-block mr- export const GITHUB_REVIEW_SUGGESTION_SELECTOR = "js-suggestion-button-placeholder"; export const GITHUB_REPO_ACTIONS_SELECTOR = ".pagehead-actions"; export const GITHUB_PR_COMMENT_TEXT_AREA_SELECTOR = "pull_request[body]"; -export const GITHUB_PR_SUGGESTION_TEXT_AREA_SELECTOR = "comment[body]"; +export const GITHUB_PR_SUGGESTION_TEXT_AREA_SELECTOR = "[name='comment[body]']"; export const GITHUB_PR_BASE_BRANCH_SELECTOR = "css-truncate css-truncate-target"; diff --git a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts index 9544a339..19c313c1 100644 --- a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts +++ b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts @@ -7,19 +7,19 @@ import { insertAtCursorFromStream } from "../../../utils/aiprdescription/cursorP import { getAIDescriptionConfig } from "../../../utils/aiprdescription/descriptionconfig"; import { isLoggedIn } from "../../../utils/checkAuthentication"; -export const ChangeSuggestorButton = () => { +export const ChangeSuggestorButton = (commentNode: HTMLElement) => { const changeSuggestoreButton = createHtmlElement("a", { innerHTML: ` Get Refactor Suggestions`, - onclick: handleSubmit, + onclick: async () => handleSubmit(commentNode), }); return changeSuggestoreButton; }; -const handleSubmit = async () => { +const handleSubmit = async (commentNode: HTMLElement) => { try { if (!(await isLoggedIn())) { return window.open(SUPABASE_LOGIN_URL, "_blank"); @@ -63,9 +63,9 @@ const handleSubmit = async () => { if (!suggestionStream) { return console.error("No description was generated!"); } - const textArea = document.getElementsByName(GITHUB_PR_SUGGESTION_TEXT_AREA_SELECTOR)[0] as HTMLTextAreaElement; + const textArea = commentNode.querySelector(GITHUB_PR_SUGGESTION_TEXT_AREA_SELECTOR)!; - void insertAtCursorFromStream(textArea, suggestionStream); + void insertAtCursorFromStream(textArea as HTMLTextAreaElement, suggestionStream); } catch (error: unknown) { if (error instanceof Error) { console.error("Description generation error:", error.message); diff --git a/src/utils/dom-utils/changeSuggestorButton.ts b/src/utils/dom-utils/changeSuggestorButton.ts index 19d05007..d6216a67 100644 --- a/src/utils/dom-utils/changeSuggestorButton.ts +++ b/src/utils/dom-utils/changeSuggestorButton.ts @@ -2,13 +2,14 @@ import { ChangeSuggestorButton } from "../../content-scripts/components/AICodeRe import { GITHUB_REVIEW_SUGGESTION_SELECTOR } from "../../constants"; import { isPublicRepository } from "../fetchGithubAPIData"; -const injectChangeSuggestorButton = async () => { +const injectChangeSuggestorButton = async (commentNode: HTMLElement) => { + console.log(commentNode); if (!(await isPublicRepository(window.location.href))) { return; } - const suggestChangesIcon = document.getElementsByClassName(GITHUB_REVIEW_SUGGESTION_SELECTOR)[0]; - const changeSuggestorButton = ChangeSuggestorButton(); + const suggestChangesIcon = commentNode.getElementsByClassName(GITHUB_REVIEW_SUGGESTION_SELECTOR)[0]; + const changeSuggestorButton = ChangeSuggestorButton(commentNode); if (suggestChangesIcon.firstChild?.isEqualNode(changeSuggestorButton)) { return; diff --git a/src/utils/dom-utils/prWatcher.ts b/src/utils/dom-utils/prWatcher.ts index c0f838aa..3b18fe62 100644 --- a/src/utils/dom-utils/prWatcher.ts +++ b/src/utils/dom-utils/prWatcher.ts @@ -11,17 +11,22 @@ const prEditWatch = (callback: () => void, delayInMs = 0) => { observer.observe(document.body, { attributes: true, subtree: true }); }; -export const prReviewWatch = (callback: () => void, delayInMs = 0) => { +export const prReviewWatch = (callback: (node: HTMLElement) => void, delayInMs = 0) => { const observer = new MutationObserver((mutationList: MutationRecord[], observer: MutationObserver) => { mutationList.forEach(mutation => { - if (Array.from((mutation.target as HTMLElement).classList).includes("js-inline-comment-form-container")) { - setTimeout(callback, delayInMs); - observer.disconnect(); + if (Array.from((mutation.target as HTMLElement).classList).includes("inline-comments")) { + setTimeout(() => { + const commentNodes = document.getElementsByClassName("inline-comments"); + + Array.from(commentNodes).forEach(node => { + callback(node as HTMLElement); + }); + }, delayInMs); } }); }); - observer.observe(document.body, { attributes: true, subtree: true }); + observer.observe(document.body, { attributes: true, subtree: true, childList: true }); }; export default prEditWatch; From f4b33abef70661a7fbf52635090de22be75c9eb8 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Fri, 19 May 2023 15:57:19 +0530 Subject: [PATCH 04/12] refactor a single line of code --- .../components/AICodeRefactor/ChangeSuggestorButton.ts | 8 +++++++- src/utils/dom-utils/changeSuggestorButton.ts | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts index 19c313c1..56000a6e 100644 --- a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts +++ b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts @@ -42,9 +42,15 @@ const handleSubmit = async (commentNode: HTMLElement) => { logo.classList.toggle("animate-spin"); const selectedLines = document.querySelectorAll(".code-review.selected-line"); - const selectedCode = Array.from(selectedLines).map(line => line.textContent) + let selectedCode = Array.from(selectedLines).map(line => line.textContent) .join("\n"); + // find input with name="position" and get its value + if (!selectedCode) { + const position = (commentNode.querySelector("input[name=position]")!).value; + + selectedCode = document.querySelector(`[data-line-number="${position}"]`)?.nextSibling?.nextSibling.getElementsByClassName("blob-code-inner")[0].textContent; + } if (!isContextWithinBounds([selectedCode, [] ], descriptionConfig.config.maxInputLength)) { logo.classList.toggle("animate-spin"); return alert(`Max input length exceeded. Try reducing the number of selected lines to refactor.`); diff --git a/src/utils/dom-utils/changeSuggestorButton.ts b/src/utils/dom-utils/changeSuggestorButton.ts index d6216a67..60aeadbb 100644 --- a/src/utils/dom-utils/changeSuggestorButton.ts +++ b/src/utils/dom-utils/changeSuggestorButton.ts @@ -3,7 +3,6 @@ import { GITHUB_REVIEW_SUGGESTION_SELECTOR } from "../../constants"; import { isPublicRepository } from "../fetchGithubAPIData"; const injectChangeSuggestorButton = async (commentNode: HTMLElement) => { - console.log(commentNode); if (!(await isPublicRepository(window.location.href))) { return; } From 1e5a7959dd5fb29d284dbd90c02393d5e9b99dd6 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Fri, 19 May 2023 19:57:42 +0530 Subject: [PATCH 05/12] extensions suggestions --- .../components/AICodeRefactor/ChangeSuggestorButton.ts | 6 +++--- src/utils/aiprdescription/openai.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts index 56000a6e..1edf9886 100644 --- a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts +++ b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts @@ -8,15 +8,15 @@ import { getAIDescriptionConfig } from "../../../utils/aiprdescription/descripti import { isLoggedIn } from "../../../utils/checkAuthentication"; export const ChangeSuggestorButton = (commentNode: HTMLElement) => { - const changeSuggestoreButton = createHtmlElement("a", { + const changeSuggestorButton = createHtmlElement("a", { innerHTML: ` Get Refactor Suggestions`, - onclick: async () => handleSubmit(commentNode), + onclick: handleSubmit(commentNode), }); - return changeSuggestoreButton; + return changeSuggestorButton; }; const handleSubmit = async (commentNode: HTMLElement) => { diff --git a/src/utils/aiprdescription/openai.ts b/src/utils/aiprdescription/openai.ts index 0c1508d5..c2652821 100644 --- a/src/utils/aiprdescription/openai.ts +++ b/src/utils/aiprdescription/openai.ts @@ -19,7 +19,7 @@ const generateCodeSuggestionPrompt = ( ) => [ `Generate a code refactor suggestion for a given code snippet written in ${locale} with the specifications mentioned below`, `The code snippet must be a maximum of ${maxLength} characters.`, - "Exclude anything unnecessary such as translation. Start with \"```suggestion\" and end with ``` to create a valid GitHub suggestion codeblock.", + "Exclude anything unnecessary such as translation and instructions. The code snippet you suggest should start with \"```suggestion\" and end with ``` to create a valid GitHub suggestion codeblock. All non-code text or description should be outside of the codeblock.", ].join("\n"); const createChatCompletion = async ( From b5e5cf0359170f60393ab12dedece8ca18d29641 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Fri, 19 May 2023 20:00:50 +0530 Subject: [PATCH 06/12] dont blindly believe ai --- .../components/AICodeRefactor/ChangeSuggestorButton.ts | 2 +- src/utils/dom-utils/changeSuggestorButton.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts index 1edf9886..3d32bbf0 100644 --- a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts +++ b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts @@ -13,7 +13,7 @@ export const ChangeSuggestorButton = (commentNode: HTMLElement) => { Get Refactor Suggestions`, - onclick: handleSubmit(commentNode), + onclick: async () => handleSubmit(commentNode), }); return changeSuggestorButton; diff --git a/src/utils/dom-utils/changeSuggestorButton.ts b/src/utils/dom-utils/changeSuggestorButton.ts index 60aeadbb..fbbbccdd 100644 --- a/src/utils/dom-utils/changeSuggestorButton.ts +++ b/src/utils/dom-utils/changeSuggestorButton.ts @@ -11,8 +11,10 @@ const injectChangeSuggestorButton = async (commentNode: HTMLElement) => { const changeSuggestorButton = ChangeSuggestorButton(commentNode); if (suggestChangesIcon.firstChild?.isEqualNode(changeSuggestorButton)) { + console.log("Change suggestor button already exists"); return; } + console.log("Injecting change suggestor button", changeSuggestorButton); suggestChangesIcon.insertBefore(changeSuggestorButton, suggestChangesIcon.firstChild); }; From 083279885631c6cba78a228d78d415d2ac53b955 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Fri, 19 May 2023 20:36:30 +0530 Subject: [PATCH 07/12] add delay to DOM manipulation --- src/content-scripts/github.ts | 2 +- src/utils/dom-utils/changeSuggestorButton.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/content-scripts/github.ts b/src/content-scripts/github.ts index 95c1dc9f..6a28e314 100644 --- a/src/content-scripts/github.ts +++ b/src/content-scripts/github.ts @@ -24,7 +24,7 @@ const processGithubPage = async () => { if (isPullRequestCreatePage(window.location.href)) { void injectDescriptionGeneratorButton(); } else if (isPullRequestFilesChangedPage(window.location.href)) { - prReviewWatch(injectChangeSuggestorButton); + prReviewWatch(injectChangeSuggestorButton, 500); } else if (isGithubPullRequestPage(window.location.href)) { prEditWatch(injectDescriptionGeneratorButton); void injectAddPRToHighlightsButton(); diff --git a/src/utils/dom-utils/changeSuggestorButton.ts b/src/utils/dom-utils/changeSuggestorButton.ts index fbbbccdd..60aeadbb 100644 --- a/src/utils/dom-utils/changeSuggestorButton.ts +++ b/src/utils/dom-utils/changeSuggestorButton.ts @@ -11,10 +11,8 @@ const injectChangeSuggestorButton = async (commentNode: HTMLElement) => { const changeSuggestorButton = ChangeSuggestorButton(commentNode); if (suggestChangesIcon.firstChild?.isEqualNode(changeSuggestorButton)) { - console.log("Change suggestor button already exists"); return; } - console.log("Injecting change suggestor button", changeSuggestorButton); suggestChangesIcon.insertBefore(changeSuggestorButton, suggestChangesIcon.firstChild); }; From fff9d855925230e8f2e49f926bf0dc5ea741e2d4 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Fri, 19 May 2023 23:39:21 +0530 Subject: [PATCH 08/12] improve type checks --- .../components/AICodeRefactor/ChangeSuggestorButton.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts index 3d32bbf0..97f4f8ba 100644 --- a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts +++ b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts @@ -47,9 +47,12 @@ const handleSubmit = async (commentNode: HTMLElement) => { // find input with name="position" and get its value if (!selectedCode) { - const position = (commentNode.querySelector("input[name=position]")!).value; + const positionElement = (commentNode.querySelector("input[name=position]")!); + const position = positionElement.getAttribute("value")!; - selectedCode = document.querySelector(`[data-line-number="${position}"]`)?.nextSibling?.nextSibling.getElementsByClassName("blob-code-inner")[0].textContent; + const codeDiv = document.querySelector(`[data-line-number="${position}"]`)?.nextSibling?.nextSibling as HTMLElement; + + selectedCode = codeDiv.getElementsByClassName("blob-code-inner")[0].textContent!; } if (!isContextWithinBounds([selectedCode, [] ], descriptionConfig.config.maxInputLength)) { logo.classList.toggle("animate-spin"); From ab229fedfb45d3c7e724c5548c72cd620dff0d39 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Mon, 22 May 2023 21:30:12 +0530 Subject: [PATCH 09/12] remove description tone from code suggestions --- src/utils/aiprdescription/openai.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils/aiprdescription/openai.ts b/src/utils/aiprdescription/openai.ts index c2652821..c3cc1f71 100644 --- a/src/utils/aiprdescription/openai.ts +++ b/src/utils/aiprdescription/openai.ts @@ -15,7 +15,6 @@ const generatePRDescriptionPrompt = ( const generateCodeSuggestionPrompt = ( locale: string, maxLength: number, - tone: DescriptionTone, ) => [ `Generate a code refactor suggestion for a given code snippet written in ${locale} with the specifications mentioned below`, `The code snippet must be a maximum of ${maxLength} characters.`, @@ -80,7 +79,6 @@ export const generateCodeSuggestion = async ( locale: string, maxLength: number, temperature: number, - tone: DescriptionTone, code: string, ) => { const content = `Code: ${code}`; @@ -93,7 +91,7 @@ export const generateCodeSuggestion = async ( messages: [ { role: "system", - content: generateCodeSuggestionPrompt(locale, maxLength, tone), + content: generateCodeSuggestionPrompt(locale, maxLength), }, { role: "user", From 275a0c11993d493f0aaf0b20c8b90b0e185dc940 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Mon, 22 May 2023 21:59:52 +0530 Subject: [PATCH 10/12] update to opensauced api endpoint --- src/constants.ts | 1 + .../AICodeRefactor/ChangeSuggestorButton.ts | 11 +++--- .../DescriptionGeneratorButton.ts | 5 ++- src/utils/aiprdescription/openai.ts | 38 ++++++++++++++++++- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 5aa12c87..339776c1 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -11,6 +11,7 @@ export const OPEN_SAUCED_REPOS_ENDPOINT = "https://api.opensauced.pizza/v1/repos export const OPEN_SAUCED_SESSION_ENDPOINT = "https://api.opensauced.pizza/v1/auth/session"; export const OPEN_SAUCED_USER_INSIGHTS_ENDPOINT = "https://api.opensauced.pizza/v1/user/insights"; export const OPEN_SAUCED_AI_PR_DESCRIPTION_ENDPOINT = "https://api.opensauced.pizza/v1/prs/description/generate"; +export const OPEN_SAUCED_AI_CODE_REFACTOR_ENDPOINT = "https://beta.api.opensauced.pizza/v1/prs/suggestion/generate"; // GitHub constants/selectors export const GITHUB_PROFILE_MENU_SELECTOR = ".p-nickname.vcard-username.d-block"; diff --git a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts index 97f4f8ba..87604f8e 100644 --- a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts +++ b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts @@ -2,10 +2,10 @@ import { createHtmlElement } from "../../../utils/createHtmlElement"; import openSaucedLogoIcon from "../../../assets/opensauced-icon.svg"; import { GITHUB_PR_SUGGESTION_TEXT_AREA_SELECTOR, OPEN_AI_COMPLETION_MODEL_NAME, SUPABASE_LOGIN_URL } from "../../../constants"; import { generateCodeSuggestion } from "../../../utils/aiprdescription/openai"; -import { isContextWithinBounds } from "../../../utils/fetchGithubAPIData"; +import { isOutOfContextBounds } from "../../../utils/fetchGithubAPIData"; import { insertAtCursorFromStream } from "../../../utils/aiprdescription/cursorPositionInsert"; import { getAIDescriptionConfig } from "../../../utils/aiprdescription/descriptionconfig"; -import { isLoggedIn } from "../../../utils/checkAuthentication"; +import { getAuthToken, isLoggedIn } from "../../../utils/checkAuthentication"; export const ChangeSuggestorButton = (commentNode: HTMLElement) => { const changeSuggestorButton = createHtmlElement("a", { @@ -54,17 +54,16 @@ const handleSubmit = async (commentNode: HTMLElement) => { selectedCode = codeDiv.getElementsByClassName("blob-code-inner")[0].textContent!; } - if (!isContextWithinBounds([selectedCode, [] ], descriptionConfig.config.maxInputLength)) { + if (isOutOfContextBounds([selectedCode, [] ], descriptionConfig.config.maxInputLength)) { logo.classList.toggle("animate-spin"); return alert(`Max input length exceeded. Try reducing the number of selected lines to refactor.`); } + const token = await getAuthToken(); const suggestionStream = await generateCodeSuggestion( - descriptionConfig.config.openai_api_key, - OPEN_AI_COMPLETION_MODEL_NAME, + token, descriptionConfig.config.language, descriptionConfig.config.length, descriptionConfig.config.temperature / 10, - descriptionConfig.config.tone, selectedCode, ); diff --git a/src/content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton.ts b/src/content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton.ts index c67bb220..be17bf5c 100644 --- a/src/content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton.ts +++ b/src/content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton.ts @@ -42,9 +42,10 @@ const handleSubmit = async () => { } logo.classList.toggle("animate-spin"); const [diff, commitMessages] = await getDescriptionContext(url, descriptionConfig.config.source); - if(!diff && !commitMessages) { + + if (!diff && !commitMessages) { logo.classList.toggle("animate-spin"); - return alert(`No input context was generated.`) + return alert(`No input context was generated.`); } if (isOutOfContextBounds([diff, commitMessages], descriptionConfig.config.maxInputLength)) { logo.classList.toggle("animate-spin"); diff --git a/src/utils/aiprdescription/openai.ts b/src/utils/aiprdescription/openai.ts index 299a9e6a..dbed8db9 100644 --- a/src/utils/aiprdescription/openai.ts +++ b/src/utils/aiprdescription/openai.ts @@ -1,4 +1,4 @@ -import { OPEN_SAUCED_AI_PR_DESCRIPTION_ENDPOINT } from "../../constants"; +import { OPEN_SAUCED_AI_PR_DESCRIPTION_ENDPOINT, OPEN_SAUCED_AI_CODE_REFACTOR_ENDPOINT } from "../../constants"; import type { DescriptionTone } from "./descriptionconfig"; export const generateDescription = async ( @@ -39,3 +39,39 @@ export const generateDescription = async ( } return undefined; }; + +export const generateCodeSuggestion = async ( + apiKey: string, + language: string, + descriptionLength: number, + temperature: number, + code: string, +): Promise => { + try { + const response = await fetch(OPEN_SAUCED_AI_CODE_REFACTOR_ENDPOINT, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + descriptionLength, + temperature, + language, + code, + }), + }); + + if (response.status === 201) { + const { suggestion } = await response.json(); + + return suggestion; + } + } catch (error: unknown) { + if (error instanceof Error) { + console.error("OpenAI error: ", error.message); + } + } + return undefined; +}; + From d147d09733895637b6deab991e5deab882fe67de Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Mon, 22 May 2023 23:21:23 +0530 Subject: [PATCH 11/12] update stream selector --- src/constants.ts | 2 +- .../components/AICodeRefactor/ChangeSuggestorButton.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 339776c1..ae7253ee 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -11,7 +11,7 @@ export const OPEN_SAUCED_REPOS_ENDPOINT = "https://api.opensauced.pizza/v1/repos export const OPEN_SAUCED_SESSION_ENDPOINT = "https://api.opensauced.pizza/v1/auth/session"; export const OPEN_SAUCED_USER_INSIGHTS_ENDPOINT = "https://api.opensauced.pizza/v1/user/insights"; export const OPEN_SAUCED_AI_PR_DESCRIPTION_ENDPOINT = "https://api.opensauced.pizza/v1/prs/description/generate"; -export const OPEN_SAUCED_AI_CODE_REFACTOR_ENDPOINT = "https://beta.api.opensauced.pizza/v1/prs/suggestion/generate"; +export const OPEN_SAUCED_AI_CODE_REFACTOR_ENDPOINT = "https://api.opensauced.pizza/v1/prs/suggestion/generate"; // GitHub constants/selectors export const GITHUB_PROFILE_MENU_SELECTOR = ".p-nickname.vcard-username.d-block"; diff --git a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts index 87604f8e..23c49a0a 100644 --- a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts +++ b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts @@ -1,9 +1,9 @@ import { createHtmlElement } from "../../../utils/createHtmlElement"; import openSaucedLogoIcon from "../../../assets/opensauced-icon.svg"; -import { GITHUB_PR_SUGGESTION_TEXT_AREA_SELECTOR, OPEN_AI_COMPLETION_MODEL_NAME, SUPABASE_LOGIN_URL } from "../../../constants"; +import { GITHUB_PR_SUGGESTION_TEXT_AREA_SELECTOR, SUPABASE_LOGIN_URL } from "../../../constants"; import { generateCodeSuggestion } from "../../../utils/aiprdescription/openai"; import { isOutOfContextBounds } from "../../../utils/fetchGithubAPIData"; -import { insertAtCursorFromStream } from "../../../utils/aiprdescription/cursorPositionInsert"; +import { insertTextAtCursor } from "../../../utils/aiprdescription/cursorPositionInsert"; import { getAIDescriptionConfig } from "../../../utils/aiprdescription/descriptionconfig"; import { getAuthToken, isLoggedIn } from "../../../utils/checkAuthentication"; @@ -73,7 +73,7 @@ const handleSubmit = async (commentNode: HTMLElement) => { } const textArea = commentNode.querySelector(GITHUB_PR_SUGGESTION_TEXT_AREA_SELECTOR)!; - void insertAtCursorFromStream(textArea as HTMLTextAreaElement, suggestionStream); + void insertTextAtCursor(textArea as HTMLTextAreaElement, suggestionStream); } catch (error: unknown) { if (error instanceof Error) { console.error("Description generation error:", error.message); From d6f6e59add0cccb2de6e5c11b2c0db5fecc13a89 Mon Sep 17 00:00:00 2001 From: Abdurrahman Rajab Date: Tue, 23 May 2023 13:11:01 +0300 Subject: [PATCH 12/12] fix: showing the icon (#104) --- .../components/AICodeRefactor/ChangeSuggestorButton.ts | 1 + src/utils/dom-utils/changeSuggestorButton.ts | 2 +- src/utils/dom-utils/prWatcher.ts | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts index 23c49a0a..c8992659 100644 --- a/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts +++ b/src/content-scripts/components/AICodeRefactor/ChangeSuggestorButton.ts @@ -14,6 +14,7 @@ export const ChangeSuggestorButton = (commentNode: HTMLElement) => { Get Refactor Suggestions`, onclick: async () => handleSubmit(commentNode), + id: "os-ai-change-gen", }); return changeSuggestorButton; diff --git a/src/utils/dom-utils/changeSuggestorButton.ts b/src/utils/dom-utils/changeSuggestorButton.ts index 60aeadbb..36148ed0 100644 --- a/src/utils/dom-utils/changeSuggestorButton.ts +++ b/src/utils/dom-utils/changeSuggestorButton.ts @@ -10,7 +10,7 @@ const injectChangeSuggestorButton = async (commentNode: HTMLElement) => { const suggestChangesIcon = commentNode.getElementsByClassName(GITHUB_REVIEW_SUGGESTION_SELECTOR)[0]; const changeSuggestorButton = ChangeSuggestorButton(commentNode); - if (suggestChangesIcon.firstChild?.isEqualNode(changeSuggestorButton)) { + if (suggestChangesIcon.querySelector("#os-ai-change-gen")) { return; } suggestChangesIcon.insertBefore(changeSuggestorButton, suggestChangesIcon.firstChild); diff --git a/src/utils/dom-utils/prWatcher.ts b/src/utils/dom-utils/prWatcher.ts index 3b18fe62..03f3efa7 100644 --- a/src/utils/dom-utils/prWatcher.ts +++ b/src/utils/dom-utils/prWatcher.ts @@ -12,11 +12,12 @@ const prEditWatch = (callback: () => void, delayInMs = 0) => { }; export const prReviewWatch = (callback: (node: HTMLElement) => void, delayInMs = 0) => { + const githubCommentSelector = "inline-comment-form-container"; const observer = new MutationObserver((mutationList: MutationRecord[], observer: MutationObserver) => { mutationList.forEach(mutation => { - if (Array.from((mutation.target as HTMLElement).classList).includes("inline-comments")) { + if (Array.from((mutation.target as HTMLElement).classList).includes(githubCommentSelector)) { setTimeout(() => { - const commentNodes = document.getElementsByClassName("inline-comments"); + const commentNodes = document.getElementsByClassName(githubCommentSelector); Array.from(commentNodes).forEach(node => { callback(node as HTMLElement);