forked from open-webui/open-webui
		
	Merge pull request #650 from ollama-webui/settings-refac
refac: settings
This commit is contained in:
		
						commit
						3e1c7e4e06
					
				
					 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"> | <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 | 		// Advanced | ||||||
| 		seed: 0, | 		seed: 0, | ||||||
| 		stop: '', |  | ||||||
| 		temperature: '', | 		temperature: '', | ||||||
| 		repeat_penalty: '', | 		repeat_penalty: '', | ||||||
| 		repeat_last_n: '', | 		repeat_last_n: '', | ||||||
|  | @ -11,546 +18,101 @@ | ||||||
| 		mirostat_tau: '', | 		mirostat_tau: '', | ||||||
| 		top_k: '', | 		top_k: '', | ||||||
| 		top_p: '', | 		top_p: '', | ||||||
|  | 		stop: '', | ||||||
| 		tfs_z: '', | 		tfs_z: '', | ||||||
| 		num_ctx: '', | 		num_ctx: '', | ||||||
| 		num_predict: '' | 		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> | </script> | ||||||
| 
 | 
 | ||||||
| <div class=" space-y-3 text-xs"> | <div class="flex flex-col h-full justify-between text-sm"> | ||||||
| 	<div> | 	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80"> | ||||||
| 		<div class=" py-0.5 flex w-full justify-between"> | 		<div class=" text-sm font-medium">Parameters</div> | ||||||
| 			<div class=" w-20 text-xs font-medium self-center">Seed</div> | 
 | ||||||
| 			<div class=" flex-1 self-center"> | 		<AdvancedParams bind:options /> | ||||||
| 				<input | 		<hr class=" dark:border-gray-700" /> | ||||||
| 					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" | 		<div> | ||||||
| 					placeholder="Enter Seed" | 			<div class=" py-1 flex w-full justify-between"> | ||||||
| 					bind:value={options.seed} | 				<div class=" self-center text-sm font-medium">Request Mode</div> | ||||||
| 					autocomplete="off" | 
 | ||||||
| 					min="0" | 				<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> | 	</div> | ||||||
| 
 | 
 | ||||||
| 	<div> | 	<div class="flex justify-end pt-3 text-sm font-medium"> | ||||||
| 		<div class=" py-0.5 flex w-full justify-between"> | 		<button | ||||||
| 			<div class=" w-20 text-xs font-medium self-center">Stop Sequence</div> | 			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" | ||||||
| 			<div class=" flex-1 self-center"> | 			on:click={() => { | ||||||
| 				<input | 				saveSettings({ | ||||||
| 					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" | 					options: { | ||||||
| 					type="text" | 						seed: (options.seed !== 0 ? options.seed : undefined) ?? undefined, | ||||||
| 					placeholder="Enter Stop Sequence" | 						stop: options.stop !== '' ? options.stop.split(',').filter((e) => e) : undefined, | ||||||
| 					bind:value={options.stop} | 						temperature: options.temperature !== '' ? options.temperature : undefined, | ||||||
| 					autocomplete="off" | 						repeat_penalty: options.repeat_penalty !== '' ? options.repeat_penalty : undefined, | ||||||
| 				/> | 						repeat_last_n: options.repeat_last_n !== '' ? options.repeat_last_n : undefined, | ||||||
| 			</div> | 						mirostat: options.mirostat !== '' ? options.mirostat : undefined, | ||||||
| 		</div> | 						mirostat_eta: options.mirostat_eta !== '' ? options.mirostat_eta : undefined, | ||||||
| 	</div> | 						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"> | 				dispatch('save'); | ||||||
| 		<div class="flex w-full justify-between"> | 			}} | ||||||
| 			<div class=" self-center text-xs font-medium">Temperature</div> | 		> | ||||||
| 
 | 			Save | ||||||
| 			<button | 		</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> | ||||||
| </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
											
										
									
								
							|  | @ -4,7 +4,7 @@ | ||||||
| 	import { goto } from '$app/navigation'; | 	import { goto } from '$app/navigation'; | ||||||
| 	import { settings, user, config, modelfiles, models } from '$lib/stores'; | 	import { settings, user, config, modelfiles, models } from '$lib/stores'; | ||||||
| 
 | 
 | ||||||
| 	import Advanced from '$lib/components/chat/Settings/Advanced.svelte'; | 	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte'; | ||||||
| 	import { splitStream } from '$lib/utils'; | 	import { splitStream } from '$lib/utils'; | ||||||
| 	import { onMount, tick } from 'svelte'; | 	import { onMount, tick } from 'svelte'; | ||||||
| 	import { createModel } from '$lib/apis/ollama'; | 	import { createModel } from '$lib/apis/ollama'; | ||||||
|  | @ -552,7 +552,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, ''); | ||||||
| 								<div class=" text-xs font-semibold mb-2">Parameters</div> | 								<div class=" text-xs font-semibold mb-2">Parameters</div> | ||||||
| 
 | 
 | ||||||
| 								<div> | 								<div> | ||||||
| 									<Advanced bind:options /> | 									<AdvancedParams bind:options /> | ||||||
| 								</div> | 								</div> | ||||||
| 							</div> | 							</div> | ||||||
| 						{/if} | 						{/if} | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ | ||||||
| 	import { createModel } from '$lib/apis/ollama'; | 	import { createModel } from '$lib/apis/ollama'; | ||||||
| 	import { getModelfiles, updateModelfileByTagName } from '$lib/apis/modelfiles'; | 	import { getModelfiles, updateModelfileByTagName } from '$lib/apis/modelfiles'; | ||||||
| 
 | 
 | ||||||
| 	import Advanced from '$lib/components/chat/Settings/Advanced.svelte'; | 	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte'; | ||||||
| 
 | 
 | ||||||
| 	let loading = false; | 	let loading = false; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy Jaeryang Baek
						Timothy Jaeryang Baek