|
@@ -1,4 +1,5 @@
|
|
|
<script lang="ts">
|
|
|
+ import Fuse from 'fuse.js';
|
|
|
import Bolt from '$lib/components/icons/Bolt.svelte';
|
|
|
import { onMount, getContext, createEventDispatcher } from 'svelte';
|
|
|
|
|
@@ -7,47 +8,119 @@
|
|
|
|
|
|
export let suggestionPrompts = [];
|
|
|
export let className = '';
|
|
|
+ export let inputValue = '';
|
|
|
|
|
|
- let prompts = [];
|
|
|
+ let sortedPrompts = [];
|
|
|
+ onMount(() => {
|
|
|
+ sortedPrompts = [...(suggestionPrompts ?? [])].sort(() => Math.random() - 0.5);
|
|
|
+ });
|
|
|
|
|
|
- $: prompts = (suggestionPrompts ?? [])
|
|
|
- .reduce((acc, current) => [...acc, ...[current]], [])
|
|
|
- .sort(() => Math.random() - 0.5);
|
|
|
+ const fuseOptions = {
|
|
|
+ keys: ['content', 'title'],
|
|
|
+ threshold: 0.5
|
|
|
+ };
|
|
|
+
|
|
|
+ let fuse;
|
|
|
+ let filteredPrompts = [];
|
|
|
+ let oldFilteredPrompts = [];
|
|
|
+
|
|
|
+ // This variable controls the re-rendering of the suggestions
|
|
|
+ let version = 0;
|
|
|
+
|
|
|
+ // Initialize Fuse
|
|
|
+ $: fuse = new Fuse(sortedPrompts, fuseOptions);
|
|
|
+
|
|
|
+ // Update the filteredPrompts if inputValue changes
|
|
|
+ // Only increase version if something wirklich geändert hat
|
|
|
+ $: {
|
|
|
+ const newFilteredPrompts = inputValue.trim()
|
|
|
+ ? fuse.search(inputValue.trim()).map((result) => result.item)
|
|
|
+ : sortedPrompts;
|
|
|
+
|
|
|
+ // 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;
|
|
|
+ }
|
|
|
</script>
|
|
|
|
|
|
-{#if prompts.length > 0}
|
|
|
- <div class="mb-1 flex gap-1 text-sm font-medium items-center text-gray-400 dark:text-gray-600">
|
|
|
- <Bolt />
|
|
|
- {$i18n.t('Suggested')}
|
|
|
- </div>
|
|
|
-{/if}
|
|
|
-
|
|
|
-<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
|
|
|
+ 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>
|
|
|
+
|
|
|
+<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">{i18n.t('Prompt')}</div>
|
|
|
+ {/if}
|
|
|
+ </div>
|
|
|
+ </button>
|
|
|
+ {/each}
|
|
|
+ {:else}
|
|
|
+ <!-- Keine Vorschläge -->
|
|
|
+ {/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>
|