2023-11-20 01:47:07 +00:00
< script lang = "ts" >
2024-03-01 09:18:07 +00:00
import { toast } from 'svelte-sonner';
2024-03-02 20:38:51 +00:00
import { onMount , tick , getContext } from 'svelte';
2023-12-27 02:44:08 +00:00
import { openDB , deleteDB } from 'idb';
2023-12-27 00:35:01 +00:00
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
2024-02-23 08:47:54 +00:00
import { goto } from '$app/navigation';
2023-12-26 11:28:30 +00:00
import { getOllamaModels , getOllamaVersion } from '$lib/apis/ollama';
2023-12-27 06:51:52 +00:00
import { getModelfiles } from '$lib/apis/modelfiles';
2024-01-03 05:00:50 +00:00
import { getPrompts } from '$lib/apis/prompts';
2023-12-26 19:32:22 +00:00
import { getOpenAIModels } from '$lib/apis/openai';
2024-02-22 12:12:26 +00:00
import { getLiteLLMModels } from '$lib/apis/litellm';
2024-02-23 08:47:54 +00:00
import { getDocs } from '$lib/apis/documents';
import { getAllChatTags } from '$lib/apis/chats';
2023-12-26 20:50:52 +00:00
2024-01-08 09:26:15 +00:00
import {
user,
showSettings,
settings,
models,
modelfiles,
prompts,
2024-01-18 10:55:25 +00:00
documents,
2024-02-23 08:47:54 +00:00
tags,
showChangelog,
config
2024-01-08 09:26:15 +00:00
} from '$lib/stores';
2024-01-04 21:06:31 +00:00
import { REQUIRED_OLLAMA_VERSION , WEBUI_API_BASE_URL } from '$lib/constants';
2024-02-25 19:55:15 +00:00
import { compareVersion } from '$lib/utils';
2023-12-26 21:10:50 +00:00
2023-12-26 20:50:52 +00:00
import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
import Sidebar from '$lib/components/layout/Sidebar.svelte';
2023-12-28 10:46:57 +00:00
import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
2024-02-23 08:30:26 +00:00
import ChangelogModal from '$lib/components/ChangelogModal.svelte';
2024-03-04 21:58:39 +00:00
import Tooltip from '$lib/components/common/Tooltip.svelte';
2023-12-26 20:50:52 +00:00
2024-03-02 20:38:51 +00:00
const i18n = getContext('i18n');
2023-12-26 21:10:50 +00:00
let ollamaVersion = '';
2023-11-19 05:41:43 +00:00
let loaded = false;
2024-03-04 21:58:39 +00:00
let showShortcutsButtonElement: HTMLButtonElement;
2023-12-27 01:09:24 +00:00
let DB = null;
let localDBChats = [];
2023-12-28 10:46:57 +00:00
let showShortcuts = false;
2023-11-20 01:47:07 +00:00
const getModels = async () => {
2024-02-24 09:03:28 +00:00
let models = await Promise.all([
await getOllamaModels(localStorage.token).catch((error) => {
console.log(error);
return null;
}),
await getOpenAIModels(localStorage.token).catch((error) => {
console.log(error);
return null;
}),
await getLiteLLMModels(localStorage.token).catch((error) => {
console.log(error);
return null;
})
]);
models = models
.filter((models) => models)
.reduce((a, e, i, arr) => a.concat(e, ...(i < arr.length - 1 ? [ { name : 'hr' } ] : [])), []);
2023-11-20 01:47:07 +00:00
return models;
};
2023-12-26 20:50:52 +00:00
const setOllamaVersion = async (version: string = '') => {
if (version === '') {
2024-01-04 21:06:31 +00:00
version = await getOllamaVersion(localStorage.token).catch((error) => {
2023-12-26 21:10:50 +00:00
return '';
2023-12-26 20:50:52 +00:00
});
}
2023-12-26 21:10:50 +00:00
ollamaVersion = version;
console.log(ollamaVersion);
2024-02-25 19:55:15 +00:00
if (compareVersion(REQUIRED_OLLAMA_VERSION, ollamaVersion)) {
2023-12-26 21:10:50 +00:00
toast.error(`Ollama Version: ${ ollamaVersion !== '' ? ollamaVersion : 'Not Detected' } `);
2023-12-15 03:43:52 +00:00
}
};
2023-11-19 05:41:43 +00:00
onMount(async () => {
2023-12-26 20:50:52 +00:00
if ($user === undefined) {
2023-11-19 05:41:43 +00:00
await goto('/auth');
2023-12-26 21:10:50 +00:00
} else if (['user', 'admin'].includes($user.role)) {
2023-12-27 02:44:08 +00:00
try {
// Check if IndexedDB exists
DB = await openDB('Chats', 1);
2023-12-27 00:35:01 +00:00
2023-12-27 02:44:08 +00:00
if (DB) {
const chats = await DB.getAllFromIndex('chats', 'timestamp');
localDBChats = chats.map((item, idx) => chats[chats.length - 1 - idx]);
2023-12-27 00:35:01 +00:00
2023-12-27 02:44:08 +00:00
if (localDBChats.length === 0) {
await deleteDB('Chats');
}
}
2023-12-27 00:35:01 +00:00
2023-12-27 02:44:08 +00:00
console.log(DB);
} catch (error) {
// IndexedDB Not Found
}
2023-12-27 00:35:01 +00:00
console.log();
2024-02-26 00:09:05 +00:00
await models.set(await getModels());
await tick();
2023-12-26 21:10:50 +00:00
await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{} '));
2024-01-03 05:00:50 +00:00
2024-01-08 09:26:15 +00:00
await modelfiles.set(await getModelfiles(localStorage.token));
2024-01-03 05:00:50 +00:00
await prompts.set(await getPrompts(localStorage.token));
2024-01-08 09:26:15 +00:00
await documents.set(await getDocs(localStorage.token));
2024-01-18 10:55:25 +00:00
await tags.set(await getAllChatTags(localStorage.token));
2023-11-19 05:41:43 +00:00
2023-12-26 21:10:50 +00:00
modelfiles.subscribe(async () => {
// should fetch models
2023-12-27 06:51:52 +00:00
await models.set(await getModels());
2023-12-26 21:10:50 +00:00
});
2023-12-03 18:59:47 +00:00
2023-12-28 10:46:57 +00:00
document.addEventListener('keydown', function (event) {
const isCtrlPressed = event.ctrlKey || event.metaKey; // metaKey is for Cmd key on Mac
// Check if the Shift key is pressed
const isShiftPressed = event.shiftKey;
// Check if Ctrl + Shift + O is pressed
if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 'o') {
event.preventDefault();
console.log('newChat');
document.getElementById('sidebar-new-chat-button')?.click();
}
// Check if Shift + Esc is pressed
if (isShiftPressed && event.key === 'Escape') {
event.preventDefault();
console.log('focusInput');
document.getElementById('chat-textarea')?.focus();
}
// Check if Ctrl + Shift + ; is pressed
if (isCtrlPressed && isShiftPressed && event.key === ';') {
event.preventDefault();
console.log('copyLastCodeBlock');
const button = [...document.getElementsByClassName('copy-code-button')]?.at(-1);
button?.click();
}
// Check if Ctrl + Shift + C is pressed
if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 'c') {
event.preventDefault();
console.log('copyLastResponse');
const button = [...document.getElementsByClassName('copy-response-button')]?.at(-1);
console.log(button);
button?.click();
}
// Check if Ctrl + Shift + S is pressed
if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 's') {
event.preventDefault();
console.log('toggleSidebar');
document.getElementById('sidebar-toggle-button')?.click();
}
// Check if Ctrl + Shift + Backspace is pressed
if (isCtrlPressed && isShiftPressed && event.key === 'Backspace') {
event.preventDefault();
console.log('deleteChat');
document.getElementById('delete-chat-button')?.click();
}
2023-12-30 08:24:57 +00:00
// Check if Ctrl + . is pressed
if (isCtrlPressed && event.key === '.') {
2023-12-30 07:35:08 +00:00
event.preventDefault();
console.log('openSettings');
document.getElementById('open-settings-button')?.click();
}
2023-12-28 10:46:57 +00:00
// Check if Ctrl + / is pressed
if (isCtrlPressed && event.key === '/') {
event.preventDefault();
console.log('showShortcuts');
2024-03-04 04:05:16 +00:00
showShortcutsButtonElement.click();
2023-12-28 10:46:57 +00:00
}
});
2024-02-23 08:50:54 +00:00
if ($user.role === 'admin') {
showChangelog.set(localStorage.version !== $config.version);
}
2024-02-23 08:47:54 +00:00
2023-12-26 21:10:50 +00:00
await tick();
}
2023-12-15 03:43:52 +00:00
2023-11-19 05:41:43 +00:00
loaded = true;
});
2023-11-19 00:47:12 +00:00
< / script >
2023-11-19 05:41:43 +00:00
{ #if loaded }
2023-12-28 10:46:57 +00:00
< div class = " hidden lg:flex fixed bottom-0 right-0 px-3 py-3 z-10" >
2024-03-04 21:58:39 +00:00
< Tooltip content = "help" placement = "left" >
< button
id="show-shortcuts-button"
bind:this={ showShortcutsButtonElement }
class="text-gray-600 dark:text-gray-300 bg-gray-300/20 w-6 h-6 flex items-center justify-center text-xs rounded-full"
on:click={() => {
showShortcuts = !showShortcuts;
}}
>
?
< / button >
< / Tooltip >
2023-12-28 10:46:57 +00:00
< / div >
< ShortcutsModal bind:show = { showShortcuts } / >
2023-12-15 03:43:52 +00:00
< div class = "app relative" >
2023-12-26 11:28:30 +00:00
{ #if ! [ 'user' , 'admin' ]. includes ( $user . role )}
2023-12-27 01:09:24 +00:00
< div class = "fixed w-full h-full flex z-50" >
2023-12-26 11:28:30 +00:00
< div
2023-12-27 01:18:29 +00:00
class="absolute w-full h-full backdrop-blur-md bg-white/20 dark:bg-gray-900/50 flex justify-center"
2023-12-26 11:28:30 +00:00
>
< 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
2023-12-27 01:16:25 +00:00
class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 transition font-medium text-sm"
2023-12-26 11:28:30 +00:00
on:click={ async () => {
location.href = '/';
}}
>
2024-03-03 10:01:34 +00:00
{ $i18n . t ( 'Check Again' )}
2023-12-26 11:28:30 +00:00
< / 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 >
2023-12-27 01:09:24 +00:00
{ :else if localDBChats . length > 0 }
< div class = "fixed w-full h-full flex z-50" >
< div
2023-12-27 01:18:29 +00:00
class="absolute w-full h-full backdrop-blur-md bg-white/20 dark:bg-gray-900/50 flex justify-center"
2023-12-27 01:09:24 +00:00
>
< 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" >
Important Update< br / > Action Required for Chat Log Storage
< / div >
< div class = " mt-4 text-center text-sm dark:text-gray-200 w-full" >
2024-03-02 20:38:51 +00:00
{ $i18n . t (
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through"
)}
< span class = "font-semibold dark:text-white"
>{ $i18n . t ( 'Settings' )} > { $i18n . t ( 'Chats' )} > { $i18n . t ( 'Import Chats' )} < /span
>. { $i18n . t (
'This ensures that your valuable conversations are securely saved to your backend database. Thank you!'
)}
2023-12-27 01:09:24 +00:00
< / div >
< div class = " mt-6 mx-auto relative group w-fit" >
< button
2023-12-27 01:16:25 +00:00
class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 transition font-medium text-sm"
2023-12-27 01:09:24 +00:00
on:click={ async () => {
let blob = new Blob([JSON.stringify(localDBChats)], {
type: 'application/json'
});
saveAs(blob, `chat-export-${ Date . now ()} .json`);
const tx = DB.transaction('chats', 'readwrite');
await Promise.all([tx.store.clear(), tx.done]);
2023-12-27 02:44:08 +00:00
await deleteDB('Chats');
2023-12-27 01:09:24 +00:00
localDBChats = [];
}}
>
Download & Delete
< / button >
< button
class="text-xs text-center w-full mt-2 text-gray-400 underline"
on:click={ async () => {
localDBChats = [];
}}>Close< /button
2023-12-19 19:57:49 +00:00
>
< / div >
2023-12-15 03:43:52 +00:00
< / div >
< / div >
< / div >
< / div >
{ /if }
2023-11-20 01:47:07 +00:00
< div
2024-02-08 01:15:01 +00:00
class=" text-gray-700 dark:text-gray-100 bg-white dark:bg-gray-900 min-h-screen overflow-auto flex flex-row"
2023-11-20 01:47:07 +00:00
>
< Sidebar / >
< SettingsModal bind:show = { $showSettings } / >
2024-02-23 08:47:54 +00:00
< ChangelogModal bind:show = { $showChangelog } / >
2023-11-20 01:47:07 +00:00
< slot / >
< / div >
< / div >
2023-11-19 00:47:12 +00:00
{ /if }
2023-11-20 01:47:07 +00:00
< style >
.loading {
display: inline-block;
clip-path: inset(0 1ch 0 0);
animation: l 1s steps(3) infinite;
letter-spacing: -0.5px;
}
@keyframes l {
to {
clip-path: inset(0 -1ch 0 0);
}
}
pre[class*='language-'] {
position: relative;
overflow: auto;
/* make space */
margin: 5px 0;
padding: 1.75rem 0 1.75rem 1rem;
border-radius: 10px;
}
pre[class*='language-'] button {
position: absolute;
top: 5px;
right: 5px;
font-size: 0.9rem;
padding: 0.15rem;
background-color: #828282;
border: ridge 1px #7b7b7c;
border-radius: 5px;
text-shadow: #c4c4c4 0 0 2px;
}
pre[class*='language-'] button:hover {
cursor: pointer;
background-color: #bcbabb;
}
< / style >