Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a640126c2c | ||
|
|
729017fd5f | ||
|
|
f92137dcaa | ||
|
|
a39c3e9edd | ||
|
|
b01e48b032 | ||
|
|
80039a0342 | ||
|
|
cc1b0986b9 | ||
|
|
93b362ca3e | ||
|
|
d9a782036f | ||
|
|
60b84e8229 | ||
|
|
f0a5f9317f | ||
|
|
8377ef4f82 |
@@ -69,7 +69,9 @@ Entry 7 & Entry 8 & Entry 9\\
|
|||||||
```
|
```
|
||||||
|
|
||||||
## 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 [here](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 users' 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 do with this data. The plugin's authors do not collect any data from the plugin's users.
|
||||||
@@ -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.2.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script src="/scripts/jquery.js"></script>
|
<script src="/scripts/jquery.js"></script>
|
||||||
<script src="/popup/popup.js"></script>
|
<script src="/scripts/utils.js" type="module"></script>
|
||||||
|
<script src="/popup/popup.js" type="module"></script>
|
||||||
<link rel="stylesheet" href="/popup/popup.css"/>
|
<link rel="stylesheet" href="/popup/popup.css"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1 class="extension-title">LeafLLM: an AI-powered Overleaf</h1>
|
<h1 class="extension-title">LeafLLM: an AI-powered Overleaf</h1>
|
||||||
<div id="controls" class="form-container">
|
<div id="controls" class="form-container">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="input"><span>ALT</span><span>I</span></div>
|
<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>
|
||||||
@@ -26,21 +27,21 @@
|
|||||||
<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>
|
||||||
@@ -49,10 +50,13 @@
|
|||||||
<div id="api-token-form" class="form-container">
|
<div id="api-token-form" class="form-container">
|
||||||
<div class="api-token-text"><span>API Token is </span><span class="api-token-status">not set</span></div>
|
<div class="api-token-text"><span>API Token is </span><span class="api-token-status">not set</span></div>
|
||||||
<input class="api-token-input" name="api-token" placeholder="OpenAI API Token" type="text"/>
|
<input class="api-token-input" name="api-token" placeholder="OpenAI API Token" type="text"/>
|
||||||
<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>
|
<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>
|
||||||
|
<div class="btn-container">
|
||||||
<button class="submit btn">Set API Token</button>
|
<button class="submit btn">Set API Token</button>
|
||||||
<button class="clear btn">Reset API Token</button>
|
<button class="clear btn">Reset API Token</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div id="message-box" class="message-box"></div>
|
<div id="message-box" class="message-box"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
130
popup/popup.js
130
popup/popup.js
@@ -1,105 +1,97 @@
|
|||||||
const apiKeyRegex = /sk-[a-zA-Z0-9]{48}/;
|
const apiKeyRegex = /sk-[a-zA-Z0-9]{48}/
|
||||||
|
|
||||||
const settings = [
|
|
||||||
{ key: "textCompletion", name: "text-completion" },
|
|
||||||
{ key: "textImprovement", name: "text-improvement" },
|
|
||||||
{ key: "textAsk", name: "text-ask" },
|
|
||||||
];
|
|
||||||
|
|
||||||
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) => {
|
chrome.storage.local.get(['Improve', 'Complete', 'Ask']).then((settings) => {
|
||||||
settings.forEach(({ key, name }) => {
|
let bindingFailures = Object.values(settings)
|
||||||
$(`#settings-form input[name='${name}']:checkbox`).prop("checked", storage[key]);
|
.filter(({ status }) => status === 'error')
|
||||||
});
|
.map(({ key, shortcut }) => `${shortcut} for ${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 = status === 'error' ? '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 || !apiKeyRegex.test(openAIAPIKey)) {
|
||||||
addErrorMessage("Invalid API Token.");
|
addErrorMessage('Invalid API Token.')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
chrome.storage.local.set({ openAIAPIKey })
|
||||||
await chrome.storage.local.set({ openAIAPIKey });
|
.then(refreshStorage)
|
||||||
} catch (error) {
|
.catch((error) => addErrorMessage(`Failed to remove API Token. Error: ${error}`))
|
||||||
console.log(error);
|
|
||||||
addErrorMessage("Failed to set API Token.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await refreshStorage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
async 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)
|
||||||
const value = event.target.checked;
|
if (setting[key].status !== 'error') {
|
||||||
|
setting[key].status = value ? 'enabled' : 'disabled'
|
||||||
try {
|
await chrome.storage.local.set({ [key]: setting[key] })
|
||||||
await chrome.storage.local.set({ [key]: value });
|
}
|
||||||
} catch (error) {
|
return refreshStorage()
|
||||||
console.log(error);
|
|
||||||
addErrorMessage(`Failed to set ${key} setting.`);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await refreshStorage();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(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;
|
||||||
});
|
});
|
||||||
|
return refreshStorage()
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
class OpenAIAPI {
|
class OpenAIAPI {
|
||||||
static defaultModel = 'text-davinci-003'
|
static defaultModel = 'text-davinci-003'
|
||||||
|
|
||||||
@@ -38,9 +39,8 @@ class OpenAIAPI {
|
|||||||
temperature: 0.5
|
temperature: 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.query('completions', data)
|
return this.query('completions', data)
|
||||||
|
.then(result => result[0].text)
|
||||||
return result[0].text
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async improveText(text) {
|
async improveText(text) {
|
||||||
@@ -53,9 +53,8 @@ class OpenAIAPI {
|
|||||||
temperature: 0.5
|
temperature: 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.query('edits', data)
|
return this.query('edits', data)
|
||||||
|
.then(result => result[0].text)
|
||||||
return result[0].text
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,14 +68,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,7 +85,7 @@ 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
|
||||||
@@ -100,20 +95,20 @@ async function improveTextHandler(openAI) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.completeText(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.completeText('In latex, ' + selectedText)).trimStart()
|
||||||
replaceSelectedText(editedText, selection)
|
replaceSelectedText(editedText, selection)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,28 +124,38 @@ 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: ${error.message}`
|
||||||
|
if(error.cause) {
|
||||||
|
console.error(`\nCause: ${JSON.stringify(error.cause)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
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 +172,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 {
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
|
const settings = [
|
||||||
|
{ key: 'Complete', shortcut: 'Alt+C', status: 'enabled', type: 'Command' },
|
||||||
|
{ key: 'Improve', shortcut: 'Alt+I', status: 'enabled', type: 'Command' },
|
||||||
|
{ key: 'Ask', shortcut: 'Alt+A', status: 'enabled', type: 'Command' }
|
||||||
|
]
|
||||||
|
|
||||||
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 +20,35 @@ function addListener(commandName) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chrome.runtime.onInstalled.addListener((reason) => {
|
||||||
|
if (reason.reason === chrome.runtime.OnInstalledReason.INSTALL) {
|
||||||
|
checkCommandShortcuts()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Only use this function during the initial install 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
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 })
|
|
||||||
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