diff --git a/.eslintrc.js b/.eslintrc.js index 6307a39..1b64c89 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -81,7 +81,7 @@ module.exports = { }, ], "no-useless-rename": "error", - "no-useless-return": "error", + "no-useless-return": "warn", "no-var": "error", "object-shorthand": "error", "one-var": ["error", "never"], diff --git a/src/constants.ts b/src/constants.ts index 6cfaeba..bab999a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -14,13 +14,11 @@ export const SUPABASE_AUTH_COOKIE_NAME = `sb-${import.meta.env.VITE_OPEN_SAUCED_ export const SUPABASE_PKCE_VERIFIER_COOKIE_NAME = `sb-${import.meta.env.VITE_OPEN_SAUCED_SUPABASE_ID}-auth-token-code-verifier`; export const OPEN_SAUCED_AUTH_TOKEN_KEY = "os-access-token"; export const OPEN_SAUCED_OPTED_LOG_OUT_KEY = "opted-log-out"; -export const AI_PR_DESCRIPTION_CONFIG_KEY = "ai-pr-description-config"; export const OPEN_SAUCED_USERS_ENDPOINT = `${OPEN_SAUCED_API_ENDPOINT}/users`; export const OPEN_SAUCED_REPOS_ENDPOINT = `${OPEN_SAUCED_API_ENDPOINT}/repos`; export const OPEN_SAUCED_SESSION_ENDPOINT = `${OPEN_SAUCED_API_ENDPOINT}/auth/session`; export const OPEN_SAUCED_USER_INSIGHTS_ENDPOINT = `${OPEN_SAUCED_API_ENDPOINT}/user/insights`; -export const OPEN_SAUCED_AI_PR_DESCRIPTION_ENDPOINT = `${OPEN_SAUCED_API_ENDPOINT}/prs/description/generate`; export const OPEN_SAUCED_USER_HIGHLIGHTS_ENDPOINT = `${OPEN_SAUCED_API_ENDPOINT}/user/highlights`; export const OPEN_SAUCED_AI_CODE_REFACTOR_ENDPOINT = `${OPEN_SAUCED_API_ENDPOINT}/prs/suggestion/generate`; export const OPEN_SAUCED_AI_CODE_EXPLANATION_ENDPOINT = `${OPEN_SAUCED_API_ENDPOINT}/prs/explanation/generate`; diff --git a/src/content-scripts/components/AICodeReview/AICodeReviewButton.ts b/src/content-scripts/components/AICodeReview/AICodeReviewButton.ts deleted file mode 100644 index dfe55ca..0000000 --- a/src/content-scripts/components/AICodeReview/AICodeReviewButton.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { createHtmlElement } from "../../../utils/createHtmlElement"; -import openSaucedLogoIcon from "../../../assets/opensauced-icon.svg"; -import { - generateCodeExplanation, - generateCodeSuggestion, - generateCodeTest, -} from "../../../utils/ai-utils/openai"; -import { AICodeReviewMenu, AICodeReviewMenuItem } from "./AICodeReviewMenu"; - -export const AICodeReviewButton = (commentNode: HTMLElement) => { - const changeSuggestorButton = createHtmlElement("a", { - innerHTML: ` - - `, - onclick: (event: MouseEvent) => { - event.stopPropagation(); - menu.classList.toggle("hidden"); - }, - id: "os-ai-change-gen", - }); - - const refactorCode = AICodeReviewMenuItem( - "Refactor Code", - "Generate a code refactor", - generateCodeSuggestion, - commentNode, - ); - const testCode = AICodeReviewMenuItem( - "Test Code", - "Generate a test for the code", - generateCodeTest, - commentNode, - ); - const explainCode = AICodeReviewMenuItem( - "Explain Code", - "Generate an explanation for the code", - generateCodeExplanation, - commentNode, - ); - - const menu = AICodeReviewMenu([refactorCode, testCode, explainCode]); - - changeSuggestorButton.append(menu); - return changeSuggestorButton; -}; diff --git a/src/content-scripts/components/AICodeReview/AICodeReviewMenu.ts b/src/content-scripts/components/AICodeReview/AICodeReviewMenu.ts deleted file mode 100644 index 9654b19..0000000 --- a/src/content-scripts/components/AICodeReview/AICodeReviewMenu.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { GITHUB_PR_SUGGESTION_TEXT_AREA_Attribute } from "../../../constants"; -import { insertTextAtCursor } from "../../../utils/ai-utils/cursorPositionInsert"; -import { - DescriptionConfig, - getAIDescriptionConfig, -} from "../../../utils/ai-utils/descriptionconfig"; -import { - getAuthToken, - isLoggedIn, - optLogIn, -} from "../../../utils/checkAuthentication"; -import { createHtmlElement } from "../../../utils/createHtmlElement"; -import { isOutOfContextBounds } from "../../../utils/fetchGithubAPIData"; - -type SuggestionGenerator = ( - token: string, - code: string, - config: DescriptionConfig, -) => Promise; - -export const AICodeReviewMenu = (items: HTMLLIElement[]) => { - const menu = createHtmlElement("div", { - className: "SelectMenu js-slash-command-menu hidden mt-6", - innerHTML: `
-
-
- - OpenSauced.ai -
-
AI
- - Give feedback - -
-
-
    -
-
-
`, - }); - - menu.querySelector("ul")?.append(...items); - - document.addEventListener("click", (event) => { - if (event.target instanceof HTMLElement) { - menu.classList.add("hidden"); - } - }); - return menu; -}; - -export const AICodeReviewMenuItem = ( - title: string, - description: string, - suggestionGenerator: SuggestionGenerator, - commentNode: HTMLElement, -) => { - const menuItem = createHtmlElement("li", { - className: "SelectMenu-item d-block slash-command-menu-item", - role: "option", - onclick: () => { - void handleSubmit(suggestionGenerator, commentNode); - }, - innerHTML: `
${title}
- ${description}`, - }); - - return menuItem; -}; - -const handleSubmit = async ( - suggestionGenerator: SuggestionGenerator, - commentNode: HTMLElement, -) => { - const logo = commentNode.querySelector("#ai-description-button-logo"); - const button = commentNode.querySelector("#os-ai-change-gen"); - - try { - if (!(await isLoggedIn())) { - return void optLogIn(); - } - - if (!logo || !button) { - return; - } - - const descriptionConfig = await getAIDescriptionConfig(); - - if (!descriptionConfig) { - return; - } - - logo.classList.toggle("animate-spin"); - button.classList.toggle("pointer-events-none"); - - const selectedLines = document.querySelectorAll( - ".code-review.selected-line", - ); - let selectedCode = Array.from(selectedLines) - .map((line) => line.textContent) - .join("\n"); - - // find input with name="position" and get its value - if (!selectedCode) { - const positionElement = commentNode.querySelector( - "input[name=position]", - )!; - const position = positionElement.getAttribute("value")!; - - const codeDiv = document.querySelector( - `[data-line-number="${position}"]`, - )?.nextSibling?.nextSibling as HTMLElement; - - selectedCode = - codeDiv.getElementsByClassName("blob-code-inner")[0] - .textContent!; - } - 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 suggestionGenerator( - token, - selectedCode, - descriptionConfig, - ); - - logo.classList.toggle("animate-spin"); - button.classList.toggle("pointer-events-none"); - if (!suggestionStream) { - return console.error("No description was generated!"); - } - const textArea = commentNode.querySelector( - GITHUB_PR_SUGGESTION_TEXT_AREA_Attribute, - )!; - - insertTextAtCursor(textArea as HTMLTextAreaElement, suggestionStream); - } catch (error: unknown) { - logo?.classList.toggle("animate-spin"); - button?.classList.toggle("pointer-events-none"); - - if (error instanceof Error) { - console.error("Description generation error:", error.message); - } - } -}; diff --git a/src/content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton.ts b/src/content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton.ts deleted file mode 100644 index 95846e1..0000000 --- a/src/content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { createHtmlElement } from "../../../utils/createHtmlElement"; -import openSaucedLogoIcon from "../../../assets/opensauced-icon.svg"; -import { getPullRequestAPIURL } from "../../../utils/urlMatchers"; -import { - getDescriptionContext, - isOutOfContextBounds, -} from "../../../utils/fetchGithubAPIData"; -import { generateDescription } from "../../../utils/ai-utils/openai"; -import { insertTextAtCursor } from "../../../utils/ai-utils/cursorPositionInsert"; -import { getAIDescriptionConfig } from "../../../utils/ai-utils/descriptionconfig"; -import { - getAuthToken, - isLoggedIn, - optLogIn, -} from "../../../utils/checkAuthentication"; - -export const DescriptionGeneratorButton = (number: number) => { - const descriptionGeneratorButton = createHtmlElement("a", { - id: `ai-description-button-${number}`, - innerHTML: ` - - - Generate PR description`, - onclick: handleSubmit, - }); - - return descriptionGeneratorButton; -}; - -const handleSubmit = async (event: Event) => { - const button = event.currentTarget as HTMLElement; - const logo = button.querySelector("#ai-description-button-logo"); - - try { - if (!(await isLoggedIn())) { - return void optLogIn(); - } - - const descriptionConfig = await getAIDescriptionConfig(); - - if (!descriptionConfig) { - return; - } - - logo?.classList.toggle("animate-spin"); - button.classList.toggle("pointer-events-none"); - - const { protocol, hostname, pathname } = window.location; - const descriptionStream = await getAiDescription( - `${protocol}//${hostname}${pathname}`, - ); - - logo?.classList.toggle("animate-spin"); - button.classList.toggle("pointer-events-none"); - const textArea = button - .closest(".Box.CommentBox") - ?.querySelector("textarea"); - - if (textArea) { - insertTextAtCursor(textArea, descriptionStream); - } - } catch (error: unknown) { - logo?.classList.toggle("animate-spin"); - button.classList.toggle("pointer-events-none"); - - if (error instanceof Error) { - alert(error.message); - console.error("Description generation error:", error.message); - } - } -}; - -export const getAiDescription = async (prUrl: string) => { - const prApiUrl = await getPullRequestAPIURL(prUrl); - - const descriptionConfig = await getAIDescriptionConfig(); - - if (!descriptionConfig) { - throw new Error("Configuration file is empty!"); - } - - const [diff, commitMessages] = await getDescriptionContext( - prApiUrl, - descriptionConfig.config.source, - ); - - if (!diff && !commitMessages) { - throw new Error(`No input context was generated.`); - } - if ( - isOutOfContextBounds( - [diff, commitMessages], - descriptionConfig.config.maxInputLength, - ) - ) { - throw new Error( - `Max input length exceeded. Try setting the description source to commit-messages.`, - ); - } - const token = await getAuthToken(); - const description = await generateDescription( - token, - descriptionConfig.config.language, - descriptionConfig.config.length, - descriptionConfig.config.temperature / 10, - descriptionConfig.config.tone, - diff, - commitMessages, - ); - - if (!description) { - throw new Error("No description was generated!"); - } - - return description; -}; diff --git a/src/content-scripts/github.ts b/src/content-scripts/github.ts index 26fcc68..b60857f 100644 --- a/src/content-scripts/github.ts +++ b/src/content-scripts/github.ts @@ -1,38 +1,19 @@ import { getGithubUsername, isGithubProfilePage, - isGithubPullRequestPage, - isGithubRepoPage, - isPullRequestCreatePage, - isPullRequestFilesChangedPage, } from "../utils/urlMatchers"; import { isOpenSaucedUser } from "../utils/fetchOpenSaucedApiData"; import injectViewOnOpenSauced from "../utils/dom-utils/viewOnOpenSauced"; import injectInviteToOpenSauced from "../utils/dom-utils/inviteToOpenSauced"; import { prefersDarkMode } from "../utils/colorPreference"; -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 injectChangeSuggestorButton from "../utils/dom-utils/changeSuggestorButton"; -import prEditWatch, { prReviewWatch } from "../utils/dom-utils/prWatcher"; -import injectChatDialog from "../utils/dom-utils/addChatDialog"; -import { pageUrlWatch } from "../utils/dom-utils/pageUrlWatcher"; const processGithubPage = async () => { if (prefersDarkMode(document.cookie)) { document.documentElement.classList.add("dark"); } - if (isPullRequestCreatePage(window.location.href)) { - void injectDescriptionGeneratorButton(); - } else if (isPullRequestFilesChangedPage(window.location.href)) { - prReviewWatch(injectChangeSuggestorButton, 500); - } else if (isGithubPullRequestPage(window.location.href)) { - prEditWatch(injectDescriptionGeneratorButton, 500); - void injectDescriptionGeneratorButton(); - void injectAddPRToHighlightsButton(); - } else if (isGithubProfilePage(window.location.href)) { + if (isGithubProfilePage(window.location.href)) { const username = getGithubUsername(window.location.href); if (!username) { @@ -43,29 +24,8 @@ const processGithubPage = async () => { } else { injectInviteToOpenSauced(username); } - } else if (isGithubRepoPage(window.location.href)) { - const ownerName = getGithubUsername(window.location.href) ?? ""; - const repoName = window.location.href.split("/").pop() ?? ""; - - await injectChatDialog(ownerName, repoName); - - pageUrlWatch(() => { - if (document.getElementById("repo-query-root")) { - document.getElementById("repo-query-root")?.remove(); - } - }, 50); } - /* - * commenting out repo voting because it's not ready yet // issue #106 - * } else if (isGithubRepoPage(window.location.href)) { - * const ownerName = getGithubUsername(window.location.href) ?? ""; - * const repoName = window.location.href.split("/").pop() ?? ""; - * - * await injectRepoVotingButtons(ownerName, repoName); - * } - */ - domUpdateWatch(processGithubPage, 50); }; diff --git a/src/popup/pages/aiprdescription.tsx b/src/popup/pages/aiprdescription.tsx deleted file mode 100644 index d881abf..0000000 --- a/src/popup/pages/aiprdescription.tsx +++ /dev/null @@ -1,273 +0,0 @@ -import React, { useEffect, useReducer } from "react"; -import { FaChevronLeft } from "react-icons/fa"; -import OpenSaucedLogo from "../../assets/opensauced-logo.svg"; -import toast, { Toaster } from "react-hot-toast"; - -import { - getAIDescriptionConfig, - DescriptionTone, - DescriptionSource, - DescriptionLanguage, - setAIDescriptionConfig, - getDefaultDescriptionConfig, -} from "../../utils/ai-utils/descriptionconfig"; -import { configurationReducer } from "../../utils/ai-utils/configurationReducer"; -import { goBack } from "react-chrome-extension-router"; - -const AIPRDescription = () => { - const [config, dispatch] = useReducer( - configurationReducer, - getDefaultDescriptionConfig(), - ); - - const tones: DescriptionTone[] = [ - "exciting", - "persuasive", - "informative", - "humorous", - "formal", - ]; - const sources: DescriptionSource[] = ["diff", "commitMessage", "both"]; - const languages: DescriptionLanguage[] = [ - "english", - "spanish", - "french", - "german", - "italian", - "portuguese", - "dutch", - "russian", - "chinese", - "korean", - ]; - - useEffect(() => { - const descriptionConfig = async () => { - const configData = await getAIDescriptionConfig(); - - dispatch({ type: "SET", value: configData }); - }; - - void descriptionConfig(); - }, []); - - const handleFormSubmit = (e: React.FormEvent) => { - e.preventDefault(); - void setAIDescriptionConfig(config); - toast.success("Configuration updated!"); - }; - - return ( - <> - - -
-
-
-
- - - OpenSauced logo -
-
- -
-
-
-

- AI Configuration: -

- -
-
-

- Description Length [ - {config.config.length}] -

- - - dispatch({ - type: "SET_LENGTH", - value: parseInt( - e.target.value, - ), - }) - } - /> -
- -
-

- Temperature [ - - {config.config.temperature / 10} - - ] -

- - - dispatch({ - type: "SET_TEMPERATURE", - value: parseInt( - e.target.value, - ), - }) - } - /> -
- -
-

- Max Input Length [ - - {config.config.maxInputLength} - - ] -

- - - dispatch({ - type: "SET_MAX_INPUT_LENGTH", - value: parseInt( - e.target.value, - ), - }) - } - /> -
- -
-

Description Language

- - -
- -
-

Description Tone

- - -
- -
-

Description Source

- - -
-
- - -
-
-
-
-
- - ); -}; - -export default AIPRDescription; diff --git a/src/popup/pages/posthighlight.tsx b/src/popup/pages/posthighlight.tsx index 8ff73d4..4337b2d 100644 --- a/src/popup/pages/posthighlight.tsx +++ b/src/popup/pages/posthighlight.tsx @@ -6,7 +6,6 @@ import toast, { Toaster } from "react-hot-toast"; import { createHighlight } from "../../utils/fetchOpenSaucedApiData"; import { goBack, goTo } from "react-chrome-extension-router"; import { OPEN_SAUCED_INSIGHTS_DOMAIN } from "../../constants"; -import { getAiDescription } from "../../content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton"; import Home from "./home"; const PostOnHighlight = ({ @@ -22,28 +21,6 @@ const PostOnHighlight = ({ const [highlightContent, setHighlightContent] = useState(""); const [isSendButtonEnabled, enableSendButton] = useState(true); - const generateAiDescription = () => { - enableSendButton(false); - const description = getAiDescription(prUrl); - - toast - .promise(description, { - loading: "Generating summary...", - success: (data) => { - enableSendButton(true); - setHighlightContent(data); - - return "Successfully Generated Summary"; - }, - error: (e) => { - enableSendButton(true); - return `Uh oh, there was an error! ${e.message}`; - }, - }) - .catch(console.error); - }; - - // post highlight function const postHighlight = () => { enableSendButton(false); const postHighlightAPI = createHighlight( @@ -130,14 +107,6 @@ const PostOnHighlight = ({ />
- -