Skip to content

Commit

Permalink
feat: New PR description endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Anush008 committed May 19, 2023
1 parent 5c15de5 commit 1448ea2
Show file tree
Hide file tree
Showing 11 changed files with 57 additions and 158 deletions.
58 changes: 0 additions & 58 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"@types/node-emoji": "^1.8.2",
"gpt-tokenizer": "^1.0.5",
"node-emoji": "^1.11.0",
"openai-streams": "^5.3.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-hot-toast": "^2.4.1",
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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";

// GitHub constants/selectors
export const GITHUB_PROFILE_MENU_SELECTOR = ".p-nickname.vcard-username.d-block";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { createHtmlElement } from "../../../utils/createHtmlElement";
import openSaucedLogoIcon from "../../../assets/opensauced-icon.svg";
import { getPullRequestAPIURL } from "../../../utils/urlMatchers";
import { getDescriptionContext, isContextWithinBounds } from "../../../utils/fetchGithubAPIData";
import { getDescriptionContext, isOutOfContextBounds } from "../../../utils/fetchGithubAPIData";
import { generateDescription } from "../../../utils/aiprdescription/openai";
import { GITHUB_PR_COMMENT_TEXT_AREA_SELECTOR, OPEN_AI_COMPLETION_MODEL_NAME, SUPABASE_LOGIN_URL } from "../../../constants";
import { insertAtCursorFromStream } from "../../../utils/aiprdescription/cursorPositionInsert";
import { GITHUB_PR_COMMENT_TEXT_AREA_SELECTOR, SUPABASE_LOGIN_URL } from "../../../constants";
import { insertTextAtCursor } from "../../../utils/aiprdescription/cursorPositionInsert";
import { getAIDescriptionConfig } from "../../../utils/aiprdescription/descriptionconfig";
import { isLoggedIn } from "../../../utils/checkAuthentication";
import { getAuthToken, isLoggedIn } from "../../../utils/checkAuthentication";

export const DescriptionGeneratorButton = () => {
const descriptionGeneratorButton = createHtmlElement("a", {
Expand Down Expand Up @@ -42,14 +42,17 @@ const handleSubmit = async () => {
}
logo.classList.toggle("animate-spin");
const [diff, commitMessages] = await getDescriptionContext(url, descriptionConfig.config.source);

if (!isContextWithinBounds([diff, commitMessages], descriptionConfig.config.maxInputLength)) {
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(
descriptionConfig.config.openai_api_key,
OPEN_AI_COMPLETION_MODEL_NAME,
token,
descriptionConfig.config.language,
descriptionConfig.config.length,
descriptionConfig.config.temperature / 10,
Expand All @@ -64,7 +67,7 @@ descriptionConfig.config.openai_api_key,
}
const textArea = document.getElementsByName(GITHUB_PR_COMMENT_TEXT_AREA_SELECTOR)[0] as HTMLTextAreaElement;

void insertAtCursorFromStream(textArea, descriptionStream);
void insertTextAtCursor(textArea, descriptionStream);
} catch (error: unknown) {
if (error instanceof Error) {
console.error("Description generation error:", error.message);
Expand Down
28 changes: 1 addition & 27 deletions src/pages/aiprdescription.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useContext, useEffect, useReducer, useState } from "react";
import { BiInfoCircle } from "react-icons/bi";
import { FaChevronLeft } from "react-icons/fa";
import OpenSaucedLogo from "../assets/opensauced-logo.svg";
import { RouteContext } from "../App";
Expand Down Expand Up @@ -35,15 +34,13 @@ const AIPRDescription = () => {

dispatch({ type: "SET", value: configData });
setFormDisabled(!configData?.enabled);
console.log(config.config.openai_api_key);
};

void descriptionConfig();
}, []);

const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const openai_api_key = refs.openai_api_key!.getAttribute("value")!;
const length = parseInt(refs.length?.getAttribute("value")!);
const temperature = Number(Number(refs.temperature?.getAttribute("value")!));
const maxInputLength = parseInt(refs.maxInputLength?.getAttribute("value")!);
Expand All @@ -53,7 +50,7 @@ const AIPRDescription = () => {

void setAIDescriptionConfig({
enabled: true,
config: { openai_api_key, length, temperature, maxInputLength, language, source, tone },
config: { length, temperature, maxInputLength, language, source, tone },
});
toast.success("Configuration updated!");
};
Expand Down Expand Up @@ -111,29 +108,6 @@ const AIPRDescription = () => {
OpenSauced AI
</h1>

<p className="mb-2 text-gray-300 text-sm">
OpenAI API Key
<BiInfoCircle
className="inline-block ml-1 text-gray-400 align-middle cursor-pointer"
title="Find your API key here"
onClick={() =>
window.open(
"https://platform.openai.com/account/api-keys",
"_blank",
)}
/>
</p>

<input
ref={setRefFromKey("openai_api_key")}
className="p-1.5 rounded-md mb-2 w-full text-black"
pattern="sk-[a-zA-Z0-9]{40,55}"
placeholder="sk-xxxxxxxxxxxxxxxxx"
type="password"
value={config.config.openai_api_key}
onChange={e => dispatch({ type: "SET_OPENAI_API_KEY", value: e.currentTarget.value })}
/>

<div className="grid grid-cols-2 -mx-4 mb-4 text-gray-300 text-sm">
<div className="flex flex-col items-center justify-center">
<p>
Expand Down
3 changes: 0 additions & 3 deletions src/utils/aiprdescription/configurationReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ export const configurationReducer = (state: DescriptionConfig, action: { type: s
case "SET":
newState = action.value;
break;
case "SET_OPENAI_API_KEY":
newState.config.openai_api_key = action.value;
break;
case "SET_LENGTH":
newState.config.length = action.value;
break;
Expand Down
16 changes: 14 additions & 2 deletions src/utils/aiprdescription/cursorPositionInsert.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
// This function is used to insert text at the cursor position in the text area
export const insertTextAtCursor = async (textArea: HTMLTextAreaElement, text: string) => {
let length = 0;
const typewriter = setInterval(() => {
textArea.setRangeText(text[length++], textArea.selectionStart, textArea.selectionEnd, "end");
if (length >= text.length) {
clearInterval(typewriter);
textArea.setRangeText("\n\n_Description generated using [OpenSauced](https://opensauced.ai/)._", textArea.selectionStart, textArea.selectionEnd, "end");
}
}, 10);

};

export const insertAtCursorFromStream = async (textArea: HTMLTextAreaElement, stream: ReadableStream<Uint8Array>) => {
const reader = stream.getReader();
const decoder = new TextDecoder("utf-8");
Expand All @@ -15,5 +27,5 @@ export const insertAtCursorFromStream = async (textArea: HTMLTextAreaElement, st
textArea.setRangeText(chunk, start, end, "end");
}
}
textArea.setRangeText("\n\n_Description generated using [OpenSauced](https://opensauced.ai/_).", textArea.selectionStart, textArea.selectionEnd, "end");
};
textArea.setRangeText("\n\n_Description generated using [OpenSauced](https://opensauced.ai/)._", textArea.selectionStart, textArea.selectionEnd, "end");
};
2 changes: 0 additions & 2 deletions src/utils/aiprdescription/descriptionconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export type DescriptionLanguage =
export interface DescriptionConfig {
enabled: boolean;
config: {
openai_api_key: string;
length: number;
maxInputLength: number;
temperature: number;
Expand Down Expand Up @@ -45,7 +44,6 @@ export const setAIDescriptionConfig = async (data: DescriptionConfig): Promise<v
export const getDefaultDescriptionConfig = (): DescriptionConfig => ({
enabled: false,
config: {
openai_api_key: "",
length: 500,
maxInputLength: 3900,
temperature: 7,
Expand Down
74 changes: 25 additions & 49 deletions src/utils/aiprdescription/openai.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,41 @@
import { OPEN_SAUCED_AI_PR_DESCRIPTION_ENDPOINT } from "../../constants";
import type { DescriptionTone } from "./descriptionconfig";
import { OpenAI, CreateChatCompletionRequest } from "openai-streams";

const generatePrompt = (
locale: string,
maxLength: number,
tone: DescriptionTone,
) => [
`Generate an apt github PR description written in present tense and ${tone} tone for the given code diff/commit-messages with the specifications mentioned below`,
`Description language: ${locale}`,
`Description must be a maximum of ${maxLength} characters.`,
"Exclude anything unnecessary such as translation. Your entire response will be passed directly into a pull request description",
].join("\n");

const createChatCompletion = async (
apiKey: string,
json: CreateChatCompletionRequest,
): Promise<ReadableStream<Uint8Array>> => {
const stream = await OpenAI("chat", json, {
apiKey,
mode: "tokens",
});

return stream;
};

export const generateDescription = async (
apiKey: string,
model: "gpt-3.5-turbo" | "gpt-3.5-turbo-0301",
locale: string,
maxLength: number,
language: string,
descriptionLength: number,
temperature: number,
tone: DescriptionTone,
diff?: string,
commitMessages?: string[],
) => {
const content = `${diff ? `Diff: ${diff}\n` : ""}${commitMessages ? `\nCommit Messages: ${commitMessages.join(",")}` : ""}`;
): Promise<string | undefined> => {

try {
const completion = await createChatCompletion(
apiKey,
{
model,
messages: [
{
role: "system",
content: generatePrompt(locale, maxLength, tone),
},
{
role: "user",
content,
},
],
temperature,
n: 1,
const response = await fetch(OPEN_SAUCED_AI_PR_DESCRIPTION_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`
},
);
body: JSON.stringify({
descriptionLength,
temperature,
tone,
language,
diff,
commitMessages
})
});
if (response.status === 201) {
const { description } = await response.json();
return description;
}

return completion;
} catch (error: unknown) {
if (error instanceof Error) {
console.error("OpenAI error: ", error.message);
}
console.error("OpenAI error: ", error.message);
}
}
return undefined;
};
7 changes: 2 additions & 5 deletions src/utils/checkAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,8 @@ export const checkAuthentication = () => {
);
};

export const isLoggedIn = async () =>
export const isLoggedIn = async (): Promise<boolean> => Object.entries(await chrome.storage.sync.get(OPEN_SAUCED_AUTH_TOKEN_KEY)).length !== 0;

// only a valid auth token can exist in the storage due to the check in line 23
Object.entries(await chrome.storage.sync.get(OPEN_SAUCED_AUTH_TOKEN_KEY)).length !== 0;

export const getAuthToken = async () => (await chrome.storage.sync.get(OPEN_SAUCED_AUTH_TOKEN_KEY))[OPEN_SAUCED_AUTH_TOKEN_KEY];
export const getAuthToken = async (): Promise<string> => (await chrome.storage.sync.get(OPEN_SAUCED_AUTH_TOKEN_KEY))[OPEN_SAUCED_AUTH_TOKEN_KEY];


Loading

0 comments on commit 1448ea2

Please sign in to comment.