forked from open-webui/open-webui
feat: multi-user chat history support
This commit is contained in:
parent
1274bd986b
commit
0810a2648f
15 changed files with 495 additions and 216 deletions
|
@ -12,5 +12,5 @@ __pycache__
|
||||||
_old
|
_old
|
||||||
uploads
|
uploads
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
*.db
|
**/*.db
|
||||||
_test
|
_test
|
2
run.sh
2
run.sh
|
@ -1,5 +1,5 @@
|
||||||
docker stop ollama-webui || true
|
docker stop ollama-webui || true
|
||||||
docker rm ollama-webui || true
|
docker rm ollama-webui || true
|
||||||
docker build -t ollama-webui .
|
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
|
docker image prune -f
|
162
src/lib/apis/chats/index.ts
Normal file
162
src/lib/apis/chats/index.ts
Normal 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
35
src/lib/apis/index.ts
Normal 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));
|
||||||
|
};
|
71
src/lib/apis/ollama/index.ts
Normal file
71
src/lib/apis/ollama/index.ts
Normal 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 ?? [];
|
||||||
|
};
|
|
@ -8,11 +8,12 @@
|
||||||
import auto_render from 'katex/dist/contrib/auto-render.mjs';
|
import auto_render from 'katex/dist/contrib/auto-render.mjs';
|
||||||
import 'katex/dist/katex.min.css';
|
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 { tick } from 'svelte';
|
||||||
|
|
||||||
import toast from 'svelte-french-toast';
|
import toast from 'svelte-french-toast';
|
||||||
|
|
||||||
|
export let chatId = '';
|
||||||
export let sendPrompt: Function;
|
export let sendPrompt: Function;
|
||||||
export let regenerateResponse: Function;
|
export let regenerateResponse: Function;
|
||||||
|
|
||||||
|
@ -239,7 +240,7 @@
|
||||||
history.currentId = userMessageId;
|
history.currentId = userMessageId;
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
await sendPrompt(userPrompt, userMessageId, $chatId);
|
await sendPrompt(userPrompt, userMessageId, chatId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmEditResponseMessage = async (messageId) => {
|
const confirmEditResponseMessage = async (messageId) => {
|
||||||
|
|
|
@ -5,11 +5,12 @@
|
||||||
import { chatId, db, modelfiles } from '$lib/stores';
|
import { chatId, db, modelfiles } from '$lib/stores';
|
||||||
import toast from 'svelte-french-toast';
|
import toast from 'svelte-french-toast';
|
||||||
|
|
||||||
|
export let initNewChat: Function;
|
||||||
export let title: string = 'Ollama Web UI';
|
export let title: string = 'Ollama Web UI';
|
||||||
export let shareEnabled: boolean = false;
|
export let shareEnabled: boolean = false;
|
||||||
|
|
||||||
const shareChat = async () => {
|
const shareChat = async () => {
|
||||||
const chat = await $db.getChatById($chatId);
|
const chat = (await $db.getChatById($chatId)).chat;
|
||||||
console.log('share', chat);
|
console.log('share', chat);
|
||||||
toast.success('Redirecting you to OllamaHub');
|
toast.success('Redirecting you to OllamaHub');
|
||||||
|
|
||||||
|
@ -44,12 +45,9 @@
|
||||||
<div class="flex w-full max-w-full">
|
<div class="flex w-full max-w-full">
|
||||||
<div class="pr-2 self-center">
|
<div class="pr-2 self-center">
|
||||||
<button
|
<button
|
||||||
|
id="new-chat-button"
|
||||||
class=" cursor-pointer p-1 flex dark:hover:bg-gray-700 rounded-lg transition"
|
class=" cursor-pointer p-1 flex dark:hover:bg-gray-700 rounded-lg transition"
|
||||||
on:click={async () => {
|
on:click={initNewChat}
|
||||||
console.log('newChat');
|
|
||||||
goto('/');
|
|
||||||
await chatId.set(uuidv4());
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div class=" m-auto self-center">
|
<div class=" m-auto self-center">
|
||||||
<svg
|
<svg
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const importChats = async (chatHistory) => {
|
const importChats = async (chatHistory) => {
|
||||||
await $db.addChats(chatHistory);
|
await $db.importChats(chatHistory);
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportChats = async () => {
|
const exportChats = async () => {
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
bind:this={navElement}
|
bind:this={navElement}
|
||||||
class="h-screen {show
|
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">
|
<div class="py-2.5 my-auto flex flex-col justify-between h-screen">
|
||||||
|
@ -91,8 +91,11 @@
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
goto('/');
|
goto('/');
|
||||||
|
|
||||||
await chatId.set(uuidv4());
|
const newChatButton = document.getElementById('new-chat-button');
|
||||||
// createNewChat();
|
|
||||||
|
if (newChatButton) {
|
||||||
|
newChatButton.click();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="flex self-center">
|
<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="px-2.5 mt-1 mb-2 flex justify-center space-x-2">
|
||||||
<div class="flex w-full">
|
<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
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
|
@ -169,7 +172,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input
|
<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"
|
placeholder="Search"
|
||||||
bind:value={search}
|
bind:value={search}
|
||||||
/>
|
/>
|
||||||
|
@ -394,10 +397,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-2.5">
|
<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 flex-col">
|
||||||
<div class="flex">
|
<!-- <div class="flex">
|
||||||
<input bind:this={importFileInputElement} bind:files={importFiles} type="file" hidden />
|
<input bind:this={importFileInputElement} bind:files={importFiles} type="file" hidden />
|
||||||
<button
|
<button
|
||||||
class=" flex rounded-md py-3 px-3.5 w-full hover:bg-gray-900 transition"
|
class=" flex rounded-md py-3 px-3.5 w-full hover:bg-gray-900 transition"
|
||||||
|
@ -534,7 +537,7 @@
|
||||||
</div>
|
</div>
|
||||||
<span>Clear conversations</span>
|
<span>Clear conversations</span>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if} -->
|
||||||
|
|
||||||
{#if $user !== undefined}
|
{#if $user !== undefined}
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -21,77 +21,37 @@
|
||||||
import Sidebar from '$lib/components/layout/Sidebar.svelte';
|
import Sidebar from '$lib/components/layout/Sidebar.svelte';
|
||||||
import toast from 'svelte-french-toast';
|
import toast from 'svelte-french-toast';
|
||||||
import { OLLAMA_API_BASE_URL, WEBUI_API_BASE_URL } from '$lib/constants';
|
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 requiredOllamaVersion = '0.1.16';
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
|
||||||
const getModels = async () => {
|
const getModels = async () => {
|
||||||
let models = [];
|
let models = [];
|
||||||
const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/tags`, {
|
models.push(
|
||||||
method: 'GET',
|
...(await getOllamaModels($settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL, localStorage.token))
|
||||||
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 OpenAI API Key exists
|
||||||
if ($settings.OPENAI_API_KEY) {
|
if ($settings.OPENAI_API_KEY) {
|
||||||
// Validate OPENAI_API_KEY
|
const openAIModels = await getOpenAIModels(
|
||||||
|
$settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1',
|
||||||
const API_BASE_URL = $settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1';
|
$settings.OPENAI_API_KEY
|
||||||
const openaiModelRes = await fetch(`${API_BASE_URL}/models`, {
|
).catch((error) => {
|
||||||
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);
|
console.log(error);
|
||||||
toast.error(`OpenAI: ${error?.error?.message ?? 'Network Problem'}`);
|
toast.error(error);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const openAIModels = Array.isArray(openaiModelRes)
|
models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : []));
|
||||||
? 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
|
|
||||||
)
|
|
||||||
]
|
|
||||||
: [])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return models;
|
return models;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -109,135 +69,152 @@
|
||||||
return {
|
return {
|
||||||
db: DB,
|
db: DB,
|
||||||
getChatById: async function (id) {
|
getChatById: async function (id) {
|
||||||
return await this.db.get('chats', id);
|
const chat = await getChatById(localStorage.token, id);
|
||||||
|
return chat;
|
||||||
},
|
},
|
||||||
getChats: async function () {
|
getChats: async function () {
|
||||||
let chats = await this.db.getAllFromIndex('chats', 'timestamp');
|
const chats = await getChatlist(localStorage.token);
|
||||||
chats = chats.map((item, idx) => ({
|
|
||||||
title: chats[chats.length - 1 - idx].title,
|
|
||||||
id: chats[chats.length - 1 - idx].id
|
|
||||||
}));
|
|
||||||
return chats;
|
return chats;
|
||||||
},
|
},
|
||||||
exportChats: async function () {
|
createNewChat: async function (_chat) {
|
||||||
let chats = await this.db.getAllFromIndex('chats', 'timestamp');
|
const chat = await createNewChat(localStorage.token, { ..._chat, timestamp: Date.now() });
|
||||||
chats = chats.map((item, idx) => chats[chats.length - 1 - idx]);
|
|
||||||
return chats;
|
|
||||||
},
|
|
||||||
addChats: async function (_chats) {
|
|
||||||
for (const chat of _chats) {
|
|
||||||
console.log(chat);
|
console.log(chat);
|
||||||
await this.addChat(chat);
|
|
||||||
}
|
|
||||||
await chats.set(await this.getChats());
|
await chats.set(await this.getChats());
|
||||||
|
|
||||||
|
return chat;
|
||||||
},
|
},
|
||||||
|
|
||||||
addChat: async function (chat) {
|
addChat: async function (chat) {
|
||||||
await this.db.put('chats', {
|
await this.db.put('chats', {
|
||||||
...chat
|
...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', {
|
updateChatById: async function (id, updated) {
|
||||||
...chat,
|
const chat = await updateChatById(localStorage.token, id, {
|
||||||
...updated,
|
...updated,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
});
|
});
|
||||||
|
|
||||||
await chats.set(await this.getChats());
|
await chats.set(await this.getChats());
|
||||||
|
return chat;
|
||||||
},
|
},
|
||||||
deleteChatById: async function (id) {
|
deleteChatById: async function (id) {
|
||||||
if ($chatId === id) {
|
if ($chatId === id) {
|
||||||
goto('/');
|
goto('/');
|
||||||
await chatId.set(uuidv4());
|
await chatId.set(uuidv4());
|
||||||
}
|
}
|
||||||
await this.db.delete('chats', id);
|
|
||||||
|
await deleteChatById(localStorage.token, id);
|
||||||
await chats.set(await this.getChats());
|
await chats.set(await this.getChats());
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteAllChat: async function () {
|
deleteAllChat: async function () {
|
||||||
const tx = this.db.transaction('chats', 'readwrite');
|
const tx = this.db.transaction('chats', 'readwrite');
|
||||||
await Promise.all([tx.store.clear(), tx.done]);
|
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());
|
await chats.set(await this.getChats());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOllamaVersion = async () => {
|
const setOllamaVersion = async () => {
|
||||||
const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/version`, {
|
const version = await getOllamaVersion(
|
||||||
method: 'GET',
|
$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
|
||||||
headers: {
|
localStorage.token
|
||||||
Accept: 'application/json',
|
).catch((error) => {
|
||||||
'Content-Type': 'application/json',
|
toast.error(error);
|
||||||
...($settings.authHeader && { Authorization: $settings.authHeader }),
|
return '0';
|
||||||
...($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);
|
await info.set({ ...$info, ollama: { version: version } });
|
||||||
|
|
||||||
return res?.version ?? '0';
|
|
||||||
};
|
|
||||||
|
|
||||||
const setOllamaVersion = async (ollamaVersion) => {
|
|
||||||
await info.set({ ...$info, ollama: { version: ollamaVersion } });
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
ollamaVersion.localeCompare(requiredOllamaVersion, undefined, {
|
version.localeCompare(requiredOllamaVersion, undefined, {
|
||||||
numeric: true,
|
numeric: true,
|
||||||
sensitivity: 'case',
|
sensitivity: 'case',
|
||||||
caseFirst: 'upper'
|
caseFirst: 'upper'
|
||||||
}) < 0
|
}) < 0
|
||||||
) {
|
) {
|
||||||
toast.error(`Ollama Version: ${ollamaVersion}`);
|
toast.error(`Ollama Version: ${version}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($config && $config.auth && $user === undefined) {
|
if ($config && $user === undefined) {
|
||||||
await goto('/auth');
|
await goto('/auth');
|
||||||
}
|
}
|
||||||
|
|
||||||
await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
|
await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
|
||||||
|
|
||||||
await models.set(await getModels());
|
await models.set(await getModels());
|
||||||
|
|
||||||
await modelfiles.set(JSON.parse(localStorage.getItem('modelfiles') ?? '[]'));
|
await modelfiles.set(JSON.parse(localStorage.getItem('modelfiles') ?? '[]'));
|
||||||
|
|
||||||
modelfiles.subscribe(async () => {
|
modelfiles.subscribe(async () => {});
|
||||||
await models.set(await getModels());
|
|
||||||
});
|
|
||||||
|
|
||||||
let _db = await getDB();
|
let _db = await getDB();
|
||||||
await db.set(_db);
|
await db.set(_db);
|
||||||
|
await setOllamaVersion();
|
||||||
await setOllamaVersion(await getOllamaVersion());
|
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
loaded = true;
|
loaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let child;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loaded}
|
{#if loaded}
|
||||||
<div class="app relative">
|
<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 w-full h-full flex z-50">
|
||||||
<div
|
<div
|
||||||
class="absolute rounded-xl w-full h-full backdrop-blur bg-gray-900/60 flex justify-center"
|
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"
|
class=" text-gray-700 dark:text-gray-100 bg-white dark:bg-gray-800 min-h-screen overflow-auto flex flex-row"
|
||||||
>
|
>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
|
||||||
<SettingsModal bind:show={$showSettings} />
|
<SettingsModal bind:show={$showSettings} />
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,18 +2,18 @@
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import toast from 'svelte-french-toast';
|
import toast from 'svelte-french-toast';
|
||||||
|
|
||||||
import { OLLAMA_API_BASE_URL } from '$lib/constants';
|
import { onDestroy, onMount, tick } from 'svelte';
|
||||||
import { onMount, tick } from 'svelte';
|
|
||||||
import { splitStream } from '$lib/utils';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
import { config, models, modelfiles, user, settings, db, chats, chatId } from '$lib/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 MessageInput from '$lib/components/chat/MessageInput.svelte';
|
||||||
import Messages from '$lib/components/chat/Messages.svelte';
|
import Messages from '$lib/components/chat/Messages.svelte';
|
||||||
import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
|
import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
|
||||||
import Navbar from '$lib/components/layout/Navbar.svelte';
|
import Navbar from '$lib/components/layout/Navbar.svelte';
|
||||||
import { page } from '$app/stores';
|
|
||||||
|
|
||||||
let stopResponseFlag = false;
|
let stopResponseFlag = false;
|
||||||
let autoScroll = true;
|
let autoScroll = true;
|
||||||
|
@ -26,10 +26,11 @@
|
||||||
? $modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0])[0]
|
? $modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0])[0]
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
let chat = null;
|
||||||
|
|
||||||
let title = '';
|
let title = '';
|
||||||
let prompt = '';
|
let prompt = '';
|
||||||
let files = [];
|
let files = [];
|
||||||
|
|
||||||
let messages = [];
|
let messages = [];
|
||||||
let history = {
|
let history = {
|
||||||
messages: {},
|
messages: {},
|
||||||
|
@ -50,23 +51,18 @@
|
||||||
messages = [];
|
messages = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (files) {
|
|
||||||
console.log(files);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await chatId.set(uuidv4());
|
|
||||||
|
|
||||||
chatId.subscribe(async () => {
|
|
||||||
await initNewChat();
|
await initNewChat();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
// Web functions
|
// Web functions
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
|
||||||
const initNewChat = async () => {
|
const initNewChat = async () => {
|
||||||
|
console.log('initNewChat');
|
||||||
|
|
||||||
|
await chatId.set('');
|
||||||
console.log($chatId);
|
console.log($chatId);
|
||||||
|
|
||||||
autoScroll = true;
|
autoScroll = true;
|
||||||
|
@ -82,7 +78,6 @@
|
||||||
: $settings.models ?? [''];
|
: $settings.models ?? [''];
|
||||||
|
|
||||||
let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
||||||
console.log(_settings);
|
|
||||||
settings.set({
|
settings.set({
|
||||||
..._settings
|
..._settings
|
||||||
});
|
});
|
||||||
|
@ -127,14 +122,15 @@
|
||||||
// Ollama functions
|
// Ollama functions
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
|
||||||
const sendPrompt = async (userPrompt, parentId, _chatId) => {
|
const sendPrompt = async (prompt, parentId) => {
|
||||||
|
const _chatId = JSON.parse(JSON.stringify($chatId));
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
selectedModels.map(async (model) => {
|
selectedModels.map(async (model) => {
|
||||||
console.log(model);
|
console.log(model);
|
||||||
if ($models.filter((m) => m.name === model)[0].external) {
|
if ($models.filter((m) => m.name === model)[0].external) {
|
||||||
await sendPromptOpenAI(model, userPrompt, parentId, _chatId);
|
await sendPromptOpenAI(model, prompt, parentId, _chatId);
|
||||||
} else {
|
} else {
|
||||||
await sendPromptOllama(model, userPrompt, parentId, _chatId);
|
await sendPromptOllama(model, prompt, parentId, _chatId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -297,8 +293,11 @@
|
||||||
if (autoScroll) {
|
if (autoScroll) {
|
||||||
window.scrollTo({ top: document.body.scrollHeight });
|
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,
|
title: title === '' ? 'New Chat' : title,
|
||||||
models: selectedModels,
|
models: selectedModels,
|
||||||
system: $settings.system ?? undefined,
|
system: $settings.system ?? undefined,
|
||||||
|
@ -481,8 +480,11 @@
|
||||||
if (autoScroll) {
|
if (autoScroll) {
|
||||||
window.scrollTo({ top: document.body.scrollHeight });
|
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,
|
title: title === '' ? 'New Chat' : title,
|
||||||
models: selectedModels,
|
models: selectedModels,
|
||||||
system: $settings.system ?? undefined,
|
system: $settings.system ?? undefined,
|
||||||
|
@ -542,8 +544,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitPrompt = async (userPrompt) => {
|
const submitPrompt = async (userPrompt) => {
|
||||||
const _chatId = JSON.parse(JSON.stringify($chatId));
|
console.log('submitPrompt', $chatId);
|
||||||
console.log('submitPrompt', _chatId);
|
|
||||||
|
|
||||||
if (selectedModels.includes('')) {
|
if (selectedModels.includes('')) {
|
||||||
toast.error('Model not selected');
|
toast.error('Model not selected');
|
||||||
|
@ -570,9 +571,10 @@
|
||||||
history.currentId = userMessageId;
|
history.currentId = userMessageId;
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
if (messages.length == 1) {
|
if (messages.length == 1) {
|
||||||
await $db.createNewChat({
|
chat = await $db.createNewChat({
|
||||||
id: _chatId,
|
id: $chatId,
|
||||||
title: 'New Chat',
|
title: 'New Chat',
|
||||||
models: selectedModels,
|
models: selectedModels,
|
||||||
system: $settings.system ?? undefined,
|
system: $settings.system ?? undefined,
|
||||||
|
@ -588,6 +590,11 @@
|
||||||
messages: messages,
|
messages: messages,
|
||||||
history: history
|
history: history
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(chat);
|
||||||
|
|
||||||
|
await chatId.set(chat.id);
|
||||||
|
await tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt = '';
|
prompt = '';
|
||||||
|
@ -597,7 +604,7 @@
|
||||||
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
await sendPrompt(userPrompt, userMessageId, _chatId);
|
await sendPrompt(userPrompt, userMessageId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -629,7 +636,6 @@
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/event-stream',
|
'Content-Type': 'text/event-stream',
|
||||||
...($settings.authHeader && { Authorization: $settings.authHeader }),
|
|
||||||
...($user && { Authorization: `Bearer ${localStorage.token}` })
|
...($user && { Authorization: `Bearer ${localStorage.token}` })
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
@ -659,7 +665,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const setChatTitle = async (_chatId, _title) => {
|
const setChatTitle = async (_chatId, _title) => {
|
||||||
await $db.updateChatById(_chatId, { title: _title });
|
chat = await $db.updateChatById(_chatId, { ...chat.chat, title: _title });
|
||||||
if (_chatId === $chatId) {
|
if (_chatId === $chatId) {
|
||||||
title = _title;
|
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="min-h-screen w-full flex justify-center">
|
||||||
<div class=" py-2.5 flex flex-col justify-between w-full">
|
<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">
|
<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">
|
<div class=" h-full mt-10 mb-32 w-full flex flex-col">
|
||||||
<Messages
|
<Messages
|
||||||
|
chatId={$chatId}
|
||||||
{selectedModels}
|
{selectedModels}
|
||||||
{selectedModelfile}
|
{selectedModelfile}
|
||||||
bind:history
|
bind:history
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
? $modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0])[0]
|
? $modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0])[0]
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
let chat = null;
|
||||||
|
|
||||||
let title = '';
|
let title = '';
|
||||||
let prompt = '';
|
let prompt = '';
|
||||||
let files = [];
|
let files = [];
|
||||||
|
@ -53,10 +55,8 @@
|
||||||
|
|
||||||
$: if ($page.params.id) {
|
$: if ($page.params.id) {
|
||||||
(async () => {
|
(async () => {
|
||||||
let chat = await loadChat();
|
if (await loadChat()) {
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
if (chat) {
|
|
||||||
loaded = true;
|
loaded = true;
|
||||||
} else {
|
} else {
|
||||||
await goto('/');
|
await goto('/');
|
||||||
|
@ -70,33 +70,38 @@
|
||||||
|
|
||||||
const loadChat = async () => {
|
const loadChat = async () => {
|
||||||
await chatId.set($page.params.id);
|
await chatId.set($page.params.id);
|
||||||
const chat = await $db.getChatById($chatId);
|
chat = await $db.getChatById($chatId);
|
||||||
|
|
||||||
if (chat) {
|
const chatContent = chat.chat;
|
||||||
console.log(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 =
|
history =
|
||||||
(chat?.history ?? undefined) !== undefined
|
(chatContent?.history ?? undefined) !== undefined
|
||||||
? chat.history
|
? chatContent.history
|
||||||
: convertMessagesToHistory(chat.messages);
|
: convertMessagesToHistory(chatContent.messages);
|
||||||
title = chat.title;
|
title = chatContent.title;
|
||||||
|
|
||||||
let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
||||||
await settings.set({
|
await settings.set({
|
||||||
..._settings,
|
..._settings,
|
||||||
system: chat.system ?? _settings.system,
|
system: chatContent.system ?? _settings.system,
|
||||||
options: chat.options ?? _settings.options
|
options: chatContent.options ?? _settings.options
|
||||||
});
|
});
|
||||||
autoScroll = true;
|
autoScroll = true;
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
if (messages.length > 0) {
|
if (messages.length > 0) {
|
||||||
history.messages[messages.at(-1).id].done = true;
|
history.messages[messages.at(-1).id].done = true;
|
||||||
}
|
}
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
return chat;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -141,14 +146,15 @@
|
||||||
// Ollama functions
|
// Ollama functions
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
|
||||||
const sendPrompt = async (userPrompt, parentId, _chatId) => {
|
const sendPrompt = async (prompt, parentId) => {
|
||||||
|
const _chatId = JSON.parse(JSON.stringify($chatId));
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
selectedModels.map(async (model) => {
|
selectedModels.map(async (model) => {
|
||||||
console.log(model);
|
console.log(model);
|
||||||
if ($models.filter((m) => m.name === model)[0].external) {
|
if ($models.filter((m) => m.name === model)[0].external) {
|
||||||
await sendPromptOpenAI(model, userPrompt, parentId, _chatId);
|
await sendPromptOpenAI(model, prompt, parentId, _chatId);
|
||||||
} else {
|
} else {
|
||||||
await sendPromptOllama(model, userPrompt, parentId, _chatId);
|
await sendPromptOllama(model, prompt, parentId, _chatId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -311,8 +317,11 @@
|
||||||
if (autoScroll) {
|
if (autoScroll) {
|
||||||
window.scrollTo({ top: document.body.scrollHeight });
|
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,
|
title: title === '' ? 'New Chat' : title,
|
||||||
models: selectedModels,
|
models: selectedModels,
|
||||||
system: $settings.system ?? undefined,
|
system: $settings.system ?? undefined,
|
||||||
|
@ -495,8 +504,11 @@
|
||||||
if (autoScroll) {
|
if (autoScroll) {
|
||||||
window.scrollTo({ top: document.body.scrollHeight });
|
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,
|
title: title === '' ? 'New Chat' : title,
|
||||||
models: selectedModels,
|
models: selectedModels,
|
||||||
system: $settings.system ?? undefined,
|
system: $settings.system ?? undefined,
|
||||||
|
@ -556,8 +568,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitPrompt = async (userPrompt) => {
|
const submitPrompt = async (userPrompt) => {
|
||||||
const _chatId = JSON.parse(JSON.stringify($chatId));
|
console.log('submitPrompt', $chatId);
|
||||||
console.log('submitPrompt', _chatId);
|
|
||||||
|
|
||||||
if (selectedModels.includes('')) {
|
if (selectedModels.includes('')) {
|
||||||
toast.error('Model not selected');
|
toast.error('Model not selected');
|
||||||
|
@ -584,9 +595,10 @@
|
||||||
history.currentId = userMessageId;
|
history.currentId = userMessageId;
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
if (messages.length == 1) {
|
if (messages.length == 1) {
|
||||||
await $db.createNewChat({
|
chat = await $db.createNewChat({
|
||||||
id: _chatId,
|
id: $chatId,
|
||||||
title: 'New Chat',
|
title: 'New Chat',
|
||||||
models: selectedModels,
|
models: selectedModels,
|
||||||
system: $settings.system ?? undefined,
|
system: $settings.system ?? undefined,
|
||||||
|
@ -602,6 +614,11 @@
|
||||||
messages: messages,
|
messages: messages,
|
||||||
history: history
|
history: history
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(chat);
|
||||||
|
|
||||||
|
await chatId.set(chat.id);
|
||||||
|
await tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt = '';
|
prompt = '';
|
||||||
|
@ -611,7 +628,7 @@
|
||||||
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
await sendPrompt(userPrompt, userMessageId, _chatId);
|
await sendPrompt(userPrompt, userMessageId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -673,7 +690,10 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const setChatTitle = async (_chatId, _title) => {
|
const setChatTitle = async (_chatId, _title) => {
|
||||||
await $db.updateChatById(_chatId, { title: _title });
|
chat = await $db.updateChatById(_chatId, {
|
||||||
|
...chat.chat,
|
||||||
|
title: _title
|
||||||
|
});
|
||||||
if (_chatId === $chatId) {
|
if (_chatId === $chatId) {
|
||||||
title = _title;
|
title = _title;
|
||||||
}
|
}
|
||||||
|
@ -687,7 +707,13 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if loaded}
|
{#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="min-h-screen w-full flex justify-center">
|
||||||
<div class=" py-2.5 flex flex-col justify-between w-full">
|
<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">
|
<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">
|
<div class=" h-full mt-10 mb-32 w-full flex flex-col">
|
||||||
<Messages
|
<Messages
|
||||||
|
chatId={$chatId}
|
||||||
{selectedModels}
|
{selectedModels}
|
||||||
{selectedModelfile}
|
{selectedModelfile}
|
||||||
bind:history
|
bind:history
|
||||||
|
|
|
@ -132,7 +132,6 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/event-stream',
|
'Content-Type': 'text/event-stream',
|
||||||
...($settings.authHeader && { Authorization: $settings.authHeader }),
|
|
||||||
...($user && { Authorization: `Bearer ${localStorage.token}` })
|
...($user && { Authorization: `Bearer ${localStorage.token}` })
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUsers = async () => {
|
const getUsers = async () => {
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/users/`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/users`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($config === null || !$config.auth || ($config.auth && $user && $user.role !== 'admin')) {
|
if ($user?.role !== 'admin') {
|
||||||
await goto('/');
|
await goto('/');
|
||||||
} else {
|
} else {
|
||||||
await getUsers();
|
await getUsers();
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class=" my-auto pb-10 w-full">
|
||||||
<form
|
<form
|
||||||
class=" flex flex-col justify-center"
|
class=" flex flex-col justify-center"
|
||||||
|
|
|
@ -16,23 +16,24 @@
|
||||||
|
|
||||||
{#if loaded}
|
{#if loaded}
|
||||||
<div class="absolute w-full h-full flex z-50">
|
<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="m-auto pb-44 flex flex-col justify-center">
|
||||||
<div class="max-w-md">
|
<div class="max-w-md">
|
||||||
<div class="text-center text-2xl font-medium z-50">Ollama WebUI Backend Required</div>
|
<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">
|
<div class=" mt-4 text-center text-sm w-full">
|
||||||
Oops! It seems like your Ollama WebUI needs a little attention. <br
|
Oops! You're using an unsupported method (frontend only).<br class=" hidden sm:flex" />
|
||||||
class=" hidden sm:flex"
|
Please access the WebUI from the backend. See readme.md for instructions or join our Discord
|
||||||
/>
|
for help.
|
||||||
describe troubleshooting/installation, help @ discord
|
<!-- TODO: update links -->
|
||||||
|
|
||||||
<!-- TODO: update text -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" mt-6 mx-auto relative group w-fit">
|
<div class=" mt-6 mx-auto relative group w-fit">
|
||||||
<button
|
<button
|
||||||
class="relative z-20 flex px-5 py-2 rounded-full bg-gray-100 hover:bg-gray-200 transition font-medium text-sm"
|
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
|
Check Again
|
||||||
</button>
|
</button>
|
||||||
|
|
Loading…
Reference in a new issue