forked from open-webui/open-webui
		
	Merge branch 'dev' into feature/img-gen-steps-setting
This commit is contained in:
		
						commit
						271e5bf44d
					
				
					 23 changed files with 1132 additions and 755 deletions
				
			
		| 
						 | 
				
			
			@ -25,7 +25,7 @@
 | 
			
		|||
 | 
			
		||||
	$: if (selectedModels.length > 0 && $models.length > 0) {
 | 
			
		||||
		selectedModels = selectedModels.map((model) =>
 | 
			
		||||
			$models.map((m) => m.name).includes(model) ? model : ''
 | 
			
		||||
			$models.map((m) => m.id).includes(model) ? model : ''
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +45,7 @@
 | 
			
		|||
					{#if model.name === 'hr'}
 | 
			
		||||
						<hr />
 | 
			
		||||
					{:else}
 | 
			
		||||
						<option value={model.name} class="text-gray-700 text-lg"
 | 
			
		||||
						<option value={model.id} class="text-gray-700 text-lg"
 | 
			
		||||
							>{model.name +
 | 
			
		||||
								`${model.size ? ` (${(model.size / 1024 ** 3).toFixed(1)}GB)` : ''}`}</option
 | 
			
		||||
						>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,16 +38,18 @@
 | 
			
		|||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
		{#if ollamaVersion}
 | 
			
		||||
			<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">Ollama Version</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
 | 
			
		||||
					{ollamaVersion ?? 'N/A'}
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" mb-2.5 text-sm font-medium">Ollama Version</div>
 | 
			
		||||
				<div class="flex w-full">
 | 
			
		||||
					<div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
 | 
			
		||||
						{ollamaVersion ?? 'N/A'}
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
	import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	import { getOllamaAPIUrl, updateOllamaAPIUrl } from '$lib/apis/ollama';
 | 
			
		||||
	import { getOllamaAPIUrl, getOllamaVersion, updateOllamaAPIUrl } from '$lib/apis/ollama';
 | 
			
		||||
	import { getOpenAIKey, getOpenAIUrl, updateOpenAIKey, updateOpenAIUrl } from '$lib/apis/openai';
 | 
			
		||||
	import toast from 'svelte-french-toast';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +15,9 @@
 | 
			
		|||
	let OPENAI_API_KEY = '';
 | 
			
		||||
	let OPENAI_API_BASE_URL = '';
 | 
			
		||||
 | 
			
		||||
	let showOpenAI = false;
 | 
			
		||||
	let showLiteLLM = false;
 | 
			
		||||
 | 
			
		||||
	const updateOpenAIHandler = async () => {
 | 
			
		||||
		OPENAI_API_BASE_URL = await updateOpenAIUrl(localStorage.token, OPENAI_API_BASE_URL);
 | 
			
		||||
		OPENAI_API_KEY = await updateOpenAIKey(localStorage.token, OPENAI_API_KEY);
 | 
			
		||||
| 
						 | 
				
			
			@ -24,11 +27,14 @@
 | 
			
		|||
 | 
			
		||||
	const updateOllamaAPIUrlHandler = async () => {
 | 
			
		||||
		API_BASE_URL = await updateOllamaAPIUrl(localStorage.token, API_BASE_URL);
 | 
			
		||||
		const _models = await getModels('ollama');
 | 
			
		||||
 | 
			
		||||
		if (_models.length > 0) {
 | 
			
		||||
		const ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => {
 | 
			
		||||
			return null;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (ollamaVersion) {
 | 
			
		||||
			toast.success('Server connection verified');
 | 
			
		||||
			await models.set(_models);
 | 
			
		||||
			await models.set(await getModels());
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +48,7 @@
 | 
			
		|||
</script>
 | 
			
		||||
 | 
			
		||||
<form
 | 
			
		||||
	class="flex flex-col h-full space-y-3 text-sm"
 | 
			
		||||
	class="flex flex-col h-full justify-between text-sm"
 | 
			
		||||
	on:submit|preventDefault={() => {
 | 
			
		||||
		updateOpenAIHandler();
 | 
			
		||||
		dispatch('save');
 | 
			
		||||
| 
						 | 
				
			
			@ -53,81 +59,100 @@
 | 
			
		|||
		// });
 | 
			
		||||
	}}
 | 
			
		||||
>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class=" mb-2.5 text-sm font-medium">Ollama API URL</div>
 | 
			
		||||
		<div class="flex w-full">
 | 
			
		||||
			<div class="flex-1 mr-2">
 | 
			
		||||
				<input
 | 
			
		||||
					class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
					placeholder="Enter URL (e.g. http://localhost:11434/api)"
 | 
			
		||||
					bind:value={API_BASE_URL}
 | 
			
		||||
				/>
 | 
			
		||||
	<div class="  pr-1.5 overflow-y-scroll max-h-[21rem] 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>
 | 
			
		||||
					<button
 | 
			
		||||
						class=" text-xs font-medium text-gray-500"
 | 
			
		||||
						type="button"
 | 
			
		||||
						on:click={() => {
 | 
			
		||||
							showOpenAI = !showOpenAI;
 | 
			
		||||
						}}>{showOpenAI ? 'Hide' : 'Show'}</button
 | 
			
		||||
					>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				{#if showOpenAI}
 | 
			
		||||
					<div>
 | 
			
		||||
						<div class=" mb-2.5 text-sm font-medium">API Key</div>
 | 
			
		||||
						<div class="flex w-full">
 | 
			
		||||
							<div class="flex-1">
 | 
			
		||||
								<input
 | 
			
		||||
									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
									placeholder="Enter OpenAI API Key"
 | 
			
		||||
									bind:value={OPENAI_API_KEY}
 | 
			
		||||
									autocomplete="off"
 | 
			
		||||
								/>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div>
 | 
			
		||||
						<div class=" mb-2.5 text-sm font-medium">API Base URL</div>
 | 
			
		||||
						<div class="flex w-full">
 | 
			
		||||
							<div class="flex-1">
 | 
			
		||||
								<input
 | 
			
		||||
									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
									placeholder="Enter OpenAI API Base URL"
 | 
			
		||||
									bind:value={OPENAI_API_BASE_URL}
 | 
			
		||||
									autocomplete="off"
 | 
			
		||||
								/>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
							WebUI will make requests to <span class=" text-gray-200"
 | 
			
		||||
								>'{OPENAI_API_BASE_URL}/chat'</span
 | 
			
		||||
							>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</div>
 | 
			
		||||
			<button
 | 
			
		||||
				class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 rounded transition"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					updateOllamaAPIUrlHandler();
 | 
			
		||||
				}}
 | 
			
		||||
				type="button"
 | 
			
		||||
			>
 | 
			
		||||
				<svg
 | 
			
		||||
					xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
					viewBox="0 0 20 20"
 | 
			
		||||
					fill="currentColor"
 | 
			
		||||
					class="w-4 h-4"
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">Ollama API URL</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1 mr-2">
 | 
			
		||||
					<input
 | 
			
		||||
						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
						placeholder="Enter URL (e.g. http://localhost:11434/api)"
 | 
			
		||||
						bind:value={API_BASE_URL}
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<button
 | 
			
		||||
					class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 rounded transition"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						updateOllamaAPIUrlHandler();
 | 
			
		||||
					}}
 | 
			
		||||
					type="button"
 | 
			
		||||
				>
 | 
			
		||||
					<path
 | 
			
		||||
						fill-rule="evenodd"
 | 
			
		||||
						d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
 | 
			
		||||
						clip-rule="evenodd"
 | 
			
		||||
					/>
 | 
			
		||||
				</svg>
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
			Trouble accessing Ollama?
 | 
			
		||||
			<a
 | 
			
		||||
				class=" text-gray-300 font-medium"
 | 
			
		||||
				href="https://github.com/open-webui/open-webui#troubleshooting"
 | 
			
		||||
				target="_blank"
 | 
			
		||||
			>
 | 
			
		||||
				Click here for help.
 | 
			
		||||
			</a>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
	<div class=" space-y-3">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">OpenAI API Key</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
						placeholder="Enter OpenAI API Key"
 | 
			
		||||
						bind:value={OPENAI_API_KEY}
 | 
			
		||||
						autocomplete="off"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
					<svg
 | 
			
		||||
						xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
						viewBox="0 0 20 20"
 | 
			
		||||
						fill="currentColor"
 | 
			
		||||
						class="w-4 h-4"
 | 
			
		||||
					>
 | 
			
		||||
						<path
 | 
			
		||||
							fill-rule="evenodd"
 | 
			
		||||
							d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
 | 
			
		||||
							clip-rule="evenodd"
 | 
			
		||||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">OpenAI API Base URL</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1">
 | 
			
		||||
					<input
 | 
			
		||||
						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
						placeholder="Enter OpenAI API Base URL"
 | 
			
		||||
						bind:value={OPENAI_API_BASE_URL}
 | 
			
		||||
						autocomplete="off"
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
				WebUI will make requests to <span class=" text-gray-200">'{OPENAI_API_BASE_URL}/chat'</span>
 | 
			
		||||
				Trouble accessing Ollama?
 | 
			
		||||
				<a
 | 
			
		||||
					class=" text-gray-300 font-medium"
 | 
			
		||||
					href="https://github.com/open-webui/open-webui#troubleshooting"
 | 
			
		||||
					target="_blank"
 | 
			
		||||
				>
 | 
			
		||||
					Click here for help.
 | 
			
		||||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,9 +35,11 @@
 | 
			
		|||
	const getModels = async () => {
 | 
			
		||||
		models = await getDiffusionModels(localStorage.token).catch((error) => {
 | 
			
		||||
			toast.error(error);
 | 
			
		||||
			return null;
 | 
			
		||||
			return [];
 | 
			
		||||
		});
 | 
			
		||||
		selectedModel = await getDefaultDiffusionModel(localStorage.token).catch((error) => {
 | 
			
		||||
			return '';
 | 
			
		||||
		});
 | 
			
		||||
		selectedModel = await getDefaultDiffusionModel(localStorage.token);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const updateAUTOMATIC1111UrlHandler = async () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,14 +2,33 @@
 | 
			
		|||
	import queue from 'async/queue';
 | 
			
		||||
	import toast from 'svelte-french-toast';
 | 
			
		||||
 | 
			
		||||
	import { createModel, deleteModel, pullModel } from '$lib/apis/ollama';
 | 
			
		||||
	import { createModel, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
 | 
			
		||||
	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 { addLiteLLMModel, deleteLiteLLMModel, getLiteLLMModelInfo } from '$lib/apis/litellm';
 | 
			
		||||
 | 
			
		||||
	export let getModels: Function;
 | 
			
		||||
 | 
			
		||||
	let showLiteLLM = false;
 | 
			
		||||
	let showLiteLLMParams = false;
 | 
			
		||||
 | 
			
		||||
	let liteLLMModelInfo = [];
 | 
			
		||||
 | 
			
		||||
	let liteLLMModel = '';
 | 
			
		||||
	let liteLLMModelName = '';
 | 
			
		||||
	let liteLLMAPIBase = '';
 | 
			
		||||
	let liteLLMAPIKey = '';
 | 
			
		||||
	let liteLLMRPM = '';
 | 
			
		||||
 | 
			
		||||
	let deleteLiteLLMModelId = '';
 | 
			
		||||
 | 
			
		||||
	$: liteLLMModelName = liteLLMModel;
 | 
			
		||||
 | 
			
		||||
	// Models
 | 
			
		||||
	let showExperimentalOllama = false;
 | 
			
		||||
	let ollamaVersion = '';
 | 
			
		||||
	const MAX_PARALLEL_DOWNLOADS = 3;
 | 
			
		||||
	const modelDownloadQueue = queue(
 | 
			
		||||
		(task: { modelName: string }, cb) =>
 | 
			
		||||
| 
						 | 
				
			
			@ -286,256 +305,184 @@
 | 
			
		|||
			opts.callback({ success: true, modelName: opts.modelName });
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const addLiteLLMModelHandler = async () => {
 | 
			
		||||
		if (!liteLLMModelInfo.find((info) => info.model_name === liteLLMModelName)) {
 | 
			
		||||
			const res = await addLiteLLMModel(localStorage.token, {
 | 
			
		||||
				name: liteLLMModelName,
 | 
			
		||||
				model: liteLLMModel,
 | 
			
		||||
				api_base: liteLLMAPIBase,
 | 
			
		||||
				api_key: liteLLMAPIKey,
 | 
			
		||||
				rpm: liteLLMRPM
 | 
			
		||||
			}).catch((error) => {
 | 
			
		||||
				toast.error(error);
 | 
			
		||||
				return null;
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			if (res) {
 | 
			
		||||
				if (res.message) {
 | 
			
		||||
					toast.success(res.message);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			toast.error(`Model ${liteLLMModelName} already exists.`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		liteLLMModelName = '';
 | 
			
		||||
		liteLLMModel = '';
 | 
			
		||||
		liteLLMAPIBase = '';
 | 
			
		||||
		liteLLMAPIKey = '';
 | 
			
		||||
		liteLLMRPM = '';
 | 
			
		||||
 | 
			
		||||
		liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
 | 
			
		||||
		models.set(await getModels());
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const deleteLiteLLMModelHandler = async () => {
 | 
			
		||||
		const res = await deleteLiteLLMModel(localStorage.token, deleteLiteLLMModelId).catch(
 | 
			
		||||
			(error) => {
 | 
			
		||||
				toast.error(error);
 | 
			
		||||
				return null;
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		if (res) {
 | 
			
		||||
			if (res.message) {
 | 
			
		||||
				toast.success(res.message);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		deleteLiteLLMModelId = '';
 | 
			
		||||
		liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
 | 
			
		||||
		models.set(await getModels());
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
 | 
			
		||||
		liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="flex flex-col h-full justify-between text-sm">
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll h-80">
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">Pull a model from Ollama.com</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1 mr-2">
 | 
			
		||||
					<input
 | 
			
		||||
						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
						placeholder="Enter model tag (e.g. mistral:7b)"
 | 
			
		||||
						bind:value={modelTag}
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<button
 | 
			
		||||
					class="px-3 text-gray-100 bg-emerald-600 hover:bg-emerald-700 disabled:bg-gray-700 disabled:cursor-not-allowed rounded transition"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						pullModelHandler();
 | 
			
		||||
					}}
 | 
			
		||||
					disabled={modelTransferring}
 | 
			
		||||
				>
 | 
			
		||||
					{#if modelTransferring}
 | 
			
		||||
						<div class="self-center">
 | 
			
		||||
							<svg
 | 
			
		||||
								class=" w-4 h-4"
 | 
			
		||||
								viewBox="0 0 24 24"
 | 
			
		||||
								fill="currentColor"
 | 
			
		||||
								xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
								><style>
 | 
			
		||||
									.spinner_ajPY {
 | 
			
		||||
										transform-origin: center;
 | 
			
		||||
										animation: spinner_AtaB 0.75s infinite linear;
 | 
			
		||||
									}
 | 
			
		||||
									@keyframes spinner_AtaB {
 | 
			
		||||
										100% {
 | 
			
		||||
											transform: rotate(360deg);
 | 
			
		||||
										}
 | 
			
		||||
									}
 | 
			
		||||
								</style><path
 | 
			
		||||
									d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
 | 
			
		||||
									opacity=".25"
 | 
			
		||||
								/><path
 | 
			
		||||
									d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
 | 
			
		||||
									class="spinner_ajPY"
 | 
			
		||||
								/></svg
 | 
			
		||||
							>
 | 
			
		||||
	<div class=" space-y-3 pr-1.5 overflow-y-scroll h-[23rem]">
 | 
			
		||||
		{#if ollamaVersion}
 | 
			
		||||
			<div class="space-y-2 pr-1.5">
 | 
			
		||||
				<div>
 | 
			
		||||
					<div class=" mb-2 text-sm font-medium">Manage Ollama Models</div>
 | 
			
		||||
 | 
			
		||||
					<div class=" mb-2 text-sm font-medium">Pull a model from Ollama.com</div>
 | 
			
		||||
					<div class="flex w-full">
 | 
			
		||||
						<div class="flex-1 mr-2">
 | 
			
		||||
							<input
 | 
			
		||||
								class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
								placeholder="Enter model tag (e.g. mistral:7b)"
 | 
			
		||||
								bind:value={modelTag}
 | 
			
		||||
							/>
 | 
			
		||||
						</div>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<svg
 | 
			
		||||
							xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
							viewBox="0 0 16 16"
 | 
			
		||||
							fill="currentColor"
 | 
			
		||||
							class="w-4 h-4"
 | 
			
		||||
						<button
 | 
			
		||||
							class="px-3 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
 | 
			
		||||
							on:click={() => {
 | 
			
		||||
								pullModelHandler();
 | 
			
		||||
							}}
 | 
			
		||||
							disabled={modelTransferring}
 | 
			
		||||
						>
 | 
			
		||||
							<path
 | 
			
		||||
								d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
 | 
			
		||||
							/>
 | 
			
		||||
							<path
 | 
			
		||||
								d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
 | 
			
		||||
							/>
 | 
			
		||||
						</svg>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
				To access the available model names for downloading, <a
 | 
			
		||||
					class=" text-gray-500 dark:text-gray-300 font-medium"
 | 
			
		||||
					href="https://ollama.com/library"
 | 
			
		||||
					target="_blank">click here.</a
 | 
			
		||||
				>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			{#if Object.keys(modelDownloadStatus).length > 0}
 | 
			
		||||
				{#each Object.keys(modelDownloadStatus) as model}
 | 
			
		||||
					<div class="flex flex-col">
 | 
			
		||||
						<div class="font-medium mb-1">{model}</div>
 | 
			
		||||
						<div class="">
 | 
			
		||||
							<div
 | 
			
		||||
								class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
 | 
			
		||||
								style="width: {Math.max(15, modelDownloadStatus[model].pullProgress ?? 0)}%"
 | 
			
		||||
							>
 | 
			
		||||
								{modelDownloadStatus[model].pullProgress ?? 0}%
 | 
			
		||||
							</div>
 | 
			
		||||
							<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
 | 
			
		||||
								{modelDownloadStatus[model].digest}
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				{/each}
 | 
			
		||||
			{/if}
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=" mb-2.5 text-sm font-medium">Delete a model</div>
 | 
			
		||||
			<div class="flex w-full">
 | 
			
		||||
				<div class="flex-1 mr-2">
 | 
			
		||||
					<select
 | 
			
		||||
						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
 | 
			
		||||
						bind:value={deleteModelTag}
 | 
			
		||||
						placeholder="Select a model"
 | 
			
		||||
					>
 | 
			
		||||
						{#if !deleteModelTag}
 | 
			
		||||
							<option value="" disabled selected>Select a model</option>
 | 
			
		||||
						{/if}
 | 
			
		||||
						{#each $models.filter((m) => m.size != null) as model}
 | 
			
		||||
							<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
 | 
			
		||||
								>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
 | 
			
		||||
							>
 | 
			
		||||
						{/each}
 | 
			
		||||
					</select>
 | 
			
		||||
				</div>
 | 
			
		||||
				<button
 | 
			
		||||
					class="px-3 bg-red-700 hover:bg-red-800 text-gray-100 rounded transition"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						deleteModelHandler();
 | 
			
		||||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					<svg
 | 
			
		||||
						xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
						viewBox="0 0 16 16"
 | 
			
		||||
						fill="currentColor"
 | 
			
		||||
						class="w-4 h-4"
 | 
			
		||||
					>
 | 
			
		||||
						<path
 | 
			
		||||
							fill-rule="evenodd"
 | 
			
		||||
							d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
 | 
			
		||||
							clip-rule="evenodd"
 | 
			
		||||
						/>
 | 
			
		||||
					</svg>
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700" />
 | 
			
		||||
 | 
			
		||||
		<form
 | 
			
		||||
			on:submit|preventDefault={() => {
 | 
			
		||||
				uploadModelHandler();
 | 
			
		||||
			}}
 | 
			
		||||
		>
 | 
			
		||||
			<div class=" mb-2 flex w-full justify-between">
 | 
			
		||||
				<div class="  text-sm font-medium">
 | 
			
		||||
					Upload a GGUF model <a
 | 
			
		||||
						class=" text-xs font-medium text-gray-500 underline"
 | 
			
		||||
						href="https://github.com/jmorganca/ollama/blob/main/README.md#import-from-gguf"
 | 
			
		||||
						target="_blank">(Experimental)</a
 | 
			
		||||
					>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						if (modelUploadMode === 'file') {
 | 
			
		||||
							modelUploadMode = 'url';
 | 
			
		||||
						} else {
 | 
			
		||||
							modelUploadMode = 'file';
 | 
			
		||||
						}
 | 
			
		||||
					}}
 | 
			
		||||
					type="button"
 | 
			
		||||
				>
 | 
			
		||||
					{#if modelUploadMode === 'file'}
 | 
			
		||||
						<span class="ml-2 self-center">File Mode</span>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<span class="ml-2 self-center">URL Mode</span>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="flex w-full mb-1.5">
 | 
			
		||||
				<div class="flex flex-col w-full">
 | 
			
		||||
					{#if modelUploadMode === 'file'}
 | 
			
		||||
						<div class="flex-1 {modelInputFile && modelInputFile.length > 0 ? 'mr-2' : ''}">
 | 
			
		||||
							<input
 | 
			
		||||
								id="model-upload-input"
 | 
			
		||||
								type="file"
 | 
			
		||||
								bind:files={modelInputFile}
 | 
			
		||||
								on:change={() => {
 | 
			
		||||
									console.log(modelInputFile);
 | 
			
		||||
								}}
 | 
			
		||||
								accept=".gguf"
 | 
			
		||||
								required
 | 
			
		||||
								hidden
 | 
			
		||||
							/>
 | 
			
		||||
 | 
			
		||||
							<button
 | 
			
		||||
								type="button"
 | 
			
		||||
								class="w-full rounded text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-800"
 | 
			
		||||
								on:click={() => {
 | 
			
		||||
									document.getElementById('model-upload-input').click();
 | 
			
		||||
								}}
 | 
			
		||||
							>
 | 
			
		||||
								{#if modelInputFile && modelInputFile.length > 0}
 | 
			
		||||
									{modelInputFile[0].name}
 | 
			
		||||
								{:else}
 | 
			
		||||
									Click here to select
 | 
			
		||||
								{/if}
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
					{:else}
 | 
			
		||||
						<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
 | 
			
		||||
							<input
 | 
			
		||||
								class="w-full rounded text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-800 outline-none {modelFileUrl !==
 | 
			
		||||
								''
 | 
			
		||||
									? 'mr-2'
 | 
			
		||||
									: ''}"
 | 
			
		||||
								type="url"
 | 
			
		||||
								required
 | 
			
		||||
								bind:value={modelFileUrl}
 | 
			
		||||
								placeholder="Type HuggingFace Resolve (Download) URL"
 | 
			
		||||
							/>
 | 
			
		||||
						</div>
 | 
			
		||||
					{/if}
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
 | 
			
		||||
					<button
 | 
			
		||||
						class="px-3 text-gray-100 bg-emerald-600 hover:bg-emerald-700 disabled:bg-gray-700 disabled:cursor-not-allowed rounded transition"
 | 
			
		||||
						type="submit"
 | 
			
		||||
						disabled={modelTransferring}
 | 
			
		||||
					>
 | 
			
		||||
						{#if modelTransferring}
 | 
			
		||||
							<div class="self-center">
 | 
			
		||||
								<svg
 | 
			
		||||
									class=" w-4 h-4"
 | 
			
		||||
									viewBox="0 0 24 24"
 | 
			
		||||
									fill="currentColor"
 | 
			
		||||
									xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
									><style>
 | 
			
		||||
										.spinner_ajPY {
 | 
			
		||||
											transform-origin: center;
 | 
			
		||||
											animation: spinner_AtaB 0.75s infinite linear;
 | 
			
		||||
										}
 | 
			
		||||
										@keyframes spinner_AtaB {
 | 
			
		||||
											100% {
 | 
			
		||||
												transform: rotate(360deg);
 | 
			
		||||
							{#if modelTransferring}
 | 
			
		||||
								<div class="self-center">
 | 
			
		||||
									<svg
 | 
			
		||||
										class=" w-4 h-4"
 | 
			
		||||
										viewBox="0 0 24 24"
 | 
			
		||||
										fill="currentColor"
 | 
			
		||||
										xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
										><style>
 | 
			
		||||
											.spinner_ajPY {
 | 
			
		||||
												transform-origin: center;
 | 
			
		||||
												animation: spinner_AtaB 0.75s infinite linear;
 | 
			
		||||
											}
 | 
			
		||||
										}
 | 
			
		||||
									</style><path
 | 
			
		||||
										d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
 | 
			
		||||
										opacity=".25"
 | 
			
		||||
									/><path
 | 
			
		||||
										d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
 | 
			
		||||
										class="spinner_ajPY"
 | 
			
		||||
									/></svg
 | 
			
		||||
											@keyframes spinner_AtaB {
 | 
			
		||||
												100% {
 | 
			
		||||
													transform: rotate(360deg);
 | 
			
		||||
												}
 | 
			
		||||
											}
 | 
			
		||||
										</style><path
 | 
			
		||||
											d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
 | 
			
		||||
											opacity=".25"
 | 
			
		||||
										/><path
 | 
			
		||||
											d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
 | 
			
		||||
											class="spinner_ajPY"
 | 
			
		||||
										/></svg
 | 
			
		||||
									>
 | 
			
		||||
								</div>
 | 
			
		||||
							{:else}
 | 
			
		||||
								<svg
 | 
			
		||||
									xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
									viewBox="0 0 16 16"
 | 
			
		||||
									fill="currentColor"
 | 
			
		||||
									class="w-4 h-4"
 | 
			
		||||
								>
 | 
			
		||||
									<path
 | 
			
		||||
										d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
 | 
			
		||||
									/>
 | 
			
		||||
									<path
 | 
			
		||||
										d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
 | 
			
		||||
									/>
 | 
			
		||||
								</svg>
 | 
			
		||||
							{/if}
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
						To access the available model names for downloading, <a
 | 
			
		||||
							class=" text-gray-500 dark:text-gray-300 font-medium"
 | 
			
		||||
							href="https://ollama.com/library"
 | 
			
		||||
							target="_blank">click here.</a
 | 
			
		||||
						>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					{#if Object.keys(modelDownloadStatus).length > 0}
 | 
			
		||||
						{#each Object.keys(modelDownloadStatus) as model}
 | 
			
		||||
							<div class="flex flex-col">
 | 
			
		||||
								<div class="font-medium mb-1">{model}</div>
 | 
			
		||||
								<div class="">
 | 
			
		||||
									<div
 | 
			
		||||
										class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
 | 
			
		||||
										style="width: {Math.max(15, modelDownloadStatus[model].pullProgress ?? 0)}%"
 | 
			
		||||
									>
 | 
			
		||||
										{modelDownloadStatus[model].pullProgress ?? 0}%
 | 
			
		||||
									</div>
 | 
			
		||||
									<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
 | 
			
		||||
										{modelDownloadStatus[model].digest}
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						{:else}
 | 
			
		||||
						{/each}
 | 
			
		||||
					{/if}
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div>
 | 
			
		||||
					<div class=" mb-2 text-sm font-medium">Delete a model</div>
 | 
			
		||||
					<div class="flex w-full">
 | 
			
		||||
						<div class="flex-1 mr-2">
 | 
			
		||||
							<select
 | 
			
		||||
								class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
								bind:value={deleteModelTag}
 | 
			
		||||
								placeholder="Select a model"
 | 
			
		||||
							>
 | 
			
		||||
								{#if !deleteModelTag}
 | 
			
		||||
									<option value="" disabled selected>Select a model</option>
 | 
			
		||||
								{/if}
 | 
			
		||||
								{#each $models.filter((m) => m.size != null) as model}
 | 
			
		||||
									<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
 | 
			
		||||
										>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
 | 
			
		||||
									>
 | 
			
		||||
								{/each}
 | 
			
		||||
							</select>
 | 
			
		||||
						</div>
 | 
			
		||||
						<button
 | 
			
		||||
							class="px-3 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
 | 
			
		||||
							on:click={() => {
 | 
			
		||||
								deleteModelHandler();
 | 
			
		||||
							}}
 | 
			
		||||
						>
 | 
			
		||||
							<svg
 | 
			
		||||
								xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
								viewBox="0 0 16 16"
 | 
			
		||||
| 
						 | 
				
			
			@ -543,54 +490,438 @@
 | 
			
		|||
								class="w-4 h-4"
 | 
			
		||||
							>
 | 
			
		||||
								<path
 | 
			
		||||
									d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
 | 
			
		||||
								/>
 | 
			
		||||
								<path
 | 
			
		||||
									d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
 | 
			
		||||
									fill-rule="evenodd"
 | 
			
		||||
									d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
 | 
			
		||||
									clip-rule="evenodd"
 | 
			
		||||
								/>
 | 
			
		||||
							</svg>
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div>
 | 
			
		||||
					<div class="flex justify-between items-center text-xs">
 | 
			
		||||
						<div class=" text-sm font-medium">Experimental</div>
 | 
			
		||||
						<button
 | 
			
		||||
							class=" text-xs font-medium text-gray-500"
 | 
			
		||||
							type="button"
 | 
			
		||||
							on:click={() => {
 | 
			
		||||
								showExperimentalOllama = !showExperimentalOllama;
 | 
			
		||||
							}}>{showExperimentalOllama ? 'Show' : 'Hide'}</button
 | 
			
		||||
						>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				{#if showExperimentalOllama}
 | 
			
		||||
					<form
 | 
			
		||||
						on:submit|preventDefault={() => {
 | 
			
		||||
							uploadModelHandler();
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						<div class=" mb-2 flex w-full justify-between">
 | 
			
		||||
							<div class="  text-sm font-medium">Upload a GGUF model</div>
 | 
			
		||||
 | 
			
		||||
							<button
 | 
			
		||||
								class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
								on:click={() => {
 | 
			
		||||
									if (modelUploadMode === 'file') {
 | 
			
		||||
										modelUploadMode = 'url';
 | 
			
		||||
									} else {
 | 
			
		||||
										modelUploadMode = 'file';
 | 
			
		||||
									}
 | 
			
		||||
								}}
 | 
			
		||||
								type="button"
 | 
			
		||||
							>
 | 
			
		||||
								{#if modelUploadMode === 'file'}
 | 
			
		||||
									<span class="ml-2 self-center">File Mode</span>
 | 
			
		||||
								{:else}
 | 
			
		||||
									<span class="ml-2 self-center">URL Mode</span>
 | 
			
		||||
								{/if}
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex w-full mb-1.5">
 | 
			
		||||
							<div class="flex flex-col w-full">
 | 
			
		||||
								{#if modelUploadMode === 'file'}
 | 
			
		||||
									<div class="flex-1 {modelInputFile && modelInputFile.length > 0 ? 'mr-2' : ''}">
 | 
			
		||||
										<input
 | 
			
		||||
											id="model-upload-input"
 | 
			
		||||
											type="file"
 | 
			
		||||
											bind:files={modelInputFile}
 | 
			
		||||
											on:change={() => {
 | 
			
		||||
												console.log(modelInputFile);
 | 
			
		||||
											}}
 | 
			
		||||
											accept=".gguf"
 | 
			
		||||
											required
 | 
			
		||||
											hidden
 | 
			
		||||
										/>
 | 
			
		||||
 | 
			
		||||
										<button
 | 
			
		||||
											type="button"
 | 
			
		||||
											class="w-full rounded text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-850"
 | 
			
		||||
											on:click={() => {
 | 
			
		||||
												document.getElementById('model-upload-input').click();
 | 
			
		||||
											}}
 | 
			
		||||
										>
 | 
			
		||||
											{#if modelInputFile && modelInputFile.length > 0}
 | 
			
		||||
												{modelInputFile[0].name}
 | 
			
		||||
											{:else}
 | 
			
		||||
												Click here to select
 | 
			
		||||
											{/if}
 | 
			
		||||
										</button>
 | 
			
		||||
									</div>
 | 
			
		||||
								{:else}
 | 
			
		||||
									<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
 | 
			
		||||
										<input
 | 
			
		||||
											class="w-full rounded text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !==
 | 
			
		||||
											''
 | 
			
		||||
												? 'mr-2'
 | 
			
		||||
												: ''}"
 | 
			
		||||
											type="url"
 | 
			
		||||
											required
 | 
			
		||||
											bind:value={modelFileUrl}
 | 
			
		||||
											placeholder="Type HuggingFace Resolve (Download) URL"
 | 
			
		||||
										/>
 | 
			
		||||
									</div>
 | 
			
		||||
								{/if}
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
 | 
			
		||||
								<button
 | 
			
		||||
									class="px-3 text-gray-100 bg-emerald-600 hover:bg-emerald-700 disabled:bg-gray-700 disabled:cursor-not-allowed rounded transition"
 | 
			
		||||
									type="submit"
 | 
			
		||||
									disabled={modelTransferring}
 | 
			
		||||
								>
 | 
			
		||||
									{#if modelTransferring}
 | 
			
		||||
										<div class="self-center">
 | 
			
		||||
											<svg
 | 
			
		||||
												class=" w-4 h-4"
 | 
			
		||||
												viewBox="0 0 24 24"
 | 
			
		||||
												fill="currentColor"
 | 
			
		||||
												xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
												><style>
 | 
			
		||||
													.spinner_ajPY {
 | 
			
		||||
														transform-origin: center;
 | 
			
		||||
														animation: spinner_AtaB 0.75s infinite linear;
 | 
			
		||||
													}
 | 
			
		||||
													@keyframes spinner_AtaB {
 | 
			
		||||
														100% {
 | 
			
		||||
															transform: rotate(360deg);
 | 
			
		||||
														}
 | 
			
		||||
													}
 | 
			
		||||
												</style><path
 | 
			
		||||
													d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
 | 
			
		||||
													opacity=".25"
 | 
			
		||||
												/><path
 | 
			
		||||
													d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
 | 
			
		||||
													class="spinner_ajPY"
 | 
			
		||||
												/></svg
 | 
			
		||||
											>
 | 
			
		||||
										</div>
 | 
			
		||||
									{:else}
 | 
			
		||||
										<svg
 | 
			
		||||
											xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
											viewBox="0 0 16 16"
 | 
			
		||||
											fill="currentColor"
 | 
			
		||||
											class="w-4 h-4"
 | 
			
		||||
										>
 | 
			
		||||
											<path
 | 
			
		||||
												d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
 | 
			
		||||
											/>
 | 
			
		||||
											<path
 | 
			
		||||
												d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
 | 
			
		||||
											/>
 | 
			
		||||
										</svg>
 | 
			
		||||
									{/if}
 | 
			
		||||
								</button>
 | 
			
		||||
							{/if}
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
 | 
			
		||||
							<div>
 | 
			
		||||
								<div>
 | 
			
		||||
									<div class=" my-2.5 text-sm font-medium">Modelfile Content</div>
 | 
			
		||||
									<textarea
 | 
			
		||||
										bind:value={modelFileContent}
 | 
			
		||||
										class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
 | 
			
		||||
										rows="6"
 | 
			
		||||
									/>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</button>
 | 
			
		||||
						<div class=" mt-1 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
							To access the GGUF models available for downloading, <a
 | 
			
		||||
								class=" text-gray-500 dark:text-gray-300 font-medium"
 | 
			
		||||
								href="https://huggingface.co/models?search=gguf"
 | 
			
		||||
								target="_blank">click here.</a
 | 
			
		||||
							>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						{#if uploadProgress !== null}
 | 
			
		||||
							<div class="mt-2">
 | 
			
		||||
								<div class=" mb-2 text-xs">Upload Progress</div>
 | 
			
		||||
 | 
			
		||||
								<div class="w-full rounded-full dark:bg-gray-800">
 | 
			
		||||
									<div
 | 
			
		||||
										class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
 | 
			
		||||
										style="width: {Math.max(15, uploadProgress ?? 0)}%"
 | 
			
		||||
									>
 | 
			
		||||
										{uploadProgress ?? 0}%
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
								<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
 | 
			
		||||
									{modelFileDigest}
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</form>
 | 
			
		||||
				{/if}
 | 
			
		||||
			</div>
 | 
			
		||||
			<hr class=" dark:border-gray-700 my-2" />
 | 
			
		||||
		{/if}
 | 
			
		||||
 | 
			
		||||
			{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
 | 
			
		||||
		<div class=" space-y-3">
 | 
			
		||||
			<div class="mt-2 space-y-3 pr-1.5">
 | 
			
		||||
				<div>
 | 
			
		||||
					<div class=" mb-2 text-sm font-medium">Manage LiteLLM Models</div>
 | 
			
		||||
 | 
			
		||||
					<div>
 | 
			
		||||
						<div class=" my-2.5 text-sm font-medium">Modelfile Content</div>
 | 
			
		||||
						<textarea
 | 
			
		||||
							bind:value={modelFileContent}
 | 
			
		||||
							class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
 | 
			
		||||
							rows="6"
 | 
			
		||||
						/>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			{/if}
 | 
			
		||||
			<div class=" mt-1 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
				To access the GGUF models available for downloading, <a
 | 
			
		||||
					class=" text-gray-500 dark:text-gray-300 font-medium"
 | 
			
		||||
					href="https://huggingface.co/models?search=gguf"
 | 
			
		||||
					target="_blank">click here.</a
 | 
			
		||||
				>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			{#if uploadProgress !== null}
 | 
			
		||||
				<div class="mt-2">
 | 
			
		||||
					<div class=" mb-2 text-xs">Upload Progress</div>
 | 
			
		||||
 | 
			
		||||
					<div class="w-full rounded-full dark:bg-gray-800">
 | 
			
		||||
						<div
 | 
			
		||||
							class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
 | 
			
		||||
							style="width: {Math.max(15, uploadProgress ?? 0)}%"
 | 
			
		||||
						>
 | 
			
		||||
							{uploadProgress ?? 0}%
 | 
			
		||||
						<div class="flex justify-between items-center text-xs">
 | 
			
		||||
							<div class=" text-sm font-medium">Add a model</div>
 | 
			
		||||
							<button
 | 
			
		||||
								class=" text-xs font-medium text-gray-500"
 | 
			
		||||
								type="button"
 | 
			
		||||
								on:click={() => {
 | 
			
		||||
									showLiteLLMParams = !showLiteLLMParams;
 | 
			
		||||
								}}>{showLiteLLMParams ? 'Advanced' : 'Default'}</button
 | 
			
		||||
							>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
 | 
			
		||||
						{modelFileDigest}
 | 
			
		||||
 | 
			
		||||
					<div class="my-2 space-y-2">
 | 
			
		||||
						<div class="flex w-full mb-1.5">
 | 
			
		||||
							<div class="flex-1 mr-2">
 | 
			
		||||
								<input
 | 
			
		||||
									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
									placeholder="Enter LiteLLM Model (litellm_params.model)"
 | 
			
		||||
									bind:value={liteLLMModel}
 | 
			
		||||
									autocomplete="off"
 | 
			
		||||
								/>
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							<button
 | 
			
		||||
								class="px-3 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
 | 
			
		||||
								on:click={() => {
 | 
			
		||||
									addLiteLLMModelHandler();
 | 
			
		||||
								}}
 | 
			
		||||
							>
 | 
			
		||||
								<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>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						{#if showLiteLLMParams}
 | 
			
		||||
							<div>
 | 
			
		||||
								<div class=" mb-1.5 text-sm font-medium">Model Name</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-850 outline-none"
 | 
			
		||||
											placeholder="Enter Model Name (model_name)"
 | 
			
		||||
											bind:value={liteLLMModelName}
 | 
			
		||||
											autocomplete="off"
 | 
			
		||||
										/>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							<div>
 | 
			
		||||
								<div class=" mb-1.5 text-sm font-medium">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-850 outline-none"
 | 
			
		||||
											placeholder="Enter LiteLLM API Base URL (litellm_params.api_base)"
 | 
			
		||||
											bind:value={liteLLMAPIBase}
 | 
			
		||||
											autocomplete="off"
 | 
			
		||||
										/>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							<div>
 | 
			
		||||
								<div class=" mb-1.5 text-sm font-medium">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-850 outline-none"
 | 
			
		||||
											placeholder="Enter LiteLLM API Key (litellm_params.api_key)"
 | 
			
		||||
											bind:value={liteLLMAPIKey}
 | 
			
		||||
											autocomplete="off"
 | 
			
		||||
										/>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							<div>
 | 
			
		||||
								<div class="mb-1.5 text-sm font-medium">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-850 outline-none"
 | 
			
		||||
											placeholder="Enter LiteLLM API RPM (litellm_params.rpm)"
 | 
			
		||||
											bind:value={liteLLMRPM}
 | 
			
		||||
											autocomplete="off"
 | 
			
		||||
										/>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="mb-2 text-xs text-gray-400 dark:text-gray-500">
 | 
			
		||||
						Not sure what to add?
 | 
			
		||||
						<a
 | 
			
		||||
							class=" text-gray-300 font-medium"
 | 
			
		||||
							href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
 | 
			
		||||
							target="_blank"
 | 
			
		||||
						>
 | 
			
		||||
							Click here for help.
 | 
			
		||||
						</a>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div>
 | 
			
		||||
						<div class=" mb-2.5 text-sm font-medium">Delete a model</div>
 | 
			
		||||
						<div class="flex w-full">
 | 
			
		||||
							<div class="flex-1 mr-2">
 | 
			
		||||
								<select
 | 
			
		||||
									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
									bind:value={deleteLiteLLMModelId}
 | 
			
		||||
									placeholder="Select a model"
 | 
			
		||||
								>
 | 
			
		||||
									{#if !deleteLiteLLMModelId}
 | 
			
		||||
										<option value="" disabled selected>Select a model</option>
 | 
			
		||||
									{/if}
 | 
			
		||||
									{#each liteLLMModelInfo as model}
 | 
			
		||||
										<option value={model.model_info.id} class="bg-gray-100 dark:bg-gray-700"
 | 
			
		||||
											>{model.model_name}</option
 | 
			
		||||
										>
 | 
			
		||||
									{/each}
 | 
			
		||||
								</select>
 | 
			
		||||
							</div>
 | 
			
		||||
							<button
 | 
			
		||||
								class="px-3 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
 | 
			
		||||
								on:click={() => {
 | 
			
		||||
									deleteLiteLLMModelHandler();
 | 
			
		||||
								}}
 | 
			
		||||
							>
 | 
			
		||||
								<svg
 | 
			
		||||
									xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
									viewBox="0 0 16 16"
 | 
			
		||||
									fill="currentColor"
 | 
			
		||||
									class="w-4 h-4"
 | 
			
		||||
								>
 | 
			
		||||
									<path
 | 
			
		||||
										fill-rule="evenodd"
 | 
			
		||||
										d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
 | 
			
		||||
										clip-rule="evenodd"
 | 
			
		||||
									/>
 | 
			
		||||
								</svg>
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			{/if}
 | 
			
		||||
		</form>
 | 
			
		||||
			</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"
 | 
			
		||||
							href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
 | 
			
		||||
							target="_blank"
 | 
			
		||||
						>
 | 
			
		||||
							Click here for help.
 | 
			
		||||
						</a>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div> -->
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
 | 
			
		||||
	import { getOllamaModels } from '$lib/apis/ollama';
 | 
			
		||||
	import { getOpenAIModels } from '$lib/apis/openai';
 | 
			
		||||
	import { getLiteLLMModels } from '$lib/apis/litellm';
 | 
			
		||||
 | 
			
		||||
	import Modal from '../common/Modal.svelte';
 | 
			
		||||
	import Account from './Settings/Account.svelte';
 | 
			
		||||
| 
						 | 
				
			
			@ -27,23 +28,29 @@
 | 
			
		|||
 | 
			
		||||
	let selectedTab = 'general';
 | 
			
		||||
 | 
			
		||||
	const getModels = async (type = 'all') => {
 | 
			
		||||
		const models = [];
 | 
			
		||||
		models.push(
 | 
			
		||||
			...(await getOllamaModels(localStorage.token).catch((error) => {
 | 
			
		||||
				toast.error(error);
 | 
			
		||||
				return [];
 | 
			
		||||
			}))
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		if (type === 'all') {
 | 
			
		||||
			const openAIModels = await getOpenAIModels(localStorage.token).catch((error) => {
 | 
			
		||||
	const getModels = async () => {
 | 
			
		||||
		let models = await Promise.all([
 | 
			
		||||
			await getOllamaModels(localStorage.token).catch((error) => {
 | 
			
		||||
				console.log(error);
 | 
			
		||||
				return null;
 | 
			
		||||
			});
 | 
			
		||||
			models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : []));
 | 
			
		||||
		}
 | 
			
		||||
			}),
 | 
			
		||||
			await getOpenAIModels(localStorage.token).catch((error) => {
 | 
			
		||||
				console.log(error);
 | 
			
		||||
				return null;
 | 
			
		||||
			}),
 | 
			
		||||
			await getLiteLLMModels(localStorage.token).catch((error) => {
 | 
			
		||||
				console.log(error);
 | 
			
		||||
				return null;
 | 
			
		||||
			})
 | 
			
		||||
		]);
 | 
			
		||||
 | 
			
		||||
		models = models
 | 
			
		||||
			.filter((models) => models)
 | 
			
		||||
			.reduce((a, e, i, arr) => a.concat(e, ...(i < arr.length - 1 ? [{ name: 'hr' }] : [])), []);
 | 
			
		||||
 | 
			
		||||
		// models.push(...(ollamaModels ? [{ name: 'hr' }, ...ollamaModels] : []));
 | 
			
		||||
		// models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : []));
 | 
			
		||||
		// models.push(...(liteLLMModels ? [{ name: 'hr' }, ...liteLLMModels] : []));
 | 
			
		||||
		return models;
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue