Docs
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
Neon
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
Neon
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
Persistence (Advanced)
Embeddings
Reranking
Evaluate
Sandboxes (Advanced)
Class References
Function References
Interface References
Type Alias References
Variable References
UI

Octane Chat UI

Install @tanstack/ai-octane. Import the UI factory from @tanstack/ai-octane/ui. Call createChatHook({ options, ...components }) once at module scope. Your app calls useAppChat() to create the instance. Render <chat.AppChat />.

The factory API matches React. Widgets are Octane components. Import hooks from octane.

The factory needs a toolsComponents entry for every tool name in chatOptions. It also needs an interruptsComponents.generic entry for every interrupt id. generic.fallback is optional.

The server route matches the React page. The chat UI recipes show the same option groups. The code there is React, and the shape carries over.

Client

tsx
import { fetchServerSentEvents } from '@tanstack/ai-octane'
import { createChatHook } from '@tanstack/ai-octane/ui'
import { toolDefinition } from '@tanstack/ai'
import { z } from 'zod'

const getWeather = toolDefinition({
  name: 'getWeather',
  description: 'Look up weather',
  inputSchema: z.object({ city: z.string() }),
  outputSchema: z.object({ temperature: z.number() }),
}).client()

const chatOptions = {
  connection: fetchServerSentEvents('/api/chat'),
  tools: [getWeather],
}

const { useAppChat } = createChatHook({
  options: chatOptions,
  components: {
    layout: (props) => (
      <main>
        <props.Messages />
        <props.Interrupts />
        <props.Queue />
      </main>
    ),
    message: (props) => (
      <article>
        <props.Parts />
      </article>
    ),
  },
  partsComponents: { fallback: (props) => <span>{props.part.type}</span> },
  toolsComponents: {
    getWeather: (props) => <strong>{props.part.input?.city}</strong>,
  },
})

export function ChatScreen() {
  const chat = useAppChat()
  return <chat.AppChat />
}
import { fetchServerSentEvents } from '@tanstack/ai-octane'
import { createChatHook } from '@tanstack/ai-octane/ui'
import { toolDefinition } from '@tanstack/ai'
import { z } from 'zod'

const getWeather = toolDefinition({
  name: 'getWeather',
  description: 'Look up weather',
  inputSchema: z.object({ city: z.string() }),
  outputSchema: z.object({ temperature: z.number() }),
}).client()

const chatOptions = {
  connection: fetchServerSentEvents('/api/chat'),
  tools: [getWeather],
}

const { useAppChat } = createChatHook({
  options: chatOptions,
  components: {
    layout: (props) => (
      <main>
        <props.Messages />
        <props.Interrupts />
        <props.Queue />
      </main>
    ),
    message: (props) => (
      <article>
        <props.Parts />
      </article>
    ),
  },
  partsComponents: { fallback: (props) => <span>{props.part.type}</span> },
  toolsComponents: {
    getWeather: (props) => <strong>{props.part.input?.city}</strong>,
  },
})

export function ChatScreen() {
  const chat = useAppChat()
  return <chat.AppChat />
}