forked from open-webui/open-webui
		
	feat: editable prompt suggestions integration
This commit is contained in:
		
							parent
							
								
									4e1b52e91b
								
							
						
					
					
						commit
						c4a039326f
					
				
					 6 changed files with 172 additions and 52 deletions
				
			
		|  | @ -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; | ||||
| }; | ||||
|  |  | |||
|  | @ -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} | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
| 		} else if (size === 'sm') { | ||||
| 			return 'w-[30rem]'; | ||||
| 		} else { | ||||
| 			return 'w-[40rem]'; | ||||
| 			return 'w-[42rem]'; | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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} | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy J. Baek
						Timothy J. Baek