147 lines
5.2 KiB
Svelte
147 lines
5.2 KiB
Svelte
<script lang="ts">
|
|
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 { scale } from 'svelte/transition';
|
|
import { useCachedQuery } from '$lib/cache/cached-query.svelte';
|
|
import { api } from '$lib/backend/convex/_generated/api';
|
|
import { Provider } from '$lib/types';
|
|
|
|
const defaultSuggestions = [
|
|
'Give me bad medical advice, doctor.',
|
|
'Explain why Theo hates Svelte.',
|
|
'Write a song about losing money.',
|
|
'When are you going to take my job?',
|
|
];
|
|
|
|
const settings = useCachedQuery(api.user_settings.get, {
|
|
session_token: session.current?.session.token ?? '',
|
|
});
|
|
|
|
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 userKeysQuery = useCachedQuery(api.user_keys.all, {
|
|
session_token: session.current?.session.token ?? '',
|
|
});
|
|
|
|
const prompt = usePrompt();
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>New Chat | thom.chat</title>
|
|
</svelte:head>
|
|
|
|
<div class="flex h-svh flex-col items-center justify-center">
|
|
{#if prompt.current.length === 0 && userKeysQuery.data && Object.values(userKeysQuery.data).some(key => key !== null)}
|
|
<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 <span class={{ 'blur-sm': settings.data?.privacy_mode }}
|
|
>{session.current?.user.name ? ` ${session.current?.user.name}` : ''}</span
|
|
>!
|
|
</h2>
|
|
<div class="mt-4 flex flex-wrap items-center gap-1">
|
|
{#each Object.entries(suggestionCategories) as [category, opts] (category)}
|
|
<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>
|
|
|
|
<div class="mt-2 flex flex-col gap-2 p-2">
|
|
{#if selectedCategory && suggestionCategories[selectedCategory]}
|
|
{#each suggestionCategories[selectedCategory]?.suggestions ?? [] as suggestion (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 px-2 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 px-2 py-2 text-start"
|
|
>
|
|
{suggestion}
|
|
</Button>
|
|
</div>
|
|
{/each}
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{:else if userKeysQuery.data && !Object.values(userKeysQuery.data).some(key => key !== null) && !userKeysQuery.isLoading}
|
|
<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, <span class={{ 'blur-sm': settings.data?.privacy_mode }}
|
|
>{session.current?.user.name}</span
|
|
>!
|
|
</h2>
|
|
<p class="mt-2 text-left text-lg">
|
|
You can send some free messages, or provide a key for limitless access.
|
|
<br />
|
|
<a href="/account/api-keys" class="text-primary"> Go to settings </a>
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
</div>
|