Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Post a highlight #101

Merged
merged 26 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
196c311
feat: post on highlights
a0m0rajab May 21, 2023
edcf4c0
feat: add open graph GitHub
a0m0rajab May 22, 2023
93d37b3
feat: communicate between popup and content script
a0m0rajab May 23, 2023
c80092f
chore: remove social cards
a0m0rajab May 24, 2023
fa29c17
feat: add post to highlight endpoint and logic
a0m0rajab May 24, 2023
5fc32a9
feat: add toast and disable post button when fetch
a0m0rajab May 24, 2023
a5b827f
chore: refactor the fetch code
a0m0rajab May 25, 2023
3bf2ec0
feat: generate highlight by AI
a0m0rajab May 25, 2023
17568b3
chore: add getAiDesc function
a0m0rajab May 25, 2023
c0a15a8
chore: lint
a0m0rajab May 25, 2023
88fe541
chore: lint
a0m0rajab May 25, 2023
909552e
chore: minor fix after rebase
a0m0rajab May 25, 2023
5b38e55
chore: refactor ai generating function
a0m0rajab May 25, 2023
a251b92
fix: instable commits messages
a0m0rajab May 25, 2023
b8022f8
feat: add ai logic to the ui
a0m0rajab May 25, 2023
9442147
chore: refactor the send response
a0m0rajab May 26, 2023
fd5adf7
chore: lint files issues
a0m0rajab May 26, 2023
0d4372d
feat: provide error feedback.
a0m0rajab May 27, 2023
c233218
feat: add user feedback on ai generated post
a0m0rajab May 27, 2023
2765514
chore: fix build issues
a0m0rajab May 27, 2023
aa7eb45
chore: fix nullish issue
a0m0rajab May 27, 2023
64dcb50
fix: title selector update
a0m0rajab May 27, 2023
cd7e990
chore: add generate AI button to post highlight
a0m0rajab May 27, 2023
6bd72d1
Update copy to help the user
bdougie May 29, 2023
7845065
Update src/pages/posthighlight.tsx
bdougie May 29, 2023
799bdd4
fix toast message
bdougie May 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

25 changes: 25 additions & 0 deletions src/content-scripts/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;
}
}
});
13 changes: 13 additions & 0 deletions src/pages/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -78,6 +81,16 @@ const Home = () => {
AI Configuration
</button>

<button
className="flex items-center bg-slate-700 hover:bg-slate-700/70 hover:text-orange text-white gap-2 p-1.5 px-3 w-full rounded-sm font-medium text-sm"
onClick={() => {
goTo(PostOnHighlight);
}}
>
<HiPencil />
Post Highlight
</button>

{currentTabIsOpensaucedUser && (
<button
className="flex items-center bg-slate-700 hover:bg-slate-700/70 hover:text-orange text-white gap-2 p-1.5 px-3 w-full rounded-sm font-medium text-sm"
Expand Down
134 changes: 134 additions & 0 deletions src/pages/posthighlight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { useEffect, useState } from "react";
Anush008 marked this conversation as resolved.
Show resolved Hide resolved
import { FaChevronLeft } from "react-icons/fa";
import OpenSaucedLogo from "../assets/opensauced-logo.svg";
import { useAuth } from "../hooks/useAuth";
import toast, { Toaster } from "react-hot-toast";
import { cerateHighlight } from "../utils/fetchOpenSaucedApiData";
import { goBack } from "react-chrome-extension-router";

const PostOnHighlight = () => {
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 (
<span>
<a
href={`https://insights.opensauced.pizza/user/${user?.user_name}/highlights`}
rel="noreferrer"
target="_blank"
>
See the highlight live
</a>
</span>
);
},
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 (
<div className="p-4 bg-slate-800">
<div className="grid grid-cols-1 divide-y divider-y-center-2 min-w-[320px]">
<Toaster />

<header className="flex justify-between">
<div className="flex items-center gap-2">
<button
className="rounded-full p-2 bg-slate-700 hover:bg-slate-700/50"
onClick={() => {
goBack();
}}
>
<FaChevronLeft className="text-osOrange text-white" />
</button>

<img
alt="OpenSauced logo"
className="w-[100%]"
src={OpenSaucedLogo}
/>
</div>
</header>

<main className="text-white">

<input
className="p-1.5 rounded-md mb-2 w-full text-black"
maxLength={50}
placeholder="An amazing title here"
type="text"
value={highlightTitle}
onChange={e => setHighlightTitle(e.target.value)}
/>

<textarea
className="p-1.5 rounded-md mb-2 w-full text-black"
placeholder="Summarize this pull request"
rows={5}
value={highlightContent}
onChange={e => setHighlightContent(e.target.value)}
/>

<div className="flex justify-evenly">
<button
className="inline-block disabled:bg-gray-500 text-black bg-gh-white rounded-md p-2 text-sm font-semibold text-center select-none w-5/12 border hover:shadow-button hover:no-underline"
disabled={!isSendButtonEnabled}
onClick={() => generateAiDescription()}
>
Summarize
</button>

<button
className="inline-block disabled:bg-gray-500 text-black bg-gh-white rounded-md p-2 text-sm font-semibold text-center select-none w-5/12 border hover:shadow-button hover:no-underline"
disabled={!isSendButtonEnabled}
onClick={postHighlight}
>
Post
</button>
</div>
</main>
</div>
</div>
);
};

export default PostOnHighlight;
6 changes: 3 additions & 3 deletions src/utils/fetchGithubAPIData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
16 changes: 16 additions & 0 deletions src/utils/fetchOpenSaucedApiData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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,
}),
});