new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

shadcn/ui · all subjects

blocks/helpers

95 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

User message files example

chat.user("Describe this image.", { files: [ { mediaType: "image/png", url: "https://example.com/screenshot.png", }, ], })

TanStack AI helper purpose and capabilities

@shadcn/helpers/tanstack-ai lets you write an AI conversation in code and stream it through TanStack AI's useChat with no model, API route, network request, or API key. It creates native TanStack UIMessage[] values and replays each assistant response through a local connection adapter as real AG-UI events, so components behave exactly as they would in production: text and reasoning stream word by word, and tool calls move from input to result.

TanStack AI helper use cases

The helper decouples chat UI from the model and backend for frontend-only work. It enables: building components against realistic streaming output without wiring up a model first; creating reproducible previews, screenshots, and videos that never depend on a live model; powering documentation examples with conversations that render the same way on every load; and writing deterministic tests in CI with no network calls, token spend, or flaky model output.

Basic createChat usage example

import { createChat } from "@shadcn/helpers/tanstack-ai" const chat = createChat() .user("What changed in this release?") .assistant("The release adds keyboard shortcuts and faster search.") .user("Can you show me the shortcuts?") .assistant("Press ⌘K to search and ⌘Enter to submit.")

useChat integration with TanStack AI helper

Pass chat.get(0) as initialMessages to start with no messages, and chat.transport() as the connection. Use chat.next(messages) to find the next predefined user message. Call append(nextMessage) to send the next predefined user message; the connection then streams its assistant response as AG-UI events.

useChat integration code example

import { useChat } from "@tanstack/ai-react" function Chat() { const { messages, append } = useChat({ initialMessages: chat.get(0), connection: chat.transport(), }) const nextMessage = chat.next(messages) return ( <button disabled={!nextMessage} onClick={() => { if (nextMessage) { void append(nextMessage) } }} > Send next message </button> ) }

User message creation

Use user() to add a user message. User messages support id and metadata with createdAt timestamp as the second argument. User messages can include files via the files option.

User message example

const chat = createChat().user("What changed in this release?") const [message] = chat.get() message.role // "user" message.parts // [{ type: "text", content: "What changed in this release?" }]

User message with metadata and ID

chat.user("What changed in this release?", { id: "user-release-question", metadata: { createdAt: "2026-01-01T10:00:00.000Z", }, })

Assistant message creation

Use assistant() to add an assistant message. A string creates one text part. You can also pass an array of TanStack message parts or use a writer callback for messages that should stream in smaller steps.

Assistant message string example

const chat = createChat().assistant( "The release adds keyboard shortcuts and faster search." ) const [message] = chat.get() message.role // "assistant" message.parts // [{ type: "text", content: "The release adds..." }]

Assistant message with parts array

chat.assistant([ { type: "thinking", content: "I should summarize the release." }, { type: "text", content: "The release adds keyboard shortcuts." }, ])

Assistant message with writer callback

chat.assistant(({ writer }) => { writer.reasoning("I should summarize the release.") writer.text("The release adds keyboard shortcuts and faster search.") })

Writer text method

Use writer.text() to add text that streams word by word. Consecutive text calls materialize as separate parts in get(), but TanStack's stream processor combines them into one text part during playback. Use mode: "instant" to send the whole value at once, or delayMs to change the delay between text deltas.

Writer text method example

chat.assistant(({ writer }) => { writer.text("The release adds keyboard shortcuts.") writer.text(" Search is faster too.") }) writer.text("Done.", { mode: "instant" }) writer.text("This part streams more slowly.", { delayMs: 100 })

Writer reasoning method

Use writer.reasoning() to add reasoning that becomes a TanStack thinking part. Reasoning uses the same delayMs and mode options as text.

Writer reasoning method example

chat.assistant(({ writer }) => { writer.reasoning("I should check the latest conditions first.") writer.text("Let me check the weather.") }) writer.reasoning("Checking the forecast.", { mode: "instant" })

Writer tool method

Use writer.tool() to add a tool call. It returns a handle that follows the tool from input to output. The tool can finish with output() or error(). The completed TanStack message contains a tool-call part and a sibling tool-result part.

Writer tool method example

chat.assistant(({ writer }) => { writer .tool("getWeather", { input: { city: "San Francisco" }, }) .sleep(900) .output({ city: "San Francisco", temperature: 18, condition: "Breezy", }) writer.text("It is 18°C and breezy in San Francisco.") }) writer.tool("getWeather", { input: { city: "San Francisco" } }).error()

Tool typing with clientTools

Pass the same client-tool tuple used by your app to type the tool name, input, and output.

Tool typing example

import { clientTools } from "@tanstack/ai-client" import { getWeatherTool } from "@/lib/tools" const tools = clientTools(getWeatherTool.client()) const chat = createChat<typeof tools>() chat.assistant(({ writer }) => { writer .tool("getWeather", { input: { city: "San Francisco" }, }) .output({ city: "San Francisco", temperature: 18, condition: "Breezy", }) })

User message files

Add files to a user message with the files option. Each file is an object with mediaType (required string), url (required string), type (optional string, defaults to "file"), filename (optional string), and providerMetadata (optional record).

user() method options

id (string) uses a specific message ID. metadata (TanStackMessageMetadata) sets the message's createdAt timestamp. files (FilePayload[]) appends media parts after the user text part.

File media type to TanStack part mapping

The helper converts each file into a TanStack media part based on its media type: image/* becomes image part, audio/* becomes audio part, video/* becomes video part, and other types become document part.

Message part support in TanStack AI adapter

The TanStack AI adapter exposes only operations it can represent end to end. Text streams as AG-UI text and becomes a text part. Reasoning streams as AG-UI reasoning and becomes a thinking part. Tool calls stream input, output, and errors into tool-call and tool-result parts. Data, structured output, custom events, sources, reasoning files, and step starts have no writer method or equivalent. Assistant files are available in get() but omitted from connection streams. Stop closes the stream; AG-UI has no separate abort event.

get() method

Use get() to return messages from the start of the conversation. get() returns cloned TanStack UIMessage[] values and does not change the chat. get() with no argument returns every message. get(count) returns the first count messages. get(0) returns an empty initial conversation.

next() method

Use next(messages) to find the next predefined user message after the messages already shown. next() always accepts a message transcript, not an index. It returns the next user message or null when none remain.

get() and next() example

chat.get() // Every message. chat.get(2) // The first two messages. chat.get(0) // An empty initial conversation. const initialMessages = chat.get(2) const nextMessage = chat.next(initialMessages)

Create chat from existing messages example

import { createChat } from "@shadcn/helpers/tanstack-ai" import type { UIMessage } from "@tanstack/ai-client" declare const savedMessages: UIMessage[] const chat = createChat({ messages: savedMessages }) .user("What should we do next?") .assistant("Turn the open questions into a checklist.")

transport() method creates ConnectConnectionAdapter

transport() creates a TanStack ConnectConnectionAdapter that you pass to useChat as its connection. When append() or sendMessage() starts a run, the connection finds the assistant message that follows the current transcript and emits AG-UI events through TanStack's normal stream processor. It matches message IDs first and falls back to the role and text of the latest message.

transport() usage example

const connection = chat.transport() const { messages, append, sendMessage } = useChat({ initialMessages: chat.get(0), connection, }) const nextMessage = chat.next(messages) if (nextMessage) { void append(nextMessage) } void sendMessage("Tell me more.")

append() vs sendMessage() in transport

Use append(nextMessage) to replay a predefined user message; it preserves that message's ID and media parts. Use sendMessage() for ordinary user input; it accepts a string or multimodal content rather than a complete UIMessage.

Transport AG-UI event sequence

A response can emit the following sequence: RUN_STARTED, REASONING_MESSAGE_START → REASONING_MESSAGE_CONTENT → REASONING_MESSAGE_END, TOOL_CALL_START → TOOL_CALL_ARGS → TOOL_CALL_END → TOOL_CALL_RESULT, TEXT_MESSAGE_START → TEXT_MESSAGE_CONTENT → TEXT_MESSAGE_END, RUN_FINISHED. Predefined errors emit RUN_ERROR. TanStack supplies the threadId and runId used by each run.

transport() delayMs option

const connection = chat.transport({ delayMs: 25, })

transport() fallback option

Use fallback when the conversation has no predefined assistant response left. This keeps a demo usable after its predefined replies are exhausted. A fallback can be a string, an array of TanStack message parts, or a writer callback that receives the incoming transcript and messages.

transport() fallback examples

const connection = chat.transport({ fallback: "This demo has no more predefined replies.", }) const connection = chat.transport({ fallback: ({ writer, messages }) => { writer.text(`This example already has ${messages.length} messages.`, { mode: "instant", }) }, })

Timing with chat.sleep() and writer.sleep()

chat.sleep(ms) waits before the next assistant response starts. writer.sleep(ms) waits between parts of an assistant response. tool.sleep(ms) waits between a tool's input and its result. transport({ delayMs }) sets the default delay between text and reasoning deltas. writer.text(text, { delayMs }) and writer.reasoning(text, { delayMs }) override that delay for one part. mode: "instant" sends a whole text or reasoning value in one delta.

Timing example

const chat = createChat() .user("Give me a project update.") .sleep(800) .assistant(({ writer }) => { writer.reasoning("I should lead with the completed milestone.") writer.sleep(500) writer.text("The first milestone is complete.", { mode: "instant" }) }) const connection = chat.transport({ delayMs: 50 })

Fast tests with transport

For fast tests, use chat.transport({ delayMs: 0 }) and instant text.

Error handling with chat.error()

Use error() on the chat when the whole assistant response should fail. This emits a TanStack RUN_ERROR event.

Chat error example

const chat = createChat() .user("Load the report.") .error("The report could not be loaded.")

Writer error example

chat.assistant(({ writer }) => { writer.text("I found the report.") writer.error("The connection closed before it could be read.") })

Message metadata and timestamps

TanStack messages support a createdAt timestamp. Pass it as message metadata; it is preserved through get() and next(). The AG-UI connection does not stream message metadata.

Message metadata example

const chat = createChat() .user("Hello", { metadata: { createdAt: "2026-01-01T10:00:00.000Z" }, }) .assistant("Hi.", { id: "assistant-welcome", metadata: { createdAt: new Date("2026-01-01T10:00:01.000Z") }, })

Custom message IDs and clock

Use chat options when a fixture needs custom prefixes or a fixed clock: messageIdPrefix (default "msg"), toolCallIdPrefix (default "call"), and now (default "2026-01-01T00:00:00.000Z").

createChat() options example

const chat = createChat({ messageIdPrefix: "demo-message", toolCallIdPrefix: "demo-tool", now: "2026-01-01T00:00:00.000Z", })

createChat() function signature

function createChat<TOOLS extends ReadonlyArray<AnyClientTool> = AnyClientTool[], DATA = unknown>(options?: CreateChatOptions<TOOLS, DATA>): TanStackChat<TOOLS, DATA>

createChat() type parameters

TOOLS is the client-tool tuple used to type tool names, inputs, and outputs. DATA is the payload type of structured-output parts in existing TanStack messages. The helper writer does not create structured-output parts; DATA preserves their type when a chat starts from existing messages.

createChat() options reference

messages (UIMessage<TOOLS, DATA>[], default []) starts from an existing transcript; messages are cloned. messageIdPrefix (string, default "msg") is prefix for generated message IDs. toolCallIdPrefix (string, default "call") is prefix for generated tool call IDs. now (Date | string, default "2026-01-01T00:00:00.000Z") is fixed time used for generated createdAt values. IDs found in messages are reserved, so newly generated IDs continue after the existing transcript.

TanStackChat methods

user(text?, options?) adds a user message with text and optional files, returns TanStackChat. assistant(input?, options?) adds an assistant message from text, parts, or a writer callback, returns TanStackChat. sleep(delayMs) waits before the next assistant response starts, returns TanStackChat. error(errorText?) adds an assistant response that emits RUN_ERROR, returns TanStackChat. get(count?) returns cloned messages from the start of the conversation as UIMessage[]. next(messages) returns the next predefined user message after a transcript as UIMessage or null. transport(options?) creates the connection used by useChat as ConnectConnectionAdapter.

TanStackChat default values

Calling user() or assistant() without content uses "Summarize the uploaded receipt.". Calling error() without a message uses "An error occurred.". get(count) throws when count is negative or not an integer.

FilePayload type

type FilePayload = { type?: "file" mediaType: string url: string filename?: string providerMetadata?: Record<string, unknown> } TanStack media parts preserve the URL and media type. Their message shape does not preserve filename or providerMetadata.

assistant() input variants

assistant() input can be: string (creates one text part that streams word by word), MessagePart[] (static TanStack parts in their existing order), or ({ writer }) => void (synchronous callback for scripting parts, tools, errors, and timing).

assistant() method options

id (string) uses a specific message ID. metadata (TanStackMessageMetadata) sets the message's createdAt timestamp.

Writer methods

text(text?, options?) adds streamed text. reasoning(text?, options?) adds streamed reasoning that becomes a thinking part. tool(name, options?) adds a typed tool call and returns its lifecycle handle. sleep(delayMs) pauses before the next writer event. error(errorText?) emits RUN_ERROR and ends without a RUN_FINISHED event.

Writer default values

Calling text() without content uses "Summarize the uploaded receipt.". Calling reasoning() without content uses "I need to inspect the available context before answering.". Calling error() without a message uses "An error occurred.".

Text and reasoning options

delayMs (number, default from connection) overrides the connection delay for this value. mode ("stream" | "instant", default "stream") streams word deltas or emits the whole value in one delta.

Tool options

toolCallId (string) uses a specific tool call ID. input (TOOLS[NAME]["input"]) sets the typed tool input. output (TOOLS[NAME]["output"]) immediately finishes with a typed output. errorText (string) immediately finishes with an error.

Exported types from TanStack AI helper

TanStackChat is the typed fluent chat returned by createChat(). CreateChatOptions are options accepted by createChat(). TanStackMessageMetadata is the supported { createdAt? } message metadata. TanStackToolHandle is the lifecycle handle returned by writer.tool(). TanStackWriter is the writer available inside assistant and fallback callbacks.

Full usage example with all features

"use client" import { createChat } from "@shadcn/helpers/tanstack-ai" import { useChat } from "@tanstack/ai-react" const chat = createChat() .user("What changed in this release?") .assistant("The release adds keyboard shortcuts and faster search.") .user("Can you show me the shortcuts?") .assistant("Press ⌘K to search and ⌘Enter to submit.") const initialMessages = chat.get(0) const connection = chat.transport() export function Chat() { const { messages, append, status } = useChat({ initialMessages, connection, }) const nextMessage = chat.next(messages) const isBusy = status === "submitted" || status === "streaming" return ( <div> {messages.map((message) => ( <div key={message.id}>{/* Render the message */}</div> ))} <button disabled={!nextMessage || isBusy} onClick={() => { if (nextMessage && !isBusy) { void append(nextMessage) } }} > Send </button> </div> ) }

Give your agent this brain