Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1618679eb | ||
|
|
42960a01a2 | ||
|
|
3a09eb1432 | ||
|
|
261a34484c | ||
|
|
32b11efa1a | ||
|
|
d3bcccf4b2 | ||
|
|
d01fcafeda | ||
|
|
0af4246751 | ||
|
|
f5228daeeb | ||
|
|
1938c03a7a | ||
|
|
d61da20c03 | ||
|
|
f054228c6b | ||
|
|
def21b1450 | ||
|
|
8fe42a658a | ||
|
|
082afd778f | ||
|
|
dd19cd9fa3 | ||
|
|
eb2a9ae867 | ||
|
|
a7b540de70 | ||
|
|
116c4898d5 | ||
|
|
7c475c9de1 | ||
|
|
15319c67ef | ||
|
|
693f3f2bce | ||
|
|
a640126c2c | ||
|
|
729017fd5f | ||
|
|
f92137dcaa | ||
|
|
a39c3e9edd | ||
|
|
b01e48b032 | ||
|
|
80039a0342 | ||
|
|
cc1b0986b9 | ||
|
|
93b362ca3e | ||
|
|
d9a782036f | ||
|
|
60b84e8229 | ||
|
|
f0a5f9317f | ||
|
|
8377ef4f82 |
59
README.md
59
README.md
@@ -15,7 +15,13 @@ Just go to the [extension's page](https://chrome.google.com/webstore/detail/leaf
|
|||||||
4. Click "Load unpacked" and select the repository folder
|
4. Click "Load unpacked" and select the repository folder
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
The plugin can be configured by clicking the plugin button in the Chrome toolbar. It requires inserting an API key from [OpenAI](https://platform.openai.com/account/api-keys). You also need to choose which tools you wish to enable.
|
The plugin can be configured by clicking the plugin button in the Chrome toolbar.
|
||||||
|
|
||||||
|
It requires inserting an API key from [OpenAI](https://platform.openai.com/account/api-keys).
|
||||||
|
|
||||||
|
You also need to choose which tools you wish to enable. See the [Issues](#issues) section if you have problems activating the tools.
|
||||||
|
|
||||||
|
Change shortcuts: `chrome://extensions/shortcuts`
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
These are the tools that are currently available:
|
These are the tools that are currently available:
|
||||||
@@ -29,7 +35,7 @@ Select a text and press `Alt+I` to trigger the improvement tool. The original te
|
|||||||
### Ask
|
### Ask
|
||||||
Select a text and press `Alt+A` to trigger the ask tool. The original text will be deleted and the answer will be inserted in its place.
|
Select a text and press `Alt+A` to trigger the ask tool. The original text will be deleted and the answer will be inserted in its place.
|
||||||
|
|
||||||
For example: "Create a table 4x3 that the first row is bold face" will be replaced with, e.g.,:
|
For example: "Create a table 4x3 that the first row is boldface" will be replaced with, e.g.:
|
||||||
```latex
|
```latex
|
||||||
\begin{tabular}{|c|c|c|}
|
\begin{tabular}{|c|c|c|}
|
||||||
\hline
|
\hline
|
||||||
@@ -47,7 +53,7 @@ Entry 7 & Entry 8 & Entry 9\\
|
|||||||
You can then, for example:
|
You can then, for example:
|
||||||
1. Write before the table: Place the following tabular inside a table environment, center it, and give the following title: "The comparison of the three approaches"
|
1. Write before the table: Place the following tabular inside a table environment, center it, and give the following title: "The comparison of the three approaches"
|
||||||
2. Select the sentence and the table
|
2. Select the sentence and the table
|
||||||
3. Press `Alt+a` to trigger the ask tool.
|
3. Press `Alt+A` to trigger the ask tool.
|
||||||
|
|
||||||
The result will be:
|
The result will be:
|
||||||
```latex
|
```latex
|
||||||
@@ -68,8 +74,51 @@ Entry 7 & Entry 8 & Entry 9\\
|
|||||||
\end{table}
|
\end{table}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Using non-GPT Models
|
||||||
|
Other LLM deployments/models are supported as long as they can be accessed via OpenAI Chat Completion API. Some examples: [vLLM models](https://docs.vllm.ai/en/latest/getting_started/quickstart.html#using-openai-chat-api-with-vllm), [LLAMA models](https://github.com/c0sogi/llama-api#usage-chat-completion), and [easyLLM](https://philschmid.github.io/easyllm/examples/chat-completion-api/).
|
||||||
|
|
||||||
|
Use the plugin's JSON editor to change the URL and the model. See [Issue #8](https://github.com/bThink-BGU/LeafLLM/issues/8) for further details.
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
If you encounter any issues, please open an issue in the project's repository.
|
If nothing happens when you use the plugin, verify that the plugin's shortcuts are not in conflict with other plugins' shortcuts. To do so, go to `chrome://extensions/shortcuts`.
|
||||||
|
|
||||||
|
If you encounter any problem/question, please open an issue in the project's repository.
|
||||||
|
|
||||||
## Privacy
|
## Privacy
|
||||||
The plugin does not collect any data. The only data that is sent is the text that you select and the API key that you provide. The data is sent to OpenAI's servers only.
|
The plugin saves its configuration locally on the user's computer. The plugin sends the API key and the selected text to OpenAI only, and only for the purpose it was made for (i.e., completing and improving text and asking GPT questions). The plugin's authors are not responsible for what OpenAI does with this data. The plugin's authors do not collect any data from the plugin's users.
|
||||||
|
|
||||||
|
## Advanced Configuration
|
||||||
|
If you feel advanced, you can also change the request JSON sent to OpenAI and the base URL.
|
||||||
|
To do that, go to the 'Advance Configuration' component on the configuration page. By default, the value is
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"openai": {
|
||||||
|
"url": "https://api.openai.com/v1/chat/completions",
|
||||||
|
"base": {
|
||||||
|
"n": 1,
|
||||||
|
"temperature": 0.5,
|
||||||
|
"model": "gpt-3.5-turbo"
|
||||||
|
},
|
||||||
|
"Complete": {
|
||||||
|
"max_tokens": 512,
|
||||||
|
"messages": [{
|
||||||
|
"role": "system",
|
||||||
|
"content": "You are an assistant in a Latex editor that continues the given text. No need to rewrite the given text"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
"Improve": {
|
||||||
|
"messages": [{
|
||||||
|
"role": "system",
|
||||||
|
"content": "You are an assistant in a Latex editor that improves the given text"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
"Ask": {
|
||||||
|
"messages": [{
|
||||||
|
"role": "system",
|
||||||
|
"content": "You are an assistant in a Latex editor. Answer questions without introduction/explanations"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Base is the default configuration which is overridden by the specific command configuration.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"js": ["scripts/jquery.js", "scripts/content.js"],
|
"js": ["scripts/jquery.js", "scripts/content.js"],
|
||||||
"matches": ["https://*.overleaf.com/project/*"]
|
"matches": ["https://*.overleaf.com/project/*", "https://*.mildstone.org/project/*"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "LLM-based tools for Overleaf",
|
"description": "LLM-based tools for Overleaf",
|
||||||
@@ -18,21 +18,21 @@
|
|||||||
"commands": {
|
"commands": {
|
||||||
"Complete": {
|
"Complete": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Alt+C"
|
"default": "Ctrl + Shift + C"
|
||||||
},
|
},
|
||||||
"description": "Complete selected text"
|
"description": "Completa il testo selezionato"
|
||||||
},
|
},
|
||||||
"Improve": {
|
"Improve": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Alt+I"
|
"default": "Ctrl + Shift + I"
|
||||||
},
|
},
|
||||||
"description": "Improve selected text"
|
"description": "Migliora il testo selezionato"
|
||||||
},
|
},
|
||||||
"Ask": {
|
"Ask": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Alt+A"
|
"default": "Ctrl + Shift + A"
|
||||||
},
|
},
|
||||||
"description": "Use the selected text to ask GPT. It adds to the beginning of the selected text: 'In Latex, '"
|
"description": "Chiedi a Chat-GPT attraverso il testo selezionato"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"background": {
|
"background": {
|
||||||
@@ -42,5 +42,5 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "LeafLLM",
|
"name": "LeafLLM",
|
||||||
"homepage_url": "https://github.com/achiyae/LeafLLM",
|
"homepage_url": "https://github.com/achiyae/LeafLLM",
|
||||||
"version": "1.1.0"
|
"version": "1.4.1"
|
||||||
}
|
}
|
||||||
|
|||||||
6
popup/jsoneditor.min.css
vendored
Normal file
6
popup/jsoneditor.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
46
popup/jsoneditor.min.js
vendored
Normal file
46
popup/jsoneditor.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -8,14 +8,15 @@ body {
|
|||||||
.extension-title {
|
.extension-title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: #4a4a4a;
|
color: #4a4a4a;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-container {
|
.form-container {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin: 10px 0;
|
margin-bottom: 10px;
|
||||||
padding: 20px;
|
padding: 10px;
|
||||||
|
|
||||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +39,14 @@ body {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-left: 3px;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
@@ -46,9 +55,9 @@ body {
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 5px 0;
|
margin: 5px 5px 0 5px;
|
||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
width: 300px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
@@ -70,7 +79,6 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#controls {
|
#controls {
|
||||||
margin-top: 30px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -78,7 +86,8 @@ body {
|
|||||||
.control {
|
.control {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
@@ -103,7 +112,7 @@ body {
|
|||||||
.settings-text {
|
.settings-text {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: #4a4a4a;
|
color: #4a4a4a;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-container {
|
.settings-container {
|
||||||
@@ -115,7 +124,8 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 3px;
|
||||||
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-item-text {
|
.settings-item-text {
|
||||||
@@ -126,8 +136,8 @@ body {
|
|||||||
.switch {
|
.switch {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 60px;
|
width: 40px;
|
||||||
height: 34px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch input {
|
.switch input {
|
||||||
@@ -150,8 +160,8 @@ body {
|
|||||||
.slider:before {
|
.slider:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "";
|
content: "";
|
||||||
height: 26px;
|
height: 16px;
|
||||||
width: 26px;
|
width: 16px;
|
||||||
left: 4px;
|
left: 4px;
|
||||||
bottom: 4px;
|
bottom: 4px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
@@ -163,11 +173,11 @@ input:checked + .slider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input:checked + .slider:before {
|
input:checked + .slider:before {
|
||||||
transform: translateX(26px);
|
transform: translateX(16px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider.round {
|
.slider.round {
|
||||||
border-radius: 34px;
|
border-radius: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider.round:before {
|
.slider.round:before {
|
||||||
|
|||||||
100
popup/popup.html
100
popup/popup.html
@@ -1,58 +1,72 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script src="/scripts/jquery.js"></script>
|
<script src="/scripts/jquery.js"></script>
|
||||||
<script src="/popup/popup.js"></script>
|
<script src="/popup/jsoneditor.min.js"></script>
|
||||||
<link rel="stylesheet" href="/popup/popup.css" />
|
<script src="/popup/popup.js" type="module"></script>
|
||||||
</head>
|
<link rel="stylesheet" href="/popup/popup.css"/>
|
||||||
<body>
|
<link rel="stylesheet" href="/popup/jsoneditor.min.css"/>
|
||||||
<h1 class="extension-title">LeafLLM: an AI-powered Overleaf</h1>
|
</head>
|
||||||
<div id="controls" class="form-container">
|
<body>
|
||||||
<div class="control">
|
<h1 class="extension-title">LeafLLM</h1>
|
||||||
<div class="input"><span>ALT</span><span>I</span></div>
|
<div id="controls" class="form-container">
|
||||||
|
<div class="control">
|
||||||
|
<div class="input" id="shortcut-Improve"><span>Alt</span><span>I</span></div>
|
||||||
<div class="effect">Improve selected text.</div>
|
<div class="effect">Improve selected text.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="input"><span>ALT</span><span>C</span></div>
|
<div class="input" id="shortcut-Complete"><span>Alt</span><span>C</span></div>
|
||||||
<div class="effect">Complete selected text.</div>
|
<div class="effect">Complete selected text.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="input"><span>ALT</span><span>A</span></div>
|
<div class="input" id="shortcut-Ask"><span>Alt</span><span>A</span></div>
|
||||||
<div class="effect">Ask GPT.</div>
|
<div class="effect">Ask GPT.</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="settings-form" class="form-container">
|
</div>
|
||||||
<div class="settings-text">Settings</div>
|
<div id="settings-form" class="form-container">
|
||||||
<div class="settings-container">
|
<div class="settings-text">Settings</div>
|
||||||
|
<div class="settings-container">
|
||||||
<div class="settings-item">
|
<div class="settings-item">
|
||||||
<div class="settings-item-text">Text Improvement</div>
|
<div class="settings-item-text">Text Improvement</div>
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input name="text-improvement" class="settings-item-checkbox" type="checkbox" />
|
<input name="text-Improve" class="settings-item-checkbox" type="checkbox"/>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-item">
|
<div class="settings-item">
|
||||||
<div class="settings-item-text">Text Completion</div>
|
<div class="settings-item-text">Text Completion</div>
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input name="text-completion" class="settings-item-checkbox" type="checkbox" />
|
<input name="text-Complete" class="settings-item-checkbox" type="checkbox"/>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-item">
|
<div class="settings-item">
|
||||||
<div class="settings-item-text">Ask</div>
|
<div class="settings-item-text">Ask</div>
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input name="text-ask" class="settings-item-checkbox" type="checkbox" />
|
<input name="text-Ask" class="settings-item-checkbox" type="checkbox"/>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="api-token-form" class="form-container">
|
</div>
|
||||||
<div class="api-token-text"><span>API Token is </span><span class="api-token-status">not set</span></div>
|
<div id="api-token-form" class="form-container">
|
||||||
<input class="api-token-input" name="api-token" placeholder="OpenAI API Token" type="text" />
|
<div class="api-token-text"><span>API Token is </span><span class="api-token-status">not set</span></div>
|
||||||
<div class="message">To get an API key, go to <a href="https://platform.openai.com/account/api-keys">https://platform.openai.com/account/api-keys</a> </div>
|
<input class="api-token-input" name="api-token" placeholder="OpenAI API Token" type="text"/>
|
||||||
<button class="submit btn">Set API Token</button>
|
<div class="message">To get an API key, go to <a href="https://platform.openai.com/account/api-keys">https://platform.openai.com/account/api-keys</a>
|
||||||
<button class="clear btn">Reset API Token</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="message-box" class="message-box"></div>
|
<div class="btn-container">
|
||||||
</body>
|
<button class="submit btn">Set API Token</button>
|
||||||
|
<button class="clear btn">Reset API Token</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="message-box" class="message-box"></div>
|
||||||
|
<div id="configuration-form" class="form-container">
|
||||||
|
<div class="settings-text">Advanced Configuration</div>
|
||||||
|
<!-- JSON Editor Container -->
|
||||||
|
<div id="json-editor" style="height: 300px;"></div>
|
||||||
|
<div class="btn-container">
|
||||||
|
<button class="submit btn" id="saveConfig">Save config</button>
|
||||||
|
<button class="clear btn" id="resetConfig">Reset config</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
270
popup/popup.js
270
popup/popup.js
@@ -1,105 +1,241 @@
|
|||||||
const apiKeyRegex = /sk-[a-zA-Z0-9]{48}/;
|
// Changing defaultConfigurations requires changing service-worker.js
|
||||||
|
const defaultConfigurations = {
|
||||||
|
openai: {
|
||||||
|
url: 'https://api.openai.com/v1/chat/completions',
|
||||||
|
base: {
|
||||||
|
n: 1,
|
||||||
|
temperature: 0.5,
|
||||||
|
model: 'gpt-3.5-turbo'
|
||||||
|
},
|
||||||
|
Complete: {
|
||||||
|
max_tokens: 512,
|
||||||
|
messages: [{
|
||||||
|
role: 'system',
|
||||||
|
content: 'You are an assistant in a Latex editor, usually used for writing academic papers. Continues the given text. No need to rewrite the given text.'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
Improve: {
|
||||||
|
messages: [{
|
||||||
|
role: 'system',
|
||||||
|
content: 'You are an assistant in a Latex editor, usually used for writing academic papers. Style and improves the given text.'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
Ask: {
|
||||||
|
messages: [{
|
||||||
|
role: 'system',
|
||||||
|
content: 'You are an assistant in a Latex editor, usually used for writing academic papers. Address the given questions/request without introduction/explanations.'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const settings = [
|
// Changing the class here requires changing scripts/content.js
|
||||||
{ key: "textCompletion", name: "text-completion" },
|
class OpenAIAPI {
|
||||||
{ key: "textImprovement", name: "text-improvement" },
|
constructor(apiKey) {
|
||||||
{ key: "textAsk", name: "text-ask" },
|
this.apiKey = apiKey
|
||||||
];
|
}
|
||||||
|
|
||||||
|
query(url, data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const xhr = new XMLHttpRequest()
|
||||||
|
xhr.open('POST', url, true)
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/json')
|
||||||
|
xhr.setRequestHeader('Authorization', `Bearer ${this.apiKey}`)
|
||||||
|
xhr.onerror = function () {
|
||||||
|
reject('Failed to query OpenAI API: network error.')
|
||||||
|
}
|
||||||
|
xhr.onload = function () {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
let jsonResponse
|
||||||
|
try {
|
||||||
|
jsonResponse = JSON.parse(xhr.responseText)
|
||||||
|
} catch (e) {
|
||||||
|
reject('Failed to query OpenAI API, cannot parse response:\n' + e + '\n' + xhr.responseText)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (jsonResponse.hasOwnProperty('choices')) {
|
||||||
|
resolve(jsonResponse.choices)
|
||||||
|
} else {
|
||||||
|
reject('Failed to query OpenAI API: invalid response: ' + jsonResponse)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject('Failed to query OpenAI API: invalid status: ' + xhr.status + ' - ' + xhr.responseText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send(JSON.stringify(data))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async act(command, text) {
|
||||||
|
let conf = (await chrome.storage.local.get('RequestConfiguration')).RequestConfiguration.openai
|
||||||
|
let request = Object.assign({}, conf.base)
|
||||||
|
let url = conf.url
|
||||||
|
Object.assign(request, conf[command])
|
||||||
|
request.messages.push({ role: 'user', 'content': text })
|
||||||
|
return this.query(url, request)
|
||||||
|
.then(result => result[0]['message'].content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const jsonEditor = createJsonEditor()
|
||||||
|
|
||||||
|
function createJsonEditor() {
|
||||||
|
return new JSONEditor($('#json-editor')[0], {
|
||||||
|
mode: 'code', // Use code mode for better editing
|
||||||
|
onChange: function () {
|
||||||
|
$('#saveConfig').prop('disabled', false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function addMessage(message) {
|
function addMessage(message) {
|
||||||
$("#message-box").append(`<div class="message">${message}</div>`);
|
$('#message-box').append(`<div class="message">${message}</div>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function addErrorMessage(message) {
|
function addErrorMessage(message) {
|
||||||
$("#message-box").append(`<div class="error">${message}</div>`);
|
$('#message-box').append(`<div class="error">${message}</div>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearMessages() {
|
function clearMessages() {
|
||||||
$("#message-box").empty();
|
$('#message-box').empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshStorage() {
|
async function refreshStorage() {
|
||||||
chrome.storage.local.get("openAIAPIKey").then(({ openAIAPIKey }) => {
|
chrome.storage.local.get('openAIAPIKey').then(({ openAIAPIKey }) => {
|
||||||
$("#api-token-form .api-token-status").text(chrome.runtime.lastError || !openAIAPIKey ? "not set" : "set");
|
$('#api-token-form .api-token-status').text(chrome.runtime.lastError || !openAIAPIKey ? 'not set' : 'set')
|
||||||
});
|
})
|
||||||
|
|
||||||
chrome.storage.local.get(settings.map(({ key }) => key)).then((storage) => {
|
const commands = await chrome.commands.getAll()
|
||||||
settings.forEach(({ key, name }) => {
|
|
||||||
$(`#settings-form input[name='${name}']:checkbox`).prop("checked", storage[key]);
|
chrome.storage.local.get(['RequestConfiguration']).then((settings) => {
|
||||||
});
|
jsonEditor.set(settings.RequestConfiguration)
|
||||||
});
|
$('#saveConfig').prop('disabled', true);
|
||||||
|
})
|
||||||
|
|
||||||
|
chrome.storage.local.get(['Improve', 'Complete', 'Ask']).then((settings) => {
|
||||||
|
Object.values(settings).forEach(setting => {
|
||||||
|
let command = commands.filter(({ name }) => name === setting.key)[0]
|
||||||
|
if (command.shortcut !== setting.shortcut) {
|
||||||
|
setting.shortcut = command.shortcut
|
||||||
|
if (setting.status === 'enabled' && setting.shortcut === '') {
|
||||||
|
setting.status = 'error'
|
||||||
|
}
|
||||||
|
chrome.storage.local.set({ [setting.key]: setting })
|
||||||
|
} else if (setting.status === 'enabled' && setting.shortcut === '') {
|
||||||
|
setting.status = 'error'
|
||||||
|
chrome.storage.local.set({ [setting.key]: setting })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let bindingFailures = Object.values(settings)
|
||||||
|
.filter(({ status }) => status === 'error')
|
||||||
|
.map(({ key }) => `${key}`)
|
||||||
|
.join(', ')
|
||||||
|
if (bindingFailures.length > 0) {
|
||||||
|
addErrorMessage(`Could not bind the following shortcuts:\n${bindingFailures}.\nYou can set it manually at <a href="chrome://extensions/shortcuts">chrome://extensions/shortcuts</a>.`)
|
||||||
|
}
|
||||||
|
Object.values(settings).forEach(({ key, status, shortcut }) => {
|
||||||
|
$(`#settings-form input[name='text-${key}']:checkbox`).prop('checked', status === 'enabled')
|
||||||
|
let shortcut2 = shortcut === '' ? 'not set' : shortcut
|
||||||
|
$(`#shortcut-${key}`).html(`<span>${shortcut2}</span>`)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleAPITokenSet(event) {
|
async function handleAPITokenSet(event) {
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
event.stopPropagation();
|
event.stopPropagation()
|
||||||
|
|
||||||
clearMessages();
|
clearMessages()
|
||||||
|
|
||||||
const input = $("#api-token-form").find("input[name='api-token']");
|
const input = $('#api-token-form').find('input[name=\'api-token\']')
|
||||||
const openAIAPIKey = input.val();
|
const openAIAPIKey = input.val()
|
||||||
input.val("");
|
input.val('')
|
||||||
|
|
||||||
if (!openAIAPIKey || !apiKeyRegex.test(openAIAPIKey)) {
|
if (!openAIAPIKey) {
|
||||||
addErrorMessage("Invalid API Token.");
|
addErrorMessage('Invalid API Token.')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await chrome.storage.local.set({ openAIAPIKey });
|
await validateAPIKey(openAIAPIKey)
|
||||||
} catch (error) {
|
}catch (e) {
|
||||||
console.log(error);
|
addErrorMessage(`Failed to validate API Token. Error: ${e}`)
|
||||||
addErrorMessage("Failed to set API Token.");
|
return
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await refreshStorage();
|
chrome.storage.local.set({ openAIAPIKey })
|
||||||
|
.then(refreshStorage)
|
||||||
|
.catch((error) => addErrorMessage(`Failed to remove API Token. Error: ${error}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateAPIKey(apiKey) {
|
||||||
|
const openAI = new OpenAIAPI(apiKey)
|
||||||
|
return openAI.act('Ask', 'write a random latex command. do not explain it.')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleAPITokenClear(event) {
|
async function handleAPITokenClear(event) {
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
event.stopPropagation();
|
event.stopPropagation()
|
||||||
|
|
||||||
clearMessages();
|
clearMessages()
|
||||||
|
|
||||||
try {
|
chrome.storage.local.remove('openAIAPIKey')
|
||||||
await chrome.storage.local.remove("openAIAPIKey");
|
.then(refreshStorage)
|
||||||
} catch (error) {
|
.catch((error) => addErrorMessage(`Failed to remove API Token. Error: ${error}`))
|
||||||
console.log(error);
|
|
||||||
addErrorMessage("Failed to remove API Token.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await refreshStorage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeHandleSettingChange(key) {
|
function makeHandleSettingChange(key) {
|
||||||
return async (event) => {
|
return async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
event.stopPropagation();
|
event.stopPropagation()
|
||||||
|
clearMessages()
|
||||||
|
|
||||||
clearMessages();
|
const value = event.target.checked
|
||||||
|
const setting = await chrome.storage.local.get(key)
|
||||||
|
/* let commandKey = await chrome.commands.getAll()
|
||||||
|
commandKey = commandKey.filter(({ name }) => name === key)[0]*/
|
||||||
|
// if (setting[key].status !== 'error') {
|
||||||
|
setting[key].status = value ? 'enabled' : 'disabled'
|
||||||
|
await chrome.storage.local.set({ [key]: setting[key] })
|
||||||
|
// }
|
||||||
|
return refreshStorage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const value = event.target.checked;
|
async function saveConfig() {
|
||||||
|
clearMessages()
|
||||||
try {
|
let config;
|
||||||
await chrome.storage.local.set({ [key]: value });
|
try {
|
||||||
} catch (error) {
|
config = jsonEditor.get()
|
||||||
console.log(error);
|
} catch (e) {
|
||||||
addErrorMessage(`Failed to set ${key} setting.`);
|
addErrorMessage(`Failed to parse configuration. Error: ${e}`)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
chrome.storage.local.set({ RequestConfiguration: config })
|
||||||
await refreshStorage();
|
.then(refreshStorage)
|
||||||
};
|
.catch((error) => addErrorMessage(`Failed to save configuration. Error: ${error}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(async function () {
|
$(document).ready(async function () {
|
||||||
$("#api-token-form .submit").on("click", handleAPITokenSet);
|
$('#api-token-form .submit').on('click', handleAPITokenSet)
|
||||||
$("#api-token-form .clear").on("click", handleAPITokenClear);
|
$('#api-token-form .clear').on('click', handleAPITokenClear)
|
||||||
|
|
||||||
settings.forEach(({ key, name }) =>
|
let commands = ['Improve', 'Complete', 'Ask']
|
||||||
$(`#settings-form input[name='${name}']:checkbox`).on("change", makeHandleSettingChange(key))
|
commands.forEach((key) => {
|
||||||
);
|
$(`#settings-form input[name='text-${key}']:checkbox`).on('change', makeHandleSettingChange(key))
|
||||||
await refreshStorage();
|
})
|
||||||
});
|
|
||||||
|
$('body').on('click', 'a', function () {
|
||||||
|
chrome.tabs.create({ url: $(this).attr('href') })
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
$('#resetConfig').on('click', async function () {
|
||||||
|
jsonEditor.set(defaultConfigurations)
|
||||||
|
await saveConfig()
|
||||||
|
})
|
||||||
|
|
||||||
|
$('#saveConfig').on('click', saveConfig)
|
||||||
|
return refreshStorage()
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,61 +1,49 @@
|
|||||||
|
// Changing the class here requires changing popup/popup.js
|
||||||
class OpenAIAPI {
|
class OpenAIAPI {
|
||||||
static defaultModel = 'text-davinci-003'
|
|
||||||
|
|
||||||
constructor(apiKey) {
|
constructor(apiKey) {
|
||||||
this.apiKey = apiKey
|
this.apiKey = apiKey
|
||||||
}
|
}
|
||||||
|
|
||||||
query(endpoint, data) {
|
query(url, data) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const url = `https://api.openai.com/v1/${endpoint}`
|
|
||||||
|
|
||||||
if (!data.model) data.model = OpenAIAPI.defaultModel
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest()
|
const xhr = new XMLHttpRequest()
|
||||||
xhr.open('POST', url, true)
|
xhr.open('POST', url, true)
|
||||||
xhr.setRequestHeader('Content-Type', 'application/json')
|
xhr.setRequestHeader('Content-Type', 'application/json')
|
||||||
xhr.setRequestHeader('Authorization', `Bearer ${this.apiKey}`)
|
xhr.setRequestHeader('Authorization', `Bearer ${this.apiKey}`)
|
||||||
xhr.onreadystatechange = function () {
|
xhr.onerror = function () {
|
||||||
if (xhr.readyState !== 4) return
|
reject('Failed to query OpenAI API: network error.')
|
||||||
if (xhr.status !== 200) return reject('Failed to query OpenAI API.')
|
}
|
||||||
|
xhr.onload = function () {
|
||||||
const jsonResponse = JSON.parse(xhr.responseText)
|
if (xhr.status === 200) {
|
||||||
|
let jsonResponse
|
||||||
if (!jsonResponse.choices) return reject('Failed to query OpenAI API.')
|
try {
|
||||||
|
jsonResponse = JSON.parse(xhr.responseText)
|
||||||
return resolve(jsonResponse.choices)
|
} catch (e) {
|
||||||
|
reject('Failed to query OpenAI API, cannot parse response:\n' + e + '\n' + xhr.responseText)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (jsonResponse.hasOwnProperty('choices')) {
|
||||||
|
resolve(jsonResponse.choices)
|
||||||
|
} else {
|
||||||
|
reject('Failed to query OpenAI API: invalid response: ' + jsonResponse)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject('Failed to query OpenAI API: invalid status: ' + xhr.status + ' - ' + xhr.responseText)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
xhr.send(JSON.stringify(data))
|
xhr.send(JSON.stringify(data))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async completeText(text) {
|
async act(command, text) {
|
||||||
const data = {
|
let conf = (await chrome.storage.local.get('RequestConfiguration')).RequestConfiguration.openai
|
||||||
max_tokens: 512,
|
let request = Object.assign({}, conf.base)
|
||||||
prompt: text,
|
let url = conf.url
|
||||||
n: 1,
|
Object.assign(request, conf[command])
|
||||||
temperature: 0.5
|
request.messages.push({ role: 'user', 'content': text })
|
||||||
}
|
return this.query(url, request)
|
||||||
|
.then(result => result[0]['message'].content)
|
||||||
const result = await this.query('completions', data)
|
|
||||||
|
|
||||||
return result[0].text
|
|
||||||
}
|
|
||||||
|
|
||||||
async improveText(text) {
|
|
||||||
const data = {
|
|
||||||
model: 'code-davinci-edit-001',
|
|
||||||
input: text,
|
|
||||||
instruction:
|
|
||||||
'Correct any spelling mistakes, grammar mistakes, and improve the overall style of the (latex) text.',
|
|
||||||
n: 1,
|
|
||||||
temperature: 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await this.query('edits', data)
|
|
||||||
|
|
||||||
return result[0].text
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,14 +57,10 @@ function replaceSelectedText(replacementText, selection) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function settingIsEnabled(setting) {
|
async function settingIsEnabled(key) {
|
||||||
let result
|
return chrome.storage.local.get(key)
|
||||||
try {
|
.then(setting => 'enabled' === setting[key].status)
|
||||||
result = await chrome.storage.local.get(setting)
|
.catch(() => false)
|
||||||
} catch (error) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return result[setting]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentText(text) {
|
function commentText(text) {
|
||||||
@@ -90,30 +74,30 @@ function commentText(text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function improveTextHandler(openAI) {
|
async function improveTextHandler(openAI) {
|
||||||
if (!(await settingIsEnabled('textImprovement'))) throw new Error('Text improvement is not enabled.')
|
if (!(await settingIsEnabled('Improve'))) throw new Error('Text improvement is not enabled.')
|
||||||
const selection = window.getSelection()
|
const selection = window.getSelection()
|
||||||
const selectedText = selection.toString()
|
const selectedText = selection.toString()
|
||||||
if (!selectedText) return
|
if (!selectedText) return
|
||||||
const editedText = await openAI.improveText(selectedText)
|
const editedText = await openAI.act('Improve', selectedText)
|
||||||
const commentedText = commentText(selectedText)
|
const commentedText = commentText(selectedText)
|
||||||
replaceSelectedText(commentedText + '\n' + editedText, selection)
|
replaceSelectedText(commentedText + '\n' + editedText, selection)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function completeTextHandler(openAI) {
|
async function completeTextHandler(openAI) {
|
||||||
if (!(await settingIsEnabled('textCompletion'))) throw new Error('Text completion is not enabled.')
|
if (!(await settingIsEnabled('Complete'))) throw new Error('Text completion is not enabled.')
|
||||||
const selection = window.getSelection()
|
const selection = window.getSelection()
|
||||||
const selectedText = selection.toString()
|
const selectedText = selection.toString()
|
||||||
if (!selectedText) return
|
if (!selectedText) return
|
||||||
const editedText = await openAI.completeText(selectedText)
|
const editedText = (await openAI.act('Complete', selectedText)).trimStart()
|
||||||
replaceSelectedText(selectedText + editedText, selection)
|
replaceSelectedText(selectedText + '\n' + editedText, selection)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function askHandler(openAI) {
|
async function askHandler(openAI) {
|
||||||
if (!(await settingIsEnabled('textAsk'))) throw new Error('Text ask is not enabled.')
|
if (!(await settingIsEnabled('Ask'))) throw new Error('Ask is not enabled.')
|
||||||
const selection = window.getSelection()
|
const selection = window.getSelection()
|
||||||
const selectedText = selection.toString()
|
const selectedText = selection.toString()
|
||||||
if (!selectedText) return
|
if (!selectedText) return
|
||||||
const editedText = await openAI.completeText('In Latex, ' + selectedText)
|
const editedText = (await openAI.act('Ask', selectedText)).trimStart()
|
||||||
replaceSelectedText(editedText, selection)
|
replaceSelectedText(editedText, selection)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,28 +113,35 @@ function setAPIKey(key) {
|
|||||||
currentAPIKey = key
|
currentAPIKey = key
|
||||||
if (currentAPIKey) {
|
if (currentAPIKey) {
|
||||||
openAI = new OpenAIAPI(currentAPIKey)
|
openAI = new OpenAIAPI(currentAPIKey)
|
||||||
console.log('LeafLLM: OpenAI API key set, enabling LeafLLM features.')
|
log('OpenAI API key set, enabling LeafLLM features.')
|
||||||
} else {
|
} else {
|
||||||
openAI = undefined
|
openAI = undefined
|
||||||
console.log('LeafLLM: OpenAI API key is not set, LeafLLM features are disabled.')
|
log('OpenAI API key is not set, LeafLLM features are disabled.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCommand(command) {
|
function handleCommand(command) {
|
||||||
if (command === 'Improve') {
|
if (command === 'Improve') {
|
||||||
improveTextHandler(openAI).catch(e => error(`Failed to execute the '${command}' command. Error message: ${e}`))
|
improveTextHandler(openAI).catch(e => error(`Failed to execute the '${command}' command.`, e))
|
||||||
} else if (command === 'Complete') {
|
} else if (command === 'Complete') {
|
||||||
completeTextHandler(openAI).catch(e => error(`Failed to execute the '${command}' command. Error message: ${e}`))
|
completeTextHandler(openAI).catch(e => error(`Failed to execute the '${command}' command.`, e))
|
||||||
} else if (command === 'Ask') {
|
} else if (command === 'Ask') {
|
||||||
askHandler(openAI).catch(e => error(`Failed to execute the '${command}' command. Error message: ${e}`))
|
askHandler(openAI).catch(e => error(`Failed to execute the '${command}' command.`, e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function error(msg) {
|
function error(msg, error) {
|
||||||
|
if(error) {
|
||||||
|
msg += ` Error message: ${JSON.stringify(error)}`
|
||||||
|
}
|
||||||
customAlert(msg)
|
customAlert(msg)
|
||||||
console.error(`LeafLLM: ${msg}`)
|
console.error(`LeafLLM: ${msg}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function log(msg) {
|
||||||
|
console.log(`LeafLLM: ${msg}`)
|
||||||
|
}
|
||||||
|
|
||||||
function customAlert(msg,duration)
|
function customAlert(msg,duration)
|
||||||
{
|
{
|
||||||
if(!duration) duration = 4000;
|
if(!duration) duration = 4000;
|
||||||
@@ -167,7 +158,7 @@ function customAlert(msg,duration)
|
|||||||
|
|
||||||
chrome.runtime.onMessage.addListener(
|
chrome.runtime.onMessage.addListener(
|
||||||
function (request, sender, sendResponse) {
|
function (request, sender, sendResponse) {
|
||||||
console.log(`Received request: ${JSON.stringify(request)}`)
|
log(`Received request: ${JSON.stringify(request)}`)
|
||||||
if (request.command === 'setup') {
|
if (request.command === 'setup') {
|
||||||
setAPIKey(request.apiKey)
|
setAPIKey(request.apiKey)
|
||||||
} else {
|
} else {
|
||||||
@@ -176,6 +167,8 @@ chrome.runtime.onMessage.addListener(
|
|||||||
setAPIKey(openAIAPIKey)
|
setAPIKey(openAIAPIKey)
|
||||||
if (openAI) {
|
if (openAI) {
|
||||||
handleCommand(request.command)
|
handleCommand(request.command)
|
||||||
|
} else {
|
||||||
|
error('OpenAI API key is not set, LeafLLM features are disabled.')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,7 +1,45 @@
|
|||||||
|
// Changing defaultConfigurations requires changing popup/popup.js
|
||||||
|
const defaultConfigurations = {
|
||||||
|
openai: {
|
||||||
|
url: 'https://api.openai.com/v1/chat/completions',
|
||||||
|
base: {
|
||||||
|
n: 1,
|
||||||
|
temperature: 0.5,
|
||||||
|
model: 'gpt-4-turbo'
|
||||||
|
},
|
||||||
|
Complete: {
|
||||||
|
max_tokens: 512,
|
||||||
|
messages: [{
|
||||||
|
role: 'system',
|
||||||
|
content: 'You are an assistant in a Latex editor, usually used for writing academic papers. Continues the given text. No need to rewrite the given text.'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
Improve: {
|
||||||
|
messages: [{
|
||||||
|
role: 'system',
|
||||||
|
content: 'You are an assistant in a Latex editor, usually used for writing academic papers. Style and improves the given text.'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
Ask: {
|
||||||
|
messages: [{
|
||||||
|
role: 'system',
|
||||||
|
content: 'You are an assistant in a Latex editor, usually used for writing academic papers. Address the given questions/request without introduction/explanations.'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = [
|
||||||
|
{ key: 'Complete', shortcut: 'Ctrl + Shift + C', status: 'enabled', type: 'Command' },
|
||||||
|
{ key: 'Improve', shortcut: 'Ctrl + Shift + I', status: 'enabled', type: 'Command' },
|
||||||
|
{ key: 'Ask', shortcut: 'Ctrl + Shift + A', status: 'enabled', type: 'Command' },
|
||||||
|
{ key: 'RequestConfiguration', value: defaultConfigurations, type: 'Configuration' }
|
||||||
|
]
|
||||||
|
|
||||||
async function sendMessage(message) {
|
async function sendMessage(message) {
|
||||||
const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true })
|
const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true })
|
||||||
if (tab == null || tab.url?.startsWith('chrome://')) return undefined
|
if (tab == null || tab.url?.startsWith('chrome://')) return undefined
|
||||||
const response = await chrome.tabs.sendMessage(tab.id, message)
|
chrome.tabs.sendMessage(tab.id, message)
|
||||||
// do something with response here, not outside the function
|
// do something with response here, not outside the function
|
||||||
// console.log(response)
|
// console.log(response)
|
||||||
}
|
}
|
||||||
@@ -14,19 +52,44 @@ function addListener(commandName) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only use this function during the initial installation phase. After
|
||||||
|
// installation the user may have intentionally unassigned commands.
|
||||||
|
// Example for install commands: [{"description":"","name":"_execute_action","shortcut":""},{"description":"Use the selected text to ask GPT. It adds to the beginning of the selected text: 'In Latex, '","name":"Ask","shortcut":""},{"description":"Complete selected text","name":"Complete","shortcut":""},{"description":"Improve selected text","name":"Improve","shortcut":""}]
|
||||||
|
async function checkCommandShortcuts() {
|
||||||
|
chrome.commands.getAll((commands) => {
|
||||||
|
for (let { name, shortcut } of commands) {
|
||||||
|
let command =
|
||||||
|
settings.filter(({ type, key }) => 'Command' === type && name === key)
|
||||||
|
if (command.length > 0) {
|
||||||
|
command = command[0]
|
||||||
|
if (shortcut === '') {
|
||||||
|
command.status = 'error'
|
||||||
|
}
|
||||||
|
chrome.storage.local.set({ [command.key]: command })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.runtime.onInstalled.addListener((reason) => {
|
||||||
|
if (reason.reason === chrome.runtime.OnInstalledReason.INSTALL) {
|
||||||
|
checkCommandShortcuts()
|
||||||
|
chrome.storage.local.set({ RequestConfiguration: defaultConfigurations })
|
||||||
|
} else if (reason.reason === chrome.runtime.OnInstalledReason.UPDATE) {
|
||||||
|
let newVersion = chrome.runtime.getManifest().version
|
||||||
|
let oldVersion = reason.previousVersion
|
||||||
|
let oldVersionArray = oldVersion.split('.')
|
||||||
|
if (parseInt(oldVersionArray[0]) === 1 && parseInt(oldVersionArray[1]) < 4) {
|
||||||
|
chrome.storage.local.set({ RequestConfiguration: defaultConfigurations })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
async function setup() {
|
async function setup() {
|
||||||
addListener('Improve')
|
addListener('Improve')
|
||||||
addListener('Complete')
|
addListener('Complete')
|
||||||
addListener('Ask')
|
addListener('Ask')
|
||||||
/*const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true })
|
console.log('LeafLLM service worker installed')
|
||||||
if (tab?.url?.startsWith('chrome://')) return undefined
|
|
||||||
console.log('tab'+tab)
|
|
||||||
chrome.scripting.executeScript({
|
|
||||||
target: { tabId: tab.id },
|
|
||||||
files: ['scripts/content.js']
|
|
||||||
}).then(() => {
|
|
||||||
|
|
||||||
})*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
|
|||||||
Reference in New Issue
Block a user