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
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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue