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