mirror of
				https://github.com/open-webui/open-webui
				synced 2025-06-26 18:26:48 +00:00 
			
		
		
		
	feat: multi-user chat history support
This commit is contained in:
		
							parent
							
								
									1274bd986b
								
							
						
					
					
						commit
						0810a2648f
					
				| @ -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 New Issue
	
	Block a user