Browse Source

refac: styling

Timothy Jaeryang Baek 5 months ago
parent
commit
19fc83a12b

+ 1 - 1
src/lib/components/chat/Settings/Account.svelte

@@ -70,7 +70,7 @@
 </script>
 
 <div class="flex flex-col h-full justify-between text-sm">
-	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll">
 		<input
 			id="profile-image-input"
 			bind:this={profileImageInputElement}

+ 1 - 1
src/lib/components/chat/Settings/Audio.svelte

@@ -98,7 +98,7 @@
 		dispatch('save');
 	}}
 >
-	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll">
 		<div>
 			<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
 

+ 1 - 1
src/lib/components/chat/Settings/Chats.svelte

@@ -97,7 +97,7 @@
 	};
 </script>
 
-<div class="flex flex-col h-full justify-between space-y-3 text-sm max-h-[22rem]">
+<div class="flex flex-col h-full justify-between space-y-3 text-sm">
 	<div class=" space-y-2">
 		<div class="flex flex-col">
 			<input

+ 1 - 1
src/lib/components/chat/Settings/General.svelte

@@ -156,7 +156,7 @@
 </script>
 
 <div class="flex flex-col h-full justify-between text-sm">
-	<div class="  pr-1.5 overflow-y-scroll max-h-[25rem]">
+	<div class="  pr-1.5 overflow-y-scroll max-h-full">
 		<div class="">
 			<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Settings')}</div>
 

+ 1 - 23
src/lib/components/chat/Settings/Interface.svelte

@@ -242,29 +242,7 @@
 		}}
 	/>
 
-	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem] scrollbar-hidden">
-		<div class=" space-y-1 mb-3">
-			<div class="mb-2">
-				<div class="flex justify-between items-center text-xs">
-					<div class=" text-sm font-medium">{$i18n.t('Default Model')}</div>
-				</div>
-			</div>
-
-			<div class="flex-1 mr-2">
-				<select
-					class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-					bind:value={defaultModelId}
-					placeholder="Select a model"
-				>
-					<option value="" disabled selected>{$i18n.t('Select a model')}</option>
-					{#each $models.filter((model) => model.id) as model}
-						<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
-					{/each}
-				</select>
-			</div>
-		</div>
-		<hr class=" dark:border-gray-850" />
-
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll scrollbar-hidden">
 		<div>
 			<div class=" mb-1.5 text-sm font-medium">{$i18n.t('UI')}</div>
 

+ 0 - 1075
src/lib/components/chat/Settings/Models.svelte

@@ -1,1075 +0,0 @@
-<script lang="ts">
-	import { toast } from 'svelte-sonner';
-
-	import {
-		createModel,
-		deleteModel,
-		downloadModel,
-		getOllamaUrls,
-		getOllamaVersion,
-		pullModel,
-		uploadModel,
-		getOllamaConfig
-	} from '$lib/apis/ollama';
-
-	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
-	import { WEBUI_NAME, models, MODEL_DOWNLOAD_POOL, user, config } from '$lib/stores';
-	import { splitStream } from '$lib/utils';
-	import { onMount, getContext } from 'svelte';
-
-	import Tooltip from '$lib/components/common/Tooltip.svelte';
-	import Spinner from '$lib/components/common/Spinner.svelte';
-
-	const i18n = getContext('i18n');
-
-	export let getModels: Function;
-
-	let modelUploadInputElement: HTMLInputElement;
-
-	// Models
-
-	let ollamaEnabled = null;
-
-	let OLLAMA_URLS = [];
-	let selectedOllamaUrlIdx: number | null = null;
-
-	let updateModelId = null;
-	let updateProgress = null;
-
-	let showExperimentalOllama = false;
-
-	let ollamaVersion = null;
-	const MAX_PARALLEL_DOWNLOADS = 3;
-
-	let modelTransferring = false;
-	let modelTag = '';
-
-	let createModelLoading = false;
-	let createModelTag = '';
-	let createModelContent = '';
-	let createModelDigest = '';
-	let createModelPullProgress = null;
-
-	let digest = '';
-	let pullProgress = null;
-
-	let modelUploadMode = 'file';
-	let modelInputFile: File[] | null = null;
-	let modelFileUrl = '';
-	let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`;
-	let modelFileDigest = '';
-
-	let uploadProgress = null;
-	let uploadMessage = '';
-
-	let deleteModelTag = '';
-
-	const updateModelsHandler = async () => {
-		for (const model of $models.filter(
-			(m) =>
-				!(m?.preset ?? false) &&
-				m.owned_by === 'ollama' &&
-				(selectedOllamaUrlIdx === null
-					? true
-					: (m?.ollama?.urls ?? []).includes(selectedOllamaUrlIdx))
-		)) {
-			console.log(model);
-
-			updateModelId = model.id;
-			const [res, controller] = await pullModel(
-				localStorage.token,
-				model.id,
-				selectedOllamaUrlIdx
-			).catch((error) => {
-				toast.error(error);
-				return null;
-			});
-
-			if (res) {
-				const reader = res.body
-					.pipeThrough(new TextDecoderStream())
-					.pipeThrough(splitStream('\n'))
-					.getReader();
-
-				while (true) {
-					try {
-						const { value, done } = await reader.read();
-						if (done) break;
-
-						let lines = value.split('\n');
-
-						for (const line of lines) {
-							if (line !== '') {
-								let data = JSON.parse(line);
-
-								console.log(data);
-								if (data.error) {
-									throw data.error;
-								}
-								if (data.detail) {
-									throw data.detail;
-								}
-								if (data.status) {
-									if (data.digest) {
-										updateProgress = 0;
-										if (data.completed) {
-											updateProgress = Math.round((data.completed / data.total) * 1000) / 10;
-										} else {
-											updateProgress = 100;
-										}
-									} else {
-										toast.success(data.status);
-									}
-								}
-							}
-						}
-					} catch (error) {
-						console.log(error);
-					}
-				}
-			}
-		}
-
-		updateModelId = null;
-		updateProgress = null;
-	};
-
-	const pullModelHandler = async () => {
-		const sanitizedModelTag = modelTag.trim().replace(/^ollama\s+(run|pull)\s+/, '');
-		console.log($MODEL_DOWNLOAD_POOL);
-		if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) {
-			toast.error(
-				$i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, {
-					modelTag: sanitizedModelTag
-				})
-			);
-			return;
-		}
-		if (Object.keys($MODEL_DOWNLOAD_POOL).length === MAX_PARALLEL_DOWNLOADS) {
-			toast.error(
-				$i18n.t('Maximum of 3 models can be downloaded simultaneously. Please try again later.')
-			);
-			return;
-		}
-
-		const [res, controller] = await pullModel(localStorage.token, sanitizedModelTag, '0').catch(
-			(error) => {
-				toast.error(error);
-				return null;
-			}
-		);
-
-		if (res) {
-			const reader = res.body
-				.pipeThrough(new TextDecoderStream())
-				.pipeThrough(splitStream('\n'))
-				.getReader();
-
-			MODEL_DOWNLOAD_POOL.set({
-				...$MODEL_DOWNLOAD_POOL,
-				[sanitizedModelTag]: {
-					...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
-					abortController: controller,
-					reader,
-					done: false
-				}
-			});
-
-			while (true) {
-				try {
-					const { value, done } = await reader.read();
-					if (done) break;
-
-					let lines = value.split('\n');
-
-					for (const line of lines) {
-						if (line !== '') {
-							let data = JSON.parse(line);
-							console.log(data);
-							if (data.error) {
-								throw data.error;
-							}
-							if (data.detail) {
-								throw data.detail;
-							}
-
-							if (data.status) {
-								if (data.digest) {
-									let downloadProgress = 0;
-									if (data.completed) {
-										downloadProgress = Math.round((data.completed / data.total) * 1000) / 10;
-									} else {
-										downloadProgress = 100;
-									}
-
-									MODEL_DOWNLOAD_POOL.set({
-										...$MODEL_DOWNLOAD_POOL,
-										[sanitizedModelTag]: {
-											...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
-											pullProgress: downloadProgress,
-											digest: data.digest
-										}
-									});
-								} else {
-									toast.success(data.status);
-
-									MODEL_DOWNLOAD_POOL.set({
-										...$MODEL_DOWNLOAD_POOL,
-										[sanitizedModelTag]: {
-											...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
-											done: data.status === 'success'
-										}
-									});
-								}
-							}
-						}
-					}
-				} catch (error) {
-					console.log(error);
-					if (typeof error !== 'string') {
-						error = error.message;
-					}
-
-					toast.error(error);
-					// opts.callback({ success: false, error, modelName: opts.modelName });
-				}
-			}
-
-			console.log($MODEL_DOWNLOAD_POOL[sanitizedModelTag]);
-
-			if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag].done) {
-				toast.success(
-					$i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, {
-						modelName: sanitizedModelTag
-					})
-				);
-
-				models.set(await getModels(localStorage.token));
-			} else {
-				toast.error($i18n.t('Download canceled'));
-			}
-
-			delete $MODEL_DOWNLOAD_POOL[sanitizedModelTag];
-
-			MODEL_DOWNLOAD_POOL.set({
-				...$MODEL_DOWNLOAD_POOL
-			});
-		}
-
-		modelTag = '';
-		modelTransferring = false;
-	};
-
-	const uploadModelHandler = async () => {
-		modelTransferring = true;
-
-		let uploaded = false;
-		let fileResponse = null;
-		let name = '';
-
-		if (modelUploadMode === 'file') {
-			const file = modelInputFile ? modelInputFile[0] : null;
-
-			if (file) {
-				uploadMessage = 'Uploading...';
-
-				fileResponse = await uploadModel(localStorage.token, file, selectedOllamaUrlIdx).catch(
-					(error) => {
-						toast.error(error);
-						return null;
-					}
-				);
-			}
-		} else {
-			uploadProgress = 0;
-			fileResponse = await downloadModel(
-				localStorage.token,
-				modelFileUrl,
-				selectedOllamaUrlIdx
-			).catch((error) => {
-				toast.error(error);
-				return null;
-			});
-		}
-
-		if (fileResponse && fileResponse.ok) {
-			const reader = fileResponse.body
-				.pipeThrough(new TextDecoderStream())
-				.pipeThrough(splitStream('\n'))
-				.getReader();
-
-			while (true) {
-				const { value, done } = await reader.read();
-				if (done) break;
-
-				try {
-					let lines = value.split('\n');
-
-					for (const line of lines) {
-						if (line !== '') {
-							let data = JSON.parse(line.replace(/^data: /, ''));
-
-							if (data.progress) {
-								if (uploadMessage) {
-									uploadMessage = '';
-								}
-								uploadProgress = data.progress;
-							}
-
-							if (data.error) {
-								throw data.error;
-							}
-
-							if (data.done) {
-								modelFileDigest = data.blob;
-								name = data.name;
-								uploaded = true;
-							}
-						}
-					}
-				} catch (error) {
-					console.log(error);
-				}
-			}
-		} else {
-			const error = await fileResponse?.json();
-			toast.error(error?.detail ?? error);
-		}
-
-		if (uploaded) {
-			const res = await createModel(
-				localStorage.token,
-				`${name}:latest`,
-				`FROM @${modelFileDigest}\n${modelFileContent}`
-			);
-
-			if (res && res.ok) {
-				const reader = res.body
-					.pipeThrough(new TextDecoderStream())
-					.pipeThrough(splitStream('\n'))
-					.getReader();
-
-				while (true) {
-					const { value, done } = await reader.read();
-					if (done) break;
-
-					try {
-						let lines = value.split('\n');
-
-						for (const line of lines) {
-							if (line !== '') {
-								console.log(line);
-								let data = JSON.parse(line);
-								console.log(data);
-
-								if (data.error) {
-									throw data.error;
-								}
-								if (data.detail) {
-									throw data.detail;
-								}
-
-								if (data.status) {
-									if (
-										!data.digest &&
-										!data.status.includes('writing') &&
-										!data.status.includes('sha256')
-									) {
-										toast.success(data.status);
-									} else {
-										if (data.digest) {
-											digest = data.digest;
-
-											if (data.completed) {
-												pullProgress = Math.round((data.completed / data.total) * 1000) / 10;
-											} else {
-												pullProgress = 100;
-											}
-										}
-									}
-								}
-							}
-						}
-					} catch (error) {
-						console.log(error);
-						toast.error(error);
-					}
-				}
-			}
-		}
-
-		modelFileUrl = '';
-
-		if (modelUploadInputElement) {
-			modelUploadInputElement.value = '';
-		}
-		modelInputFile = null;
-		modelTransferring = false;
-		uploadProgress = null;
-
-		models.set(await getModels());
-	};
-
-	const deleteModelHandler = async () => {
-		const res = await deleteModel(localStorage.token, deleteModelTag, selectedOllamaUrlIdx).catch(
-			(error) => {
-				toast.error(error);
-			}
-		);
-
-		if (res) {
-			toast.success($i18n.t(`Deleted {{deleteModelTag}}`, { deleteModelTag }));
-		}
-
-		deleteModelTag = '';
-		models.set(await getModels());
-	};
-
-	const cancelModelPullHandler = async (model: string) => {
-		const { reader, abortController } = $MODEL_DOWNLOAD_POOL[model];
-		if (abortController) {
-			abortController.abort();
-		}
-		if (reader) {
-			await reader.cancel();
-			delete $MODEL_DOWNLOAD_POOL[model];
-			MODEL_DOWNLOAD_POOL.set({
-				...$MODEL_DOWNLOAD_POOL
-			});
-			await deleteModel(localStorage.token, model);
-			toast.success(`${model} download has been canceled`);
-		}
-	};
-
-	const createModelHandler = async () => {
-		createModelLoading = true;
-		const res = await createModel(
-			localStorage.token,
-			createModelTag,
-			createModelContent,
-			selectedOllamaUrlIdx
-		).catch((error) => {
-			toast.error(error);
-			return null;
-		});
-
-		if (res && res.ok) {
-			const reader = res.body
-				.pipeThrough(new TextDecoderStream())
-				.pipeThrough(splitStream('\n'))
-				.getReader();
-
-			while (true) {
-				const { value, done } = await reader.read();
-				if (done) break;
-
-				try {
-					let lines = value.split('\n');
-
-					for (const line of lines) {
-						if (line !== '') {
-							console.log(line);
-							let data = JSON.parse(line);
-							console.log(data);
-
-							if (data.error) {
-								throw data.error;
-							}
-							if (data.detail) {
-								throw data.detail;
-							}
-
-							if (data.status) {
-								if (
-									!data.digest &&
-									!data.status.includes('writing') &&
-									!data.status.includes('sha256')
-								) {
-									toast.success(data.status);
-								} else {
-									if (data.digest) {
-										createModelDigest = data.digest;
-
-										if (data.completed) {
-											createModelPullProgress =
-												Math.round((data.completed / data.total) * 1000) / 10;
-										} else {
-											createModelPullProgress = 100;
-										}
-									}
-								}
-							}
-						}
-					}
-				} catch (error) {
-					console.log(error);
-					toast.error(error);
-				}
-			}
-		}
-
-		models.set(await getModels());
-
-		createModelLoading = false;
-
-		createModelTag = '';
-		createModelContent = '';
-		createModelDigest = '';
-		createModelPullProgress = null;
-	};
-
-	onMount(async () => {
-		const ollamaConfig = await getOllamaConfig(localStorage.token);
-
-		if (ollamaConfig.ENABLE_OLLAMA_API) {
-			ollamaEnabled = true;
-
-			await Promise.all([
-				(async () => {
-					OLLAMA_URLS = await getOllamaUrls(localStorage.token).catch((error) => {
-						toast.error(error);
-						return [];
-					});
-
-					if (OLLAMA_URLS.length > 0) {
-						selectedOllamaUrlIdx = 0;
-					}
-				})(),
-				(async () => {
-					ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
-				})()
-			]);
-		} else {
-			ollamaEnabled = false;
-			toast.error($i18n.t('Ollama API is disabled'));
-		}
-	});
-</script>
-
-<div class="flex flex-col h-full justify-between text-sm">
-	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[27rem]">
-		{#if ollamaEnabled}
-			{#if ollamaVersion !== null}
-				<div class="space-y-2 pr-1.5">
-					<div class="text-sm font-medium">{$i18n.t('Manage Ollama Models')}</div>
-
-					{#if OLLAMA_URLS.length > 0}
-						<div class="flex gap-2">
-							<div class="flex-1 pb-1">
-								<select
-									class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-									bind:value={selectedOllamaUrlIdx}
-									placeholder={$i18n.t('Select an Ollama instance')}
-								>
-									{#each OLLAMA_URLS as url, idx}
-										<option value={idx} class="bg-gray-100 dark:bg-gray-700">{url}</option>
-									{/each}
-								</select>
-							</div>
-
-							<div>
-								<div class="flex w-full justify-end">
-									<Tooltip content="Update All Models" placement="top">
-										<button
-											class="p-2.5 flex gap-2 items-center bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
-											on:click={() => {
-												updateModelsHandler();
-											}}
-										>
-											<svg
-												xmlns="http://www.w3.org/2000/svg"
-												viewBox="0 0 16 16"
-												fill="currentColor"
-												class="w-4 h-4"
-											>
-												<path
-													d="M7 1a.75.75 0 0 1 .75.75V6h-1.5V1.75A.75.75 0 0 1 7 1ZM6.25 6v2.94L5.03 7.72a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06L7.75 8.94V6H10a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.25Z"
-												/>
-												<path
-													d="M4.268 14A2 2 0 0 0 6 15h6a2 2 0 0 0 2-2v-3a2 2 0 0 0-1-1.732V11a3 3 0 0 1-3 3H4.268Z"
-												/>
-											</svg>
-										</button>
-									</Tooltip>
-								</div>
-							</div>
-						</div>
-
-						{#if updateModelId}
-							Updating "{updateModelId}" {updateProgress ? `(${updateProgress}%)` : ''}
-						{/if}
-					{/if}
-
-					<div class="space-y-2">
-						<div>
-							<div class=" mb-2 text-sm font-medium">{$i18n.t('Pull a model from Ollama.com')}</div>
-							<div class="flex w-full">
-								<div class="flex-1 mr-2">
-									<input
-										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-										placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
-											modelTag: 'mistral:7b'
-										})}
-										bind:value={modelTag}
-									/>
-								</div>
-								<button
-									class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
-									on:click={() => {
-										pullModelHandler();
-									}}
-									disabled={modelTransferring}
-								>
-									{#if modelTransferring}
-										<div class="self-center">
-											<svg
-												class=" w-4 h-4"
-												viewBox="0 0 24 24"
-												fill="currentColor"
-												xmlns="http://www.w3.org/2000/svg"
-											>
-												<style>
-													.spinner_ajPY {
-														transform-origin: center;
-														animation: spinner_AtaB 0.75s infinite linear;
-													}
-
-													@keyframes spinner_AtaB {
-														100% {
-															transform: rotate(360deg);
-														}
-													}
-												</style>
-												<path
-													d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
-													opacity=".25"
-												/>
-												<path
-													d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
-													class="spinner_ajPY"
-												/>
-											</svg>
-										</div>
-									{:else}
-										<svg
-											xmlns="http://www.w3.org/2000/svg"
-											viewBox="0 0 16 16"
-											fill="currentColor"
-											class="w-4 h-4"
-										>
-											<path
-												d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
-											/>
-											<path
-												d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
-											/>
-										</svg>
-									{/if}
-								</button>
-							</div>
-
-							<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
-								{$i18n.t('To access the available model names for downloading,')}
-								<a
-									class=" text-gray-500 dark:text-gray-300 font-medium underline"
-									href="https://ollama.com/library"
-									target="_blank">{$i18n.t('click here.')}</a
-								>
-							</div>
-
-							{#if Object.keys($MODEL_DOWNLOAD_POOL).length > 0}
-								{#each Object.keys($MODEL_DOWNLOAD_POOL) as model}
-									{#if 'pullProgress' in $MODEL_DOWNLOAD_POOL[model]}
-										<div class="flex flex-col">
-											<div class="font-medium mb-1">{model}</div>
-											<div class="">
-												<div class="flex flex-row justify-between space-x-4 pr-2">
-													<div class=" flex-1">
-														<div
-															class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
-															style="width: {Math.max(
-																15,
-																$MODEL_DOWNLOAD_POOL[model].pullProgress ?? 0
-															)}%"
-														>
-															{$MODEL_DOWNLOAD_POOL[model].pullProgress ?? 0}%
-														</div>
-													</div>
-
-													<Tooltip content={$i18n.t('Cancel')}>
-														<button
-															class="text-gray-800 dark:text-gray-100"
-															on:click={() => {
-																cancelModelPullHandler(model);
-															}}
-														>
-															<svg
-																class="w-4 h-4 text-gray-800 dark:text-white"
-																aria-hidden="true"
-																xmlns="http://www.w3.org/2000/svg"
-																width="24"
-																height="24"
-																fill="currentColor"
-																viewBox="0 0 24 24"
-															>
-																<path
-																	stroke="currentColor"
-																	stroke-linecap="round"
-																	stroke-linejoin="round"
-																	stroke-width="2"
-																	d="M6 18 17.94 6M18 18 6.06 6"
-																/>
-															</svg>
-														</button>
-													</Tooltip>
-												</div>
-												{#if 'digest' in $MODEL_DOWNLOAD_POOL[model]}
-													<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
-														{$MODEL_DOWNLOAD_POOL[model].digest}
-													</div>
-												{/if}
-											</div>
-										</div>
-									{/if}
-								{/each}
-							{/if}
-						</div>
-
-						<div>
-							<div class=" mb-2 text-sm font-medium">{$i18n.t('Delete a model')}</div>
-							<div class="flex w-full">
-								<div class="flex-1 mr-2">
-									<select
-										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-										bind:value={deleteModelTag}
-										placeholder={$i18n.t('Select a model')}
-									>
-										{#if !deleteModelTag}
-											<option value="" disabled selected>{$i18n.t('Select a model')}</option>
-										{/if}
-										{#each $models.filter((m) => !(m?.preset ?? false) && m.owned_by === 'ollama' && (selectedOllamaUrlIdx === null ? true : (m?.ollama?.urls ?? []).includes(selectedOllamaUrlIdx))) as model}
-											<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
-												>{model.name +
-													' (' +
-													(model.ollama.size / 1024 ** 3).toFixed(1) +
-													' GB)'}</option
-											>
-										{/each}
-									</select>
-								</div>
-								<button
-									class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
-									on:click={() => {
-										deleteModelHandler();
-									}}
-								>
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 16 16"
-										fill="currentColor"
-										class="w-4 h-4"
-									>
-										<path
-											fill-rule="evenodd"
-											d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
-											clip-rule="evenodd"
-										/>
-									</svg>
-								</button>
-							</div>
-						</div>
-
-						<div>
-							<div class=" mb-2 text-sm font-medium">{$i18n.t('Create a model')}</div>
-							<div class="flex w-full">
-								<div class="flex-1 mr-2 flex flex-col gap-2">
-									<input
-										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-										placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
-											modelTag: 'my-modelfile'
-										})}
-										bind:value={createModelTag}
-										disabled={createModelLoading}
-									/>
-
-									<textarea
-										bind:value={createModelContent}
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-100 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none scrollbar-hidden"
-										rows="6"
-										placeholder={`TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`}
-										disabled={createModelLoading}
-									/>
-								</div>
-
-								<div class="flex self-start">
-									<button
-										class="px-2.5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition disabled:cursor-not-allowed"
-										on:click={() => {
-											createModelHandler();
-										}}
-										disabled={createModelLoading}
-									>
-										<svg
-											xmlns="http://www.w3.org/2000/svg"
-											viewBox="0 0 16 16"
-											fill="currentColor"
-											class="size-4"
-										>
-											<path
-												d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
-											/>
-											<path
-												d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
-											/>
-										</svg>
-									</button>
-								</div>
-							</div>
-
-							{#if createModelDigest !== ''}
-								<div class="flex flex-col mt-1">
-									<div class="font-medium mb-1">{createModelTag}</div>
-									<div class="">
-										<div class="flex flex-row justify-between space-x-4 pr-2">
-											<div class=" flex-1">
-												<div
-													class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
-													style="width: {Math.max(15, createModelPullProgress ?? 0)}%"
-												>
-													{createModelPullProgress ?? 0}%
-												</div>
-											</div>
-										</div>
-										{#if createModelDigest}
-											<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
-												{createModelDigest}
-											</div>
-										{/if}
-									</div>
-								</div>
-							{/if}
-						</div>
-
-						<div class="pt-1">
-							<div class="flex justify-between items-center text-xs">
-								<div class=" text-sm font-medium">{$i18n.t('Experimental')}</div>
-								<button
-									class=" text-xs font-medium text-gray-500"
-									type="button"
-									on:click={() => {
-										showExperimentalOllama = !showExperimentalOllama;
-									}}>{showExperimentalOllama ? $i18n.t('Hide') : $i18n.t('Show')}</button
-								>
-							</div>
-						</div>
-
-						{#if showExperimentalOllama}
-							<form
-								on:submit|preventDefault={() => {
-									uploadModelHandler();
-								}}
-							>
-								<div class=" mb-2 flex w-full justify-between">
-									<div class="  text-sm font-medium">{$i18n.t('Upload a GGUF model')}</div>
-
-									<button
-										class="p-1 px-3 text-xs flex rounded transition"
-										on:click={() => {
-											if (modelUploadMode === 'file') {
-												modelUploadMode = 'url';
-											} else {
-												modelUploadMode = 'file';
-											}
-										}}
-										type="button"
-									>
-										{#if modelUploadMode === 'file'}
-											<span class="ml-2 self-center">{$i18n.t('File Mode')}</span>
-										{:else}
-											<span class="ml-2 self-center">{$i18n.t('URL Mode')}</span>
-										{/if}
-									</button>
-								</div>
-
-								<div class="flex w-full mb-1.5">
-									<div class="flex flex-col w-full">
-										{#if modelUploadMode === 'file'}
-											<div
-												class="flex-1 {modelInputFile && modelInputFile.length > 0 ? 'mr-2' : ''}"
-											>
-												<input
-													id="model-upload-input"
-													bind:this={modelUploadInputElement}
-													type="file"
-													bind:files={modelInputFile}
-													on:change={() => {
-														console.log(modelInputFile);
-													}}
-													accept=".gguf,.safetensors"
-													required
-													hidden
-												/>
-
-												<button
-													type="button"
-													class="w-full rounded-lg text-left py-2 px-4 bg-white dark:text-gray-300 dark:bg-gray-850"
-													on:click={() => {
-														modelUploadInputElement.click();
-													}}
-												>
-													{#if modelInputFile && modelInputFile.length > 0}
-														{modelInputFile[0].name}
-													{:else}
-														{$i18n.t('Click here to select')}
-													{/if}
-												</button>
-											</div>
-										{:else}
-											<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
-												<input
-													class="w-full rounded-lg text-left py-2 px-4 bg-white dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !==
-													''
-														? 'mr-2'
-														: ''}"
-													type="url"
-													required
-													bind:value={modelFileUrl}
-													placeholder={$i18n.t('Type Hugging Face Resolve (Download) URL')}
-												/>
-											</div>
-										{/if}
-									</div>
-
-									{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
-										<button
-											class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg disabled:cursor-not-allowed transition"
-											type="submit"
-											disabled={modelTransferring}
-										>
-											{#if modelTransferring}
-												<div class="self-center">
-													<svg
-														class=" w-4 h-4"
-														viewBox="0 0 24 24"
-														fill="currentColor"
-														xmlns="http://www.w3.org/2000/svg"
-													>
-														<style>
-															.spinner_ajPY {
-																transform-origin: center;
-																animation: spinner_AtaB 0.75s infinite linear;
-															}
-
-															@keyframes spinner_AtaB {
-																100% {
-																	transform: rotate(360deg);
-																}
-															}
-														</style>
-														<path
-															d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
-															opacity=".25"
-														/>
-														<path
-															d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
-															class="spinner_ajPY"
-														/>
-													</svg>
-												</div>
-											{:else}
-												<svg
-													xmlns="http://www.w3.org/2000/svg"
-													viewBox="0 0 16 16"
-													fill="currentColor"
-													class="w-4 h-4"
-												>
-													<path
-														d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
-													/>
-													<path
-														d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
-													/>
-												</svg>
-											{/if}
-										</button>
-									{/if}
-								</div>
-
-								{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
-									<div>
-										<div>
-											<div class=" my-2.5 text-sm font-medium">{$i18n.t('Modelfile Content')}</div>
-											<textarea
-												bind:value={modelFileContent}
-												class="w-full rounded-lg py-2 px-4 text-sm bg-gray-100 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none"
-												rows="6"
-											/>
-										</div>
-									</div>
-								{/if}
-								<div class=" mt-1 text-xs text-gray-400 dark:text-gray-500">
-									{$i18n.t('To access the GGUF models available for downloading,')}
-									<a
-										class=" text-gray-500 dark:text-gray-300 font-medium underline"
-										href="https://huggingface.co/models?search=gguf"
-										target="_blank">{$i18n.t('click here.')}</a
-									>
-								</div>
-
-								{#if uploadMessage}
-									<div class="mt-2">
-										<div class=" mb-2 text-xs">{$i18n.t('Upload Progress')}</div>
-
-										<div class="w-full rounded-full dark:bg-gray-800">
-											<div
-												class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
-												style="width: 100%"
-											>
-												{uploadMessage}
-											</div>
-										</div>
-										<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
-											{modelFileDigest}
-										</div>
-									</div>
-								{:else if uploadProgress !== null}
-									<div class="mt-2">
-										<div class=" mb-2 text-xs">{$i18n.t('Upload Progress')}</div>
-
-										<div class="w-full rounded-full dark:bg-gray-800">
-											<div
-												class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
-												style="width: {Math.max(15, uploadProgress ?? 0)}%"
-											>
-												{uploadProgress ?? 0}%
-											</div>
-										</div>
-										<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
-											{modelFileDigest}
-										</div>
-									</div>
-								{/if}
-							</form>
-						{/if}
-					</div>
-				</div>
-			{:else if ollamaVersion === false}
-				<div>Ollama Not Detected</div>
-			{:else}
-				<div class="flex h-full justify-center">
-					<div class="my-auto">
-						<Spinner className="size-6" />
-					</div>
-				</div>
-			{/if}
-		{:else if ollamaEnabled === false}
-			<div>{$i18n.t('Ollama API is disabled')}</div>
-		{:else}
-			<div class="flex h-full justify-center">
-				<div class="my-auto">
-					<Spinner className="size-6" />
-				</div>
-			</div>
-		{/if}
-	</div>
-</div>

+ 1 - 1
src/lib/components/chat/Settings/Personalization.svelte

@@ -31,7 +31,7 @@
 		dispatch('save');
 	}}
 >
-	<div class="  pr-1.5 py-1 overflow-y-scroll max-h-[25rem]">
+	<div class="  pr-1.5 py-1 overflow-y-scroll">
 		<div>
 			<div class="flex items-center justify-between mb-1">
 				<Tooltip

+ 1 - 1
src/lib/components/chat/SettingsModal.svelte

@@ -590,7 +590,7 @@
 					</div>
 				{/if}
 			</div>
-			<div class="flex-1 md:min-h-[32rem]">
+			<div class="flex-1 md:min-h-[32rem] max-h-[32rem]">
 				{#if selectedTab === 'general'}
 					<General
 						{getModels}