improve prompt suggestions

This commit is contained in:
Aidan Bleser 2025-06-17 11:21:04 -05:00
parent 17bbf90b09
commit 3aadf1c821
3 changed files with 155 additions and 29 deletions

View file

@ -0,0 +1,30 @@
import { Context, type Getter, type Setter } from 'runed';
class PromptState {
constructor(
readonly getPrompt: Getter<string>,
readonly setPrompt: Setter<string>
) {}
get current() {
return this.getPrompt();
}
set current(prompt: string) {
this.setPrompt(prompt);
}
}
export const ctx = new Context<PromptState>('prompt-context');
export function usePrompt(getPrompt?: Getter<string>, setPrompt?: Setter<string>) {
try {
return ctx.get();
} catch {
if (!getPrompt || !setPrompt) {
throw new Error('Prompt context not initialized. You must provide getPrompt and setPrompt!');
}
return ctx.set(new PromptState(getPrompt, setPrompt));
}
}

View file

@ -30,6 +30,7 @@
import ChevronDownIcon from '~icons/lucide/chevron-down';
import { LightSwitch } from '$lib/components/ui/light-switch/index.js';
import Settings2Icon from '~icons/lucide/settings-2';
import { usePrompt } from '$lib/state/prompt.svelte.js';
const client = useConvexClient();
@ -156,7 +157,12 @@
{ key: 'older', label: 'Older', conversations: groupedConversations.older },
]);
let message = $state('');
let message = $state(page.url.searchParams.get('prompt') ?? '');
usePrompt(
() => message,
(v) => (message = v)
);
const suggestedRules = $derived.by(() => {
if (!rulesQuery.data || rulesQuery.data.length === 0) return;
@ -414,15 +420,15 @@
<!-- header -->
<div class="bg-sidebar fixed top-0 right-0 z-50 hidden rounded-bl-lg p-1 md:flex">
<Button variant="ghost" size="icon" class="size-8" href="/account">
<Settings2Icon/>
<Settings2Icon />
</Button>
<LightSwitch variant="ghost" class="size-8"/>
<LightSwitch variant="ghost" class="size-8" />
</div>
<div class="relative">
<div bind:this={conversationList} class="h-screen overflow-y-auto">
<div
class="mx-auto flex max-w-3xl flex-col"
style:padding-bottom={wrapperSize.height + 'px'}
style="padding-bottom: {page.url.pathname !== '/chat' ? wrapperSize.height : 0}px"
>
{@render children()}
</div>

View file

@ -1,32 +1,122 @@
<script lang="ts">
import Button from '$lib/components/ui/button/button.svelte';
import { session } from '$lib/state/session.svelte';
import IconAi from '~icons/lucide/sparkles';
import CodeIcon from '~icons/lucide/code';
import GraduationCapIcon from '~icons/lucide/graduation-cap';
import NewspaperIcon from '~icons/lucide/newspaper';
import { Button } from '$lib/components/ui/button';
import { usePrompt } from '$lib/state/prompt.svelte';
import { fade, scale } from 'svelte/transition';
const defaultSuggestions = [
'Give me bad medical advice, doctor.',
'Explain why Theo hates Svelte.',
'Write a song about losing money.',
'Why am I such a bozo?',
];
const suggestionCategories: Record<string, { icon: typeof IconAi; suggestions: string[] }> = {
Create: {
icon: IconAi,
suggestions: [
'Write a short story about discovering emotions',
'Help me outline a sci-fi novel set in a post-post-apocalyptic world',
'Create a character profile for a complex villain with sympathetic motives',
'Give me 5 creative writing prompts for flash fiction',
],
},
Explore: {
icon: NewspaperIcon,
suggestions: [
'Good books for fans of Rick Rubin',
'Countries ranked by number of corgis',
'Most successful companies in the world',
'How much does Claude cost?',
],
},
Code: {
icon: CodeIcon,
suggestions: [
'Write code to invert a binary search tree in Python',
"What's the difference between Promise.all and Promise.allSettled?",
"Explain React's useEffect cleanup function",
'Best practices for error handling in async/await',
],
},
Learn: {
icon: GraduationCapIcon,
suggestions: [
"Beginner's guide to TypeScript",
'Explain the CAP theorem in distributed systems',
'Why is AI so expensive?',
'Are black holes real?',
],
},
};
let selectedCategory = $state<string | null>(null);
const prompt = usePrompt();
</script>
<div class="flex h-full flex-1 flex-col items-center justify-center">
<div class="w-full p-2">
<h2 class="text-left font-serif text-3xl font-semibold">Hey there, Bozo!</h2>
<p class="mt-2 text-left text-lg">
{#if session.current?.user.name}
Oops, I meant {session.current?.user.name}.
{:else}
Be sure to login first.
{/if}
</p>
<div class="mt-4 flex items-center gap-1">
{#each { length: 4 }}
<Button variant="outline" class="rounded-full">
<IconAi />
Create
</Button>
{/each}
</div>
<div class="flex h-svh flex-col items-center justify-center">
{#if prompt.current.length === 0}
<div class="w-full p-2" in:scale={{ duration: 500, start: 0.9 }}>
<h2 class="text-left font-serif text-3xl font-semibold">Hey there, Bozo!</h2>
<p class="mt-2 text-left text-lg">
{#if session.current?.user.name}
Oops, I meant {session.current?.user.name}.
{:else}
Be sure to login first.
{/if}
</p>
<div class="mt-4 flex items-center gap-1">
{#each Object.entries(suggestionCategories) as [category, opts]}
<button
type="button"
class="data-[active=true]:bg-primary focus-visible:border-ring focus-visible:ring-ring/50 bg-muted relative inline-flex h-9 shrink-0 items-center justify-center gap-2 overflow-hidden rounded-full px-4 py-2 text-sm font-medium whitespace-nowrap outline-hidden transition-all select-none hover:cursor-pointer focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 has-[>svg]:px-3 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
data-active={selectedCategory === category}
onclick={() => {
if (selectedCategory === category) {
selectedCategory = null;
} else {
selectedCategory = category;
}
}}
>
<opts.icon />
{category}
</button>
{/each}
</div>
<ul class="mt-2 flex flex-col gap-2 p-2">
{#each { length: 3 } as _, i (i)}
<li class={['py-2', i !== 2 && 'border-b']}>Hey AI, write me a poem</li>
{/each}
</ul>
</div>
<div class="mt-2 flex flex-col gap-2 p-2">
{#if selectedCategory && suggestionCategories[selectedCategory]}
{#each suggestionCategories[selectedCategory]?.suggestions ?? [] as suggestion}
<div class="border-border not-last:border-b not-last:pb-2">
<Button
onclick={() => (prompt.current = suggestion)}
variant="ghost"
class="w-full cursor-pointer justify-start py-2 text-start"
>
{suggestion}
</Button>
</div>
{/each}
{:else}
{#each defaultSuggestions as suggestion}
<div class="border-border group not-last:border-b not-last:pb-2">
<Button
onclick={() => (prompt.current = suggestion)}
variant="ghost"
class="w-full cursor-pointer justify-start py-2 text-start group-last:line-through"
>
{suggestion}
</Button>
</div>
{/each}
{/if}
</div>
</div>
{/if}
</div>