forked from open-webui/open-webui
		
	Merge branch 'dev' into feat/system-wide-theme
This commit is contained in:
		
						commit
						db0712aefd
					
				
					 90 changed files with 8000 additions and 1164 deletions
				
			
		| 
						 | 
				
			
			@ -1,8 +1,13 @@
 | 
			
		|||
<script>
 | 
			
		||||
	import { getContext } from 'svelte';
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="  text-center text-6xl mb-3">📄</div>
 | 
			
		||||
<div class="text-center dark:text-white text-2xl font-semibold z-50">Add Files</div>
 | 
			
		||||
<div class="text-center dark:text-white text-2xl font-semibold z-50">{$i18n.t('Add Files')}</div>
 | 
			
		||||
 | 
			
		||||
<slot
 | 
			
		||||
	><div class=" mt-2 text-center text-sm dark:text-gray-200 w-full">
 | 
			
		||||
		Drop any files here to add to the conversation
 | 
			
		||||
		{$i18n.t('Drop any files here to add to the conversation')}
 | 
			
		||||
	</div>
 | 
			
		||||
</slot>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
	import { Confetti } from 'svelte-confetti';
 | 
			
		||||
 | 
			
		||||
	import { WEBUI_NAME, config } from '$lib/stores';
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +9,8 @@
 | 
			
		|||
 | 
			
		||||
	import Modal from './common/Modal.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let show = false;
 | 
			
		||||
 | 
			
		||||
	let changelog = null;
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +25,8 @@
 | 
			
		|||
	<div class="px-5 py-4 dark:text-gray-300">
 | 
			
		||||
		<div class="flex justify-between items-start">
 | 
			
		||||
			<div class="text-xl font-bold">
 | 
			
		||||
				What’s New in {$WEBUI_NAME}
 | 
			
		||||
				{$i18n.t('What’s New in')}
 | 
			
		||||
				{$WEBUI_NAME}
 | 
			
		||||
				<Confetti x={[-1, -0.25]} y={[0, 0.5]} />
 | 
			
		||||
			</div>
 | 
			
		||||
			<button
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +48,7 @@
 | 
			
		|||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="flex items-center mt-1">
 | 
			
		||||
			<div class="text-sm dark:text-gray-200">Release Notes</div>
 | 
			
		||||
			<div class="text-sm dark:text-gray-200">{$i18n.t('Release Notes')}</div>
 | 
			
		||||
			<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" />
 | 
			
		||||
			<div class="text-sm dark:text-gray-200">
 | 
			
		||||
				v{WEBUI_VERSION}
 | 
			
		||||
| 
						 | 
				
			
			@ -108,7 +111,7 @@
 | 
			
		|||
				}}
 | 
			
		||||
				class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
			>
 | 
			
		||||
				<span class="relative">Okay, Let's Go!</span>
 | 
			
		||||
				<span class="relative">{$i18n.t("Okay, Let's Go!")}</span>
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,11 +2,12 @@
 | 
			
		|||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import dayjs from 'dayjs';
 | 
			
		||||
	import { createEventDispatcher } from 'svelte';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	import { updateUserById } from '$lib/apis/users';
 | 
			
		||||
	import Modal from '../common/Modal.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	export let show = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +43,7 @@
 | 
			
		|||
<Modal size="sm" bind:show>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
 | 
			
		||||
			<div class=" text-lg font-medium self-center">Edit User</div>
 | 
			
		||||
			<div class=" text-lg font-medium self-center">{$i18n.t('Edit User')}</div>
 | 
			
		||||
			<button
 | 
			
		||||
				class="self-center"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +85,8 @@
 | 
			
		|||
							<div class=" self-center capitalize font-semibold">{selectedUser.name}</div>
 | 
			
		||||
 | 
			
		||||
							<div class="text-xs text-gray-500">
 | 
			
		||||
								Created at {dayjs(selectedUser.timestamp * 1000).format('MMMM DD, YYYY')}
 | 
			
		||||
								{$i18n.t('Created at')}
 | 
			
		||||
								{dayjs(selectedUser.timestamp * 1000).format($i18n.t('MMMM DD, YYYY'))}
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -93,7 +95,7 @@
 | 
			
		|||
 | 
			
		||||
					<div class=" flex flex-col space-y-1.5">
 | 
			
		||||
						<div class="flex flex-col w-full">
 | 
			
		||||
							<div class=" mb-1 text-xs text-gray-500">Email</div>
 | 
			
		||||
							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Email')}</div>
 | 
			
		||||
 | 
			
		||||
							<div class="flex-1">
 | 
			
		||||
								<input
 | 
			
		||||
| 
						 | 
				
			
			@ -108,7 +110,7 @@
 | 
			
		|||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex flex-col w-full">
 | 
			
		||||
							<div class=" mb-1 text-xs text-gray-500">Name</div>
 | 
			
		||||
							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div>
 | 
			
		||||
 | 
			
		||||
							<div class="flex-1">
 | 
			
		||||
								<input
 | 
			
		||||
| 
						 | 
				
			
			@ -122,7 +124,7 @@
 | 
			
		|||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex flex-col w-full">
 | 
			
		||||
							<div class=" mb-1 text-xs text-gray-500">New Password</div>
 | 
			
		||||
							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('New Password')}</div>
 | 
			
		||||
 | 
			
		||||
							<div class="flex-1">
 | 
			
		||||
								<input
 | 
			
		||||
| 
						 | 
				
			
			@ -140,7 +142,7 @@
 | 
			
		|||
							class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
							type="submit"
 | 
			
		||||
						>
 | 
			
		||||
							Save
 | 
			
		||||
							{$i18n.t('Save')}
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,8 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { downloadDatabase } from '$lib/apis/utils';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let saveHandler: Function;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -17,10 +19,10 @@
 | 
			
		|||
>
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2 text-sm font-medium">Database</div>
 | 
			
		||||
			<div class=" mb-2 text-sm font-medium">{$i18n.t('Database')}</div>
 | 
			
		||||
 | 
			
		||||
			<div class="  flex w-full justify-between">
 | 
			
		||||
				<!-- <div class=" self-center text-xs font-medium">Allow Chat Deletion</div> -->
 | 
			
		||||
				<!-- <div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div> -->
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class=" flex rounded-md py-1.5 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -46,7 +48,7 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class=" self-center text-sm font-medium">Download Database</div>
 | 
			
		||||
					<div class=" self-center text-sm font-medium">{$i18n.t('Download Database')}</div>
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,9 @@
 | 
			
		|||
		updateDefaultUserRole,
 | 
			
		||||
		updateJWTExpiresDuration
 | 
			
		||||
	} from '$lib/apis/auths';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let saveHandler: Function;
 | 
			
		||||
	let signUpEnabled = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -43,10 +45,10 @@
 | 
			
		|||
>
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2 text-sm font-medium">General Settings</div>
 | 
			
		||||
			<div class=" mb-2 text-sm font-medium">{$i18n.t('General Settings')}</div>
 | 
			
		||||
 | 
			
		||||
			<div class="  flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">Enable New Sign Ups</div>
 | 
			
		||||
				<div class=" self-center text-xs font-medium">{$i18n.t('Enable New Sign Ups')}</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +68,7 @@
 | 
			
		|||
								d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"
 | 
			
		||||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
						<span class="ml-2 self-center">Enabled</span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('Enabled')}</span>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<svg
 | 
			
		||||
							xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
| 
						 | 
				
			
			@ -81,25 +83,25 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
 | 
			
		||||
						<span class="ml-2 self-center">Disabled</span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('Disabled')}</span>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class=" flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">Default User Role</div>
 | 
			
		||||
				<div class=" self-center text-xs font-medium">{$i18n.t('Default User Role')}</div>
 | 
			
		||||
				<div class="flex items-center relative">
 | 
			
		||||
					<select
 | 
			
		||||
						class="w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
 | 
			
		||||
						class="dark:bg-gray-900 w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
 | 
			
		||||
						bind:value={defaultUserRole}
 | 
			
		||||
						placeholder="Select a theme"
 | 
			
		||||
						on:change={(e) => {
 | 
			
		||||
							updateDefaultUserRoleHandler(e.target.value);
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						<option value="pending">Pending</option>
 | 
			
		||||
						<option value="user">User</option>
 | 
			
		||||
						<option value="admin">Admin</option>
 | 
			
		||||
						<option value="pending">{$i18n.t('pending')}</option>
 | 
			
		||||
						<option value="user">{$i18n.t('user')}</option>
 | 
			
		||||
						<option value="admin">{$i18n.t('admin')}</option>
 | 
			
		||||
					</select>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -108,7 +110,7 @@
 | 
			
		|||
 | 
			
		||||
			<div class=" w-full justify-between">
 | 
			
		||||
				<div class="flex w-full justify-between">
 | 
			
		||||
					<div class=" self-center text-xs font-medium">JWT Expiration</div>
 | 
			
		||||
					<div class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="flex mt-2 space-x-2">
 | 
			
		||||
| 
						 | 
				
			
			@ -121,8 +123,9 @@
 | 
			
		|||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
					Valid time units: <span class=" text-gray-300 font-medium"
 | 
			
		||||
						>'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.</span
 | 
			
		||||
					{$i18n.t('Valid time units:')}
 | 
			
		||||
					<span class=" text-gray-300 font-medium"
 | 
			
		||||
						>{$i18n.t("'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.")}</span
 | 
			
		||||
					>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -134,7 +137,7 @@
 | 
			
		|||
			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
			type="submit"
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
			{$i18n.t('Save')}
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,17 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { getModelFilterConfig, updateModelFilterConfig } from '$lib/apis';
 | 
			
		||||
	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
 | 
			
		||||
	import { getUserPermissions, updateUserPermissions } from '$lib/apis/users';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
	import { models } from '$lib/stores';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let saveHandler: Function;
 | 
			
		||||
 | 
			
		||||
	let whitelistEnabled = false;
 | 
			
		||||
	let whitelistModels = [''];
 | 
			
		||||
	let permissions = {
 | 
			
		||||
		chat: {
 | 
			
		||||
			deletion: true
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +20,13 @@
 | 
			
		|||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		permissions = await getUserPermissions(localStorage.token);
 | 
			
		||||
 | 
			
		||||
		const res = await getModelFilterConfig(localStorage.token);
 | 
			
		||||
		if (res) {
 | 
			
		||||
			whitelistEnabled = res.enabled;
 | 
			
		||||
 | 
			
		||||
			whitelistModels = res.models.length > 0 ? res.models : [''];
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -21,15 +35,17 @@
 | 
			
		|||
	on:submit|preventDefault={async () => {
 | 
			
		||||
		// console.log('submit');
 | 
			
		||||
		await updateUserPermissions(localStorage.token, permissions);
 | 
			
		||||
 | 
			
		||||
		await updateModelFilterConfig(localStorage.token, whitelistEnabled, whitelistModels);
 | 
			
		||||
		saveHandler();
 | 
			
		||||
	}}
 | 
			
		||||
>
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2 text-sm font-medium">User Permissions</div>
 | 
			
		||||
			<div class=" mb-2 text-sm font-medium">{$i18n.t('User Permissions')}</div>
 | 
			
		||||
 | 
			
		||||
			<div class="  flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">Allow Chat Deletion</div>
 | 
			
		||||
				<div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +65,7 @@
 | 
			
		|||
								d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"
 | 
			
		||||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
						<span class="ml-2 self-center">Allow</span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('Allow')}</span>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<svg
 | 
			
		||||
							xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
| 
						 | 
				
			
			@ -64,11 +80,112 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
 | 
			
		||||
						<span class="ml-2 self-center">Don't Allow</span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t("Don't Allow")}</span>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700 my-2" />
 | 
			
		||||
 | 
			
		||||
		<div class="mt-2 space-y-3 pr-1.5">
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class="mb-2">
 | 
			
		||||
					<div class="flex justify-between items-center text-xs">
 | 
			
		||||
						<div class=" text-sm font-medium">{$i18n.t('Manage Models')}</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class=" space-y-3">
 | 
			
		||||
					<div>
 | 
			
		||||
						<div class="flex justify-between items-center text-xs">
 | 
			
		||||
							<div class=" text-xs font-medium">{$i18n.t('Model Whitelisting')}</div>
 | 
			
		||||
 | 
			
		||||
							<button
 | 
			
		||||
								class=" text-xs font-medium text-gray-500"
 | 
			
		||||
								type="button"
 | 
			
		||||
								on:click={() => {
 | 
			
		||||
									whitelistEnabled = !whitelistEnabled;
 | 
			
		||||
								}}>{whitelistEnabled ? $i18n.t('On') : $i18n.t('Off')}</button
 | 
			
		||||
							>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					{#if whitelistEnabled}
 | 
			
		||||
						<div>
 | 
			
		||||
							<div class=" space-y-1.5">
 | 
			
		||||
								{#each whitelistModels as modelId, modelIdx}
 | 
			
		||||
									<div class="flex w-full">
 | 
			
		||||
										<div class="flex-1 mr-2">
 | 
			
		||||
											<select
 | 
			
		||||
												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
												bind:value={modelId}
 | 
			
		||||
												placeholder="Select a model"
 | 
			
		||||
											>
 | 
			
		||||
												<option value="" disabled selected>{$i18n.t('Select a model')}</option>
 | 
			
		||||
												{#each $models.filter((model) => model.id) as model}
 | 
			
		||||
													<option value={model.id} class="bg-gray-100 dark:bg-gray-700"
 | 
			
		||||
														>{model.name}</option
 | 
			
		||||
													>
 | 
			
		||||
												{/each}
 | 
			
		||||
											</select>
 | 
			
		||||
										</div>
 | 
			
		||||
 | 
			
		||||
										{#if modelIdx === 0}
 | 
			
		||||
											<button
 | 
			
		||||
												class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-900 dark:text-white rounded-lg transition"
 | 
			
		||||
												type="button"
 | 
			
		||||
												on:click={() => {
 | 
			
		||||
													if (whitelistModels.at(-1) !== '') {
 | 
			
		||||
														whitelistModels = [...whitelistModels, ''];
 | 
			
		||||
													}
 | 
			
		||||
												}}
 | 
			
		||||
											>
 | 
			
		||||
												<svg
 | 
			
		||||
													xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
													viewBox="0 0 16 16"
 | 
			
		||||
													fill="currentColor"
 | 
			
		||||
													class="w-4 h-4"
 | 
			
		||||
												>
 | 
			
		||||
													<path
 | 
			
		||||
														d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
 | 
			
		||||
													/>
 | 
			
		||||
												</svg>
 | 
			
		||||
											</button>
 | 
			
		||||
										{:else}
 | 
			
		||||
											<button
 | 
			
		||||
												class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-900 dark:text-white rounded-lg transition"
 | 
			
		||||
												type="button"
 | 
			
		||||
												on:click={() => {
 | 
			
		||||
													whitelistModels.splice(modelIdx, 1);
 | 
			
		||||
													whitelistModels = whitelistModels;
 | 
			
		||||
												}}
 | 
			
		||||
											>
 | 
			
		||||
												<svg
 | 
			
		||||
													xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
													viewBox="0 0 16 16"
 | 
			
		||||
													fill="currentColor"
 | 
			
		||||
													class="w-4 h-4"
 | 
			
		||||
												>
 | 
			
		||||
													<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
 | 
			
		||||
												</svg>
 | 
			
		||||
											</button>
 | 
			
		||||
										{/if}
 | 
			
		||||
									</div>
 | 
			
		||||
								{/each}
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							<div class="flex justify-end items-center text-xs mt-1.5 text-right">
 | 
			
		||||
								<div class=" text-xs font-medium">
 | 
			
		||||
									{whitelistModels.length}
 | 
			
		||||
									{$i18n.t('Model(s) Whitelisted')}
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="flex justify-end pt-3 text-sm font-medium">
 | 
			
		||||
| 
						 | 
				
			
			@ -76,7 +193,7 @@
 | 
			
		|||
			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
			type="submit"
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
			{$i18n.t('Save')}
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,13 @@
 | 
			
		|||
<script>
 | 
			
		||||
	import { getContext } from 'svelte';
 | 
			
		||||
	import Modal from '../common/Modal.svelte';
 | 
			
		||||
	import Database from './Settings/Database.svelte';
 | 
			
		||||
 | 
			
		||||
	import General from './Settings/General.svelte';
 | 
			
		||||
	import Users from './Settings/Users.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let show = false;
 | 
			
		||||
 | 
			
		||||
	let selectedTab = 'general';
 | 
			
		||||
| 
						 | 
				
			
			@ -13,7 +16,7 @@
 | 
			
		|||
<Modal bind:show>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
 | 
			
		||||
			<div class=" text-lg font-medium self-center">Admin Settings</div>
 | 
			
		||||
			<div class=" text-lg font-medium self-center">{$i18n.t('Admin Settings')}</div>
 | 
			
		||||
			<button
 | 
			
		||||
				class="self-center"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +64,7 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class=" self-center">General</div>
 | 
			
		||||
					<div class=" self-center">{$i18n.t('General')}</div>
 | 
			
		||||
				</button>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +88,7 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class=" self-center">Users</div>
 | 
			
		||||
					<div class=" self-center">{$i18n.t('Users')}</div>
 | 
			
		||||
				</button>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
| 
						 | 
				
			
			@ -113,7 +116,7 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class=" self-center">Database</div>
 | 
			
		||||
					<div class=" self-center">{$i18n.t('Database')}</div>
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="flex-1 md:min-h-[380px]">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import { onMount, tick } from 'svelte';
 | 
			
		||||
	import { onMount, tick, getContext } from 'svelte';
 | 
			
		||||
	import { settings } from '$lib/stores';
 | 
			
		||||
	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -14,12 +14,14 @@
 | 
			
		|||
	import { transcribeAudio } from '$lib/apis/audio';
 | 
			
		||||
	import Tooltip from '../common/Tooltip.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let submitPrompt: Function;
 | 
			
		||||
	export let stopResponse: Function;
 | 
			
		||||
 | 
			
		||||
	export let suggestionPrompts = [];
 | 
			
		||||
	export let autoScroll = true;
 | 
			
		||||
	let chatTextAreaElement:HTMLTextAreaElement
 | 
			
		||||
	let chatTextAreaElement: HTMLTextAreaElement;
 | 
			
		||||
	let filesInputElement;
 | 
			
		||||
 | 
			
		||||
	let promptsElement;
 | 
			
		||||
| 
						 | 
				
			
			@ -209,11 +211,11 @@
 | 
			
		|||
					// Event triggered when an error occurs
 | 
			
		||||
					speechRecognition.onerror = function (event) {
 | 
			
		||||
						console.log(event);
 | 
			
		||||
						toast.error(`Speech recognition error: ${event.error}`);
 | 
			
		||||
						toast.error($i18n.t(`Speech recognition error: {{error}}`, { error: event.error }));
 | 
			
		||||
						isRecording = false;
 | 
			
		||||
					};
 | 
			
		||||
				} else {
 | 
			
		||||
					toast.error('SpeechRecognition API is not supported in this browser.');
 | 
			
		||||
					toast.error($i18n.t('SpeechRecognition API is not supported in this browser.'));
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -333,12 +335,15 @@
 | 
			
		|||
						uploadDoc(file);
 | 
			
		||||
					} else {
 | 
			
		||||
						toast.error(
 | 
			
		||||
							`Unknown File Type '${file['type']}', but accepting and treating as plain text`
 | 
			
		||||
							$i18n.t(
 | 
			
		||||
								`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
 | 
			
		||||
								{ file_type: file['type'] }
 | 
			
		||||
							)
 | 
			
		||||
						);
 | 
			
		||||
						uploadDoc(file);
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					toast.error(`File not found.`);
 | 
			
		||||
					toast.error($i18n.t(`File not found.`));
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -359,12 +364,12 @@
 | 
			
		|||
 | 
			
		||||
{#if dragged}
 | 
			
		||||
	<div
 | 
			
		||||
		class="fixed w-full h-full flex z-50 touch-none pointer-events-none"
 | 
			
		||||
		class="fixed lg:w-[calc(100%-260px)] w-full h-full flex z-50 touch-none pointer-events-none"
 | 
			
		||||
		id="dropzone"
 | 
			
		||||
		role="region"
 | 
			
		||||
		aria-label="Drag and Drop Container"
 | 
			
		||||
	>
 | 
			
		||||
		<div class="absolute rounded-xl w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
 | 
			
		||||
		<div class="absolute w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
 | 
			
		||||
			<div class="m-auto pt-64 flex flex-col justify-center">
 | 
			
		||||
				<div class="max-w-md">
 | 
			
		||||
					<AddFilesPlaceholder />
 | 
			
		||||
| 
						 | 
				
			
			@ -477,13 +482,16 @@
 | 
			
		|||
								filesInputElement.value = '';
 | 
			
		||||
							} else {
 | 
			
		||||
								toast.error(
 | 
			
		||||
									`Unknown File Type '${file['type']}', but accepting and treating as plain text`
 | 
			
		||||
									$i18n.t(
 | 
			
		||||
										`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
 | 
			
		||||
										{ file_type: file['type'] }
 | 
			
		||||
									)
 | 
			
		||||
								);
 | 
			
		||||
								uploadDoc(file);
 | 
			
		||||
								filesInputElement.value = '';
 | 
			
		||||
							}
 | 
			
		||||
						} else {
 | 
			
		||||
							toast.error(`File not found.`);
 | 
			
		||||
							toast.error($i18n.t(`File not found.`));
 | 
			
		||||
						}
 | 
			
		||||
					}}
 | 
			
		||||
				/>
 | 
			
		||||
| 
						 | 
				
			
			@ -570,7 +578,7 @@
 | 
			
		|||
													{file.name}
 | 
			
		||||
												</div>
 | 
			
		||||
 | 
			
		||||
												<div class=" text-gray-500 text-sm">Document</div>
 | 
			
		||||
												<div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
									{:else if file.type === 'collection'}
 | 
			
		||||
| 
						 | 
				
			
			@ -598,7 +606,7 @@
 | 
			
		|||
													{file?.title ?? `#${file.name}`}
 | 
			
		||||
												</div>
 | 
			
		||||
 | 
			
		||||
												<div class=" text-gray-500 text-sm">Collection</div>
 | 
			
		||||
												<div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
									{/if}
 | 
			
		||||
| 
						 | 
				
			
			@ -632,7 +640,7 @@
 | 
			
		|||
					<div class=" flex">
 | 
			
		||||
						{#if fileUploadEnabled}
 | 
			
		||||
							<div class=" self-end mb-2 ml-1">
 | 
			
		||||
								<Tooltip content="Upload files">
 | 
			
		||||
								<Tooltip content={$i18n.t('Upload files')}>
 | 
			
		||||
									<button
 | 
			
		||||
										class="bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
 | 
			
		||||
										type="button"
 | 
			
		||||
| 
						 | 
				
			
			@ -664,8 +672,8 @@
 | 
			
		|||
							placeholder={chatInputPlaceholder !== ''
 | 
			
		||||
								? chatInputPlaceholder
 | 
			
		||||
								: isRecording
 | 
			
		||||
								? 'Listening...'
 | 
			
		||||
								: 'Send a message'}
 | 
			
		||||
								? $i18n.t('Listening...')
 | 
			
		||||
								: $i18n.t('Send a Messsage')}
 | 
			
		||||
							bind:value={prompt}
 | 
			
		||||
							on:keypress={(e) => {
 | 
			
		||||
								if (e.keyCode == 13 && !e.shiftKey) {
 | 
			
		||||
| 
						 | 
				
			
			@ -804,7 +812,7 @@
 | 
			
		|||
 | 
			
		||||
						<div class="self-end mb-2 flex space-x-1 mr-1">
 | 
			
		||||
							{#if messages.length == 0 || messages.at(-1).done == true}
 | 
			
		||||
								<Tooltip content="Record voice">
 | 
			
		||||
								<Tooltip content={$i18n.t('Record voice')}>
 | 
			
		||||
									{#if speechRecognitionEnabled}
 | 
			
		||||
										<button
 | 
			
		||||
											id="voice-input-button"
 | 
			
		||||
| 
						 | 
				
			
			@ -873,7 +881,7 @@
 | 
			
		|||
									{/if}
 | 
			
		||||
								</Tooltip>
 | 
			
		||||
 | 
			
		||||
								<Tooltip content="Send message">
 | 
			
		||||
								<Tooltip content={$i18n.t('Send message')}>
 | 
			
		||||
									<button
 | 
			
		||||
										class="{prompt !== ''
 | 
			
		||||
											? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
 | 
			
		||||
| 
						 | 
				
			
			@ -919,7 +927,7 @@
 | 
			
		|||
				</form>
 | 
			
		||||
 | 
			
		||||
				<div class="mt-1.5 text-xs text-gray-500 text-center">
 | 
			
		||||
					LLMs can make mistakes. Verify important information.
 | 
			
		||||
					{$i18n.t('LLMs can make mistakes. Verify important information.')}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,9 +3,11 @@
 | 
			
		|||
 | 
			
		||||
	import { documents } from '$lib/stores';
 | 
			
		||||
	import { removeFirstHashWord, isValidHttpUrl } from '$lib/utils';
 | 
			
		||||
	import { tick } from 'svelte';
 | 
			
		||||
	import { tick, getContext } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let prompt = '';
 | 
			
		||||
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +119,7 @@
 | 
			
		|||
									{doc?.title ?? `#${doc.name}`}
 | 
			
		||||
								</div>
 | 
			
		||||
 | 
			
		||||
								<div class=" text-xs text-gray-600 line-clamp-1">Collection</div>
 | 
			
		||||
								<div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Collection')}</div>
 | 
			
		||||
							{:else}
 | 
			
		||||
								<div class=" font-medium text-black line-clamp-1">
 | 
			
		||||
									#{doc.name} ({doc.filename})
 | 
			
		||||
| 
						 | 
				
			
			@ -140,7 +142,9 @@
 | 
			
		|||
									confirmSelectWeb(url);
 | 
			
		||||
								} else {
 | 
			
		||||
									toast.error(
 | 
			
		||||
										'Oops! Looks like the URL is invalid. Please double-check and try again.'
 | 
			
		||||
										$i18n.t(
 | 
			
		||||
											'Oops! Looks like the URL is invalid. Please double-check and try again.'
 | 
			
		||||
										)
 | 
			
		||||
									);
 | 
			
		||||
								}
 | 
			
		||||
							}}
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +153,7 @@
 | 
			
		|||
								{prompt.split(' ')?.at(0)?.substring(1)}
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							<div class=" text-xs text-gray-600 line-clamp-1">Web</div>
 | 
			
		||||
							<div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Web')}</div>
 | 
			
		||||
						</button>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,9 +2,11 @@
 | 
			
		|||
	import { generatePrompt } from '$lib/apis/ollama';
 | 
			
		||||
	import { models } from '$lib/stores';
 | 
			
		||||
	import { splitStream } from '$lib/utils';
 | 
			
		||||
	import { tick } from 'svelte';
 | 
			
		||||
	import { tick, getContext } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let prompt = '';
 | 
			
		||||
	export let user = null;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +43,7 @@
 | 
			
		|||
		user = JSON.parse(JSON.stringify(model.name));
 | 
			
		||||
		await tick();
 | 
			
		||||
 | 
			
		||||
		chatInputPlaceholder = `'${model.name}' is thinking...`;
 | 
			
		||||
		chatInputPlaceholder = $i18n.t('{{modelName}} is thinking...', { modelName: model.name });
 | 
			
		||||
 | 
			
		||||
		const chatInputElement = document.getElementById('chat-textarea');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -113,7 +115,9 @@
 | 
			
		|||
					toast.error(error.error);
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				toast.error(`Uh-oh! There was an issue connecting to Ollama.`);
 | 
			
		||||
				toast.error(
 | 
			
		||||
					$i18n.t('Uh-oh! There was an issue connecting to {{provider}}.', { provider: 'llama' })
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,11 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { prompts } from '$lib/stores';
 | 
			
		||||
	import { findWordIndices } from '$lib/utils';
 | 
			
		||||
	import { tick } from 'svelte';
 | 
			
		||||
	import { tick, getContext } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let prompt = '';
 | 
			
		||||
	let selectedCommandIdx = 0;
 | 
			
		||||
	let filteredPromptCommands = [];
 | 
			
		||||
| 
						 | 
				
			
			@ -29,7 +31,7 @@
 | 
			
		|||
 | 
			
		||||
		if (command.content.includes('{{CLIPBOARD}}')) {
 | 
			
		||||
			const clipboardText = await navigator.clipboard.readText().catch((err) => {
 | 
			
		||||
				toast.error('Failed to read clipboard contents');
 | 
			
		||||
				toast.error($i18n.t('Failed to read clipboard contents'));
 | 
			
		||||
				return '{{CLIPBOARD}}';
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -113,8 +115,9 @@
 | 
			
		|||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="line-clamp-1">
 | 
			
		||||
						Tip: Update multiple variable slots consecutively by pressing the tab key in the chat
 | 
			
		||||
						input after each replacement.
 | 
			
		||||
						{$i18n.t(
 | 
			
		||||
							'Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.'
 | 
			
		||||
						)}
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
	import { v4 as uuidv4 } from 'uuid';
 | 
			
		||||
 | 
			
		||||
	import { chats, config, modelfiles, settings, user } from '$lib/stores';
 | 
			
		||||
	import { tick } from 'svelte';
 | 
			
		||||
	import { tick, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import { getChatList, updateChatById } from '$lib/apis/chats';
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +13,8 @@
 | 
			
		|||
	import Spinner from '../common/Spinner.svelte';
 | 
			
		||||
	import { imageGenerations } from '$lib/apis/images';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let chatId = '';
 | 
			
		||||
	export let sendPrompt: Function;
 | 
			
		||||
	export let continueGeneration: Function;
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +69,7 @@
 | 
			
		|||
		navigator.clipboard.writeText(text).then(
 | 
			
		||||
			function () {
 | 
			
		||||
				console.log('Async: Copying to clipboard was successful!');
 | 
			
		||||
				toast.success('Copying to clipboard was successful!');
 | 
			
		||||
				toast.success($i18n.t('Copying to clipboard was successful!'));
 | 
			
		||||
			},
 | 
			
		||||
			function (err) {
 | 
			
		||||
				console.error('Async: Could not copy text: ', err);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,9 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { WEBUI_BASE_URL } from '$lib/constants';
 | 
			
		||||
	import { user } from '$lib/stores';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let models = [];
 | 
			
		||||
	export let modelfiles = [];
 | 
			
		||||
| 
						 | 
				
			
			@ -64,9 +66,9 @@
 | 
			
		|||
					</div>
 | 
			
		||||
				{/if}
 | 
			
		||||
			{:else}
 | 
			
		||||
				<div class=" line-clamp-1">Hello, {$user.name}</div>
 | 
			
		||||
				<div class=" line-clamp-1">{$i18n.t('Hello, {{name}}', { name: $user.name })}</div>
 | 
			
		||||
 | 
			
		||||
				<div>How can I help you today?</div>
 | 
			
		||||
				<div>{$i18n.t('How can I help you today?')}</div>
 | 
			
		||||
			{/if}
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,9 @@
 | 
			
		|||
 | 
			
		||||
	import { fade } from 'svelte/transition';
 | 
			
		||||
	import { createEventDispatcher } from 'svelte';
 | 
			
		||||
	import { onMount, tick } from 'svelte';
 | 
			
		||||
	import { onMount, tick, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -316,7 +318,7 @@
 | 
			
		|||
 | 
			
		||||
				{#if message.timestamp}
 | 
			
		||||
					<span class=" invisible group-hover:visible text-gray-400 text-xs font-medium">
 | 
			
		||||
						{dayjs(message.timestamp * 1000).format('DD/MM/YYYY HH:mm')}
 | 
			
		||||
						{dayjs(message.timestamp * 1000).format($i18n.t('DD/MM/YYYY HH:mm'))}
 | 
			
		||||
					</span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</Name>
 | 
			
		||||
| 
						 | 
				
			
			@ -360,7 +362,7 @@
 | 
			
		|||
											editMessageConfirmHandler();
 | 
			
		||||
										}}
 | 
			
		||||
									>
 | 
			
		||||
										Save
 | 
			
		||||
										{$i18n.t('Save')}
 | 
			
		||||
									</button>
 | 
			
		||||
 | 
			
		||||
									<button
 | 
			
		||||
| 
						 | 
				
			
			@ -369,7 +371,7 @@
 | 
			
		|||
											cancelEditMessage();
 | 
			
		||||
										}}
 | 
			
		||||
									>
 | 
			
		||||
										Cancel
 | 
			
		||||
										{$i18n.t('Cancel')}
 | 
			
		||||
									</button>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,14 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import dayjs from 'dayjs';
 | 
			
		||||
 | 
			
		||||
	import { tick, createEventDispatcher } from 'svelte';
 | 
			
		||||
	import { tick, createEventDispatcher, getContext } from 'svelte';
 | 
			
		||||
	import Name from './Name.svelte';
 | 
			
		||||
	import ProfileImage from './ProfileImage.svelte';
 | 
			
		||||
	import { modelfiles, settings } from '$lib/stores';
 | 
			
		||||
	import Tooltip from '$lib/components/common/Tooltip.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	export let user;
 | 
			
		||||
| 
						 | 
				
			
			@ -65,17 +67,18 @@
 | 
			
		|||
					{#if $modelfiles.map((modelfile) => modelfile.tagName).includes(message.user)}
 | 
			
		||||
						{$modelfiles.find((modelfile) => modelfile.tagName === message.user)?.title}
 | 
			
		||||
					{:else}
 | 
			
		||||
						You <span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
 | 
			
		||||
						{$i18n.t('You')}
 | 
			
		||||
						<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
 | 
			
		||||
					{/if}
 | 
			
		||||
				{:else if $settings.showUsername}
 | 
			
		||||
					{user.name}
 | 
			
		||||
				{:else}
 | 
			
		||||
					You
 | 
			
		||||
					{$i18n.t('You')}
 | 
			
		||||
				{/if}
 | 
			
		||||
 | 
			
		||||
				{#if message.timestamp}
 | 
			
		||||
					<span class=" invisible group-hover:visible text-gray-400 text-xs font-medium">
 | 
			
		||||
						{dayjs(message.timestamp * 1000).format('DD/MM/YYYY HH:mm')}
 | 
			
		||||
						{dayjs(message.timestamp * 1000).format($i18n.t('DD/MM/YYYY HH:mm'))}
 | 
			
		||||
					</span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</Name>
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +126,7 @@
 | 
			
		|||
											{file.name}
 | 
			
		||||
										</div>
 | 
			
		||||
 | 
			
		||||
										<div class=" text-gray-500 text-sm">Document</div>
 | 
			
		||||
										<div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
 | 
			
		||||
									</div>
 | 
			
		||||
								</button>
 | 
			
		||||
							{:else if file.type === 'collection'}
 | 
			
		||||
| 
						 | 
				
			
			@ -152,7 +155,7 @@
 | 
			
		|||
											{file?.title ?? `#${file.name}`}
 | 
			
		||||
										</div>
 | 
			
		||||
 | 
			
		||||
										<div class=" text-gray-500 text-sm">Collection</div>
 | 
			
		||||
										<div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
 | 
			
		||||
									</div>
 | 
			
		||||
								</button>
 | 
			
		||||
							{/if}
 | 
			
		||||
| 
						 | 
				
			
			@ -181,7 +184,7 @@
 | 
			
		|||
								editMessageConfirmHandler();
 | 
			
		||||
							}}
 | 
			
		||||
						>
 | 
			
		||||
							Save & Submit
 | 
			
		||||
							{$i18n.t('Save & Submit')}
 | 
			
		||||
						</button>
 | 
			
		||||
 | 
			
		||||
						<button
 | 
			
		||||
| 
						 | 
				
			
			@ -190,7 +193,7 @@
 | 
			
		|||
								cancelEditMessage();
 | 
			
		||||
							}}
 | 
			
		||||
						>
 | 
			
		||||
							Cancel
 | 
			
		||||
							{$i18n.t('Cancel')}
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,16 +1,18 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { setDefaultModels } from '$lib/apis/configs';
 | 
			
		||||
	import { models, showSettings, settings, user } from '$lib/stores';
 | 
			
		||||
	import { onMount, tick } from 'svelte';
 | 
			
		||||
	import { onMount, tick, getContext } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let selectedModels = [''];
 | 
			
		||||
	export let disabled = false;
 | 
			
		||||
 | 
			
		||||
	const saveDefaultModel = async () => {
 | 
			
		||||
		const hasEmptyModel = selectedModels.filter((it) => it === '');
 | 
			
		||||
		if (hasEmptyModel.length) {
 | 
			
		||||
			toast.error('Choose a model before saving...');
 | 
			
		||||
			toast.error($i18n.t('Choose a model before saving...'));
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		settings.set({ ...$settings, models: selectedModels });
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +22,7 @@
 | 
			
		|||
			console.log('setting default models globally');
 | 
			
		||||
			await setDefaultModels(localStorage.token, selectedModels.join(','));
 | 
			
		||||
		}
 | 
			
		||||
		toast.success('Default model updated');
 | 
			
		||||
		toast.success($i18n.t('Default model updated'));
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	$: if (selectedModels.length > 0 && $models.length > 0) {
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +41,9 @@
 | 
			
		|||
				bind:value={selectedModel}
 | 
			
		||||
				{disabled}
 | 
			
		||||
			>
 | 
			
		||||
				<option class=" text-gray-700" value="" selected disabled>Select a model</option>
 | 
			
		||||
				<option class=" text-gray-700" value="" selected disabled
 | 
			
		||||
					>{$i18n.t('Select a model')}</option
 | 
			
		||||
				>
 | 
			
		||||
 | 
			
		||||
				{#each $models as model}
 | 
			
		||||
					{#if model.name === 'hr'}
 | 
			
		||||
| 
						 | 
				
			
			@ -133,5 +137,5 @@
 | 
			
		|||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="text-left mt-1.5 text-xs text-gray-500">
 | 
			
		||||
	<button on:click={saveDefaultModel}> Set as default</button>
 | 
			
		||||
	<button on:click={saveDefaultModel}> {$i18n.t('Set as default')}</button>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,9 @@
 | 
			
		|||
	import { WEBUI_VERSION } from '$lib/constants';
 | 
			
		||||
	import { WEBUI_NAME, config, showChangelog } from '$lib/stores';
 | 
			
		||||
	import { compareVersion } from '$lib/utils';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	let ollamaVersion = '';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +45,8 @@
 | 
			
		|||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium flex space-x-2 items-center">
 | 
			
		||||
				<div>
 | 
			
		||||
					{$WEBUI_NAME} Version
 | 
			
		||||
					{$WEBUI_NAME}
 | 
			
		||||
					{$i18n.t('Version')}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="flex w-full justify-between items-center">
 | 
			
		||||
| 
						 | 
				
			
			@ -56,10 +59,10 @@
 | 
			
		|||
							target="_blank"
 | 
			
		||||
						>
 | 
			
		||||
							{updateAvailable === null
 | 
			
		||||
								? 'Checking for updates...'
 | 
			
		||||
								? $i18n.t('Checking for updates...')
 | 
			
		||||
								: updateAvailable
 | 
			
		||||
								? `(v${version.latest} available!)`
 | 
			
		||||
								: '(latest)'}
 | 
			
		||||
								? `(v${version.latest} ${$i18n.t('available!')})`
 | 
			
		||||
								: $i18n.t('(latest)')}
 | 
			
		||||
						</a>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -69,7 +72,7 @@
 | 
			
		|||
							showChangelog.set(true);
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						<div>See what's new</div>
 | 
			
		||||
						<div>{$i18n.t("See what's new")}</div>
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -79,7 +82,7 @@
 | 
			
		|||
						checkForVersionUpdates();
 | 
			
		||||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					Check for updates
 | 
			
		||||
					{$i18n.t('Check for updates')}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -88,7 +91,7 @@
 | 
			
		|||
			<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">Ollama Version</div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama Version')}</div>
 | 
			
		||||
				<div class="flex w-full">
 | 
			
		||||
					<div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
 | 
			
		||||
						{ollamaVersion ?? 'N/A'}
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +126,8 @@
 | 
			
		|||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
			Created by <a
 | 
			
		||||
			{$i18n.t('Created by')}
 | 
			
		||||
			<a
 | 
			
		||||
				class=" text-gray-500 dark:text-gray-300 font-medium"
 | 
			
		||||
				href="https://github.com/tjbck"
 | 
			
		||||
				target="_blank">Timothy J. Baek</a
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	import { user } from '$lib/stores';
 | 
			
		||||
	import { updateUserProfile } from '$lib/apis/auths';
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +9,8 @@
 | 
			
		|||
	import { getGravatarUrl } from '$lib/apis/utils';
 | 
			
		||||
	import { copyToClipboard } from '$lib/utils';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let saveHandler: Function;
 | 
			
		||||
 | 
			
		||||
	let profileImageUrl = '';
 | 
			
		||||
| 
						 | 
				
			
			@ -38,7 +40,7 @@
 | 
			
		|||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="flex flex-col h-full justify-between text-sm">
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
 | 
			
		||||
		<input
 | 
			
		||||
			id="profile-image-input"
 | 
			
		||||
			bind:this={profileImageInputElement}
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +103,7 @@
 | 
			
		|||
			}}
 | 
			
		||||
		/>
 | 
			
		||||
 | 
			
		||||
		<div class=" mb-2.5 text-sm font-medium">Profile</div>
 | 
			
		||||
		<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Profile')}</div>
 | 
			
		||||
 | 
			
		||||
		<div class="flex space-x-5">
 | 
			
		||||
			<div class="flex flex-col">
 | 
			
		||||
| 
						 | 
				
			
			@ -143,13 +145,13 @@
 | 
			
		|||
						const url = await getGravatarUrl($user.email);
 | 
			
		||||
 | 
			
		||||
						profileImageUrl = url;
 | 
			
		||||
					}}>Use Gravatar</button
 | 
			
		||||
					}}>{$i18n.t('Use Gravatar')}</button
 | 
			
		||||
				>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="flex-1">
 | 
			
		||||
				<div class="flex flex-col w-full">
 | 
			
		||||
					<div class=" mb-1 text-xs text-gray-500">Name</div>
 | 
			
		||||
					<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div>
 | 
			
		||||
 | 
			
		||||
					<div class="flex-1">
 | 
			
		||||
						<input
 | 
			
		||||
| 
						 | 
				
			
			@ -170,7 +172,7 @@
 | 
			
		|||
 | 
			
		||||
		<div class=" w-full justify-between">
 | 
			
		||||
			<div class="flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">JWT Token</div>
 | 
			
		||||
				<div class=" self-center text-xs font-medium">{$i18n.t('JWT Token')}</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="flex mt-2">
 | 
			
		||||
| 
						 | 
				
			
			@ -271,7 +273,7 @@
 | 
			
		|||
 | 
			
		||||
	<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"
 | 
			
		||||
			class="  px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 | 
			
		||||
			on:click={async () => {
 | 
			
		||||
				const res = await submitHandler();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -280,7 +282,7 @@
 | 
			
		|||
				}
 | 
			
		||||
			}}
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
			{$i18n.t('Save')}
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,10 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { getContext } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import { updateUserPassword } from '$lib/apis/auths';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	let show = false;
 | 
			
		||||
	let currentPassword = '';
 | 
			
		||||
	let newPassword = '';
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +20,7 @@
 | 
			
		|||
			);
 | 
			
		||||
 | 
			
		||||
			if (res) {
 | 
			
		||||
				toast.success('Successfully updated.');
 | 
			
		||||
				toast.success($i18n.t('Successfully updated.'));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			currentPassword = '';
 | 
			
		||||
| 
						 | 
				
			
			@ -40,20 +43,20 @@
 | 
			
		|||
	}}
 | 
			
		||||
>
 | 
			
		||||
	<div class="flex justify-between items-center text-sm">
 | 
			
		||||
		<div class="  font-medium">Change Password</div>
 | 
			
		||||
		<div class="  font-medium">{$i18n.t('Change Password')}</div>
 | 
			
		||||
		<button
 | 
			
		||||
			class=" text-xs font-medium text-gray-500"
 | 
			
		||||
			type="button"
 | 
			
		||||
			on:click={() => {
 | 
			
		||||
				show = !show;
 | 
			
		||||
			}}>{show ? 'Hide' : 'Show'}</button
 | 
			
		||||
			}}>{show ? $i18n.t('Hide') : $i18n.t('Show')}</button
 | 
			
		||||
		>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	{#if show}
 | 
			
		||||
		<div class=" py-2.5 space-y-1.5">
 | 
			
		||||
			<div class="flex flex-col w-full">
 | 
			
		||||
				<div class=" mb-1 text-xs text-gray-500">Current Password</div>
 | 
			
		||||
				<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Current Password')}</div>
 | 
			
		||||
 | 
			
		||||
				<div class="flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +70,7 @@
 | 
			
		|||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="flex flex-col w-full">
 | 
			
		||||
				<div class=" mb-1 text-xs text-gray-500">New Password</div>
 | 
			
		||||
				<div class=" mb-1 text-xs text-gray-500">{$i18n.t('New Password')}</div>
 | 
			
		||||
 | 
			
		||||
				<div class="flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
| 
						 | 
				
			
			@ -81,7 +84,7 @@
 | 
			
		|||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="flex flex-col w-full">
 | 
			
		||||
				<div class=" mb-1 text-xs text-gray-500">Confirm Password</div>
 | 
			
		||||
				<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Confirm Password')}</div>
 | 
			
		||||
 | 
			
		||||
				<div class="flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +102,7 @@
 | 
			
		|||
			<button
 | 
			
		||||
				class=" px-4 py-2 text-xs bg-gray-800 hover:bg-gray-900 dark:bg-gray-700 dark:hover:bg-gray-800 text-gray-100 transition rounded-md font-medium"
 | 
			
		||||
			>
 | 
			
		||||
				Update password
 | 
			
		||||
				{$i18n.t('Update password')}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
	{/if}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,10 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	import { createEventDispatcher, onMount, getContext } from 'svelte';
 | 
			
		||||
	import AdvancedParams from './Advanced/AdvancedParams.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	import AdvancedParams from './Advanced/AdvancedParams.svelte';
 | 
			
		||||
	export let saveSettings: Function;
 | 
			
		||||
 | 
			
		||||
	// Advanced
 | 
			
		||||
| 
						 | 
				
			
			@ -55,14 +57,14 @@
 | 
			
		|||
 | 
			
		||||
<div class="flex flex-col h-full justify-between text-sm">
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 | 
			
		||||
		<div class=" text-sm font-medium">Parameters</div>
 | 
			
		||||
		<div class=" text-sm font-medium">{$i18n.t('Parameters')}</div>
 | 
			
		||||
 | 
			
		||||
		<AdvancedParams bind:options />
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div class=" py-1 w-full justify-between">
 | 
			
		||||
			<div class="flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">Keep Alive</div>
 | 
			
		||||
				<div class=" self-center text-xs font-medium">{$i18n.t('Keep Alive')}</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -72,9 +74,9 @@
 | 
			
		|||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					{#if keepAlive === null}
 | 
			
		||||
						<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +86,7 @@
 | 
			
		|||
					<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={`e.g.) "30s","10m". Valid time units are "s", "m", "h".`}
 | 
			
		||||
						placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
 | 
			
		||||
						bind:value={keepAlive}
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -93,7 +95,7 @@
 | 
			
		|||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" py-1 flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Request Mode</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">{$i18n.t('Request Mode')}</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +104,7 @@
 | 
			
		|||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					{#if requestFormat === ''}
 | 
			
		||||
						<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
						<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
 | 
			
		||||
					{:else if requestFormat === 'json'}
 | 
			
		||||
						<!-- <svg
 | 
			
		||||
                            xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
| 
						 | 
				
			
			@ -114,7 +116,7 @@
 | 
			
		|||
                                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>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('JSON')}</span>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -147,7 +149,7 @@
 | 
			
		|||
				dispatch('save');
 | 
			
		||||
			}}
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
			{$i18n.t('Save')}
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,8 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let options = {
 | 
			
		||||
		// Advanced
 | 
			
		||||
		seed: 0,
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +24,7 @@
 | 
			
		|||
<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=" w-20 text-xs font-medium self-center">{$i18n.t('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"
 | 
			
		||||
| 
						 | 
				
			
			@ -36,12 +40,12 @@
 | 
			
		|||
 | 
			
		||||
	<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=" w-20 text-xs font-medium self-center">{$i18n.t('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"
 | 
			
		||||
					placeholder={$i18n.t('Enter stop sequence')}
 | 
			
		||||
					bind:value={options.stop}
 | 
			
		||||
					autocomplete="off"
 | 
			
		||||
				/>
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +55,7 @@
 | 
			
		|||
 | 
			
		||||
	<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>
 | 
			
		||||
			<div class=" self-center text-xs font-medium">{$i18n.t('Temperature')}</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -61,9 +65,9 @@
 | 
			
		|||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.temperature === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -97,7 +101,7 @@
 | 
			
		|||
 | 
			
		||||
	<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>
 | 
			
		||||
			<div class=" self-center text-xs font-medium">{$i18n.t('Mirostat')}</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -107,9 +111,9 @@
 | 
			
		|||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.mirostat === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -143,7 +147,7 @@
 | 
			
		|||
 | 
			
		||||
	<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>
 | 
			
		||||
			<div class=" self-center text-xs font-medium">{$i18n.t('Mirostat Eta')}</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -153,9 +157,9 @@
 | 
			
		|||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.mirostat_eta === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -189,7 +193,7 @@
 | 
			
		|||
 | 
			
		||||
	<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>
 | 
			
		||||
			<div class=" self-center text-xs font-medium">{$i18n.t('Mirostat Tau')}</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -199,9 +203,9 @@
 | 
			
		|||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.mirostat_tau === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -235,7 +239,7 @@
 | 
			
		|||
 | 
			
		||||
	<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>
 | 
			
		||||
			<div class=" self-center text-xs font-medium">{$i18n.t('Top K')}</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -245,9 +249,9 @@
 | 
			
		|||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.top_k === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -281,7 +285,7 @@
 | 
			
		|||
 | 
			
		||||
	<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>
 | 
			
		||||
			<div class=" self-center text-xs font-medium">{$i18n.t('Top P')}</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -291,9 +295,9 @@
 | 
			
		|||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.top_p === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -327,7 +331,7 @@
 | 
			
		|||
 | 
			
		||||
	<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>
 | 
			
		||||
			<div class=" self-center text-xs font-medium">{$i18n.t('Repeat Penalty')}</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -337,9 +341,9 @@
 | 
			
		|||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.repeat_penalty === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -373,7 +377,7 @@
 | 
			
		|||
 | 
			
		||||
	<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>
 | 
			
		||||
			<div class=" self-center text-xs font-medium">{$i18n.t('Repeat Last N')}</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -383,9 +387,9 @@
 | 
			
		|||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.repeat_last_n === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -419,7 +423,7 @@
 | 
			
		|||
 | 
			
		||||
	<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>
 | 
			
		||||
			<div class=" self-center text-xs font-medium">{$i18n.t('Tfs Z')}</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -429,9 +433,9 @@
 | 
			
		|||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.tfs_z === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -465,7 +469,7 @@
 | 
			
		|||
 | 
			
		||||
	<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>
 | 
			
		||||
			<div class=" self-center text-xs font-medium">{$i18n.t('Context Length')}</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -475,9 +479,9 @@
 | 
			
		|||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.num_ctx === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -510,7 +514,7 @@
 | 
			
		|||
	</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>
 | 
			
		||||
			<div class=" self-center text-xs font-medium">{$i18n.t('Max Tokens')}</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -520,9 +524,9 @@
 | 
			
		|||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				{#if options.num_predict === ''}
 | 
			
		||||
					<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,10 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	import { createEventDispatcher, onMount, getContext } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let saveSettings: Function;
 | 
			
		||||
 | 
			
		||||
	// Audio
 | 
			
		||||
| 
						 | 
				
			
			@ -101,32 +103,36 @@
 | 
			
		|||
>
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-1 text-sm font-medium">STT Settings</div>
 | 
			
		||||
			<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
 | 
			
		||||
 | 
			
		||||
			<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">Speech-to-Text Engine</div>
 | 
			
		||||
				<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
 | 
			
		||||
				<div class="flex items-center relative">
 | 
			
		||||
					<select
 | 
			
		||||
						class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
 | 
			
		||||
						class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
 | 
			
		||||
						bind:value={STTEngine}
 | 
			
		||||
						placeholder="Select a mode"
 | 
			
		||||
						on:change={(e) => {
 | 
			
		||||
							if (e.target.value !== '') {
 | 
			
		||||
								navigator.mediaDevices.getUserMedia({ audio: true }).catch(function (err) {
 | 
			
		||||
									toast.error(`Permission denied when accessing microphone: ${err}`);
 | 
			
		||||
									toast.error(
 | 
			
		||||
										$i18n.t(`Permission denied when accessing microphone: {{error}}`, {
 | 
			
		||||
											error: err
 | 
			
		||||
										})
 | 
			
		||||
									);
 | 
			
		||||
									STTEngine = '';
 | 
			
		||||
								});
 | 
			
		||||
							}
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						<option value="">Default (Web API)</option>
 | 
			
		||||
						<option value="whisper-local">Whisper (Local)</option>
 | 
			
		||||
						<option value="">{$i18n.t('Default (Web API)')}</option>
 | 
			
		||||
						<option value="whisper-local">{$i18n.t('Whisper (Local)')}</option>
 | 
			
		||||
					</select>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">Conversation Mode</div>
 | 
			
		||||
				<div class=" self-center text-xs font-medium">{$i18n.t('Conversation Mode')}</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -136,15 +142,17 @@
 | 
			
		|||
					type="button"
 | 
			
		||||
				>
 | 
			
		||||
					{#if conversationMode === true}
 | 
			
		||||
						<span class="ml-2 self-center">On</span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('On')}</span>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<span class="ml-2 self-center">Off</span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">Auto-send input after 3 sec.</div>
 | 
			
		||||
				<div class=" self-center text-xs font-medium">
 | 
			
		||||
					{$i18n.t('Auto-send input after 3 sec.')}
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -154,22 +162,22 @@
 | 
			
		|||
					type="button"
 | 
			
		||||
				>
 | 
			
		||||
					{#if speechAutoSend === true}
 | 
			
		||||
						<span class="ml-2 self-center">On</span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('On')}</span>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<span class="ml-2 self-center">Off</span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-1 text-sm font-medium">TTS Settings</div>
 | 
			
		||||
			<div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>
 | 
			
		||||
 | 
			
		||||
			<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">Text-to-Speech Engine</div>
 | 
			
		||||
				<div class=" self-center text-xs font-medium">{$i18n.t('Text-to-Speech Engine')}</div>
 | 
			
		||||
				<div class="flex items-center relative">
 | 
			
		||||
					<select
 | 
			
		||||
						class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
 | 
			
		||||
						class=" dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
 | 
			
		||||
						bind:value={TTSEngine}
 | 
			
		||||
						placeholder="Select a mode"
 | 
			
		||||
						on:change={(e) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -182,14 +190,14 @@
 | 
			
		|||
							}
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						<option value="">Default (Web API)</option>
 | 
			
		||||
						<option value="openai">Open AI</option>
 | 
			
		||||
						<option value="">{$i18n.t('Default (Web API)')}</option>
 | 
			
		||||
						<option value="openai">{$i18n.t('Open AI')}</option>
 | 
			
		||||
					</select>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">Auto-playback response</div>
 | 
			
		||||
				<div class=" self-center text-xs font-medium">{$i18n.t('Auto-playback response')}</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -199,9 +207,9 @@
 | 
			
		|||
					type="button"
 | 
			
		||||
				>
 | 
			
		||||
					{#if responseAutoPlayback === true}
 | 
			
		||||
						<span class="ml-2 self-center">On</span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('On')}</span>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<span class="ml-2 self-center">Off</span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -211,7 +219,7 @@
 | 
			
		|||
 | 
			
		||||
		{#if TTSEngine === ''}
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">Set Voice</div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
 | 
			
		||||
				<div class="flex w-full">
 | 
			
		||||
					<div class="flex-1">
 | 
			
		||||
						<select
 | 
			
		||||
| 
						 | 
				
			
			@ -219,7 +227,7 @@
 | 
			
		|||
							bind:value={speaker}
 | 
			
		||||
							placeholder="Select a voice"
 | 
			
		||||
						>
 | 
			
		||||
							<option value="" selected>Default</option>
 | 
			
		||||
							<option value="" selected>{$i18n.t('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
 | 
			
		||||
								>
 | 
			
		||||
| 
						 | 
				
			
			@ -230,7 +238,7 @@
 | 
			
		|||
			</div>
 | 
			
		||||
		{:else if TTSEngine === 'openai'}
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">Set Voice</div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
 | 
			
		||||
				<div class="flex w-full">
 | 
			
		||||
					<div class="flex-1">
 | 
			
		||||
						<select
 | 
			
		||||
| 
						 | 
				
			
			@ -251,10 +259,10 @@
 | 
			
		|||
 | 
			
		||||
	<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"
 | 
			
		||||
			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 | 
			
		||||
			type="submit"
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
			{$i18n.t('Save')}
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,10 +13,12 @@
 | 
			
		|||
		getChatList
 | 
			
		||||
	} from '$lib/apis/chats';
 | 
			
		||||
	import { getImportOrigin, convertOpenAIChats } from '$lib/utils';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
	import { goto } from '$app/navigation';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let saveSettings: Function;
 | 
			
		||||
	// Chats
 | 
			
		||||
	let saveChatHistory = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -99,13 +101,13 @@
 | 
			
		|||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="flex flex-col h-full justify-between space-y-3 text-sm">
 | 
			
		||||
<div class="flex flex-col h-full justify-between space-y-3 text-sm max-h-[22rem]">
 | 
			
		||||
	<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>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">{$i18n.t('Chat History')}</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -129,7 +131,7 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
 | 
			
		||||
						<span class="ml-2 self-center"> On </span>
 | 
			
		||||
						<span class="ml-2 self-center"> {$i18n.t('On')} </span>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<svg
 | 
			
		||||
							xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
| 
						 | 
				
			
			@ -147,13 +149,13 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
 | 
			
		||||
						<span class="ml-2 self-center">Off</span>
 | 
			
		||||
						<span class="ml-2 self-center">{$i18n.t('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.
 | 
			
		||||
				{$i18n.t('This setting does not sync across browsers or devices.')}
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -188,7 +190,7 @@
 | 
			
		|||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Import Chats</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">{$i18n.t('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"
 | 
			
		||||
| 
						 | 
				
			
			@ -210,7 +212,7 @@
 | 
			
		|||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Export Chats</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">{$i18n.t('Export Chats')}</div>
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -232,7 +234,7 @@
 | 
			
		|||
							clip-rule="evenodd"
 | 
			
		||||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
					<span>Are you sure?</span>
 | 
			
		||||
					<span>{$i18n.t('Are you sure?')}</span>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="flex space-x-1.5 items-center">
 | 
			
		||||
| 
						 | 
				
			
			@ -296,7 +298,7 @@
 | 
			
		|||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Delete Chats</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">{$i18n.t('Delete Chats')}</div>
 | 
			
		||||
			</button>
 | 
			
		||||
		{/if}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -324,7 +326,9 @@
 | 
			
		|||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Export All Chats (All Users)</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">
 | 
			
		||||
					{$i18n.t('Export All Chats (All Users)')}
 | 
			
		||||
				</div>
 | 
			
		||||
			</button>
 | 
			
		||||
 | 
			
		||||
			<hr class=" dark:border-gray-700" />
 | 
			
		||||
| 
						 | 
				
			
			@ -338,7 +342,7 @@
 | 
			
		|||
					});
 | 
			
		||||
 | 
			
		||||
					if (res) {
 | 
			
		||||
						toast.success('Success');
 | 
			
		||||
						toast.success($i18n.t('Success'));
 | 
			
		||||
					}
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
| 
						 | 
				
			
			@ -356,7 +360,7 @@
 | 
			
		|||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">Reset Vector Storage</div>
 | 
			
		||||
				<div class=" self-center text-sm font-medium">{$i18n.t('Reset Vector Storage')}</div>
 | 
			
		||||
			</button>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { models, user } from '$lib/stores';
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	import { createEventDispatcher, onMount, getContext } from 'svelte';
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama';
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +12,8 @@
 | 
			
		|||
	} from '$lib/apis/openai';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let getModels: Function;
 | 
			
		||||
 | 
			
		||||
	// External
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +44,7 @@
 | 
			
		|||
		});
 | 
			
		||||
 | 
			
		||||
		if (ollamaVersion) {
 | 
			
		||||
			toast.success('Server connection verified');
 | 
			
		||||
			toast.success($i18n.t('Server connection verified'));
 | 
			
		||||
			await models.set(await getModels());
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
| 
						 | 
				
			
			@ -63,17 +65,17 @@
 | 
			
		|||
		dispatch('save');
 | 
			
		||||
	}}
 | 
			
		||||
>
 | 
			
		||||
	<div class="  pr-1.5 overflow-y-scroll max-h-[20.5rem] space-y-3">
 | 
			
		||||
	<div class="  pr-1.5 overflow-y-scroll max-h-[22rem] space-y-3">
 | 
			
		||||
		<div class=" space-y-3">
 | 
			
		||||
			<div class="mt-2 space-y-2 pr-1.5">
 | 
			
		||||
				<div class="flex justify-between items-center text-sm">
 | 
			
		||||
					<div class="  font-medium">OpenAI API</div>
 | 
			
		||||
					<div class="  font-medium">{$i18n.t('OpenAI API')}</div>
 | 
			
		||||
					<button
 | 
			
		||||
						class=" text-xs font-medium text-gray-500"
 | 
			
		||||
						type="button"
 | 
			
		||||
						on:click={() => {
 | 
			
		||||
							showOpenAI = !showOpenAI;
 | 
			
		||||
						}}>{showOpenAI ? 'Hide' : 'Show'}</button
 | 
			
		||||
						}}>{showOpenAI ? $i18n.t('Hide') : $i18n.t('Show')}</button
 | 
			
		||||
					>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +86,7 @@
 | 
			
		|||
								<div class="flex-1">
 | 
			
		||||
									<input
 | 
			
		||||
										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
										placeholder="API Base URL"
 | 
			
		||||
										placeholder={$i18n.t('API Base URL')}
 | 
			
		||||
										bind:value={url}
 | 
			
		||||
										autocomplete="off"
 | 
			
		||||
									/>
 | 
			
		||||
| 
						 | 
				
			
			@ -93,7 +95,7 @@
 | 
			
		|||
								<div class="flex-1">
 | 
			
		||||
									<input
 | 
			
		||||
										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
										placeholder="API Key"
 | 
			
		||||
										placeholder={$i18n.t('API Key')}
 | 
			
		||||
										bind:value={OPENAI_API_KEYS[idx]}
 | 
			
		||||
										autocomplete="off"
 | 
			
		||||
									/>
 | 
			
		||||
| 
						 | 
				
			
			@ -143,7 +145,8 @@
 | 
			
		|||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
							<div class=" mb-1 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
								WebUI will make requests to <span class=" text-gray-200">'{url}/models'</span>
 | 
			
		||||
								{$i18n.t('WebUI will make requests to')}
 | 
			
		||||
								<span class=" text-gray-200">'{url}/models'</span>
 | 
			
		||||
							</div>
 | 
			
		||||
						{/each}
 | 
			
		||||
					</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -154,7 +157,7 @@
 | 
			
		|||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">Ollama Base URL</div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama Base URL')}</div>
 | 
			
		||||
			<div class="flex w-full gap-1.5">
 | 
			
		||||
				<div class="flex-1 flex flex-col gap-2">
 | 
			
		||||
					{#each OLLAMA_BASE_URLS as url, idx}
 | 
			
		||||
| 
						 | 
				
			
			@ -233,13 +236,13 @@
 | 
			
		|||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
				Trouble accessing Ollama?
 | 
			
		||||
				{$i18n.t('Trouble accessing Ollama?')}
 | 
			
		||||
				<a
 | 
			
		||||
					class=" text-gray-300 font-medium"
 | 
			
		||||
					class=" text-gray-300 font-medium underline"
 | 
			
		||||
					href="https://github.com/open-webui/open-webui#troubleshooting"
 | 
			
		||||
					target="_blank"
 | 
			
		||||
				>
 | 
			
		||||
					Click here for help.
 | 
			
		||||
					{$i18n.t('Click here for help.')}
 | 
			
		||||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -247,10 +250,10 @@
 | 
			
		|||
 | 
			
		||||
	<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"
 | 
			
		||||
			class="  px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 | 
			
		||||
			type="submit"
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
			{$i18n.t('Save')}
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,14 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	import { theme, setTheme } from '../../../stores/index';
 | 
			
		||||
	import { createEventDispatcher, onMount, getContext } from 'svelte';
 | 
			
		||||
	import { getLanguages } from '$lib/i18n';
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	import { models, user } from '$lib/stores';
 | 
			
		||||
 
 | 
			
		||||
	import { theme, setTheme } from '../../../stores/index';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	import AdvancedParams from './Advanced/AdvancedParams.svelte';
 | 
			
		||||
 | 
			
		||||
	export let saveSettings: Function;
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +19,9 @@
 | 
			
		|||
	let selectedTheme = 'system';
 | 
			
		||||
	let actualTheme: string;
 | 
			
		||||
	$: actualTheme = $theme;
 | 
			
		||||
  
 | 
			
		||||
	let languages = [];
 | 
			
		||||
	let lang = $i18n.language;
 | 
			
		||||
	let notificationEnabled = false;
 | 
			
		||||
	let system = '';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -91,6 +97,7 @@
 | 
			
		|||
		applyTheme(selectedTheme);
 | 
			
		||||
 | 
			
		||||
		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
 | 
			
		||||
		languages = await getLanguages();
 | 
			
		||||
 | 
			
		||||
		notificationEnabled = settings.notificationEnabled ?? false;
 | 
			
		||||
		system = settings.system ?? '';
 | 
			
		||||
| 
						 | 
				
			
			@ -117,59 +124,61 @@
 | 
			
		|||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="flex flex-col h-full justify-between text-sm">
 | 
			
		||||
	<div class="  pr-1.5 overflow-y-scroll max-h-[20.5rem]">
 | 
			
		||||
	<div class="  pr-1.5 overflow-y-scroll max-h-[22rem]">
 | 
			
		||||
		<div class="">
 | 
			
		||||
			<div class=" mb-1 text-sm font-medium">WebUI Settings</div>
 | 
			
		||||
			<div class=" mb-1 text-sm font-medium">{$i18n.t('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 w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">{$i18n.t('Theme')}</div>
 | 
			
		||||
				<div class="flex items-center relative">
 | 
			
		||||
					<div class=" absolute right-16">
 | 
			
		||||
						{#if actualTheme === '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 actualTheme === '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={selectedTheme}
 | 
			
		||||
						class=" dark:bg-gray-900 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="{() => handleThemeChange(selectedTheme)}"
 | 
			
		||||
					>
 | 
			
		||||
						<option value="system">System</option>
 | 
			
		||||
						<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>
 | 
			
		||||
      			<option value="system">System</option>
 | 
			
		||||
						<option value="dark">🌑 {$i18n.t('Dark')}</option>
 | 
			
		||||
						<option value="light">☀️ {$i18n.t('Light')}</option>
 | 
			
		||||
						<option value="rose-pine dark">🪻 {$i18n.t('Rosé Pine')}</option>
 | 
			
		||||
						<option value="rose-pine-dawn light">🌷 {$i18n.t('Rosé Pine Dawn')}</option>
 | 
			
		||||
					</select>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class=" flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">{$i18n.t('Language')}</div>
 | 
			
		||||
				<div class="flex items-center relative">
 | 
			
		||||
					<select
 | 
			
		||||
						class=" dark:bg-gray-900 w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
 | 
			
		||||
						bind:value={lang}
 | 
			
		||||
						placeholder="Select a language"
 | 
			
		||||
						on:change={(e) => {
 | 
			
		||||
							$i18n.changeLanguage(lang);
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						{#each languages as language}
 | 
			
		||||
							<option value={language['code']}>{language['title']}</option>
 | 
			
		||||
						{/each}
 | 
			
		||||
					</select>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			{#if $i18n.language === 'en-US'}
 | 
			
		||||
				<div class="mb-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
					Couldn't find your language?
 | 
			
		||||
					<a
 | 
			
		||||
						class=" text-gray-300 font-medium underline"
 | 
			
		||||
						href="https://github.com/open-webui/open-webui/blob/main/docs/CONTRIBUTING.md#-translations-and-internationalization"
 | 
			
		||||
						target="_blank"
 | 
			
		||||
					>
 | 
			
		||||
						Help us translate Open WebUI!
 | 
			
		||||
					</a>
 | 
			
		||||
				</div>
 | 
			
		||||
			{/if}
 | 
			
		||||
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
					<div class=" self-center text-xs font-medium">Notification</div>
 | 
			
		||||
					<div class=" self-center text-xs font-medium">{$i18n.t('Desktop Notifications')}</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
						class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -179,9 +188,9 @@
 | 
			
		|||
						type="button"
 | 
			
		||||
					>
 | 
			
		||||
						{#if notificationEnabled === true}
 | 
			
		||||
							<span class="ml-2 self-center">On</span>
 | 
			
		||||
							<span class="ml-2 self-center">{$i18n.t('On')}</span>
 | 
			
		||||
						{:else}
 | 
			
		||||
							<span class="ml-2 self-center">Off</span>
 | 
			
		||||
							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -191,7 +200,7 @@
 | 
			
		|||
		<hr class=" dark:border-gray-700 my-3" />
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" my-2.5 text-sm font-medium">System Prompt</div>
 | 
			
		||||
			<div class=" my-2.5 text-sm font-medium">{$i18n.t('System Prompt')}</div>
 | 
			
		||||
			<textarea
 | 
			
		||||
				bind:value={system}
 | 
			
		||||
				class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
 | 
			
		||||
| 
						 | 
				
			
			@ -201,13 +210,13 @@
 | 
			
		|||
 | 
			
		||||
		<div class="mt-2 space-y-3 pr-1.5">
 | 
			
		||||
			<div class="flex justify-between items-center text-sm">
 | 
			
		||||
				<div class="  font-medium">Advanced Parameters</div>
 | 
			
		||||
				<div class="  font-medium">{$i18n.t('Advanced Parameters')}</div>
 | 
			
		||||
				<button
 | 
			
		||||
					class=" text-xs font-medium text-gray-500"
 | 
			
		||||
					type="button"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						showAdvanced = !showAdvanced;
 | 
			
		||||
					}}>{showAdvanced ? 'Hide' : 'Show'}</button
 | 
			
		||||
					}}>{showAdvanced ? $i18n.t('Hide') : $i18n.t('Show')}</button
 | 
			
		||||
				>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -217,7 +226,7 @@
 | 
			
		|||
 | 
			
		||||
				<div class=" py-1 w-full justify-between">
 | 
			
		||||
					<div class="flex w-full justify-between">
 | 
			
		||||
						<div class=" self-center text-xs font-medium">Keep Alive</div>
 | 
			
		||||
						<div class=" self-center text-xs font-medium">{$i18n.t('Keep Alive')}</div>
 | 
			
		||||
 | 
			
		||||
						<button
 | 
			
		||||
							class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -227,9 +236,9 @@
 | 
			
		|||
							}}
 | 
			
		||||
						>
 | 
			
		||||
							{#if keepAlive === null}
 | 
			
		||||
								<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
								<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
 | 
			
		||||
							{:else}
 | 
			
		||||
								<span class="ml-2 self-center"> Custom </span>
 | 
			
		||||
								<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
 | 
			
		||||
							{/if}
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -239,7 +248,7 @@
 | 
			
		|||
							<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={`e.g.) "30s","10m". Valid time units are "s", "m", "h".`}
 | 
			
		||||
								placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
 | 
			
		||||
								bind:value={keepAlive}
 | 
			
		||||
							/>
 | 
			
		||||
						</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -248,7 +257,7 @@
 | 
			
		|||
 | 
			
		||||
				<div>
 | 
			
		||||
					<div class=" py-1 flex w-full justify-between">
 | 
			
		||||
						<div class=" self-center text-sm font-medium">Request Mode</div>
 | 
			
		||||
						<div class=" self-center text-sm font-medium">{$i18n.t('Request Mode')}</div>
 | 
			
		||||
 | 
			
		||||
						<button
 | 
			
		||||
							class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -257,7 +266,7 @@
 | 
			
		|||
							}}
 | 
			
		||||
						>
 | 
			
		||||
							{#if requestFormat === ''}
 | 
			
		||||
								<span class="ml-2 self-center"> Default </span>
 | 
			
		||||
								<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
 | 
			
		||||
							{:else if requestFormat === 'json'}
 | 
			
		||||
								<!-- <svg
 | 
			
		||||
                            xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
| 
						 | 
				
			
			@ -269,7 +278,7 @@
 | 
			
		|||
                                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>
 | 
			
		||||
								<span class="ml-2 self-center"> {$i18n.t('JSON')} </span>
 | 
			
		||||
							{/if}
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -280,7 +289,7 @@
 | 
			
		|||
 | 
			
		||||
	<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"
 | 
			
		||||
			class="  px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 | 
			
		||||
			on:click={() => {
 | 
			
		||||
				saveSettings({
 | 
			
		||||
					system: system !== '' ? system : undefined,
 | 
			
		||||
| 
						 | 
				
			
			@ -304,7 +313,7 @@
 | 
			
		|||
				dispatch('save');
 | 
			
		||||
			}}
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
			{$i18n.t('Save')}
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	import { createEventDispatcher, onMount, getContext } from 'svelte';
 | 
			
		||||
	import { config, user } from '$lib/stores';
 | 
			
		||||
	import {
 | 
			
		||||
		getAUTOMATIC1111Url,
 | 
			
		||||
| 
						 | 
				
			
			@ -21,6 +21,8 @@
 | 
			
		|||
	import { getBackendConfig } from '$lib/apis';
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let saveSettings: Function;
 | 
			
		||||
 | 
			
		||||
	let loading = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +63,7 @@
 | 
			
		|||
			await getModels();
 | 
			
		||||
 | 
			
		||||
			if (models) {
 | 
			
		||||
				toast.success('Server connection verified');
 | 
			
		||||
				toast.success($i18n.t('Server connection verified'));
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
 | 
			
		||||
| 
						 | 
				
			
			@ -116,11 +118,13 @@
 | 
			
		|||
	class="flex flex-col h-full justify-between space-y-3 text-sm"
 | 
			
		||||
	on:submit|preventDefault={async () => {
 | 
			
		||||
		loading = true;
 | 
			
		||||
		await updateOpenAIKey(localStorage.token, OPENAI_API_KEY);
 | 
			
		||||
 | 
			
		||||
		if (imageGenerationEngine === 'openai') {
 | 
			
		||||
			await updateOpenAIKey(localStorage.token, OPENAI_API_KEY);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await updateDefaultImageGenerationModel(localStorage.token, selectedModel);
 | 
			
		||||
 | 
			
		||||
		await updateDefaultImageGenerationModel(localStorage.token, selectedModel);
 | 
			
		||||
		await updateImageSize(localStorage.token, imageSize).catch((error) => {
 | 
			
		||||
			toast.error(error);
 | 
			
		||||
			return null;
 | 
			
		||||
| 
						 | 
				
			
			@ -134,39 +138,41 @@
 | 
			
		|||
		loading = false;
 | 
			
		||||
	}}
 | 
			
		||||
>
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[20.5rem]">
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[24rem]">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-1 text-sm font-medium">Image Settings</div>
 | 
			
		||||
			<div class=" mb-1 text-sm font-medium">{$i18n.t('Image Settings')}</div>
 | 
			
		||||
 | 
			
		||||
			<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">Image Generation Engine</div>
 | 
			
		||||
				<div class=" self-center text-xs font-medium">{$i18n.t('Image Generation Engine')}</div>
 | 
			
		||||
				<div class="flex items-center relative">
 | 
			
		||||
					<select
 | 
			
		||||
						class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
 | 
			
		||||
						bind:value={imageGenerationEngine}
 | 
			
		||||
						placeholder="Select a mode"
 | 
			
		||||
						placeholder={$i18n.t('Select a mode')}
 | 
			
		||||
						on:change={async () => {
 | 
			
		||||
							await updateImageGeneration();
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						<option value="">Default (Automatic1111)</option>
 | 
			
		||||
						<option value="openai">Open AI (Dall-E)</option>
 | 
			
		||||
						<option value="">{$i18n.t('Default (Automatic1111)')}</option>
 | 
			
		||||
						<option value="openai">{$i18n.t('Open AI (Dall-E)')}</option>
 | 
			
		||||
					</select>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
					<div class=" self-center text-xs font-medium">Image Generation (Experimental)</div>
 | 
			
		||||
					<div class=" self-center text-xs font-medium">
 | 
			
		||||
						{$i18n.t('Image Generation (Experimental)')}
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
						class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
						on:click={() => {
 | 
			
		||||
							if (imageGenerationEngine === '' && AUTOMATIC1111_BASE_URL === '') {
 | 
			
		||||
								toast.error('AUTOMATIC1111 Base URL is required.');
 | 
			
		||||
								toast.error($i18n.t('AUTOMATIC1111 Base URL is required.'));
 | 
			
		||||
								enableImageGeneration = false;
 | 
			
		||||
							} else if (imageGenerationEngine === 'openai' && OPENAI_API_KEY === '') {
 | 
			
		||||
								toast.error('OpenAI API Key is required.');
 | 
			
		||||
								toast.error($i18n.t('OpenAI API Key is required.'));
 | 
			
		||||
								enableImageGeneration = false;
 | 
			
		||||
							} else {
 | 
			
		||||
								enableImageGeneration = !enableImageGeneration;
 | 
			
		||||
| 
						 | 
				
			
			@ -177,9 +183,9 @@
 | 
			
		|||
						type="button"
 | 
			
		||||
					>
 | 
			
		||||
						{#if enableImageGeneration === true}
 | 
			
		||||
							<span class="ml-2 self-center">On</span>
 | 
			
		||||
							<span class="ml-2 self-center">{$i18n.t('On')}</span>
 | 
			
		||||
						{:else}
 | 
			
		||||
							<span class="ml-2 self-center">Off</span>
 | 
			
		||||
							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -188,12 +194,12 @@
 | 
			
		|||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		{#if imageGenerationEngine === ''}
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">AUTOMATIC1111 Base URL</div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">{$i18n.t('AUTOMATIC1111 Base URL')}</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1 mr-2">
 | 
			
		||||
					<input
 | 
			
		||||
						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
						placeholder="Enter URL (e.g. http://127.0.0.1:7860/)"
 | 
			
		||||
						placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
 | 
			
		||||
						bind:value={AUTOMATIC1111_BASE_URL}
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -222,22 +228,22 @@
 | 
			
		|||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
				Include `--api` flag when running stable-diffusion-webui
 | 
			
		||||
				{$i18n.t('Include `--api` flag when running stable-diffusion-webui')}
 | 
			
		||||
				<a
 | 
			
		||||
					class=" text-gray-300 font-medium"
 | 
			
		||||
					href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/3734"
 | 
			
		||||
					target="_blank"
 | 
			
		||||
				>
 | 
			
		||||
					(e.g. `sh webui.sh --api`)
 | 
			
		||||
					{$i18n.t('(e.g. `sh webui.sh --api`)')}
 | 
			
		||||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
		{:else if imageGenerationEngine === 'openai'}
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">OpenAI API Key</div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">{$i18n.t('OpenAI API Key')}</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1 mr-2">
 | 
			
		||||
					<input
 | 
			
		||||
						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
						placeholder="Enter API Key"
 | 
			
		||||
						placeholder={$i18n.t('Enter API Key')}
 | 
			
		||||
						bind:value={OPENAI_API_KEY}
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -248,16 +254,16 @@
 | 
			
		|||
			<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">Set Default Model</div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Default Model')}</div>
 | 
			
		||||
				<div class="flex w-full">
 | 
			
		||||
					<div class="flex-1 mr-2">
 | 
			
		||||
						<select
 | 
			
		||||
							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
							bind:value={selectedModel}
 | 
			
		||||
							placeholder="Select a model"
 | 
			
		||||
							placeholder={$i18n.t('Select a model')}
 | 
			
		||||
						>
 | 
			
		||||
							{#if !selectedModel}
 | 
			
		||||
								<option value="" disabled selected>Select a model</option>
 | 
			
		||||
								<option value="" disabled selected>{$i18n.t('Select a model')}</option>
 | 
			
		||||
							{/if}
 | 
			
		||||
							{#each models ?? [] as model}
 | 
			
		||||
								<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
 | 
			
		||||
| 
						 | 
				
			
			@ -268,12 +274,12 @@
 | 
			
		|||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">Set Image Size</div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Image Size')}</div>
 | 
			
		||||
				<div class="flex w-full">
 | 
			
		||||
					<div class="flex-1 mr-2">
 | 
			
		||||
						<input
 | 
			
		||||
							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
							placeholder="Enter Image Size (e.g. 512x512)"
 | 
			
		||||
							placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
 | 
			
		||||
							bind:value={imageSize}
 | 
			
		||||
						/>
 | 
			
		||||
					</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -281,12 +287,12 @@
 | 
			
		|||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">Set Steps</div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Steps')}</div>
 | 
			
		||||
				<div class="flex w-full">
 | 
			
		||||
					<div class="flex-1 mr-2">
 | 
			
		||||
						<input
 | 
			
		||||
							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
							placeholder="Enter Number of Steps (e.g. 50)"
 | 
			
		||||
							placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
 | 
			
		||||
							bind:value={steps}
 | 
			
		||||
						/>
 | 
			
		||||
					</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -297,13 +303,13 @@
 | 
			
		|||
 | 
			
		||||
	<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 flex flex-row space-x-1 items-center {loading
 | 
			
		||||
			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg flex flex-row space-x-1 items-center {loading
 | 
			
		||||
				? ' cursor-not-allowed'
 | 
			
		||||
				: ''}"
 | 
			
		||||
			type="submit"
 | 
			
		||||
			disabled={loading}
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
			{$i18n.t('Save')}
 | 
			
		||||
 | 
			
		||||
			{#if loading}
 | 
			
		||||
				<div class="ml-2 self-center">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,10 +2,12 @@
 | 
			
		|||
	import { getBackendConfig } from '$lib/apis';
 | 
			
		||||
	import { setDefaultPromptSuggestions } from '$lib/apis/configs';
 | 
			
		||||
	import { config, models, user } from '$lib/stores';
 | 
			
		||||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	import { createEventDispatcher, onMount, getContext } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let saveSettings: Function;
 | 
			
		||||
 | 
			
		||||
	// Addons
 | 
			
		||||
| 
						 | 
				
			
			@ -63,6 +65,7 @@
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		saveSettings({
 | 
			
		||||
			titleAutoGenerateModel: titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined,
 | 
			
		||||
			titleGenerationPrompt: titleGenerationPrompt ? titleGenerationPrompt : undefined
 | 
			
		||||
		});
 | 
			
		||||
	};
 | 
			
		||||
| 
						 | 
				
			
			@ -81,7 +84,9 @@
 | 
			
		|||
		titleAutoGenerateModel = settings.titleAutoGenerateModel ?? '';
 | 
			
		||||
		titleGenerationPrompt =
 | 
			
		||||
			settings.titleGenerationPrompt ??
 | 
			
		||||
			`Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title': {{prompt}}`;
 | 
			
		||||
			$i18n.t(
 | 
			
		||||
				"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':"
 | 
			
		||||
			) + ' {{prompt}}';
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -92,13 +97,13 @@
 | 
			
		|||
		dispatch('save');
 | 
			
		||||
	}}
 | 
			
		||||
>
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll h-80">
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-1 text-sm font-medium">WebUI Add-ons</div>
 | 
			
		||||
			<div class=" mb-1 text-sm font-medium">{$i18n.t('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>
 | 
			
		||||
					<div class=" self-center text-xs font-medium">{$i18n.t('Title Auto-Generation')}</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
						class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -108,9 +113,9 @@
 | 
			
		|||
						type="button"
 | 
			
		||||
					>
 | 
			
		||||
						{#if titleAutoGenerate === true}
 | 
			
		||||
							<span class="ml-2 self-center">On</span>
 | 
			
		||||
							<span class="ml-2 self-center">{$i18n.t('On')}</span>
 | 
			
		||||
						{:else}
 | 
			
		||||
							<span class="ml-2 self-center">Off</span>
 | 
			
		||||
							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -118,7 +123,9 @@
 | 
			
		|||
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
					<div class=" self-center text-xs font-medium">Response AutoCopy to Clipboard</div>
 | 
			
		||||
					<div class=" self-center text-xs font-medium">
 | 
			
		||||
						{$i18n.t('Response AutoCopy to Clipboard')}
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
						class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -128,9 +135,9 @@
 | 
			
		|||
						type="button"
 | 
			
		||||
					>
 | 
			
		||||
						{#if responseAutoCopy === true}
 | 
			
		||||
							<span class="ml-2 self-center">On</span>
 | 
			
		||||
							<span class="ml-2 self-center">{$i18n.t('On')}</span>
 | 
			
		||||
						{:else}
 | 
			
		||||
							<span class="ml-2 self-center">Off</span>
 | 
			
		||||
							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -138,7 +145,7 @@
 | 
			
		|||
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
					<div class=" self-center text-xs font-medium">Full Screen Mode</div>
 | 
			
		||||
					<div class=" self-center text-xs font-medium">{$i18n.t('Full Screen Mode')}</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
						class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -148,9 +155,9 @@
 | 
			
		|||
						type="button"
 | 
			
		||||
					>
 | 
			
		||||
						{#if fullScreenMode === true}
 | 
			
		||||
							<span class="ml-2 self-center">On</span>
 | 
			
		||||
							<span class="ml-2 self-center">{$i18n.t('On')}</span>
 | 
			
		||||
						{:else}
 | 
			
		||||
							<span class="ml-2 self-center">Off</span>
 | 
			
		||||
							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -159,7 +166,7 @@
 | 
			
		|||
			<div>
 | 
			
		||||
				<div class=" py-0.5 flex w-full justify-between">
 | 
			
		||||
					<div class=" self-center text-xs font-medium">
 | 
			
		||||
						Display the username instead of "You" in the Chat
 | 
			
		||||
						{$i18n.t('Display the username instead of You in the Chat')}
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
| 
						 | 
				
			
			@ -170,9 +177,9 @@
 | 
			
		|||
						type="button"
 | 
			
		||||
					>
 | 
			
		||||
						{#if showUsername === true}
 | 
			
		||||
							<span class="ml-2 self-center">On</span>
 | 
			
		||||
							<span class="ml-2 self-center">{$i18n.t('On')}</span>
 | 
			
		||||
						{:else}
 | 
			
		||||
							<span class="ml-2 self-center">Off</span>
 | 
			
		||||
							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -182,15 +189,15 @@
 | 
			
		|||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">Set Title Auto-Generation Model</div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">{$i18n.t('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"
 | 
			
		||||
						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
						bind:value={titleAutoGenerateModel}
 | 
			
		||||
						placeholder="Select a model"
 | 
			
		||||
						placeholder={$i18n.t('Select a model')}
 | 
			
		||||
					>
 | 
			
		||||
						<option value="" selected>Current Model</option>
 | 
			
		||||
						<option value="" selected>{$i18n.t('Current Model')}</option>
 | 
			
		||||
						{#each $models as model}
 | 
			
		||||
							{#if model.size != null}
 | 
			
		||||
								<option value={model.name} class="bg-gray-100 dark:bg-gray-700">
 | 
			
		||||
| 
						 | 
				
			
			@ -200,35 +207,13 @@
 | 
			
		|||
						{/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 class="mt-3">
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">Title Generation Prompt</div>
 | 
			
		||||
 | 
			
		||||
			<div class="mt-3 mr-2">
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Title Generation Prompt')}</div>
 | 
			
		||||
				<textarea
 | 
			
		||||
					bind:value={titleGenerationPrompt}
 | 
			
		||||
					class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
 | 
			
		||||
					class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
 | 
			
		||||
					rows="3"
 | 
			
		||||
				/>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -239,7 +224,9 @@
 | 
			
		|||
 | 
			
		||||
			<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>
 | 
			
		||||
					<div class=" self-center text-sm font-semibold">
 | 
			
		||||
						{$i18n.t('Default Prompt Suggestions')}
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
						class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -312,19 +299,19 @@
 | 
			
		|||
 | 
			
		||||
				{#if promptSuggestions.length > 0}
 | 
			
		||||
					<div class="text-xs text-left w-full mt-2">
 | 
			
		||||
						Adjusting these settings will apply changes universally to all users.
 | 
			
		||||
						{$i18n.t('Adjusting these settings will apply changes universally to all users.')}
 | 
			
		||||
					</div>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="flex justify-end pt-3 text-sm font-medium">
 | 
			
		||||
	<div class="flex justify-end text-sm font-medium">
 | 
			
		||||
		<button
 | 
			
		||||
			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 | 
			
		||||
			type="submit"
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
			{$i18n.t('Save')}
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,8 +12,11 @@
 | 
			
		|||
	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
 | 
			
		||||
	import { WEBUI_NAME, models, user } from '$lib/stores';
 | 
			
		||||
	import { splitStream } from '$lib/utils';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
	import { addLiteLLMModel, deleteLiteLLMModel, getLiteLLMModelInfo } from '$lib/apis/litellm';
 | 
			
		||||
	import Tooltip from '$lib/components/common/Tooltip.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let getModels: Function;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +40,10 @@
 | 
			
		|||
 | 
			
		||||
	let OLLAMA_URLS = [];
 | 
			
		||||
	let selectedOllamaUrlIdx: string | null = null;
 | 
			
		||||
 | 
			
		||||
	let updateModelId = null;
 | 
			
		||||
	let updateProgress = null;
 | 
			
		||||
 | 
			
		||||
	let showExperimentalOllama = false;
 | 
			
		||||
	let ollamaVersion = '';
 | 
			
		||||
	const MAX_PARALLEL_DOWNLOADS = 3;
 | 
			
		||||
| 
						 | 
				
			
			@ -61,14 +68,85 @@
 | 
			
		|||
 | 
			
		||||
	let deleteModelTag = '';
 | 
			
		||||
 | 
			
		||||
	const updateModelsHandler = async () => {
 | 
			
		||||
		for (const model of $models.filter(
 | 
			
		||||
			(m) =>
 | 
			
		||||
				m.size != null &&
 | 
			
		||||
				(selectedOllamaUrlIdx === null ? true : (m?.urls ?? []).includes(selectedOllamaUrlIdx))
 | 
			
		||||
		)) {
 | 
			
		||||
			console.log(model);
 | 
			
		||||
 | 
			
		||||
			updateModelId = model.id;
 | 
			
		||||
			const res = await pullModel(localStorage.token, model.id, selectedOllamaUrlIdx).catch(
 | 
			
		||||
				(error) => {
 | 
			
		||||
					toast.error(error);
 | 
			
		||||
					return null;
 | 
			
		||||
				}
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			if (res) {
 | 
			
		||||
				const reader = res.body
 | 
			
		||||
					.pipeThrough(new TextDecoderStream())
 | 
			
		||||
					.pipeThrough(splitStream('\n'))
 | 
			
		||||
					.getReader();
 | 
			
		||||
 | 
			
		||||
				while (true) {
 | 
			
		||||
					try {
 | 
			
		||||
						const { value, done } = await reader.read();
 | 
			
		||||
						if (done) break;
 | 
			
		||||
 | 
			
		||||
						let lines = value.split('\n');
 | 
			
		||||
 | 
			
		||||
						for (const line of lines) {
 | 
			
		||||
							if (line !== '') {
 | 
			
		||||
								let data = JSON.parse(line);
 | 
			
		||||
 | 
			
		||||
								console.log(data);
 | 
			
		||||
								if (data.error) {
 | 
			
		||||
									throw data.error;
 | 
			
		||||
								}
 | 
			
		||||
								if (data.detail) {
 | 
			
		||||
									throw data.detail;
 | 
			
		||||
								}
 | 
			
		||||
								if (data.status) {
 | 
			
		||||
									if (data.digest) {
 | 
			
		||||
										updateProgress = 0;
 | 
			
		||||
										if (data.completed) {
 | 
			
		||||
											updateProgress = Math.round((data.completed / data.total) * 1000) / 10;
 | 
			
		||||
										} else {
 | 
			
		||||
											updateProgress = 100;
 | 
			
		||||
										}
 | 
			
		||||
									} else {
 | 
			
		||||
										toast.success(data.status);
 | 
			
		||||
									}
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					} catch (error) {
 | 
			
		||||
						console.log(error);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		updateModelId = null;
 | 
			
		||||
		updateProgress = null;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const pullModelHandler = async () => {
 | 
			
		||||
		const sanitizedModelTag = modelTag.trim();
 | 
			
		||||
		if (modelDownloadStatus[sanitizedModelTag]) {
 | 
			
		||||
			toast.error(`Model '${sanitizedModelTag}' is already in queue for downloading.`);
 | 
			
		||||
			toast.error(
 | 
			
		||||
				$i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, {
 | 
			
		||||
					modelTag: sanitizedModelTag
 | 
			
		||||
				})
 | 
			
		||||
			);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		if (Object.keys(modelDownloadStatus).length === 3) {
 | 
			
		||||
			toast.error('Maximum of 3 models can be downloaded simultaneously. Please try again later.');
 | 
			
		||||
			toast.error(
 | 
			
		||||
				$i18n.t('Maximum of 3 models can be downloaded simultaneously. Please try again later.')
 | 
			
		||||
			);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -86,10 +164,12 @@
 | 
			
		|||
				if (!data.success) {
 | 
			
		||||
					toast.error(data.error);
 | 
			
		||||
				} else {
 | 
			
		||||
					toast.success(`Model '${modelName}' has been successfully downloaded.`);
 | 
			
		||||
					toast.success(
 | 
			
		||||
						$i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, { modelName })
 | 
			
		||||
					);
 | 
			
		||||
 | 
			
		||||
					const notification = new Notification($WEBUI_NAME, {
 | 
			
		||||
						body: `Model '${modelName}' has been successfully downloaded.`,
 | 
			
		||||
						body: $i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, { modelName }),
 | 
			
		||||
						icon: `${WEBUI_BASE_URL}/static/favicon.png`
 | 
			
		||||
					});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -253,7 +333,7 @@
 | 
			
		|||
		);
 | 
			
		||||
 | 
			
		||||
		if (res) {
 | 
			
		||||
			toast.success(`Deleted ${deleteModelTag}`);
 | 
			
		||||
			toast.success($i18n.t(`Deleted {{deleteModelTag}}`, { deleteModelTag }));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		deleteModelTag = '';
 | 
			
		||||
| 
						 | 
				
			
			@ -340,7 +420,7 @@
 | 
			
		|||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			toast.error(`Model ${liteLLMModelName} already exists.`);
 | 
			
		||||
			toast.error($i18n.t(`Model {{modelName}} already exists.`, { modelName: liteLLMModelName }));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		liteLLMModelName = '';
 | 
			
		||||
| 
						 | 
				
			
			@ -379,7 +459,7 @@
 | 
			
		|||
			return [];
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (OLLAMA_URLS.length > 1) {
 | 
			
		||||
		if (OLLAMA_URLS.length > 0) {
 | 
			
		||||
			selectedOllamaUrlIdx = 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -389,33 +469,68 @@
 | 
			
		|||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="flex flex-col h-full justify-between text-sm">
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll h-[23rem]">
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll h-[24rem]">
 | 
			
		||||
		{#if ollamaVersion}
 | 
			
		||||
			<div class="space-y-2 pr-1.5">
 | 
			
		||||
				<div class="text-sm font-medium">Manage Ollama Models</div>
 | 
			
		||||
				<div class="text-sm font-medium">{$i18n.t('Manage Ollama Models')}</div>
 | 
			
		||||
 | 
			
		||||
				{#if OLLAMA_URLS.length > 1}
 | 
			
		||||
					<div class="flex-1 pb-1">
 | 
			
		||||
						<select
 | 
			
		||||
							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
							bind:value={selectedOllamaUrlIdx}
 | 
			
		||||
							placeholder="Select an Ollama instance"
 | 
			
		||||
						>
 | 
			
		||||
							{#each OLLAMA_URLS as url, idx}
 | 
			
		||||
								<option value={idx} class="bg-gray-100 dark:bg-gray-700">{url}</option>
 | 
			
		||||
							{/each}
 | 
			
		||||
						</select>
 | 
			
		||||
				{#if OLLAMA_URLS.length > 0}
 | 
			
		||||
					<div class="flex gap-2">
 | 
			
		||||
						<div class="flex-1 pb-1">
 | 
			
		||||
							<select
 | 
			
		||||
								class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
								bind:value={selectedOllamaUrlIdx}
 | 
			
		||||
								placeholder={$i18n.t('Select an Ollama instance')}
 | 
			
		||||
							>
 | 
			
		||||
								{#each OLLAMA_URLS as url, idx}
 | 
			
		||||
									<option value={idx} class="bg-gray-100 dark:bg-gray-700">{url}</option>
 | 
			
		||||
								{/each}
 | 
			
		||||
							</select>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div>
 | 
			
		||||
							<div class="flex w-full justify-end">
 | 
			
		||||
								<Tooltip content="Update All Models" placement="top">
 | 
			
		||||
									<button
 | 
			
		||||
										class="p-2.5 flex gap-2 items-center bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
 | 
			
		||||
										on:click={() => {
 | 
			
		||||
											updateModelsHandler();
 | 
			
		||||
										}}
 | 
			
		||||
									>
 | 
			
		||||
										<svg
 | 
			
		||||
											xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
											viewBox="0 0 16 16"
 | 
			
		||||
											fill="currentColor"
 | 
			
		||||
											class="w-4 h-4"
 | 
			
		||||
										>
 | 
			
		||||
											<path
 | 
			
		||||
												d="M7 1a.75.75 0 0 1 .75.75V6h-1.5V1.75A.75.75 0 0 1 7 1ZM6.25 6v2.94L5.03 7.72a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06L7.75 8.94V6H10a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.25Z"
 | 
			
		||||
											/>
 | 
			
		||||
											<path
 | 
			
		||||
												d="M4.268 14A2 2 0 0 0 6 15h6a2 2 0 0 0 2-2v-3a2 2 0 0 0-1-1.732V11a3 3 0 0 1-3 3H4.268Z"
 | 
			
		||||
											/>
 | 
			
		||||
										</svg>
 | 
			
		||||
									</button>
 | 
			
		||||
								</Tooltip>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					{#if updateModelId}
 | 
			
		||||
						Updating "{updateModelId}" {updateProgress ? `(${updateProgress}%)` : ''}
 | 
			
		||||
					{/if}
 | 
			
		||||
				{/if}
 | 
			
		||||
 | 
			
		||||
				<div class="space-y-2">
 | 
			
		||||
					<div>
 | 
			
		||||
						<div class=" mb-2 text-sm font-medium">Pull a model from Ollama.com</div>
 | 
			
		||||
						<div class=" mb-2 text-sm font-medium">{$i18n.t('Pull a model from Ollama.com')}</div>
 | 
			
		||||
						<div class="flex w-full">
 | 
			
		||||
							<div class="flex-1 mr-2">
 | 
			
		||||
								<input
 | 
			
		||||
									class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
									placeholder="Enter model tag (e.g. mistral:7b)"
 | 
			
		||||
									placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
 | 
			
		||||
										modelTag: 'mistral:7b'
 | 
			
		||||
									})}
 | 
			
		||||
									bind:value={modelTag}
 | 
			
		||||
								/>
 | 
			
		||||
							</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -471,10 +586,11 @@
 | 
			
		|||
						</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
 | 
			
		||||
							{$i18n.t('To access the available model names for downloading,')}
 | 
			
		||||
							<a
 | 
			
		||||
								class=" text-gray-500 dark:text-gray-300 font-medium underline"
 | 
			
		||||
								href="https://ollama.com/library"
 | 
			
		||||
								target="_blank">click here.</a
 | 
			
		||||
								target="_blank">{$i18n.t('click here.')}</a
 | 
			
		||||
							>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -499,16 +615,16 @@
 | 
			
		|||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div>
 | 
			
		||||
						<div class=" mb-2 text-sm font-medium">Delete a model</div>
 | 
			
		||||
						<div class=" mb-2 text-sm font-medium">{$i18n.t('Delete a model')}</div>
 | 
			
		||||
						<div class="flex w-full">
 | 
			
		||||
							<div class="flex-1 mr-2">
 | 
			
		||||
								<select
 | 
			
		||||
									class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
									bind:value={deleteModelTag}
 | 
			
		||||
									placeholder="Select a model"
 | 
			
		||||
									placeholder={$i18n.t('Select a model')}
 | 
			
		||||
								>
 | 
			
		||||
									{#if !deleteModelTag}
 | 
			
		||||
										<option value="" disabled selected>Select a model</option>
 | 
			
		||||
										<option value="" disabled selected>{$i18n.t('Select a model')}</option>
 | 
			
		||||
									{/if}
 | 
			
		||||
									{#each $models.filter((m) => m.size != null && (selectedOllamaUrlIdx === null ? true : (m?.urls ?? []).includes(selectedOllamaUrlIdx))) as model}
 | 
			
		||||
										<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
 | 
			
		||||
| 
						 | 
				
			
			@ -541,13 +657,13 @@
 | 
			
		|||
 | 
			
		||||
					<div class="pt-1">
 | 
			
		||||
						<div class="flex justify-between items-center text-xs">
 | 
			
		||||
							<div class=" text-sm font-medium">Experimental</div>
 | 
			
		||||
							<div class=" text-sm font-medium">{$i18n.t('Experimental')}</div>
 | 
			
		||||
							<button
 | 
			
		||||
								class=" text-xs font-medium text-gray-500"
 | 
			
		||||
								type="button"
 | 
			
		||||
								on:click={() => {
 | 
			
		||||
									showExperimentalOllama = !showExperimentalOllama;
 | 
			
		||||
								}}>{showExperimentalOllama ? 'Hide' : 'Show'}</button
 | 
			
		||||
								}}>{showExperimentalOllama ? $i18n.t('Hide') : $i18n.t('Show')}</button
 | 
			
		||||
							>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -559,7 +675,7 @@
 | 
			
		|||
							}}
 | 
			
		||||
						>
 | 
			
		||||
							<div class=" mb-2 flex w-full justify-between">
 | 
			
		||||
								<div class="  text-sm font-medium">Upload a GGUF model</div>
 | 
			
		||||
								<div class="  text-sm font-medium">{$i18n.t('Upload a GGUF model')}</div>
 | 
			
		||||
 | 
			
		||||
								<button
 | 
			
		||||
									class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
| 
						 | 
				
			
			@ -573,9 +689,9 @@
 | 
			
		|||
									type="button"
 | 
			
		||||
								>
 | 
			
		||||
									{#if modelUploadMode === 'file'}
 | 
			
		||||
										<span class="ml-2 self-center">File Mode</span>
 | 
			
		||||
										<span class="ml-2 self-center">{$i18n.t('File Mode')}</span>
 | 
			
		||||
									{:else}
 | 
			
		||||
										<span class="ml-2 self-center">URL Mode</span>
 | 
			
		||||
										<span class="ml-2 self-center">{$i18n.t('URL Mode')}</span>
 | 
			
		||||
									{/if}
 | 
			
		||||
								</button>
 | 
			
		||||
							</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -607,7 +723,7 @@
 | 
			
		|||
												{#if modelInputFile && modelInputFile.length > 0}
 | 
			
		||||
													{modelInputFile[0].name}
 | 
			
		||||
												{:else}
 | 
			
		||||
													Click here to select
 | 
			
		||||
													{$i18n.t('Click here to select')}
 | 
			
		||||
												{/if}
 | 
			
		||||
											</button>
 | 
			
		||||
										</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -621,7 +737,7 @@
 | 
			
		|||
												type="url"
 | 
			
		||||
												required
 | 
			
		||||
												bind:value={modelFileUrl}
 | 
			
		||||
												placeholder="Type Hugging Face Resolve (Download) URL"
 | 
			
		||||
												placeholder={$i18n.t('Type Hugging Face Resolve (Download) URL')}
 | 
			
		||||
											/>
 | 
			
		||||
										</div>
 | 
			
		||||
									{/if}
 | 
			
		||||
| 
						 | 
				
			
			@ -681,7 +797,7 @@
 | 
			
		|||
							{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
 | 
			
		||||
								<div>
 | 
			
		||||
									<div>
 | 
			
		||||
										<div class=" my-2.5 text-sm font-medium">Modelfile Content</div>
 | 
			
		||||
										<div class=" my-2.5 text-sm font-medium">{$i18n.t('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"
 | 
			
		||||
| 
						 | 
				
			
			@ -691,16 +807,17 @@
 | 
			
		|||
								</div>
 | 
			
		||||
							{/if}
 | 
			
		||||
							<div class=" mt-1 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
								To access the GGUF models available for downloading, <a
 | 
			
		||||
								{$i18n.t('To access the GGUF models available for downloading,')}
 | 
			
		||||
								<a
 | 
			
		||||
									class=" text-gray-500 dark:text-gray-300 font-medium underline"
 | 
			
		||||
									href="https://huggingface.co/models?search=gguf"
 | 
			
		||||
									target="_blank">click here.</a
 | 
			
		||||
									target="_blank">{$i18n.t('click here.')}</a
 | 
			
		||||
								>
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							{#if uploadProgress !== null}
 | 
			
		||||
								<div class="mt-2">
 | 
			
		||||
									<div class=" mb-2 text-xs">Upload Progress</div>
 | 
			
		||||
									<div class=" mb-2 text-xs">{$i18n.t('Upload Progress')}</div>
 | 
			
		||||
 | 
			
		||||
									<div class="w-full rounded-full dark:bg-gray-800">
 | 
			
		||||
										<div
 | 
			
		||||
| 
						 | 
				
			
			@ -727,13 +844,13 @@
 | 
			
		|||
				<div>
 | 
			
		||||
					<div class="mb-2">
 | 
			
		||||
						<div class="flex justify-between items-center text-xs">
 | 
			
		||||
							<div class=" text-sm font-medium">Manage LiteLLM Models</div>
 | 
			
		||||
							<div class=" text-sm font-medium">{$i18n.t('Manage LiteLLM Models')}</div>
 | 
			
		||||
							<button
 | 
			
		||||
								class=" text-xs font-medium text-gray-500"
 | 
			
		||||
								type="button"
 | 
			
		||||
								on:click={() => {
 | 
			
		||||
									showLiteLLM = !showLiteLLM;
 | 
			
		||||
								}}>{showLiteLLM ? 'Hide' : 'Show'}</button
 | 
			
		||||
								}}>{showLiteLLM ? $i18n.t('Hide') : $i18n.t('Show')}</button
 | 
			
		||||
							>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -741,14 +858,16 @@
 | 
			
		|||
					{#if showLiteLLM}
 | 
			
		||||
						<div>
 | 
			
		||||
							<div class="flex justify-between items-center text-xs">
 | 
			
		||||
								<div class=" text-sm font-medium">Add a model</div>
 | 
			
		||||
								<div class=" text-sm font-medium">{$i18n.t('Add a model')}</div>
 | 
			
		||||
								<button
 | 
			
		||||
									class=" text-xs font-medium text-gray-500"
 | 
			
		||||
									type="button"
 | 
			
		||||
									on:click={() => {
 | 
			
		||||
										showLiteLLMParams = !showLiteLLMParams;
 | 
			
		||||
									}}
 | 
			
		||||
									>{showLiteLLMParams ? 'Hide Additional Params' : 'Show Additional Params'}</button
 | 
			
		||||
									>{showLiteLLMParams
 | 
			
		||||
										? $i18n.t('Hide Additional Params')
 | 
			
		||||
										: $i18n.t('Show Additional Params')}</button
 | 
			
		||||
								>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -758,7 +877,7 @@
 | 
			
		|||
								<div class="flex-1 mr-2">
 | 
			
		||||
									<input
 | 
			
		||||
										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
										placeholder="Enter LiteLLM Model (litellm_params.model)"
 | 
			
		||||
										placeholder={$i18n.t('Enter LiteLLM Model (litellm_params.model)')}
 | 
			
		||||
										bind:value={liteLLMModel}
 | 
			
		||||
										autocomplete="off"
 | 
			
		||||
									/>
 | 
			
		||||
| 
						 | 
				
			
			@ -785,7 +904,7 @@
 | 
			
		|||
 | 
			
		||||
							{#if showLiteLLMParams}
 | 
			
		||||
								<div>
 | 
			
		||||
									<div class=" mb-1.5 text-sm font-medium">Model Name</div>
 | 
			
		||||
									<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Model Name')}</div>
 | 
			
		||||
									<div class="flex w-full">
 | 
			
		||||
										<div class="flex-1">
 | 
			
		||||
											<input
 | 
			
		||||
| 
						 | 
				
			
			@ -799,12 +918,14 @@
 | 
			
		|||
								</div>
 | 
			
		||||
 | 
			
		||||
								<div>
 | 
			
		||||
									<div class=" mb-1.5 text-sm font-medium">API Base URL</div>
 | 
			
		||||
									<div class=" mb-1.5 text-sm font-medium">{$i18n.t('API Base URL')}</div>
 | 
			
		||||
									<div class="flex w-full">
 | 
			
		||||
										<div class="flex-1">
 | 
			
		||||
											<input
 | 
			
		||||
												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
												placeholder="Enter LiteLLM API Base URL (litellm_params.api_base)"
 | 
			
		||||
												placeholder={$i18n.t(
 | 
			
		||||
													'Enter LiteLLM API Base URL (litellm_params.api_base)'
 | 
			
		||||
												)}
 | 
			
		||||
												bind:value={liteLLMAPIBase}
 | 
			
		||||
												autocomplete="off"
 | 
			
		||||
											/>
 | 
			
		||||
| 
						 | 
				
			
			@ -813,12 +934,12 @@
 | 
			
		|||
								</div>
 | 
			
		||||
 | 
			
		||||
								<div>
 | 
			
		||||
									<div class=" mb-1.5 text-sm font-medium">API Key</div>
 | 
			
		||||
									<div class=" mb-1.5 text-sm font-medium">{$i18n.t('API Key')}</div>
 | 
			
		||||
									<div class="flex w-full">
 | 
			
		||||
										<div class="flex-1">
 | 
			
		||||
											<input
 | 
			
		||||
												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
												placeholder="Enter LiteLLM API Key (litellm_params.api_key)"
 | 
			
		||||
												placeholder={$i18n.t('Enter LiteLLM API Key (litellm_params.api_key)')}
 | 
			
		||||
												bind:value={liteLLMAPIKey}
 | 
			
		||||
												autocomplete="off"
 | 
			
		||||
											/>
 | 
			
		||||
| 
						 | 
				
			
			@ -827,12 +948,12 @@
 | 
			
		|||
								</div>
 | 
			
		||||
 | 
			
		||||
								<div>
 | 
			
		||||
									<div class="mb-1.5 text-sm font-medium">API RPM</div>
 | 
			
		||||
									<div class="mb-1.5 text-sm font-medium">{$i18n.t('API RPM')}</div>
 | 
			
		||||
									<div class="flex w-full">
 | 
			
		||||
										<div class="flex-1">
 | 
			
		||||
											<input
 | 
			
		||||
												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
												placeholder="Enter LiteLLM API RPM (litellm_params.rpm)"
 | 
			
		||||
												placeholder={$i18n.t('Enter LiteLLM API RPM (litellm_params.rpm)')}
 | 
			
		||||
												bind:value={liteLLMRPM}
 | 
			
		||||
												autocomplete="off"
 | 
			
		||||
											/>
 | 
			
		||||
| 
						 | 
				
			
			@ -841,12 +962,12 @@
 | 
			
		|||
								</div>
 | 
			
		||||
 | 
			
		||||
								<div>
 | 
			
		||||
									<div class="mb-1.5 text-sm font-medium">Max Tokens</div>
 | 
			
		||||
									<div class="mb-1.5 text-sm font-medium">{$i18n.t('Max Tokens')}</div>
 | 
			
		||||
									<div class="flex w-full">
 | 
			
		||||
										<div class="flex-1">
 | 
			
		||||
											<input
 | 
			
		||||
												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
												placeholder="Enter Max Tokens (litellm_params.max_tokens)"
 | 
			
		||||
												placeholder={$i18n.t('Enter Max Tokens (litellm_params.max_tokens)')}
 | 
			
		||||
												bind:value={liteLLMMaxTokens}
 | 
			
		||||
												type="number"
 | 
			
		||||
												min="1"
 | 
			
		||||
| 
						 | 
				
			
			@ -859,27 +980,27 @@
 | 
			
		|||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div class="mb-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
							Not sure what to add?
 | 
			
		||||
							{$i18n.t('Not sure what to add?')}
 | 
			
		||||
							<a
 | 
			
		||||
								class=" text-gray-300 font-medium underline"
 | 
			
		||||
								href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
 | 
			
		||||
								target="_blank"
 | 
			
		||||
							>
 | 
			
		||||
								Click here for help.
 | 
			
		||||
								{$i18n.t('Click here for help.')}
 | 
			
		||||
							</a>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div>
 | 
			
		||||
							<div class=" mb-2.5 text-sm font-medium">Delete a model</div>
 | 
			
		||||
							<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Delete a model')}</div>
 | 
			
		||||
							<div class="flex w-full">
 | 
			
		||||
								<div class="flex-1 mr-2">
 | 
			
		||||
									<select
 | 
			
		||||
										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
										bind:value={deleteLiteLLMModelId}
 | 
			
		||||
										placeholder="Select a model"
 | 
			
		||||
										placeholder={$i18n.t('Select a model')}
 | 
			
		||||
									>
 | 
			
		||||
										{#if !deleteLiteLLMModelId}
 | 
			
		||||
											<option value="" disabled selected>Select a model</option>
 | 
			
		||||
											<option value="" disabled selected>{$i18n.t('Select a model')}</option>
 | 
			
		||||
										{/if}
 | 
			
		||||
										{#each liteLLMModelInfo as model}
 | 
			
		||||
											<option value={model.model_info.id} class="bg-gray-100 dark:bg-gray-700"
 | 
			
		||||
| 
						 | 
				
			
			@ -912,88 +1033,6 @@
 | 
			
		|||
					{/if}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<!-- <div class="mt-2 space-y-3 pr-1.5">
 | 
			
		||||
				<div>
 | 
			
		||||
					<div class=" mb-2.5 text-sm font-medium">Add LiteLLM Model</div>
 | 
			
		||||
					<div class="flex w-full mb-2">
 | 
			
		||||
						<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 LiteLLM Model (e.g. ollama/mistral)"
 | 
			
		||||
								bind:value={liteLLMModel}
 | 
			
		||||
								autocomplete="off"
 | 
			
		||||
							/>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="flex justify-between items-center text-sm">
 | 
			
		||||
						<div class="  font-medium">Advanced Model Params</div>
 | 
			
		||||
						<button
 | 
			
		||||
							class=" text-xs font-medium text-gray-500"
 | 
			
		||||
							type="button"
 | 
			
		||||
							on:click={() => {
 | 
			
		||||
								showLiteLLMParams = !showLiteLLMParams;
 | 
			
		||||
							}}>{showLiteLLMParams ? 'Hide' : 'Show'}</button
 | 
			
		||||
						>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					{#if showLiteLLMParams}
 | 
			
		||||
						<div>
 | 
			
		||||
							<div class=" mb-2.5 text-sm font-medium">LiteLLM 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 LiteLLM API Key (e.g. os.environ/AZURE_API_KEY_CA)"
 | 
			
		||||
										bind:value={liteLLMAPIKey}
 | 
			
		||||
										autocomplete="off"
 | 
			
		||||
									/>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div>
 | 
			
		||||
							<div class=" mb-2.5 text-sm font-medium">LiteLLM 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 LiteLLM API Base URL"
 | 
			
		||||
										bind:value={liteLLMAPIBase}
 | 
			
		||||
										autocomplete="off"
 | 
			
		||||
									/>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div>
 | 
			
		||||
							<div class=" mb-2.5 text-sm font-medium">LiteLLM API RPM</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 LiteLLM API RPM"
 | 
			
		||||
										bind:value={liteLLMRPM}
 | 
			
		||||
										autocomplete="off"
 | 
			
		||||
									/>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					{/if}
 | 
			
		||||
 | 
			
		||||
					<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
						Not sure what to add?
 | 
			
		||||
						<a
 | 
			
		||||
							class=" text-gray-300 font-medium underline"
 | 
			
		||||
							href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
 | 
			
		||||
							target="_blank"
 | 
			
		||||
						>
 | 
			
		||||
							Click here for help.
 | 
			
		||||
						</a>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div> -->
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { getContext } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import { models, settings, user } from '$lib/stores';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -17,6 +18,8 @@
 | 
			
		|||
	import Connections from './Settings/Connections.svelte';
 | 
			
		||||
	import Images from './Settings/Images.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let show = false;
 | 
			
		||||
 | 
			
		||||
	const saveSettings = async (updated) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +61,7 @@
 | 
			
		|||
<Modal bind:show>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
 | 
			
		||||
			<div class=" text-lg font-medium self-center">Settings</div>
 | 
			
		||||
			<div class=" text-lg font-medium self-center">{$i18n.t('Settings')}</div>
 | 
			
		||||
			<button
 | 
			
		||||
				class="self-center"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +109,7 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class=" self-center">General</div>
 | 
			
		||||
					<div class=" self-center">{$i18n.t('General')}</div>
 | 
			
		||||
				</button>
 | 
			
		||||
 | 
			
		||||
				{#if $user?.role === 'admin'}
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +134,7 @@
 | 
			
		|||
								/>
 | 
			
		||||
							</svg>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class=" self-center">Connections</div>
 | 
			
		||||
						<div class=" self-center">{$i18n.t('Connections')}</div>
 | 
			
		||||
					</button>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
| 
						 | 
				
			
			@ -157,7 +160,7 @@
 | 
			
		|||
								/>
 | 
			
		||||
							</svg>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class=" self-center">Models</div>
 | 
			
		||||
						<div class=" self-center">{$i18n.t('Models')}</div>
 | 
			
		||||
					</button>
 | 
			
		||||
				{/if}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -184,7 +187,7 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class=" self-center">Interface</div>
 | 
			
		||||
					<div class=" self-center">{$i18n.t('Interface')}</div>
 | 
			
		||||
				</button>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
| 
						 | 
				
			
			@ -211,7 +214,7 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class=" self-center">Audio</div>
 | 
			
		||||
					<div class=" self-center">{$i18n.t('Audio')}</div>
 | 
			
		||||
				</button>
 | 
			
		||||
 | 
			
		||||
				{#if $user.role === 'admin'}
 | 
			
		||||
| 
						 | 
				
			
			@ -238,7 +241,7 @@
 | 
			
		|||
								/>
 | 
			
		||||
							</svg>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class=" self-center">Images</div>
 | 
			
		||||
						<div class=" self-center">{$i18n.t('Images')}</div>
 | 
			
		||||
					</button>
 | 
			
		||||
				{/if}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -265,7 +268,7 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class=" self-center">Chats</div>
 | 
			
		||||
					<div class=" self-center">{$i18n.t('Chats')}</div>
 | 
			
		||||
				</button>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
| 
						 | 
				
			
			@ -291,7 +294,7 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class=" self-center">Account</div>
 | 
			
		||||
					<div class=" self-center">{$i18n.t('Account')}</div>
 | 
			
		||||
				</button>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
| 
						 | 
				
			
			@ -317,16 +320,16 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class=" self-center">About</div>
 | 
			
		||||
					<div class=" self-center">{$i18n.t('About')}</div>
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="flex-1 md:min-h-[380px]">
 | 
			
		||||
			<div class="flex-1 md:min-h-[25rem]">
 | 
			
		||||
				{#if selectedTab === 'general'}
 | 
			
		||||
					<General
 | 
			
		||||
						{getModels}
 | 
			
		||||
						{saveSettings}
 | 
			
		||||
						on:save={() => {
 | 
			
		||||
							show = false;
 | 
			
		||||
							toast.success($i18n.t('Settings saved successfully!'));
 | 
			
		||||
						}}
 | 
			
		||||
					/>
 | 
			
		||||
				{:else if selectedTab === 'models'}
 | 
			
		||||
| 
						 | 
				
			
			@ -335,28 +338,28 @@
 | 
			
		|||
					<Connections
 | 
			
		||||
						{getModels}
 | 
			
		||||
						on:save={() => {
 | 
			
		||||
							show = false;
 | 
			
		||||
							toast.success($i18n.t('Settings saved successfully!'));
 | 
			
		||||
						}}
 | 
			
		||||
					/>
 | 
			
		||||
				{:else if selectedTab === 'interface'}
 | 
			
		||||
					<Interface
 | 
			
		||||
						{saveSettings}
 | 
			
		||||
						on:save={() => {
 | 
			
		||||
							show = false;
 | 
			
		||||
							toast.success($i18n.t('Settings saved successfully!'));
 | 
			
		||||
						}}
 | 
			
		||||
					/>
 | 
			
		||||
				{:else if selectedTab === 'audio'}
 | 
			
		||||
					<Audio
 | 
			
		||||
						{saveSettings}
 | 
			
		||||
						on:save={() => {
 | 
			
		||||
							show = false;
 | 
			
		||||
							toast.success($i18n.t('Settings saved successfully!'));
 | 
			
		||||
						}}
 | 
			
		||||
					/>
 | 
			
		||||
				{:else if selectedTab === 'images'}
 | 
			
		||||
					<Images
 | 
			
		||||
						{saveSettings}
 | 
			
		||||
						on:save={() => {
 | 
			
		||||
							show = false;
 | 
			
		||||
							toast.success($i18n.t('Settings saved successfully!'));
 | 
			
		||||
						}}
 | 
			
		||||
					/>
 | 
			
		||||
				{:else if selectedTab === 'chats'}
 | 
			
		||||
| 
						 | 
				
			
			@ -364,7 +367,7 @@
 | 
			
		|||
				{:else if selectedTab === 'account'}
 | 
			
		||||
					<Account
 | 
			
		||||
						saveHandler={() => {
 | 
			
		||||
							show = false;
 | 
			
		||||
							toast.success($i18n.t('Settings saved successfully!'));
 | 
			
		||||
						}}
 | 
			
		||||
					/>
 | 
			
		||||
				{:else if selectedTab === 'about'}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,9 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { getContext } from 'svelte';
 | 
			
		||||
	import Modal from '../common/Modal.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let downloadChat: Function;
 | 
			
		||||
	export let shareChat: Function;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -17,11 +20,11 @@
 | 
			
		|||
				show = false;
 | 
			
		||||
			}}
 | 
			
		||||
		>
 | 
			
		||||
			Share to OpenWebUI Community
 | 
			
		||||
			{$i18n.t('Share to OpenWebUI Community')}
 | 
			
		||||
		</button>
 | 
			
		||||
 | 
			
		||||
		<div class="flex justify-center space-x-1 mt-1.5">
 | 
			
		||||
			<div class=" self-center text-gray-400 text-xs font-medium">or</div>
 | 
			
		||||
			<div class=" self-center text-gray-400 text-xs font-medium">{$i18n.t('or')}</div>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				class=" self-center rounded-full text-xs font-medium text-gray-700 dark:text-gray-500 underline"
 | 
			
		||||
| 
						 | 
				
			
			@ -31,7 +34,7 @@
 | 
			
		|||
					show = false;
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				Download as a File
 | 
			
		||||
				{$i18n.t('Download as a File')}
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,16 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { getContext } from 'svelte';
 | 
			
		||||
	import Modal from '../common/Modal.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let show = false;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<Modal bind:show>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
 | 
			
		||||
			<div class=" text-lg font-medium self-center">Keyboard shortcuts</div>
 | 
			
		||||
			<div class=" text-lg font-medium self-center">{$i18n.t('Keyboard shortcuts')}</div>
 | 
			
		||||
			<button
 | 
			
		||||
				class="self-center"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +35,7 @@
 | 
			
		|||
			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
 | 
			
		||||
				<div class="flex flex-col space-y-3 w-full self-start">
 | 
			
		||||
					<div class="w-full flex justify-between items-center">
 | 
			
		||||
						<div class=" text-sm">Open new chat</div>
 | 
			
		||||
						<div class=" text-sm">{$i18n.t('Open new chat')}</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex space-x-1 text-xs">
 | 
			
		||||
							<div
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +59,7 @@
 | 
			
		|||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="w-full flex justify-between items-center">
 | 
			
		||||
						<div class=" text-sm">Focus chat input</div>
 | 
			
		||||
						<div class=" text-sm">{$i18n.t('Focus chat input')}</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex space-x-1 text-xs">
 | 
			
		||||
							<div
 | 
			
		||||
| 
						 | 
				
			
			@ -74,7 +77,7 @@
 | 
			
		|||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="w-full flex justify-between items-center">
 | 
			
		||||
						<div class=" text-sm">Copy last code block</div>
 | 
			
		||||
						<div class=" text-sm">{$i18n.t('Copy last code block')}</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex space-x-1 text-xs">
 | 
			
		||||
							<div
 | 
			
		||||
| 
						 | 
				
			
			@ -98,7 +101,7 @@
 | 
			
		|||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="w-full flex justify-between items-center">
 | 
			
		||||
						<div class=" text-sm">Copy last response</div>
 | 
			
		||||
						<div class=" text-sm">{$i18n.t('Copy last response')}</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex space-x-1 text-xs">
 | 
			
		||||
							<div
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +127,7 @@
 | 
			
		|||
 | 
			
		||||
				<div class="flex flex-col space-y-3 w-full self-start">
 | 
			
		||||
					<div class="w-full flex justify-between items-center">
 | 
			
		||||
						<div class=" text-sm">Toggle settings</div>
 | 
			
		||||
						<div class=" text-sm">{$i18n.t('Toggle settings')}</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex space-x-1 text-xs">
 | 
			
		||||
							<div
 | 
			
		||||
| 
						 | 
				
			
			@ -141,7 +144,7 @@
 | 
			
		|||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="w-full flex justify-between items-center">
 | 
			
		||||
						<div class=" text-sm">Toggle sidebar</div>
 | 
			
		||||
						<div class=" text-sm">{$i18n.t('Toggle sidebar')}</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex space-x-1 text-xs">
 | 
			
		||||
							<div
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +168,7 @@
 | 
			
		|||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="w-full flex justify-between items-center">
 | 
			
		||||
						<div class=" text-sm">Delete chat</div>
 | 
			
		||||
						<div class=" text-sm">{$i18n.t('Delete chat')}</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex space-x-1 text-xs">
 | 
			
		||||
							<div
 | 
			
		||||
| 
						 | 
				
			
			@ -188,7 +191,7 @@
 | 
			
		|||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="w-full flex justify-between items-center">
 | 
			
		||||
						<div class=" text-sm">Show shortcuts</div>
 | 
			
		||||
						<div class=" text-sm">{$i18n.t('Show shortcuts')}</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex space-x-1 text-xs">
 | 
			
		||||
							<div
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										37
									
								
								src/lib/components/common/Dropdown.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/lib/components/common/Dropdown.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { DropdownMenu } from 'bits-ui';
 | 
			
		||||
	import { createEventDispatcher } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<DropdownMenu.Root
 | 
			
		||||
	onOpenChange={(state) => {
 | 
			
		||||
		dispatch('change', state);
 | 
			
		||||
	}}
 | 
			
		||||
>
 | 
			
		||||
	<DropdownMenu.Trigger>
 | 
			
		||||
		<slot />
 | 
			
		||||
	</DropdownMenu.Trigger>
 | 
			
		||||
 | 
			
		||||
	<slot name="content">
 | 
			
		||||
		<DropdownMenu.Content
 | 
			
		||||
			class="w-full max-w-[130px] rounded-lg px-1 py-1.5 border border-gray-700 z-50 bg-gray-850 text-white"
 | 
			
		||||
			sideOffset={8}
 | 
			
		||||
			side="bottom"
 | 
			
		||||
			align="start"
 | 
			
		||||
		>
 | 
			
		||||
			<DropdownMenu.Item class="flex items-center px-3 py-2 text-sm  font-medium">
 | 
			
		||||
				<div class="flex items-center">Profile</div>
 | 
			
		||||
			</DropdownMenu.Item>
 | 
			
		||||
 | 
			
		||||
			<DropdownMenu.Item class="flex items-center px-3 py-2 text-sm  font-medium">
 | 
			
		||||
				<div class="flex items-center">Profile</div>
 | 
			
		||||
			</DropdownMenu.Item>
 | 
			
		||||
 | 
			
		||||
			<DropdownMenu.Item class="flex items-center px-3 py-2 text-sm  font-medium">
 | 
			
		||||
				<div class="flex items-center">Profile</div>
 | 
			
		||||
			</DropdownMenu.Item>
 | 
			
		||||
		</DropdownMenu.Content>
 | 
			
		||||
	</slot>
 | 
			
		||||
</DropdownMenu.Root>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,9 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { createEventDispatcher } from 'svelte';
 | 
			
		||||
	import { createEventDispatcher, getContext } from 'svelte';
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	let showTagInput = false;
 | 
			
		||||
	let tagName = '';
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -9,12 +11,6 @@
 | 
			
		|||
<div class="flex space-x-1 pl-1.5">
 | 
			
		||||
	{#if showTagInput}
 | 
			
		||||
		<div class="flex items-center">
 | 
			
		||||
			<input
 | 
			
		||||
				bind:value={tagName}
 | 
			
		||||
				class=" cursor-pointer self-center text-xs h-fit bg-transparent outline-none line-clamp-1 w-[4rem]"
 | 
			
		||||
				placeholder="Add a tag"
 | 
			
		||||
			/>
 | 
			
		||||
 | 
			
		||||
			<button
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +32,11 @@
 | 
			
		|||
					/>
 | 
			
		||||
				</svg>
 | 
			
		||||
			</button>
 | 
			
		||||
			<input
 | 
			
		||||
				bind:value={tagName}
 | 
			
		||||
				class=" pl-2 cursor-pointer self-center text-xs h-fit bg-transparent outline-none line-clamp-1 w-[8rem]"
 | 
			
		||||
				placeholder={$i18n.t('Add a tag')}
 | 
			
		||||
			/>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<!-- TODO: Tag Suggestions -->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import dayjs from 'dayjs';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	import { createNewDoc, getDocs, tagDocByName, updateDocByName } from '$lib/apis/documents';
 | 
			
		||||
	import Modal from '../common/Modal.svelte';
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +13,8 @@
 | 
			
		|||
	import { transformFileName } from '$lib/utils';
 | 
			
		||||
	import { SUPPORTED_FILE_EXTENSIONS, SUPPORTED_FILE_TYPE } from '$lib/constants';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let show = false;
 | 
			
		||||
	export let selectedDoc;
 | 
			
		||||
	let uploadDocInputElement: HTMLInputElement;
 | 
			
		||||
| 
						 | 
				
			
			@ -71,7 +73,7 @@
 | 
			
		|||
			inputFiles = null;
 | 
			
		||||
			uploadDocInputElement.value = '';
 | 
			
		||||
		} else {
 | 
			
		||||
			toast.error(`File not found.`);
 | 
			
		||||
			toast.error($i18n.t(`File not found.`));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		show = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -96,7 +98,7 @@
 | 
			
		|||
<Modal size="sm" bind:show>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
 | 
			
		||||
			<div class=" text-lg font-medium self-center">Add Docs</div>
 | 
			
		||||
			<div class=" text-lg font-medium self-center">{$i18n.t('Add Docs')}</div>
 | 
			
		||||
			<button
 | 
			
		||||
				class="self-center"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -145,14 +147,14 @@
 | 
			
		|||
							{#if inputFiles}
 | 
			
		||||
								{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
 | 
			
		||||
							{:else}
 | 
			
		||||
								Click here to select documents.
 | 
			
		||||
								{$i18n.t('Click here to select documents.')}
 | 
			
		||||
							{/if}
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class=" flex flex-col space-y-1.5">
 | 
			
		||||
						<div class="flex flex-col w-full">
 | 
			
		||||
							<div class=" mb-1.5 text-xs text-gray-500">Tags</div>
 | 
			
		||||
							<div class=" mb-1.5 text-xs text-gray-500">{$i18n.t('Tags')}</div>
 | 
			
		||||
 | 
			
		||||
							<Tags {tags} addTag={addTagHandler} deleteTag={deleteTagHandler} />
 | 
			
		||||
						</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -163,7 +165,7 @@
 | 
			
		|||
							class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
							type="submit"
 | 
			
		||||
						>
 | 
			
		||||
							Save
 | 
			
		||||
							{$i18n.t('Save')}
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import dayjs from 'dayjs';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	import { getDocs, tagDocByName, updateDocByName } from '$lib/apis/documents';
 | 
			
		||||
	import Modal from '../common/Modal.svelte';
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +10,8 @@
 | 
			
		|||
	import Tags from '../common/Tags.svelte';
 | 
			
		||||
	import { addTagById } from '$lib/apis/chats';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let show = false;
 | 
			
		||||
	export let selectedDoc;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -74,7 +76,7 @@
 | 
			
		|||
<Modal size="sm" bind:show>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
 | 
			
		||||
			<div class=" text-lg font-medium self-center">Edit Doc</div>
 | 
			
		||||
			<div class=" text-lg font-medium self-center">{$i18n.t('Edit Doc')}</div>
 | 
			
		||||
			<button
 | 
			
		||||
				class="self-center"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -105,7 +107,7 @@
 | 
			
		|||
				>
 | 
			
		||||
					<div class=" flex flex-col space-y-1.5">
 | 
			
		||||
						<div class="flex flex-col w-full">
 | 
			
		||||
							<div class=" mb-1 text-xs text-gray-500">Name Tag</div>
 | 
			
		||||
							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name Tag')}</div>
 | 
			
		||||
 | 
			
		||||
							<div class="flex flex-1">
 | 
			
		||||
								<div
 | 
			
		||||
| 
						 | 
				
			
			@ -134,7 +136,7 @@
 | 
			
		|||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex flex-col w-full">
 | 
			
		||||
							<div class=" mb-1 text-xs text-gray-500">Title</div>
 | 
			
		||||
							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Title')}</div>
 | 
			
		||||
 | 
			
		||||
							<div class="flex-1">
 | 
			
		||||
								<input
 | 
			
		||||
| 
						 | 
				
			
			@ -148,7 +150,7 @@
 | 
			
		|||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex flex-col w-full">
 | 
			
		||||
							<div class=" mb-1.5 text-xs text-gray-500">Tags</div>
 | 
			
		||||
							<div class=" mb-1.5 text-xs text-gray-500">{$i18n.t('Tags')}</div>
 | 
			
		||||
 | 
			
		||||
							<Tags {tags} addTag={addTagHandler} deleteTag={deleteTagHandler} />
 | 
			
		||||
						</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -159,7 +161,7 @@
 | 
			
		|||
							class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
							type="submit"
 | 
			
		||||
						>
 | 
			
		||||
							Save
 | 
			
		||||
							{$i18n.t('Save')}
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,22 +1,25 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { getDocs } from '$lib/apis/documents';
 | 
			
		||||
	import {
 | 
			
		||||
		getChunkParams,
 | 
			
		||||
		getRAGConfig,
 | 
			
		||||
		updateRAGConfig,
 | 
			
		||||
		getQuerySettings,
 | 
			
		||||
		scanDocs,
 | 
			
		||||
		updateChunkParams,
 | 
			
		||||
		updateQuerySettings
 | 
			
		||||
	} from '$lib/apis/rag';
 | 
			
		||||
	import { documents } from '$lib/stores';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let saveHandler: Function;
 | 
			
		||||
 | 
			
		||||
	let loading = false;
 | 
			
		||||
 | 
			
		||||
	let chunkSize = 0;
 | 
			
		||||
	let chunkOverlap = 0;
 | 
			
		||||
	let pdfExtractImages = true;
 | 
			
		||||
 | 
			
		||||
	let querySettings = {
 | 
			
		||||
		template: '',
 | 
			
		||||
| 
						 | 
				
			
			@ -30,21 +33,29 @@
 | 
			
		|||
 | 
			
		||||
		if (res) {
 | 
			
		||||
			await documents.set(await getDocs(localStorage.token));
 | 
			
		||||
			toast.success('Scan complete!');
 | 
			
		||||
			toast.success($i18n.t('Scan complete!'));
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const submitHandler = async () => {
 | 
			
		||||
		const res = await updateChunkParams(localStorage.token, chunkSize, chunkOverlap);
 | 
			
		||||
		const res = await updateRAGConfig(localStorage.token, {
 | 
			
		||||
			pdf_extract_images: pdfExtractImages,
 | 
			
		||||
			chunk: {
 | 
			
		||||
				chunk_overlap: chunkOverlap,
 | 
			
		||||
				chunk_size: chunkSize
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		querySettings = await updateQuerySettings(localStorage.token, querySettings);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		const res = await getChunkParams(localStorage.token);
 | 
			
		||||
		const res = await getRAGConfig(localStorage.token);
 | 
			
		||||
 | 
			
		||||
		if (res) {
 | 
			
		||||
			chunkSize = res.chunk_size;
 | 
			
		||||
			chunkOverlap = res.chunk_overlap;
 | 
			
		||||
			pdfExtractImages = res.pdf_extract_images;
 | 
			
		||||
 | 
			
		||||
			chunkSize = res.chunk.chunk_size;
 | 
			
		||||
			chunkOverlap = res.chunk.chunk_overlap;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		querySettings = await getQuerySettings(localStorage.token);
 | 
			
		||||
| 
						 | 
				
			
			@ -60,10 +71,12 @@
 | 
			
		|||
>
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2 text-sm font-medium">General Settings</div>
 | 
			
		||||
			<div class=" mb-2 text-sm font-medium">{$i18n.t('General Settings')}</div>
 | 
			
		||||
 | 
			
		||||
			<div class="  flex w-full justify-between">
 | 
			
		||||
				<div class=" self-center text-xs font-medium">Scan for documents from '/data/docs'</div>
 | 
			
		||||
				<div class=" self-center text-xs font-medium">
 | 
			
		||||
					{$i18n.t('Scan for documents from {{path}}', { path: '/data/docs' })}
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded flex flex-row space-x-1 items-center {loading
 | 
			
		||||
| 
						 | 
				
			
			@ -76,7 +89,7 @@
 | 
			
		|||
					type="button"
 | 
			
		||||
					disabled={loading}
 | 
			
		||||
				>
 | 
			
		||||
					<div class="self-center font-medium">Scan</div>
 | 
			
		||||
					<div class="self-center font-medium">{$i18n.t('Scan')}</div>
 | 
			
		||||
 | 
			
		||||
					<!-- <svg
 | 
			
		||||
						xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
| 
						 | 
				
			
			@ -125,17 +138,17 @@
 | 
			
		|||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div class=" ">
 | 
			
		||||
			<div class=" text-sm font-medium">Chunk Params</div>
 | 
			
		||||
			<div class=" text-sm font-medium">{$i18n.t('Chunk Params')}</div>
 | 
			
		||||
 | 
			
		||||
			<div class=" flex">
 | 
			
		||||
				<div class="  flex w-full justify-between">
 | 
			
		||||
					<div class="self-center text-xs font-medium min-w-fit">Chunk Size</div>
 | 
			
		||||
					<div class="self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Size')}</div>
 | 
			
		||||
 | 
			
		||||
					<div class="self-center p-3">
 | 
			
		||||
						<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 Chunk Size"
 | 
			
		||||
							placeholder={$i18n.t('Enter Chunk Size')}
 | 
			
		||||
							bind:value={chunkSize}
 | 
			
		||||
							autocomplete="off"
 | 
			
		||||
							min="0"
 | 
			
		||||
| 
						 | 
				
			
			@ -144,13 +157,13 @@
 | 
			
		|||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="flex w-full">
 | 
			
		||||
					<div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
 | 
			
		||||
					<div class=" self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Overlap')}</div>
 | 
			
		||||
 | 
			
		||||
					<div class="self-center p-3">
 | 
			
		||||
						<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 Chunk Overlap"
 | 
			
		||||
							placeholder={$i18n.t('Enter Chunk Overlap')}
 | 
			
		||||
							bind:value={chunkOverlap}
 | 
			
		||||
							autocomplete="off"
 | 
			
		||||
							min="0"
 | 
			
		||||
| 
						 | 
				
			
			@ -159,17 +172,33 @@
 | 
			
		|||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class=" text-sm font-medium">Query Params</div>
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class="flex justify-between items-center text-xs">
 | 
			
		||||
					<div class=" text-xs font-medium">{$i18n.t('PDF Extract Images (OCR)')}</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
						class=" text-xs font-medium text-gray-500"
 | 
			
		||||
						type="button"
 | 
			
		||||
						on:click={() => {
 | 
			
		||||
							pdfExtractImages = !pdfExtractImages;
 | 
			
		||||
						}}>{pdfExtractImages ? $i18n.t('On') : $i18n.t('Off')}</button
 | 
			
		||||
					>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" text-sm font-medium">{$i18n.t('Query Params')}</div>
 | 
			
		||||
 | 
			
		||||
			<div class=" flex">
 | 
			
		||||
				<div class="  flex w-full justify-between">
 | 
			
		||||
					<div class="self-center text-xs font-medium flex-1">Top K</div>
 | 
			
		||||
					<div class="self-center text-xs font-medium flex-1">{$i18n.t('Top K')}</div>
 | 
			
		||||
 | 
			
		||||
					<div class="self-center p-3">
 | 
			
		||||
						<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 Top K"
 | 
			
		||||
							placeholder={$i18n.t('Enter Top K')}
 | 
			
		||||
							bind:value={querySettings.k}
 | 
			
		||||
							autocomplete="off"
 | 
			
		||||
							min="0"
 | 
			
		||||
| 
						 | 
				
			
			@ -178,23 +207,23 @@
 | 
			
		|||
				</div>
 | 
			
		||||
 | 
			
		||||
				<!-- <div class="flex w-full">
 | 
			
		||||
					<div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
 | 
			
		||||
 | 
			
		||||
					<div class="self-center p-3">
 | 
			
		||||
						<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 Chunk Overlap"
 | 
			
		||||
							bind:value={chunkOverlap}
 | 
			
		||||
							autocomplete="off"
 | 
			
		||||
							min="0"
 | 
			
		||||
						/>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div> -->
 | 
			
		||||
						<div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
 | 
			
		||||
	
 | 
			
		||||
						<div class="self-center p-3">
 | 
			
		||||
							<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 Chunk Overlap"
 | 
			
		||||
								bind:value={chunkOverlap}
 | 
			
		||||
								autocomplete="off"
 | 
			
		||||
								min="0"
 | 
			
		||||
							/>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div> -->
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">RAG Template</div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('RAG Template')}</div>
 | 
			
		||||
				<textarea
 | 
			
		||||
					bind:value={querySettings.template}
 | 
			
		||||
					class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
 | 
			
		||||
| 
						 | 
				
			
			@ -209,7 +238,7 @@
 | 
			
		|||
			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 | 
			
		||||
			type="submit"
 | 
			
		||||
		>
 | 
			
		||||
			Save
 | 
			
		||||
			{$i18n.t('Save')}
 | 
			
		||||
		</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,10 @@
 | 
			
		|||
<script>
 | 
			
		||||
	import { getContext } from 'svelte';
 | 
			
		||||
	import Modal from '../common/Modal.svelte';
 | 
			
		||||
	import General from './Settings/General.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let show = false;
 | 
			
		||||
 | 
			
		||||
	let selectedTab = 'general';
 | 
			
		||||
| 
						 | 
				
			
			@ -10,7 +13,7 @@
 | 
			
		|||
<Modal bind:show>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
 | 
			
		||||
			<div class=" text-lg font-medium self-center">Document Settings</div>
 | 
			
		||||
			<div class=" text-lg font-medium self-center">{$i18n.t('Document Settings')}</div>
 | 
			
		||||
			<button
 | 
			
		||||
				class="self-center"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +61,7 @@
 | 
			
		|||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class=" self-center">General</div>
 | 
			
		||||
					<div class=" self-center">{$i18n.t('General')}</div>
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="flex-1 md:min-h-[380px]">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								src/lib/components/icons/GarbageBin.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/lib/components/icons/GarbageBin.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	export let className = 'w-4 h-4';
 | 
			
		||||
	export let strokeWidth = '1.5';
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svg
 | 
			
		||||
	xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
	fill="none"
 | 
			
		||||
	viewBox="0 0 24 24"
 | 
			
		||||
	stroke-width={strokeWidth}
 | 
			
		||||
	stroke="currentColor"
 | 
			
		||||
	class={className}
 | 
			
		||||
>
 | 
			
		||||
	<path
 | 
			
		||||
		stroke-linecap="round"
 | 
			
		||||
		stroke-linejoin="round"
 | 
			
		||||
		d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
 | 
			
		||||
	/>
 | 
			
		||||
</svg>
 | 
			
		||||
							
								
								
									
										19
									
								
								src/lib/components/icons/Pencil.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/lib/components/icons/Pencil.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	export let className = 'w-4 h-4';
 | 
			
		||||
	export let strokeWidth = '1.5';
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svg
 | 
			
		||||
	xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
	fill="none"
 | 
			
		||||
	viewBox="0 0 24 24"
 | 
			
		||||
	stroke-width={strokeWidth}
 | 
			
		||||
	stroke="currentColor"
 | 
			
		||||
	class={className}
 | 
			
		||||
>
 | 
			
		||||
	<path
 | 
			
		||||
		stroke-linecap="round"
 | 
			
		||||
		stroke-linejoin="round"
 | 
			
		||||
		d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
 | 
			
		||||
	/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { getContext } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import fileSaver from 'file-saver';
 | 
			
		||||
	const { saveAs } = fileSaver;
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +10,8 @@
 | 
			
		|||
	import TagInput from '../common/Tags/TagInput.svelte';
 | 
			
		||||
	import Tags from '../common/Tags.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let initNewChat: Function;
 | 
			
		||||
	export let title: string = $WEBUI_NAME;
 | 
			
		||||
	export let shareEnabled: boolean = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +29,7 @@
 | 
			
		|||
		const chat = (await getChatById(localStorage.token, $chatId)).chat;
 | 
			
		||||
		console.log('share', chat);
 | 
			
		||||
 | 
			
		||||
		toast.success('Redirecting you to OpenWebUI Community');
 | 
			
		||||
		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
 | 
			
		||||
		const url = 'https://openwebui.com';
 | 
			
		||||
		// const url = 'http://localhost:5173';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,10 @@
 | 
			
		|||
	import { goto, invalidateAll } from '$app/navigation';
 | 
			
		||||
	import { page } from '$app/stores';
 | 
			
		||||
	import { user, chats, settings, showSettings, chatId, tags } from '$lib/stores';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	import {
 | 
			
		||||
		deleteChatById,
 | 
			
		||||
		getChatList,
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +23,8 @@
 | 
			
		|||
	import { slide } from 'svelte/transition';
 | 
			
		||||
	import { WEBUI_BASE_URL } from '$lib/constants';
 | 
			
		||||
	import Tooltip from '../common/Tooltip.svelte';
 | 
			
		||||
	import Dropdown from '../common/Dropdown.svelte';
 | 
			
		||||
	import ChatMenu from './Sidebar/ChatMenu.svelte';
 | 
			
		||||
 | 
			
		||||
	let show = false;
 | 
			
		||||
	let navElement;
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +32,8 @@
 | 
			
		|||
	let title: string = 'UI';
 | 
			
		||||
	let search = '';
 | 
			
		||||
 | 
			
		||||
	let selectedChatId = null;
 | 
			
		||||
 | 
			
		||||
	let chatDeleteId = null;
 | 
			
		||||
	let chatTitleEditId = null;
 | 
			
		||||
	let chatTitle = '';
 | 
			
		||||
| 
						 | 
				
			
			@ -82,7 +89,10 @@
 | 
			
		|||
		});
 | 
			
		||||
 | 
			
		||||
		if (res) {
 | 
			
		||||
			goto('/');
 | 
			
		||||
			if ($chatId === id) {
 | 
			
		||||
				goto('/');
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			await chats.set(await getChatList(localStorage.token));
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
| 
						 | 
				
			
			@ -128,7 +138,7 @@
 | 
			
		|||
						/>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class=" self-center font-medium text-sm">New Chat</div>
 | 
			
		||||
					<div class=" self-center font-medium text-sm">{$i18n.t('New Chat')}</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="self-center">
 | 
			
		||||
| 
						 | 
				
			
			@ -173,7 +183,7 @@
 | 
			
		|||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="flex self-center">
 | 
			
		||||
						<div class=" self-center font-medium text-sm">Modelfiles</div>
 | 
			
		||||
						<div class=" self-center font-medium text-sm">{$i18n.t('Modelfiles')}</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -201,7 +211,7 @@
 | 
			
		|||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="flex self-center">
 | 
			
		||||
						<div class=" self-center font-medium text-sm">Prompts</div>
 | 
			
		||||
						<div class=" self-center font-medium text-sm">{$i18n.t('Prompts')}</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -229,7 +239,7 @@
 | 
			
		|||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="flex self-center">
 | 
			
		||||
						<div class=" self-center font-medium text-sm">Documents</div>
 | 
			
		||||
						<div class=" self-center font-medium text-sm">{$i18n.t('Documents')}</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -239,11 +249,13 @@
 | 
			
		|||
			{#if !($settings.saveChatHistory ?? true)}
 | 
			
		||||
				<div class="absolute z-40 w-full h-full bg-black/90 flex justify-center">
 | 
			
		||||
					<div class=" text-left px-5 py-2">
 | 
			
		||||
						<div class=" font-medium">Chat History is off for this browser.</div>
 | 
			
		||||
						<div class=" font-medium">{$i18n.t('Chat History is off for this browser.')}</div>
 | 
			
		||||
						<div class="text-xs mt-2">
 | 
			
		||||
							When history is turned off, new chats on this browser won't appear in your history on
 | 
			
		||||
							any of your devices. <span class=" font-semibold"
 | 
			
		||||
								>This setting does not sync across browsers or devices.</span
 | 
			
		||||
							{$i18n.t(
 | 
			
		||||
								"When history is turned off, new chats on this browser won't appear in your history on any of your devices."
 | 
			
		||||
							)}
 | 
			
		||||
							<span class=" font-semibold"
 | 
			
		||||
								>{$i18n.t('This setting does not sync across browsers or devices.')}</span
 | 
			
		||||
							>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -270,7 +282,7 @@
 | 
			
		|||
									/>
 | 
			
		||||
								</svg>
 | 
			
		||||
 | 
			
		||||
								<div>Enable Chat History</div>
 | 
			
		||||
								<div>{$i18n.t('Enable Chat History')}</div>
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -296,7 +308,7 @@
 | 
			
		|||
 | 
			
		||||
					<input
 | 
			
		||||
						class="w-full rounded-r-xl py-1.5 pl-2.5 pr-4 text-sm text-gray-300 bg-gray-950 outline-none"
 | 
			
		||||
						placeholder="Search"
 | 
			
		||||
						placeholder={$i18n.t('Search')}
 | 
			
		||||
						bind:value={search}
 | 
			
		||||
						on:focus={() => {
 | 
			
		||||
							enrichChatsWithContent($chats);
 | 
			
		||||
| 
						 | 
				
			
			@ -370,24 +382,27 @@
 | 
			
		|||
						return title.includes(query) || contentMatches;
 | 
			
		||||
					}
 | 
			
		||||
				}) as chat, i}
 | 
			
		||||
					<div class=" w-full pr-2 relative">
 | 
			
		||||
					<div class=" w-full pr-2 relative group">
 | 
			
		||||
						{#if chatTitleEditId === chat.id}
 | 
			
		||||
							<div
 | 
			
		||||
								class=" w-full flex justify-between rounded-xl px-3 py-2 hover:bg-gray-900 {chat.id ===
 | 
			
		||||
								$chatId
 | 
			
		||||
								class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId
 | 
			
		||||
									? 'bg-gray-900'
 | 
			
		||||
									: ''} transition whitespace-nowrap text-ellipsis"
 | 
			
		||||
									: chat.id === selectedChatId
 | 
			
		||||
									? 'bg-gray-950'
 | 
			
		||||
									: 'group-hover:bg-gray-950'}  whitespace-nowrap text-ellipsis"
 | 
			
		||||
							>
 | 
			
		||||
								<input bind:value={chatTitle} class=" bg-transparent w-full outline-none mr-10" />
 | 
			
		||||
							</div>
 | 
			
		||||
						{:else}
 | 
			
		||||
							<a
 | 
			
		||||
								class=" w-full flex justify-between rounded-xl px-3 py-2 hover:bg-gray-900 {chat.id ===
 | 
			
		||||
								$chatId
 | 
			
		||||
								class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId
 | 
			
		||||
									? 'bg-gray-900'
 | 
			
		||||
									: ''} transition whitespace-nowrap text-ellipsis"
 | 
			
		||||
									: chat.id === selectedChatId
 | 
			
		||||
									? 'bg-gray-950'
 | 
			
		||||
									: 'group-hover:bg-gray-950'}  whitespace-nowrap text-ellipsis"
 | 
			
		||||
								href="/c/{chat.id}"
 | 
			
		||||
								on:click={() => {
 | 
			
		||||
									selectedChatId = chat.id;
 | 
			
		||||
									if (window.innerWidth < 1024) {
 | 
			
		||||
										show = false;
 | 
			
		||||
									}
 | 
			
		||||
| 
						 | 
				
			
			@ -395,156 +410,142 @@
 | 
			
		|||
								draggable="false"
 | 
			
		||||
							>
 | 
			
		||||
								<div class=" flex self-center flex-1 w-full">
 | 
			
		||||
									<div
 | 
			
		||||
										class=" text-left self-center overflow-hidden {chat.id === $chatId
 | 
			
		||||
											? 'w-[160px]'
 | 
			
		||||
											: 'w-full'}  h-[20px]"
 | 
			
		||||
									>
 | 
			
		||||
									<div class=" text-left self-center overflow-hidden w-full h-[20px]">
 | 
			
		||||
										{chat.title}
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</a>
 | 
			
		||||
						{/if}
 | 
			
		||||
 | 
			
		||||
						{#if chat.id === $chatId}
 | 
			
		||||
							<div class=" absolute right-[22px] top-[10px]">
 | 
			
		||||
								{#if chatTitleEditId === chat.id}
 | 
			
		||||
									<div class="flex self-center space-x-1.5">
 | 
			
		||||
						<div
 | 
			
		||||
							class="
 | 
			
		||||
							
 | 
			
		||||
							{chat.id === $chatId
 | 
			
		||||
								? ' from-gray-900'
 | 
			
		||||
								: chat.id === selectedChatId
 | 
			
		||||
								? 'from-gray-950'
 | 
			
		||||
								: 'invisible group-hover:visible from-gray-950'}
 | 
			
		||||
								absolute right-[10px] top-[10px] pr-2 pl-5 bg-gradient-to-l from-80%
 | 
			
		||||
								
 | 
			
		||||
								  to-transparent"
 | 
			
		||||
						>
 | 
			
		||||
							{#if chatTitleEditId === chat.id}
 | 
			
		||||
								<div class="flex self-center space-x-1.5 z-10">
 | 
			
		||||
									<button
 | 
			
		||||
										class=" self-center hover:text-white transition"
 | 
			
		||||
										on:click={() => {
 | 
			
		||||
											editChatTitle(chat.id, chatTitle);
 | 
			
		||||
											chatTitleEditId = null;
 | 
			
		||||
											chatTitle = '';
 | 
			
		||||
										}}
 | 
			
		||||
									>
 | 
			
		||||
										<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=" self-center hover:text-white transition"
 | 
			
		||||
										on:click={() => {
 | 
			
		||||
											chatTitleEditId = null;
 | 
			
		||||
											chatTitle = '';
 | 
			
		||||
										}}
 | 
			
		||||
									>
 | 
			
		||||
										<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>
 | 
			
		||||
							{:else if chatDeleteId === chat.id}
 | 
			
		||||
								<div class="flex self-center space-x-1.5 z-10">
 | 
			
		||||
									<button
 | 
			
		||||
										class=" self-center hover:text-white transition"
 | 
			
		||||
										on:click={() => {
 | 
			
		||||
											deleteChat(chat.id);
 | 
			
		||||
										}}
 | 
			
		||||
									>
 | 
			
		||||
										<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=" self-center hover:text-white transition"
 | 
			
		||||
										on:click={() => {
 | 
			
		||||
											chatDeleteId = null;
 | 
			
		||||
										}}
 | 
			
		||||
									>
 | 
			
		||||
										<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>
 | 
			
		||||
							{:else}
 | 
			
		||||
								<div class="flex self-center space-x-1.5 z-10">
 | 
			
		||||
									<ChatMenu
 | 
			
		||||
										renameHandler={() => {
 | 
			
		||||
											chatTitle = chat.title;
 | 
			
		||||
											chatTitleEditId = chat.id;
 | 
			
		||||
										}}
 | 
			
		||||
										deleteHandler={() => {
 | 
			
		||||
											chatDeleteId = chat.id;
 | 
			
		||||
										}}
 | 
			
		||||
										onClose={() => {
 | 
			
		||||
											selectedChatId = null;
 | 
			
		||||
										}}
 | 
			
		||||
									>
 | 
			
		||||
										<button
 | 
			
		||||
											aria-label="Chat Menu"
 | 
			
		||||
											class=" self-center hover:text-white transition"
 | 
			
		||||
											on:click={() => {
 | 
			
		||||
												editChatTitle(chat.id, chatTitle);
 | 
			
		||||
												chatTitleEditId = null;
 | 
			
		||||
												chatTitle = '';
 | 
			
		||||
												selectedChatId = chat.id;
 | 
			
		||||
											}}
 | 
			
		||||
										>
 | 
			
		||||
											<svg
 | 
			
		||||
												xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
												viewBox="0 0 20 20"
 | 
			
		||||
												viewBox="0 0 16 16"
 | 
			
		||||
												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"
 | 
			
		||||
													d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
 | 
			
		||||
												/>
 | 
			
		||||
											</svg>
 | 
			
		||||
										</button>
 | 
			
		||||
										<button
 | 
			
		||||
											class=" self-center hover:text-white transition"
 | 
			
		||||
											on:click={() => {
 | 
			
		||||
												chatTitleEditId = null;
 | 
			
		||||
												chatTitle = '';
 | 
			
		||||
											}}
 | 
			
		||||
										>
 | 
			
		||||
											<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>
 | 
			
		||||
								{:else if chatDeleteId === chat.id}
 | 
			
		||||
									<div class="flex self-center space-x-1.5">
 | 
			
		||||
										<button
 | 
			
		||||
											class=" self-center hover:text-white transition"
 | 
			
		||||
											on:click={() => {
 | 
			
		||||
												deleteChat(chat.id);
 | 
			
		||||
											}}
 | 
			
		||||
										>
 | 
			
		||||
											<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=" self-center hover:text-white transition"
 | 
			
		||||
											on:click={() => {
 | 
			
		||||
												chatDeleteId = null;
 | 
			
		||||
											}}
 | 
			
		||||
										>
 | 
			
		||||
											<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>
 | 
			
		||||
								{:else}
 | 
			
		||||
									<div class="flex self-center space-x-1.5">
 | 
			
		||||
										<button
 | 
			
		||||
											id="delete-chat-button"
 | 
			
		||||
											class=" hidden"
 | 
			
		||||
											on:click={() => {
 | 
			
		||||
												deleteChat(chat.id);
 | 
			
		||||
											}}
 | 
			
		||||
										/>
 | 
			
		||||
										<button
 | 
			
		||||
											class=" self-center hover:text-white transition"
 | 
			
		||||
											on:click={() => {
 | 
			
		||||
												chatTitle = chat.title;
 | 
			
		||||
												chatTitleEditId = chat.id;
 | 
			
		||||
											}}
 | 
			
		||||
										>
 | 
			
		||||
											<svg
 | 
			
		||||
												xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
												fill="none"
 | 
			
		||||
												viewBox="0 0 24 24"
 | 
			
		||||
												stroke-width="1.5"
 | 
			
		||||
												stroke="currentColor"
 | 
			
		||||
												class="w-4 h-4"
 | 
			
		||||
											>
 | 
			
		||||
												<path
 | 
			
		||||
													stroke-linecap="round"
 | 
			
		||||
													stroke-linejoin="round"
 | 
			
		||||
													d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
 | 
			
		||||
												/>
 | 
			
		||||
											</svg>
 | 
			
		||||
										</button>
 | 
			
		||||
										<button
 | 
			
		||||
											class=" self-center hover:text-white transition"
 | 
			
		||||
											on:click={() => {
 | 
			
		||||
												chatDeleteId = chat.id;
 | 
			
		||||
											}}
 | 
			
		||||
										>
 | 
			
		||||
											<svg
 | 
			
		||||
												xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
												fill="none"
 | 
			
		||||
												viewBox="0 0 24 24"
 | 
			
		||||
												stroke-width="1.5"
 | 
			
		||||
												stroke="currentColor"
 | 
			
		||||
												class="w-4 h-4"
 | 
			
		||||
											>
 | 
			
		||||
												<path
 | 
			
		||||
													stroke-linecap="round"
 | 
			
		||||
													stroke-linejoin="round"
 | 
			
		||||
													d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
 | 
			
		||||
												/>
 | 
			
		||||
											</svg>
 | 
			
		||||
										</button>
 | 
			
		||||
									</div>
 | 
			
		||||
								{/if}
 | 
			
		||||
							</div>
 | 
			
		||||
						{/if}
 | 
			
		||||
									</ChatMenu>
 | 
			
		||||
								</div>
 | 
			
		||||
							{/if}
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				{/each}
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -602,7 +603,7 @@
 | 
			
		|||
												/>
 | 
			
		||||
											</svg>
 | 
			
		||||
										</div>
 | 
			
		||||
										<div class=" self-center font-medium">Admin Panel</div>
 | 
			
		||||
										<div class=" self-center font-medium">{$i18n.t('Admin Panel')}</div>
 | 
			
		||||
									</button>
 | 
			
		||||
 | 
			
		||||
									<button
 | 
			
		||||
| 
						 | 
				
			
			@ -628,7 +629,7 @@
 | 
			
		|||
												/>
 | 
			
		||||
											</svg>
 | 
			
		||||
										</div>
 | 
			
		||||
										<div class=" self-center font-medium">Playground</div>
 | 
			
		||||
										<div class=" self-center font-medium">{$i18n.t('Playground')}</div>
 | 
			
		||||
									</button>
 | 
			
		||||
								{/if}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -660,7 +661,7 @@
 | 
			
		|||
											/>
 | 
			
		||||
										</svg>
 | 
			
		||||
									</div>
 | 
			
		||||
									<div class=" self-center font-medium">Settings</div>
 | 
			
		||||
									<div class=" self-center font-medium">{$i18n.t('Settings')}</div>
 | 
			
		||||
								</button>
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -694,7 +695,7 @@
 | 
			
		|||
											/>
 | 
			
		||||
										</svg>
 | 
			
		||||
									</div>
 | 
			
		||||
									<div class=" self-center font-medium">Sign Out</div>
 | 
			
		||||
									<div class=" self-center font-medium">{$i18n.t('Sign Out')}</div>
 | 
			
		||||
								</button>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -707,7 +708,11 @@
 | 
			
		|||
	<div
 | 
			
		||||
		class="fixed left-0 top-[50dvh] z-40 -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
 | 
			
		||||
	>
 | 
			
		||||
		<Tooltip placement="right" content={`${show ? 'Close' : 'Open'} sidebar`} touch={false}>
 | 
			
		||||
		<Tooltip
 | 
			
		||||
			placement="right"
 | 
			
		||||
			content={`${show ? $i18n.t('Close') : $i18n.t('Open')} ${$i18n.t('sidebar')}`}
 | 
			
		||||
			touch={false}
 | 
			
		||||
		>
 | 
			
		||||
			<button
 | 
			
		||||
				id="sidebar-toggle-button"
 | 
			
		||||
				class=" group"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										54
									
								
								src/lib/components/layout/Sidebar/ChatMenu.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/lib/components/layout/Sidebar/ChatMenu.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { DropdownMenu } from 'bits-ui';
 | 
			
		||||
 | 
			
		||||
	import Dropdown from '$lib/components/common/Dropdown.svelte';
 | 
			
		||||
	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
 | 
			
		||||
	import Pencil from '$lib/components/icons/Pencil.svelte';
 | 
			
		||||
	import Tooltip from '$lib/components/common/Tooltip.svelte';
 | 
			
		||||
 | 
			
		||||
	export let renameHandler: Function;
 | 
			
		||||
	export let deleteHandler: Function;
 | 
			
		||||
 | 
			
		||||
	export let onClose: Function;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<Dropdown
 | 
			
		||||
	on:change={(e) => {
 | 
			
		||||
		if (e.detail === false) {
 | 
			
		||||
			onClose();
 | 
			
		||||
		}
 | 
			
		||||
	}}
 | 
			
		||||
>
 | 
			
		||||
	<Tooltip content="More">
 | 
			
		||||
		<slot />
 | 
			
		||||
	</Tooltip>
 | 
			
		||||
 | 
			
		||||
	<div slot="content">
 | 
			
		||||
		<DropdownMenu.Content
 | 
			
		||||
			class="w-full max-w-[130px] rounded-lg px-1 py-1.5 border border-gray-700/50 z-50 bg-gray-850 text-white"
 | 
			
		||||
			sideOffset={8}
 | 
			
		||||
			side="bottom"
 | 
			
		||||
			align="start"
 | 
			
		||||
		>
 | 
			
		||||
			<DropdownMenu.Item
 | 
			
		||||
				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					renameHandler();
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				<Pencil strokeWidth="2" />
 | 
			
		||||
				<div class="flex items-center">Rename</div>
 | 
			
		||||
			</DropdownMenu.Item>
 | 
			
		||||
 | 
			
		||||
			<DropdownMenu.Item
 | 
			
		||||
				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					deleteHandler();
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				<GarbageBin strokeWidth="2" />
 | 
			
		||||
				<div class="flex items-center">Delete</div>
 | 
			
		||||
			</DropdownMenu.Item>
 | 
			
		||||
		</DropdownMenu.Content>
 | 
			
		||||
	</div>
 | 
			
		||||
</Dropdown>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,7 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let messages = [];
 | 
			
		||||
	let textAreaElement: HTMLTextAreaElement;
 | 
			
		||||
| 
						 | 
				
			
			@ -19,16 +21,20 @@
 | 
			
		|||
					class="px-2 py-1 text-sm font-semibold uppercase min-w-[6rem] text-left dark:group-hover:bg-gray-800 rounded-lg transition"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						message.role = message.role === 'user' ? 'assistant' : 'user';
 | 
			
		||||
					}}>{message.role}</button
 | 
			
		||||
					}}>{$i18n.t(message.role)}</button
 | 
			
		||||
				>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="flex-1">
 | 
			
		||||
				<!-- $i18n.t('a user') -->
 | 
			
		||||
				<!-- $i18n.t('an assistant') -->
 | 
			
		||||
				<textarea
 | 
			
		||||
					id="{message.role}-{idx}-textarea"
 | 
			
		||||
					bind:this={textAreaElement}
 | 
			
		||||
					class="w-full bg-transparent outline-none rounded-lg p-2 text-sm resize-none overflow-hidden"
 | 
			
		||||
					placeholder="Enter {message.role === 'user' ? 'a user' : 'an assistant'} message here"
 | 
			
		||||
					placeholder={$i18n.t(`Enter {{role}} message here`, {
 | 
			
		||||
						role: message.role === 'user' ? $i18n.t('a user') : $i18n.t('an assistant')
 | 
			
		||||
					})}
 | 
			
		||||
					rows="1"
 | 
			
		||||
					on:input={(e) => {
 | 
			
		||||
						textAreaElement.style.height = '';
 | 
			
		||||
| 
						 | 
				
			
			@ -100,6 +106,6 @@
 | 
			
		|||
			</svg>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class=" text-sm font-medium">Add message</div>
 | 
			
		||||
		<div class=" text-sm font-medium">{$i18n.t('Add message')}</div>
 | 
			
		||||
	</button>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue