浏览代码

feat: smart suggestions

Jannik Streidl 2 月之前
父节点
当前提交
43a490457d
共有 2 个文件被更改,包括 92 次插入40 次删除
  1. 1 0
      src/lib/components/chat/Placeholder.svelte
  2. 91 40
      src/lib/components/chat/Suggestions.svelte

+ 1 - 0
src/lib/components/chat/Placeholder.svelte

@@ -220,6 +220,7 @@
 				suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
 					$config?.default_prompt_suggestions ??
 					[]}
+				inputValue={prompt}
 				on:select={(e) => {
 					selectSuggestionPrompt(e.detail);
 				}}

+ 91 - 40
src/lib/components/chat/Suggestions.svelte

@@ -1,53 +1,104 @@
 <script lang="ts">
+	import Fuse from 'fuse.js';
 	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 dispatch = createEventDispatcher();
 
 	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 = [];
+
+	// 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>
 
-{#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">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>