Skip to content

Commit

Permalink
feat: remove State and add Overleaf specific tools
Browse files Browse the repository at this point in the history
  • Loading branch information
Neet-Nestor committed Dec 3, 2024
1 parent dcf0d15 commit 429573b
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 89 deletions.
92 changes: 43 additions & 49 deletions src/action.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { CallerType, Scope, ToolType } from "./enum";
import { State } from "./state";
import { Tool } from "./tool";
import { isOverleafDocument } from "./util";

export const replaceSelectedText = (
state: State,
parameters: { newText: string | string[] },
): void => {
import * as Overleaf from "./overleaf/action";

export * as Overleaf from "./overleaf/action";

export const replaceSelectedText = (parameters: {
newText: string | string[];
}): void => {
const { newText } = parameters;
const selection = state.currentSelection;
const selection = window.getSelection();
if (!newText || !selection) {
return;
}
Expand All @@ -17,7 +20,7 @@ export const replaceSelectedText = (
if (Array.isArray(newText)) {
const fragment = document.createDocumentFragment();
newText.forEach((text) =>
fragment.appendChild(document.createTextNode(text)),
fragment.appendChild(document.createTextNode(text))
);
range.insertNode(fragment);
} else {
Expand All @@ -27,46 +30,32 @@ export const replaceSelectedText = (
}
};

export const appendTextToDocument = (
state: State,
parameters: { text: string },
): void => {
if (window.location.hostname.includes("overleaf.com")) {
const { text } = parameters;
const editorElement = document.querySelector(".cm-content");
if (editorElement) {
const textNode = document.createTextNode(text);
editorElement.appendChild(textNode);

// Scroll to bottom
const scroller = document.querySelector(".cm-scroller");
if (scroller) {
scroller.scrollTo({ top: scroller.scrollHeight, behavior: "smooth" });
}
}
export const insertText = (parameters: {
textToInsert: string;
position: "beginning" | "end" | "cursor";
}): void => {
if (isOverleafDocument()) {
return Overleaf.insertText(parameters);
} else {
throw new Error("Not Implemented");
throw new Error("Action is not implemented");
}
};

export async function createGoogleCalendarEvent(
state: State,
parameters: {
token?: string;
summary: string;
location?: string;
description?: string;
startDateTime: string;
endDateTime: string;
timeZone?: string;
},
) {
export async function createGoogleCalendarEvent(parameters: {
token?: string;
summary: string;
location?: string;
description?: string;
startDateTime: string;
endDateTime: string;
timeZone?: string;
}) {
let { token } = parameters;

if (!token) {
// try to get token by using Chrome Identity API
console.log(
"`token` not specified, trying retrieving through Google identity API OAuth flow...",
"`token` not specified, trying retrieving through Google identity API OAuth flow..."
);
try {
const authResult = await chrome.identity.getAuthToken({
Expand All @@ -77,7 +66,7 @@ export async function createGoogleCalendarEvent(
} catch (e) {
throw new Error(
"createGoogleCalendarEvent: `token` must be specified in parameters or `identity` permission must be added to the extension manifest.\n" +
e,
e
);
}
}
Expand Down Expand Up @@ -111,7 +100,7 @@ export async function createGoogleCalendarEvent(
"Content-Type": "application/json",
},
body: JSON.stringify(event),
},
}
);

if (!response.ok) {
Expand Down Expand Up @@ -153,29 +142,34 @@ export const actions: Record<string, Tool> = {
caller: CallerType.ContentScript,
implementation: replaceSelectedText,
},
appendTextToDocument: {
name: "appendTextToDocument",
displayName: "Append Text To Document",
description: "Append text content to the end of the document.",
insertText: {
name: "insertText",
displayName: "Insert Text",
description:
"Insert the specified text at a given position relative to the document: at the beginning, end, or cursor position.",
schema: {
type: "function",
function: {
name: "appendTextToDocument",
name: "insertText",
description:
"appendTextToDocument(text: str) - Add some text content to the end of the document.\\n\\n Args:\\n text (str): Text content to be added to the end of the document.",
"insertText(parameters: { textToInsert: str, position: 'beginning' | 'end' | 'cursor' }) - Insert text into the document at the specified position.\\n\\n Args:\\n textToInsert (str): The text content to be inserted.\\n position ('beginning' | 'end' | 'cursor'): Where to insert the text (beginning of the document, end of the document, or at the cursor position).",
parameters: {
type: "object",
properties: {
text: { type: "string" },
textToInsert: { type: "string" },
position: {
type: "string",
enum: ["beginning", "end", "cursor"],
},
},
required: ["text"],
required: ["textToInsert", "position"],
},
},
},
type: ToolType.Action,
scope: [Scope.Overleaf],
caller: CallerType.ContentScript,
implementation: appendTextToDocument,
implementation: insertText,
},
createGoogleCalendarEvent: {
name: "createGoogleCalendarEvent",
Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export { retrievers } from "./retriever";
export { actions } from "./action";
export { tool, toolName, ToolName } from "./tool";
export { State } from "./state";
export { Scope, ToolType, CallerType } from "./enum";
export * as retriever from "./retriever";
export * as action from "./action";
70 changes: 70 additions & 0 deletions src/overleaf/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
export function insertText(parameters: {
textToInsert: string;
position: 'beginning' | 'end' | 'cursor',
}) {
const { textToInsert, position = 'cursor' } = parameters;

if (position === "beginning") {
const editorElement = document.querySelector(".cm-content");
if (editorElement) {
const textNode = document.createTextNode(textToInsert);
editorElement.prepend(textNode);

// Scroll to bottom
const scroller = document.querySelector(".cm-scroller");
if (scroller) {
scroller.scrollTo({ top: 0, behavior: "smooth" });
}
}
}
else if (position === 'end') {
const editorElement = document.querySelector(".cm-content");
if (editorElement) {
const textNode = document.createTextNode(textToInsert);
editorElement.appendChild(textNode);

// Scroll to start
const scroller = document.querySelector(".cm-scroller");
if (scroller) {
scroller.scrollTo({ top: scroller.scrollHeight, behavior: "smooth" });
}
}
} else if (position === "cursor") {
const selection = window.getSelection();

if (!selection?.rangeCount) {
console.error("No cursor location available");
return;
}

// Get the range of the current selection or cursor position
const range = selection.getRangeAt(0);

// Extract the currently selected content (if any)
const selectedContent = range.cloneContents();

// Create a document fragment to hold the new content
const fragment = document.createDocumentFragment();

// Create a text node for the text to insert before the selection
if (textToInsert) {
fragment.appendChild(document.createTextNode(textToInsert));
}

// Append the selected content to the fragment
if (selectedContent) {
fragment.appendChild(selectedContent);
}

// Insert the fragment into the range
range.deleteContents(); // Remove the current selection
range.insertNode(fragment);

// Move the cursor to the end of the inserted content
range.collapse(false); // Collapse the range to its end

// Clear the selection and set the updated range
selection.removeAllRanges();
selection.addRange(range);
}
}
48 changes: 48 additions & 0 deletions src/overleaf/retriever.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export function getEditorContext() {
// Identify the contenteditable container
const contentEditableElement = document.querySelector('.cm-content');

if (!contentEditableElement) {
console.error('Editable area not found.');
return null;
}

// Get the selection object
const selection = window.getSelection();

if (!selection?.rangeCount) {
console.warn('No selection or cursor found.');
return null;
}

// Get the active range (selection or cursor position)
const range = selection.getRangeAt(0);

// Check if the selection is within the editable area
if (!contentEditableElement.contains(range.startContainer)) {
console.warn('Selection is outside the editable area.');
return null;
}

// Get the selected text (if any)
const selectedText = selection.toString();

// Get text content before the cursor/selection
const beforeCursorRange = document.createRange();
beforeCursorRange.setStart(contentEditableElement, 0);
beforeCursorRange.setEnd(range.startContainer, range.startOffset);
const textBeforeCursor = beforeCursorRange.toString();

// Get text content after the cursor/selection
const afterCursorRange = document.createRange();
afterCursorRange.setStart(range.endContainer, range.endOffset);
afterCursorRange.setEnd(contentEditableElement, contentEditableElement.childNodes.length);
const textAfterCursor = afterCursorRange.toString();

return {
selectedText: selectedText,
textBeforeCursor: textBeforeCursor,
textAfterCursor: textAfterCursor,
cursorPosition: range.startOffset // Cursor offset in the start container
};
}
57 changes: 38 additions & 19 deletions src/retriever.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
import * as Overleaf from "./overleaf/retriever";
import { CallerType, Scope, ToolType } from "./enum";
import { State } from "./state";
import { Tool } from "./tool";
import { isOverleafDocument } from "./util";

export const getSelectedText = (state: State, parameters: {}): string => {
if (!state.currentSelection) {
export * as Overleaf from "./overleaf/retriever";

export const getSelectedText = (parameters: {}): string => {
const selection = window.getSelection();
if (!selection) {
return "";
}
return state.currentSelection.toString();
return selection.toString();
};

export const getPageContent = (): string => {
export const getPageContext = (): string => {
const pageTitle = document.title;
const metaDescription = document.querySelector("meta[name='description']");
const pageDescription = metaDescription ? metaDescription.getAttribute("content") : "No description available";

let context: any = {
"url": window.location.href,
"title": pageTitle,
"description": pageDescription,
}
if (isOverleafDocument()) {
context = {
...context,
...Overleaf.getEditorContext()
}
}
if (document) {
return document.body.innerText || "";
context = {
...context,
content: document.body.innerText || "",
}
}
return "";
return JSON.stringify(context);
};

export async function getCalendarEvents(
state: State,
parameters: { token?: string },
) {
export async function getCalendarEvents(parameters: { token?: string }) {
let { token } = parameters;

if (!token) {
Expand All @@ -33,7 +52,7 @@ export async function getCalendarEvents(
} catch (e) {
throw new Error(
"getCalendarEvents: `token` must be specified in parameters or `identity` permission must be added to the extension manifest.\n" +
e,
e
);
}
}
Expand Down Expand Up @@ -94,23 +113,23 @@ export const retrievers: Record<string, Tool> = {
caller: CallerType.Any,
implementation: getSelectedText,
},
getPageContent: {
name: "getPageContent",
displayName: "Get Page Content",
description: "Fetch the entire text content of the current webpage.",
getPageContext: {
name: "getPageContext",
displayName: "Get Page Context",
description: "Get context information regarding the current page which the user is currently viewing.",
schema: {
type: "function",
function: {
name: "getPageContent",
name: "getPageContext",
description:
"getPageContent() -> str - Fetches the entire text content of the current webpage.\n\n Returns:\n str: The entire text content of the webpage.",
"getPageContext() -> str - Get context information regarding the current page which the user is currently viewing.\n\n Returns:\n str: The entire text content of the webpage.",
parameters: { type: "object", properties: {}, required: [] },
},
},
type: ToolType.Retriever,
scope: Scope.Any,
caller: CallerType.ContentScript,
implementation: getPageContent,
implementation: getPageContext,
},
getGoogleCalendarEvents: {
name: "getGoogleCalendarEvents",
Expand Down
Loading

0 comments on commit 429573b

Please sign in to comment.