Skip to content

Commit 429573b

Browse files
committed
feat: remove State and add Overleaf specific tools
1 parent dcf0d15 commit 429573b

File tree

8 files changed

+204
-89
lines changed

8 files changed

+204
-89
lines changed

src/action.ts

Lines changed: 43 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { CallerType, Scope, ToolType } from "./enum";
2-
import { State } from "./state";
32
import { Tool } from "./tool";
3+
import { isOverleafDocument } from "./util";
44

5-
export const replaceSelectedText = (
6-
state: State,
7-
parameters: { newText: string | string[] },
8-
): void => {
5+
import * as Overleaf from "./overleaf/action";
6+
7+
export * as Overleaf from "./overleaf/action";
8+
9+
export const replaceSelectedText = (parameters: {
10+
newText: string | string[];
11+
}): void => {
912
const { newText } = parameters;
10-
const selection = state.currentSelection;
13+
const selection = window.getSelection();
1114
if (!newText || !selection) {
1215
return;
1316
}
@@ -17,7 +20,7 @@ export const replaceSelectedText = (
1720
if (Array.isArray(newText)) {
1821
const fragment = document.createDocumentFragment();
1922
newText.forEach((text) =>
20-
fragment.appendChild(document.createTextNode(text)),
23+
fragment.appendChild(document.createTextNode(text))
2124
);
2225
range.insertNode(fragment);
2326
} else {
@@ -27,46 +30,32 @@ export const replaceSelectedText = (
2730
}
2831
};
2932

30-
export const appendTextToDocument = (
31-
state: State,
32-
parameters: { text: string },
33-
): void => {
34-
if (window.location.hostname.includes("overleaf.com")) {
35-
const { text } = parameters;
36-
const editorElement = document.querySelector(".cm-content");
37-
if (editorElement) {
38-
const textNode = document.createTextNode(text);
39-
editorElement.appendChild(textNode);
40-
41-
// Scroll to bottom
42-
const scroller = document.querySelector(".cm-scroller");
43-
if (scroller) {
44-
scroller.scrollTo({ top: scroller.scrollHeight, behavior: "smooth" });
45-
}
46-
}
33+
export const insertText = (parameters: {
34+
textToInsert: string;
35+
position: "beginning" | "end" | "cursor";
36+
}): void => {
37+
if (isOverleafDocument()) {
38+
return Overleaf.insertText(parameters);
4739
} else {
48-
throw new Error("Not Implemented");
40+
throw new Error("Action is not implemented");
4941
}
5042
};
5143

52-
export async function createGoogleCalendarEvent(
53-
state: State,
54-
parameters: {
55-
token?: string;
56-
summary: string;
57-
location?: string;
58-
description?: string;
59-
startDateTime: string;
60-
endDateTime: string;
61-
timeZone?: string;
62-
},
63-
) {
44+
export async function createGoogleCalendarEvent(parameters: {
45+
token?: string;
46+
summary: string;
47+
location?: string;
48+
description?: string;
49+
startDateTime: string;
50+
endDateTime: string;
51+
timeZone?: string;
52+
}) {
6453
let { token } = parameters;
6554

6655
if (!token) {
6756
// try to get token by using Chrome Identity API
6857
console.log(
69-
"`token` not specified, trying retrieving through Google identity API OAuth flow...",
58+
"`token` not specified, trying retrieving through Google identity API OAuth flow..."
7059
);
7160
try {
7261
const authResult = await chrome.identity.getAuthToken({
@@ -77,7 +66,7 @@ export async function createGoogleCalendarEvent(
7766
} catch (e) {
7867
throw new Error(
7968
"createGoogleCalendarEvent: `token` must be specified in parameters or `identity` permission must be added to the extension manifest.\n" +
80-
e,
69+
e
8170
);
8271
}
8372
}
@@ -111,7 +100,7 @@ export async function createGoogleCalendarEvent(
111100
"Content-Type": "application/json",
112101
},
113102
body: JSON.stringify(event),
114-
},
103+
}
115104
);
116105

117106
if (!response.ok) {
@@ -153,29 +142,34 @@ export const actions: Record<string, Tool> = {
153142
caller: CallerType.ContentScript,
154143
implementation: replaceSelectedText,
155144
},
156-
appendTextToDocument: {
157-
name: "appendTextToDocument",
158-
displayName: "Append Text To Document",
159-
description: "Append text content to the end of the document.",
145+
insertText: {
146+
name: "insertText",
147+
displayName: "Insert Text",
148+
description:
149+
"Insert the specified text at a given position relative to the document: at the beginning, end, or cursor position.",
160150
schema: {
161151
type: "function",
162152
function: {
163-
name: "appendTextToDocument",
153+
name: "insertText",
164154
description:
165-
"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.",
155+
"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).",
166156
parameters: {
167157
type: "object",
168158
properties: {
169-
text: { type: "string" },
159+
textToInsert: { type: "string" },
160+
position: {
161+
type: "string",
162+
enum: ["beginning", "end", "cursor"],
163+
},
170164
},
171-
required: ["text"],
165+
required: ["textToInsert", "position"],
172166
},
173167
},
174168
},
175169
type: ToolType.Action,
176170
scope: [Scope.Overleaf],
177171
caller: CallerType.ContentScript,
178-
implementation: appendTextToDocument,
172+
implementation: insertText,
179173
},
180174
createGoogleCalendarEvent: {
181175
name: "createGoogleCalendarEvent",

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
export { retrievers } from "./retriever";
22
export { actions } from "./action";
33
export { tool, toolName, ToolName } from "./tool";
4-
export { State } from "./state";
54
export { Scope, ToolType, CallerType } from "./enum";
65
export * as retriever from "./retriever";
76
export * as action from "./action";

src/overleaf/action.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
export function insertText(parameters: {
2+
textToInsert: string;
3+
position: 'beginning' | 'end' | 'cursor',
4+
}) {
5+
const { textToInsert, position = 'cursor' } = parameters;
6+
7+
if (position === "beginning") {
8+
const editorElement = document.querySelector(".cm-content");
9+
if (editorElement) {
10+
const textNode = document.createTextNode(textToInsert);
11+
editorElement.prepend(textNode);
12+
13+
// Scroll to bottom
14+
const scroller = document.querySelector(".cm-scroller");
15+
if (scroller) {
16+
scroller.scrollTo({ top: 0, behavior: "smooth" });
17+
}
18+
}
19+
}
20+
else if (position === 'end') {
21+
const editorElement = document.querySelector(".cm-content");
22+
if (editorElement) {
23+
const textNode = document.createTextNode(textToInsert);
24+
editorElement.appendChild(textNode);
25+
26+
// Scroll to start
27+
const scroller = document.querySelector(".cm-scroller");
28+
if (scroller) {
29+
scroller.scrollTo({ top: scroller.scrollHeight, behavior: "smooth" });
30+
}
31+
}
32+
} else if (position === "cursor") {
33+
const selection = window.getSelection();
34+
35+
if (!selection?.rangeCount) {
36+
console.error("No cursor location available");
37+
return;
38+
}
39+
40+
// Get the range of the current selection or cursor position
41+
const range = selection.getRangeAt(0);
42+
43+
// Extract the currently selected content (if any)
44+
const selectedContent = range.cloneContents();
45+
46+
// Create a document fragment to hold the new content
47+
const fragment = document.createDocumentFragment();
48+
49+
// Create a text node for the text to insert before the selection
50+
if (textToInsert) {
51+
fragment.appendChild(document.createTextNode(textToInsert));
52+
}
53+
54+
// Append the selected content to the fragment
55+
if (selectedContent) {
56+
fragment.appendChild(selectedContent);
57+
}
58+
59+
// Insert the fragment into the range
60+
range.deleteContents(); // Remove the current selection
61+
range.insertNode(fragment);
62+
63+
// Move the cursor to the end of the inserted content
64+
range.collapse(false); // Collapse the range to its end
65+
66+
// Clear the selection and set the updated range
67+
selection.removeAllRanges();
68+
selection.addRange(range);
69+
}
70+
}

src/overleaf/retriever.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
export function getEditorContext() {
2+
// Identify the contenteditable container
3+
const contentEditableElement = document.querySelector('.cm-content');
4+
5+
if (!contentEditableElement) {
6+
console.error('Editable area not found.');
7+
return null;
8+
}
9+
10+
// Get the selection object
11+
const selection = window.getSelection();
12+
13+
if (!selection?.rangeCount) {
14+
console.warn('No selection or cursor found.');
15+
return null;
16+
}
17+
18+
// Get the active range (selection or cursor position)
19+
const range = selection.getRangeAt(0);
20+
21+
// Check if the selection is within the editable area
22+
if (!contentEditableElement.contains(range.startContainer)) {
23+
console.warn('Selection is outside the editable area.');
24+
return null;
25+
}
26+
27+
// Get the selected text (if any)
28+
const selectedText = selection.toString();
29+
30+
// Get text content before the cursor/selection
31+
const beforeCursorRange = document.createRange();
32+
beforeCursorRange.setStart(contentEditableElement, 0);
33+
beforeCursorRange.setEnd(range.startContainer, range.startOffset);
34+
const textBeforeCursor = beforeCursorRange.toString();
35+
36+
// Get text content after the cursor/selection
37+
const afterCursorRange = document.createRange();
38+
afterCursorRange.setStart(range.endContainer, range.endOffset);
39+
afterCursorRange.setEnd(contentEditableElement, contentEditableElement.childNodes.length);
40+
const textAfterCursor = afterCursorRange.toString();
41+
42+
return {
43+
selectedText: selectedText,
44+
textBeforeCursor: textBeforeCursor,
45+
textAfterCursor: textAfterCursor,
46+
cursorPosition: range.startOffset // Cursor offset in the start container
47+
};
48+
}

src/retriever.ts

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,44 @@
1+
import * as Overleaf from "./overleaf/retriever";
12
import { CallerType, Scope, ToolType } from "./enum";
2-
import { State } from "./state";
33
import { Tool } from "./tool";
4+
import { isOverleafDocument } from "./util";
45

5-
export const getSelectedText = (state: State, parameters: {}): string => {
6-
if (!state.currentSelection) {
6+
export * as Overleaf from "./overleaf/retriever";
7+
8+
export const getSelectedText = (parameters: {}): string => {
9+
const selection = window.getSelection();
10+
if (!selection) {
711
return "";
812
}
9-
return state.currentSelection.toString();
13+
return selection.toString();
1014
};
1115

12-
export const getPageContent = (): string => {
16+
export const getPageContext = (): string => {
17+
const pageTitle = document.title;
18+
const metaDescription = document.querySelector("meta[name='description']");
19+
const pageDescription = metaDescription ? metaDescription.getAttribute("content") : "No description available";
20+
21+
let context: any = {
22+
"url": window.location.href,
23+
"title": pageTitle,
24+
"description": pageDescription,
25+
}
26+
if (isOverleafDocument()) {
27+
context = {
28+
...context,
29+
...Overleaf.getEditorContext()
30+
}
31+
}
1332
if (document) {
14-
return document.body.innerText || "";
33+
context = {
34+
...context,
35+
content: document.body.innerText || "",
36+
}
1537
}
16-
return "";
38+
return JSON.stringify(context);
1739
};
1840

19-
export async function getCalendarEvents(
20-
state: State,
21-
parameters: { token?: string },
22-
) {
41+
export async function getCalendarEvents(parameters: { token?: string }) {
2342
let { token } = parameters;
2443

2544
if (!token) {
@@ -33,7 +52,7 @@ export async function getCalendarEvents(
3352
} catch (e) {
3453
throw new Error(
3554
"getCalendarEvents: `token` must be specified in parameters or `identity` permission must be added to the extension manifest.\n" +
36-
e,
55+
e
3756
);
3857
}
3958
}
@@ -94,23 +113,23 @@ export const retrievers: Record<string, Tool> = {
94113
caller: CallerType.Any,
95114
implementation: getSelectedText,
96115
},
97-
getPageContent: {
98-
name: "getPageContent",
99-
displayName: "Get Page Content",
100-
description: "Fetch the entire text content of the current webpage.",
116+
getPageContext: {
117+
name: "getPageContext",
118+
displayName: "Get Page Context",
119+
description: "Get context information regarding the current page which the user is currently viewing.",
101120
schema: {
102121
type: "function",
103122
function: {
104-
name: "getPageContent",
123+
name: "getPageContext",
105124
description:
106-
"getPageContent() -> str - Fetches the entire text content of the current webpage.\n\n Returns:\n str: The entire text content of the webpage.",
125+
"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.",
107126
parameters: { type: "object", properties: {}, required: [] },
108127
},
109128
},
110129
type: ToolType.Retriever,
111130
scope: Scope.Any,
112131
caller: CallerType.ContentScript,
113-
implementation: getPageContent,
132+
implementation: getPageContext,
114133
},
115134
getGoogleCalendarEvents: {
116135
name: "getGoogleCalendarEvents",

0 commit comments

Comments
 (0)