Showcase

Markdown chat

Rich Markdown rendering in assistant responses.

markdownchat
Live preview
Source
'use client'import { useMemo } from 'react'import { useChat, ChatContainer, InputBar } from '@agentskit/react'import '@/styles/agentskit-theme.css'import { createMockAdapter, initialAssistant } from './_shared/mock-adapter'import { MdRenderer } from './_shared/md-renderer'const TURNS = [  {    text: `## Styling options in AgentsKitYou can pick from three styling modes:1. **Headless** — use the hooks and style with your design system2. **Default theme** — \`import '@/styles/agentskit-theme.css'\` for the tokenised CSS3. **Custom CSS** — target \`data-ak-*\` attributes directly| Mode | Lines | Output || --- | --- | --- || headless | ~20 | fully custom || theme | 1 | branded defaults || data-ak | a few | anywhere in between |> The \`Markdown\` component ships with support for GFM tables, task lists, and inline code.`,  },  {    text: `### Inline formatting- **Bold** and *italic* text- \`inline code\` with proper spacing- [Links](https://agentskit.io) that keep their styling- ~~strikethrough~~ when you need it\`\`\`tsconst muted = 'var(--color-fd-muted-foreground)'\`\`\``,  },]export function MarkdownChat() {  const adapter = useMemo(() => createMockAdapter(TURNS, 130), [])  const chat = useChat({    adapter,    initialMessages: [      initialAssistant(        'Ask me for a styled response — I stream full GitHub-flavoured Markdown.',      ),    ],  })  return (    <div      data-ak-example      className="flex h-[520px] flex-col overflow-hidden rounded-lg border border-ak-border bg-ak-surface"    >      <ChatContainer className="flex-1 space-y-3 p-4">        {chat.messages.map((m) => (          <div key={m.id} data-ak-message data-ak-role={m.role} className="rounded-lg bg-ak-midnight/40 p-3">            <MdRenderer content={m.content} />          </div>        ))}      </ChatContainer>      <InputBar chat={chat} />    </div>  )}
import type { AdapterFactory, StreamChunk, ToolDefinition } from '@agentskit/core'export type ToolCallEmit = {  name: string  args?: Record<string, unknown>  result?: unknown  durationMs?: number}export type Turn = {  /** Text streamed as the assistant reply. */  text: string  /** Optional tool calls to emit before the text. The runtime executes each via   *  the matching tool stub exposed by `toolsFor(turns)`. */  toolCalls?: ToolCallEmit[]  /** Optional reasoning stream emitted before tool calls / text. */  reasoning?: string}export function createMockAdapter(turns: Turn[], cps = 80): AdapterFactory {  let idx = 0  return {    createSource: () => ({      stream: async function* (): AsyncIterableIterator<StreamChunk> {        const turn = turns[idx % turns.length]        idx += 1        if (turn.reasoning) {          for (const ch of turn.reasoning) {            await sleep(1000 / cps)            yield { type: 'reasoning', content: ch }          }        }        if (turn.toolCalls) {          for (const call of turn.toolCalls) {            yield {              type: 'tool_call',              toolCall: {                id: `call-${Math.random().toString(36).slice(2, 8)}`,                name: call.name,                args: JSON.stringify(call.args ?? {}),              },            }          }        }        for (const ch of turn.text) {          await sleep(1000 / cps)          yield { type: 'text', content: ch }        }        yield { type: 'done' }      },      abort() {},    }),    capabilities: { streaming: true, tools: true },  }}/** * Build a registry of tool stubs whose `execute()` resolves to the mocked * result declared for that tool name in `turns`. When the controller sees a * `tool_call` chunk from the mock adapter it looks up the name here, runs the * stub (with a simulated latency), and emits `tool_result`. */export function toolsFor(turns: Turn[]): ToolDefinition[] {  const byName = new Map<string, ToolCallEmit>()  for (const t of turns) {    for (const c of t.toolCalls ?? []) {      if (!byName.has(c.name)) byName.set(c.name, c)    }  }  return Array.from(byName.values()).map<ToolDefinition>((call) => ({    name: call.name,    description: `Mock ${call.name}`,    schema: {},    async execute() {      if (call.durationMs) await sleep(call.durationMs)      return JSON.stringify(call.result ?? { ok: true })    },  }))}function sleep(ms: number) {  return new Promise<void>((r) => setTimeout(r, ms))}export function initialAssistant(content: string) {  return {    id: 'init',    role: 'assistant' as const,    content,    status: 'complete' as const,    createdAt: new Date(),  }}
'use client'import ReactMarkdown from 'react-markdown'import remarkGfm from 'remark-gfm'import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock'/** * Markdown renderer used across showcase examples. * react-markdown + remark-gfm handles tables, task lists, strikethrough, etc. * Fenced code blocks are rendered by fumadocs' DynamicCodeBlock for shiki * syntax highlighting consistent with the rest of the docs. */export function MdRenderer({ content }: { content: string }) {  return (    <div data-ak-md className="ak-md space-y-2 text-sm text-ak-foam">      <ReactMarkdown        remarkPlugins={[remarkGfm]}        components={{          code({ className, children }) {            const text = String(children ?? '').replace(/\n$/, '')            const match = /language-(\w+)/.exec(className ?? '')            // react-markdown v9 dropped the `inline` flag. Treat anything            // without a `language-*` class (and no newline) as inline.            if (!match && !text.includes('\n')) {              return (                <code className="rounded bg-ak-midnight px-1 py-0.5 font-mono text-[0.85em] text-ak-blue">                  {text}                </code>              )            }            return <DynamicCodeBlock lang={match?.[1] ?? 'text'} code={text} />          },          pre({ children }) {            // Block <code> is rendered directly by the `code` component above.            // Returning the children raw avoids nesting <div> (DynamicCodeBlock)            // inside <pre> or the surrounding <p>.            return <>{children}</>          },          a({ href, children }) {            return (              <a href={href} target="_blank" rel="noreferrer" className="text-ak-blue underline">                {children}              </a>            )          },          table({ children }) {            return (              <div className="my-2 overflow-x-auto">                <table className="w-full border-collapse text-left text-xs">{children}</table>              </div>            )          },          thead({ children }) {            return <thead className="bg-ak-midnight/60 text-ak-foam">{children}</thead>          },          th({ children }) {            return <th className="border-b border-ak-border px-3 py-2 font-semibold">{children}</th>          },          td({ children }) {            return <td className="border-b border-ak-border px-3 py-2 align-top">{children}</td>          },          blockquote({ children }) {            return (              <blockquote className="border-l-2 border-ak-border pl-3 text-ak-graphite">                {children}              </blockquote>            )          },          ul({ children }) {            return <ul className="my-1 list-disc space-y-1 pl-5">{children}</ul>          },          ol({ children }) {            return <ol className="my-1 list-decimal space-y-1 pl-5">{children}</ol>          },          h1({ children }) {            return <h2 className="mt-2 font-display text-lg font-semibold text-ak-foam">{children}</h2>          },          h2({ children }) {            return <h3 className="mt-2 font-display text-base font-semibold text-ak-foam">{children}</h3>          },          h3({ children }) {            return <h4 className="mt-1 font-display text-sm font-semibold text-ak-foam">{children}</h4>          },          p({ children }) {            // Render every paragraph as a div to avoid invalid nesting when            // fenced code blocks (rendered as <figure>/<div> via DynamicCodeBlock)            // land inside a <p> because of react-markdown's default wrapping.            return <div className="leading-relaxed">{children}</div>          },        }}      >        {content}      </ReactMarkdown>    </div>  )}
More examples
See all →