Timothy J. Baek há 11 meses atrás
pai
commit
ca3108a54d

+ 1 - 1
backend/apps/web/internal/migrations/010_migrate_modelfiles_to_models.py

@@ -70,7 +70,7 @@ def migrate_modelfile_to_model(migrator: Migrator, database: pw.Database):
 
 
         # Insert the processed data into the 'model' table
         # Insert the processed data into the 'model' table
         Model.create(
         Model.create(
-            id=modelfile.tag_name,
+            id=f"ollama-{modelfile.tag_name}",
             user_id=modelfile.user_id,
             user_id=modelfile.user_id,
             base_model_id=info.get("base_model_id"),
             base_model_id=info.get("base_model_id"),
             name=modelfile.modelfile.get("title"),
             name=modelfile.modelfile.get("title"),

+ 5 - 2
backend/main.py

@@ -314,10 +314,12 @@ async def get_all_models():
                     model["name"] = custom_model.name
                     model["name"] = custom_model.name
                     model["info"] = custom_model.model_dump()
                     model["info"] = custom_model.model_dump()
         else:
         else:
-
             owned_by = "openai"
             owned_by = "openai"
             for model in models:
             for model in models:
-                if custom_model.base_model_id == model["id"]:
+                if (
+                    custom_model.base_model_id == model["id"]
+                    or custom_model.base_model_id == model["id"].split(":")[0]
+                ):
                     owned_by = model["owned_by"]
                     owned_by = model["owned_by"]
                     break
                     break
 
 
@@ -329,6 +331,7 @@ async def get_all_models():
                     "created": custom_model.created_at,
                     "created": custom_model.created_at,
                     "owned_by": owned_by,
                     "owned_by": owned_by,
                     "info": custom_model.model_dump(),
                     "info": custom_model.model_dump(),
+                    "preset": True,
                 }
                 }
             )
             )
 
 

+ 17 - 1
src/lib/apis/index.ts

@@ -27,7 +27,23 @@ export const getModels = async (token: string = '') => {
 
 
 	let models = res?.data ?? [];
 	let models = res?.data ?? [];
 
 
-	models = models.filter((models) => models).sort((a, b) => (a.name > b.name ? 1 : -1));
+	models = models
+		.filter((models) => models)
+		.sort((a, b) => {
+			// Compare case-insensitively
+			const lowerA = a.name.toLowerCase();
+			const lowerB = b.name.toLowerCase();
+
+			if (lowerA < lowerB) return -1;
+			if (lowerA > lowerB) return 1;
+
+			// If same case-insensitively, sort by original strings,
+			// lowercase will come before uppercase due to ASCII values
+			if (a < b) return -1;
+			if (a > b) return 1;
+
+			return 0; // They are equal
+		});
 
 
 	console.log(models);
 	console.log(models);
 	return models;
 	return models;

+ 24 - 24
src/lib/components/chat/Settings/Advanced.svelte

@@ -11,7 +11,7 @@
 	let requestFormat = '';
 	let requestFormat = '';
 	let keepAlive = null;
 	let keepAlive = null;
 
 
-	let options = {
+	let params = {
 		// Advanced
 		// Advanced
 		seed: 0,
 		seed: 0,
 		temperature: '',
 		temperature: '',
@@ -44,14 +44,14 @@
 		requestFormat = settings.requestFormat ?? '';
 		requestFormat = settings.requestFormat ?? '';
 		keepAlive = settings.keepAlive ?? null;
 		keepAlive = settings.keepAlive ?? null;
 
 
-		options.seed = settings.seed ?? 0;
-		options.temperature = settings.temperature ?? '';
-		options.repeat_penalty = settings.repeat_penalty ?? '';
-		options.top_k = settings.top_k ?? '';
-		options.top_p = settings.top_p ?? '';
-		options.num_ctx = settings.num_ctx ?? '';
-		options = { ...options, ...settings.options };
-		options.stop = (settings?.options?.stop ?? []).join(',');
+		params.seed = settings.seed ?? 0;
+		params.temperature = settings.temperature ?? '';
+		params.repeat_penalty = settings.repeat_penalty ?? '';
+		params.top_k = settings.top_k ?? '';
+		params.top_p = settings.top_p ?? '';
+		params.num_ctx = settings.num_ctx ?? '';
+		params = { ...params, ...settings.params };
+		params.stop = (settings?.params?.stop ?? []).join(',');
 	});
 	});
 </script>
 </script>
 
 
@@ -59,7 +59,7 @@
 	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 		<div class=" text-sm font-medium">{$i18n.t('Parameters')}</div>
 		<div class=" text-sm font-medium">{$i18n.t('Parameters')}</div>
 
 
-		<AdvancedParams bind:options />
+		<AdvancedParams bind:params />
 		<hr class=" dark:border-gray-700" />
 		<hr class=" dark:border-gray-700" />
 
 
 		<div class=" py-1 w-full justify-between">
 		<div class=" py-1 w-full justify-between">
@@ -128,20 +128,20 @@
 			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 			on:click={() => {
 			on:click={() => {
 				saveSettings({
 				saveSettings({
-					options: {
-						seed: (options.seed !== 0 ? options.seed : undefined) ?? undefined,
-						stop: options.stop !== '' ? options.stop.split(',').filter((e) => e) : undefined,
-						temperature: options.temperature !== '' ? options.temperature : undefined,
-						repeat_penalty: options.repeat_penalty !== '' ? options.repeat_penalty : undefined,
-						repeat_last_n: options.repeat_last_n !== '' ? options.repeat_last_n : undefined,
-						mirostat: options.mirostat !== '' ? options.mirostat : undefined,
-						mirostat_eta: options.mirostat_eta !== '' ? options.mirostat_eta : undefined,
-						mirostat_tau: options.mirostat_tau !== '' ? options.mirostat_tau : undefined,
-						top_k: options.top_k !== '' ? options.top_k : undefined,
-						top_p: options.top_p !== '' ? options.top_p : undefined,
-						tfs_z: options.tfs_z !== '' ? options.tfs_z : undefined,
-						num_ctx: options.num_ctx !== '' ? options.num_ctx : undefined,
-						num_predict: options.num_predict !== '' ? options.num_predict : undefined
+					params: {
+						seed: (params.seed !== 0 ? params.seed : undefined) ?? undefined,
+						stop: params.stop !== '' ? params.stop.split(',').filter((e) => e) : undefined,
+						temperature: params.temperature !== '' ? params.temperature : undefined,
+						repeat_penalty: params.repeat_penalty !== '' ? params.repeat_penalty : undefined,
+						repeat_last_n: params.repeat_last_n !== '' ? params.repeat_last_n : undefined,
+						mirostat: params.mirostat !== '' ? params.mirostat : undefined,
+						mirostat_eta: params.mirostat_eta !== '' ? params.mirostat_eta : undefined,
+						mirostat_tau: params.mirostat_tau !== '' ? params.mirostat_tau : undefined,
+						top_k: params.top_k !== '' ? params.top_k : undefined,
+						top_p: params.top_p !== '' ? params.top_p : undefined,
+						tfs_z: params.tfs_z !== '' ? params.tfs_z : undefined,
+						num_ctx: params.num_ctx !== '' ? params.num_ctx : undefined,
+						num_predict: params.num_predict !== '' ? params.num_predict : undefined
 					},
 					},
 					keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined
 					keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined
 				});
 				});

+ 165 - 91
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte

@@ -3,7 +3,7 @@
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
-	export let options = {
+	export let params = {
 		// Advanced
 		// Advanced
 		seed: 0,
 		seed: 0,
 		stop: '',
 		stop: '',
@@ -17,40 +17,82 @@
 		top_p: '',
 		top_p: '',
 		tfs_z: '',
 		tfs_z: '',
 		num_ctx: '',
 		num_ctx: '',
-		num_predict: ''
+		num_predict: '',
+		template: null
 	};
 	};
+
+	let customFieldName = '';
+	let customFieldValue = '';
 </script>
 </script>
 
 
 <div class=" space-y-3 text-xs">
 <div class=" space-y-3 text-xs">
-	<div>
-		<div class=" py-0.5 flex w-full justify-between">
-			<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Seed')}</div>
-			<div class=" flex-1 self-center">
-				<input
-					class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-					type="number"
-					placeholder="Enter Seed"
-					bind:value={options.seed}
-					autocomplete="off"
-					min="0"
-				/>
-			</div>
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Seed')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition"
+				type="button"
+				on:click={() => {
+					params.seed = (params?.seed ?? null) === null ? 0 : null;
+				}}
+			>
+				{#if (params?.seed ?? null) === null}
+					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
+				{:else}
+					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
+				{/if}
+			</button>
 		</div>
 		</div>
-	</div>
 
 
-	<div>
-		<div class=" py-0.5 flex w-full justify-between">
-			<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Stop Sequence')}</div>
-			<div class=" flex-1 self-center">
-				<input
-					class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-					type="text"
-					placeholder={$i18n.t('Enter stop sequence')}
-					bind:value={options.stop}
-					autocomplete="off"
-				/>
+		{#if (params?.seed ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+						type="number"
+						placeholder="Enter Seed"
+						bind:value={params.seed}
+						autocomplete="off"
+						min="0"
+					/>
+				</div>
 			</div>
 			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Stop Sequence')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition"
+				type="button"
+				on:click={() => {
+					params.stop = (params?.stop ?? null) === null ? '' : null;
+				}}
+			>
+				{#if (params?.stop ?? null) === null}
+					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
+				{:else}
+					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
+				{/if}
+			</button>
 		</div>
 		</div>
+
+		{#if (params?.stop ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+						type="text"
+						placeholder={$i18n.t('Enter stop sequence')}
+						bind:value={params.stop}
+						autocomplete="off"
+					/>
+				</div>
+			</div>
+		{/if}
 	</div>
 	</div>
 
 
 	<div class=" py-0.5 w-full justify-between">
 	<div class=" py-0.5 w-full justify-between">
@@ -61,10 +103,10 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.temperature = options.temperature === '' ? 0.8 : '';
+					params.temperature = (params?.temperature ?? '') === '' ? 0.8 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.temperature === ''}
+				{#if (params?.temperature ?? '') === ''}
 					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
 					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
 				{:else}
 				{:else}
 					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
 					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
@@ -72,7 +114,7 @@
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.temperature !== ''}
+		{#if (params?.temperature ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -81,13 +123,13 @@
 						min="0"
 						min="0"
 						max="1"
 						max="1"
 						step="0.05"
 						step="0.05"
-						bind:value={options.temperature}
+						bind:value={params.temperature}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.temperature}
+						bind:value={params.temperature}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -107,18 +149,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.mirostat = options.mirostat === '' ? 0 : '';
+					params.mirostat = (params?.mirostat ?? '') === '' ? 0 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.mirostat === ''}
+				{#if (params?.mirostat ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.mirostat !== ''}
+		{#if (params?.mirostat ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -127,13 +169,13 @@
 						min="0"
 						min="0"
 						max="2"
 						max="2"
 						step="1"
 						step="1"
-						bind:value={options.mirostat}
+						bind:value={params.mirostat}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.mirostat}
+						bind:value={params.mirostat}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -153,18 +195,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.mirostat_eta = options.mirostat_eta === '' ? 0.1 : '';
+					params.mirostat_eta = (params?.mirostat_eta ?? '') === '' ? 0.1 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.mirostat_eta === ''}
+				{#if (params?.mirostat_eta ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.mirostat_eta !== ''}
+		{#if (params?.mirostat_eta ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -173,13 +215,13 @@
 						min="0"
 						min="0"
 						max="1"
 						max="1"
 						step="0.05"
 						step="0.05"
-						bind:value={options.mirostat_eta}
+						bind:value={params.mirostat_eta}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.mirostat_eta}
+						bind:value={params.mirostat_eta}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -199,10 +241,10 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.mirostat_tau = options.mirostat_tau === '' ? 5.0 : '';
+					params.mirostat_tau = (params?.mirostat_tau ?? '') === '' ? 5.0 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.mirostat_tau === ''}
+				{#if (params?.mirostat_tau ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
 					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
@@ -210,7 +252,7 @@
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.mirostat_tau !== ''}
+		{#if (params?.mirostat_tau ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -219,13 +261,13 @@
 						min="0"
 						min="0"
 						max="10"
 						max="10"
 						step="0.5"
 						step="0.5"
-						bind:value={options.mirostat_tau}
+						bind:value={params.mirostat_tau}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.mirostat_tau}
+						bind:value={params.mirostat_tau}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -245,18 +287,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.top_k = options.top_k === '' ? 40 : '';
+					params.top_k = (params?.top_k ?? '') === '' ? 40 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.top_k === ''}
+				{#if (params?.top_k ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.top_k !== ''}
+		{#if (params?.top_k ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -265,13 +307,13 @@
 						min="0"
 						min="0"
 						max="100"
 						max="100"
 						step="0.5"
 						step="0.5"
-						bind:value={options.top_k}
+						bind:value={params.top_k}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.top_k}
+						bind:value={params.top_k}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -291,18 +333,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.top_p = options.top_p === '' ? 0.9 : '';
+					params.top_p = (params?.top_p ?? '') === '' ? 0.9 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.top_p === ''}
+				{#if (params?.top_p ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.top_p !== ''}
+		{#if (params?.top_p ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -311,13 +353,13 @@
 						min="0"
 						min="0"
 						max="1"
 						max="1"
 						step="0.05"
 						step="0.05"
-						bind:value={options.top_p}
+						bind:value={params.top_p}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.top_p}
+						bind:value={params.top_p}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -337,18 +379,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.repeat_penalty = options.repeat_penalty === '' ? 1.1 : '';
+					params.repeat_penalty = (params?.repeat_penalty ?? '') === '' ? 1.1 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.repeat_penalty === ''}
+				{#if (params?.repeat_penalty ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.repeat_penalty !== ''}
+		{#if (params?.repeat_penalty ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -357,13 +399,13 @@
 						min="0"
 						min="0"
 						max="2"
 						max="2"
 						step="0.05"
 						step="0.05"
-						bind:value={options.repeat_penalty}
+						bind:value={params.repeat_penalty}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.repeat_penalty}
+						bind:value={params.repeat_penalty}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -383,18 +425,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.repeat_last_n = options.repeat_last_n === '' ? 64 : '';
+					params.repeat_last_n = (params?.repeat_last_n ?? '') === '' ? 64 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.repeat_last_n === ''}
+				{#if (params?.repeat_last_n ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.repeat_last_n !== ''}
+		{#if (params?.repeat_last_n ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -403,13 +445,13 @@
 						min="-1"
 						min="-1"
 						max="128"
 						max="128"
 						step="1"
 						step="1"
-						bind:value={options.repeat_last_n}
+						bind:value={params.repeat_last_n}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.repeat_last_n}
+						bind:value={params.repeat_last_n}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="-1"
 						min="-1"
@@ -429,18 +471,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.tfs_z = options.tfs_z === '' ? 1 : '';
+					params.tfs_z = (params?.tfs_z ?? '') === '' ? 1 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.tfs_z === ''}
+				{#if (params?.tfs_z ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.tfs_z !== ''}
+		{#if (params?.tfs_z ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -449,13 +491,13 @@
 						min="0"
 						min="0"
 						max="2"
 						max="2"
 						step="0.05"
 						step="0.05"
-						bind:value={options.tfs_z}
+						bind:value={params.tfs_z}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div>
 				<div>
 					<input
 					<input
-						bind:value={options.tfs_z}
+						bind:value={params.tfs_z}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="0"
 						min="0"
@@ -475,18 +517,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.num_ctx = options.num_ctx === '' ? 2048 : '';
+					params.num_ctx = (params?.num_ctx ?? '') === '' ? 2048 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.num_ctx === ''}
+				{#if (params?.num_ctx ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.num_ctx !== ''}
+		{#if (params?.num_ctx ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -495,13 +537,13 @@
 						min="-1"
 						min="-1"
 						max="10240000"
 						max="10240000"
 						step="1"
 						step="1"
-						bind:value={options.num_ctx}
+						bind:value={params.num_ctx}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div class="">
 				<div class="">
 					<input
 					<input
-						bind:value={options.num_ctx}
+						bind:value={params.num_ctx}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="-1"
 						min="-1"
@@ -519,18 +561,18 @@
 				class="p-1 px-3 text-xs flex rounded transition"
 				class="p-1 px-3 text-xs flex rounded transition"
 				type="button"
 				type="button"
 				on:click={() => {
 				on:click={() => {
-					options.num_predict = options.num_predict === '' ? 128 : '';
+					params.num_predict = (params?.num_predict ?? '') === '' ? 128 : '';
 				}}
 				}}
 			>
 			>
-				{#if options.num_predict === ''}
+				{#if (params?.num_predict ?? '') === ''}
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
 				{:else}
 				{:else}
-					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
 				{/if}
 				{/if}
 			</button>
 			</button>
 		</div>
 		</div>
 
 
-		{#if options.num_predict !== ''}
+		{#if (params?.num_predict ?? '') !== ''}
 			<div class="flex mt-0.5 space-x-2">
 			<div class="flex mt-0.5 space-x-2">
 				<div class=" flex-1">
 				<div class=" flex-1">
 					<input
 					<input
@@ -539,13 +581,13 @@
 						min="-2"
 						min="-2"
 						max="16000"
 						max="16000"
 						step="1"
 						step="1"
-						bind:value={options.num_predict}
+						bind:value={params.num_predict}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
 					/>
 					/>
 				</div>
 				</div>
 				<div class="">
 				<div class="">
 					<input
 					<input
-						bind:value={options.num_predict}
+						bind:value={params.num_predict}
 						type="number"
 						type="number"
 						class=" bg-transparent text-center w-14"
 						class=" bg-transparent text-center w-14"
 						min="-2"
 						min="-2"
@@ -556,4 +598,36 @@
 			</div>
 			</div>
 		{/if}
 		{/if}
 	</div>
 	</div>
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Template')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition"
+				type="button"
+				on:click={() => {
+					params.template = (params?.template ?? null) === null ? '' : null;
+				}}
+			>
+				{#if (params?.template ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.template ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<textarea
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
+						placeholder="Write your model template content here"
+						rows="4"
+						bind:value={params.template}
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
 </div>
 </div>

+ 24 - 24
src/lib/components/chat/Settings/General.svelte

@@ -41,7 +41,7 @@
 	let requestFormat = '';
 	let requestFormat = '';
 	let keepAlive = null;
 	let keepAlive = null;
 
 
-	let options = {
+	let params = {
 		// Advanced
 		// Advanced
 		seed: 0,
 		seed: 0,
 		temperature: '',
 		temperature: '',
@@ -80,14 +80,14 @@
 		requestFormat = settings.requestFormat ?? '';
 		requestFormat = settings.requestFormat ?? '';
 		keepAlive = settings.keepAlive ?? null;
 		keepAlive = settings.keepAlive ?? null;
 
 
-		options.seed = settings.seed ?? 0;
-		options.temperature = settings.temperature ?? '';
-		options.repeat_penalty = settings.repeat_penalty ?? '';
-		options.top_k = settings.top_k ?? '';
-		options.top_p = settings.top_p ?? '';
-		options.num_ctx = settings.num_ctx ?? '';
-		options = { ...options, ...settings.options };
-		options.stop = (settings?.options?.stop ?? []).join(',');
+		params.seed = settings.seed ?? 0;
+		params.temperature = settings.temperature ?? '';
+		params.repeat_penalty = settings.repeat_penalty ?? '';
+		params.top_k = settings.top_k ?? '';
+		params.top_p = settings.top_p ?? '';
+		params.num_ctx = settings.num_ctx ?? '';
+		params = { ...params, ...settings.params };
+		params.stop = (settings?.params?.stop ?? []).join(',');
 	});
 	});
 
 
 	const applyTheme = (_theme: string) => {
 	const applyTheme = (_theme: string) => {
@@ -228,7 +228,7 @@
 			</div>
 			</div>
 
 
 			{#if showAdvanced}
 			{#if showAdvanced}
-				<AdvancedParams bind:options />
+				<AdvancedParams bind:params />
 				<hr class=" dark:border-gray-700" />
 				<hr class=" dark:border-gray-700" />
 
 
 				<div class=" py-1 w-full justify-between">
 				<div class=" py-1 w-full justify-between">
@@ -300,20 +300,20 @@
 			on:click={() => {
 			on:click={() => {
 				saveSettings({
 				saveSettings({
 					system: system !== '' ? system : undefined,
 					system: system !== '' ? system : undefined,
-					options: {
-						seed: (options.seed !== 0 ? options.seed : undefined) ?? undefined,
-						stop: options.stop !== '' ? options.stop.split(',').filter((e) => e) : undefined,
-						temperature: options.temperature !== '' ? options.temperature : undefined,
-						repeat_penalty: options.repeat_penalty !== '' ? options.repeat_penalty : undefined,
-						repeat_last_n: options.repeat_last_n !== '' ? options.repeat_last_n : undefined,
-						mirostat: options.mirostat !== '' ? options.mirostat : undefined,
-						mirostat_eta: options.mirostat_eta !== '' ? options.mirostat_eta : undefined,
-						mirostat_tau: options.mirostat_tau !== '' ? options.mirostat_tau : undefined,
-						top_k: options.top_k !== '' ? options.top_k : undefined,
-						top_p: options.top_p !== '' ? options.top_p : undefined,
-						tfs_z: options.tfs_z !== '' ? options.tfs_z : undefined,
-						num_ctx: options.num_ctx !== '' ? options.num_ctx : undefined,
-						num_predict: options.num_predict !== '' ? options.num_predict : undefined
+					params: {
+						seed: (params.seed !== 0 ? params.seed : undefined) ?? undefined,
+						stop: params.stop !== '' ? params.stop.split(',').filter((e) => e) : undefined,
+						temperature: params.temperature !== '' ? params.temperature : undefined,
+						repeat_penalty: params.repeat_penalty !== '' ? params.repeat_penalty : undefined,
+						repeat_last_n: params.repeat_last_n !== '' ? params.repeat_last_n : undefined,
+						mirostat: params.mirostat !== '' ? params.mirostat : undefined,
+						mirostat_eta: params.mirostat_eta !== '' ? params.mirostat_eta : undefined,
+						mirostat_tau: params.mirostat_tau !== '' ? params.mirostat_tau : undefined,
+						top_k: params.top_k !== '' ? params.top_k : undefined,
+						top_p: params.top_p !== '' ? params.top_p : undefined,
+						tfs_z: params.tfs_z !== '' ? params.tfs_z : undefined,
+						num_ctx: params.num_ctx !== '' ? params.num_ctx : undefined,
+						num_predict: params.num_predict !== '' ? params.num_predict : undefined
 					},
 					},
 					keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined
 					keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined
 				});
 				});

+ 15 - 15
src/routes/(app)/workspace/models/create/+page.svelte

@@ -39,7 +39,7 @@
 	let model = '';
 	let model = '';
 	let system = '';
 	let system = '';
 	let template = '';
 	let template = '';
-	let options = {
+	let params = {
 		// Advanced
 		// Advanced
 		seed: 0,
 		seed: 0,
 		stop: '',
 		stop: '',
@@ -63,19 +63,19 @@
 	$: if (!raw) {
 	$: if (!raw) {
 		content = `FROM ${model}
 		content = `FROM ${model}
 ${template !== '' ? `TEMPLATE """${template}"""` : ''}
 ${template !== '' ? `TEMPLATE """${template}"""` : ''}
-${options.seed !== 0 ? `PARAMETER seed ${options.seed}` : ''}
-${options.stop !== '' ? `PARAMETER stop ${options.stop}` : ''}
-${options.temperature !== '' ? `PARAMETER temperature ${options.temperature}` : ''}
-${options.repeat_penalty !== '' ? `PARAMETER repeat_penalty ${options.repeat_penalty}` : ''}
-${options.repeat_last_n !== '' ? `PARAMETER repeat_last_n ${options.repeat_last_n}` : ''}
-${options.mirostat !== '' ? `PARAMETER mirostat ${options.mirostat}` : ''}
-${options.mirostat_eta !== '' ? `PARAMETER mirostat_eta ${options.mirostat_eta}` : ''}
-${options.mirostat_tau !== '' ? `PARAMETER mirostat_tau ${options.mirostat_tau}` : ''}
-${options.top_k !== '' ? `PARAMETER top_k ${options.top_k}` : ''}
-${options.top_p !== '' ? `PARAMETER top_p ${options.top_p}` : ''}
-${options.tfs_z !== '' ? `PARAMETER tfs_z ${options.tfs_z}` : ''}
-${options.num_ctx !== '' ? `PARAMETER num_ctx ${options.num_ctx}` : ''}
-${options.num_predict !== '' ? `PARAMETER num_predict ${options.num_predict}` : ''}
+${params.seed !== 0 ? `PARAMETER seed ${params.seed}` : ''}
+${params.stop !== '' ? `PARAMETER stop ${params.stop}` : ''}
+${params.temperature !== '' ? `PARAMETER temperature ${params.temperature}` : ''}
+${params.repeat_penalty !== '' ? `PARAMETER repeat_penalty ${params.repeat_penalty}` : ''}
+${params.repeat_last_n !== '' ? `PARAMETER repeat_last_n ${params.repeat_last_n}` : ''}
+${params.mirostat !== '' ? `PARAMETER mirostat ${params.mirostat}` : ''}
+${params.mirostat_eta !== '' ? `PARAMETER mirostat_eta ${params.mirostat_eta}` : ''}
+${params.mirostat_tau !== '' ? `PARAMETER mirostat_tau ${params.mirostat_tau}` : ''}
+${params.top_k !== '' ? `PARAMETER top_k ${params.top_k}` : ''}
+${params.top_p !== '' ? `PARAMETER top_p ${params.top_p}` : ''}
+${params.tfs_z !== '' ? `PARAMETER tfs_z ${params.tfs_z}` : ''}
+${params.num_ctx !== '' ? `PARAMETER num_ctx ${params.num_ctx}` : ''}
+${params.num_predict !== '' ? `PARAMETER num_predict ${params.num_predict}` : ''}
 SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 	}
 	}
 
 
@@ -583,7 +583,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 						<div class=" text-xs font-semibold mb-2">{$i18n.t('Parameters')}</div>
 						<div class=" text-xs font-semibold mb-2">{$i18n.t('Parameters')}</div>
 
 
 						<div>
 						<div>
-							<AdvancedParams bind:options />
+							<AdvancedParams bind:params />
 						</div>
 						</div>
 					</div>
 					</div>
 				{/if}
 				{/if}

+ 104 - 12
src/routes/(app)/workspace/models/edit/+page.svelte

@@ -24,6 +24,9 @@
 	let digest = '';
 	let digest = '';
 	let pullProgress = null;
 	let pullProgress = null;
 
 
+	let showAdvanced = false;
+	let showPreview = false;
+
 	// ///////////
 	// ///////////
 	// model
 	// model
 	// ///////////
 	// ///////////
@@ -39,9 +42,13 @@
 			content: '',
 			content: '',
 			suggestion_prompts: []
 			suggestion_prompts: []
 		},
 		},
-		params: {}
+		params: {
+			system: ''
+		}
 	};
 	};
 
 
+	let params = {};
+
 	const updateHandler = async () => {
 	const updateHandler = async () => {
 		loading = true;
 		loading = true;
 		const res = await updateModelById(localStorage.token, info.id, info);
 		const res = await updateModelById(localStorage.token, info.id, info);
@@ -74,6 +81,11 @@
 						)
 						)
 					)
 					)
 				};
 				};
+
+				if (model.preset && model.owned_by === 'ollama' && !info.base_model_id.includes(':')) {
+					info.base_model_id = `${info.base_model_id}:latest`;
+				}
+
 				console.log(model);
 				console.log(model);
 			} else {
 			} else {
 				goto('/workspace/models');
 				goto('/workspace/models');
@@ -244,8 +256,30 @@
 				</div>
 				</div>
 			</div>
 			</div>
 
 
+			{#if model.preset}
+				<div class="my-2">
+					<div class=" text-sm font-semibold mb-2">{$i18n.t('Base Model (From)')}</div>
+
+					<div>
+						<select
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+							placeholder="Select a base model (e.g. llama3, gpt-4o)"
+							bind:value={info.base_model_id}
+							required
+						>
+							<option value={null} class=" placeholder:text-gray-500"
+								>{$i18n.t('Select a base model')}</option
+							>
+							{#each $models.filter((m) => m.id !== model.id) as model}
+								<option value={model.id}>{model.name}</option>
+							{/each}
+						</select>
+					</div>
+				</div>
+			{/if}
+
 			<div class="my-2">
 			<div class="my-2">
-				<div class=" text-sm font-semibold mb-2">{$i18n.t('description')}*</div>
+				<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}*</div>
 
 
 				<div>
 				<div>
 					<input
 					<input
@@ -259,23 +293,49 @@
 
 
 			<div class="my-2">
 			<div class="my-2">
 				<div class="flex w-full justify-between">
 				<div class="flex w-full justify-between">
-					<div class=" self-center text-sm font-semibold">{$i18n.t('Model')}</div>
+					<div class=" self-center text-sm font-semibold">{$i18n.t('Model Params')}</div>
 				</div>
 				</div>
 
 
 				<!-- <div class=" text-sm font-semibold mb-2"></div> -->
 				<!-- <div class=" text-sm font-semibold mb-2"></div> -->
 
 
 				<div class="mt-2">
 				<div class="mt-2">
-					<div class=" text-xs font-semibold mb-2">{$i18n.t('Params')}*</div>
+					<div class="my-1">
+						<div class=" text-xs font-semibold mb-2">{$i18n.t('System Prompt')}</div>
+						<div>
+							<textarea
+								class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
+								placeholder={`Write your model system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.`}
+								rows="4"
+								bind:value={info.params.system}
+							/>
+						</div>
+					</div>
 
 
-					<div>
-						<!-- <textarea
-							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-							placeholder={`FROM llama2\nPARAMETER temperature 1\nSYSTEM """\nYou are Mario from Super Mario Bros, acting as an assistant.\n"""`}
-							rows="6"
-							bind:value={content}
-							required
-						/> -->
+					<div class="flex w-full justify-between">
+						<div class=" self-center text-sm font-semibold">
+							{$i18n.t('Advanced Params')}
+						</div>
+
+						<button
+							class="p-1 px-3 text-xs flex rounded transition"
+							type="button"
+							on:click={() => {
+								showAdvanced = !showAdvanced;
+							}}
+						>
+							{#if showAdvanced}
+								<span class="ml-2 self-center">{$i18n.t('Hide')}</span>
+							{:else}
+								<span class="ml-2 self-center">{$i18n.t('Show')}</span>
+							{/if}
+						</button>
 					</div>
 					</div>
+
+					{#if showAdvanced}
+						<div class="my-2">
+							<AdvancedParams bind:params />
+						</div>
+					{/if}
 				</div>
 				</div>
 			</div>
 			</div>
 
 
@@ -340,6 +400,38 @@
 				</div>
 				</div>
 			</div>
 			</div>
 
 
+			<div class="my-2 text-gray-500">
+				<div class="flex w-full justify-between mb-2">
+					<div class=" self-center text-sm font-semibold">{$i18n.t('JSON Preview')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						type="button"
+						on:click={() => {
+							showPreview = !showPreview;
+						}}
+					>
+						{#if showPreview}
+							<span class="ml-2 self-center">{$i18n.t('Hide')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Show')}</span>
+						{/if}
+					</button>
+				</div>
+
+				{#if showPreview}
+					<div>
+						<textarea
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+							rows="10"
+							value={JSON.stringify(info, null, 2)}
+							disabled
+							readonly
+						/>
+					</div>
+				{/if}
+			</div>
+
 			{#if pullProgress !== null}
 			{#if pullProgress !== null}
 				<div class="my-2">
 				<div class="my-2">
 					<div class=" text-sm font-semibold mb-2">{$i18n.t('Pull Progress')}</div>
 					<div class=" text-sm font-semibold mb-2">{$i18n.t('Pull Progress')}</div>