diff --git a/.dockerignore b/.dockerignore index 0221b0858..419f53fb4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -12,5 +12,5 @@ __pycache__ _old uploads .ipynb_checkpoints -*.db +**/*.db _test \ No newline at end of file diff --git a/run.sh b/run.sh index 584c7f640..e2fae795d 100644 --- a/run.sh +++ b/run.sh @@ -1,5 +1,5 @@ docker stop ollama-webui || true docker rm ollama-webui || true docker build -t ollama-webui . -docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway --name ollama-webui --restart always ollama-webui +docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v ollama-webui:/app --name ollama-webui --restart always ollama-webui docker image prune -f \ No newline at end of file diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts new file mode 100644 index 000000000..9c421816d --- /dev/null +++ b/src/lib/apis/chats/index.ts @@ -0,0 +1,162 @@ +import { WEBUI_API_BASE_URL } from '$lib/constants'; + +export const createNewChat = async (token: string, chat: object) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/new`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + chat: chat + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const getChatlist = async (token: string = '') => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const getChatById = async (token: string, id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const updateChatById = async (token: string, id: string, chat: object) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + }, + body: JSON.stringify({ + chat: chat + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const deleteChatById = async (token: string, id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}`, { + method: 'DELETE', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts new file mode 100644 index 000000000..6b6f96316 --- /dev/null +++ b/src/lib/apis/index.ts @@ -0,0 +1,35 @@ +export const getOpenAIModels = async ( + base_url: string = 'https://api.openai.com/v1', + api_key: string = '' +) => { + let error = null; + + const res = await fetch(`${base_url}/models`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${api_key}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((error) => { + console.log(error); + error = `OpenAI: ${error?.error?.message ?? 'Network Problem'}`; + return null; + }); + + if (error) { + throw error; + } + + 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/apis/ollama/index.ts b/src/lib/apis/ollama/index.ts new file mode 100644 index 000000000..67adcdf6c --- /dev/null +++ b/src/lib/apis/ollama/index.ts @@ -0,0 +1,71 @@ +import { OLLAMA_API_BASE_URL } from '$lib/constants'; + +export const getOllamaVersion = async ( + base_url: string = OLLAMA_API_BASE_URL, + token: string = '' +) => { + let error = null; + + const res = await fetch(`${base_url}/version`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((error) => { + console.log(error); + if ('detail' in error) { + error = error.detail; + } else { + error = 'Server connection failed'; + } + return null; + }); + + if (error) { + throw error; + } + + return res?.version ?? '0'; +}; + +export const getOllamaModels = async ( + base_url: string = OLLAMA_API_BASE_URL, + token: string = '' +) => { + let error = null; + + const res = await fetch(`${base_url}/tags`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((error) => { + console.log(error); + if ('detail' in error) { + error = error.detail; + } else { + error = 'Server connection failed'; + } + return null; + }); + + if (error) { + throw error; + } + + return res?.models ?? []; +}; diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index 543ce6a5e..072ade46e 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -8,11 +8,12 @@ import auto_render from 'katex/dist/contrib/auto-render.mjs'; import 'katex/dist/katex.min.css'; - import { chatId, config, db, modelfiles, settings, user } from '$lib/stores'; + import { config, db, modelfiles, settings, user } from '$lib/stores'; import { tick } from 'svelte'; import toast from 'svelte-french-toast'; + export let chatId = ''; export let sendPrompt: Function; export let regenerateResponse: Function; @@ -239,7 +240,7 @@ history.currentId = userMessageId; await tick(); - await sendPrompt(userPrompt, userMessageId, $chatId); + await sendPrompt(userPrompt, userMessageId, chatId); }; const confirmEditResponseMessage = async (messageId) => { diff --git a/src/lib/components/layout/Navbar.svelte b/src/lib/components/layout/Navbar.svelte index bcd66ee9e..219801bf8 100644 --- a/src/lib/components/layout/Navbar.svelte +++ b/src/lib/components/layout/Navbar.svelte @@ -5,11 +5,12 @@ import { chatId, db, modelfiles } from '$lib/stores'; import toast from 'svelte-french-toast'; + export let initNewChat: Function; export let title: string = 'Ollama Web UI'; export let shareEnabled: boolean = false; const shareChat = async () => { - const chat = await $db.getChatById($chatId); + const chat = (await $db.getChatById($chatId)).chat; console.log('share', chat); toast.success('Redirecting you to OllamaHub'); @@ -44,12 +45,9 @@ <div class="flex w-full max-w-full"> <div class="pr-2 self-center"> <button + id="new-chat-button" class=" cursor-pointer p-1 flex dark:hover:bg-gray-700 rounded-lg transition" - on:click={async () => { - console.log('newChat'); - goto('/'); - await chatId.set(uuidv4()); - }} + on:click={initNewChat} > <div class=" m-auto self-center"> <svg diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index bb54dbc1a..1e2a57b0f 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -55,7 +55,7 @@ }; const importChats = async (chatHistory) => { - await $db.addChats(chatHistory); + await $db.importChats(chatHistory); }; const exportChats = async () => { @@ -81,7 +81,7 @@ bind:this={navElement} class="h-screen {show ? '' - : '-translate-x-[260px]'} w-[260px] fixed top-0 left-0 z-40 transition bg-[#0a0a0a] text-gray-200 shadow-2xl text-sm + : '-translate-x-[260px]'} w-[260px] fixed top-0 left-0 z-40 transition bg-black text-gray-200 shadow-2xl text-sm " > <div class="py-2.5 my-auto flex flex-col justify-between h-screen"> @@ -91,8 +91,11 @@ on:click={async () => { goto('/'); - await chatId.set(uuidv4()); - // createNewChat(); + const newChatButton = document.getElementById('new-chat-button'); + + if (newChatButton) { + newChatButton.click(); + } }} > <div class="flex self-center"> @@ -153,7 +156,7 @@ <div class="px-2.5 mt-1 mb-2 flex justify-center space-x-2"> <div class="flex w-full"> - <div class="self-center pl-3 py-2 rounded-l bg-gray-900"> + <div class="self-center pl-3 py-2 rounded-l bg-gray-950"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" @@ -169,7 +172,7 @@ </div> <input - class="w-full rounded-r py-1.5 pl-2.5 pr-4 text-sm text-gray-300 bg-gray-900 outline-none" + class="w-full rounded-r py-1.5 pl-2.5 pr-4 text-sm text-gray-300 bg-gray-950 outline-none" placeholder="Search" bind:value={search} /> @@ -394,10 +397,10 @@ </div> <div class="px-2.5"> - <hr class=" border-gray-800 mb-2 w-full" /> + <hr class=" border-gray-900 mb-1 w-full" /> <div class="flex flex-col"> - <div class="flex"> + <!-- <div class="flex"> <input bind:this={importFileInputElement} bind:files={importFiles} type="file" hidden /> <button class=" flex rounded-md py-3 px-3.5 w-full hover:bg-gray-900 transition" @@ -534,7 +537,7 @@ </div> <span>Clear conversations</span> </button> - {/if} + {/if} --> {#if $user !== undefined} <button diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index af8c75224..991fd810b 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -21,77 +21,37 @@ 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'; + import { + createNewChat, + deleteChatById, + getChatById, + getChatlist, + updateChatById + } from '$lib/apis/chats'; let requiredOllamaVersion = '0.1.16'; let loaded = false; const getModels = async () => { let models = []; - const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/tags`, { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - ...($settings.authHeader && { Authorization: $settings.authHeader }), - ...($user && { Authorization: `Bearer ${localStorage.token}` }) - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((error) => { - console.log(error); - if ('detail' in error) { - toast.error(error.detail); - } else { - toast.error('Server connection failed'); - } - return null; - }); - console.log(res); - models.push(...(res?.models ?? [])); - + models.push( + ...(await getOllamaModels($settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL, localStorage.token)) + ); // If OpenAI API Key exists if ($settings.OPENAI_API_KEY) { - // Validate OPENAI_API_KEY + const openAIModels = await getOpenAIModels( + $settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1', + $settings.OPENAI_API_KEY + ).catch((error) => { + console.log(error); + toast.error(error); + return null; + }); - const API_BASE_URL = $settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1'; - const openaiModelRes = await fetch(`${API_BASE_URL}/models`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${$settings.OPENAI_API_KEY}` - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((error) => { - console.log(error); - toast.error(`OpenAI: ${error?.error?.message ?? 'Network Problem'}`); - return null; - }); - - const openAIModels = Array.isArray(openaiModelRes) - ? openaiModelRes - : openaiModelRes?.data ?? null; - - models.push( - ...(openAIModels - ? [ - { name: 'hr' }, - ...openAIModels - .map((model) => ({ name: model.id, external: true })) - .filter((model) => - API_BASE_URL.includes('openai') ? model.name.includes('gpt') : true - ) - ] - : []) - ); + models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : [])); } - return models; }; @@ -109,135 +69,152 @@ return { db: DB, getChatById: async function (id) { - return await this.db.get('chats', id); + const chat = await getChatById(localStorage.token, id); + return chat; }, getChats: async function () { - let chats = await this.db.getAllFromIndex('chats', 'timestamp'); - chats = chats.map((item, idx) => ({ - title: chats[chats.length - 1 - idx].title, - id: chats[chats.length - 1 - idx].id - })); + const chats = await getChatlist(localStorage.token); return chats; }, - exportChats: async function () { - let chats = await this.db.getAllFromIndex('chats', 'timestamp'); - chats = chats.map((item, idx) => chats[chats.length - 1 - idx]); - return chats; - }, - addChats: async function (_chats) { - for (const chat of _chats) { - console.log(chat); - await this.addChat(chat); - } + 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 }); }, - createNewChat: async function (chat) { - await this.addChat({ ...chat, timestamp: Date.now() }); - await chats.set(await this.getChats()); - }, - updateChatById: async function (id, updated) { - const chat = await this.getChatById(id); - 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 this.db.delete('chats', id); + + 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 getOllamaVersion = async () => { - const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/version`, { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - ...($settings.authHeader && { Authorization: $settings.authHeader }), - ...($user && { Authorization: `Bearer ${localStorage.token}` }) - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((error) => { - console.log(error); - if ('detail' in error) { - toast.error(error.detail); - } else { - toast.error('Server connection failed'); - } - return null; - }); + const setOllamaVersion = async () => { + const version = await getOllamaVersion( + $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL, + localStorage.token + ).catch((error) => { + toast.error(error); + return '0'; + }); - console.log(res); - - return res?.version ?? '0'; - }; - - const setOllamaVersion = async (ollamaVersion) => { - await info.set({ ...$info, ollama: { version: ollamaVersion } }); + await info.set({ ...$info, ollama: { version: version } }); if ( - ollamaVersion.localeCompare(requiredOllamaVersion, undefined, { + version.localeCompare(requiredOllamaVersion, undefined, { numeric: true, sensitivity: 'case', caseFirst: 'upper' }) < 0 ) { - toast.error(`Ollama Version: ${ollamaVersion}`); + toast.error(`Ollama Version: ${version}`); } }; onMount(async () => { - if ($config && $config.auth && $user === undefined) { + if ($config && $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 () => { - await models.set(await getModels()); - }); + modelfiles.subscribe(async () => {}); let _db = await getDB(); await db.set(_db); - - await setOllamaVersion(await getOllamaVersion()); + await setOllamaVersion(); await tick(); loaded = true; }); + + let child; </script> {#if loaded} <div class="app relative"> - {#if ($info?.ollama?.version ?? '0').localeCompare( requiredOllamaVersion, undefined, { numeric: true, sensitivity: 'case', caseFirst: 'upper' } ) < 0} + {#if !['user', 'admin'].includes($user.role)} + <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" + > + <div class="m-auto pb-44 flex flex-col justify-center"> + <div class="max-w-md"> + <div class="text-center dark:text-white text-2xl font-medium z-50"> + Account Activation Pending<br /> Contact Admin for WebUI Access + </div> + + <div class=" mt-4 text-center text-sm dark:text-gray-200 w-full"> + Your account status is currently pending activation. To access the WebUI, please + reach out to the administrator. Admins can manage user statuses from the Admin + Panel. + </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 () => { + location.href = '/'; + }} + > + Check Again + </button> + + <button + class="text-xs text-center w-full mt-2 text-gray-400 underline" + on:click={async () => { + localStorage.removeItem('token'); + location.href = '/auth'; + }}>Sign Out</button + > + </div> + </div> + </div> + </div> + </div> + {:else if ($info?.ollama?.version ?? '0').localeCompare( requiredOllamaVersion, 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" @@ -285,9 +262,7 @@ class=" text-gray-700 dark:text-gray-100 bg-white dark:bg-gray-800 min-h-screen overflow-auto flex flex-row" > <Sidebar /> - <SettingsModal bind:show={$showSettings} /> - <slot /> </div> </div> diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index 0d1055f9c..e7e8a03cd 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -2,18 +2,18 @@ import { v4 as uuidv4 } from 'uuid'; import toast from 'svelte-french-toast'; - import { OLLAMA_API_BASE_URL } from '$lib/constants'; - import { onMount, tick } from 'svelte'; - import { splitStream } from '$lib/utils'; + import { onDestroy, 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 { OLLAMA_API_BASE_URL } from '$lib/constants'; + import { 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 { page } from '$app/stores'; let stopResponseFlag = false; let autoScroll = true; @@ -26,10 +26,11 @@ ? $modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0])[0] : null; + let chat = null; + let title = ''; let prompt = ''; let files = []; - let messages = []; let history = { messages: {}, @@ -50,16 +51,8 @@ messages = []; } - $: if (files) { - console.log(files); - } - onMount(async () => { - await chatId.set(uuidv4()); - - chatId.subscribe(async () => { - await initNewChat(); - }); + await initNewChat(); }); ////////////////////////// @@ -67,6 +60,9 @@ ////////////////////////// const initNewChat = async () => { + console.log('initNewChat'); + + await chatId.set(''); console.log($chatId); autoScroll = true; @@ -82,7 +78,6 @@ : $settings.models ?? ['']; let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}'); - console.log(_settings); settings.set({ ..._settings }); @@ -127,14 +122,15 @@ // Ollama functions ////////////////////////// - const sendPrompt = async (userPrompt, parentId, _chatId) => { + const sendPrompt = async (prompt, parentId) => { + const _chatId = JSON.parse(JSON.stringify($chatId)); await Promise.all( selectedModels.map(async (model) => { console.log(model); if ($models.filter((m) => m.name === model)[0].external) { - await sendPromptOpenAI(model, userPrompt, parentId, _chatId); + await sendPromptOpenAI(model, prompt, parentId, _chatId); } else { - await sendPromptOllama(model, userPrompt, parentId, _chatId); + await sendPromptOllama(model, prompt, parentId, _chatId); } }) ); @@ -297,8 +293,11 @@ if (autoScroll) { window.scrollTo({ top: document.body.scrollHeight }); } + } - await $db.updateChatById(_chatId, { + if ($chatId == _chatId) { + chat = await $db.updateChatById(_chatId, { + ...chat.chat, title: title === '' ? 'New Chat' : title, models: selectedModels, system: $settings.system ?? undefined, @@ -481,8 +480,11 @@ if (autoScroll) { window.scrollTo({ top: document.body.scrollHeight }); } + } - await $db.updateChatById(_chatId, { + if ($chatId == _chatId) { + chat = await $db.updateChatById(_chatId, { + ...chat.chat, title: title === '' ? 'New Chat' : title, models: selectedModels, system: $settings.system ?? undefined, @@ -542,8 +544,7 @@ }; const submitPrompt = async (userPrompt) => { - const _chatId = JSON.parse(JSON.stringify($chatId)); - console.log('submitPrompt', _chatId); + console.log('submitPrompt', $chatId); if (selectedModels.includes('')) { toast.error('Model not selected'); @@ -570,9 +571,10 @@ history.currentId = userMessageId; await tick(); + if (messages.length == 1) { - await $db.createNewChat({ - id: _chatId, + chat = await $db.createNewChat({ + id: $chatId, title: 'New Chat', models: selectedModels, system: $settings.system ?? undefined, @@ -588,6 +590,11 @@ messages: messages, history: history }); + + console.log(chat); + + await chatId.set(chat.id); + await tick(); } prompt = ''; @@ -597,7 +604,7 @@ window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); }, 50); - await sendPrompt(userPrompt, userMessageId, _chatId); + await sendPrompt(userPrompt, userMessageId); } }; @@ -629,7 +636,6 @@ method: 'POST', headers: { 'Content-Type': 'text/event-stream', - ...($settings.authHeader && { Authorization: $settings.authHeader }), ...($user && { Authorization: `Bearer ${localStorage.token}` }) }, body: JSON.stringify({ @@ -659,7 +665,7 @@ }; const setChatTitle = async (_chatId, _title) => { - await $db.updateChatById(_chatId, { title: _title }); + chat = await $db.updateChatById(_chatId, { ...chat.chat, title: _title }); if (_chatId === $chatId) { title = _title; } @@ -672,7 +678,7 @@ }} /> -<Navbar {title} shareEnabled={messages.length > 0} /> +<Navbar {title} shareEnabled={messages.length > 0} {initNewChat} /> <div class="min-h-screen w-full flex justify-center"> <div class=" py-2.5 flex flex-col justify-between w-full"> <div class="max-w-2xl mx-auto w-full px-3 md:px-0 mt-10"> @@ -681,6 +687,7 @@ <div class=" h-full mt-10 mb-32 w-full flex flex-col"> <Messages + chatId={$chatId} {selectedModels} {selectedModelfile} bind:history diff --git a/src/routes/(app)/c/[id]/+page.svelte b/src/routes/(app)/c/[id]/+page.svelte index 1b2c7053b..a2054c7b7 100644 --- a/src/routes/(app)/c/[id]/+page.svelte +++ b/src/routes/(app)/c/[id]/+page.svelte @@ -27,6 +27,8 @@ ? $modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0])[0] : null; + let chat = null; + let title = ''; let prompt = ''; let files = []; @@ -53,10 +55,8 @@ $: if ($page.params.id) { (async () => { - let chat = await loadChat(); - - await tick(); - if (chat) { + if (await loadChat()) { + await tick(); loaded = true; } else { await goto('/'); @@ -70,33 +70,38 @@ const loadChat = async () => { await chatId.set($page.params.id); - const chat = await $db.getChatById($chatId); + chat = await $db.getChatById($chatId); - if (chat) { - console.log(chat); + const chatContent = chat.chat; - selectedModels = (chat?.models ?? undefined) !== undefined ? chat.models : [chat.model ?? '']; + if (chatContent) { + console.log(chatContent); + + selectedModels = + (chatContent?.models ?? undefined) !== undefined + ? chatContent.models + : [chatContent.model ?? '']; history = - (chat?.history ?? undefined) !== undefined - ? chat.history - : convertMessagesToHistory(chat.messages); - title = chat.title; + (chatContent?.history ?? undefined) !== undefined + ? chatContent.history + : convertMessagesToHistory(chatContent.messages); + title = chatContent.title; let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}'); await settings.set({ ..._settings, - system: chat.system ?? _settings.system, - options: chat.options ?? _settings.options + system: chatContent.system ?? _settings.system, + options: chatContent.options ?? _settings.options }); autoScroll = true; - await tick(); + if (messages.length > 0) { history.messages[messages.at(-1).id].done = true; } await tick(); - return chat; + return true; } else { return null; } @@ -141,14 +146,15 @@ // Ollama functions ////////////////////////// - const sendPrompt = async (userPrompt, parentId, _chatId) => { + const sendPrompt = async (prompt, parentId) => { + const _chatId = JSON.parse(JSON.stringify($chatId)); await Promise.all( selectedModels.map(async (model) => { console.log(model); if ($models.filter((m) => m.name === model)[0].external) { - await sendPromptOpenAI(model, userPrompt, parentId, _chatId); + await sendPromptOpenAI(model, prompt, parentId, _chatId); } else { - await sendPromptOllama(model, userPrompt, parentId, _chatId); + await sendPromptOllama(model, prompt, parentId, _chatId); } }) ); @@ -311,8 +317,11 @@ if (autoScroll) { window.scrollTo({ top: document.body.scrollHeight }); } + } - await $db.updateChatById(_chatId, { + if ($chatId == _chatId) { + chat = await $db.updateChatById(_chatId, { + ...chat.chat, title: title === '' ? 'New Chat' : title, models: selectedModels, system: $settings.system ?? undefined, @@ -495,8 +504,11 @@ if (autoScroll) { window.scrollTo({ top: document.body.scrollHeight }); } + } - await $db.updateChatById(_chatId, { + if ($chatId == _chatId) { + chat = await $db.updateChatById(_chatId, { + ...chat.chat, title: title === '' ? 'New Chat' : title, models: selectedModels, system: $settings.system ?? undefined, @@ -556,8 +568,7 @@ }; const submitPrompt = async (userPrompt) => { - const _chatId = JSON.parse(JSON.stringify($chatId)); - console.log('submitPrompt', _chatId); + console.log('submitPrompt', $chatId); if (selectedModels.includes('')) { toast.error('Model not selected'); @@ -584,9 +595,10 @@ history.currentId = userMessageId; await tick(); + if (messages.length == 1) { - await $db.createNewChat({ - id: _chatId, + chat = await $db.createNewChat({ + id: $chatId, title: 'New Chat', models: selectedModels, system: $settings.system ?? undefined, @@ -602,6 +614,11 @@ messages: messages, history: history }); + + console.log(chat); + + await chatId.set(chat.id); + await tick(); } prompt = ''; @@ -611,7 +628,7 @@ window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); }, 50); - await sendPrompt(userPrompt, userMessageId, _chatId); + await sendPrompt(userPrompt, userMessageId); } }; @@ -673,7 +690,10 @@ }; const setChatTitle = async (_chatId, _title) => { - await $db.updateChatById(_chatId, { title: _title }); + chat = await $db.updateChatById(_chatId, { + ...chat.chat, + title: _title + }); if (_chatId === $chatId) { title = _title; } @@ -687,7 +707,13 @@ /> {#if loaded} - <Navbar {title} shareEnabled={messages.length > 0} /> + <Navbar + {title} + shareEnabled={messages.length > 0} + initNewChat={() => { + goto('/'); + }} + /> <div class="min-h-screen w-full flex justify-center"> <div class=" py-2.5 flex flex-col justify-between w-full"> <div class="max-w-2xl mx-auto w-full px-3 md:px-0 mt-10"> @@ -696,6 +722,7 @@ <div class=" h-full mt-10 mb-32 w-full flex flex-col"> <Messages + chatId={$chatId} {selectedModels} {selectedModelfile} bind:history diff --git a/src/routes/(app)/modelfiles/create/+page.svelte b/src/routes/(app)/modelfiles/create/+page.svelte index 505ab02d8..fecb02579 100644 --- a/src/routes/(app)/modelfiles/create/+page.svelte +++ b/src/routes/(app)/modelfiles/create/+page.svelte @@ -132,7 +132,6 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, ''); method: 'POST', headers: { 'Content-Type': 'text/event-stream', - ...($settings.authHeader && { Authorization: $settings.authHeader }), ...($user && { Authorization: `Bearer ${localStorage.token}` }) }, body: JSON.stringify({ diff --git a/src/routes/admin/+page.svelte b/src/routes/admin/+page.svelte index 6cdbcbf37..5c2fdb874 100644 --- a/src/routes/admin/+page.svelte +++ b/src/routes/admin/+page.svelte @@ -37,7 +37,7 @@ }; const getUsers = async () => { - const res = await fetch(`${WEBUI_API_BASE_URL}/users/`, { + const res = await fetch(`${WEBUI_API_BASE_URL}/users`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -58,7 +58,7 @@ }; onMount(async () => { - if ($config === null || !$config.auth || ($config.auth && $user && $user.role !== 'admin')) { + if ($user?.role !== 'admin') { await goto('/'); } else { await getUsers(); diff --git a/src/routes/auth/+page.svelte b/src/routes/auth/+page.svelte index 9ec0b16f9..d77ee503c 100644 --- a/src/routes/auth/+page.svelte +++ b/src/routes/auth/+page.svelte @@ -105,7 +105,7 @@ </div> </div> - <div class="w-full max-w-xl px-10 md:px-16 bg-white min-h-screen w-full flex flex-col"> + <div class="w-full max-w-xl px-10 md:px-16 bg-white min-h-screen flex flex-col"> <div class=" my-auto pb-10 w-full"> <form class=" flex flex-col justify-center" diff --git a/src/routes/error/+page.svelte b/src/routes/error/+page.svelte index 5ce3fa03f..fd3eb5645 100644 --- a/src/routes/error/+page.svelte +++ b/src/routes/error/+page.svelte @@ -16,23 +16,24 @@ {#if loaded} <div class="absolute w-full h-full flex z-50"> - <div class="absolute rounded-xl w-full h-full backdrop-blur bg-gray-900/5 flex justify-center"> + <div class="absolute rounded-xl w-full h-full backdrop-blur flex justify-center"> <div class="m-auto pb-44 flex flex-col justify-center"> <div class="max-w-md"> <div class="text-center text-2xl font-medium z-50">Ollama WebUI Backend Required</div> <div class=" mt-4 text-center text-sm w-full"> - Oops! It seems like your Ollama WebUI needs a little attention. <br - class=" hidden sm:flex" - /> - describe troubleshooting/installation, help @ discord - - <!-- TODO: update text --> + Oops! You're using an unsupported method (frontend only).<br class=" hidden sm:flex" /> + Please access the WebUI from the backend. See readme.md for instructions or join our Discord + for help. + <!-- TODO: update links --> </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={() => { + location.href = '/'; + }} > Check Again </button>