@assistant-ui/mcp-docs-server 0.1.33 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/.docs/organized/code-examples/waterfall.md +7 -7
  2. package/.docs/organized/code-examples/with-a2a.md +8 -8
  3. package/.docs/organized/code-examples/with-ag-ui.md +12 -12
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +10 -10
  5. package/.docs/organized/code-examples/with-artifacts.md +40 -34
  6. package/.docs/organized/code-examples/with-assistant-transport.md +11 -11
  7. package/.docs/organized/code-examples/with-browser-extension.md +9 -9
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +72 -51
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +10 -10
  10. package/.docs/organized/code-examples/with-cloud.md +10 -10
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +10 -10
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +12 -12
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +12 -12
  14. package/.docs/organized/code-examples/with-expo.md +66 -31
  15. package/.docs/organized/code-examples/with-external-store.md +8 -8
  16. package/.docs/organized/code-examples/with-ffmpeg.md +13 -13
  17. package/.docs/organized/code-examples/with-generative-ui.md +98 -368
  18. package/.docs/organized/code-examples/with-google-adk.md +9 -9
  19. package/.docs/organized/code-examples/with-heat-graph.md +7 -7
  20. package/.docs/organized/code-examples/with-image-generation.md +10 -10
  21. package/.docs/organized/code-examples/with-interactables.md +10 -10
  22. package/.docs/organized/code-examples/with-langchain.md +10 -10
  23. package/.docs/organized/code-examples/with-langgraph.md +33 -29
  24. package/.docs/organized/code-examples/with-livekit.md +12 -12
  25. package/.docs/organized/code-examples/with-mcp.md +11 -11
  26. package/.docs/organized/code-examples/with-opencode.md +109 -583
  27. package/.docs/organized/code-examples/with-pi.md +2044 -0
  28. package/.docs/organized/code-examples/with-react-hook-form.md +11 -11
  29. package/.docs/organized/code-examples/with-react-ink-web.md +691 -0
  30. package/.docs/organized/code-examples/with-react-ink.md +309 -102
  31. package/.docs/organized/code-examples/with-react-router.md +14 -14
  32. package/.docs/organized/code-examples/with-resumable-stream.md +11 -11
  33. package/.docs/organized/code-examples/with-store.md +70 -66
  34. package/.docs/organized/code-examples/with-tanstack.md +25 -11
  35. package/.docs/organized/code-examples/with-tap-runtime.md +8 -8
  36. package/.docs/organized/code-examples/with-virtualized-thread.md +656 -0
  37. package/.docs/raw/docs/(docs)/architecture.mdx +65 -53
  38. package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +1 -1
  39. package/.docs/raw/docs/(reference)/api-reference/external-store/runtime.mdx +3 -0
  40. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +44 -1
  41. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +14 -3
  42. package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -1
  43. package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +2 -23
  44. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +22 -0
  45. package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +93 -9
  46. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +21 -86
  47. package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
  48. package/.docs/raw/docs/guides/index.mdx +3 -0
  49. package/.docs/raw/docs/guides/input-history.mdx +55 -0
  50. package/.docs/raw/docs/guides/virtualization.mdx +63 -0
  51. package/.docs/raw/docs/ink/adapters.mdx +23 -1
  52. package/.docs/raw/docs/ink/hooks.mdx +22 -19
  53. package/.docs/raw/docs/migrations/toolkit-tools.mdx +14 -8
  54. package/.docs/raw/docs/react-native/hooks.mdx +26 -18
  55. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +39 -0
  56. package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +3 -3
  57. package/.docs/raw/docs/runtimes/concepts/architecture.mdx +46 -4
  58. package/.docs/raw/docs/runtimes/concepts/stability.mdx +1 -1
  59. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +54 -12
  60. package/.docs/raw/docs/runtimes/custom/data-stream.mdx +1 -1
  61. package/.docs/raw/docs/runtimes/custom/external-store.mdx +62 -4
  62. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +120 -4
  63. package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +9 -9
  64. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +13 -0
  65. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +5 -5
  66. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +3 -3
  67. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +6 -6
  68. package/.docs/raw/docs/tools/backend.mdx +19 -11
  69. package/.docs/raw/docs/tools/defining-tools.mdx +177 -52
  70. package/.docs/raw/docs/tools/index.mdx +7 -12
  71. package/.docs/raw/docs/tools/mcp.mdx +83 -15
  72. package/.docs/raw/docs/tools/multi-agent.mdx +5 -5
  73. package/.docs/raw/docs/tools/tool-ui.mdx +64 -28
  74. package/.docs/raw/docs/tools/user-managed-mcp.mdx +4 -4
  75. package/.docs/raw/docs/ui/dot-matrix.mdx +133 -0
  76. package/.docs/raw/docs/ui/mermaid.mdx +16 -9
  77. package/.docs/raw/docs/ui/model-selector.mdx +219 -52
  78. package/.docs/raw/docs/ui/number-roll.mdx +154 -0
  79. package/.docs/raw/docs/ui/part-grouping.mdx +2 -2
  80. package/.docs/raw/docs/ui/reasoning.mdx +3 -3
  81. package/.docs/raw/docs/ui/streamdown.mdx +2 -0
  82. package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -1
  83. package/.docs/raw/docs/ui/thread.mdx +52 -0
  84. package/.docs/raw/docs/ui/tool-fallback.mdx +16 -0
  85. package/.docs/raw/docs/utilities/react-o11y.mdx +2 -2
  86. package/dist/constants.js.map +1 -1
  87. package/dist/index.js.map +1 -1
  88. package/dist/prepare-docs/code-examples.js.map +1 -1
  89. package/dist/prepare-docs/copy-raw.js.map +1 -1
  90. package/dist/prepare-docs/prepare.js.map +1 -1
  91. package/dist/stdio.js.map +1 -1
  92. package/dist/tools/docs.js.map +1 -1
  93. package/dist/tools/examples.js.map +1 -1
  94. package/dist/tools/tests/test-setup.js.map +1 -1
  95. package/dist/utils/mdx.js.map +1 -1
  96. package/dist/utils/paths.js.map +1 -1
  97. package/package.json +4 -4
  98. /package/.docs/raw/docs/{(docs)/copilots → copilots}/assistant-frame.mdx +0 -0
  99. /package/.docs/raw/docs/{(docs)/copilots → copilots}/make-assistant-visible.mdx +0 -0
  100. /package/.docs/raw/docs/{(docs)/copilots → copilots}/model-context.mdx +0 -0
  101. /package/.docs/raw/docs/{(docs)/copilots → copilots}/motivation.mdx +0 -0
  102. /package/.docs/raw/docs/{(docs)/copilots → copilots}/use-assistant-instructions.mdx +0 -0
@@ -0,0 +1,691 @@
1
+ # Example: with-react-ink-web
2
+
3
+ ## app/globals.css
4
+
5
+ ```css
6
+ * {
7
+ box-sizing: border-box;
8
+ }
9
+
10
+ html,
11
+ body {
12
+ height: 100%;
13
+ margin: 0;
14
+ background: #1a1a1a;
15
+ }
16
+
17
+ .terminal-center {
18
+ display: flex;
19
+ height: 100vh;
20
+ align-items: center;
21
+ justify-content: center;
22
+ }
23
+
24
+ .terminal-host {
25
+ width: 100%;
26
+ max-width: 960px;
27
+ height: min(100%, 560px);
28
+ }
29
+
30
+ .terminal-host .wterm {
31
+ height: 100%;
32
+ border-radius: 0;
33
+ box-shadow: none;
34
+ background: #1a1a1a;
35
+ --term-bg: #1a1a1a;
36
+ --term-fg: #e0e0e0;
37
+ --term-cursor: #34d399;
38
+ --term-font-family: Menlo, Monaco, "Courier New", monospace;
39
+ --term-font-size: 13px;
40
+ --term-line-height: 1.3;
41
+ }
42
+
43
+ .terminal-loading {
44
+ display: flex;
45
+ height: 100vh;
46
+ align-items: center;
47
+ justify-content: center;
48
+ font-family: ui-monospace, Menlo, Monaco, monospace;
49
+ font-size: 14px;
50
+ color: #6b7280;
51
+ }
52
+
53
+ ```
54
+
55
+ ## app/ink-app.tsx
56
+
57
+ ```tsx
58
+ "use client";
59
+
60
+ // Mirrors examples/with-react-ink/src/app.tsx; keep in sync.
61
+ import { useMemo } from "react";
62
+ import { Box, Text } from "ink";
63
+ import {
64
+ AssistantRuntimeProvider,
65
+ StatusBarPrimitive,
66
+ useAui,
67
+ } from "@assistant-ui/react-ink";
68
+ import {
69
+ useChatRuntime,
70
+ AssistantChatTransport,
71
+ } from "@assistant-ui/react-ai-sdk";
72
+ import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";
73
+ import { Thread } from "./thread";
74
+
75
+ const CHAT_API =
76
+ process.env.NEXT_PUBLIC_CHAT_ENDPOINT_URL ??
77
+ (process.env.NODE_ENV === "development"
78
+ ? "http://localhost:3000/api/chat"
79
+ : "https://www.assistant-ui.com/api/chat");
80
+
81
+ const MODEL_NAME = "assistant-ui";
82
+
83
+ export const InkApp = () => {
84
+ const transport = useMemo(
85
+ () => new AssistantChatTransport({ api: CHAT_API }),
86
+ [],
87
+ );
88
+ const runtime = useChatRuntime({
89
+ transport,
90
+ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
91
+ });
92
+ const aui = useAui();
93
+
94
+ return (
95
+ <AssistantRuntimeProvider aui={aui} runtime={runtime}>
96
+ <Box flexDirection="column" padding={1}>
97
+ <Box>
98
+ <Text bold color="cyan">
99
+ {MODEL_NAME}
100
+ </Text>
101
+ <Text dimColor>{" ~/assistant-ui"}</Text>
102
+ </Box>
103
+ <StatusBarPrimitive.Root>
104
+ <Text dimColor>
105
+ model: <StatusBarPrimitive.ModelName name={MODEL_NAME} /> ·{" "}
106
+ <StatusBarPrimitive.MessageCount /> · <StatusBarPrimitive.Status />
107
+ </Text>
108
+ </StatusBarPrimitive.Root>
109
+ <Box marginTop={1}>
110
+ <Thread />
111
+ </Box>
112
+ </Box>
113
+ </AssistantRuntimeProvider>
114
+ );
115
+ };
116
+
117
+ ```
118
+
119
+ ## app/ink-streams.ts
120
+
121
+ ```typescript
122
+ type Listener = (...args: unknown[]) => void;
123
+
124
+ class MiniEmitter {
125
+ private listeners = new Map<string, Set<Listener>>();
126
+
127
+ on(event: string, fn: Listener) {
128
+ let set = this.listeners.get(event);
129
+ if (!set) {
130
+ set = new Set();
131
+ this.listeners.set(event, set);
132
+ }
133
+ set.add(fn);
134
+ return this;
135
+ }
136
+
137
+ off(event: string, fn: Listener) {
138
+ this.listeners.get(event)?.delete(fn);
139
+ return this;
140
+ }
141
+
142
+ addListener(event: string, fn: Listener) {
143
+ return this.on(event, fn);
144
+ }
145
+
146
+ removeListener(event: string, fn: Listener) {
147
+ return this.off(event, fn);
148
+ }
149
+
150
+ emit(event: string, ...args: unknown[]) {
151
+ const set = this.listeners.get(event);
152
+ if (!set) return false;
153
+ for (const fn of [...set]) fn(...args);
154
+ return set.size > 0;
155
+ }
156
+ }
157
+
158
+ export type InkStreams = {
159
+ stdout: NodeJS.WriteStream;
160
+ stdin: NodeJS.ReadStream;
161
+ pushInput: (data: string) => void;
162
+ setSize: (cols: number, rows: number) => void;
163
+ };
164
+
165
+ // Fake streams implementing the subset of the Node stream API that ink 6
166
+ // touches (see ink's Ink/App classes). The writer receives ink's ANSI output
167
+ // with bare \n rewritten to \r\n (wterm does not emulate ONLCR). Ink's
168
+ // clear-terminal sequence passes through: its 3J resets wterm's scrollback on
169
+ // every overflow repaint, leaving only the final frame's overflow scrollable.
170
+ export function createInkStreams(
171
+ writeToTerminal: (data: string) => void,
172
+ ): InkStreams {
173
+ const stdoutBase = new MiniEmitter() as MiniEmitter & Record<string, unknown>;
174
+ Object.assign(stdoutBase, {
175
+ columns: 80,
176
+ rows: 24,
177
+ isTTY: true,
178
+ writable: true,
179
+ setDefaultEncoding: () => stdoutBase,
180
+ cork: () => {},
181
+ uncork: () => {},
182
+ write: (chunk: unknown, encoding?: unknown, cb?: () => void) => {
183
+ const str = typeof chunk === "string" ? chunk : String(chunk);
184
+ if (str.length > 0) {
185
+ writeToTerminal(str.replace(/\r?\n/g, "\r\n"));
186
+ }
187
+ if (typeof encoding === "function") (encoding as () => void)();
188
+ else cb?.();
189
+ return true;
190
+ },
191
+ });
192
+
193
+ const inputBuffer: string[] = [];
194
+ const stdinBase = new MiniEmitter() as MiniEmitter & Record<string, unknown>;
195
+ Object.assign(stdinBase, {
196
+ columns: 80,
197
+ rows: 24,
198
+ isTTY: true,
199
+ readable: true,
200
+ setEncoding: () => stdinBase,
201
+ setRawMode: () => stdinBase,
202
+ resume: () => stdinBase,
203
+ pause: () => stdinBase,
204
+ ref: () => stdinBase,
205
+ unref: () => stdinBase,
206
+ read: () => (inputBuffer.length > 0 ? inputBuffer.shift()! : null),
207
+ });
208
+
209
+ return {
210
+ stdout: stdoutBase as unknown as NodeJS.WriteStream,
211
+ stdin: stdinBase as unknown as NodeJS.ReadStream,
212
+ pushInput: (data: string) => {
213
+ inputBuffer.push(data);
214
+ stdinBase.emit("readable");
215
+ },
216
+ setSize: (cols: number, rows: number) => {
217
+ const changed =
218
+ stdoutBase["columns"] !== cols || stdoutBase["rows"] !== rows;
219
+ stdoutBase["columns"] = cols;
220
+ stdoutBase["rows"] = rows;
221
+ stdinBase["columns"] = cols;
222
+ stdinBase["rows"] = rows;
223
+ if (changed) stdoutBase.emit("resize");
224
+ },
225
+ };
226
+ }
227
+
228
+ ```
229
+
230
+ ## app/layout.tsx
231
+
232
+ ```tsx
233
+ import type { Metadata } from "next";
234
+ import type { ReactNode } from "react";
235
+ import "./globals.css";
236
+
237
+ export const metadata: Metadata = {
238
+ title: "assistant-ui ink demo",
239
+ description: "The with-react-ink example running in the browser via ink-web.",
240
+ };
241
+
242
+ export default function RootLayout({ children }: { children: ReactNode }) {
243
+ return (
244
+ <html lang="en">
245
+ <body>{children}</body>
246
+ </html>
247
+ );
248
+ }
249
+
250
+ ```
251
+
252
+ ## app/page.tsx
253
+
254
+ ```tsx
255
+ "use client";
256
+
257
+ import dynamic from "next/dynamic";
258
+
259
+ const Terminal = dynamic(() => import("./terminal"), {
260
+ ssr: false,
261
+ loading: () => <div className="terminal-loading">starting terminal...</div>,
262
+ });
263
+
264
+ export default function Home() {
265
+ return <Terminal />;
266
+ }
267
+
268
+ ```
269
+
270
+ ## app/terminal.tsx
271
+
272
+ ```tsx
273
+ "use client";
274
+
275
+ import { useCallback, useEffect, useRef } from "react";
276
+ import { Terminal, useTerminal, type WTerm } from "@wterm/react";
277
+ import { render, type Instance } from "ink";
278
+ import { waitForYogaInit } from "ink-web";
279
+ import { createInkStreams, type InkStreams } from "./ink-streams";
280
+ import { InkApp } from "./ink-app";
281
+ import "@wterm/dom/src/terminal.css";
282
+
283
+ const inkApp = <InkApp />;
284
+
285
+ export default function InkTerminal() {
286
+ const { ref, write } = useTerminal();
287
+ const wrapperRef = useRef<HTMLDivElement>(null);
288
+ const streamsRef = useRef<InkStreams | null>(null);
289
+ const instanceRef = useRef<Instance | null>(null);
290
+ const startedRef = useRef(false);
291
+ const disposedRef = useRef(false);
292
+ const colsRef = useRef(0);
293
+ const scrollPendingRef = useRef(false);
294
+
295
+ const writeRef = useRef(write);
296
+ writeRef.current = write;
297
+
298
+ // wterm does not auto-scroll on new output (vercel-labs/wterm#61); follow
299
+ // the tail after its coalesced write -> setTimeout(0) -> rAF render pass.
300
+ // Skipped while the visitor has scrolled up to read scrollback.
301
+ const scheduleScroll = useCallback(() => {
302
+ if (scrollPendingRef.current) return;
303
+ const el = wrapperRef.current?.querySelector(".wterm");
304
+ if (el && el.scrollHeight - el.scrollTop - el.clientHeight > 100) return;
305
+ scrollPendingRef.current = true;
306
+ setTimeout(() => {
307
+ requestAnimationFrame(() => {
308
+ scrollPendingRef.current = false;
309
+ const target = wrapperRef.current?.querySelector(".wterm");
310
+ if (target) target.scrollTop = target.scrollHeight;
311
+ });
312
+ }, 0);
313
+ }, []);
314
+
315
+ const getStreams = useCallback(() => {
316
+ streamsRef.current ??= createInkStreams((data) => {
317
+ writeRef.current(data);
318
+ scheduleScroll();
319
+ });
320
+ return streamsRef.current;
321
+ }, [scheduleScroll]);
322
+
323
+ const handleReady = useCallback(
324
+ (wt: WTerm) => {
325
+ const streams = getStreams();
326
+ colsRef.current = wt.cols;
327
+ streams.setSize(wt.cols, wt.rows);
328
+ // ink components draw their own cursor; the bundled ink's cli-cursor
329
+ // shim is a no-op, so hide wterm's grid cursor here.
330
+ wt.write("\x1b[?25l");
331
+
332
+ if (!startedRef.current) {
333
+ startedRef.current = true;
334
+ void waitForYogaInit().then(() => {
335
+ if (disposedRef.current) return;
336
+ instanceRef.current = render(inkApp, {
337
+ stdout: streams.stdout,
338
+ stderr: streams.stdout,
339
+ stdin: streams.stdin,
340
+ patchConsole: false,
341
+ exitOnCtrlC: false,
342
+ });
343
+ });
344
+ } else if (instanceRef.current) {
345
+ instanceRef.current.clear();
346
+ instanceRef.current.rerender(inkApp);
347
+ }
348
+ },
349
+ [getStreams],
350
+ );
351
+
352
+ // wterm encodes the Backspace key as \x7f, which the bundled ink 6 parses
353
+ // as key.delete (forward delete); rewrite to \x08, which it parses as
354
+ // backspace. \x03 (Ctrl+C) is dropped so it stays free for copy.
355
+ const handleData = useCallback(
356
+ (data: string) => {
357
+ const mapped = data.replaceAll("\x7f", "\x08").replaceAll("\x03", "");
358
+ if (mapped.length === 0) return;
359
+ getStreams().pushInput(mapped);
360
+ },
361
+ [getStreams],
362
+ );
363
+
364
+ const handleResize = useCallback(
365
+ (cols: number, rows: number) => {
366
+ if (cols < colsRef.current && instanceRef.current) {
367
+ instanceRef.current.clear();
368
+ writeRef.current("\x1b[2J\x1b[3J\x1b[H");
369
+ }
370
+ colsRef.current = cols;
371
+ getStreams().setSize(cols, rows);
372
+ },
373
+ [getStreams],
374
+ );
375
+
376
+ useEffect(() => {
377
+ disposedRef.current = false;
378
+ return () => {
379
+ disposedRef.current = true;
380
+ instanceRef.current?.unmount();
381
+ instanceRef.current = null;
382
+ startedRef.current = false;
383
+ };
384
+ }, []);
385
+
386
+ return (
387
+ <div className="terminal-center">
388
+ <div ref={wrapperRef} className="terminal-host">
389
+ <Terminal
390
+ ref={ref}
391
+ autoResize
392
+ onReady={handleReady}
393
+ onData={handleData}
394
+ onResize={handleResize}
395
+ />
396
+ </div>
397
+ </div>
398
+ );
399
+ }
400
+
401
+ ```
402
+
403
+ ## app/thread.tsx
404
+
405
+ ```tsx
406
+ // Mirrors examples/with-react-ink/src/components/thread.tsx; keep in sync.
407
+ import { Box, Text, useStdout } from "ink";
408
+ import {
409
+ ThreadPrimitive,
410
+ ComposerPrimitive,
411
+ MessagePrimitive,
412
+ ErrorPrimitive,
413
+ LoadingPrimitive,
414
+ LiveChecklist,
415
+ } from "@assistant-ui/react-ink";
416
+ import { MarkdownText } from "@assistant-ui/react-ink-markdown";
417
+
418
+ // markdansi defaults width and color from process.stdout, which does not
419
+ // exist in the browser bundle; pass both explicitly so it never reads it.
420
+ // color is forwarded to markdansi but not yet declared on MarkdownTextProps.
421
+ const BrowserMarkdownText = ({ text }: { text: string }) => {
422
+ const { stdout } = useStdout();
423
+ return (
424
+ <MarkdownText
425
+ text={text}
426
+ width={(stdout?.columns ?? 80) - 4}
427
+ hyperlinks={false}
428
+ {...{ color: true }}
429
+ />
430
+ );
431
+ };
432
+
433
+ const UserMessage = () => (
434
+ <MessagePrimitive.Root>
435
+ <Box marginBottom={1}>
436
+ <Text bold color="green">
437
+ You:{" "}
438
+ </Text>
439
+ <MessagePrimitive.Content
440
+ renderText={({ part }) => <Text wrap="wrap">{part.text}</Text>}
441
+ />
442
+ </Box>
443
+ </MessagePrimitive.Root>
444
+ );
445
+
446
+ const AssistantMessage = () => (
447
+ <MessagePrimitive.Root>
448
+ <Box flexDirection="column" marginBottom={1}>
449
+ <Text bold color="blue">
450
+ AI:
451
+ </Text>
452
+ <MessagePrimitive.Content
453
+ renderText={({ part }) => <BrowserMarkdownText text={part.text} />}
454
+ renderReasoning={({ part }) => (
455
+ <Text dimColor italic>
456
+ {part.text}
457
+ </Text>
458
+ )}
459
+ />
460
+ <LiveChecklist title="Plan" showProgress marginTop={1} />
461
+ <ErrorPrimitive.Root>
462
+ <ErrorPrimitive.Message />
463
+ </ErrorPrimitive.Root>
464
+ </Box>
465
+ </MessagePrimitive.Root>
466
+ );
467
+
468
+ const Loading = () => (
469
+ <LoadingPrimitive.Root marginBottom={1}>
470
+ <LoadingPrimitive.Spinner />
471
+ <Text> </Text>
472
+ <LoadingPrimitive.Text>Working</LoadingPrimitive.Text>
473
+ <Text> </Text>
474
+ <LoadingPrimitive.ElapsedTime />
475
+ </LoadingPrimitive.Root>
476
+ );
477
+
478
+ export const Thread = () => {
479
+ return (
480
+ <ThreadPrimitive.Root>
481
+ <ThreadPrimitive.Empty>
482
+ <Box flexDirection="column" marginBottom={1}>
483
+ <Text>
484
+ A real LLM streaming into a real Ink render loop in your browser.
485
+ </Text>
486
+ <Text dimColor>
487
+ {' try: "what is assistant-ui?" or "how do I render markdown?"'}
488
+ </Text>
489
+ </Box>
490
+ </ThreadPrimitive.Empty>
491
+
492
+ <ThreadPrimitive.Messages>
493
+ {({ message }) =>
494
+ message.role === "user" ? <UserMessage /> : <AssistantMessage />
495
+ }
496
+ </ThreadPrimitive.Messages>
497
+
498
+ <Loading />
499
+
500
+ <Box borderStyle="round" borderColor="gray" paddingX={1}>
501
+ <Text color="gray">{"> "}</Text>
502
+ <ComposerPrimitive.Input
503
+ submitOnEnter
504
+ multiLine
505
+ placeholder="Type a message... (Enter to send)"
506
+ autoFocus
507
+ />
508
+ </Box>
509
+ </ThreadPrimitive.Root>
510
+ );
511
+ };
512
+
513
+ ```
514
+
515
+ ## next.config.ts
516
+
517
+ ```typescript
518
+ import path from "node:path";
519
+ import type { NextConfig } from "next";
520
+
521
+ const config: NextConfig = {
522
+ output: "export",
523
+ transpilePackages: ["ink-web"],
524
+ turbopack: {
525
+ // Pin the workspace root; vercel build's environment makes Turbopack's
526
+ // lockfile-based inference resolve to the app directory instead of the
527
+ // monorepo root, which breaks resolution of pnpm-symlinked packages.
528
+ root: path.join(__dirname, "../.."),
529
+ resolveAlias: {
530
+ ink: "ink-web",
531
+ "supports-hyperlinks": {
532
+ browser: "./shims/supports-hyperlinks.ts",
533
+ },
534
+ },
535
+ },
536
+ };
537
+
538
+ export default config;
539
+
540
+ ```
541
+
542
+ ## package.json
543
+
544
+ ```json
545
+ {
546
+ "name": "with-react-ink-web",
547
+ "version": "0.0.0",
548
+ "private": true,
549
+ "scripts": {
550
+ "dev": "next dev -p 8081",
551
+ "build": "next build",
552
+ "start": "next start"
553
+ },
554
+ "dependencies": {
555
+ "@assistant-ui/react-ai-sdk": "workspace:*",
556
+ "@assistant-ui/react-ink": "workspace:*",
557
+ "@assistant-ui/react-ink-markdown": "workspace:*",
558
+ "@wterm/dom": "0.3.0",
559
+ "@wterm/react": "0.3.0",
560
+ "ai": "^6.0.204",
561
+ "ink": "^6.8.0",
562
+ "ink-web": "^0.2.0",
563
+ "next": "^16.2.9",
564
+ "react": "^19.2.7",
565
+ "react-dom": "^19.2.7",
566
+ "react-reconciler": "^0.33.0",
567
+ "scheduler": "^0.27.0"
568
+ },
569
+ "devDependencies": {
570
+ "@types/node": "^25.9.3",
571
+ "@types/react": "^19.2.17",
572
+ "typescript": "^6.0.3"
573
+ }
574
+ }
575
+
576
+ ```
577
+
578
+ ## README.md
579
+
580
+ ```markdown
581
+ # Welcome to the assistant-ui ink web demo 👋
582
+
583
+ This app runs the [`@assistant-ui/react-ink`](https://www.assistant-ui.com/docs/ink) terminal chat in the browser against a real LLM. It powers the live demo embedded at [assistant-ui.com/ink](https://www.assistant-ui.com/ink) and is deployed at [assistant-ui-ink.vercel.app](https://assistant-ui-ink.vercel.app).
584
+
585
+ ## Get started
586
+
587
+ 1. Install dependencies
588
+
589
+ ```bash
590
+ pnpm install
591
+ ```
592
+
593
+ 2. Start the docs app, which serves the chat endpoint on [localhost:3000](http://localhost:3000)
594
+
595
+ ```bash
596
+ pnpm --filter @assistant-ui/docs dev
597
+ ```
598
+
599
+ 3. Start the app
600
+
601
+ ```bash
602
+ pnpm dev
603
+ ```
604
+
605
+ The terminal opens on [localhost:8081](http://localhost:8081), and the docs app embeds this URL on its `/ink` page in development.
606
+
607
+ ## How it works
608
+
609
+ Ink has no official web target, so this app renders the real `@assistant-ui/react-ink` component tree through [ink-web](https://github.com/cjroth/ink-web)'s browser build of Ink, bridged to a [wterm](https://github.com/vercel-labs/wterm) terminal. wterm renders to the DOM, so text selection, copy, find, and screen readers work natively.
610
+
611
+ Chat requests go to the assistant-ui docs site's `/api/chat`, the same pattern as the React Native demo at [assistant-ui-expo.vercel.app](https://assistant-ui-expo.vercel.app). This app holds no API keys; set `NEXT_PUBLIC_CHAT_ENDPOINT_URL` to point at a different endpoint.
612
+
613
+ ## Learn more
614
+
615
+ - [assistant-ui ink documentation](https://www.assistant-ui.com/docs/ink): primitives, hooks, and adapters for terminal UIs.
616
+ - [with-react-ink](../with-react-ink): the real terminal version of this example. Start there if you are building an Ink app.
617
+
618
+ ```
619
+
620
+ ## shims/supports-hyperlinks.ts
621
+
622
+ ```typescript
623
+ const supportsHyperlinks = {
624
+ stdout: false,
625
+ stderr: false,
626
+ };
627
+
628
+ export default supportsHyperlinks;
629
+
630
+ ```
631
+
632
+ ## tsconfig.json
633
+
634
+ ```json
635
+ {
636
+ "compilerOptions": {
637
+ "target": "ES2022",
638
+ "lib": ["dom", "dom.iterable", "esnext"],
639
+ "allowJs": false,
640
+ "skipLibCheck": true,
641
+ "strict": true,
642
+ "noEmit": true,
643
+ "esModuleInterop": true,
644
+ "module": "esnext",
645
+ "moduleResolution": "bundler",
646
+ "resolveJsonModule": true,
647
+ "isolatedModules": true,
648
+ "jsx": "react-jsx",
649
+ "incremental": true,
650
+ "plugins": [
651
+ {
652
+ "name": "next"
653
+ }
654
+ ]
655
+ },
656
+ "include": [
657
+ "next-env.d.ts",
658
+ "**/*.ts",
659
+ "**/*.tsx",
660
+ ".next/types/**/*.ts",
661
+ ".next/dev/types/**/*.ts"
662
+ ],
663
+ "exclude": ["node_modules"]
664
+ }
665
+
666
+ ```
667
+
668
+ ## vercel.json
669
+
670
+ ```json
671
+ {
672
+ "$schema": "https://openapi.vercel.sh/vercel.json",
673
+ "ignoreCommand": "bash ../../scripts/vercel-ignore-changeset-release.sh",
674
+ "buildCommand": "cd ../.. && pnpm turbo build --filter=@assistant-ui/react-ink --filter=@assistant-ui/react-ink-markdown --filter=@assistant-ui/react-ai-sdk && cd examples/with-react-ink-web && pnpm run build",
675
+ "outputDirectory": "out",
676
+ "framework": null,
677
+ "headers": [
678
+ {
679
+ "source": "/(.*)",
680
+ "headers": [
681
+ {
682
+ "key": "Content-Security-Policy",
683
+ "value": "frame-ancestors 'self' https://*.assistant-ui.com https://*.vercel.app http://localhost:*;"
684
+ }
685
+ ]
686
+ }
687
+ ]
688
+ }
689
+
690
+ ```
691
+