Kaynağa Gözat

feat: enable api_base_url edit from ui

Timothy J. Baek 1 yıl önce
ebeveyn
işleme
b6a780e7c7

+ 77 - 0
package-lock.json

@@ -21,6 +21,7 @@
 				"@sveltejs/adapter-auto": "^2.0.0",
 				"@sveltejs/adapter-static": "^2.0.3",
 				"@sveltejs/kit": "^1.20.4",
+				"@tailwindcss/typography": "^0.5.10",
 				"@typescript-eslint/eslint-plugin": "^6.0.0",
 				"@typescript-eslint/parser": "^6.0.0",
 				"autoprefixer": "^10.4.16",
@@ -838,6 +839,34 @@
 				"vite": "^4.0.0"
 			}
 		},
+		"node_modules/@tailwindcss/typography": {
+			"version": "0.5.10",
+			"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
+			"integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==",
+			"dev": true,
+			"dependencies": {
+				"lodash.castarray": "^4.4.0",
+				"lodash.isplainobject": "^4.0.6",
+				"lodash.merge": "^4.6.2",
+				"postcss-selector-parser": "6.0.10"
+			},
+			"peerDependencies": {
+				"tailwindcss": ">=3.0.0 || insiders"
+			}
+		},
+		"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
+			"version": "6.0.10",
+			"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+			"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+			"dev": true,
+			"dependencies": {
+				"cssesc": "^3.0.0",
+				"util-deprecate": "^1.0.2"
+			},
+			"engines": {
+				"node": ">=4"
+			}
+		},
 		"node_modules/@types/cookie": {
 			"version": "0.5.2",
 			"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz",
@@ -2603,6 +2632,18 @@
 			"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
 			"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
 		},
+		"node_modules/lodash.castarray": {
+			"version": "4.4.0",
+			"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
+			"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
+			"dev": true
+		},
+		"node_modules/lodash.isplainobject": {
+			"version": "4.0.6",
+			"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+			"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+			"dev": true
+		},
 		"node_modules/lodash.merge": {
 			"version": "4.6.2",
 			"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -4670,6 +4711,30 @@
 				"debug": "^4.3.4"
 			}
 		},
+		"@tailwindcss/typography": {
+			"version": "0.5.10",
+			"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
+			"integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==",
+			"dev": true,
+			"requires": {
+				"lodash.castarray": "^4.4.0",
+				"lodash.isplainobject": "^4.0.6",
+				"lodash.merge": "^4.6.2",
+				"postcss-selector-parser": "6.0.10"
+			},
+			"dependencies": {
+				"postcss-selector-parser": {
+					"version": "6.0.10",
+					"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+					"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+					"dev": true,
+					"requires": {
+						"cssesc": "^3.0.0",
+						"util-deprecate": "^1.0.2"
+					}
+				}
+			}
+		},
 		"@types/cookie": {
 			"version": "0.5.2",
 			"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz",
@@ -5909,6 +5974,18 @@
 			"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
 			"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
 		},
+		"lodash.castarray": {
+			"version": "4.4.0",
+			"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
+			"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
+			"dev": true
+		},
+		"lodash.isplainobject": {
+			"version": "4.0.6",
+			"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+			"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+			"dev": true
+		},
 		"lodash.merge": {
 			"version": "4.6.2",
 			"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",

+ 1 - 0
package.json

@@ -20,6 +20,7 @@
 		"@sveltejs/adapter-auto": "^2.0.0",
 		"@sveltejs/adapter-static": "^2.0.3",
 		"@sveltejs/kit": "^1.20.4",
+		"@tailwindcss/typography": "^0.5.10",
 		"@typescript-eslint/eslint-plugin": "^6.0.0",
 		"@typescript-eslint/parser": "^6.0.0",
 		"autoprefixer": "^10.4.16",

+ 59 - 4
src/lib/components/chat/SettingsModal.svelte

@@ -1,13 +1,14 @@
 <script lang="ts">
 	import Modal from '../common/Modal.svelte';
 
-	import { API_BASE_URL } from '$lib/constants';
+	import { API_BASE_URL as BUILD_TIME_API_BASE_URL } from '$lib/constants';
 	import toast from 'svelte-french-toast';
 
 	export let show = false;
 	export let saveSettings: Function;
 	export let getModelTags: Function;
 
+	let API_BASE_URL = BUILD_TIME_API_BASE_URL;
 	let system = '';
 	let temperature = 0.8;
 
@@ -33,6 +34,14 @@
 		});
 	};
 
+	const checkOllamaConnection = async () => {
+		const res = await getModelTags(API_BASE_URL);
+
+		if (res) {
+			toast.success('Server connection verified');
+		}
+	};
+
 	const pullModelHandler = async () => {
 		const res = await fetch(`${API_BASE_URL}/pull`, {
 			method: 'POST',
@@ -139,6 +148,7 @@
 
 	$: if (show) {
 		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
+		API_BASE_URL = settings.API_BASE_URL ?? BUILD_TIME_API_BASE_URL;
 		system = settings.system ?? '';
 		temperature = settings.temperature ?? 0.8;
 	}
@@ -227,6 +237,50 @@
 			<div class="flex-1 md:min-h-[300px]">
 				{#if selectedMenu === 'general'}
 					<div class="flex flex-col space-y-3">
+						<div>
+							<div class=" mb-2.5 text-sm font-medium">Ollama Server URL</div>
+							<div class="flex w-full">
+								<div class="flex-1 mr-2">
+									<input
+										class="w-full rounded py-2 px-4 text-sm text-gray-300 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-600 hover:bg-gray-700 rounded transition"
+									on:click={() => {
+										checkOllamaConnection();
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										viewBox="0 0 20 20"
+										fill="currentColor"
+										class="w-4 h-4"
+									>
+										<path
+											fill-rule="evenodd"
+											d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
+											clip-rule="evenodd"
+										/>
+									</svg>
+								</button>
+							</div>
+
+							<div class="mt-2 text-xs text-gray-500">
+								Trouble accessing Ollama? <a
+									class=" text-gray-300 font-medium"
+									href="https://github.com/ollama-webui/ollama-webui#troubleshooting"
+									target="_blank"
+								>
+									Click here for help.
+								</a>
+							</div>
+						</div>
+
+						<hr class=" border-gray-700" />
+
 						<div>
 							<div class=" mb-2.5 text-sm font-medium">System Prompt</div>
 							<textarea
@@ -260,6 +314,7 @@
 								class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 transition rounded"
 								on:click={() => {
 									saveSettings(
+										API_BASE_URL === '' ? BUILD_TIME_API_BASE_URL : API_BASE_URL,
 										system != '' ? system : null,
 										temperature != 0.8 ? temperature : null
 									);
@@ -305,11 +360,11 @@
 							</div>
 
 							<div class="mt-2 text-xs text-gray-500">
-								To access the available model names for downloading, click <a
+								To access the available model names for downloading, <a
 									class=" text-gray-300 font-medium"
 									href="https://ollama.ai/library"
-									target="_blank">here</a
-								>.
+									target="_blank">click here.</a
+								>
 							</div>
 
 							{#if pullProgress !== ''}

+ 29 - 23
src/routes/+page.svelte

@@ -6,13 +6,14 @@
 	import fileSaver from 'file-saver';
 	const { saveAs } = fileSaver;
 	import hljs from 'highlight.js';
-	import 'highlight.js/styles/dark.min.css';
-	import { API_BASE_URL } from '$lib/constants';
+	import 'highlight.js/styles/github-dark.min.css';
+	import { API_BASE_URL as BUILD_TIME_API_BASE_URL } from '$lib/constants';
 	import { onMount, tick } from 'svelte';
 
 	import Navbar from '$lib/components/layout/Navbar.svelte';
 	import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
 
+	let API_BASE_URL = BUILD_TIME_API_BASE_URL;
 	let suggestions = ''; // $page.url.searchParams.get('suggestions');
 
 	let models = [];
@@ -31,21 +32,19 @@
 	let messages = [];
 
 	onMount(async () => {
+		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
+
+		API_BASE_URL = settings.API_BASE_URL ?? BUILD_TIME_API_BASE_URL;
 		console.log(API_BASE_URL);
+		system = settings.system ?? null;
+		temperature = settings.temperature ?? null;
+
 		await getModelTags();
 
-		let settings = localStorage.getItem('settings');
-		if (settings) {
-			settings = JSON.parse(settings);
-			console.log(settings);
-
-			selectedModel =
-				settings.model && models.map((model) => model.name).includes(settings.model)
-					? settings.model
-					: '';
-			system = settings.system ?? null;
-			temperature = settings.temperature ?? null;
-		}
+		selectedModel =
+			settings.model && models.map((model) => model.name).includes(settings.model)
+				? settings.model
+				: '';
 
 		db = await openDB('Chats', 1, {
 			upgrade(db) {
@@ -133,21 +132,23 @@
 		toast.success('Default model updated');
 	};
 
-	const saveSettings = (_system, _temperature) => {
+	const saveSettings = async (_api_base_url, _system, _temperature) => {
+		API_BASE_URL = _api_base_url;
 		system = _system;
 		temperature = _temperature;
 
 		let settings = localStorage.getItem('settings') ?? '{}';
 		if (settings) {
 			settings = JSON.parse(settings);
+
+			settings.API_BASE_URL = API_BASE_URL;
 			settings.system = system;
 			settings.temperature = temperature;
 			localStorage.setItem('settings', JSON.stringify(settings));
 		}
 
 		console.log(settings);
-
-		console.log('saved');
+		await getModelTags();
 	};
 
 	const createNewChat = () => {
@@ -163,7 +164,10 @@
 				settings = JSON.parse(settings);
 				console.log(settings);
 
-				selectedModel = settings.model ?? selectedModel;
+				selectedModel =
+					settings.model && models.map((model) => model.name).includes(settings.model)
+						? settings.model
+						: '';
 				system = settings.system ?? system;
 				temperature = settings.temperature ?? temperature;
 			}
@@ -219,8 +223,8 @@
 	// Ollama functions
 	//////////////////////////
 
-	const getModelTags = async () => {
-		const res = await fetch(`${API_BASE_URL}/tags`, {
+	const getModelTags = async (url = null) => {
+		const res = await fetch(`${url === null ? API_BASE_URL : url}/tags`, {
 			method: 'GET',
 			headers: {
 				Accept: 'application/json',
@@ -233,11 +237,13 @@
 			})
 			.catch((error) => {
 				console.log(error);
-				return { models: [] };
+				toast.error('Server connection failed');
+				return null;
 			});
 
 		console.log(res);
-		models = res.models ?? [];
+		models = res?.models ?? [];
+		return res;
 	};
 
 	const submitPrompt = async (user_prompt) => {
@@ -609,7 +615,7 @@
 												</div>
 											</div>
 										{:else}
-											<div class="markdown-body whitespace-pre-line">
+											<div class="whitespace-pre-line">
 												{@html marked.parse(message.content)}
 											</div>
 										{/if}

+ 1 - 1
tailwind.config.js

@@ -20,5 +20,5 @@ export default {
 			}
 		}
 	},
-	plugins: []
+	plugins: [require('@tailwindcss/typography')]
 };