@@ -42,5 +42,5 @@
|
||||
"manifest_version": 3,
|
||||
"name": "LeafLLM",
|
||||
"homepage_url": "https://github.com/achiyae/LeafLLM",
|
||||
"version": "1.3.2"
|
||||
"version": "1.4.0"
|
||||
}
|
||||
|
||||
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
@@ -1,8 +1,10 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="/scripts/jquery.js"></script>
|
||||
<script src="/popup/jsoneditor.min.js"></script>
|
||||
<script src="/popup/popup.js" type="module"></script>
|
||||
<link rel="stylesheet" href="/popup/popup.css"/>
|
||||
<link rel="stylesheet" href="/popup/jsoneditor.min.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="extension-title">LeafLLM: an AI-powered Overleaf</h1>
|
||||
@@ -57,5 +59,14 @@
|
||||
</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>
|
||||
|
||||
@@ -1,4 +1,45 @@
|
||||
// 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 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'
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const apiKeyRegex = /sk-[a-zA-Z0-9]{48}/
|
||||
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) {
|
||||
$('#message-box').append(`<div class="message">${message}</div>`)
|
||||
@@ -17,17 +58,22 @@ async function refreshStorage() {
|
||||
$('#api-token-form .api-token-status').text(chrome.runtime.lastError || !openAIAPIKey ? 'not set' : 'set')
|
||||
})
|
||||
|
||||
const commands = await chrome.commands.getAll();
|
||||
const commands = await chrome.commands.getAll()
|
||||
|
||||
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;
|
||||
setting.shortcut = command.shortcut
|
||||
if (setting.status === 'enabled' && setting.shortcut === '') {
|
||||
setting.status = 'error'
|
||||
}
|
||||
chrome.storage.local.set({ [setting.key]: setting });
|
||||
chrome.storage.local.set({ [setting.key]: setting })
|
||||
} else if (setting.status === 'enabled' && setting.shortcut === '') {
|
||||
setting.status = 'error'
|
||||
chrome.storage.local.set({ [setting.key]: setting })
|
||||
@@ -36,7 +82,7 @@ async function refreshStorage() {
|
||||
let bindingFailures = Object.values(settings)
|
||||
.filter(({ status }) => status === 'error')
|
||||
.map(({ key }) => `${key}`)
|
||||
.join(', ');
|
||||
.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>.`)
|
||||
}
|
||||
@@ -97,6 +143,20 @@ function makeHandleSettingChange(key) {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveConfig() {
|
||||
clearMessages()
|
||||
let config;
|
||||
try {
|
||||
config = jsonEditor.get()
|
||||
} catch (e) {
|
||||
addErrorMessage(`Failed to parse configuration. Error: ${e}`)
|
||||
return
|
||||
}
|
||||
chrome.storage.local.set({ RequestConfiguration: config })
|
||||
.then(refreshStorage)
|
||||
.catch((error) => addErrorMessage(`Failed to save configuration. Error: ${error}`))
|
||||
}
|
||||
|
||||
$(document).ready(async function () {
|
||||
$('#api-token-form .submit').on('click', handleAPITokenSet)
|
||||
$('#api-token-form .clear').on('click', handleAPITokenClear)
|
||||
@@ -107,8 +167,15 @@ $(document).ready(async function () {
|
||||
})
|
||||
|
||||
$('body').on('click', 'a', function () {
|
||||
chrome.tabs.create({url: $(this).attr('href')});
|
||||
return false;
|
||||
});
|
||||
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()
|
||||
})
|
||||
|
||||
@@ -6,14 +6,8 @@ class OpenAIAPI {
|
||||
this.apiKey = apiKey
|
||||
}
|
||||
|
||||
query(endpoint, data) {
|
||||
query(url, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = `https://api.openai.com/v1/${endpoint}`
|
||||
|
||||
if (!data.model) data.model = OpenAIAPI.defaultModel
|
||||
if (!data.n) data.n = 1
|
||||
if (!data.temperature) data.temperature = 0.5
|
||||
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.open('POST', url, true)
|
||||
xhr.setRequestHeader('Content-Type', 'application/json')
|
||||
@@ -44,40 +38,13 @@ class OpenAIAPI {
|
||||
})
|
||||
}
|
||||
|
||||
async completeText(text) {
|
||||
const data = {
|
||||
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' },
|
||||
{ role: 'user', 'content': text }
|
||||
],
|
||||
}
|
||||
|
||||
return this.query('chat/completions', data)
|
||||
.then(result => result[0]['message'].content)
|
||||
}
|
||||
|
||||
async improveText(text) {
|
||||
const data = {
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are an assistant in a Latex editor' },
|
||||
{ role: 'user', 'content': 'Improve the following text:\n'+text }],
|
||||
}
|
||||
|
||||
return this.query('chat/completions', data)
|
||||
.then(result => result[0]['message'].content)
|
||||
}
|
||||
|
||||
async ask(text) {
|
||||
const data = {
|
||||
max_tokens: 512,
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are an assistant in a Latex editor. Answer questions without introduction/explanations' },
|
||||
{ role: 'user', 'content': text }
|
||||
],
|
||||
}
|
||||
|
||||
return this.query('chat/completions', 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)
|
||||
}
|
||||
}
|
||||
@@ -113,7 +80,7 @@ async function improveTextHandler(openAI) {
|
||||
const selection = window.getSelection()
|
||||
const selectedText = selection.toString()
|
||||
if (!selectedText) return
|
||||
const editedText = await openAI.improveText(selectedText)
|
||||
const editedText = await openAI.act('Improve', selectedText)
|
||||
const commentedText = commentText(selectedText)
|
||||
replaceSelectedText(commentedText + '\n' + editedText, selection)
|
||||
}
|
||||
@@ -123,7 +90,7 @@ async function completeTextHandler(openAI) {
|
||||
const selection = window.getSelection()
|
||||
const selectedText = selection.toString()
|
||||
if (!selectedText) return
|
||||
const editedText = (await openAI.completeText(selectedText)).trimStart()
|
||||
const editedText = (await openAI.act('Complete', selectedText)).trimStart()
|
||||
replaceSelectedText(selectedText + '\n' + editedText, selection)
|
||||
}
|
||||
|
||||
@@ -132,7 +99,7 @@ async function askHandler(openAI) {
|
||||
const selection = window.getSelection()
|
||||
const selectedText = selection.toString()
|
||||
if (!selectedText) return
|
||||
const editedText = (await openAI.ask(selectedText)).trimStart()
|
||||
const editedText = (await openAI.act('Ask', selectedText)).trimStart()
|
||||
replaceSelectedText(editedText, selection)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,39 @@
|
||||
// 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-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'
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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' }
|
||||
{ key: 'Ask', shortcut: 'Alt+A', status: 'enabled', type: 'Command' },
|
||||
{ key: 'RequestConfiguration', value: defaultConfigurations, type: 'Configuration' }
|
||||
]
|
||||
|
||||
async function sendMessage(message) {
|
||||
@@ -42,6 +74,14 @@ async function checkCommandShortcuts() {
|
||||
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 })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user