123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- <script lang="ts">
- import { v4 as uuidv4 } from 'uuid';
- import { openDB, deleteDB } from 'idb';
- import { onMount, tick } from 'svelte';
- import { goto } from '$app/navigation';
- import {
- config,
- info,
- user,
- showSettings,
- settings,
- models,
- db,
- chats,
- chatId,
- modelfiles
- } from '$lib/stores';
- import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
- 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';
- 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 ?? []));
- // If OpenAI API Key exists
- if ($settings.OPENAI_API_KEY) {
- // Validate OPENAI_API_KEY
- const openaiModelRes = await fetch(`https://api.openai.com/v1/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 = openaiModelRes?.data ?? null;
- models.push(
- ...(openAIModels
- ? [
- { name: 'hr' },
- ...openAIModels
- .map((model) => ({ name: model.id, label: 'OpenAI' }))
- .filter((model) => model.name.includes('gpt'))
- ]
- : [])
- );
- }
- return models;
- };
- const getDB = async () => {
- const DB = await openDB('Chats', 1, {
- upgrade(db) {
- const store = db.createObjectStore('chats', {
- keyPath: 'id',
- autoIncrement: true
- });
- store.createIndex('timestamp', 'timestamp');
- }
- });
- return {
- db: DB,
- getChatById: async function (id) {
- return await this.db.get('chats', id);
- },
- 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
- }));
- 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);
- }
- await chats.set(await this.getChats());
- },
- 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,
- ...updated,
- timestamp: Date.now()
- });
- await chats.set(await this.getChats());
- },
- deleteChatById: async function (id) {
- if ($chatId === id) {
- goto('/');
- await chatId.set(uuidv4());
- }
- await this.db.delete('chats', 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());
- }
- };
- };
- 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;
- });
- console.log(res);
- return res?.version ?? '0';
- };
- const setOllamaVersion = async (ollamaVersion) => {
- await info.set({ ...$info, ollama: { version: ollamaVersion } });
- if (
- ollamaVersion.localeCompare(requiredOllamaVersion, undefined, {
- numeric: true,
- sensitivity: 'case',
- caseFirst: 'upper'
- }) < 0
- ) {
- toast.error(`Ollama Version: ${ollamaVersion}`);
- }
- };
- onMount(async () => {
- if ($config && $config.auth && $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());
- });
- let _db = await getDB();
- await db.set(_db);
- await setOllamaVersion(await getOllamaVersion());
- await tick();
- loaded = true;
- });
- </script>
- {#if loaded}
- <div class="app relative">
- {#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"
- >
- <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">
- Connection Issue or Update Needed
- </div>
- <div class=" mt-4 text-center text-sm dark:text-gray-200 w-full">
- Oops! It seems like your Ollama needs a little attention. <br
- class=" hidden sm:flex"
- />We've detected either a connection hiccup or observed that you're using an older
- version. Ensure you're on the latest Ollama version
- <br class=" hidden sm:flex" />(version
- <span class=" dark:text-white font-medium">{requiredOllamaVersion} or higher</span>)
- or check your connection.
- </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 () => {
- await setOllamaVersion(await getOllamaVersion());
- }}
- >
- Check Again
- </button>
- <button
- class="text-xs text-center w-full mt-2 text-gray-400 underline"
- on:click={async () => {
- await setOllamaVersion(requiredOllamaVersion);
- }}>Close</button
- >
- </div>
- </div>
- </div>
- </div>
- </div>
- {/if}
- <div
- 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>
- {/if}
- <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>
|