forked from open-webui/open-webui
		
	refac: settings
This commit is contained in:
		
							parent
							
								
									87a1927447
								
							
						
					
					
						commit
						cd9f0135f6
					
				
					 12 changed files with 2381 additions and 2325 deletions
				
			
		
							
								
								
									
										63
									
								
								src/lib/components/chat/Settings/About.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/lib/components/chat/Settings/About.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { getOllamaVersion } from '$lib/apis/ollama';
 | 
			
		||||
	import { WEB_UI_VERSION } from '$lib/constants';
 | 
			
		||||
	import { config } from '$lib/stores';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	let ollamaVersion = '';
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => {
 | 
			
		||||
			return '';
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="flex flex-col h-full justify-between space-y-3 text-sm mb-6">
 | 
			
		||||
	<div class=" space-y-3">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">Ollama Web UI Version</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
 | 
			
		||||
					{$config && $config.version ? $config.version : WEB_UI_VERSION}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">Ollama Version</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
 | 
			
		||||
					{ollamaVersion ?? 'N/A'}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div class="flex space-x-1">
 | 
			
		||||
			<a href="https://discord.gg/5rJgQTnV4s" target="_blank">
 | 
			
		||||
				<img
 | 
			
		||||
					alt="Discord"
 | 
			
		||||
					src="https://img.shields.io/badge/Discord-Ollama_Web_UI-blue?logo=discord&logoColor=white"
 | 
			
		||||
				/>
 | 
			
		||||
			</a>
 | 
			
		||||
 | 
			
		||||
			<a href="https://github.com/ollama-webui/ollama-webui" target="_blank">
 | 
			
		||||
				<img
 | 
			
		||||
					alt="Github Repo"
 | 
			
		||||
					src="https://img.shields.io/github/stars/ollama-webui/ollama-webui?style=social&label=Star us on Github"
 | 
			
		||||
				/>
 | 
			
		||||
			</a>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
			Created by <a
 | 
			
		||||
				class=" text-gray-500 dark:text-gray-300 font-medium"
 | 
			
		||||
				href="https://github.com/tjbck"
 | 
			
		||||
				target="_blank">Timothy J. Baek</a
 | 
			
		||||
			>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										249
									
								
								src/lib/components/chat/Settings/AddOns.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								src/lib/components/chat/Settings/AddOns.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,249 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import toast from 'svelte-french-toast';
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	import { models, voices } from '$lib/stores';
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	export let saveSettings: Function;
 | 
			
		||||
	// Addons
 | 
			
		||||
	let titleAutoGenerate = true;
 | 
			
		||||
	let speechAutoSend = false;
 | 
			
		||||
	let responseAutoCopy = false;
 | 
			
		||||
 | 
			
		||||
	let gravatarEmail = '';
 | 
			
		||||
	let titleAutoGenerateModel = '';
 | 
			
		||||
 | 
			
		||||
	// Voice
 | 
			
		||||
	let speakVoice = '';
 | 
			
		||||
 | 
			
		||||
	const toggleSpeechAutoSend = async () => {
 | 
			
		||||
		speechAutoSend = !speechAutoSend;
 | 
			
		||||
		saveSettings({ speechAutoSend: speechAutoSend });
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const toggleTitleAutoGenerate = async () => {
 | 
			
		||||
		titleAutoGenerate = !titleAutoGenerate;
 | 
			
		||||
		saveSettings({ titleAutoGenerate: titleAutoGenerate });
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const toggleResponseAutoCopy = async () => {
 | 
			
		||||
		const permission = await navigator.clipboard
 | 
			
		||||
			.readText()
 | 
			
		||||
			.then(() => {
 | 
			
		||||
				return 'granted';
 | 
			
		||||
			})
 | 
			
		||||
			.catch(() => {
 | 
			
		||||
				return '';
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
		console.log(permission);
 | 
			
		||||
 | 
			
		||||
		if (permission === 'granted') {
 | 
			
		||||
			responseAutoCopy = !responseAutoCopy;
 | 
			
		||||
			saveSettings({ responseAutoCopy: responseAutoCopy });
 | 
			
		||||
		} else {
 | 
			
		||||
			toast.error(
 | 
			
		||||
				'Clipboard write permission denied. Please check your browser settings to grant the necessary access.'
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
 | 
			
		||||
 | 
			
		||||
		titleAutoGenerate = settings.titleAutoGenerate ?? true;
 | 
			
		||||
		speechAutoSend = settings.speechAutoSend ?? false;
 | 
			
		||||
		responseAutoCopy = settings.responseAutoCopy ?? false;
 | 
			
		||||
		titleAutoGenerateModel = settings.titleAutoGenerateModel ?? '';
 | 
			
		||||
		gravatarEmail = settings.gravatarEmail ?? '';
 | 
			
		||||
		speakVoice = settings.speakVoice ?? '';
 | 
			
		||||
 | 
			
		||||
		const getVoicesLoop = setInterval(async () => {
 | 
			
		||||
			const _voices = await speechSynthesis.getVoices();
 | 
			
		||||
			await voices.set(_voices);
 | 
			
		||||
 | 
			
		||||
			// do your loop
 | 
			
		||||
			if (_voices.length > 0) {
 | 
			
		||||
				clearInterval(getVoicesLoop);
 | 
			
		||||
			}
 | 
			
		||||
		}, 100);
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<form
 | 
			
		||||
	class="flex flex-col h-full justify-between space-y-3 text-sm"
 | 
			
		||||
	on:submit|preventDefault={() => {
 | 
			
		||||
		saveSettings({
 | 
			
		||||
			speakVoice: speakVoice !== '' ? speakVoice : undefined
 | 
			
		||||
		});
 | 
			
		||||
		dispatch('save');
 | 
			
		||||
	}}
 | 
			
		||||
>
 | 
			
		||||
	<div class=" space-y-3">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-1 text-sm font-medium">WebUI Add-ons</div>
 | 
			
		||||
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
					<div class=" self-center text-xs font-medium">Title Auto-Generation</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
						class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
						on:click={() => {
 | 
			
		||||
							toggleTitleAutoGenerate();
 | 
			
		||||
						}}
 | 
			
		||||
						type="button"
 | 
			
		||||
					>
 | 
			
		||||
						{#if titleAutoGenerate === true}
 | 
			
		||||
							<span class="ml-2 self-center">On</span>
 | 
			
		||||
						{:else}
 | 
			
		||||
							<span class="ml-2 self-center">Off</span>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
					<div class=" self-center text-xs font-medium">Voice Input Auto-Send</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
						class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
						on:click={() => {
 | 
			
		||||
							toggleSpeechAutoSend();
 | 
			
		||||
						}}
 | 
			
		||||
						type="button"
 | 
			
		||||
					>
 | 
			
		||||
						{#if speechAutoSend === true}
 | 
			
		||||
							<span class="ml-2 self-center">On</span>
 | 
			
		||||
						{:else}
 | 
			
		||||
							<span class="ml-2 self-center">Off</span>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
					<div class=" self-center text-xs font-medium">Response AutoCopy to Clipboard</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
						class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
						on:click={() => {
 | 
			
		||||
							toggleResponseAutoCopy();
 | 
			
		||||
						}}
 | 
			
		||||
						type="button"
 | 
			
		||||
					>
 | 
			
		||||
						{#if responseAutoCopy === true}
 | 
			
		||||
							<span class="ml-2 self-center">On</span>
 | 
			
		||||
						{:else}
 | 
			
		||||
							<span class="ml-2 self-center">Off</span>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">Set Title Auto-Generation Model</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1 mr-2">
 | 
			
		||||
					<select
 | 
			
		||||
						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
						bind:value={titleAutoGenerateModel}
 | 
			
		||||
						placeholder="Select a model"
 | 
			
		||||
					>
 | 
			
		||||
						<option value="" selected>Current Model</option>
 | 
			
		||||
						{#each $models.filter((m) => m.size != null) as model}
 | 
			
		||||
							<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
 | 
			
		||||
								>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
 | 
			
		||||
							>
 | 
			
		||||
						{/each}
 | 
			
		||||
					</select>
 | 
			
		||||
				</div>
 | 
			
		||||
				<button
 | 
			
		||||
					class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						saveSettings({
 | 
			
		||||
							titleAutoGenerateModel:
 | 
			
		||||
								titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined
 | 
			
		||||
						});
 | 
			
		||||
					}}
 | 
			
		||||
					type="button"
 | 
			
		||||
				>
 | 
			
		||||
					<svg
 | 
			
		||||
						xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
						viewBox="0 0 16 16"
 | 
			
		||||
						fill="currentColor"
 | 
			
		||||
						class="w-3.5 h-3.5"
 | 
			
		||||
					>
 | 
			
		||||
						<path
 | 
			
		||||
							fill-rule="evenodd"
 | 
			
		||||
							d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
 | 
			
		||||
							clip-rule="evenodd"
 | 
			
		||||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div class=" space-y-3">
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">Set Default Voice</div>
 | 
			
		||||
				<div class="flex w-full">
 | 
			
		||||
					<div class="flex-1">
 | 
			
		||||
						<select
 | 
			
		||||
							class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
							bind:value={speakVoice}
 | 
			
		||||
							placeholder="Select a voice"
 | 
			
		||||
						>
 | 
			
		||||
							<option value="" selected>Default</option>
 | 
			
		||||
							{#each $voices.filter((v) => v.localService === true) as voice}
 | 
			
		||||
								<option value={voice.name} class="bg-gray-100 dark:bg-gray-700">{voice.name}</option
 | 
			
		||||
								>
 | 
			
		||||
							{/each}
 | 
			
		||||
						</select>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<!--
 | 
			
		||||
							<div>
 | 
			
		||||
								<div class=" mb-2.5 text-sm font-medium">
 | 
			
		||||
									Gravatar Email <span class=" text-gray-400 text-sm">(optional)</span>
 | 
			
		||||
								</div>
 | 
			
		||||
								<div class="flex w-full">
 | 
			
		||||
									<div class="flex-1">
 | 
			
		||||
										<input
 | 
			
		||||
											class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
											placeholder="Enter Your Email"
 | 
			
		||||
											bind:value={gravatarEmail}
 | 
			
		||||
											autocomplete="off"
 | 
			
		||||
											type="email"
 | 
			
		||||
										/>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
								<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
									Changes user profile image to match your <a
 | 
			
		||||
										class=" text-gray-500 dark:text-gray-300 font-medium"
 | 
			
		||||
										href="https://gravatar.com/"
 | 
			
		||||
										target="_blank">Gravatar.</a
 | 
			
		||||
									>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div> -->
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="flex justify-end pt-3 text-sm font-medium">
 | 
			
		||||
		<button
 | 
			
		||||
			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
			type="submit"
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</form>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +1,15 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	export let options = {
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	import AdvancedParams from './Advanced/AdvancedParams.svelte';
 | 
			
		||||
	export let saveSettings: Function;
 | 
			
		||||
 | 
			
		||||
	// Advanced
 | 
			
		||||
	let requestFormat = '';
 | 
			
		||||
	let options = {
 | 
			
		||||
		// Advanced
 | 
			
		||||
		seed: 0,
 | 
			
		||||
		stop: '',
 | 
			
		||||
		temperature: '',
 | 
			
		||||
		repeat_penalty: '',
 | 
			
		||||
		repeat_last_n: '',
 | 
			
		||||
| 
						 | 
				
			
			@ -11,546 +18,101 @@
 | 
			
		|||
		mirostat_tau: '',
 | 
			
		||||
		top_k: '',
 | 
			
		||||
		top_p: '',
 | 
			
		||||
		stop: '',
 | 
			
		||||
		tfs_z: '',
 | 
			
		||||
		num_ctx: '',
 | 
			
		||||
		num_predict: ''
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const toggleRequestFormat = async () => {
 | 
			
		||||
		if (requestFormat === '') {
 | 
			
		||||
			requestFormat = 'json';
 | 
			
		||||
		} else {
 | 
			
		||||
			requestFormat = '';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		saveSettings({ requestFormat: requestFormat !== '' ? requestFormat : undefined });
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	onMount(() => {
 | 
			
		||||
		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
 | 
			
		||||
 | 
			
		||||
		requestFormat = settings.requestFormat ?? '';
 | 
			
		||||
 | 
			
		||||
		options.seed = settings.seed ?? 0;
 | 
			
		||||
		options.temperature = settings.temperature ?? '';
 | 
			
		||||
		options.repeat_penalty = settings.repeat_penalty ?? '';
 | 
			
		||||
		options.top_k = settings.top_k ?? '';
 | 
			
		||||
		options.top_p = settings.top_p ?? '';
 | 
			
		||||
		options.num_ctx = settings.num_ctx ?? '';
 | 
			
		||||
		options = { ...options, ...settings.options };
 | 
			
		||||
		options.stop = (settings?.options?.stop ?? []).join(',');
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class=" space-y-3 text-xs">
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
			<div class=" w-20 text-xs font-medium self-center">Seed</div>
 | 
			
		||||
			<div class=" flex-1 self-center">
 | 
			
		||||
				<input
 | 
			
		||||
					class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
 | 
			
		||||
					type="number"
 | 
			
		||||
					placeholder="Enter Seed"
 | 
			
		||||
					bind:value={options.seed}
 | 
			
		||||
					autocomplete="off"
 | 
			
		||||
					min="0"
 | 
			
		||||
				/>
 | 
			
		||||
<div class="flex flex-col h-full justify-between text-sm">
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 | 
			
		||||
		<div class=" text-sm font-medium">Parameters</div>
 | 
			
		||||
 | 
			
		||||
		<AdvancedParams bind:options />
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" py-1 flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Request Mode</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						toggleRequestFormat();
 | 
			
		||||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					{#if requestFormat === ''}
 | 
			
		||||
						<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
					{:else if requestFormat === 'json'}
 | 
			
		||||
						<!-- <svg
 | 
			
		||||
                            xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
                            viewBox="0 0 20 20"
 | 
			
		||||
                            fill="currentColor"
 | 
			
		||||
                            class="w-4 h-4 self-center"
 | 
			
		||||
                        >
 | 
			
		||||
                            <path
 | 
			
		||||
                                d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
 | 
			
		||||
                            />
 | 
			
		||||
                        </svg> -->
 | 
			
		||||
						<span class="ml-2 self-center"> JSON </span>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
			<div class=" w-20 text-xs font-medium self-center">Stop Sequence</div>
 | 
			
		||||
			<div class=" flex-1 self-center">
 | 
			
		||||
				<input
 | 
			
		||||
					class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
 | 
			
		||||
					type="text"
 | 
			
		||||
					placeholder="Enter Stop Sequence"
 | 
			
		||||
					bind:value={options.stop}
 | 
			
		||||
					autocomplete="off"
 | 
			
		||||
				/>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="flex justify-end pt-3 text-sm font-medium">
 | 
			
		||||
		<button
 | 
			
		||||
			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
			on:click={() => {
 | 
			
		||||
				saveSettings({
 | 
			
		||||
					options: {
 | 
			
		||||
						seed: (options.seed !== 0 ? options.seed : undefined) ?? undefined,
 | 
			
		||||
						stop: options.stop !== '' ? options.stop.split(',').filter((e) => e) : undefined,
 | 
			
		||||
						temperature: options.temperature !== '' ? options.temperature : undefined,
 | 
			
		||||
						repeat_penalty: options.repeat_penalty !== '' ? options.repeat_penalty : undefined,
 | 
			
		||||
						repeat_last_n: options.repeat_last_n !== '' ? options.repeat_last_n : undefined,
 | 
			
		||||
						mirostat: options.mirostat !== '' ? options.mirostat : undefined,
 | 
			
		||||
						mirostat_eta: options.mirostat_eta !== '' ? options.mirostat_eta : undefined,
 | 
			
		||||
						mirostat_tau: options.mirostat_tau !== '' ? options.mirostat_tau : undefined,
 | 
			
		||||
						top_k: options.top_k !== '' ? options.top_k : undefined,
 | 
			
		||||
						top_p: options.top_p !== '' ? options.top_p : undefined,
 | 
			
		||||
						tfs_z: options.tfs_z !== '' ? options.tfs_z : undefined,
 | 
			
		||||
						num_ctx: options.num_ctx !== '' ? options.num_ctx : undefined,
 | 
			
		||||
						num_predict: options.num_predict !== '' ? options.num_predict : undefined
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Temperature</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.temperature = options.temperature === '' ? 0.8 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.temperature === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.temperature !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="1"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
						bind:value={options.temperature}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.temperature}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="1"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Mirostat</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.mirostat = options.mirostat === '' ? 0 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.mirostat === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.mirostat !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="2"
 | 
			
		||||
						step="1"
 | 
			
		||||
						bind:value={options.mirostat}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.mirostat}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="2"
 | 
			
		||||
						step="1"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Mirostat Eta</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.mirostat_eta = options.mirostat_eta === '' ? 0.1 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.mirostat_eta === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.mirostat_eta !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="1"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
						bind:value={options.mirostat_eta}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.mirostat_eta}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="1"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Mirostat Tau</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.mirostat_tau = options.mirostat_tau === '' ? 5.0 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.mirostat_tau === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.mirostat_tau !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="10"
 | 
			
		||||
						step="0.5"
 | 
			
		||||
						bind:value={options.mirostat_tau}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.mirostat_tau}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="10"
 | 
			
		||||
						step="0.5"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Top K</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.top_k = options.top_k === '' ? 40 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.top_k === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.top_k !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="100"
 | 
			
		||||
						step="0.5"
 | 
			
		||||
						bind:value={options.top_k}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.top_k}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="100"
 | 
			
		||||
						step="0.5"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Top P</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.top_p = options.top_p === '' ? 0.9 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.top_p === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.top_p !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="1"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
						bind:value={options.top_p}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.top_p}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="1"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Repeat Penalty</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.repeat_penalty = options.repeat_penalty === '' ? 1.1 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.repeat_penalty === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.repeat_penalty !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="2"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
						bind:value={options.repeat_penalty}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.repeat_penalty}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="2"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Repeat Last N</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.repeat_last_n = options.repeat_last_n === '' ? 64 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.repeat_last_n === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.repeat_last_n !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="-1"
 | 
			
		||||
						max="128"
 | 
			
		||||
						step="1"
 | 
			
		||||
						bind:value={options.repeat_last_n}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.repeat_last_n}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="-1"
 | 
			
		||||
						max="128"
 | 
			
		||||
						step="1"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Tfs Z</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.tfs_z = options.tfs_z === '' ? 1 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.tfs_z === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.tfs_z !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="2"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
						bind:value={options.tfs_z}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.tfs_z}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="2"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Context Length</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.num_ctx = options.num_ctx === '' ? 2048 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.num_ctx === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.num_ctx !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="1"
 | 
			
		||||
						max="16000"
 | 
			
		||||
						step="1"
 | 
			
		||||
						bind:value={options.num_ctx}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="">
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.num_ctx}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="1"
 | 
			
		||||
						max="16000"
 | 
			
		||||
						step="1"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Max Tokens</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.num_predict = options.num_predict === '' ? 128 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.num_predict === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.num_predict !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="-2"
 | 
			
		||||
						max="16000"
 | 
			
		||||
						step="1"
 | 
			
		||||
						bind:value={options.num_predict}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="">
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.num_predict}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="-2"
 | 
			
		||||
						max="16000"
 | 
			
		||||
						step="1"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
				dispatch('save');
 | 
			
		||||
			}}
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										556
									
								
								src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										556
									
								
								src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,556 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	export let options = {
 | 
			
		||||
		// Advanced
 | 
			
		||||
		seed: 0,
 | 
			
		||||
		stop: '',
 | 
			
		||||
		temperature: '',
 | 
			
		||||
		repeat_penalty: '',
 | 
			
		||||
		repeat_last_n: '',
 | 
			
		||||
		mirostat: '',
 | 
			
		||||
		mirostat_eta: '',
 | 
			
		||||
		mirostat_tau: '',
 | 
			
		||||
		top_k: '',
 | 
			
		||||
		top_p: '',
 | 
			
		||||
		tfs_z: '',
 | 
			
		||||
		num_ctx: '',
 | 
			
		||||
		num_predict: ''
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class=" space-y-3 text-xs">
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
			<div class=" w-20 text-xs font-medium self-center">Seed</div>
 | 
			
		||||
			<div class=" flex-1 self-center">
 | 
			
		||||
				<input
 | 
			
		||||
					class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
 | 
			
		||||
					type="number"
 | 
			
		||||
					placeholder="Enter Seed"
 | 
			
		||||
					bind:value={options.seed}
 | 
			
		||||
					autocomplete="off"
 | 
			
		||||
					min="0"
 | 
			
		||||
				/>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
			<div class=" w-20 text-xs font-medium self-center">Stop Sequence</div>
 | 
			
		||||
			<div class=" flex-1 self-center">
 | 
			
		||||
				<input
 | 
			
		||||
					class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
 | 
			
		||||
					type="text"
 | 
			
		||||
					placeholder="Enter Stop Sequence"
 | 
			
		||||
					bind:value={options.stop}
 | 
			
		||||
					autocomplete="off"
 | 
			
		||||
				/>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Temperature</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.temperature = options.temperature === '' ? 0.8 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.temperature === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.temperature !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="1"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
						bind:value={options.temperature}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.temperature}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="1"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Mirostat</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.mirostat = options.mirostat === '' ? 0 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.mirostat === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.mirostat !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="2"
 | 
			
		||||
						step="1"
 | 
			
		||||
						bind:value={options.mirostat}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.mirostat}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="2"
 | 
			
		||||
						step="1"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Mirostat Eta</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.mirostat_eta = options.mirostat_eta === '' ? 0.1 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.mirostat_eta === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.mirostat_eta !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="1"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
						bind:value={options.mirostat_eta}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.mirostat_eta}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="1"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Mirostat Tau</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.mirostat_tau = options.mirostat_tau === '' ? 5.0 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.mirostat_tau === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.mirostat_tau !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="10"
 | 
			
		||||
						step="0.5"
 | 
			
		||||
						bind:value={options.mirostat_tau}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.mirostat_tau}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="10"
 | 
			
		||||
						step="0.5"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Top K</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.top_k = options.top_k === '' ? 40 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.top_k === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.top_k !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="100"
 | 
			
		||||
						step="0.5"
 | 
			
		||||
						bind:value={options.top_k}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.top_k}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="100"
 | 
			
		||||
						step="0.5"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Top P</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.top_p = options.top_p === '' ? 0.9 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.top_p === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.top_p !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="1"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
						bind:value={options.top_p}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.top_p}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="1"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Repeat Penalty</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.repeat_penalty = options.repeat_penalty === '' ? 1.1 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.repeat_penalty === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.repeat_penalty !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="2"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
						bind:value={options.repeat_penalty}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.repeat_penalty}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="2"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Repeat Last N</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.repeat_last_n = options.repeat_last_n === '' ? 64 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.repeat_last_n === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.repeat_last_n !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="-1"
 | 
			
		||||
						max="128"
 | 
			
		||||
						step="1"
 | 
			
		||||
						bind:value={options.repeat_last_n}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.repeat_last_n}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="-1"
 | 
			
		||||
						max="128"
 | 
			
		||||
						step="1"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Tfs Z</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.tfs_z = options.tfs_z === '' ? 1 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.tfs_z === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.tfs_z !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="2"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
						bind:value={options.tfs_z}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.tfs_z}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="0"
 | 
			
		||||
						max="2"
 | 
			
		||||
						step="0.05"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Context Length</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.num_ctx = options.num_ctx === '' ? 2048 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.num_ctx === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.num_ctx !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="1"
 | 
			
		||||
						max="16000"
 | 
			
		||||
						step="1"
 | 
			
		||||
						bind:value={options.num_ctx}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="">
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.num_ctx}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="1"
 | 
			
		||||
						max="16000"
 | 
			
		||||
						step="1"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class=" py-0.5 w-full justify-between">
 | 
			
		||||
		<div class="flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Max Tokens</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					options.num_predict = options.num_predict === '' ? 128 : '';
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.num_predict === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if options.num_predict !== ''}
 | 
			
		||||
			<div class="flex mt-0.5 space-x-2">
 | 
			
		||||
				<div class=" flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						id="steps-range"
 | 
			
		||||
						type="range"
 | 
			
		||||
						min="-2"
 | 
			
		||||
						max="16000"
 | 
			
		||||
						step="1"
 | 
			
		||||
						bind:value={options.num_predict}
 | 
			
		||||
						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="">
 | 
			
		||||
					<input
 | 
			
		||||
						bind:value={options.num_predict}
 | 
			
		||||
						type="number"
 | 
			
		||||
						class=" bg-transparent text-center w-14"
 | 
			
		||||
						min="-2"
 | 
			
		||||
						max="16000"
 | 
			
		||||
						step="1"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										353
									
								
								src/lib/components/chat/Settings/Chats.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										353
									
								
								src/lib/components/chat/Settings/Chats.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,353 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import fileSaver from 'file-saver';
 | 
			
		||||
	const { saveAs } = fileSaver;
 | 
			
		||||
 | 
			
		||||
	import { resetVectorDB } from '$lib/apis/rag';
 | 
			
		||||
	import { chats, user } from '$lib/stores';
 | 
			
		||||
 | 
			
		||||
	import {
 | 
			
		||||
		createNewChat,
 | 
			
		||||
		deleteAllChats,
 | 
			
		||||
		getAllChats,
 | 
			
		||||
		getAllUserChats,
 | 
			
		||||
		getChatList
 | 
			
		||||
	} from '$lib/apis/chats';
 | 
			
		||||
	import { getImportOrigin, convertOpenAIChats } from '$lib/utils';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { goto } from '$app/navigation';
 | 
			
		||||
	import toast from 'svelte-french-toast';
 | 
			
		||||
 | 
			
		||||
	export let saveSettings: Function;
 | 
			
		||||
	// Chats
 | 
			
		||||
	let saveChatHistory = true;
 | 
			
		||||
	let importFiles;
 | 
			
		||||
	let showDeleteConfirm = false;
 | 
			
		||||
 | 
			
		||||
	$: if (importFiles) {
 | 
			
		||||
		console.log(importFiles);
 | 
			
		||||
 | 
			
		||||
		let reader = new FileReader();
 | 
			
		||||
		reader.onload = (event) => {
 | 
			
		||||
			let chats = JSON.parse(event.target.result);
 | 
			
		||||
			console.log(chats);
 | 
			
		||||
			if (getImportOrigin(chats) == 'openai') {
 | 
			
		||||
				try {
 | 
			
		||||
					chats = convertOpenAIChats(chats);
 | 
			
		||||
				} catch (error) {
 | 
			
		||||
					console.log('Unable to import chats:', error);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			importChats(chats);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		if (importFiles.length > 0) {
 | 
			
		||||
			reader.readAsText(importFiles[0]);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const importChats = async (_chats) => {
 | 
			
		||||
		for (const chat of _chats) {
 | 
			
		||||
			console.log(chat);
 | 
			
		||||
 | 
			
		||||
			if (chat.chat) {
 | 
			
		||||
				await createNewChat(localStorage.token, chat.chat);
 | 
			
		||||
			} else {
 | 
			
		||||
				await createNewChat(localStorage.token, chat);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await chats.set(await getChatList(localStorage.token));
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const exportChats = async () => {
 | 
			
		||||
		let blob = new Blob([JSON.stringify(await getAllChats(localStorage.token))], {
 | 
			
		||||
			type: 'application/json'
 | 
			
		||||
		});
 | 
			
		||||
		saveAs(blob, `chat-export-${Date.now()}.json`);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const exportAllUserChats = async () => {
 | 
			
		||||
		let blob = new Blob([JSON.stringify(await getAllUserChats(localStorage.token))], {
 | 
			
		||||
			type: 'application/json'
 | 
			
		||||
		});
 | 
			
		||||
		saveAs(blob, `all-chats-export-${Date.now()}.json`);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const deleteChats = async () => {
 | 
			
		||||
		await goto('/');
 | 
			
		||||
		await deleteAllChats(localStorage.token);
 | 
			
		||||
		await chats.set(await getChatList(localStorage.token));
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const toggleSaveChatHistory = async () => {
 | 
			
		||||
		saveChatHistory = !saveChatHistory;
 | 
			
		||||
		console.log(saveChatHistory);
 | 
			
		||||
 | 
			
		||||
		if (saveChatHistory === false) {
 | 
			
		||||
			await goto('/');
 | 
			
		||||
		}
 | 
			
		||||
		saveSettings({ saveChatHistory: saveChatHistory });
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
 | 
			
		||||
 | 
			
		||||
		saveChatHistory = settings.saveChatHistory ?? true;
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="flex flex-col h-full justify-between space-y-3 text-sm">
 | 
			
		||||
	<div class=" space-y-2">
 | 
			
		||||
		<div
 | 
			
		||||
			class="flex flex-col justify-between rounded-md items-center py-2 px-3.5 w-full transition"
 | 
			
		||||
		>
 | 
			
		||||
			<div class="flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Chat History</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
					type="button"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						toggleSaveChatHistory();
 | 
			
		||||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					{#if saveChatHistory === true}
 | 
			
		||||
						<svg
 | 
			
		||||
							xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
							viewBox="0 0 16 16"
 | 
			
		||||
							fill="currentColor"
 | 
			
		||||
							class="w-4 h-4"
 | 
			
		||||
						>
 | 
			
		||||
							<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" />
 | 
			
		||||
							<path
 | 
			
		||||
								fill-rule="evenodd"
 | 
			
		||||
								d="M1.38 8.28a.87.87 0 0 1 0-.566 7.003 7.003 0 0 1 13.238.006.87.87 0 0 1 0 .566A7.003 7.003 0 0 1 1.379 8.28ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
 | 
			
		||||
								clip-rule="evenodd"
 | 
			
		||||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
 | 
			
		||||
						<span class="ml-2 self-center"> On </span>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<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="M3.28 2.22a.75.75 0 0 0-1.06 1.06l10.5 10.5a.75.75 0 1 0 1.06-1.06l-1.322-1.323a7.012 7.012 0 0 0 2.16-3.11.87.87 0 0 0 0-.567A7.003 7.003 0 0 0 4.82 3.76l-1.54-1.54Zm3.196 3.195 1.135 1.136A1.502 1.502 0 0 1 9.45 8.389l1.136 1.135a3 3 0 0 0-4.109-4.109Z"
 | 
			
		||||
								clip-rule="evenodd"
 | 
			
		||||
							/>
 | 
			
		||||
							<path
 | 
			
		||||
								d="m7.812 10.994 1.816 1.816A7.003 7.003 0 0 1 1.38 8.28a.87.87 0 0 1 0-.566 6.985 6.985 0 0 1 1.113-2.039l2.513 2.513a3 3 0 0 0 2.806 2.806Z"
 | 
			
		||||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
 | 
			
		||||
						<span class="ml-2 self-center">Off</span>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="text-xs text-left w-full font-medium mt-0.5">
 | 
			
		||||
				This setting does not sync across browsers or devices.
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div class="flex flex-col">
 | 
			
		||||
			<input id="chat-import-input" bind:files={importFiles} type="file" accept=".json" hidden />
 | 
			
		||||
			<button
 | 
			
		||||
				class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					document.getElementById('chat-import-input').click();
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				<div class=" self-center mr-3">
 | 
			
		||||
					<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="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
 | 
			
		||||
							clip-rule="evenodd"
 | 
			
		||||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Import Chats</div>
 | 
			
		||||
			</button>
 | 
			
		||||
			<button
 | 
			
		||||
				class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					exportChats();
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				<div class=" self-center mr-3">
 | 
			
		||||
					<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="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
 | 
			
		||||
							clip-rule="evenodd"
 | 
			
		||||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Export Chats</div>
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		{#if showDeleteConfirm}
 | 
			
		||||
			<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
 | 
			
		||||
				<div class="flex items-center space-x-3">
 | 
			
		||||
					<svg
 | 
			
		||||
						xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
						viewBox="0 0 16 16"
 | 
			
		||||
						fill="currentColor"
 | 
			
		||||
						class="w-4 h-4"
 | 
			
		||||
					>
 | 
			
		||||
						<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
 | 
			
		||||
						<path
 | 
			
		||||
							fill-rule="evenodd"
 | 
			
		||||
							d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM5.72 7.47a.75.75 0 0 1 1.06 0L8 8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 9.75l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 10.81l-1.22 1.22a.75.75 0 0 1-1.06-1.06l1.22-1.22-1.22-1.22a.75.75 0 0 1 0-1.06Z"
 | 
			
		||||
							clip-rule="evenodd"
 | 
			
		||||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
					<span>Are you sure?</span>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="flex space-x-1.5 items-center">
 | 
			
		||||
					<button
 | 
			
		||||
						class="hover:text-white transition"
 | 
			
		||||
						on:click={() => {
 | 
			
		||||
							deleteChats();
 | 
			
		||||
							showDeleteConfirm = false;
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						<svg
 | 
			
		||||
							xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
							viewBox="0 0 20 20"
 | 
			
		||||
							fill="currentColor"
 | 
			
		||||
							class="w-4 h-4"
 | 
			
		||||
						>
 | 
			
		||||
							<path
 | 
			
		||||
								fill-rule="evenodd"
 | 
			
		||||
								d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
 | 
			
		||||
								clip-rule="evenodd"
 | 
			
		||||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</button>
 | 
			
		||||
					<button
 | 
			
		||||
						class="hover:text-white transition"
 | 
			
		||||
						on:click={() => {
 | 
			
		||||
							showDeleteConfirm = false;
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						<svg
 | 
			
		||||
							xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
							viewBox="0 0 20 20"
 | 
			
		||||
							fill="currentColor"
 | 
			
		||||
							class="w-4 h-4"
 | 
			
		||||
						>
 | 
			
		||||
							<path
 | 
			
		||||
								d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
 | 
			
		||||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		{:else}
 | 
			
		||||
			<button
 | 
			
		||||
				class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					showDeleteConfirm = true;
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				<div class=" self-center mr-3">
 | 
			
		||||
					<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="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm7 7a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1 0-1.5h4.5A.75.75 0 0 1 11 9Z"
 | 
			
		||||
							clip-rule="evenodd"
 | 
			
		||||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Delete Chats</div>
 | 
			
		||||
			</button>
 | 
			
		||||
		{/if}
 | 
			
		||||
 | 
			
		||||
		{#if $user?.role === 'admin'}
 | 
			
		||||
			<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					exportAllUserChats();
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				<div class=" self-center mr-3">
 | 
			
		||||
					<svg
 | 
			
		||||
						xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
						viewBox="0 0 16 16"
 | 
			
		||||
						fill="currentColor"
 | 
			
		||||
						class="w-4 h-4"
 | 
			
		||||
					>
 | 
			
		||||
						<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
 | 
			
		||||
						<path
 | 
			
		||||
							fill-rule="evenodd"
 | 
			
		||||
							d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.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.06l-1.22 1.22V7.75Z"
 | 
			
		||||
							clip-rule="evenodd"
 | 
			
		||||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Export All Chats (All Users)</div>
 | 
			
		||||
			</button>
 | 
			
		||||
 | 
			
		||||
			<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					const res = resetVectorDB(localStorage.token).catch((error) => {
 | 
			
		||||
						toast.error(error);
 | 
			
		||||
						return null;
 | 
			
		||||
					});
 | 
			
		||||
 | 
			
		||||
					if (res) {
 | 
			
		||||
						toast.success('Success');
 | 
			
		||||
					}
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				<div class=" self-center mr-3">
 | 
			
		||||
					<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="M3.5 2A1.5 1.5 0 0 0 2 3.5v9A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 12.5 4H9.621a1.5 1.5 0 0 1-1.06-.44L7.439 2.44A1.5 1.5 0 0 0 6.38 2H3.5Zm6.75 7.75a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0 0 1.5h4.5Z"
 | 
			
		||||
							clip-rule="evenodd"
 | 
			
		||||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Reset Vector Storage</div>
 | 
			
		||||
			</button>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										86
									
								
								src/lib/components/chat/Settings/External.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/lib/components/chat/Settings/External.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,86 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { getOpenAIKey, getOpenAIUrl, updateOpenAIKey, updateOpenAIUrl } from '$lib/apis/openai';
 | 
			
		||||
	import { models, user } from '$lib/stores';
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	export let getModels: Function;
 | 
			
		||||
 | 
			
		||||
	// External
 | 
			
		||||
	let OPENAI_API_KEY = '';
 | 
			
		||||
	let OPENAI_API_BASE_URL = '';
 | 
			
		||||
 | 
			
		||||
	const updateOpenAIHandler = async () => {
 | 
			
		||||
		OPENAI_API_BASE_URL = await updateOpenAIUrl(localStorage.token, OPENAI_API_BASE_URL);
 | 
			
		||||
		OPENAI_API_KEY = await updateOpenAIKey(localStorage.token, OPENAI_API_KEY);
 | 
			
		||||
 | 
			
		||||
		await models.set(await getModels());
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		if ($user.role === 'admin') {
 | 
			
		||||
			OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token);
 | 
			
		||||
			OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<form
 | 
			
		||||
	class="flex flex-col h-full justify-between space-y-3 text-sm"
 | 
			
		||||
	on:submit|preventDefault={() => {
 | 
			
		||||
		updateOpenAIHandler();
 | 
			
		||||
		dispatch('save');
 | 
			
		||||
 | 
			
		||||
		// saveSettings({
 | 
			
		||||
		// 	OPENAI_API_KEY: OPENAI_API_KEY !== '' ? OPENAI_API_KEY : undefined,
 | 
			
		||||
		// 	OPENAI_API_BASE_URL: OPENAI_API_BASE_URL !== '' ? OPENAI_API_BASE_URL : undefined
 | 
			
		||||
		// });
 | 
			
		||||
	}}
 | 
			
		||||
>
 | 
			
		||||
	<div class=" space-y-3">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">OpenAI API Key</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
						placeholder="Enter OpenAI API Key"
 | 
			
		||||
						bind:value={OPENAI_API_KEY}
 | 
			
		||||
						autocomplete="off"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
				Adds optional support for online models.
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">OpenAI API Base URL</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
						placeholder="Enter OpenAI API Key"
 | 
			
		||||
						bind:value={OPENAI_API_BASE_URL}
 | 
			
		||||
						autocomplete="off"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
				WebUI will make requests to <span class=" text-gray-200">'{OPENAI_API_BASE_URL}/chat'</span>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="flex justify-end pt-3 text-sm font-medium">
 | 
			
		||||
		<button
 | 
			
		||||
			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
			type="submit"
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</form>
 | 
			
		||||
							
								
								
									
										224
									
								
								src/lib/components/chat/Settings/General.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								src/lib/components/chat/Settings/General.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,224 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import toast from 'svelte-french-toast';
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	import { getOllamaAPIUrl, updateOllamaAPIUrl } from '$lib/apis/ollama';
 | 
			
		||||
	import { models, user } from '$lib/stores';
 | 
			
		||||
 | 
			
		||||
	export let saveSettings: Function;
 | 
			
		||||
	export let getModels: Function;
 | 
			
		||||
 | 
			
		||||
	// General
 | 
			
		||||
	let API_BASE_URL = '';
 | 
			
		||||
	let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light'];
 | 
			
		||||
	let theme = 'dark';
 | 
			
		||||
	let notificationEnabled = false;
 | 
			
		||||
	let system = '';
 | 
			
		||||
 | 
			
		||||
	const toggleTheme = async () => {
 | 
			
		||||
		if (theme === 'dark') {
 | 
			
		||||
			theme = 'light';
 | 
			
		||||
		} else {
 | 
			
		||||
			theme = 'dark';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		localStorage.theme = theme;
 | 
			
		||||
 | 
			
		||||
		document.documentElement.classList.remove(theme === 'dark' ? 'light' : 'dark');
 | 
			
		||||
		document.documentElement.classList.add(theme);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const toggleNotification = async () => {
 | 
			
		||||
		const permission = await Notification.requestPermission();
 | 
			
		||||
 | 
			
		||||
		if (permission === 'granted') {
 | 
			
		||||
			notificationEnabled = !notificationEnabled;
 | 
			
		||||
			saveSettings({ notificationEnabled: notificationEnabled });
 | 
			
		||||
		} else {
 | 
			
		||||
			toast.error(
 | 
			
		||||
				'Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.'
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const updateOllamaAPIUrlHandler = async () => {
 | 
			
		||||
		API_BASE_URL = await updateOllamaAPIUrl(localStorage.token, API_BASE_URL);
 | 
			
		||||
		const _models = await getModels('ollama');
 | 
			
		||||
 | 
			
		||||
		if (_models.length > 0) {
 | 
			
		||||
			toast.success('Server connection verified');
 | 
			
		||||
			await models.set(_models);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		if ($user.role === 'admin') {
 | 
			
		||||
			API_BASE_URL = await getOllamaAPIUrl(localStorage.token);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
 | 
			
		||||
 | 
			
		||||
		theme = localStorage.theme ?? 'dark';
 | 
			
		||||
		notificationEnabled = settings.notificationEnabled ?? false;
 | 
			
		||||
		system = settings.system ?? '';
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="flex flex-col space-y-3">
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" mb-1 text-sm font-medium">WebUI Settings</div>
 | 
			
		||||
 | 
			
		||||
		<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
			<div class=" self-center text-xs font-medium">Theme</div>
 | 
			
		||||
			<div class="flex items-center relative">
 | 
			
		||||
				<div class=" absolute right-16">
 | 
			
		||||
					{#if theme === 'dark'}
 | 
			
		||||
						<svg
 | 
			
		||||
							xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
							viewBox="0 0 20 20"
 | 
			
		||||
							fill="currentColor"
 | 
			
		||||
							class="w-4 h-4"
 | 
			
		||||
						>
 | 
			
		||||
							<path
 | 
			
		||||
								fill-rule="evenodd"
 | 
			
		||||
								d="M7.455 2.004a.75.75 0 01.26.77 7 7 0 009.958 7.967.75.75 0 011.067.853A8.5 8.5 0 116.647 1.921a.75.75 0 01.808.083z"
 | 
			
		||||
								clip-rule="evenodd"
 | 
			
		||||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					{:else if theme === 'light'}
 | 
			
		||||
						<svg
 | 
			
		||||
							xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
							viewBox="0 0 20 20"
 | 
			
		||||
							fill="currentColor"
 | 
			
		||||
							class="w-4 h-4 self-center"
 | 
			
		||||
						>
 | 
			
		||||
							<path
 | 
			
		||||
								d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
 | 
			
		||||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<select
 | 
			
		||||
					class="w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
 | 
			
		||||
					bind:value={theme}
 | 
			
		||||
					placeholder="Select a theme"
 | 
			
		||||
					on:change={(e) => {
 | 
			
		||||
						localStorage.theme = theme;
 | 
			
		||||
 | 
			
		||||
						themes
 | 
			
		||||
							.filter((e) => e !== theme)
 | 
			
		||||
							.forEach((e) => {
 | 
			
		||||
								e.split(' ').forEach((e) => {
 | 
			
		||||
									document.documentElement.classList.remove(e);
 | 
			
		||||
								});
 | 
			
		||||
							});
 | 
			
		||||
 | 
			
		||||
						theme.split(' ').forEach((e) => {
 | 
			
		||||
							document.documentElement.classList.add(e);
 | 
			
		||||
						});
 | 
			
		||||
 | 
			
		||||
						console.log(theme);
 | 
			
		||||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					<option value="dark">Dark</option>
 | 
			
		||||
					<option value="light">Light</option>
 | 
			
		||||
					<option value="rose-pine dark">Rosé Pine</option>
 | 
			
		||||
					<option value="rose-pine-dawn light">Rosé Pine Dawn</option>
 | 
			
		||||
				</select>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">Notification</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						toggleNotification();
 | 
			
		||||
					}}
 | 
			
		||||
					type="button"
 | 
			
		||||
				>
 | 
			
		||||
					{#if notificationEnabled === true}
 | 
			
		||||
						<span class="ml-2 self-center">On</span>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<span class="ml-2 self-center">Off</span>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	{#if $user.role === 'admin'}
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">Ollama API URL</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1 mr-2">
 | 
			
		||||
					<input
 | 
			
		||||
						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
						placeholder="Enter URL (e.g. http://localhost:11434/api)"
 | 
			
		||||
						bind:value={API_BASE_URL}
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<button
 | 
			
		||||
					class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 rounded transition"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						updateOllamaAPIUrlHandler();
 | 
			
		||||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					<svg
 | 
			
		||||
						xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
						viewBox="0 0 20 20"
 | 
			
		||||
						fill="currentColor"
 | 
			
		||||
						class="w-4 h-4"
 | 
			
		||||
					>
 | 
			
		||||
						<path
 | 
			
		||||
							fill-rule="evenodd"
 | 
			
		||||
							d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
 | 
			
		||||
							clip-rule="evenodd"
 | 
			
		||||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
				Trouble accessing Ollama?
 | 
			
		||||
				<a
 | 
			
		||||
					class=" text-gray-300 font-medium"
 | 
			
		||||
					href="https://github.com/ollama-webui/ollama-webui#troubleshooting"
 | 
			
		||||
					target="_blank"
 | 
			
		||||
				>
 | 
			
		||||
					Click here for help.
 | 
			
		||||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	{/if}
 | 
			
		||||
 | 
			
		||||
	<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" mb-2.5 text-sm font-medium">System Prompt</div>
 | 
			
		||||
		<textarea
 | 
			
		||||
			bind:value={system}
 | 
			
		||||
			class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
 | 
			
		||||
			rows="4"
 | 
			
		||||
		/>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="flex justify-end pt-3 text-sm font-medium">
 | 
			
		||||
		<button
 | 
			
		||||
			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
			on:click={() => {
 | 
			
		||||
				saveSettings({
 | 
			
		||||
					system: system !== '' ? system : undefined
 | 
			
		||||
				});
 | 
			
		||||
				dispatch('save');
 | 
			
		||||
			}}
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										118
									
								
								src/lib/components/chat/Settings/Interface.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/lib/components/chat/Settings/Interface.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,118 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { getBackendConfig } from '$lib/apis';
 | 
			
		||||
	import { setDefaultPromptSuggestions } from '$lib/apis/configs';
 | 
			
		||||
	import { config, user } from '$lib/stores';
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	// Interface
 | 
			
		||||
	let promptSuggestions = [];
 | 
			
		||||
 | 
			
		||||
	const updateInterfaceHandler = async () => {
 | 
			
		||||
		promptSuggestions = await setDefaultPromptSuggestions(localStorage.token, promptSuggestions);
 | 
			
		||||
		await config.set(await getBackendConfig());
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		if ($user.role === 'admin') {
 | 
			
		||||
			promptSuggestions = $config?.default_prompt_suggestions;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<form
 | 
			
		||||
	class="flex flex-col h-full justify-between space-y-3 text-sm"
 | 
			
		||||
	on:submit|preventDefault={() => {
 | 
			
		||||
		updateInterfaceHandler();
 | 
			
		||||
		dispatch('save');
 | 
			
		||||
	}}
 | 
			
		||||
>
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 | 
			
		||||
		<div class="flex w-full justify-between mb-2">
 | 
			
		||||
			<div class=" self-center text-sm font-semibold">Default Prompt Suggestions</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
 | 
			
		||||
						promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
 | 
			
		||||
					}
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				<svg
 | 
			
		||||
					xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
					viewBox="0 0 20 20"
 | 
			
		||||
					fill="currentColor"
 | 
			
		||||
					class="w-4 h-4"
 | 
			
		||||
				>
 | 
			
		||||
					<path
 | 
			
		||||
						d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
 | 
			
		||||
					/>
 | 
			
		||||
				</svg>
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="flex flex-col space-y-1">
 | 
			
		||||
			{#each promptSuggestions as prompt, promptIdx}
 | 
			
		||||
				<div class=" flex border dark:border-gray-600 rounded-lg">
 | 
			
		||||
					<div class="flex flex-col flex-1">
 | 
			
		||||
						<div class="flex border-b dark:border-gray-600 w-full">
 | 
			
		||||
							<input
 | 
			
		||||
								class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
 | 
			
		||||
								placeholder="Title (e.g. Tell me a fun fact)"
 | 
			
		||||
								bind:value={prompt.title[0]}
 | 
			
		||||
							/>
 | 
			
		||||
 | 
			
		||||
							<input
 | 
			
		||||
								class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
 | 
			
		||||
								placeholder="Subtitle (e.g. about the Roman Empire)"
 | 
			
		||||
								bind:value={prompt.title[1]}
 | 
			
		||||
							/>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<input
 | 
			
		||||
							class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
 | 
			
		||||
							placeholder="Prompt (e.g. Tell me a fun fact about the Roman Empire)"
 | 
			
		||||
							bind:value={prompt.content}
 | 
			
		||||
						/>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
						class="px-2"
 | 
			
		||||
						type="button"
 | 
			
		||||
						on:click={() => {
 | 
			
		||||
							promptSuggestions.splice(promptIdx, 1);
 | 
			
		||||
							promptSuggestions = promptSuggestions;
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						<svg
 | 
			
		||||
							xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
							viewBox="0 0 20 20"
 | 
			
		||||
							fill="currentColor"
 | 
			
		||||
							class="w-4 h-4"
 | 
			
		||||
						>
 | 
			
		||||
							<path
 | 
			
		||||
								d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
 | 
			
		||||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
			{/each}
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{#if promptSuggestions.length > 0}
 | 
			
		||||
			<div class="text-xs text-left w-full mt-2">
 | 
			
		||||
				Adjusting these settings will apply changes universally to all users.
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="flex justify-end pt-3 text-sm font-medium">
 | 
			
		||||
		<button
 | 
			
		||||
			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
			type="submit"
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</form>
 | 
			
		||||
							
								
								
									
										596
									
								
								src/lib/components/chat/Settings/Models.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										596
									
								
								src/lib/components/chat/Settings/Models.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,596 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import queue from 'async/queue';
 | 
			
		||||
	import toast from 'svelte-french-toast';
 | 
			
		||||
 | 
			
		||||
	import { createModel, deleteModel, pullModel } from '$lib/apis/ollama';
 | 
			
		||||
	import { WEBUI_API_BASE_URL } from '$lib/constants';
 | 
			
		||||
	import { models, user } from '$lib/stores';
 | 
			
		||||
	import { splitStream } from '$lib/utils';
 | 
			
		||||
 | 
			
		||||
	export let getModels: Function;
 | 
			
		||||
 | 
			
		||||
	// Models
 | 
			
		||||
	const MAX_PARALLEL_DOWNLOADS = 3;
 | 
			
		||||
	const modelDownloadQueue = queue(
 | 
			
		||||
		(task: { modelName: string }, cb) =>
 | 
			
		||||
			pullModelHandlerProcessor({ modelName: task.modelName, callback: cb }),
 | 
			
		||||
		MAX_PARALLEL_DOWNLOADS
 | 
			
		||||
	);
 | 
			
		||||
	let modelDownloadStatus: Record<string, any> = {};
 | 
			
		||||
 | 
			
		||||
	let modelTransferring = false;
 | 
			
		||||
	let modelTag = '';
 | 
			
		||||
	let digest = '';
 | 
			
		||||
	let pullProgress = null;
 | 
			
		||||
 | 
			
		||||
	let modelUploadMode = 'file';
 | 
			
		||||
	let modelInputFile = '';
 | 
			
		||||
	let modelFileUrl = '';
 | 
			
		||||
	let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSSISTANT:"`;
 | 
			
		||||
	let modelFileDigest = '';
 | 
			
		||||
	let uploadProgress = null;
 | 
			
		||||
 | 
			
		||||
	let deleteModelTag = '';
 | 
			
		||||
 | 
			
		||||
	const pullModelHandler = async () => {
 | 
			
		||||
		const sanitizedModelTag = modelTag.trim();
 | 
			
		||||
		if (modelDownloadStatus[sanitizedModelTag]) {
 | 
			
		||||
			toast.error(`Model '${sanitizedModelTag}' is already in queue for downloading.`);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		if (Object.keys(modelDownloadStatus).length === 3) {
 | 
			
		||||
			toast.error('Maximum of 3 models can be downloaded simultaneously. Please try again later.');
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		modelTransferring = true;
 | 
			
		||||
 | 
			
		||||
		modelDownloadQueue.push(
 | 
			
		||||
			{ modelName: sanitizedModelTag },
 | 
			
		||||
			async (data: { modelName: string; success: boolean; error?: Error }) => {
 | 
			
		||||
				const { modelName } = data;
 | 
			
		||||
				// Remove the downloaded model
 | 
			
		||||
				delete modelDownloadStatus[modelName];
 | 
			
		||||
 | 
			
		||||
				console.log(data);
 | 
			
		||||
 | 
			
		||||
				if (!data.success) {
 | 
			
		||||
					toast.error(data.error);
 | 
			
		||||
				} else {
 | 
			
		||||
					toast.success(`Model '${modelName}' has been successfully downloaded.`);
 | 
			
		||||
 | 
			
		||||
					const notification = new Notification(`Ollama`, {
 | 
			
		||||
						body: `Model '${modelName}' has been successfully downloaded.`,
 | 
			
		||||
						icon: '/favicon.png'
 | 
			
		||||
					});
 | 
			
		||||
 | 
			
		||||
					models.set(await getModels());
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		modelTag = '';
 | 
			
		||||
		modelTransferring = false;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const uploadModelHandler = async () => {
 | 
			
		||||
		modelTransferring = true;
 | 
			
		||||
		uploadProgress = 0;
 | 
			
		||||
 | 
			
		||||
		let uploaded = false;
 | 
			
		||||
		let fileResponse = null;
 | 
			
		||||
		let name = '';
 | 
			
		||||
 | 
			
		||||
		if (modelUploadMode === 'file') {
 | 
			
		||||
			const file = modelInputFile[0];
 | 
			
		||||
			const formData = new FormData();
 | 
			
		||||
			formData.append('file', file);
 | 
			
		||||
 | 
			
		||||
			fileResponse = await fetch(`${WEBUI_API_BASE_URL}/utils/upload`, {
 | 
			
		||||
				method: 'POST',
 | 
			
		||||
				headers: {
 | 
			
		||||
					...($user && { Authorization: `Bearer ${localStorage.token}` })
 | 
			
		||||
				},
 | 
			
		||||
				body: formData
 | 
			
		||||
			}).catch((error) => {
 | 
			
		||||
				console.log(error);
 | 
			
		||||
				return null;
 | 
			
		||||
			});
 | 
			
		||||
		} else {
 | 
			
		||||
			fileResponse = await fetch(`${WEBUI_API_BASE_URL}/utils/download?url=${modelFileUrl}`, {
 | 
			
		||||
				method: 'GET',
 | 
			
		||||
				headers: {
 | 
			
		||||
					...($user && { Authorization: `Bearer ${localStorage.token}` })
 | 
			
		||||
				}
 | 
			
		||||
			}).catch((error) => {
 | 
			
		||||
				console.log(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) {
 | 
			
		||||
								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);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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 = '';
 | 
			
		||||
		modelInputFile = '';
 | 
			
		||||
		modelTransferring = false;
 | 
			
		||||
		uploadProgress = null;
 | 
			
		||||
 | 
			
		||||
		models.set(await getModels());
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const deleteModelHandler = async () => {
 | 
			
		||||
		const res = await deleteModel(localStorage.token, deleteModelTag).catch((error) => {
 | 
			
		||||
			toast.error(error);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (res) {
 | 
			
		||||
			toast.success(`Deleted ${deleteModelTag}`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		deleteModelTag = '';
 | 
			
		||||
		models.set(await getModels());
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const pullModelHandlerProcessor = async (opts: { modelName: string; callback: Function }) => {
 | 
			
		||||
		const res = await pullModel(localStorage.token, opts.modelName).catch((error) => {
 | 
			
		||||
			opts.callback({ success: false, error, modelName: opts.modelName });
 | 
			
		||||
			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);
 | 
			
		||||
							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;
 | 
			
		||||
									}
 | 
			
		||||
									modelDownloadStatus[opts.modelName] = {
 | 
			
		||||
										pullProgress: downloadProgress,
 | 
			
		||||
										digest: data.digest
 | 
			
		||||
									};
 | 
			
		||||
								} else {
 | 
			
		||||
									toast.success(data.status);
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				} catch (error) {
 | 
			
		||||
					console.log(error);
 | 
			
		||||
					if (typeof error !== 'string') {
 | 
			
		||||
						error = error.message;
 | 
			
		||||
					}
 | 
			
		||||
					opts.callback({ success: false, error, modelName: opts.modelName });
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			opts.callback({ success: true, modelName: opts.modelName });
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="flex flex-col h-full justify-between text-sm">
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll h-80">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">Pull a model from Ollama.ai</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1 mr-2">
 | 
			
		||||
					<input
 | 
			
		||||
						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
						placeholder="Enter model tag (e.g. mistral:7b)"
 | 
			
		||||
						bind:value={modelTag}
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<button
 | 
			
		||||
					class="px-3 text-gray-100 bg-emerald-600 hover:bg-emerald-700 disabled:bg-gray-700 disabled:cursor-not-allowed rounded 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">
 | 
			
		||||
				To access the available model names for downloading, <a
 | 
			
		||||
					class=" text-gray-500 dark:text-gray-300 font-medium"
 | 
			
		||||
					href="https://ollama.ai/library"
 | 
			
		||||
					target="_blank">click here.</a
 | 
			
		||||
				>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			{#if Object.keys(modelDownloadStatus).length > 0}
 | 
			
		||||
				{#each Object.keys(modelDownloadStatus) as model}
 | 
			
		||||
					<div class="flex flex-col">
 | 
			
		||||
						<div class="font-medium mb-1">{model}</div>
 | 
			
		||||
						<div class="">
 | 
			
		||||
							<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, modelDownloadStatus[model].pullProgress ?? 0)}%"
 | 
			
		||||
							>
 | 
			
		||||
								{modelDownloadStatus[model].pullProgress ?? 0}%
 | 
			
		||||
							</div>
 | 
			
		||||
							<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
 | 
			
		||||
								{modelDownloadStatus[model].digest}
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				{/each}
 | 
			
		||||
			{/if}
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">Delete a model</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1 mr-2">
 | 
			
		||||
					<select
 | 
			
		||||
						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
						bind:value={deleteModelTag}
 | 
			
		||||
						placeholder="Select a model"
 | 
			
		||||
					>
 | 
			
		||||
						{#if !deleteModelTag}
 | 
			
		||||
							<option value="" disabled selected>Select a model</option>
 | 
			
		||||
						{/if}
 | 
			
		||||
						{#each $models.filter((m) => m.size != null) as model}
 | 
			
		||||
							<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
 | 
			
		||||
								>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
 | 
			
		||||
							>
 | 
			
		||||
						{/each}
 | 
			
		||||
					</select>
 | 
			
		||||
				</div>
 | 
			
		||||
				<button
 | 
			
		||||
					class="px-3 bg-red-700 hover:bg-red-800 text-gray-100 rounded 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>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<form
 | 
			
		||||
			on:submit|preventDefault={() => {
 | 
			
		||||
				uploadModelHandler();
 | 
			
		||||
			}}
 | 
			
		||||
		>
 | 
			
		||||
			<div class=" mb-2 flex w-full justify-between">
 | 
			
		||||
				<div class="  text-sm font-medium">
 | 
			
		||||
					Upload a GGUF model <a
 | 
			
		||||
						class=" text-xs font-medium text-gray-500 underline"
 | 
			
		||||
						href="https://github.com/jmorganca/ollama/blob/main/README.md#import-from-gguf"
 | 
			
		||||
						target="_blank">(Experimental)</a
 | 
			
		||||
					>
 | 
			
		||||
				</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">File Mode</span>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<span class="ml-2 self-center">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"
 | 
			
		||||
								type="file"
 | 
			
		||||
								bind:files={modelInputFile}
 | 
			
		||||
								on:change={() => {
 | 
			
		||||
									console.log(modelInputFile);
 | 
			
		||||
								}}
 | 
			
		||||
								accept=".gguf"
 | 
			
		||||
								required
 | 
			
		||||
								hidden
 | 
			
		||||
							/>
 | 
			
		||||
 | 
			
		||||
							<button
 | 
			
		||||
								type="button"
 | 
			
		||||
								class="w-full rounded text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-800"
 | 
			
		||||
								on:click={() => {
 | 
			
		||||
									document.getElementById('model-upload-input').click();
 | 
			
		||||
								}}
 | 
			
		||||
							>
 | 
			
		||||
								{#if modelInputFile && modelInputFile.length > 0}
 | 
			
		||||
									{modelInputFile[0].name}
 | 
			
		||||
								{:else}
 | 
			
		||||
									Click here to select
 | 
			
		||||
								{/if}
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
 | 
			
		||||
							<input
 | 
			
		||||
								class="w-full rounded text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-800 outline-none {modelFileUrl !==
 | 
			
		||||
								''
 | 
			
		||||
									? 'mr-2'
 | 
			
		||||
									: ''}"
 | 
			
		||||
								type="url"
 | 
			
		||||
								required
 | 
			
		||||
								bind:value={modelFileUrl}
 | 
			
		||||
								placeholder="Type HuggingFace Resolve (Download) URL"
 | 
			
		||||
							/>
 | 
			
		||||
						</div>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
 | 
			
		||||
					<button
 | 
			
		||||
						class="px-3 text-gray-100 bg-emerald-600 hover:bg-emerald-700 disabled:bg-gray-700 disabled:cursor-not-allowed rounded 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">Modelfile Content</div>
 | 
			
		||||
						<textarea
 | 
			
		||||
							bind:value={modelFileContent}
 | 
			
		||||
							class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
 | 
			
		||||
							rows="6"
 | 
			
		||||
						/>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			{/if}
 | 
			
		||||
			<div class=" mt-1 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
				To access the GGUF models available for downloading, <a
 | 
			
		||||
					class=" text-gray-500 dark:text-gray-300 font-medium"
 | 
			
		||||
					href="https://huggingface.co/models?search=gguf"
 | 
			
		||||
					target="_blank">click here.</a
 | 
			
		||||
				>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			{#if uploadProgress !== null}
 | 
			
		||||
				<div class="mt-2">
 | 
			
		||||
					<div class=" mb-2 text-xs">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>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue