Blocks
AI chat
A complete chat with an AI model: replies stream in, you can stop them, copy them, and start over.
Examples
Simple
This demo answers from a small script so it runs without a key; your app streams real replies from Claude. Scroll up while it writes and it leaves you there, with a button back down.
Ask the librarian
How can I help you today?
<script lang="ts">
import Chat from '#lib/blocks/chat/Chat.svelte';
// In your app: import { fetchServerSentEvents } from '@tanstack/ai-svelte';
// and pass connection={fetchServerSentEvents('/api/chat')}.
import { demoConnection } from './demo-connection';
</script>
<Chat
connection={demoConnection}
title="Ask the librarian"
suggestions={[
'What was the House of Wisdom?',
'How did Ibn al-Haytham explain sight?',
'Where does the word algebra come from?'
]}
/>Assistant with history
Conversations are kept in this browser and listed by day; reload and they're still here. Attach images, audio, video or PDFs with the paperclip, by dropping them on the chat, or by pasting.
How can I help you today?
<script lang="ts">
import Assistant from '#lib/blocks/chat/Assistant.svelte';
// In your app: connection={fetchServerSentEvents('/api/chat')}.
import { demoConnection } from './demo-connection';
</script>
<Assistant
connection={demoConnection}
suggestions={[
'What was the House of Wisdom?',
'How did Ibn al-Haytham explain sight?',
'Where does the word algebra come from?'
]}
/>Terminal agent
An agent in a terminal: it thinks, calls tools and shows what each returned, then answers. Try “How did Ibn al-Haytham explain sight?”, then /help. Escape interrupts a run; Up brings back what you sent.
✻ Welcome to Librarian
/help for commands · cwd: ~/house-of-wisdom
<script lang="ts">
import Terminal from '#lib/blocks/chat/Terminal.svelte';
// In your app: connection={fetchServerSentEvents('/api/agent')}, an endpoint with tools.
import { demoAgent } from './demo-agent';
</script>
<Terminal connection={demoAgent} />Installation
npx sinaui add blocks/chatCopy src/lib/blocks/chat/, and the Button, Icon and announce.ts it uses. Then install TanStack AI with its Anthropic adapter:
pnpm add @tanstack/ai@0.63.0 @tanstack/ai-svelte@0.24.3 @tanstack/ai-anthropic@0.19.3These are early versions, so they're pinned exactly: a later one may change its API. Upgrade on purpose, one at a time.
The endpoint
Add a route that streams replies from Claude:
// src/routes/api/chat/+server.ts — streams Claude's replies to the chat block.
import { chat, toServerSentEventsResponse } from '@tanstack/ai';
import { createAnthropicChat } from '@tanstack/ai-anthropic';
import { error } from '@sveltejs/kit';
import { ANTHROPIC_API_KEY } from '$app/env/private';
export async function POST({ request }) {
const body = await request.json().catch(() => undefined);
if (!Array.isArray(body?.messages)) error(400, 'Expected { messages: [...] }');
const abortController = new AbortController();
// Stop, or leaving the page, stops the model too.
request.signal.addEventListener('abort', () => abortController.abort());
const stream = chat({
adapter: createAnthropicChat('claude-sonnet-5', ANTHROPIC_API_KEY),
messages: body.messages,
systemPrompts: ['You are a helpful librarian. Answer briefly and plainly.'],
modelOptions: { max_tokens: 1024 },
abortController
});
return toServerSentEventsResponse(stream, { abortController });
}
Your API key
Declare the key, so the app refuses to start without it rather than showing a chat that never
answers. Set ANTHROPIC_API_KEY in your environment.
// src/env.ts — the app refuses to start without the key: a loud failure beats a chat that never answers.
import { defineEnvVars } from '@sveltejs/kit/env';
export const variables = defineEnvVars({
ANTHROPIC_API_KEY: { description: 'Anthropic API key, used by /api/chat' }
});
Then give the block its connection: connection={fetchServerSentEvents('/api/chat')}.
Props
| Prop | Type | Default |
|---|---|---|
connection | TanStack AI connection (required) | |
title | string | 'Ask the librarian' |
suggestions | string[]: questions offered when empty | |
placeholder | string | 'Ask anything…' |
Keyboard
| Key | Does |
|---|---|
| Enter | Send |
| Shift + Enter | A new line |
Accessibility
- The conversation is a log: each reply is read once it's complete, never word by word as it streams.
- Stop, Copy, Retry and New chat are named buttons; Send says so, and is unavailable until there's something to send.
- A failed reply is announced, in plain words, with a way to try again.
- With reduced motion, messages appear without rising and the typing dots slow down.