diff --git a/package-lock.json b/package-lock.json index f2a36d5..9751af7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,6 @@ "flourite": "^1.3.0", "gpt-tokenizer": "^2.1.2", "katex": "^0.16.10", - "llama-tokenizer-js": "^1.2.2", "postcss": "^8.4.32", "sass": "^1.77.6", "stacking-order": "^2.0.0", @@ -3834,13 +3833,6 @@ "node": ">= 0.8.0" } }, - "node_modules/llama-tokenizer-js": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/llama-tokenizer-js/-/llama-tokenizer-js-1.2.2.tgz", - "integrity": "sha512-Wmth393dc3odWU3IzARJ3r2oIfWgw9GdJ5Gm+hGhfECNO18UHLRqEFSf511jn4E9KcQGzuuKw4Wl08pHAemLAw==", - "dev": true, - "license": "MIT" - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", diff --git a/package.json b/package.json index 67db87c..db89eb1 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "flourite": "^1.3.0", "gpt-tokenizer": "^2.1.2", "katex": "^0.16.10", - "llama-tokenizer-js": "^1.2.2", "postcss": "^8.4.32", "sass": "^1.77.6", "stacking-order": "^2.0.0", diff --git a/src/App.svelte b/src/App.svelte index d38a3aa..1e772f4 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -7,7 +7,7 @@ import Home from './lib/Home.svelte' import Chat from './lib/Chat.svelte' import NewChat from './lib/NewChat.svelte' - import { chatsStorage, setGlobalSettingValueByKey } from './lib/Storage.svelte' + import { chatsStorage } from './lib/Storage.svelte' import { Modals, closeModal } from 'svelte-modals' import { dispatchModalEsc, checkModalEsc } from './lib/Util.svelte' import { set as setOpenAI } from './lib/providers/openai/util.svelte' @@ -19,10 +19,6 @@ if (urlParams.has('key')) { setOpenAI({ apiKey: urlParams.get('key') as string }) } - if (urlParams.has('petals')) { - console.log('enablePetals') - setGlobalSettingValueByKey('enablePetals', true) - } // The definition of the routes with some conditions const routes = { diff --git a/src/lib/ApiUtil.svelte b/src/lib/ApiUtil.svelte index 42903a5..eda8289 100644 --- a/src/lib/ApiUtil.svelte +++ b/src/lib/ApiUtil.svelte @@ -2,18 +2,16 @@ import { persisted } from 'svelte-local-storage-store' import { get } from 'svelte/store' // This makes it possible to override the OpenAI API base URL in the .env file - const apiBaseStorage = persisted('apiBase', 'https://api.openai.com'); + const apiBaseStorage = persisted('apiBase', 'https://api.openai.com') - const apiBase = get(apiBaseStorage) || 'https://api.openai.com'; + const apiBase = get(apiBaseStorage) || 'https://api.openai.com' const endpointCompletions = import.meta.env.VITE_ENDPOINT_COMPLETIONS || '/v1/chat/completions' const endpointGenerations = import.meta.env.VITE_ENDPOINT_GENERATIONS || '/v1/images/generations' const endpointModels = import.meta.env.VITE_ENDPOINT_MODELS || '/v1/models' const endpointEmbeddings = import.meta.env.VITE_ENDPOINT_EMBEDDINGS || '/v1/embeddings' - const petalsBase = import.meta.env.VITE_PEDALS_WEBSOCKET || 'wss://chat.petals.dev' - const endpointPetals = import.meta.env.VITE_PEDALS_WEBSOCKET || '/api/v2/generate' - export const setApiBase = (e: Record) => { - console.log(e); + export const setApiBase = (e: string) => { + console.log(e) apiBaseStorage.set(e || '') } export const getApiBase = ():string => apiBase @@ -21,6 +19,4 @@ export const getEndpointGenerations = ():string => endpointGenerations export const getEndpointModels = ():string => endpointModels export const getEndpointEmbeddings = ():string => endpointEmbeddings - export const getPetalsBase = ():string => petalsBase - export const getPetalsWebsocket = ():string => endpointPetals \ No newline at end of file diff --git a/src/lib/Chat.svelte b/src/lib/Chat.svelte index d1adb1e..1577f3c 100644 --- a/src/lib/Chat.svelte +++ b/src/lib/Chat.svelte @@ -51,15 +51,25 @@ let recording = false let lastSubmitRecorded = false - $: chat = $chatsStorage.find((chat) => chat.id === chatId) as Chat - $: chatSettings = chat?.settings + // Optimize chat lookup to avoid expensive find() on every chats update + let chat: Chat + let chatSettings: ChatSettings let showSettingsModal - let scDelay + // Only update chat when chatId changes or when the specific chat is updated + $: { + const foundChat = $chatsStorage.find((c) => c.id === chatId) + if (foundChat && (!chat || chat.id !== foundChat.id || chat !== foundChat)) { + chat = foundChat + chatSettings = foundChat.settings + } + } + + let scDelay: any const onStateChange = (...args:any) => { if (!chat) return - clearTimeout(scDelay) - setTimeout(() => { + if (scDelay) clearTimeout(scDelay) + scDelay = setTimeout(() => { if (chat.startSession) { restartProfile(chatId) if (chat.startSession) { @@ -101,6 +111,11 @@ onDestroy(async () => { // clean up + // Clear timer to prevent memory leaks + if (scDelay) { + clearTimeout(scDelay) + scDelay = null + } // abort any pending requests. chatRequest.controller.abort() ttsStop() @@ -286,12 +301,12 @@ chatRequest.updatingMessage = '' - const userMessagesCount = chat.messages.filter(message => message.role === "user").length; - const assiMessagesCount = chat.messages.filter(message => message.role === "assistant").length; - if (userMessagesCount == 3 && chat.name.startsWith("Chat ")) { - suggestName(); + const userMessagesCount = chat.messages.filter(message => message.role === 'user').length + const assiMessagesCount = chat.messages.filter(message => message.role === 'assistant').length + if (userMessagesCount == 3 && chat.name.startsWith('Chat ')) { + suggestName() } - + focusInput() } @@ -305,7 +320,7 @@ const suggestMessages = $currentChatMessages.slice(0, 4) suggestMessages.push(suggestMessage) - const currentModel = chat.settings.model; + const currentModel = chat.settings.model // chat.settings.model = "gpt-4o"; chatRequest.updating = true @@ -318,7 +333,7 @@ maxTokens: 30 }) - chat.settings.model = currentModel; + chat.settings.model = currentModel try { await response.promiseToFinish() diff --git a/src/lib/ChatOptionMenu.svelte b/src/lib/ChatOptionMenu.svelte index 33b9de1..27a3ffe 100644 --- a/src/lib/ChatOptionMenu.svelte +++ b/src/lib/ChatOptionMenu.svelte @@ -157,74 +157,72 @@ reader.readAsText(image) } - function dumpLocalStorage(){ - try { - let storageObject = {}; + function dumpLocalStorage () { + try { + const storageObject = {} for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); + const key = localStorage.key(i) if (key) { - storageObject[key] = localStorage.getItem(key); + storageObject[key] = localStorage.getItem(key) } } - const dataStr = JSON.stringify(storageObject, null, 2); - const blob = new Blob([dataStr], { type: "application/json" }); - const url = URL.createObjectURL(blob); - const link = document.createElement("a"); - link.href = url; - const now = new Date(); - const dateTimeStr = now.toISOString().replace(/:\d+\.\d+Z$/, '').replace(/-|:/g, '_'); - link.download = `ChatGPT-web-${dateTimeStr}.json`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); - + const dataStr = JSON.stringify(storageObject, null, 2) + const blob = new Blob([dataStr], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + const now = new Date() + const dateTimeStr = now.toISOString().replace(/:\d+\.\d+Z$/, '').replace(/-|:/g, '_') + link.download = `ChatGPT-web-${dateTimeStr}.json` + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + URL.revokeObjectURL(url) } catch (error) { - console.error('Error dumping localStorage:', error); + console.error('Error dumping localStorage:', error) } } - function loadLocalStorage() { - var fileInput = document.createElement('input'); - fileInput.type = "file"; - fileInput.addEventListener('change', function(e) { - var file = e.target.files[0]; + function loadLocalStorage () { + const fileInput = document.createElement('input') + fileInput.type = 'file' + fileInput.addEventListener('change', function (e) { + const file = e.target.files[0] if (file) { - var reader = new FileReader(); - reader.onload = function(e) { - var data = JSON.parse(e.target.result); - Object.keys(data).forEach(function(key) { - localStorage.setItem(key, data[key]); - }); - window.location.reload(); - }; - reader.readAsText(file); + const reader = new FileReader() + reader.onload = function (e) { + const data = JSON.parse(e.target.result) + Object.keys(data).forEach(function (key) { + localStorage.setItem(key, data[key]) + }) + window.location.reload() + } + reader.readAsText(file) } - }); - document.body.appendChild(fileInput); - fileInput.click(); - fileInput.remove(); + }) + document.body.appendChild(fileInput) + fileInput.click() + fileInput.remove() } - function backupLocalStorage() { - try { - let storageObject = {}; + function backupLocalStorage () { + try { + const storageObject = {} for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); + const key = localStorage.key(i) if (key) { - storageObject[key] = localStorage.getItem(key); + storageObject[key] = localStorage.getItem(key) } } - const dataStr = JSON.stringify(storageObject, null, 2); - const now = new Date(); - const dateTimeStr = now.toISOString().replace(/:\d+\.\d+Z$/, '').replace(/-|:/g, '_'); - localStorage.setItem(`prev-${dateTimeStr}`, dataStr); + const dataStr = JSON.stringify(storageObject, null, 2) + const now = new Date() + const dateTimeStr = now.toISOString().replace(/:\d+\.\d+Z$/, '').replace(/-|:/g, '_') + localStorage.setItem(`prev-${dateTimeStr}`, dataStr) } catch (error) { - console.error('Error backing up localStorage:', error); + console.error('Error backing up localStorage:', error) } - - } +} diff --git a/src/lib/ChatRequest.svelte b/src/lib/ChatRequest.svelte index fbb1c70..076bb13 100644 --- a/src/lib/ChatRequest.svelte +++ b/src/lib/ChatRequest.svelte @@ -196,7 +196,7 @@ export class ChatRequest { if (value > maxAllowed || value < 1) value = null // if over max model, do not define max if (value) value = Math.floor(value) if (modelDetail.reasoning == true) { - key = 'max_completion_tokens'; + key = 'max_completion_tokens' } } if (key === 'n') { @@ -351,12 +351,21 @@ export class ChatRequest { * ************************************************************* */ - let promptSize = countPromptTokens(top.concat(rw), model, chat) + countPadding + // Pre-calculate top tokens once to avoid repeated calculations + const topTokens = countPromptTokens(top, model, chat) + let rwTokens = countPromptTokens(rw, model, chat) + let promptSize = topTokens + rwTokens + countPadding + while (rw.length && rw.length > pinBottom && promptSize >= threshold) { const rolled = rw.shift() - // Hide messages we're "rolling" - if (rolled) rolled.suppress = true - promptSize = countPromptTokens(top.concat(rw), model, chat) + countPadding + if (rolled) { + // Hide messages we're "rolling" + rolled.suppress = true + // Subtract only the rolled message tokens instead of recalculating all + const rolledTokens = countMessageTokens(rolled, model, chat) + rwTokens -= rolledTokens + promptSize = topTokens + rwTokens + countPadding + } } // Run a new request, now with the rolled messages hidden return await _this.sendRequest(get(currentChatMessages), { @@ -386,8 +395,11 @@ export class ChatRequest { // the last prompt is a user prompt as that seems to work better for summaries while (rw.length > 2 && ((topSize + reductionPoolSize + promptSummarySize + maxSummaryTokens) >= maxTokens || (reductionPoolSize >= 100 && rw[rw.length - 1]?.role !== 'user'))) { - bottom.unshift(rw.pop() as Message) - reductionPoolSize = countPromptTokens(rw, model, chat) + const removed = rw.pop() as Message + bottom.unshift(removed) + // Optimize: subtract removed message tokens instead of recalculating all + const removedTokens = countMessageTokens(removed, model, chat) + reductionPoolSize -= removedTokens maxSummaryTokens = getSS() promptSummary = prepareSummaryPrompt(chatId, maxSummaryTokens) summaryRequest.content = promptSummary diff --git a/src/lib/Code.svelte b/src/lib/Code.svelte index 7fe9436..8bce7ce 100644 --- a/src/lib/Code.svelte +++ b/src/lib/Code.svelte @@ -18,7 +18,7 @@ export const codeBlockStyle: 'indented' | undefined = undefined export let text: string - let renderedMath: string | undefined; + let renderedMath: string | undefined // For copying code - reference: https://vyacheslavbasharov.com/blog/adding-click-to-copy-code-markdown-blog const copyFunction = (event) => { diff --git a/src/lib/Codespan.svelte b/src/lib/Codespan.svelte index 1fbe6c4..fd4c303 100644 --- a/src/lib/Codespan.svelte +++ b/src/lib/Codespan.svelte @@ -4,21 +4,21 @@ import renderMathInElement from 'katex/contrib/auto-render' let renderedMath: string | undefined - if ( raw.startsWith('`\\(') || raw.startsWith('`\\[') || raw.startsWith('`$') || raw.startsWith('`$$') ) { - let dummy = document.createElement("div") + if (raw.startsWith('`\\(') || raw.startsWith('`\\[') || raw.startsWith('`$') || raw.startsWith('`$$')) { + const dummy = document.createElement('div') dummy.textContent = raw.replace(/`/g, '') renderMathInElement(dummy, { - delimiters: [ - {left: '\\(', right: '\\)', display: false}, - {left: '\\[', right: '\\]', display: true}, - {left: '$', right: '$', display: false}, - {left: '$$', right: '$$', display: true} - ], - throwOnError : false, - output: "html" - }) - renderedMath = dummy.innerHTML; - dummy.remove(); + delimiters: [ + { left: '\\(', right: '\\)', display: false }, + { left: '\\[', right: '\\]', display: true }, + { left: '$', right: '$', display: false }, + { left: '$$', right: '$$', display: true } + ], + throwOnError: false, + output: 'html' + }) + renderedMath = dummy.innerHTML + dummy.remove() } diff --git a/src/lib/EditMessage.svelte b/src/lib/EditMessage.svelte index d218f9a..162cbf2 100644 --- a/src/lib/EditMessage.svelte +++ b/src/lib/EditMessage.svelte @@ -1,7 +1,7 @@ @@ -349,7 +365,7 @@ const replaceLatexDelimiters = (text: string): string => {
{if(isUser){edit()}}} + on:dblclick|preventDefault={() => { if (isUser) { edit() } }} > {#if message.summary && !message.summary.length}

Summarizing...

diff --git a/src/lib/Home.svelte b/src/lib/Home.svelte index 33e2c4b..c1c526f 100644 --- a/src/lib/Home.svelte +++ b/src/lib/Home.svelte @@ -1,16 +1,14 @@
@@ -53,9 +43,6 @@ const setPetalsEnabled = (event: Event) => { more than 10 million tokens per month. All messages are stored in your browser's local storage, so everything is private. You can also close the browser tab and come back later to continue the conversation.

-

- As an alternative to OpenAI, you can also use Petals swarm as a free API option for open chat models like Llama 2. -