浏览代码

feat: multiple ollama model management

Timothy J. Baek 1 年之前
父节点
当前提交
fbdac832bb
共有 3 个文件被更改,包括 348 次插入303 次删除
  1. 6 2
      backend/apps/ollama/main.py
  2. 17 14
      src/lib/apis/ollama/index.ts
  3. 325 287
      src/lib/components/chat/Settings/Models.svelte

+ 6 - 2
backend/apps/ollama/main.py

@@ -207,9 +207,12 @@ async def pull_model(
     form_data: ModelNameForm, url_idx: int = 0, user=Depends(get_admin_user)
 ):
     url = app.state.OLLAMA_BASE_URLS[url_idx]
+    print(url)
+
     r = None
 
-    def get_request(url):
+    def get_request():
+        nonlocal url
         nonlocal r
         try:
 
@@ -235,7 +238,7 @@ async def pull_model(
             raise e
 
     try:
-        return await run_in_threadpool(get_request(url))
+        return await run_in_threadpool(get_request)
     except Exception as e:
         print(e)
         error_detail = "Open WebUI: Server Connection Error"
@@ -454,6 +457,7 @@ async def delete_model(
             )
 
     url = app.state.OLLAMA_BASE_URLS[url_idx]
+    print(url)
 
     try:
         r = requests.request(

+ 17 - 14
src/lib/apis/ollama/index.ts

@@ -318,20 +318,23 @@ export const createModel = async (token: string, tagName: string, content: strin
 	return res;
 };
 
-export const deleteModel = async (token: string, tagName: string) => {
+export const deleteModel = async (token: string, tagName: string, urlIdx: string | null = null) => {
 	let error = null;
 
-	const res = await fetch(`${OLLAMA_API_BASE_URL}/api/delete`, {
-		method: 'DELETE',
-		headers: {
-			Accept: 'application/json',
-			'Content-Type': 'application/json',
-			Authorization: `Bearer ${token}`
-		},
-		body: JSON.stringify({
-			name: tagName
-		})
-	})
+	const res = await fetch(
+		`${OLLAMA_API_BASE_URL}/api/delete${urlIdx !== null ? `/${urlIdx}` : ''}`,
+		{
+			method: 'DELETE',
+			headers: {
+				Accept: 'application/json',
+				'Content-Type': 'application/json',
+				Authorization: `Bearer ${token}`
+			},
+			body: JSON.stringify({
+				name: tagName
+			})
+		}
+	)
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			return res.json();
@@ -358,10 +361,10 @@ export const deleteModel = async (token: string, tagName: string) => {
 	return res;
 };
 
-export const pullModel = async (token: string, tagName: string) => {
+export const pullModel = async (token: string, tagName: string, urlIdx: string | null = null) => {
 	let error = null;
 
-	const res = await fetch(`${OLLAMA_API_BASE_URL}/api/pull`, {
+	const res = await fetch(`${OLLAMA_API_BASE_URL}/api/pull${urlIdx !== null ? `/${urlIdx}` : ''}`, {
 		method: 'POST',
 		headers: {
 			Accept: 'application/json',

+ 325 - 287
src/lib/components/chat/Settings/Models.svelte

@@ -2,7 +2,13 @@
 	import queue from 'async/queue';
 	import { toast } from 'svelte-sonner';
 
-	import { createModel, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
+	import {
+		createModel,
+		deleteModel,
+		getOllamaUrls,
+		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';
@@ -27,6 +33,9 @@
 	$: liteLLMModelName = liteLLMModel;
 
 	// Models
+
+	let OLLAMA_URLS = [];
+	let selectedOllamaUrlIdx: string | null = null;
 	let showExperimentalOllama = false;
 	let ollamaVersion = '';
 	const MAX_PARALLEL_DOWNLOADS = 3;
@@ -236,9 +245,11 @@
 	};
 
 	const deleteModelHandler = async () => {
-		const res = await deleteModel(localStorage.token, deleteModelTag).catch((error) => {
-			toast.error(error);
-		});
+		const res = await deleteModel(localStorage.token, deleteModelTag, selectedOllamaUrlIdx).catch(
+			(error) => {
+				toast.error(error);
+			}
+		);
 
 		if (res) {
 			toast.success(`Deleted ${deleteModelTag}`);
@@ -249,10 +260,12 @@
 	};
 
 	const pullModelHandlerProcessor = async (opts: { modelName: string; callback: Function }) => {
-		const res = await pullModel(localStorage.token, opts.modelName).catch((error) => {
-			opts.callback({ success: false, error, modelName: opts.modelName });
-			return null;
-		});
+		const res = await pullModel(localStorage.token, opts.modelName, selectedOllamaUrlIdx).catch(
+			(error) => {
+				opts.callback({ success: false, error, modelName: opts.modelName });
+				return null;
+			}
+		);
 
 		if (res) {
 			const reader = res.body
@@ -358,6 +371,15 @@
 	};
 
 	onMount(async () => {
+		OLLAMA_URLS = await getOllamaUrls(localStorage.token).catch((error) => {
+			toast.error(error);
+			return [];
+		});
+
+		if (OLLAMA_URLS.length > 1) {
+			selectedOllamaUrlIdx = 0;
+		}
+
 		ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
 		liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
 	});
@@ -367,52 +389,137 @@
 	<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>
-						<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}
+				<div class="text-sm font-medium">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"
 						>
-							{#if modelTransferring}
-								<div class="self-center">
+							{#each OLLAMA_URLS as url, idx}
+								<option value={idx} class="bg-gray-100 dark:bg-gray-700">{url}</option>
+							{/each}
+						</select>
+					</div>
+				{/if}
+
+				<div class="space-y-2">
+					<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-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)"
+									bind:value={modelTag}
+								/>
+							</div>
+							<button
+								class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+								on:click={() => {
+									pullModelHandler();
+								}}
+								disabled={modelTransferring}
+							>
+								{#if modelTransferring}
+									<div class="self-center">
+										<svg
+											class=" w-4 h-4"
+											viewBox="0 0 24 24"
+											fill="currentColor"
+											xmlns="http://www.w3.org/2000/svg"
+											><style>
+												.spinner_ajPY {
+													transform-origin: center;
+													animation: spinner_AtaB 0.75s infinite linear;
+												}
+												@keyframes spinner_AtaB {
+													100% {
+														transform: rotate(360deg);
+													}
+												}
+											</style><path
+												d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+												opacity=".25"
+											/><path
+												d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+												class="spinner_ajPY"
+											/></svg
+										>
+									</div>
+								{:else}
 									<svg
-										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
+										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 underline"
+								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-lg 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-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+								on:click={() => {
+									deleteModelHandler();
+								}}
+							>
 								<svg
 									xmlns="http://www.w3.org/2000/svg"
 									viewBox="0 0 16 16"
@@ -420,261 +527,192 @@
 									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"
+										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>
-							{/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 underline"
-							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>
-
-				<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>
+							</button>
 						</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"
-								fill="currentColor"
-								class="w-4 h-4"
-							>
-								<path
-									fill-rule="evenodd"
-									d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
-									clip-rule="evenodd"
-								/>
-							</svg>
-						</button>
 					</div>
-				</div>
-
-				<div>
-					<div class="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>
 
+					<div class="pt-1">
+						<div class="flex justify-between items-center text-xs">
+							<div class=" text-sm font-medium">Experimental</div>
 							<button
-								class="p-1 px-3 text-xs flex rounded transition"
-								on:click={() => {
-									if (modelUploadMode === 'file') {
-										modelUploadMode = 'url';
-									} else {
-										modelUploadMode = 'file';
-									}
-								}}
+								class=" text-xs font-medium text-gray-500"
 								type="button"
+								on:click={() => {
+									showExperimentalOllama = !showExperimentalOllama;
+								}}>{showExperimentalOllama ? 'Hide' : 'Show'}</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>
 
-						<div class="flex w-full mb-1.5">
-							<div class="flex flex-col w-full">
-								{#if modelUploadMode === 'file'}
-									<div class="flex-1 {modelInputFile && modelInputFile.length > 0 ? 'mr-2' : ''}">
-										<input
-											id="model-upload-input"
-											bind:this={modelUploadInputElement}
-											type="file"
-											bind:files={modelInputFile}
-											on:change={() => {
-												console.log(modelInputFile);
-											}}
-											accept=".gguf"
-											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={modelUploadInputElement.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 Hugging Face Resolve (Download) URL"
-										/>
-									</div>
-								{/if}
-							</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>
 
-							{#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}
+									class="p-1 px-3 text-xs flex rounded transition"
+									on:click={() => {
+										if (modelUploadMode === 'file') {
+											modelUploadMode = 'url';
+										} else {
+											modelUploadMode = 'file';
+										}
+									}}
+									type="button"
 								>
-									{#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
+									{#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"
+												bind:this={modelUploadInputElement}
+												type="file"
+												bind:files={modelInputFile}
+												on:change={() => {
+													console.log(modelInputFile);
+												}}
+												accept=".gguf"
+												required
+												hidden
+											/>
+
+											<button
+												type="button"
+												class="w-full rounded-lg text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-850"
+												on:click={modelUploadInputElement.click}
 											>
+												{#if modelInputFile && modelInputFile.length > 0}
+													{modelInputFile[0].name}
+												{:else}
+													Click here to select
+												{/if}
+											</button>
 										</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"
+										<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
+											<input
+												class="w-full rounded-lg 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 Hugging Face Resolve (Download) URL"
 											/>
-										</svg>
+										</div>
 									{/if}
-								</button>
-							{/if}
-						</div>
+								</div>
 
-						{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
-							<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 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 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 underline"
+									href="https://huggingface.co/models?search=gguf"
+									target="_blank">click here.</a
+								>
 							</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 underline"
-								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>
+							{#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="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>
-								<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
-									{modelFileDigest}
-								</div>
-							</div>
-						{/if}
-					</form>
-				{/if}
+							{/if}
+						</form>
+					{/if}
+				</div>
 			</div>
 			<hr class=" dark:border-gray-700 my-2" />
 		{/if}
@@ -692,7 +730,7 @@
 								type="button"
 								on:click={() => {
 									showLiteLLMParams = !showLiteLLMParams;
-								}}>{showLiteLLMParams ? 'Advanced' : 'Default'}</button
+								}}>{showLiteLLMParams ? 'Hide Additional Params' : 'Show Additional Params'}</button
 							>
 						</div>
 					</div>
@@ -701,7 +739,7 @@
 						<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"
+									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)"
 									bind:value={liteLLMModel}
 									autocomplete="off"
@@ -709,7 +747,7 @@
 							</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"
+								class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
 								on:click={() => {
 									addLiteLLMModelHandler();
 								}}
@@ -733,7 +771,7 @@
 								<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"
+											class="w-full rounded-lg 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"
@@ -747,7 +785,7 @@
 								<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"
+											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)"
 											bind:value={liteLLMAPIBase}
 											autocomplete="off"
@@ -761,7 +799,7 @@
 								<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"
+											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)"
 											bind:value={liteLLMAPIKey}
 											autocomplete="off"
@@ -775,7 +813,7 @@
 								<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"
+											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)"
 											bind:value={liteLLMRPM}
 											autocomplete="off"
@@ -802,7 +840,7 @@
 						<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"
+									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"
 								>
@@ -817,7 +855,7 @@
 								</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"
+								class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
 								on:click={() => {
 									deleteLiteLLMModelHandler();
 								}}