Skip to content

Commit

Permalink
feat: enable AI description generator on comments (#285)
Browse files Browse the repository at this point in the history
* feat: show PR description generator on comments

* feat: generalize the ai description generator

* chore: add inject on new page

* chore: lint the files

* chore: format

* chore: format

* Update GitHub constants and fix text area bug in
DescriptionGeneratorButton

* chore: format

* feat: enable comments button with focus and blur field

* chore: format lint
  • Loading branch information
a0m0rajab authored Jan 12, 2024
1 parent 57ff4f1 commit a30380b
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 23 deletions.
1 change: 0 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export const GITHUB_PROFILE_EDIT_MENU_CLASS = "button.js-profile-editable-edit-b
export const GITHUB_PR_COMMENT_HEADER_CLASS = "timeline-comment-header clearfix d-flex";
export const GITHUB_REVIEW_SUGGESTION_CLASS = "js-suggestion-button-placeholder";
export const GITHUB_REPO_ACTIONS_SELECTOR = ".pagehead-actions";
export const GITHUB_PR_COMMENT_TEXT_AREA_CLASS = "pull_request[body]";
export const GITHUB_PR_SUGGESTION_TEXT_AREA_Attribute = "[name='comment[body]']";
export const GITHUB_PR_BASE_BRANCH_CLASS = "css-truncate css-truncate-target";
export const LINKEDIN_PROJECT_FORM_SELECTOR = ".artdeco-text-input--input";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ 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 { GITHUB_PR_COMMENT_TEXT_AREA_CLASS } from "../../../constants";
import { insertTextAtCursor } from "../../../utils/ai-utils/cursorPositionInsert";
import { getAIDescriptionConfig } from "../../../utils/ai-utils/descriptionconfig";
import { getAuthToken, isLoggedIn, optLogIn } from "../../../utils/checkAuthentication";

export const DescriptionGeneratorButton = () => {
export const DescriptionGeneratorButton = (number: number) => {
const descriptionGeneratorButton = createHtmlElement("a", {
id: "ai-description-button",
id: `ai-description-button-${number}`,
innerHTML: `<span id="ai-description-gen" class="toolbar-item btn-octicon">
<img class="octicon octicon-heading" height="16px" width="16px" id="ai-description-button-logo" src=${chrome.runtime.getURL(openSaucedLogoIcon)}>
</span>
Expand All @@ -21,41 +20,39 @@ export const DescriptionGeneratorButton = () => {
return descriptionGeneratorButton;
};

const handleSubmit = async () => {
const logo = document.getElementById("ai-description-button-logo");
const button = document.getElementById("ai-description-button");
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();
}

if (!logo || !button) {
return;
}

const descriptionConfig = await getAIDescriptionConfig();

if (!descriptionConfig) {
return;
}

logo.classList.toggle("animate-spin");
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");
logo?.classList.toggle("animate-spin");
button.classList.toggle("pointer-events-none");
const textArea = button.closest(".Box.CommentBox")?.querySelector("textarea");

const textArea = document.getElementsByName(GITHUB_PR_COMMENT_TEXT_AREA_CLASS)[0] as HTMLTextAreaElement;

insertTextAtCursor(textArea, descriptionStream);
if (textArea) {
insertTextAtCursor(textArea, descriptionStream);
}
} catch (error: unknown) {
logo?.classList.toggle("animate-spin");
button?.classList.toggle("pointer-events-none");
button.classList.toggle("pointer-events-none");

if (error instanceof Error) {
alert(error.message);
Expand Down
1 change: 1 addition & 0 deletions src/content-scripts/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const processGithubPage = async () => {
prReviewWatch(injectChangeSuggestorButton, 500);
} else if (isGithubPullRequestPage(window.location.href)) {
prEditWatch(injectDescriptionGeneratorButton, 500);
void injectDescriptionGeneratorButton();
void injectAddPRToHighlightsButton();
} else if (isGithubProfilePage(window.location.href)) {
const username = getGithubUsername(window.location.href);
Expand Down
5 changes: 4 additions & 1 deletion src/utils/ai-utils/cursorPositionInsert.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// This function is used to insert text at the cursor position in the text area
export const insertTextAtCursor = (textArea: HTMLTextAreaElement, text: string) => {
let length = 0;

textArea.focus();
const typewriter = setInterval(() => {
textArea.setRangeText(text[length++], textArea.selectionStart, textArea.selectionEnd, "end");
if (length >= text.length) {
clearInterval(typewriter);
textArea.setRangeText("\n\n_Generated using [OpenSauced](https://opensauced.ai/)._", textArea.selectionStart, textArea.selectionEnd, "end");
textArea.ownerDocument.execCommand("insertText", false, "\n\n_Generated using [OpenSauced](https://opensauced.ai/)_");
textArea.blur();
}
}, 10);
};
12 changes: 7 additions & 5 deletions src/utils/dom-utils/addDescriptionGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ const injectDescriptionGeneratorButton = async () => {
}
}

const firstPrDescription = document.querySelector(".ActionBar-item-container");
const firstPrDescription = document.querySelectorAll(".ActionBar-item-container");

if (firstPrDescription && !firstPrDescription.querySelector("#ai-description-button")) {
const addGeneratorButton = DescriptionGeneratorButton();
firstPrDescription.forEach((item, index) => {
if (!item.querySelector(`#ai-description-button-${index}`)) {
const addGeneratorButton = DescriptionGeneratorButton(index);

firstPrDescription.insertBefore(addGeneratorButton, firstPrDescription.firstChild);
}
item.insertBefore(addGeneratorButton, item.firstChild);
}
});
};

export default injectDescriptionGeneratorButton;

0 comments on commit a30380b

Please sign in to comment.