feat: multi-user chat history support

This commit is contained in:
Timothy J. Baek 2023-12-26 03:28:30 -08:00
parent 1274bd986b
commit 0810a2648f
15 changed files with 495 additions and 216 deletions

View file

@ -12,5 +12,5 @@ __pycache__
_old
uploads
.ipynb_checkpoints
*.db
**/*.db
_test

2
run.sh
View file

@ -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

162
src/lib/apis/chats/index.ts Normal file
View file

@ -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;
};

35
src/lib/apis/index.ts Normal file
View file

@ -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));
};

View file

@ -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 ?? [];
};

View file

@ -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) => {

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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({

View file

@ -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();

View file

@ -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"

View file

@ -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>