diff --git a/src/constants.ts b/src/constants.ts index 0f345ea0..52db8271 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -6,11 +6,13 @@ export const OPEN_SAUCED_INSIGHTS_DOMAIN = "insights.opensauced.pizza"; export const AI_PR_DESCRIPTION_CONFIG_KEY = "ai-pr-description-config"; // API endpoints -export const OPEN_SAUCED_USERS_ENDPOINT = "https://api.opensauced.pizza/v1/users"; -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_API_ENDPOINT = "https://api.opensauced.pizza/v1"; +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 = "https://api.opensauced.pizza/v1/prs/suggestion/generate"; // GitHub constants/selectors diff --git a/src/content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton.ts b/src/content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton.ts index b3f60926..7c468b3b 100644 --- a/src/content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton.ts +++ b/src/content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton.ts @@ -23,56 +23,69 @@ export const DescriptionGeneratorButton = () => { }; const handleSubmit = async () => { + const logo = document.getElementById("ai-description-button-logo") ?? null; + try { if (!(await isLoggedIn())) { return window.open(SUPABASE_LOGIN_URL, "_blank"); } - const logo = document.getElementById("ai-description-button-logo"); if (!logo) { return; } - const url = getPullRequestAPIURL(window.location.href); - const descriptionConfig = await getAIDescriptionConfig(); - - if (!descriptionConfig) { - return; - } - if (!descriptionConfig.enabled) { - return alert("AI PR description is disabled!"); - } logo.classList.toggle("animate-spin"); - const [diff, commitMessages] = await getDescriptionContext(url, descriptionConfig.config.source); - - if (!diff && !commitMessages) { - logo.classList.toggle("animate-spin"); - return alert(`No input context was generated.`); - } - if (isOutOfContextBounds([diff, commitMessages], descriptionConfig.config.maxInputLength)) { - logo.classList.toggle("animate-spin"); - return alert(`Max input length exceeded. Try setting the description source to commit-messages.`); - } - const token = await getAuthToken(); - const descriptionStream = await generateDescription( - token, - descriptionConfig.config.language, - descriptionConfig.config.length, - descriptionConfig.config.temperature / 10, - descriptionConfig.config.tone, - diff, - commitMessages, - ); + const descriptionStream = await getAiDescription(); logo.classList.toggle("animate-spin"); - if (!descriptionStream) { - return console.error("No description was generated!"); - } + const textArea = document.getElementsByName(GITHUB_PR_COMMENT_TEXT_AREA_SELECTOR)[0] as HTMLTextAreaElement; insertTextAtCursor(textArea, descriptionStream); } catch (error: unknown) { + logo?.classList.toggle("animate-spin"); + if (error instanceof Error) { + alert(error.message); console.error("Description generation error:", error.message); } } }; + +export const getAiDescription = async () => { + const url = getPullRequestAPIURL(window.location.href); + const descriptionConfig = await getAIDescriptionConfig(); + + if (!descriptionConfig) { + throw new Error("Configuration file is empty!"); + } + + if (!descriptionConfig.enabled) { + throw new Error("AI PR description is disabled!"); + } + + const [diff, commitMessages] = await getDescriptionContext(url, 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 descriptionStream = await generateDescription( + token, + descriptionConfig.config.language, + descriptionConfig.config.length, + descriptionConfig.config.temperature / 10, + descriptionConfig.config.tone, + diff, + commitMessages, + ); + + if (!descriptionStream) { + throw new Error("No description was generated!"); + } + + return descriptionStream; +}; + diff --git a/src/content-scripts/github.ts b/src/content-scripts/github.ts index a1e4f9e3..c154de7c 100644 --- a/src/content-scripts/github.ts +++ b/src/content-scripts/github.ts @@ -16,6 +16,7 @@ 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 { getAiDescription } from "./components/GenerateAIDescription/DescriptionGeneratorButton"; const processGithubPage = async () => { if (prefersDarkMode(document.cookie)) { @@ -55,3 +56,27 @@ const processGithubPage = async () => { }; void processGithubPage(); + +chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { + switch (msg.type) { + case "get_highlight": { + const title = (document.querySelector(".js-issue-title.markdown-title") as HTMLHeadingElement)?.innerText; + + sendResponse(title); + break; + } + case "get_ai_description": { + const asyncRequest = async () => { + const aiText = await getAiDescription(); + + sendResponse(aiText); + }; + + asyncRequest().catch((e: Error | undefined) => { + sendResponse(e?.toString()); + console.error(e); + }); + return true; + } + } +}); diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 8fc140d1..e5a5bc06 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -10,6 +10,9 @@ import { useOpensaucedUserCheck } from "../hooks/useOpensaucedUserCheck"; import { Profile } from "./profile"; import { goTo } from "react-chrome-extension-router"; import AIPRDescription from "./aiprdescription"; +import PostOnHighlight from "./posthighlight"; + + import Help from "./help"; const Home = () => { @@ -78,6 +81,16 @@ const Home = () => { AI Configuration + { + goTo(PostOnHighlight); + }} + > + + Post Highlight + + {currentTabIsOpensaucedUser && ( { + const { authToken, user } = useAuth(); + const [pageURL, setPageURL] = useState(""); + const [highlightTitle, setHighlightTitle] = useState(""); + const [highlightContent, setHighlightContent] = useState(""); + const [isSendButtonEnabled, enableSendButton] = useState(true); + + const generateAiDescription = () => { + const toastId = toast.loading("Generating summary..."); + + enableSendButton(false); + chrome.tabs.query({ currentWindow: true, active: true }, tabs => { + chrome.tabs.sendMessage(tabs[0].id ?? 0, { type: "get_ai_description" }, response => { + toast.dismiss(toastId); + setHighlightContent(response); + enableSendButton(true); + }); + }); + }; + + + // post highlight function + const postHighlight = () => { + enableSendButton(false); + const postHighlightAPI = cerateHighlight((authToken ?? ""), pageURL, highlightTitle, highlightContent); + + toast.promise(postHighlightAPI, { + loading: "Loading ...", + success: data => { + enableSendButton(true); + if (!data.ok) { + throw new Error(`Statues code ${data.status}`); + } + return ( + + + See the highlight live + + + ); + }, + error: e => { + enableSendButton(true); + return `Uh oh, there was an error! ${e.message}`; + }, + }).catch(console.error); + }; + + useEffect(() => { + chrome.tabs.query({ currentWindow: true, active: true }, tabs => { + setPageURL(tabs[0]?.url ?? ""); + chrome.tabs.sendMessage(tabs[0].id ?? 0, { type: "get_highlight" }, setHighlightTitle); + }); + }, []); + + return ( + + + + + + + { + goBack(); + }} + > + + + + + + + + + + setHighlightTitle(e.target.value)} + /> + + setHighlightContent(e.target.value)} + /> + + + generateAiDescription()} + > + Summarize + + + + Post + + + + + + ); +}; + +export default PostOnHighlight; diff --git a/src/utils/fetchGithubAPIData.ts b/src/utils/fetchGithubAPIData.ts index fe3de316..59046297 100644 --- a/src/utils/fetchGithubAPIData.ts +++ b/src/utils/fetchGithubAPIData.ts @@ -21,13 +21,13 @@ export const getPRDiff = async (url: string) => { }; export const getPRCommitMessages = async (url: string) => { - const response = await fetch(url); + const response = await fetch(`${url}/commits`); const data = await response.json(); - if (!Array.isArray(data.commits)) { + if (!Array.isArray(data)) { return undefined; } - const commitMessages: string[] = (data.commits as Commit[]).map((commit: Commit): string => commit.commit.message); + const commitMessages: string[] = (data as Commit[]).map((commit: Commit): string => commit.commit.message); return commitMessages; }; diff --git a/src/utils/fetchOpenSaucedApiData.ts b/src/utils/fetchOpenSaucedApiData.ts index 07a7d3f9..4f4710c2 100644 --- a/src/utils/fetchOpenSaucedApiData.ts +++ b/src/utils/fetchOpenSaucedApiData.ts @@ -4,6 +4,7 @@ import { OPEN_SAUCED_SESSION_ENDPOINT, OPEN_SAUCED_REPOS_ENDPOINT, OPEN_SAUCED_USER_INSIGHTS_ENDPOINT, + OPEN_SAUCED_USER_HIGHLIGHTS_ENDPOINT, } from "../constants"; import { IInsight } from "../ts/InsightDto"; @@ -176,3 +177,18 @@ export const updateInsight = async (userToken: string, repoId: string, checked: return response.status === 200; }; + +export const cerateHighlight = async (userToken: string, url: string, title: string, highlight: string, shipped_at?: string) => fetch(OPEN_SAUCED_USER_HIGHLIGHTS_ENDPOINT, { + headers: { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: `Bearer ${userToken}`, + }, + method: "POST", + body: JSON.stringify({ + url, + title, + shipped_at, + highlight, + }), +});