123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- <script lang="ts">
- import { toast } from 'svelte-sonner';
- import { onMount, tick, getContext } from 'svelte';
- import { openDB, deleteDB } from 'idb';
- import fileSaver from 'file-saver';
- const { saveAs } = fileSaver;
- import mermaid from 'mermaid';
- import { goto } from '$app/navigation';
- import { page } from '$app/stores';
- import { fade } from 'svelte/transition';
- import { getKnowledgeItems } from '$lib/apis/knowledge';
- import { getFunctions } from '$lib/apis/functions';
- import { getModels as _getModels, getVersionUpdates } from '$lib/apis';
- import { getAllTags } from '$lib/apis/chats';
- import { getPrompts } from '$lib/apis/prompts';
- import { getTools } from '$lib/apis/tools';
- import { getBanners } from '$lib/apis/configs';
- import { getUserSettings } from '$lib/apis/users';
- import { WEBUI_VERSION } from '$lib/constants';
- import { compareVersion } from '$lib/utils';
- import {
- config,
- user,
- settings,
- models,
- prompts,
- knowledge,
- tools,
- functions,
- tags,
- banners,
- showSettings,
- showChangelog,
- temporaryChatEnabled
- } from '$lib/stores';
- import Sidebar from '$lib/components/layout/Sidebar.svelte';
- import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
- import ChangelogModal from '$lib/components/ChangelogModal.svelte';
- import AccountPending from '$lib/components/layout/Overlay/AccountPending.svelte';
- import UpdateInfoToast from '$lib/components/layout/UpdateInfoToast.svelte';
- 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 () => {
- knowledge.set(await getKnowledgeItems(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 getAllTags(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();
- }
- // Check if Shift + Esc is pressed
- if (isShiftPressed && event.key === 'Escape') {
- event.preventDefault();
- console.log('focusInput');
- document.getElementById('chat-input')?.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();
- }
- // Check if Ctrl + . is pressed
- if (isCtrlPressed && event.key === '.') {
- event.preventDefault();
- console.log('openSettings');
- showSettings.set(!$showSettings);
- }
- // Check if Ctrl + / is pressed
- 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);
- }
- // Check for version updates
- 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) {
- checkForVersionUpdates();
- }
- } else {
- 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}
- on:close={() => {
- localStorage.setItem('dismissedUpdateToast', Date.now().toString());
- version = null;
- }}
- />
- </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>
|