diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 9c421816d..0abac06a0 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -31,7 +31,7 @@ export const createNewChat = async (token: string, chat: object) => { return res; }; -export const getChatlist = async (token: string = '') => { +export const getChatList = async (token: string = '') => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/chats/`, { diff --git a/src/lib/apis/ollama/index.ts b/src/lib/apis/ollama/index.ts index 268806fdb..9964e2271 100644 --- a/src/lib/apis/ollama/index.ts +++ b/src/lib/apis/ollama/index.ts @@ -69,3 +69,68 @@ export const getOllamaModels = async ( return res?.models ?? []; }; + +export const generateTitle = async ( + base_url: string = OLLAMA_API_BASE_URL, + token: string = '', + model: string, + prompt: string +) => { + let error = null; + + const res = await fetch(`${base_url}/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'text/event-stream', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + model: model, + prompt: `Generate a brief 3-5 word title for this question, excluding the term 'title.' Then, please reply with only the title: ${prompt}`, + stream: false + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + if ('detail' in err) { + error = err.detail; + } + return null; + }); + + if (error) { + throw error; + } + + return res?.response ?? 'New Chat'; +}; + +export const generateChatCompletion = async ( + base_url: string = OLLAMA_API_BASE_URL, + token: string = '', + body: object +) => { + let error = null; + + const res = await fetch(`${base_url}/chat`, { + method: 'POST', + headers: { + 'Content-Type': 'text/event-stream', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify(body) + }).catch((err) => { + error = err; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; diff --git a/src/lib/apis/openai/index.ts b/src/lib/apis/openai/index.ts index 8a2e97677..897762684 100644 --- a/src/lib/apis/openai/index.ts +++ b/src/lib/apis/openai/index.ts @@ -27,8 +27,6 @@ export const getOpenAIModels = async ( let models = Array.isArray(res) ? res : res?.data ?? null; - console.log(models); - return models .map((model) => ({ name: model.id, external: true })) .filter((model) => (base_url.includes('openai') ? model.name.includes('gpt') : true)); diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index 6b6e66ebd..8b9fd4c63 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -8,10 +8,11 @@ import auto_render from 'katex/dist/contrib/auto-render.mjs'; import 'katex/dist/katex.min.css'; - import { config, db, modelfiles, settings, user } from '$lib/stores'; + import { chats, config, db, modelfiles, settings, user } from '$lib/stores'; import { tick } from 'svelte'; import toast from 'svelte-french-toast'; + import { getChatList, updateChatById } from '$lib/apis/chats'; export let chatId = ''; export let sendPrompt: Function; @@ -262,10 +263,12 @@ return message; }); - $db.updateChatById(chatId, { + await updateChatById(localStorage.token, chatId, { messages: messages, history: history }); + + await chats.set(await getChatList(localStorage.token)); }; const showPreviousMessage = async (message) => { diff --git a/src/lib/components/layout/Navbar.svelte b/src/lib/components/layout/Navbar.svelte index 219801bf8..fb350fb0f 100644 --- a/src/lib/components/layout/Navbar.svelte +++ b/src/lib/components/layout/Navbar.svelte @@ -1,7 +1,5 @@ <script lang="ts"> - import { v4 as uuidv4 } from 'uuid'; - - import { goto } from '$app/navigation'; + import { getChatById } from '$lib/apis/chats'; import { chatId, db, modelfiles } from '$lib/stores'; import toast from 'svelte-french-toast'; @@ -10,10 +8,10 @@ export let shareEnabled: boolean = false; const shareChat = async () => { - const chat = (await $db.getChatById($chatId)).chat; + const chat = (await getChatById(localStorage.token, $chatId)).chat; console.log('share', chat); - toast.success('Redirecting you to OllamaHub'); + toast.success('Redirecting you to OllamaHub'); const url = 'https://ollamahub.com'; // const url = 'http://localhost:5173'; diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 1e2a57b0f..0be8be82b 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -8,6 +8,7 @@ import { page } from '$app/stores'; import { user, db, chats, showSettings, chatId } from '$lib/stores'; import { onMount } from 'svelte'; + import { deleteChatById, getChatList, updateChatById } from '$lib/apis/chats'; let show = false; let navElement; @@ -31,7 +32,7 @@ show = true; } - await chats.set(await $db.getChats()); + await chats.set(await getChatList(localStorage.token)); }); const loadChat = async (id) => { @@ -39,42 +40,46 @@ }; const editChatTitle = async (id, _title) => { - await $db.updateChatById(id, { + title = _title; + + await updateChatById(localStorage.token, id, { title: _title }); - title = _title; + await chats.set(await getChatList(localStorage.token)); }; const deleteChat = async (id) => { goto('/'); - $db.deleteChatById(id); + + await deleteChatById(localStorage.token, id); + await chats.set(await getChatList(localStorage.token)); }; - const deleteChatHistory = async () => { - await $db.deleteAllChat(); - }; + // const deleteChatHistory = async () => { + // await $db.deleteAllChat(); + // }; - const importChats = async (chatHistory) => { - await $db.importChats(chatHistory); - }; + // const importChats = async (chatHistory) => { + // await $db.importChats(chatHistory); + // }; - const exportChats = async () => { - let blob = new Blob([JSON.stringify(await $db.exportChats())], { type: 'application/json' }); - saveAs(blob, `chat-export-${Date.now()}.json`); - }; + // const exportChats = async () => { + // let blob = new Blob([JSON.stringify(await $db.exportChats())], { type: 'application/json' }); + // saveAs(blob, `chat-export-${Date.now()}.json`); + // }; - $: if (importFiles) { - console.log(importFiles); + // $: if (importFiles) { + // console.log(importFiles); - let reader = new FileReader(); - reader.onload = (event) => { - let chats = JSON.parse(event.target.result); - console.log(chats); - importChats(chats); - }; + // let reader = new FileReader(); + // reader.onload = (event) => { + // let chats = JSON.parse(event.target.result); + // console.log(chats); + // importChats(chats); + // }; - reader.readAsText(importFiles[0]); - } + // reader.readAsText(importFiles[0]); + // } </script> <div diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 90a78bcd5..17449a725 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -13,6 +13,8 @@ export const WEBUI_API_BASE_URL = `${WEBUI_BASE_URL}/api/v1`; export const WEB_UI_VERSION = 'v1.0.0-alpha-static'; +export const REQUIRED_OLLAMA_VERSION = '0.1.16'; + // Source: https://kit.svelte.dev/docs/modules#$env-static-public // This feature, akin to $env/static/private, exclusively incorporates environment variables // that are prefixed with config.kit.env.publicPrefix (usually set to PUBLIC_). diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 2d9f1b31e..5bcc6c1a0 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -66,9 +66,9 @@ export const getGravatarURL = (email) => { return `https://www.gravatar.com/avatar/${hash}`; }; -const copyToClipboard = (text) => { +export const copyToClipboard = (text) => { if (!navigator.clipboard) { - var textArea = document.createElement('textarea'); + const textArea = document.createElement('textarea'); textArea.value = text; // Avoid scrolling to bottom @@ -81,8 +81,8 @@ const copyToClipboard = (text) => { textArea.select(); try { - var successful = document.execCommand('copy'); - var msg = successful ? 'successful' : 'unsuccessful'; + const successful = document.execCommand('copy'); + const msg = successful ? 'successful' : 'unsuccessful'; console.log('Fallback: Copying text command was ' + msg); } catch (err) { console.error('Fallback: Oops, unable to copy', err); diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index a3c0095e5..e91a776dd 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -1,37 +1,18 @@ <script lang="ts"> import { v4 as uuidv4 } from 'uuid'; - import { openDB, deleteDB } from 'idb'; import { onMount, tick } from 'svelte'; import { goto } from '$app/navigation'; + import toast from 'svelte-french-toast'; - import { - config, - info, - user, - showSettings, - settings, - models, - db, - chats, - chatId, - modelfiles - } from '$lib/stores'; + import { info, user, showSettings, settings, models, modelfiles } from '$lib/stores'; + + import { OLLAMA_API_BASE_URL, REQUIRED_OLLAMA_VERSION, WEBUI_API_BASE_URL } from '$lib/constants'; + import { getOllamaModels, getOllamaVersion } from '$lib/apis/ollama'; + import { getOpenAIModels } from '$lib/apis/openai'; import SettingsModal from '$lib/components/chat/SettingsModal.svelte'; import Sidebar from '$lib/components/layout/Sidebar.svelte'; - import toast from 'svelte-french-toast'; - import { OLLAMA_API_BASE_URL, WEBUI_API_BASE_URL } from '$lib/constants'; - import { getOllamaModels, getOllamaVersion } from '$lib/apis/ollama'; - import { getOpenAIModels } from '$lib/apis/openai'; - import { - createNewChat, - deleteChatById, - getChatById, - getChatlist, - updateChatById - } from '$lib/apis/chats'; - let requiredOllamaVersion = '0.1.16'; let loaded = false; const getModels = async () => { @@ -55,92 +36,19 @@ return models; }; - const getDB = async () => { - const DB = await openDB('Chats', 1, { - upgrade(db) { - const store = db.createObjectStore('chats', { - keyPath: 'id', - autoIncrement: true - }); - store.createIndex('timestamp', 'timestamp'); - } - }); - - return { - db: DB, - getChatById: async function (id) { - const chat = await getChatById(localStorage.token, id); - return chat; - }, - getChats: async function () { - const chats = await getChatlist(localStorage.token); - return chats; - }, - createNewChat: async function (_chat) { - const chat = await createNewChat(localStorage.token, { ..._chat, timestamp: Date.now() }); - console.log(chat); - await chats.set(await this.getChats()); - - return chat; - }, - - addChat: async function (chat) { - await this.db.put('chats', { - ...chat - }); - }, - - updateChatById: async function (id, updated) { - const chat = await updateChatById(localStorage.token, id, { - ...updated, - timestamp: Date.now() - }); - await chats.set(await this.getChats()); - return chat; - }, - deleteChatById: async function (id) { - if ($chatId === id) { - goto('/'); - await chatId.set(uuidv4()); - } - - await deleteChatById(localStorage.token, id); - await chats.set(await this.getChats()); - }, - - deleteAllChat: async function () { - const tx = this.db.transaction('chats', 'readwrite'); - await Promise.all([tx.store.clear(), tx.done]); - await chats.set(await this.getChats()); - }, - exportChats: async function () { - let chats = await this.db.getAllFromIndex('chats', 'timestamp'); - chats = chats.map((item, idx) => chats[chats.length - 1 - idx]); - return chats; - }, - importChats: async function (_chats) { - for (const chat of _chats) { - console.log(chat); - await this.addChat(chat); - } - await chats.set(await this.getChats()); - } - }; - }; - - const setOllamaVersion = async () => { - const version = await getOllamaVersion( - $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL, - localStorage.token - ).catch((error) => { - toast.error(error); - return '0'; - }); - + const setOllamaVersion = async (version: string = '') => { + if (version === '') { + version = await getOllamaVersion( + $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL, + localStorage.token + ).catch((error) => { + return '0'; + }); + } await info.set({ ...$info, ollama: { version: version } }); if ( - version.localeCompare(requiredOllamaVersion, undefined, { + version.localeCompare(REQUIRED_OLLAMA_VERSION, undefined, { numeric: true, sensitivity: 'case', caseFirst: 'upper' @@ -151,19 +59,18 @@ }; onMount(async () => { - if ($config && $user === undefined) { + if ($user === undefined) { await goto('/auth'); } await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}')); await models.set(await getModels()); - await modelfiles.set(JSON.parse(localStorage.getItem('modelfiles') ?? '[]')); - modelfiles.subscribe(async () => {}); + modelfiles.subscribe(async () => { + // should fetch models + }); - let _db = await getDB(); - await db.set(_db); await setOllamaVersion(); await tick(); @@ -214,7 +121,7 @@ </div> </div> </div> - {:else if ($info?.ollama?.version ?? '0').localeCompare( requiredOllamaVersion, undefined, { numeric: true, sensitivity: 'case', caseFirst: 'upper' } ) < 0} + {:else if ($info?.ollama?.version ?? '0').localeCompare( REQUIRED_OLLAMA_VERSION, undefined, { numeric: true, sensitivity: 'case', caseFirst: 'upper' } ) < 0} <div class="absolute w-full h-full flex z-50"> <div class="absolute rounded-xl w-full h-full backdrop-blur bg-gray-900/60 flex justify-center" @@ -231,15 +138,15 @@ />We've detected either a connection hiccup or observed that you're using an older version. Ensure you're on the latest Ollama version <br class=" hidden sm:flex" />(version - <span class=" dark:text-white font-medium">{requiredOllamaVersion} or higher</span>) - or check your connection. + <span class=" dark:text-white font-medium">{REQUIRED_OLLAMA_VERSION} or higher</span + >) or check your connection. </div> <div class=" mt-6 mx-auto relative group w-fit"> <button class="relative z-20 flex px-5 py-2 rounded-full bg-gray-100 hover:bg-gray-200 transition font-medium text-sm" on:click={async () => { - await setOllamaVersion(await getOllamaVersion()); + await setOllamaVersion(); }} > Check Again @@ -248,7 +155,7 @@ <button class="text-xs text-center w-full mt-2 text-gray-400 underline" on:click={async () => { - await setOllamaVersion(requiredOllamaVersion); + await setOllamaVersion(REQUIRED_OLLAMA_VERSION); }}>Close</button > </div> diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index e7e8a03cd..79bbfc178 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -2,23 +2,27 @@ import { v4 as uuidv4 } from 'uuid'; import toast from 'svelte-french-toast'; - import { onDestroy, onMount, tick } from 'svelte'; + import { onMount, tick } from 'svelte'; import { goto } from '$app/navigation'; import { page } from '$app/stores'; - import { config, models, modelfiles, user, settings, db, chats, chatId } from '$lib/stores'; + import { models, modelfiles, user, settings, db, chats, chatId } from '$lib/stores'; import { OLLAMA_API_BASE_URL } from '$lib/constants'; - import { splitStream } from '$lib/utils'; + + import { generateChatCompletion, generateTitle } from '$lib/apis/ollama'; + import { copyToClipboard, splitStream } from '$lib/utils'; import MessageInput from '$lib/components/chat/MessageInput.svelte'; import Messages from '$lib/components/chat/Messages.svelte'; import ModelSelector from '$lib/components/chat/ModelSelector.svelte'; import Navbar from '$lib/components/layout/Navbar.svelte'; + import { createNewChat, getChatList, updateChatById } from '$lib/apis/chats'; let stopResponseFlag = false; let autoScroll = true; let selectedModels = ['']; + let selectedModelfile = null; $: selectedModelfile = selectedModels.length === 1 && @@ -83,41 +87,6 @@ }); }; - const copyToClipboard = (text) => { - if (!navigator.clipboard) { - var textArea = document.createElement('textarea'); - textArea.value = text; - - // Avoid scrolling to bottom - textArea.style.top = '0'; - textArea.style.left = '0'; - textArea.style.position = 'fixed'; - - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - - try { - var successful = document.execCommand('copy'); - var msg = successful ? 'successful' : 'unsuccessful'; - console.log('Fallback: Copying text command was ' + msg); - } catch (err) { - console.error('Fallback: Oops, unable to copy', err); - } - - document.body.removeChild(textArea); - return; - } - navigator.clipboard.writeText(text).then( - function () { - console.log('Async: Copying to clipboard was successful!'); - }, - function (err) { - console.error('Async: Could not copy text: ', err); - } - ); - }; - ////////////////////////// // Ollama functions ////////////////////////// @@ -135,11 +104,11 @@ }) ); - await chats.set(await $db.getChats()); + await chats.set(await getChatList(localStorage.token)); }; const sendPromptOllama = async (model, userPrompt, parentId, _chatId) => { - console.log('sendPromptOllama'); + // Create response message let responseMessageId = uuidv4(); let responseMessage = { parentId: parentId, @@ -150,8 +119,11 @@ model: model }; + // Add message to history and Set currentId to messageId history.messages[responseMessageId] = responseMessage; history.currentId = responseMessageId; + + // Append messageId to childrenIds of parent message if (parentId !== null) { history.messages[parentId].childrenIds = [ ...history.messages[parentId].childrenIds, @@ -159,17 +131,16 @@ ]; } + // Wait until history/message have been updated await tick(); + + // Scroll down window.scrollTo({ top: document.body.scrollHeight }); - const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/chat`, { - method: 'POST', - headers: { - 'Content-Type': 'text/event-stream', - ...($settings.authHeader && { Authorization: $settings.authHeader }), - ...($user && { Authorization: `Bearer ${localStorage.token}` }) - }, - body: JSON.stringify({ + const res = await generateChatCompletion( + $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL, + localStorage.token, + { model: model, messages: [ $settings.system @@ -191,20 +162,11 @@ }) })), options: { - seed: $settings.seed ?? undefined, - temperature: $settings.temperature ?? undefined, - repeat_penalty: $settings.repeat_penalty ?? undefined, - top_k: $settings.top_k ?? undefined, - top_p: $settings.top_p ?? undefined, - num_ctx: $settings.num_ctx ?? undefined, ...($settings.options ?? {}) }, format: $settings.requestFormat ?? undefined - }) - }).catch((err) => { - console.log(err); - return null; - }); + } + ); if (res && res.ok) { const reader = res.body @@ -296,23 +258,11 @@ } if ($chatId == _chatId) { - chat = await $db.updateChatById(_chatId, { - ...chat.chat, - title: title === '' ? 'New Chat' : title, - models: selectedModels, - system: $settings.system ?? undefined, - options: { - seed: $settings.seed ?? undefined, - temperature: $settings.temperature ?? undefined, - repeat_penalty: $settings.repeat_penalty ?? undefined, - top_k: $settings.top_k ?? undefined, - top_p: $settings.top_p ?? undefined, - num_ctx: $settings.num_ctx ?? undefined, - ...($settings.options ?? {}) - }, + chat = await updateChatById(localStorage.token, _chatId, { messages: messages, history: history }); + await chats.set(await getChatList(localStorage.token)); } } else { if (res !== null) { @@ -338,6 +288,7 @@ stopResponseFlag = false; await tick(); + if (autoScroll) { window.scrollTo({ top: document.body.scrollHeight }); } @@ -483,23 +434,11 @@ } if ($chatId == _chatId) { - chat = await $db.updateChatById(_chatId, { - ...chat.chat, - title: title === '' ? 'New Chat' : title, - models: selectedModels, - system: $settings.system ?? undefined, - options: { - seed: $settings.seed ?? undefined, - temperature: $settings.temperature ?? undefined, - repeat_penalty: $settings.repeat_penalty ?? undefined, - top_k: $settings.top_k ?? undefined, - top_p: $settings.top_p ?? undefined, - num_ctx: $settings.num_ctx ?? undefined, - ...($settings.options ?? {}) - }, + chat = await updateChatById(localStorage.token, _chatId, { messages: messages, history: history }); + await chats.set(await getChatList(localStorage.token)); } } else { if (res !== null) { @@ -549,10 +488,13 @@ if (selectedModels.includes('')) { toast.error('Model not selected'); } else if (messages.length != 0 && messages.at(-1).done != true) { + // Response not done console.log('wait'); } else { + // Reset chat message textarea height document.getElementById('chat-textarea').style.height = ''; + // Create user message let userMessageId = uuidv4(); let userMessage = { id: userMessageId, @@ -563,47 +505,42 @@ files: files.length > 0 ? files : undefined }; + // Add message to history and Set currentId to messageId + history.messages[userMessageId] = userMessage; + history.currentId = userMessageId; + + // Append messageId to childrenIds of parent message if (messages.length !== 0) { history.messages[messages.at(-1).id].childrenIds.push(userMessageId); } - history.messages[userMessageId] = userMessage; - history.currentId = userMessageId; - + // Wait until history/message have been updated await tick(); + // Create new chat if only one message in messages if (messages.length == 1) { - chat = await $db.createNewChat({ + chat = await createNewChat(localStorage.token, { id: $chatId, title: 'New Chat', models: selectedModels, system: $settings.system ?? undefined, options: { - seed: $settings.seed ?? undefined, - temperature: $settings.temperature ?? undefined, - repeat_penalty: $settings.repeat_penalty ?? undefined, - top_k: $settings.top_k ?? undefined, - top_p: $settings.top_p ?? undefined, - num_ctx: $settings.num_ctx ?? undefined, ...($settings.options ?? {}) }, messages: messages, - history: history + history: history, + timestamp: Date.now() }); - - console.log(chat); - + await chats.set(await getChatList(localStorage.token)); await chatId.set(chat.id); await tick(); } + // Reset chat input textarea prompt = ''; files = []; - setTimeout(() => { - window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); - }, 50); - + // Send prompt await sendPrompt(userPrompt, userMessageId); } }; @@ -614,9 +551,7 @@ }; const regenerateResponse = async () => { - const _chatId = JSON.parse(JSON.stringify($chatId)); - console.log('regenerateResponse', _chatId); - + console.log('regenerateResponse'); if (messages.length != 0 && messages.at(-1).done == true) { messages.splice(messages.length - 1, 1); messages = messages; @@ -624,40 +559,21 @@ let userMessage = messages.at(-1); let userPrompt = userMessage.content; - await sendPrompt(userPrompt, userMessage.id, _chatId); + await sendPrompt(userPrompt, userMessage.id); } }; const generateChatTitle = async (_chatId, userPrompt) => { if ($settings.titleAutoGenerate ?? true) { - console.log('generateChatTitle'); + const title = await generateTitle( + $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL, + localStorage.token, + selectedModels[0], + userPrompt + ); - const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/generate`, { - method: 'POST', - headers: { - 'Content-Type': 'text/event-stream', - ...($user && { Authorization: `Bearer ${localStorage.token}` }) - }, - body: JSON.stringify({ - model: selectedModels[0], - prompt: `Generate a brief 3-5 word title for this question, excluding the term 'title.' Then, please reply with only the title: ${userPrompt}`, - stream: false - }) - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((error) => { - if ('detail' in error) { - toast.error(error.detail); - } - console.log(error); - return null; - }); - - if (res) { - await setChatTitle(_chatId, res.response === '' ? 'New Chat' : res.response); + if (title) { + await setChatTitle(_chatId, title); } } else { await setChatTitle(_chatId, `${userPrompt}`); @@ -665,10 +581,12 @@ }; const setChatTitle = async (_chatId, _title) => { - chat = await $db.updateChatById(_chatId, { ...chat.chat, title: _title }); if (_chatId === $chatId) { title = _title; } + + chat = await updateChatById(localStorage.token, _chatId, { title: _title }); + await chats.set(await getChatList(localStorage.token)); }; </script> diff --git a/src/routes/(app)/c/[id]/+page.svelte b/src/routes/(app)/c/[id]/+page.svelte index a2054c7b7..efa5ee1fe 100644 --- a/src/routes/(app)/c/[id]/+page.svelte +++ b/src/routes/(app)/c/[id]/+page.svelte @@ -13,6 +13,7 @@ import ModelSelector from '$lib/components/chat/ModelSelector.svelte'; import Navbar from '$lib/components/layout/Navbar.svelte'; import { page } from '$app/stores'; + import { createNewChat, getChatById, getChatList } from '$lib/apis/chats'; let loaded = false; let stopResponseFlag = false; @@ -70,7 +71,7 @@ const loadChat = async () => { await chatId.set($page.params.id); - chat = await $db.getChatById($chatId); + chat = await getChatById(localStorage.token, $chatId); const chatContent = chat.chat; @@ -159,11 +160,11 @@ }) ); - await chats.set(await $db.getChats()); + await chats.set(await getChatList(localStorage.token)); }; const sendPromptOllama = async (model, userPrompt, parentId, _chatId) => { - console.log('sendPromptOllama'); + // Create response message let responseMessageId = uuidv4(); let responseMessage = { parentId: parentId, @@ -174,8 +175,11 @@ model: model }; + // Add message to history and Set currentId to messageId history.messages[responseMessageId] = responseMessage; history.currentId = responseMessageId; + + // Append messageId to childrenIds of parent message if (parentId !== null) { history.messages[parentId].childrenIds = [ ...history.messages[parentId].childrenIds, @@ -183,17 +187,16 @@ ]; } + // Wait until history/message have been updated await tick(); + + // Scroll down window.scrollTo({ top: document.body.scrollHeight }); - const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/chat`, { - method: 'POST', - headers: { - 'Content-Type': 'text/event-stream', - ...($settings.authHeader && { Authorization: $settings.authHeader }), - ...($user && { Authorization: `Bearer ${localStorage.token}` }) - }, - body: JSON.stringify({ + const res = await generateChatCompletion( + $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL, + localStorage.token, + { model: model, messages: [ $settings.system @@ -215,20 +218,11 @@ }) })), options: { - seed: $settings.seed ?? undefined, - temperature: $settings.temperature ?? undefined, - repeat_penalty: $settings.repeat_penalty ?? undefined, - top_k: $settings.top_k ?? undefined, - top_p: $settings.top_p ?? undefined, - num_ctx: $settings.num_ctx ?? undefined, ...($settings.options ?? {}) }, format: $settings.requestFormat ?? undefined - }) - }).catch((err) => { - console.log(err); - return null; - }); + } + ); if (res && res.ok) { const reader = res.body @@ -320,23 +314,11 @@ } if ($chatId == _chatId) { - chat = await $db.updateChatById(_chatId, { - ...chat.chat, - title: title === '' ? 'New Chat' : title, - models: selectedModels, - system: $settings.system ?? undefined, - options: { - seed: $settings.seed ?? undefined, - temperature: $settings.temperature ?? undefined, - repeat_penalty: $settings.repeat_penalty ?? undefined, - top_k: $settings.top_k ?? undefined, - top_p: $settings.top_p ?? undefined, - num_ctx: $settings.num_ctx ?? undefined, - ...($settings.options ?? {}) - }, + chat = await updateChatById(localStorage.token, _chatId, { messages: messages, history: history }); + await chats.set(await getChatList(localStorage.token)); } } else { if (res !== null) { @@ -362,6 +344,7 @@ stopResponseFlag = false; await tick(); + if (autoScroll) { window.scrollTo({ top: document.body.scrollHeight }); } @@ -507,23 +490,11 @@ } if ($chatId == _chatId) { - chat = await $db.updateChatById(_chatId, { - ...chat.chat, - title: title === '' ? 'New Chat' : title, - models: selectedModels, - system: $settings.system ?? undefined, - options: { - seed: $settings.seed ?? undefined, - temperature: $settings.temperature ?? undefined, - repeat_penalty: $settings.repeat_penalty ?? undefined, - top_k: $settings.top_k ?? undefined, - top_p: $settings.top_p ?? undefined, - num_ctx: $settings.num_ctx ?? undefined, - ...($settings.options ?? {}) - }, + chat = await updateChatById(localStorage.token, _chatId, { messages: messages, history: history }); + await chats.set(await getChatList(localStorage.token)); } } else { if (res !== null) { @@ -573,10 +544,13 @@ if (selectedModels.includes('')) { toast.error('Model not selected'); } else if (messages.length != 0 && messages.at(-1).done != true) { + // Response not done console.log('wait'); } else { + // Reset chat message textarea height document.getElementById('chat-textarea').style.height = ''; + // Create user message let userMessageId = uuidv4(); let userMessage = { id: userMessageId, @@ -587,47 +561,42 @@ files: files.length > 0 ? files : undefined }; + // Add message to history and Set currentId to messageId + history.messages[userMessageId] = userMessage; + history.currentId = userMessageId; + + // Append messageId to childrenIds of parent message if (messages.length !== 0) { history.messages[messages.at(-1).id].childrenIds.push(userMessageId); } - history.messages[userMessageId] = userMessage; - history.currentId = userMessageId; - + // Wait until history/message have been updated await tick(); + // Create new chat if only one message in messages if (messages.length == 1) { - chat = await $db.createNewChat({ + chat = await createNewChat(localStorage.token, { id: $chatId, title: 'New Chat', models: selectedModels, system: $settings.system ?? undefined, options: { - seed: $settings.seed ?? undefined, - temperature: $settings.temperature ?? undefined, - repeat_penalty: $settings.repeat_penalty ?? undefined, - top_k: $settings.top_k ?? undefined, - top_p: $settings.top_p ?? undefined, - num_ctx: $settings.num_ctx ?? undefined, ...($settings.options ?? {}) }, messages: messages, - history: history + history: history, + timestamp: Date.now() }); - - console.log(chat); - + await chats.set(await getChatList(localStorage.token)); await chatId.set(chat.id); await tick(); } + // Reset chat input textarea prompt = ''; files = []; - setTimeout(() => { - window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); - }, 50); - + // Send prompt await sendPrompt(userPrompt, userMessageId); } }; @@ -638,9 +607,7 @@ }; const regenerateResponse = async () => { - const _chatId = JSON.parse(JSON.stringify($chatId)); - console.log('regenerateResponse', _chatId); - + console.log('regenerateResponse'); if (messages.length != 0 && messages.at(-1).done == true) { messages.splice(messages.length - 1, 1); messages = messages; @@ -648,41 +615,21 @@ let userMessage = messages.at(-1); let userPrompt = userMessage.content; - await sendPrompt(userPrompt, userMessage.id, _chatId); + await sendPrompt(userPrompt, userMessage.id); } }; const generateChatTitle = async (_chatId, userPrompt) => { if ($settings.titleAutoGenerate ?? true) { - console.log('generateChatTitle'); + const title = await generateTitle( + $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL, + localStorage.token, + selectedModels[0], + userPrompt + ); - const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/generate`, { - method: 'POST', - headers: { - 'Content-Type': 'text/event-stream', - ...($settings.authHeader && { Authorization: $settings.authHeader }), - ...($user && { Authorization: `Bearer ${localStorage.token}` }) - }, - body: JSON.stringify({ - model: selectedModels[0], - prompt: `Generate a brief 3-5 word title for this question, excluding the term 'title.' Then, please reply with only the title: ${userPrompt}`, - stream: false - }) - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((error) => { - if ('detail' in error) { - toast.error(error.detail); - } - console.log(error); - return null; - }); - - if (res) { - await setChatTitle(_chatId, res.response === '' ? 'New Chat' : res.response); + if (title) { + await setChatTitle(_chatId, title); } } else { await setChatTitle(_chatId, `${userPrompt}`); @@ -690,13 +637,12 @@ }; const setChatTitle = async (_chatId, _title) => { - chat = await $db.updateChatById(_chatId, { - ...chat.chat, - title: _title - }); if (_chatId === $chatId) { title = _title; } + + chat = await updateChatById(localStorage.token, _chatId, { title: _title }); + await chats.set(await getChatList(localStorage.token)); }; </script>