chore: refac

This commit is contained in:
Timothy J. Baek 2023-12-26 12:50:52 -08:00
parent f30d16a3fd
commit cc49e0d10f
11 changed files with 239 additions and 397 deletions

View file

@ -31,7 +31,7 @@ export const createNewChat = async (token: string, chat: object) => {
return res; return res;
}; };
export const getChatlist = async (token: string = '') => { export const getChatList = async (token: string = '') => {
let error = null; let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/`, { const res = await fetch(`${WEBUI_API_BASE_URL}/chats/`, {

View file

@ -69,3 +69,68 @@ export const getOllamaModels = async (
return res?.models ?? []; return res?.models ?? [];
}; };
export const generateTitle = async (
base_url: string = OLLAMA_API_BASE_URL,
token: string = '',
model: string,
prompt: string
) => {
let error = null;
const res = await fetch(`${base_url}/generate`, {
method: 'POST',
headers: {
'Content-Type': 'text/event-stream',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
model: model,
prompt: `Generate a brief 3-5 word title for this question, excluding the term 'title.' Then, please reply with only the title: ${prompt}`,
stream: false
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
if ('detail' in err) {
error = err.detail;
}
return null;
});
if (error) {
throw error;
}
return res?.response ?? 'New Chat';
};
export const generateChatCompletion = async (
base_url: string = OLLAMA_API_BASE_URL,
token: string = '',
body: object
) => {
let error = null;
const res = await fetch(`${base_url}/chat`, {
method: 'POST',
headers: {
'Content-Type': 'text/event-stream',
Authorization: `Bearer ${token}`
},
body: JSON.stringify(body)
}).catch((err) => {
error = err;
return null;
});
if (error) {
throw error;
}
return res;
};

View file

@ -27,8 +27,6 @@ export const getOpenAIModels = async (
let models = Array.isArray(res) ? res : res?.data ?? null; let models = Array.isArray(res) ? res : res?.data ?? null;
console.log(models);
return models return models
.map((model) => ({ name: model.id, external: true })) .map((model) => ({ name: model.id, external: true }))
.filter((model) => (base_url.includes('openai') ? model.name.includes('gpt') : true)); .filter((model) => (base_url.includes('openai') ? model.name.includes('gpt') : true));

View file

@ -8,10 +8,11 @@
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 { config, db, modelfiles, settings, user } from '$lib/stores'; import { chats, 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';
import { getChatList, updateChatById } from '$lib/apis/chats';
export let chatId = ''; export let chatId = '';
export let sendPrompt: Function; export let sendPrompt: Function;
@ -262,10 +263,12 @@
return message; return message;
}); });
$db.updateChatById(chatId, { await updateChatById(localStorage.token, chatId, {
messages: messages, messages: messages,
history: history history: history
}); });
await chats.set(await getChatList(localStorage.token));
}; };
const showPreviousMessage = async (message) => { const showPreviousMessage = async (message) => {

View file

@ -1,7 +1,5 @@
<script lang="ts"> <script lang="ts">
import { v4 as uuidv4 } from 'uuid'; import { getChatById } from '$lib/apis/chats';
import { goto } from '$app/navigation';
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';
@ -10,10 +8,10 @@
export let shareEnabled: boolean = false; export let shareEnabled: boolean = false;
const shareChat = async () => { const shareChat = async () => {
const chat = (await $db.getChatById($chatId)).chat; const chat = (await getChatById(localStorage.token, $chatId)).chat;
console.log('share', chat); console.log('share', chat);
toast.success('Redirecting you to OllamaHub');
toast.success('Redirecting you to OllamaHub');
const url = 'https://ollamahub.com'; const url = 'https://ollamahub.com';
// const url = 'http://localhost:5173'; // const url = 'http://localhost:5173';

View file

@ -8,6 +8,7 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { user, db, chats, showSettings, chatId } from '$lib/stores'; import { user, db, chats, showSettings, chatId } from '$lib/stores';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { deleteChatById, getChatList, updateChatById } from '$lib/apis/chats';
let show = false; let show = false;
let navElement; let navElement;
@ -31,7 +32,7 @@
show = true; show = true;
} }
await chats.set(await $db.getChats()); await chats.set(await getChatList(localStorage.token));
}); });
const loadChat = async (id) => { const loadChat = async (id) => {
@ -39,42 +40,46 @@
}; };
const editChatTitle = async (id, _title) => { const editChatTitle = async (id, _title) => {
await $db.updateChatById(id, { title = _title;
await updateChatById(localStorage.token, id, {
title: _title title: _title
}); });
title = _title; await chats.set(await getChatList(localStorage.token));
}; };
const deleteChat = async (id) => { const deleteChat = async (id) => {
goto('/'); goto('/');
$db.deleteChatById(id);
await deleteChatById(localStorage.token, id);
await chats.set(await getChatList(localStorage.token));
}; };
const deleteChatHistory = async () => { // const deleteChatHistory = async () => {
await $db.deleteAllChat(); // await $db.deleteAllChat();
}; // };
const importChats = async (chatHistory) => { // const importChats = async (chatHistory) => {
await $db.importChats(chatHistory); // await $db.importChats(chatHistory);
}; // };
const exportChats = async () => { // const exportChats = async () => {
let blob = new Blob([JSON.stringify(await $db.exportChats())], { type: 'application/json' }); // let blob = new Blob([JSON.stringify(await $db.exportChats())], { type: 'application/json' });
saveAs(blob, `chat-export-${Date.now()}.json`); // saveAs(blob, `chat-export-${Date.now()}.json`);
}; // };
$: if (importFiles) { // $: if (importFiles) {
console.log(importFiles); // console.log(importFiles);
let reader = new FileReader(); // let reader = new FileReader();
reader.onload = (event) => { // reader.onload = (event) => {
let chats = JSON.parse(event.target.result); // let chats = JSON.parse(event.target.result);
console.log(chats); // console.log(chats);
importChats(chats); // importChats(chats);
}; // };
reader.readAsText(importFiles[0]); // reader.readAsText(importFiles[0]);
} // }
</script> </script>
<div <div

View file

@ -13,6 +13,8 @@ export const WEBUI_API_BASE_URL = `${WEBUI_BASE_URL}/api/v1`;
export const WEB_UI_VERSION = 'v1.0.0-alpha-static'; export const WEB_UI_VERSION = 'v1.0.0-alpha-static';
export const REQUIRED_OLLAMA_VERSION = '0.1.16';
// Source: https://kit.svelte.dev/docs/modules#$env-static-public // Source: https://kit.svelte.dev/docs/modules#$env-static-public
// This feature, akin to $env/static/private, exclusively incorporates environment variables // This feature, akin to $env/static/private, exclusively incorporates environment variables
// that are prefixed with config.kit.env.publicPrefix (usually set to PUBLIC_). // that are prefixed with config.kit.env.publicPrefix (usually set to PUBLIC_).

View file

@ -66,9 +66,9 @@ export const getGravatarURL = (email) => {
return `https://www.gravatar.com/avatar/${hash}`; return `https://www.gravatar.com/avatar/${hash}`;
}; };
const copyToClipboard = (text) => { export const copyToClipboard = (text) => {
if (!navigator.clipboard) { if (!navigator.clipboard) {
var textArea = document.createElement('textarea'); const textArea = document.createElement('textarea');
textArea.value = text; textArea.value = text;
// Avoid scrolling to bottom // Avoid scrolling to bottom
@ -81,8 +81,8 @@ const copyToClipboard = (text) => {
textArea.select(); textArea.select();
try { try {
var successful = document.execCommand('copy'); const successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful'; const msg = successful ? 'successful' : 'unsuccessful';
console.log('Fallback: Copying text command was ' + msg); console.log('Fallback: Copying text command was ' + msg);
} catch (err) { } catch (err) {
console.error('Fallback: Oops, unable to copy', err); console.error('Fallback: Oops, unable to copy', err);

View file

@ -1,37 +1,18 @@
<script lang="ts"> <script lang="ts">
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { openDB, deleteDB } from 'idb';
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import toast from 'svelte-french-toast';
import { import { info, user, showSettings, settings, models, modelfiles } from '$lib/stores';
config,
info, import { OLLAMA_API_BASE_URL, REQUIRED_OLLAMA_VERSION, WEBUI_API_BASE_URL } from '$lib/constants';
user, import { getOllamaModels, getOllamaVersion } from '$lib/apis/ollama';
showSettings, import { getOpenAIModels } from '$lib/apis/openai';
settings,
models,
db,
chats,
chatId,
modelfiles
} from '$lib/stores';
import SettingsModal from '$lib/components/chat/SettingsModal.svelte'; import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
import Sidebar from '$lib/components/layout/Sidebar.svelte'; import Sidebar from '$lib/components/layout/Sidebar.svelte';
import toast from 'svelte-french-toast';
import { OLLAMA_API_BASE_URL, WEBUI_API_BASE_URL } from '$lib/constants';
import { getOllamaModels, getOllamaVersion } from '$lib/apis/ollama';
import { getOpenAIModels } from '$lib/apis/openai';
import {
createNewChat,
deleteChatById,
getChatById,
getChatlist,
updateChatById
} from '$lib/apis/chats';
let requiredOllamaVersion = '0.1.16';
let loaded = false; let loaded = false;
const getModels = async () => { const getModels = async () => {
@ -55,92 +36,19 @@
return models; return models;
}; };
const getDB = async () => { const setOllamaVersion = async (version: string = '') => {
const DB = await openDB('Chats', 1, { if (version === '') {
upgrade(db) { version = await getOllamaVersion(
const store = db.createObjectStore('chats', { $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
keyPath: 'id', localStorage.token
autoIncrement: true ).catch((error) => {
}); return '0';
store.createIndex('timestamp', 'timestamp'); });
} }
});
return {
db: DB,
getChatById: async function (id) {
const chat = await getChatById(localStorage.token, id);
return chat;
},
getChats: async function () {
const chats = await getChatlist(localStorage.token);
return chats;
},
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
});
},
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 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 setOllamaVersion = async () => {
const version = await getOllamaVersion(
$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
localStorage.token
).catch((error) => {
toast.error(error);
return '0';
});
await info.set({ ...$info, ollama: { version: version } }); await info.set({ ...$info, ollama: { version: version } });
if ( if (
version.localeCompare(requiredOllamaVersion, undefined, { version.localeCompare(REQUIRED_OLLAMA_VERSION, undefined, {
numeric: true, numeric: true,
sensitivity: 'case', sensitivity: 'case',
caseFirst: 'upper' caseFirst: 'upper'
@ -151,19 +59,18 @@
}; };
onMount(async () => { onMount(async () => {
if ($config && $user === undefined) { if ($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 () => {
// should fetch models
});
let _db = await getDB();
await db.set(_db);
await setOllamaVersion(); await setOllamaVersion();
await tick(); await tick();
@ -214,7 +121,7 @@
</div> </div>
</div> </div>
</div> </div>
{:else if ($info?.ollama?.version ?? '0').localeCompare( requiredOllamaVersion, undefined, { numeric: true, sensitivity: 'case', caseFirst: 'upper' } ) < 0} {:else if ($info?.ollama?.version ?? '0').localeCompare( REQUIRED_OLLAMA_VERSION, 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"
@ -231,15 +138,15 @@
/>We've detected either a connection hiccup or observed that you're using an older />We've detected either a connection hiccup or observed that you're using an older
version. Ensure you're on the latest Ollama version version. Ensure you're on the latest Ollama version
<br class=" hidden sm:flex" />(version <br class=" hidden sm:flex" />(version
<span class=" dark:text-white font-medium">{requiredOllamaVersion} or higher</span>) <span class=" dark:text-white font-medium">{REQUIRED_OLLAMA_VERSION} or higher</span
or check your connection. >) or check your connection.
</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={async () => { on:click={async () => {
await setOllamaVersion(await getOllamaVersion()); await setOllamaVersion();
}} }}
> >
Check Again Check Again
@ -248,7 +155,7 @@
<button <button
class="text-xs text-center w-full mt-2 text-gray-400 underline" class="text-xs text-center w-full mt-2 text-gray-400 underline"
on:click={async () => { on:click={async () => {
await setOllamaVersion(requiredOllamaVersion); await setOllamaVersion(REQUIRED_OLLAMA_VERSION);
}}>Close</button }}>Close</button
> >
</div> </div>

View file

@ -2,23 +2,27 @@
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 { onDestroy, onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { config, models, modelfiles, user, settings, db, chats, chatId } from '$lib/stores'; import { models, modelfiles, user, settings, db, chats, chatId } from '$lib/stores';
import { OLLAMA_API_BASE_URL } from '$lib/constants'; import { OLLAMA_API_BASE_URL } from '$lib/constants';
import { splitStream } from '$lib/utils';
import { generateChatCompletion, generateTitle } from '$lib/apis/ollama';
import { copyToClipboard, 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 { createNewChat, getChatList, updateChatById } from '$lib/apis/chats';
let stopResponseFlag = false; let stopResponseFlag = false;
let autoScroll = true; let autoScroll = true;
let selectedModels = ['']; let selectedModels = [''];
let selectedModelfile = null; let selectedModelfile = null;
$: selectedModelfile = $: selectedModelfile =
selectedModels.length === 1 && selectedModels.length === 1 &&
@ -83,41 +87,6 @@
}); });
}; };
const copyToClipboard = (text) => {
if (!navigator.clipboard) {
var textArea = document.createElement('textarea');
textArea.value = text;
// Avoid scrolling to bottom
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.position = 'fixed';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful';
console.log('Fallback: Copying text command was ' + msg);
} catch (err) {
console.error('Fallback: Oops, unable to copy', err);
}
document.body.removeChild(textArea);
return;
}
navigator.clipboard.writeText(text).then(
function () {
console.log('Async: Copying to clipboard was successful!');
},
function (err) {
console.error('Async: Could not copy text: ', err);
}
);
};
////////////////////////// //////////////////////////
// Ollama functions // Ollama functions
////////////////////////// //////////////////////////
@ -135,11 +104,11 @@
}) })
); );
await chats.set(await $db.getChats()); await chats.set(await getChatList(localStorage.token));
}; };
const sendPromptOllama = async (model, userPrompt, parentId, _chatId) => { const sendPromptOllama = async (model, userPrompt, parentId, _chatId) => {
console.log('sendPromptOllama'); // Create response message
let responseMessageId = uuidv4(); let responseMessageId = uuidv4();
let responseMessage = { let responseMessage = {
parentId: parentId, parentId: parentId,
@ -150,8 +119,11 @@
model: model model: model
}; };
// Add message to history and Set currentId to messageId
history.messages[responseMessageId] = responseMessage; history.messages[responseMessageId] = responseMessage;
history.currentId = responseMessageId; history.currentId = responseMessageId;
// Append messageId to childrenIds of parent message
if (parentId !== null) { if (parentId !== null) {
history.messages[parentId].childrenIds = [ history.messages[parentId].childrenIds = [
...history.messages[parentId].childrenIds, ...history.messages[parentId].childrenIds,
@ -159,17 +131,16 @@
]; ];
} }
// Wait until history/message have been updated
await tick(); await tick();
// Scroll down
window.scrollTo({ top: document.body.scrollHeight }); window.scrollTo({ top: document.body.scrollHeight });
const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/chat`, { const res = await generateChatCompletion(
method: 'POST', $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
headers: { localStorage.token,
'Content-Type': 'text/event-stream', {
...($settings.authHeader && { Authorization: $settings.authHeader }),
...($user && { Authorization: `Bearer ${localStorage.token}` })
},
body: JSON.stringify({
model: model, model: model,
messages: [ messages: [
$settings.system $settings.system
@ -191,20 +162,11 @@
}) })
})), })),
options: { options: {
seed: $settings.seed ?? undefined,
temperature: $settings.temperature ?? undefined,
repeat_penalty: $settings.repeat_penalty ?? undefined,
top_k: $settings.top_k ?? undefined,
top_p: $settings.top_p ?? undefined,
num_ctx: $settings.num_ctx ?? undefined,
...($settings.options ?? {}) ...($settings.options ?? {})
}, },
format: $settings.requestFormat ?? undefined format: $settings.requestFormat ?? undefined
}) }
}).catch((err) => { );
console.log(err);
return null;
});
if (res && res.ok) { if (res && res.ok) {
const reader = res.body const reader = res.body
@ -296,23 +258,11 @@
} }
if ($chatId == _chatId) { if ($chatId == _chatId) {
chat = await $db.updateChatById(_chatId, { chat = await updateChatById(localStorage.token, _chatId, {
...chat.chat,
title: title === '' ? 'New Chat' : title,
models: selectedModels,
system: $settings.system ?? undefined,
options: {
seed: $settings.seed ?? undefined,
temperature: $settings.temperature ?? undefined,
repeat_penalty: $settings.repeat_penalty ?? undefined,
top_k: $settings.top_k ?? undefined,
top_p: $settings.top_p ?? undefined,
num_ctx: $settings.num_ctx ?? undefined,
...($settings.options ?? {})
},
messages: messages, messages: messages,
history: history history: history
}); });
await chats.set(await getChatList(localStorage.token));
} }
} else { } else {
if (res !== null) { if (res !== null) {
@ -338,6 +288,7 @@
stopResponseFlag = false; stopResponseFlag = false;
await tick(); await tick();
if (autoScroll) { if (autoScroll) {
window.scrollTo({ top: document.body.scrollHeight }); window.scrollTo({ top: document.body.scrollHeight });
} }
@ -483,23 +434,11 @@
} }
if ($chatId == _chatId) { if ($chatId == _chatId) {
chat = await $db.updateChatById(_chatId, { chat = await updateChatById(localStorage.token, _chatId, {
...chat.chat,
title: title === '' ? 'New Chat' : title,
models: selectedModels,
system: $settings.system ?? undefined,
options: {
seed: $settings.seed ?? undefined,
temperature: $settings.temperature ?? undefined,
repeat_penalty: $settings.repeat_penalty ?? undefined,
top_k: $settings.top_k ?? undefined,
top_p: $settings.top_p ?? undefined,
num_ctx: $settings.num_ctx ?? undefined,
...($settings.options ?? {})
},
messages: messages, messages: messages,
history: history history: history
}); });
await chats.set(await getChatList(localStorage.token));
} }
} else { } else {
if (res !== null) { if (res !== null) {
@ -549,10 +488,13 @@
if (selectedModels.includes('')) { if (selectedModels.includes('')) {
toast.error('Model not selected'); toast.error('Model not selected');
} else if (messages.length != 0 && messages.at(-1).done != true) { } else if (messages.length != 0 && messages.at(-1).done != true) {
// Response not done
console.log('wait'); console.log('wait');
} else { } else {
// Reset chat message textarea height
document.getElementById('chat-textarea').style.height = ''; document.getElementById('chat-textarea').style.height = '';
// Create user message
let userMessageId = uuidv4(); let userMessageId = uuidv4();
let userMessage = { let userMessage = {
id: userMessageId, id: userMessageId,
@ -563,47 +505,42 @@
files: files.length > 0 ? files : undefined files: files.length > 0 ? files : undefined
}; };
// Add message to history and Set currentId to messageId
history.messages[userMessageId] = userMessage;
history.currentId = userMessageId;
// Append messageId to childrenIds of parent message
if (messages.length !== 0) { if (messages.length !== 0) {
history.messages[messages.at(-1).id].childrenIds.push(userMessageId); history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
} }
history.messages[userMessageId] = userMessage; // Wait until history/message have been updated
history.currentId = userMessageId;
await tick(); await tick();
// Create new chat if only one message in messages
if (messages.length == 1) { if (messages.length == 1) {
chat = await $db.createNewChat({ chat = await createNewChat(localStorage.token, {
id: $chatId, id: $chatId,
title: 'New Chat', title: 'New Chat',
models: selectedModels, models: selectedModels,
system: $settings.system ?? undefined, system: $settings.system ?? undefined,
options: { options: {
seed: $settings.seed ?? undefined,
temperature: $settings.temperature ?? undefined,
repeat_penalty: $settings.repeat_penalty ?? undefined,
top_k: $settings.top_k ?? undefined,
top_p: $settings.top_p ?? undefined,
num_ctx: $settings.num_ctx ?? undefined,
...($settings.options ?? {}) ...($settings.options ?? {})
}, },
messages: messages, messages: messages,
history: history history: history,
timestamp: Date.now()
}); });
await chats.set(await getChatList(localStorage.token));
console.log(chat);
await chatId.set(chat.id); await chatId.set(chat.id);
await tick(); await tick();
} }
// Reset chat input textarea
prompt = ''; prompt = '';
files = []; files = [];
setTimeout(() => { // Send prompt
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
}, 50);
await sendPrompt(userPrompt, userMessageId); await sendPrompt(userPrompt, userMessageId);
} }
}; };
@ -614,9 +551,7 @@
}; };
const regenerateResponse = async () => { const regenerateResponse = async () => {
const _chatId = JSON.parse(JSON.stringify($chatId)); console.log('regenerateResponse');
console.log('regenerateResponse', _chatId);
if (messages.length != 0 && messages.at(-1).done == true) { if (messages.length != 0 && messages.at(-1).done == true) {
messages.splice(messages.length - 1, 1); messages.splice(messages.length - 1, 1);
messages = messages; messages = messages;
@ -624,40 +559,21 @@
let userMessage = messages.at(-1); let userMessage = messages.at(-1);
let userPrompt = userMessage.content; let userPrompt = userMessage.content;
await sendPrompt(userPrompt, userMessage.id, _chatId); await sendPrompt(userPrompt, userMessage.id);
} }
}; };
const generateChatTitle = async (_chatId, userPrompt) => { const generateChatTitle = async (_chatId, userPrompt) => {
if ($settings.titleAutoGenerate ?? true) { if ($settings.titleAutoGenerate ?? true) {
console.log('generateChatTitle'); const title = await generateTitle(
$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
localStorage.token,
selectedModels[0],
userPrompt
);
const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/generate`, { if (title) {
method: 'POST', await setChatTitle(_chatId, title);
headers: {
'Content-Type': 'text/event-stream',
...($user && { Authorization: `Bearer ${localStorage.token}` })
},
body: JSON.stringify({
model: selectedModels[0],
prompt: `Generate a brief 3-5 word title for this question, excluding the term 'title.' Then, please reply with only the title: ${userPrompt}`,
stream: false
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((error) => {
if ('detail' in error) {
toast.error(error.detail);
}
console.log(error);
return null;
});
if (res) {
await setChatTitle(_chatId, res.response === '' ? 'New Chat' : res.response);
} }
} else { } else {
await setChatTitle(_chatId, `${userPrompt}`); await setChatTitle(_chatId, `${userPrompt}`);
@ -665,10 +581,12 @@
}; };
const setChatTitle = async (_chatId, _title) => { const setChatTitle = async (_chatId, _title) => {
chat = await $db.updateChatById(_chatId, { ...chat.chat, title: _title });
if (_chatId === $chatId) { if (_chatId === $chatId) {
title = _title; title = _title;
} }
chat = await updateChatById(localStorage.token, _chatId, { title: _title });
await chats.set(await getChatList(localStorage.token));
}; };
</script> </script>

View file

@ -13,6 +13,7 @@
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'; import { page } from '$app/stores';
import { createNewChat, getChatById, getChatList } from '$lib/apis/chats';
let loaded = false; let loaded = false;
let stopResponseFlag = false; let stopResponseFlag = false;
@ -70,7 +71,7 @@
const loadChat = async () => { const loadChat = async () => {
await chatId.set($page.params.id); await chatId.set($page.params.id);
chat = await $db.getChatById($chatId); chat = await getChatById(localStorage.token, $chatId);
const chatContent = chat.chat; const chatContent = chat.chat;
@ -159,11 +160,11 @@
}) })
); );
await chats.set(await $db.getChats()); await chats.set(await getChatList(localStorage.token));
}; };
const sendPromptOllama = async (model, userPrompt, parentId, _chatId) => { const sendPromptOllama = async (model, userPrompt, parentId, _chatId) => {
console.log('sendPromptOllama'); // Create response message
let responseMessageId = uuidv4(); let responseMessageId = uuidv4();
let responseMessage = { let responseMessage = {
parentId: parentId, parentId: parentId,
@ -174,8 +175,11 @@
model: model model: model
}; };
// Add message to history and Set currentId to messageId
history.messages[responseMessageId] = responseMessage; history.messages[responseMessageId] = responseMessage;
history.currentId = responseMessageId; history.currentId = responseMessageId;
// Append messageId to childrenIds of parent message
if (parentId !== null) { if (parentId !== null) {
history.messages[parentId].childrenIds = [ history.messages[parentId].childrenIds = [
...history.messages[parentId].childrenIds, ...history.messages[parentId].childrenIds,
@ -183,17 +187,16 @@
]; ];
} }
// Wait until history/message have been updated
await tick(); await tick();
// Scroll down
window.scrollTo({ top: document.body.scrollHeight }); window.scrollTo({ top: document.body.scrollHeight });
const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/chat`, { const res = await generateChatCompletion(
method: 'POST', $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
headers: { localStorage.token,
'Content-Type': 'text/event-stream', {
...($settings.authHeader && { Authorization: $settings.authHeader }),
...($user && { Authorization: `Bearer ${localStorage.token}` })
},
body: JSON.stringify({
model: model, model: model,
messages: [ messages: [
$settings.system $settings.system
@ -215,20 +218,11 @@
}) })
})), })),
options: { options: {
seed: $settings.seed ?? undefined,
temperature: $settings.temperature ?? undefined,
repeat_penalty: $settings.repeat_penalty ?? undefined,
top_k: $settings.top_k ?? undefined,
top_p: $settings.top_p ?? undefined,
num_ctx: $settings.num_ctx ?? undefined,
...($settings.options ?? {}) ...($settings.options ?? {})
}, },
format: $settings.requestFormat ?? undefined format: $settings.requestFormat ?? undefined
}) }
}).catch((err) => { );
console.log(err);
return null;
});
if (res && res.ok) { if (res && res.ok) {
const reader = res.body const reader = res.body
@ -320,23 +314,11 @@
} }
if ($chatId == _chatId) { if ($chatId == _chatId) {
chat = await $db.updateChatById(_chatId, { chat = await updateChatById(localStorage.token, _chatId, {
...chat.chat,
title: title === '' ? 'New Chat' : title,
models: selectedModels,
system: $settings.system ?? undefined,
options: {
seed: $settings.seed ?? undefined,
temperature: $settings.temperature ?? undefined,
repeat_penalty: $settings.repeat_penalty ?? undefined,
top_k: $settings.top_k ?? undefined,
top_p: $settings.top_p ?? undefined,
num_ctx: $settings.num_ctx ?? undefined,
...($settings.options ?? {})
},
messages: messages, messages: messages,
history: history history: history
}); });
await chats.set(await getChatList(localStorage.token));
} }
} else { } else {
if (res !== null) { if (res !== null) {
@ -362,6 +344,7 @@
stopResponseFlag = false; stopResponseFlag = false;
await tick(); await tick();
if (autoScroll) { if (autoScroll) {
window.scrollTo({ top: document.body.scrollHeight }); window.scrollTo({ top: document.body.scrollHeight });
} }
@ -507,23 +490,11 @@
} }
if ($chatId == _chatId) { if ($chatId == _chatId) {
chat = await $db.updateChatById(_chatId, { chat = await updateChatById(localStorage.token, _chatId, {
...chat.chat,
title: title === '' ? 'New Chat' : title,
models: selectedModels,
system: $settings.system ?? undefined,
options: {
seed: $settings.seed ?? undefined,
temperature: $settings.temperature ?? undefined,
repeat_penalty: $settings.repeat_penalty ?? undefined,
top_k: $settings.top_k ?? undefined,
top_p: $settings.top_p ?? undefined,
num_ctx: $settings.num_ctx ?? undefined,
...($settings.options ?? {})
},
messages: messages, messages: messages,
history: history history: history
}); });
await chats.set(await getChatList(localStorage.token));
} }
} else { } else {
if (res !== null) { if (res !== null) {
@ -573,10 +544,13 @@
if (selectedModels.includes('')) { if (selectedModels.includes('')) {
toast.error('Model not selected'); toast.error('Model not selected');
} else if (messages.length != 0 && messages.at(-1).done != true) { } else if (messages.length != 0 && messages.at(-1).done != true) {
// Response not done
console.log('wait'); console.log('wait');
} else { } else {
// Reset chat message textarea height
document.getElementById('chat-textarea').style.height = ''; document.getElementById('chat-textarea').style.height = '';
// Create user message
let userMessageId = uuidv4(); let userMessageId = uuidv4();
let userMessage = { let userMessage = {
id: userMessageId, id: userMessageId,
@ -587,47 +561,42 @@
files: files.length > 0 ? files : undefined files: files.length > 0 ? files : undefined
}; };
// Add message to history and Set currentId to messageId
history.messages[userMessageId] = userMessage;
history.currentId = userMessageId;
// Append messageId to childrenIds of parent message
if (messages.length !== 0) { if (messages.length !== 0) {
history.messages[messages.at(-1).id].childrenIds.push(userMessageId); history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
} }
history.messages[userMessageId] = userMessage; // Wait until history/message have been updated
history.currentId = userMessageId;
await tick(); await tick();
// Create new chat if only one message in messages
if (messages.length == 1) { if (messages.length == 1) {
chat = await $db.createNewChat({ chat = await createNewChat(localStorage.token, {
id: $chatId, id: $chatId,
title: 'New Chat', title: 'New Chat',
models: selectedModels, models: selectedModels,
system: $settings.system ?? undefined, system: $settings.system ?? undefined,
options: { options: {
seed: $settings.seed ?? undefined,
temperature: $settings.temperature ?? undefined,
repeat_penalty: $settings.repeat_penalty ?? undefined,
top_k: $settings.top_k ?? undefined,
top_p: $settings.top_p ?? undefined,
num_ctx: $settings.num_ctx ?? undefined,
...($settings.options ?? {}) ...($settings.options ?? {})
}, },
messages: messages, messages: messages,
history: history history: history,
timestamp: Date.now()
}); });
await chats.set(await getChatList(localStorage.token));
console.log(chat);
await chatId.set(chat.id); await chatId.set(chat.id);
await tick(); await tick();
} }
// Reset chat input textarea
prompt = ''; prompt = '';
files = []; files = [];
setTimeout(() => { // Send prompt
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
}, 50);
await sendPrompt(userPrompt, userMessageId); await sendPrompt(userPrompt, userMessageId);
} }
}; };
@ -638,9 +607,7 @@
}; };
const regenerateResponse = async () => { const regenerateResponse = async () => {
const _chatId = JSON.parse(JSON.stringify($chatId)); console.log('regenerateResponse');
console.log('regenerateResponse', _chatId);
if (messages.length != 0 && messages.at(-1).done == true) { if (messages.length != 0 && messages.at(-1).done == true) {
messages.splice(messages.length - 1, 1); messages.splice(messages.length - 1, 1);
messages = messages; messages = messages;
@ -648,41 +615,21 @@
let userMessage = messages.at(-1); let userMessage = messages.at(-1);
let userPrompt = userMessage.content; let userPrompt = userMessage.content;
await sendPrompt(userPrompt, userMessage.id, _chatId); await sendPrompt(userPrompt, userMessage.id);
} }
}; };
const generateChatTitle = async (_chatId, userPrompt) => { const generateChatTitle = async (_chatId, userPrompt) => {
if ($settings.titleAutoGenerate ?? true) { if ($settings.titleAutoGenerate ?? true) {
console.log('generateChatTitle'); const title = await generateTitle(
$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
localStorage.token,
selectedModels[0],
userPrompt
);
const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/generate`, { if (title) {
method: 'POST', await setChatTitle(_chatId, title);
headers: {
'Content-Type': 'text/event-stream',
...($settings.authHeader && { Authorization: $settings.authHeader }),
...($user && { Authorization: `Bearer ${localStorage.token}` })
},
body: JSON.stringify({
model: selectedModels[0],
prompt: `Generate a brief 3-5 word title for this question, excluding the term 'title.' Then, please reply with only the title: ${userPrompt}`,
stream: false
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((error) => {
if ('detail' in error) {
toast.error(error.detail);
}
console.log(error);
return null;
});
if (res) {
await setChatTitle(_chatId, res.response === '' ? 'New Chat' : res.response);
} }
} else { } else {
await setChatTitle(_chatId, `${userPrompt}`); await setChatTitle(_chatId, `${userPrompt}`);
@ -690,13 +637,12 @@
}; };
const setChatTitle = async (_chatId, _title) => { const setChatTitle = async (_chatId, _title) => {
chat = await $db.updateChatById(_chatId, {
...chat.chat,
title: _title
});
if (_chatId === $chatId) { if (_chatId === $chatId) {
title = _title; title = _title;
} }
chat = await updateChatById(localStorage.token, _chatId, { title: _title });
await chats.set(await getChatList(localStorage.token));
}; };
</script> </script>