open-webui/src/lib/components/chat/Suggestions.svelte

127 lines
3.3 KiB
Svelte
Raw Normal View History

<script lang="ts">
2025-02-04 08:14:02 +00:00
import Fuse from 'fuse.js';
2024-05-02 05:55:42 +00:00
import Bolt from '$lib/components/icons/Bolt.svelte';
2025-02-04 08:17:55 +00:00
import { onMount, getContext, createEventDispatcher } from 'svelte';
2024-05-07 06:43:25 +00:00
const i18n = getContext('i18n');
2024-10-05 10:07:56 +00:00
const dispatch = createEventDispatcher();
2024-05-02 05:55:42 +00:00
export let suggestionPrompts = [];
2024-10-06 18:52:19 +00:00
export let className = '';
2025-02-04 08:14:02 +00:00
export let inputValue = '';
2024-02-08 02:51:45 +00:00
2025-02-04 08:14:02 +00:00
let sortedPrompts = [];
onMount(() => {
sortedPrompts = [...(suggestionPrompts ?? [])].sort(() => Math.random() - 0.5);
});
2024-02-08 02:51:45 +00:00
2025-02-04 08:14:02 +00:00
const fuseOptions = {
keys: ['content', 'title'],
threshold: 0.5
};
let fuse;
let filteredPrompts = [];
2025-02-04 08:36:11 +00:00
let oldFilteredPrompts = [];
2025-02-04 08:14:02 +00:00
2025-02-04 08:36:11 +00:00
// This variable controls the re-rendering of the suggestions
2025-02-04 08:14:02 +00:00
let version = 0;
2025-02-04 08:36:11 +00:00
// Initialize Fuse
2025-02-04 08:14:02 +00:00
$: fuse = new Fuse(sortedPrompts, fuseOptions);
2025-02-04 08:36:11 +00:00
// Update the filteredPrompts if inputValue changes
// Only increase version if something wirklich geändert hat
2025-02-04 08:14:02 +00:00
$: {
2025-02-04 08:36:11 +00:00
const newFilteredPrompts = inputValue.trim()
2025-02-04 08:14:02 +00:00
? fuse.search(inputValue.trim()).map((result) => result.item)
: sortedPrompts;
2025-02-04 08:36:11 +00:00
// Compare with the oldFilteredPrompts
// If there's a difference, update array + version
if (!arraysEqual(oldFilteredPrompts, newFilteredPrompts)) {
filteredPrompts = newFilteredPrompts;
version += 1;
}
oldFilteredPrompts = newFilteredPrompts;
}
// Helper function to check if arrays are the same
// (based on unique IDs oder content)
function arraysEqual(a, b) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if ((a[i].id ?? a[i].content) !== (b[i].id ?? b[i].content)) {
return false;
}
}
return true;
2025-02-04 08:14:02 +00:00
}
</script>
2025-02-04 08:14:02 +00:00
<div
class="mb-1 flex gap-1 text-sm font-medium items-center text-gray-400 dark:text-gray-600"
style="visibility: {filteredPrompts.length > 0 ? 'visible' : 'hidden'}"
>
<Bolt />
{$i18n.t('Suggested')}
</div>
2025-02-04 08:14:02 +00:00
<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>
2025-02-04 08:17:55 +00:00
<div class="text-xs text-gray-500 font-normal line-clamp-1">{i18n.t('Prompt')}</div>
2025-02-04 08:14:02 +00:00
{/if}
</div>
</button>
{/each}
{:else}
2025-02-04 08:36:11 +00:00
<!-- Keine Vorschläge -->
2025-02-04 08:14:02 +00:00
{/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>