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
|
@ -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(
|
async def set_global_default_suggestions(
|
||||||
request: Request,
|
request: Request,
|
||||||
form_data: SetDefaultSuggestionsForm,
|
form_data: SetDefaultSuggestionsForm,
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
):
|
):
|
||||||
if user.role == "admin":
|
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
|
return request.app.state.DEFAULT_PROMPT_SUGGESTIONS
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
|
@ -29,3 +29,33 @@ export const setDefaultModels = async (token: string, models: string) => {
|
||||||
|
|
||||||
return res;
|
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">
|
<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 font-medium dark:text-gray-300">{prompt.title[0]}</div>
|
||||||
<div class="text-sm text-gray-500">{prompt.title[1]}</div>
|
<div class="text-sm text-gray-500">{prompt.title[1]}</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -34,6 +34,8 @@
|
||||||
updateOpenAIUrl
|
updateOpenAIUrl
|
||||||
} from '$lib/apis/openai';
|
} from '$lib/apis/openai';
|
||||||
import { resetVectorDB } from '$lib/apis/rag';
|
import { resetVectorDB } from '$lib/apis/rag';
|
||||||
|
import { setDefaultPromptSuggestions } from '$lib/apis/configs';
|
||||||
|
import { getBackendConfig } from '$lib/apis';
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
|
|
||||||
|
@ -99,6 +101,9 @@
|
||||||
let OPENAI_API_KEY = '';
|
let OPENAI_API_KEY = '';
|
||||||
let OPENAI_API_BASE_URL = '';
|
let OPENAI_API_BASE_URL = '';
|
||||||
|
|
||||||
|
// Interface
|
||||||
|
let promptSuggestions = [];
|
||||||
|
|
||||||
// Addons
|
// Addons
|
||||||
let titleAutoGenerate = true;
|
let titleAutoGenerate = true;
|
||||||
let speechAutoSend = false;
|
let speechAutoSend = false;
|
||||||
|
@ -191,6 +196,11 @@
|
||||||
await models.set(await getModels());
|
await models.set(await getModels());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateInterfaceHandler = async () => {
|
||||||
|
promptSuggestions = await setDefaultPromptSuggestions(localStorage.token, promptSuggestions);
|
||||||
|
await config.set(await getBackendConfig());
|
||||||
|
};
|
||||||
|
|
||||||
const toggleTheme = async () => {
|
const toggleTheme = async () => {
|
||||||
if (theme === 'dark') {
|
if (theme === 'dark') {
|
||||||
theme = 'light';
|
theme = 'light';
|
||||||
|
@ -577,6 +587,7 @@
|
||||||
API_BASE_URL = await getOllamaAPIUrl(localStorage.token);
|
API_BASE_URL = await getOllamaAPIUrl(localStorage.token);
|
||||||
OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token);
|
OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token);
|
||||||
OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
|
OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
|
||||||
|
promptSuggestions = $config?.default_prompt_suggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
||||||
|
@ -745,6 +756,32 @@
|
||||||
</div>
|
</div>
|
||||||
<div class=" self-center">External</div>
|
<div class=" self-center">External</div>
|
||||||
</button>
|
</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}
|
{/if}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -797,34 +834,6 @@
|
||||||
<div class=" self-center">Chats</div>
|
<div class=" self-center">Chats</div>
|
||||||
</button>
|
</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
|
<button
|
||||||
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
||||||
'account'
|
'account'
|
||||||
|
@ -877,7 +886,7 @@
|
||||||
<div class=" self-center">About</div>
|
<div class=" self-center">About</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 md:min-h-[340px]">
|
<div class="flex-1 md:min-h-[380px]">
|
||||||
{#if selectedTab === 'general'}
|
{#if selectedTab === 'general'}
|
||||||
<div class="flex flex-col space-y-3">
|
<div class="flex flex-col space-y-3">
|
||||||
<div>
|
<div>
|
||||||
|
@ -1048,7 +1057,7 @@
|
||||||
</div>
|
</div>
|
||||||
{:else if selectedTab === 'advanced'}
|
{:else if selectedTab === 'advanced'}
|
||||||
<div class="flex flex-col h-full justify-between text-sm">
|
<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>
|
<div class=" text-sm font-medium">Parameters</div>
|
||||||
|
|
||||||
<Advanced bind:options />
|
<Advanced bind:options />
|
||||||
|
@ -1483,6 +1492,103 @@
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="flex justify-end pt-3 text-sm font-medium">
|
||||||
<button
|
<button
|
||||||
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
|
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') {
|
} else if (size === 'sm') {
|
||||||
return 'w-[30rem]';
|
return 'w-[30rem]';
|
||||||
} else {
|
} else {
|
||||||
return 'w-[40rem]';
|
return 'w-[42rem]';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -765,24 +765,7 @@
|
||||||
bind:files
|
bind:files
|
||||||
bind:prompt
|
bind:prompt
|
||||||
bind:autoScroll
|
bind:autoScroll
|
||||||
suggestionPrompts={selectedModelfile?.suggestionPrompts ?? [
|
suggestionPrompts={selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
|
||||||
{
|
|
||||||
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.`
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
{messages}
|
{messages}
|
||||||
{submitPrompt}
|
{submitPrompt}
|
||||||
{stopResponse}
|
{stopResponse}
|
||||||
|
|
Loading…
Reference in a new issue