Browse Source

feat: editable prompt suggestions integration

Timothy J. Baek 1 year ago
parent
commit
c4a039326f

+ 3 - 2
backend/apps/web/routers/configs.py

@@ -49,14 +49,15 @@ async def set_global_default_models(
         )
 
 
-@router.post("/default/suggestions", response_model=str)
+@router.post("/default/suggestions", response_model=List[PromptSuggestion])
 async def set_global_default_suggestions(
     request: Request,
     form_data: SetDefaultSuggestionsForm,
     user=Depends(get_current_user),
 ):
     if user.role == "admin":
-        request.app.state.DEFAULT_PROMPT_SUGGESTIONS = form_data.suggestions
+        data = form_data.model_dump()
+        request.app.state.DEFAULT_PROMPT_SUGGESTIONS = data["suggestions"]
         return request.app.state.DEFAULT_PROMPT_SUGGESTIONS
     else:
         raise HTTPException(

+ 30 - 0
src/lib/apis/configs/index.ts

@@ -29,3 +29,33 @@ export const setDefaultModels = async (token: string, models: string) => {
 
 	return res;
 };
+
+export const setDefaultPromptSuggestions = async (token: string, promptSuggestions: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/configs/default/suggestions`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			suggestions: promptSuggestions
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};

+ 1 - 1
src/lib/components/chat/MessageInput/Suggestions.svelte

@@ -13,7 +13,7 @@
 				}}
 			>
 				<div class="flex flex-col text-left self-center">
-					{#if prompt.title}
+					{#if prompt.title && prompt.title[0] !== ''}
 						<div class="text-sm font-medium dark:text-gray-300">{prompt.title[0]}</div>
 						<div class="text-sm text-gray-500">{prompt.title[1]}</div>
 					{:else}

+ 136 - 30
src/lib/components/chat/SettingsModal.svelte

@@ -34,6 +34,8 @@
 		updateOpenAIUrl
 	} from '$lib/apis/openai';
 	import { resetVectorDB } from '$lib/apis/rag';
+	import { setDefaultPromptSuggestions } from '$lib/apis/configs';
+	import { getBackendConfig } from '$lib/apis';
 
 	export let show = false;
 
@@ -99,6 +101,9 @@
 	let OPENAI_API_KEY = '';
 	let OPENAI_API_BASE_URL = '';
 
+	// Interface
+	let promptSuggestions = [];
+
 	// Addons
 	let titleAutoGenerate = true;
 	let speechAutoSend = false;
@@ -191,6 +196,11 @@
 		await models.set(await getModels());
 	};
 
+	const updateInterfaceHandler = async () => {
+		promptSuggestions = await setDefaultPromptSuggestions(localStorage.token, promptSuggestions);
+		await config.set(await getBackendConfig());
+	};
+
 	const toggleTheme = async () => {
 		if (theme === 'dark') {
 			theme = 'light';
@@ -577,6 +587,7 @@
 			API_BASE_URL = await getOllamaAPIUrl(localStorage.token);
 			OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token);
 			OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
+			promptSuggestions = $config?.default_prompt_suggestions;
 		}
 
 		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
@@ -745,6 +756,32 @@
 						</div>
 						<div class=" self-center">External</div>
 					</button>
+
+					<button
+						class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+						'interface'
+							? 'bg-gray-200 dark:bg-gray-700'
+							: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
+						on:click={() => {
+							selectedTab = 'interface';
+						}}
+					>
+						<div class=" self-center mr-2">
+							<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="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4Zm10.5 5.707a.5.5 0 0 0-.146-.353l-1-1a.5.5 0 0 0-.708 0L9.354 9.646a.5.5 0 0 1-.708 0L6.354 7.354a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0-.146.353V12a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V9.707ZM12 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
+									clip-rule="evenodd"
+								/>
+							</svg>
+						</div>
+						<div class=" self-center">Interface</div>
+					</button>
 				{/if}
 
 				<button
@@ -797,34 +834,6 @@
 					<div class=" self-center">Chats</div>
 				</button>
 
-				{#if !$config || ($config && !$config.auth)}
-					<button
-						class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
-						'auth'
-							? 'bg-gray-200 dark:bg-gray-700'
-							: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
-						on:click={() => {
-							selectedTab = 'auth';
-						}}
-					>
-						<div class=" self-center mr-2">
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 24 24"
-								fill="currentColor"
-								class="w-4 h-4"
-							>
-								<path
-									fill-rule="evenodd"
-									d="M12.516 2.17a.75.75 0 00-1.032 0 11.209 11.209 0 01-7.877 3.08.75.75 0 00-.722.515A12.74 12.74 0 002.25 9.75c0 5.942 4.064 10.933 9.563 12.348a.749.749 0 00.374 0c5.499-1.415 9.563-6.406 9.563-12.348 0-1.39-.223-2.73-.635-3.985a.75.75 0 00-.722-.516l-.143.001c-2.996 0-5.717-1.17-7.734-3.08zm3.094 8.016a.75.75 0 10-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 00-1.06 1.06l2.25 2.25a.75.75 0 001.14-.094l3.75-5.25z"
-									clip-rule="evenodd"
-								/>
-							</svg>
-						</div>
-						<div class=" self-center">Authentication</div>
-					</button>
-				{/if}
-
 				<button
 					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
 					'account'
@@ -877,7 +886,7 @@
 					<div class=" self-center">About</div>
 				</button>
 			</div>
-			<div class="flex-1 md:min-h-[340px]">
+			<div class="flex-1 md:min-h-[380px]">
 				{#if selectedTab === 'general'}
 					<div class="flex flex-col space-y-3">
 						<div>
@@ -1048,7 +1057,7 @@
 					</div>
 				{:else if selectedTab === 'advanced'}
 					<div class="flex flex-col h-full justify-between text-sm">
-						<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-72">
+						<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 							<div class=" text-sm font-medium">Parameters</div>
 
 							<Advanced bind:options />
@@ -1483,6 +1492,103 @@
 							</div>
 						</div>
 
+						<div class="flex justify-end pt-3 text-sm font-medium">
+							<button
+								class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
+								type="submit"
+							>
+								Save
+							</button>
+						</div>
+					</form>
+				{:else if selectedTab === 'interface'}
+					<form
+						class="flex flex-col h-full justify-between space-y-3 text-sm"
+						on:submit|preventDefault={() => {
+							updateInterfaceHandler();
+							show = false;
+						}}
+					>
+						<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
+							<div class="flex w-full justify-between mb-2">
+								<div class=" self-center text-sm font-semibold">Default Prompt Suggestions</div>
+
+								<button
+									class="p-1 px-3 text-xs flex rounded transition"
+									type="button"
+									on:click={() => {
+										if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
+											promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
+										}
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										viewBox="0 0 20 20"
+										fill="currentColor"
+										class="w-4 h-4"
+									>
+										<path
+											d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
+										/>
+									</svg>
+								</button>
+							</div>
+							<div class="flex flex-col space-y-1">
+								{#each promptSuggestions as prompt, promptIdx}
+									<div class=" flex border dark:border-gray-600 rounded-lg">
+										<div class="flex flex-col flex-1">
+											<div class="flex border-b dark:border-gray-600 w-full">
+												<input
+													class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
+													placeholder="Title (e.g. Tell me a fun fact)"
+													bind:value={prompt.title[0]}
+												/>
+
+												<input
+													class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
+													placeholder="Subtitle (e.g. about the Roman Empire)"
+													bind:value={prompt.title[1]}
+												/>
+											</div>
+
+											<input
+												class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
+												placeholder="Prompt (e.g. Tell me a fun fact about the Roman Empire)"
+												bind:value={prompt.content}
+											/>
+										</div>
+
+										<button
+											class="px-2"
+											type="button"
+											on:click={() => {
+												promptSuggestions.splice(promptIdx, 1);
+												promptSuggestions = promptSuggestions;
+											}}
+										>
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												viewBox="0 0 20 20"
+												fill="currentColor"
+												class="w-4 h-4"
+											>
+												<path
+													d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+												/>
+											</svg>
+										</button>
+									</div>
+								{/each}
+							</div>
+
+							{#if promptSuggestions.length > 0}
+								<div class="text-xs text-left w-full mt-2">
+									Adjusting these settings will apply changes universally to all users.
+								</div>
+							{/if}
+						</div>
+
 						<div class="flex justify-end pt-3 text-sm font-medium">
 							<button
 								class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"

+ 1 - 1
src/lib/components/common/Modal.svelte

@@ -13,7 +13,7 @@
 		} else if (size === 'sm') {
 			return 'w-[30rem]';
 		} else {
-			return 'w-[40rem]';
+			return 'w-[42rem]';
 		}
 	};
 

+ 1 - 18
src/routes/(app)/+page.svelte

@@ -765,24 +765,7 @@
 		bind:files
 		bind:prompt
 		bind:autoScroll
-		suggestionPrompts={selectedModelfile?.suggestionPrompts ?? [
-			{
-				title: ['Help me study', 'vocabulary for a college entrance exam'],
-				content: `Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.`
-			},
-			{
-				title: ['Give me ideas', `for what to do with my kids' art`],
-				content: `What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.`
-			},
-			{
-				title: ['Tell me a fun fact', 'about the Roman Empire'],
-				content: 'Tell me a random fun fact about the Roman Empire'
-			},
-			{
-				title: ['Show me a code snippet', `of a website's sticky header`],
-				content: `Show me a code snippet of a website's sticky header in CSS and JavaScript.`
-			}
-		]}
+		suggestionPrompts={selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
 		{messages}
 		{submitPrompt}
 		{stopResponse}