mirror of
https://github.com/open-webui/open-webui
synced 2025-06-26 18:26:48 +00:00
feat: smart suggestions
This commit is contained in:
parent
3adfa29f7d
commit
43a490457d
@ -220,6 +220,7 @@
|
|||||||
suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
|
suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
|
||||||
$config?.default_prompt_suggestions ??
|
$config?.default_prompt_suggestions ??
|
||||||
[]}
|
[]}
|
||||||
|
inputValue={prompt}
|
||||||
on:select={(e) => {
|
on:select={(e) => {
|
||||||
selectSuggestionPrompt(e.detail);
|
selectSuggestionPrompt(e.detail);
|
||||||
}}
|
}}
|
||||||
|
@ -1,53 +1,104 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Fuse from 'fuse.js';
|
||||||
import Bolt from '$lib/components/icons/Bolt.svelte';
|
import Bolt from '$lib/components/icons/Bolt.svelte';
|
||||||
import { onMount, getContext, createEventDispatcher } from 'svelte';
|
import { getContext, createEventDispatcher, onMount } from 'svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let suggestionPrompts = [];
|
export let suggestionPrompts = [];
|
||||||
export let className = '';
|
export let className = '';
|
||||||
|
export let inputValue = '';
|
||||||
|
|
||||||
let prompts = [];
|
let sortedPrompts = [];
|
||||||
|
onMount(() => {
|
||||||
|
sortedPrompts = [...(suggestionPrompts ?? [])].sort(() => Math.random() - 0.5);
|
||||||
|
});
|
||||||
|
|
||||||
$: prompts = (suggestionPrompts ?? [])
|
const fuseOptions = {
|
||||||
.reduce((acc, current) => [...acc, ...[current]], [])
|
keys: ['content', 'title'],
|
||||||
.sort(() => Math.random() - 0.5);
|
threshold: 0.5
|
||||||
|
};
|
||||||
|
|
||||||
|
let fuse;
|
||||||
|
let filteredPrompts = [];
|
||||||
|
|
||||||
|
// Track the number of updates to filteredPrompts
|
||||||
|
let version = 0;
|
||||||
|
|
||||||
|
// Fuzzy search
|
||||||
|
$: fuse = new Fuse(sortedPrompts, fuseOptions);
|
||||||
|
// Update filteredPrompts + version whenever inputValue changes
|
||||||
|
$: {
|
||||||
|
filteredPrompts = inputValue.trim()
|
||||||
|
? fuse.search(inputValue.trim()).map((result) => result.item)
|
||||||
|
: sortedPrompts;
|
||||||
|
version = version + 1;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if prompts.length > 0}
|
<div
|
||||||
<div class="mb-1 flex gap-1 text-sm font-medium items-center text-gray-400 dark:text-gray-600">
|
class="mb-1 flex gap-1 text-sm font-medium items-center text-gray-400 dark:text-gray-600"
|
||||||
<Bolt />
|
style="visibility: {filteredPrompts.length > 0 ? 'visible' : 'hidden'}"
|
||||||
{$i18n.t('Suggested')}
|
>
|
||||||
</div>
|
<Bolt />
|
||||||
{/if}
|
{$i18n.t('Suggested')}
|
||||||
|
|
||||||
<div class=" h-40 max-h-full overflow-auto scrollbar-none {className}">
|
|
||||||
{#each prompts as prompt, promptIdx}
|
|
||||||
<button
|
|
||||||
class="flex flex-col flex-1 shrink-0 w-full justify-between px-3 py-2 rounded-xl bg-transparent hover:bg-black/5 dark:hover:bg-white/5 transition group"
|
|
||||||
on:click={() => {
|
|
||||||
dispatch('select', prompt.content);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="flex flex-col text-left">
|
|
||||||
{#if prompt.title && prompt.title[0] !== ''}
|
|
||||||
<div
|
|
||||||
class=" font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
|
|
||||||
>
|
|
||||||
{prompt.title[0]}
|
|
||||||
</div>
|
|
||||||
<div class="text-xs text-gray-500 font-normal line-clamp-1">{prompt.title[1]}</div>
|
|
||||||
{:else}
|
|
||||||
<div
|
|
||||||
class=" font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
|
|
||||||
>
|
|
||||||
{prompt.content}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-xs text-gray-500 font-normal line-clamp-1">Prompt</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="h-40 overflow-auto scrollbar-none {className}">
|
||||||
|
{#if filteredPrompts.length > 0}
|
||||||
|
{#each filteredPrompts as prompt, idx ((prompt.id || prompt.content) + version)}
|
||||||
|
<button
|
||||||
|
class="waterfall-anim flex flex-col flex-1 shrink-0 w-full justify-between
|
||||||
|
px-3 py-2 rounded-xl bg-transparent hover:bg-black/5
|
||||||
|
dark:hover:bg-white/5 transition group"
|
||||||
|
style="animation-delay: {idx * 60}ms"
|
||||||
|
on:click={() => dispatch('select', prompt.content)}
|
||||||
|
>
|
||||||
|
<div class="flex flex-col text-left">
|
||||||
|
{#if prompt.title && prompt.title[0] !== ''}
|
||||||
|
<div
|
||||||
|
class="font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
|
||||||
|
>
|
||||||
|
{prompt.title[0]}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 font-normal line-clamp-1">
|
||||||
|
{prompt.title[1]}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div
|
||||||
|
class="font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
|
||||||
|
>
|
||||||
|
{prompt.content}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 font-normal line-clamp-1">Prompt</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<!-- No suggestions -->
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Waterfall animation for the suggestions */
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.waterfall-anim {
|
||||||
|
opacity: 0;
|
||||||
|
animation-name: fadeInUp;
|
||||||
|
animation-duration: 200ms;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-timing-function: ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user