瀏覽代碼

chore: refac

Timothy J. Baek 1 年之前
父節點
當前提交
cc49e0d10f

+ 1 - 1
src/lib/apis/chats/index.ts

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

+ 65 - 0
src/lib/apis/ollama/index.ts

@@ -69,3 +69,68 @@ export const getOllamaModels = async (
 
 	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;
+};

+ 0 - 2
src/lib/apis/openai/index.ts

@@ -27,8 +27,6 @@ export const getOpenAIModels = async (
 
 	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));

+ 5 - 2
src/lib/components/chat/Messages.svelte

@@ -8,10 +8,11 @@
 	import auto_render from 'katex/dist/contrib/auto-render.mjs';
 	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 toast from 'svelte-french-toast';
+	import { getChatList, updateChatById } from '$lib/apis/chats';
 
 	export let chatId = '';
 	export let sendPrompt: Function;
@@ -262,10 +263,12 @@
 			return message;
 		});
 
-		$db.updateChatById(chatId, {
+		await updateChatById(localStorage.token, chatId, {
 			messages: messages,
 			history: history
 		});
+
+		await chats.set(await getChatList(localStorage.token));
 	};
 
 	const showPreviousMessage = async (message) => {

+ 3 - 5
src/lib/components/layout/Navbar.svelte

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

+ 29 - 24
src/lib/components/layout/Sidebar.svelte

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

+ 2 - 0
src/lib/constants.ts

@@ -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 REQUIRED_OLLAMA_VERSION = '0.1.16';
+
 // Source: https://kit.svelte.dev/docs/modules#$env-static-public
 // This feature, akin to $env/static/private, exclusively incorporates environment variables
 // that are prefixed with config.kit.env.publicPrefix (usually set to PUBLIC_).

+ 4 - 4
src/lib/utils/index.ts

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

+ 25 - 118
src/routes/(app)/+layout.svelte

@@ -1,37 +1,18 @@
 <script lang="ts">
 	import { v4 as uuidv4 } from 'uuid';
-	import { openDB, deleteDB } from 'idb';
 	import { onMount, tick } from 'svelte';
 	import { goto } from '$app/navigation';
+	import toast from 'svelte-french-toast';
 
-	import {
-		config,
-		info,
-		user,
-		showSettings,
-		settings,
-		models,
-		db,
-		chats,
-		chatId,
-		modelfiles
-	} from '$lib/stores';
+	import { info, user, showSettings, settings, models, modelfiles } from '$lib/stores';
 
-	import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
-	import Sidebar from '$lib/components/layout/Sidebar.svelte';
-	import toast from 'svelte-french-toast';
-	import { OLLAMA_API_BASE_URL, WEBUI_API_BASE_URL } from '$lib/constants';
+	import { OLLAMA_API_BASE_URL, REQUIRED_OLLAMA_VERSION, 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';
+	import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
+	import Sidebar from '$lib/components/layout/Sidebar.svelte';
+
 	let loaded = false;
 
 	const getModels = async () => {
@@ -55,92 +36,19 @@
 		return models;
 	};
 
-	const getDB = async () => {
-		const DB = await openDB('Chats', 1, {
-			upgrade(db) {
-				const store = db.createObjectStore('chats', {
-					keyPath: 'id',
-					autoIncrement: true
-				});
-				store.createIndex('timestamp', 'timestamp');
-			}
-		});
-
-		return {
-			db: DB,
-			getChatById: async function (id) {
-				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';
-		});
-
+	const setOllamaVersion = async (version: string = '') => {
+		if (version === '') {
+			version = await getOllamaVersion(
+				$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
+				localStorage.token
+			).catch((error) => {
+				return '0';
+			});
+		}
 		await info.set({ ...$info, ollama: { version: version } });
 
 		if (
-			version.localeCompare(requiredOllamaVersion, undefined, {
+			version.localeCompare(REQUIRED_OLLAMA_VERSION, undefined, {
 				numeric: true,
 				sensitivity: 'case',
 				caseFirst: 'upper'
@@ -151,19 +59,18 @@
 	};
 
 	onMount(async () => {
-		if ($config && $user === undefined) {
+		if ($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 () => {});
+		modelfiles.subscribe(async () => {
+			// should fetch models
+		});
 
-		let _db = await getDB();
-		await db.set(_db);
 		await setOllamaVersion();
 
 		await tick();
@@ -214,7 +121,7 @@
 					</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 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
 								version. Ensure you're on the latest Ollama version
 								<br class=" hidden sm:flex" />(version
-								<span class=" dark:text-white font-medium">{requiredOllamaVersion} or higher</span>)
-								or check your connection.
+								<span class=" dark:text-white font-medium">{REQUIRED_OLLAMA_VERSION} or higher</span
+								>) or check your connection.
 							</div>
 
 							<div class=" mt-6 mx-auto relative group w-fit">
 								<button
 									class="relative z-20 flex px-5 py-2 rounded-full bg-gray-100 hover:bg-gray-200 transition font-medium text-sm"
 									on:click={async () => {
-										await setOllamaVersion(await getOllamaVersion());
+										await setOllamaVersion();
 									}}
 								>
 									Check Again
@@ -248,7 +155,7 @@
 								<button
 									class="text-xs text-center w-full mt-2 text-gray-400 underline"
 									on:click={async () => {
-										await setOllamaVersion(requiredOllamaVersion);
+										await setOllamaVersion(REQUIRED_OLLAMA_VERSION);
 									}}>Close</button
 								>
 							</div>

+ 56 - 138
src/routes/(app)/+page.svelte

@@ -2,23 +2,27 @@
 	import { v4 as uuidv4 } from 'uuid';
 	import toast from 'svelte-french-toast';
 
-	import { onDestroy, onMount, tick } from 'svelte';
+	import { 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 { models, modelfiles, user, settings, db, chats, chatId } from '$lib/stores';
 	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 Messages from '$lib/components/chat/Messages.svelte';
 	import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
 	import Navbar from '$lib/components/layout/Navbar.svelte';
+	import { createNewChat, getChatList, updateChatById } from '$lib/apis/chats';
 
 	let stopResponseFlag = false;
 	let autoScroll = true;
 
 	let selectedModels = [''];
+
 	let selectedModelfile = null;
 	$: selectedModelfile =
 		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
 	//////////////////////////
@@ -135,11 +104,11 @@
 			})
 		);
 
-		await chats.set(await $db.getChats());
+		await chats.set(await getChatList(localStorage.token));
 	};
 
 	const sendPromptOllama = async (model, userPrompt, parentId, _chatId) => {
-		console.log('sendPromptOllama');
+		// Create response message
 		let responseMessageId = uuidv4();
 		let responseMessage = {
 			parentId: parentId,
@@ -150,8 +119,11 @@
 			model: model
 		};
 
+		// Add message to history and Set currentId to messageId
 		history.messages[responseMessageId] = responseMessage;
 		history.currentId = responseMessageId;
+
+		// Append messageId to childrenIds of parent message
 		if (parentId !== null) {
 			history.messages[parentId].childrenIds = [
 				...history.messages[parentId].childrenIds,
@@ -159,17 +131,16 @@
 			];
 		}
 
+		// Wait until history/message have been updated
 		await tick();
+
+		// Scroll down
 		window.scrollTo({ top: document.body.scrollHeight });
 
-		const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/chat`, {
-			method: 'POST',
-			headers: {
-				'Content-Type': 'text/event-stream',
-				...($settings.authHeader && { Authorization: $settings.authHeader }),
-				...($user && { Authorization: `Bearer ${localStorage.token}` })
-			},
-			body: JSON.stringify({
+		const res = await generateChatCompletion(
+			$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
+			localStorage.token,
+			{
 				model: model,
 				messages: [
 					$settings.system
@@ -191,20 +162,11 @@
 						})
 					})),
 				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 ?? {})
 				},
 				format: $settings.requestFormat ?? undefined
-			})
-		}).catch((err) => {
-			console.log(err);
-			return null;
-		});
+			}
+		);
 
 		if (res && res.ok) {
 			const reader = res.body
@@ -296,23 +258,11 @@
 			}
 
 			if ($chatId == _chatId) {
-				chat = await $db.updateChatById(_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 ?? {})
-					},
+				chat = await updateChatById(localStorage.token, _chatId, {
 					messages: messages,
 					history: history
 				});
+				await chats.set(await getChatList(localStorage.token));
 			}
 		} else {
 			if (res !== null) {
@@ -338,6 +288,7 @@
 
 		stopResponseFlag = false;
 		await tick();
+
 		if (autoScroll) {
 			window.scrollTo({ top: document.body.scrollHeight });
 		}
@@ -483,23 +434,11 @@
 					}
 
 					if ($chatId == _chatId) {
-						chat = await $db.updateChatById(_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 ?? {})
-							},
+						chat = await updateChatById(localStorage.token, _chatId, {
 							messages: messages,
 							history: history
 						});
+						await chats.set(await getChatList(localStorage.token));
 					}
 				} else {
 					if (res !== null) {
@@ -549,10 +488,13 @@
 		if (selectedModels.includes('')) {
 			toast.error('Model not selected');
 		} else if (messages.length != 0 && messages.at(-1).done != true) {
+			// Response not done
 			console.log('wait');
 		} else {
+			// Reset chat message textarea height
 			document.getElementById('chat-textarea').style.height = '';
 
+			// Create user message
 			let userMessageId = uuidv4();
 			let userMessage = {
 				id: userMessageId,
@@ -563,47 +505,42 @@
 				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) {
 				history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
 			}
 
-			history.messages[userMessageId] = userMessage;
-			history.currentId = userMessageId;
-
+			// Wait until history/message have been updated
 			await tick();
 
+			// Create new chat if only one message in messages
 			if (messages.length == 1) {
-				chat = await $db.createNewChat({
+				chat = await createNewChat(localStorage.token, {
 					id: $chatId,
 					title: 'New Chat',
 					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,
-					history: history
+					history: history,
+					timestamp: Date.now()
 				});
-
-				console.log(chat);
-
+				await chats.set(await getChatList(localStorage.token));
 				await chatId.set(chat.id);
 				await tick();
 			}
 
+			// Reset chat input textarea
 			prompt = '';
 			files = [];
 
-			setTimeout(() => {
-				window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
-			}, 50);
-
+			// Send prompt
 			await sendPrompt(userPrompt, userMessageId);
 		}
 	};
@@ -614,9 +551,7 @@
 	};
 
 	const regenerateResponse = async () => {
-		const _chatId = JSON.parse(JSON.stringify($chatId));
-		console.log('regenerateResponse', _chatId);
-
+		console.log('regenerateResponse');
 		if (messages.length != 0 && messages.at(-1).done == true) {
 			messages.splice(messages.length - 1, 1);
 			messages = messages;
@@ -624,40 +559,21 @@
 			let userMessage = messages.at(-1);
 			let userPrompt = userMessage.content;
 
-			await sendPrompt(userPrompt, userMessage.id, _chatId);
+			await sendPrompt(userPrompt, userMessage.id);
 		}
 	};
 
 	const generateChatTitle = async (_chatId, userPrompt) => {
 		if ($settings.titleAutoGenerate ?? true) {
-			console.log('generateChatTitle');
-
-			const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/generate`, {
-				method: 'POST',
-				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);
+			const title = await generateTitle(
+				$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
+				localStorage.token,
+				selectedModels[0],
+				userPrompt
+			);
+
+			if (title) {
+				await setChatTitle(_chatId, title);
 			}
 		} else {
 			await setChatTitle(_chatId, `${userPrompt}`);
@@ -665,10 +581,12 @@
 	};
 
 	const setChatTitle = async (_chatId, _title) => {
-		chat = await $db.updateChatById(_chatId, { ...chat.chat, title: _title });
 		if (_chatId === $chatId) {
 			title = _title;
 		}
+
+		chat = await updateChatById(localStorage.token, _chatId, { title: _title });
+		await chats.set(await getChatList(localStorage.token));
 	};
 </script>
 

+ 51 - 105
src/routes/(app)/c/[id]/+page.svelte

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