@@ -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.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>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script src="/scripts/jquery.js"></script>
|
<script src="/scripts/jquery.js"></script>
|
||||||
|
<script src="/popup/jsoneditor.min.js"></script>
|
||||||
<script src="/popup/popup.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"/>
|
||||||
|
<link rel="stylesheet" href="/popup/jsoneditor.min.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>
|
||||||
@@ -57,5 +59,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="message-box" class="message-box"></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>
|
</body>
|
||||||
</html>
|
</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 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) {
|
function addMessage(message) {
|
||||||
$('#message-box').append(`<div class="message">${message}</div>`)
|
$('#message-box').append(`<div class="message">${message}</div>`)
|
||||||
@@ -17,18 +58,23 @@ async function refreshStorage() {
|
|||||||
$('#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')
|
||||||
})
|
})
|
||||||
|
|
||||||
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) => {
|
chrome.storage.local.get(['Improve', 'Complete', 'Ask']).then((settings) => {
|
||||||
Object.values(settings).forEach(setting => {
|
Object.values(settings).forEach(setting => {
|
||||||
let command = commands.filter(({ name }) => name === setting.key)[0]
|
let command = commands.filter(({ name }) => name === setting.key)[0]
|
||||||
if(command.shortcut !== setting.shortcut) {
|
if (command.shortcut !== setting.shortcut) {
|
||||||
setting.shortcut = command.shortcut;
|
setting.shortcut = command.shortcut
|
||||||
if(setting.status === 'enabled' && setting.shortcut === '') {
|
if (setting.status === 'enabled' && setting.shortcut === '') {
|
||||||
setting.status = 'error'
|
setting.status = 'error'
|
||||||
}
|
}
|
||||||
chrome.storage.local.set({ [setting.key]: setting });
|
chrome.storage.local.set({ [setting.key]: setting })
|
||||||
} else if(setting.status === 'enabled' && setting.shortcut === '') {
|
} else if (setting.status === 'enabled' && setting.shortcut === '') {
|
||||||
setting.status = 'error'
|
setting.status = 'error'
|
||||||
chrome.storage.local.set({ [setting.key]: setting })
|
chrome.storage.local.set({ [setting.key]: setting })
|
||||||
}
|
}
|
||||||
@@ -36,7 +82,7 @@ async function refreshStorage() {
|
|||||||
let bindingFailures = Object.values(settings)
|
let bindingFailures = Object.values(settings)
|
||||||
.filter(({ status }) => status === 'error')
|
.filter(({ status }) => status === 'error')
|
||||||
.map(({ key }) => `${key}`)
|
.map(({ key }) => `${key}`)
|
||||||
.join(', ');
|
.join(', ')
|
||||||
if (bindingFailures.length > 0) {
|
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>.`)
|
addErrorMessage(`Could not bind the following shortcuts:\n${bindingFailures}.\nYou can set it manually at <a href="chrome://extensions/shortcuts">chrome://extensions/shortcuts</a>.`)
|
||||||
}
|
}
|
||||||
@@ -87,7 +133,7 @@ function makeHandleSettingChange(key) {
|
|||||||
|
|
||||||
const value = event.target.checked
|
const value = event.target.checked
|
||||||
const setting = await chrome.storage.local.get(key)
|
const setting = await chrome.storage.local.get(key)
|
||||||
/* let commandKey = await chrome.commands.getAll()
|
/* let commandKey = await chrome.commands.getAll()
|
||||||
commandKey = commandKey.filter(({ name }) => name === key)[0]*/
|
commandKey = commandKey.filter(({ name }) => name === key)[0]*/
|
||||||
// if (setting[key].status !== 'error') {
|
// if (setting[key].status !== 'error') {
|
||||||
setting[key].status = value ? 'enabled' : 'disabled'
|
setting[key].status = value ? 'enabled' : 'disabled'
|
||||||
@@ -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 () {
|
$(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)
|
||||||
@@ -106,9 +166,16 @@ $(document).ready(async function () {
|
|||||||
$(`#settings-form input[name='text-${key}']:checkbox`).on('change', makeHandleSettingChange(key))
|
$(`#settings-form input[name='text-${key}']:checkbox`).on('change', makeHandleSettingChange(key))
|
||||||
})
|
})
|
||||||
|
|
||||||
$('body').on('click', 'a', function(){
|
$('body').on('click', 'a', function () {
|
||||||
chrome.tabs.create({url: $(this).attr('href')});
|
chrome.tabs.create({ url: $(this).attr('href') })
|
||||||
return false;
|
return false
|
||||||
});
|
})
|
||||||
|
|
||||||
|
$('#resetConfig').on('click', async function () {
|
||||||
|
jsonEditor.set(defaultConfigurations)
|
||||||
|
await saveConfig()
|
||||||
|
})
|
||||||
|
|
||||||
|
$('#saveConfig').on('click', saveConfig)
|
||||||
return refreshStorage()
|
return refreshStorage()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,14 +6,8 @@ class OpenAIAPI {
|
|||||||
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
|
|
||||||
if (!data.n) data.n = 1
|
|
||||||
if (!data.temperature) data.temperature = 0.5
|
|
||||||
|
|
||||||
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')
|
||||||
@@ -44,40 +38,13 @@ class OpenAIAPI {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
messages: [
|
let url = conf.url
|
||||||
{ role: 'system', content: 'You are an assistant in a Latex editor that continues the given text. No need to rewrite the given text' },
|
Object.assign(request, conf[command])
|
||||||
{ role: 'user', 'content': text }
|
request.messages.push({ role: 'user', 'content': text })
|
||||||
],
|
return this.query(url, request)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
.then(result => result[0]['message'].content)
|
.then(result => result[0]['message'].content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +80,7 @@ async function improveTextHandler(openAI) {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
@@ -123,7 +90,7 @@ async function completeTextHandler(openAI) {
|
|||||||
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)).trimStart()
|
const editedText = (await openAI.act('Complete', selectedText)).trimStart()
|
||||||
replaceSelectedText(selectedText + '\n' + editedText, selection)
|
replaceSelectedText(selectedText + '\n' + editedText, selection)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +99,7 @@ async function askHandler(openAI) {
|
|||||||
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.ask(selectedText)).trimStart()
|
const editedText = (await openAI.act('Ask', selectedText)).trimStart()
|
||||||
replaceSelectedText(editedText, selection)
|
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 = [
|
const settings = [
|
||||||
{ key: 'Complete', shortcut: 'Alt+C', status: 'enabled', type: 'Command' },
|
{ key: 'Complete', shortcut: 'Alt+C', status: 'enabled', type: 'Command' },
|
||||||
{ key: 'Improve', shortcut: 'Alt+I', 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) {
|
async function sendMessage(message) {
|
||||||
@@ -42,6 +74,14 @@ async function checkCommandShortcuts() {
|
|||||||
chrome.runtime.onInstalled.addListener((reason) => {
|
chrome.runtime.onInstalled.addListener((reason) => {
|
||||||
if (reason.reason === chrome.runtime.OnInstalledReason.INSTALL) {
|
if (reason.reason === chrome.runtime.OnInstalledReason.INSTALL) {
|
||||||
checkCommandShortcuts()
|
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