|
<script lang="ts"> |
|
import { toast } from 'svelte-sonner'; |
|
import { onMount, tick, getContext } from 'svelte'; |
|
import { openDB, deleteDB } from 'idb'; |
|
import fileSaver from 'file-saver'; |
|
import mermaid from 'mermaid'; |
|
|
|
const { saveAs } = fileSaver; |
|
|
|
import { goto } from '$app/navigation'; |
|
|
|
import { getModels as _getModels, getVersionUpdates } from '$lib/apis'; |
|
import { getAllChatTags } from '$lib/apis/chats'; |
|
|
|
import { getPrompts } from '$lib/apis/prompts'; |
|
import { getDocs } from '$lib/apis/documents'; |
|
import { getTools } from '$lib/apis/tools'; |
|
|
|
import { getBanners } from '$lib/apis/configs'; |
|
import { getUserSettings } from '$lib/apis/users'; |
|
|
|
import { |
|
user, |
|
showSettings, |
|
settings, |
|
models, |
|
prompts, |
|
documents, |
|
tags, |
|
banners, |
|
showChangelog, |
|
config, |
|
showCallOverlay, |
|
tools, |
|
functions, |
|
temporaryChatEnabled |
|
} from '$lib/stores'; |
|
|
|
import SettingsModal from '$lib/components/chat/SettingsModal.svelte'; |
|
import Sidebar from '$lib/components/layout/Sidebar.svelte'; |
|
import ChangelogModal from '$lib/components/ChangelogModal.svelte'; |
|
import AccountPending from '$lib/components/layout/Overlay/AccountPending.svelte'; |
|
import { getFunctions } from '$lib/apis/functions'; |
|
import { page } from '$app/stores'; |
|
import { WEBUI_VERSION } from '$lib/constants'; |
|
import { compareVersion } from '$lib/utils'; |
|
|
|
import UpdateInfoToast from '$lib/components/layout/UpdateInfoToast.svelte'; |
|
import { fade } from 'svelte/transition'; |
|
|
|
const i18n = getContext('i18n'); |
|
|
|
let loaded = false; |
|
let DB = null; |
|
let localDBChats = []; |
|
|
|
let version; |
|
|
|
const getModels = async () => { |
|
return _getModels(localStorage.token); |
|
}; |
|
|
|
onMount(async () => { |
|
if ($user === undefined) { |
|
await goto('/auth'); |
|
} else if (['user', 'admin'].includes($user.role)) { |
|
try { |
|
// Check if IndexedDB exists |
|
DB = await openDB('Chats', 1); |
|
|
|
if (DB) { |
|
const chats = await DB.getAllFromIndex('chats', 'timestamp'); |
|
localDBChats = chats.map((item, idx) => chats[chats.length - 1 - idx]); |
|
|
|
if (localDBChats.length === 0) { |
|
await deleteDB('Chats'); |
|
} |
|
} |
|
|
|
console.log(DB); |
|
} catch (error) { |
|
// IndexedDB Not Found |
|
} |
|
|
|
const userSettings = await getUserSettings(localStorage.token).catch((error) => { |
|
console.error(error); |
|
return null; |
|
}); |
|
|
|
if (userSettings) { |
|
settings.set(userSettings.ui); |
|
} else { |
|
let localStorageSettings = {} as Parameters<(typeof settings)['set']>[0]; |
|
|
|
try { |
|
localStorageSettings = JSON.parse(localStorage.getItem('settings') ?? '{}'); |
|
} catch (e: unknown) { |
|
console.error('Failed to parse settings from localStorage', e); |
|
} |
|
|
|
settings.set(localStorageSettings); |
|
} |
|
|
|
await Promise.all([ |
|
(async () => { |
|
models.set(await getModels()); |
|
})(), |
|
(async () => { |
|
prompts.set(await getPrompts(localStorage.token)); |
|
})(), |
|
(async () => { |
|
documents.set(await getDocs(localStorage.token)); |
|
})(), |
|
(async () => { |
|
tools.set(await getTools(localStorage.token)); |
|
})(), |
|
(async () => { |
|
functions.set(await getFunctions(localStorage.token)); |
|
})(), |
|
(async () => { |
|
banners.set(await getBanners(localStorage.token)); |
|
})(), |
|
(async () => { |
|
tags.set(await getAllChatTags(localStorage.token)); |
|
})() |
|
]); |
|
|
|
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(); |
|
} |
|
|
|
|
|
if (isShiftPressed && event.key === 'Escape') { |
|
event.preventDefault(); |
|
console.log('focusInput'); |
|
document.getElementById('chat-textarea')?.focus(); |
|
} |
|
|
|
|
|
if (isCtrlPressed && isShiftPressed && event.key === ';') { |
|
event.preventDefault(); |
|
console.log('copyLastCodeBlock'); |
|
const button = [...document.getElementsByClassName('copy-code-button')]?.at(-1); |
|
button?.click(); |
|
} |
|
|
|
|
|
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(); |
|
} |
|
|
|
|
|
if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 's') { |
|
event.preventDefault(); |
|
console.log('toggleSidebar'); |
|
document.getElementById('sidebar-toggle-button')?.click(); |
|
} |
|
|
|
|
|
if (isCtrlPressed && isShiftPressed && event.key === 'Backspace') { |
|
event.preventDefault(); |
|
console.log('deleteChat'); |
|
document.getElementById('delete-chat-button')?.click(); |
|
} |
|
|
|
|
|
if (isCtrlPressed && event.key === '.') { |
|
event.preventDefault(); |
|
console.log('openSettings'); |
|
showSettings.set(!$showSettings); |
|
} |
|
|
|
|
|
if (isCtrlPressed && event.key === '/') { |
|
event.preventDefault(); |
|
console.log('showShortcuts'); |
|
document.getElementById('show-shortcuts-button')?.click(); |
|
} |
|
}); |
|
|
|
if ($user.role === 'admin') { |
|
showChangelog.set(localStorage.version !== $config.version); |
|
} |
|
|
|
if ($page.url.searchParams.get('temporary-chat') === 'true') { |
|
temporaryChatEnabled.set(true); |
|
} |
|
|
|
|
|
if ($user.role === 'admin') { |
|
// Check if the user has dismissed the update toast in the last 24 hours |
|
if (localStorage.dismissedUpdateToast) { |
|
const dismissedUpdateToast = new Date(Number(localStorage.dismissedUpdateToast)); |
|
const now = new Date(); |
|
|
|
if (now - dismissedUpdateToast > 24 * 60 * 60 * 1000) { |
|
await checkForVersionUpdates(); |
|
} |
|
} else { |
|
await checkForVersionUpdates(); |
|
} |
|
} |
|
await tick(); |
|
} |
|
|
|
loaded = true; |
|
}); |
|
|
|
const checkForVersionUpdates = async () => { |
|
version = await getVersionUpdates(localStorage.token).catch((error) => { |
|
return { |
|
current: WEBUI_VERSION, |
|
latest: WEBUI_VERSION |
|
}; |
|
}); |
|
}; |
|
</script> |
|
|
|
<SettingsModal bind:show={$showSettings} /> |
|
<ChangelogModal bind:show={$showChangelog} /> |
|
|
|
{#if version && compareVersion(version.latest, version.current)} |
|
<div class=" absolute bottom-8 right-8 z-50" in:fade={{ duration: 100 }}> |
|
<UpdateInfoToast {version} /> |
|
</div> |
|
{/if} |
|
|
|
<div class="app relative"> |
|
<div |
|
class=" text-gray-700 dark:text-gray-100 bg-white dark:bg-gray-900 h-screen max-h-[100dvh] overflow-auto flex flex-row" |
|
> |
|
{#if loaded} |
|
{#if !['user', 'admin'].includes($user.role)} |
|
<AccountPending /> |
|
{:else if localDBChats.length > 0} |
|
<div class="fixed w-full h-full flex z-50"> |
|
<div |
|
class="absolute w-full h-full backdrop-blur-md bg-white/20 dark:bg-gray-900/50 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"> |
|
Important Update<br /> Action Required for Chat Log Storage |
|
</div> |
|
|
|
<div class=" mt-4 text-center text-sm dark:text-gray-200 w-full"> |
|
{$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!' |
|
)} |
|
</div> |
|
|
|
<div class=" mt-6 mx-auto relative group w-fit"> |
|
<button |
|
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" |
|
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]); |
|
await deleteDB('Chats'); |
|
|
|
localDBChats = []; |
|
}} |
|
> |
|
Download & Delete |
|
</button> |
|
|
|
<button |
|
class="text-xs text-center w-full mt-2 text-gray-400 underline" |
|
on:click={async () => { |
|
localDBChats = []; |
|
}}>{$i18n.t('Close')}</button |
|
> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
{/if} |
|
|
|
<Sidebar /> |
|
<slot /> |
|
{/if} |
|
</div> |
|
</div> |
|
|
|
<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> |
|
|