ink

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

assistant-ui Ink

assistant-ui Ink

Always consult assistant-ui.com/llms.txt for the latest API.
@assistant-ui/react-ink
connects the assistant-ui runtime to Ink's terminal renderer. It shares runtime, tools, state, and AI SDK transport with web apps, but it has no DOM, CSS, or copied elements. Compose your screen from Ink's
Box
and
Text
plus the runtime aware primitives.
@assistant-ui/react-ink-markdown
renders assistant text as ANSI styled terminal markdown.
请始终参考assistant-ui.com/llms.txt获取最新API。
@assistant-ui/react-ink
将assistant-ui运行时与Ink的终端渲染器相连。它与Web应用共享运行时、工具、状态及AI SDK传输层,但不涉及DOM、CSS或复制元素。你可以使用Ink的
Box
和
Text
组件,结合感知运行时的primitives来构建终端界面。
@assistant-ui/react-ink-markdown
可将助手文本渲染为ANSI样式的终端Markdown。

References

参考文档

  • ./references/primitives.md -- every terminal primitive namespace, its parts, scoped contexts, and the controlled
    TextInput
  • ./references/hooks.md -- runtime, state, tool, notification, voice, and checklist hooks
  • ./references/adapters.md -- file storage, attachment, and title adapters
  • ./references/custom-backend.md -- local inference, local disk persistence, and backend thread ownership
  • ./references/migration.md -- what transfers from a web app and what must be rebuilt for Ink
  • ./references/primitives.md -- 所有终端primitive命名空间、组成部分、作用域上下文及受控
    TextInput
    组件
  • ./references/hooks.md -- 运行时、状态、工具、通知、语音及清单相关hooks
  • ./references/adapters.md -- 文件存储、附件及标题适配器
  • ./references/custom-backend.md -- 本地推理、本地磁盘持久化及后端会话所有权
  • ./references/migration.md -- 从Web应用迁移时可复用的内容及需为Ink重新构建的部分

Start a terminal app

启动终端应用

Scaffold the terminal example when starting fresh:
sh
npx assistant-ui@latest create --ink my-app
cd my-app
--ink
resolves to the
with-react-ink
CLI example. The example inventory also contains
with-react-ink-web
, but it is not the
--ink
scaffold target.
For an existing Node project, install the runtime, terminal renderer, and AI SDK transport together:
sh
npm install @assistant-ui/react-ink @assistant-ui/react-ink-markdown ink react @assistant-ui/ai-sdk
The API route must run in a separate backend project. Unlike a browser app, a terminal process cannot use a relative
/api/chat
URL. Give
AssistantChatTransport
a complete URL that the process can reach.
tsx
import { AssistantRuntimeProvider } from "@assistant-ui/react-ink";
import { AssistantChatTransport, useChatRuntime } from "@assistant-ui/ai-sdk";
import { Box } from "ink";
import { TerminalThread } from "./components/terminal-thread.js";

const CHAT_API_URL = "http://localhost:3000/api/chat";

export function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: CHAT_API_URL }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <Box flexDirection="column">
        <TerminalThread />
      </Box>
    </AssistantRuntimeProvider>
  );
}
useChatRuntime
and
AssistantChatTransport
come from
@assistant-ui/ai-sdk
. Its backend uses the AI SDK v7 UI message stream. See the runtime's AI SDK v7 guide for the route shape.
从零开始时,可通过脚手架搭建终端示例项目:
sh
npx assistant-ui@latest create --ink my-app
cd my-app
--ink
对应
with-react-ink
CLI示例。示例库中还包含
with-react-ink-web
,但它不是
--ink
脚手架的目标。
对于已有的Node项目,需同时安装运行时、终端渲染器及AI SDK传输层:
sh
npm install @assistant-ui/react-ink @assistant-ui/react-ink-markdown ink react @assistant-ui/ai-sdk
API路由需在独立的后端项目中运行。与浏览器应用不同,终端进程无法使用相对路径
/api/chat
。需为
AssistantChatTransport
提供一个进程可访问的完整URL。
tsx
import { AssistantRuntimeProvider } from "@assistant-ui/react-ink";
import { AssistantChatTransport, useChatRuntime } from "@assistant-ui/ai-sdk";
import { Box } from "ink";
import { TerminalThread } from "./components/terminal-thread.js";

const CHAT_API_URL = "http://localhost:3000/api/chat";

export function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: CHAT_API_URL }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <Box flexDirection="column">
        <TerminalThread />
      </Box>
    </AssistantRuntimeProvider>
  );
}
useChatRuntime
和
AssistantChatTransport
来自
@assistant-ui/ai-sdk
。其后端使用AI SDK v7 UI消息流。有关路由格式,请查看运行时的AI SDK v7指南。

Compose a terminal thread

组合终端会话

ThreadPrimitive.Messages
creates the current message scope before each child runs. Read
s.message
inside the message component, then use Ink primitives rather than web elements. Use
AuiIf
for runtime state gates and
LoadingPrimitive
for the active run.
tsx
import { Box, Text } from "ink";
import {
  AuiIf,
  ComposerPrimitive,
  LoadingPrimitive,
  ThreadPrimitive,
  useAuiState,
} from "@assistant-ui/react-ink";
import { MarkdownText } from "@assistant-ui/react-ink-markdown";

function Message() {
  const message = useAuiState((s) => s.message);
  const text = message.content
    .filter((part) => part.type === "text")
    .map((part) => ("text" in part ? part.text : ""))
    .join("");

  if (message.role === "user") {
    return <Text color="green">You: {text}</Text>;
  }

  return (
    <Box flexDirection="column" marginBottom={1}>
      <Text color="blue">Assistant:</Text>
      <MarkdownText text={text} />
    </Box>
  );
}

export function TerminalThread() {
  return (
    <ThreadPrimitive.Root flexDirection="column">
      <AuiIf condition={(s) => s.thread.isEmpty}>
        <Text dimColor>Send a message to begin.</Text>
      </AuiIf>
      <ThreadPrimitive.Messages>{() => <Message />}</ThreadPrimitive.Messages>
      <LoadingPrimitive.Root gap={1}>
        <LoadingPrimitive.Spinner variant="bar" />
        <LoadingPrimitive.Text />
        <LoadingPrimitive.ElapsedTime />
      </LoadingPrimitive.Root>
      <Box borderStyle="round" borderColor="gray" paddingX={1}>
        <Text color="gray">{"> "}</Text>
        <ComposerPrimitive.Input submitOnEnter placeholder="Message..." autoFocus />
      </Box>
    </ThreadPrimitive.Root>
  );
}
The simple message example deliberately renders only text parts. Use
MessagePrimitive.Parts
when you need tool calls, attachments, data, sources, or reasoning. Its default terminal safe renderers handle those part types, and primitives shows the component map.
ThreadPrimitive.Messages
会在每个子组件运行前创建当前消息作用域。在消息组件内部读取
s.message
,然后使用Ink的primitives而非Web元素。使用
AuiIf
处理运行时状态判断,使用
LoadingPrimitive
显示活跃运行状态。
tsx
import { Box, Text } from "ink";
import {
  AuiIf,
  ComposerPrimitive,
  LoadingPrimitive,
  ThreadPrimitive,
  useAuiState,
} from "@assistant-ui/react-ink";
import { MarkdownText } from "@assistant-ui/react-ink-markdown";

function Message() {
  const message = useAuiState((s) => s.message);
  const text = message.content
    .filter((part) => part.type === "text")
    .map((part) => ("text" in part ? part.text : ""))
    .join("");

  if (message.role === "user") {
    return <Text color="green">You: {text}</Text>;
  }

  return (
    <Box flexDirection="column" marginBottom={1}>
      <Text color="blue">Assistant:</Text>
      <MarkdownText text={text} />
    </Box>
  );
}

export function TerminalThread() {
  return (
    <ThreadPrimitive.Root flexDirection="column">
      <AuiIf condition={(s) => s.thread.isEmpty}>
        <Text dimColor>Send a message to begin.</Text>
      </AuiIf>
      <ThreadPrimitive.Messages>{() => <Message />}</ThreadPrimitive.Messages>
      <LoadingPrimitive.Root gap={1}>
        <LoadingPrimitive.Spinner variant="bar" />
        <LoadingPrimitive.Text />
        <LoadingPrimitive.ElapsedTime />
      </LoadingPrimitive.Root>
      <Box borderStyle="round" borderColor="gray" paddingX={1}>
        <Text color="gray">{"> "}</Text>
        <ComposerPrimitive.Input submitOnEnter placeholder="Message..." autoFocus />
      </Box>
    </ThreadPrimitive.Root>
  );
}
这个简单的消息示例仅渲染文本内容。如需处理工具调用、附件、数据、来源或推理内容,请使用
MessagePrimitive.Parts
。它默认的终端安全渲染器可处理这些内容类型,primitives文档展示了组件映射关系。

Terminal input behavior

终端输入行为

ComposerPrimitive.Input
is a composer bound
TextInput
. It is a controlled line editor, not Ink's nonexistent native input.
submitOnEnter
sends the current composer text. With
multiLine
, Enter inserts a newline unless
submitOnEnter
is enabled, in which case Shift Enter inserts a newline when the terminal distinguishes it.
Ctrl J
inserts a newline only in multiline mode and never submits a single line input.
  • Left, Right, Backspace, Delete, and
    Ctrl D
    operate on one grapheme, so an emoji, ZWJ sequence, combining character, or CJK ideograph is never split.
  • Home and End select the whole buffer in single line mode or the current line in multiline mode.
    Ctrl A
    and
    Ctrl E
    always select the current line boundary.
  • Up and Down navigate multiline rows while preserving the terminal display column, calculated from grapheme widths rather than UTF 16 offsets.
  • Ctrl W
    ,
    Alt B
    ,
    Alt F
    , and
    Alt D
    navigate or delete by
    Intl.Segmenter
    word boundaries.
    Ctrl U
    and
    Ctrl K
    kill to the current boundary. At multiline end of line,
    Ctrl K
    joins the next line.
Meta bindings need a terminal that emits Escape prefixed sequences. In macOS Terminal, enable “Use Option as Meta key” for the Alt bindings. Shift Enter requires CSI u support, including iTerm2 3.4 or newer, kitty, and foot. Other terminals treat it as Enter and use
submitOnEnter
behavior. See primitives for
TextInput
outside a composer.
ComposerPrimitive.Input
是一个绑定到编辑器的
TextInput
组件。它是一个受控行编辑器,而非Ink不存在的原生输入组件。
submitOnEnter
属性会在按下Enter时发送当前编辑器文本。启用
multiLine
后,Enter会插入换行符,除非同时启用了
submitOnEnter
,此时在支持的终端中Shift+Enter会插入换行符。
Ctrl+J
仅在多行模式下插入换行符,且永远不会提交单行输入。
  • 左箭头、右箭头、退格、删除及
    Ctrl+D
    操作针对单个grapheme,因此表情符号、ZWJ序列、组合字符或CJK表意文字不会被拆分。
  • Home和End键在单行模式下选中整个缓冲区,在多行模式下选中当前行。
    Ctrl+A
    和
    Ctrl+E
    始终选中当前行的边界。
  • 上箭头和下箭头在多行模式下导航行,并保持终端显示列的位置,该位置由grapheme宽度而非UTF-16偏移量计算得出。
  • Ctrl+W
    、
    Alt+B
    、
    Alt+F
    及
    Alt+D
    根据
    Intl.Segmenter
    的单词边界进行导航或删除。
    Ctrl+U
    和
    Ctrl+K
    删除至当前边界。在多行模式下的行尾,
    Ctrl+K
    会合并下一行。
Meta键绑定需要终端支持Escape前缀序列。在macOS Terminal中,需启用“将Option键用作Meta键”来支持Alt键绑定。Shift+Enter需要CSI u支持,包括iTerm2 3.4或更高版本、kitty及foot终端。其他终端会将其视为Enter键,并执行
submitOnEnter
的行为。如需在编辑器外使用
TextInput
,请参考primitives文档。

Markdown in the terminal

终端中的Markdown渲染

Pass accumulated text to
MarkdownText
from
@assistant-ui/react-ink-markdown
. It produces terminal styled output rather than HTML or a React DOM tree. The package also exports
MarkdownTextPrimitive
,
useShikiHighlighter
, and theme types for a custom renderer, but start with
MarkdownText
unless you need to change its rendering pipeline.
tsx
import { MarkdownText } from "@assistant-ui/react-ink-markdown";

export function AssistantReply({ text }: { text: string }) {
  return <MarkdownText text={text} />;
}
将累积的文本传递给
@assistant-ui/react-ink-markdown
中的
MarkdownText
组件。它会生成终端样式的输出,而非HTML或React DOM树。该包还导出了
MarkdownTextPrimitive
、
useShikiHighlighter
及主题类型用于自定义渲染器,但除非需要修改渲染流程,否则请先使用
MarkdownText
。
tsx
import { MarkdownText } from "@assistant-ui/react-ink-markdown";

export function AssistantReply({ text }: { text: string }) {
  return <MarkdownText text={text} />;
}

Common Gotchas

常见问题

The terminal app sends requests to its own process instead of the backend
  • AssistantChatTransport
    needs an absolute
    api
    URL such as
    http://localhost:3000/api/chat
    . Host the AI SDK route separately from the Ink process.
A web Thread or shadcn element renders nothing in the CLI
  • Browser elements require the DOM and CSS. Rebuild the surface with Ink
    Box
    ,
    Text
    , and
    @assistant-ui/react-ink
    primitives.
A primitive or state hook throws about missing runtime context
  • Mount it under
    AssistantRuntimeProvider runtime={runtime}
    .
    ThreadPrimitive.Messages
    and
    MessagePrimitive.Parts
    also create the item scopes their children read.
Enter does not send or inserts a newline unexpectedly
  • submitOnEnter
    is false by default. Combine it with
    multiLine
    only when Enter should send and Shift Enter should insert a newline on capable terminals.
Emoji deletion corrupts the visible input or vertical navigation lands in the wrong place
  • Use
    ComposerPrimitive.Input
    or exported
    TextInput
    . Their buffer uses grapheme segmentation and terminal display widths. Do not substitute character index arithmetic.
Thread data disappears after restarting the CLI
  • useLocalRuntime
    is process memory. Use
    createFileStorageAdapter
    for one local process or a
    RemoteThreadListAdapter
    for backend owned metadata.
Markdown was imported from the web package
  • Import
    MarkdownText
    from
    @assistant-ui/react-ink-markdown
    , which emits terminal formatting. Web markdown renderers target the DOM.
终端应用向自身进程发送请求而非后端
  • AssistantChatTransport
    需要绝对的
    api
    URL,例如
    http://localhost:3000/api/chat
    。需将AI SDK路由托管在Ink进程之外的独立服务中。
Web Thread或shadcn元素在CLI中无渲染结果
  • 浏览器元素依赖DOM和CSS。需使用Ink的
    Box
    、
    Text
    及
    @assistant-ui/react-ink
    primitives重新构建界面。
Primitive或状态Hook抛出缺少运行时上下文的错误
  • 需将其挂载在
    AssistantRuntimeProvider runtime={runtime}
    之下。
    ThreadPrimitive.Messages
    和
    MessagePrimitive.Parts
    也会创建其子组件读取的项作用域。
Enter键未发送消息或意外插入换行符
  • submitOnEnter
    默认值为false。仅当需要Enter键发送消息且在支持的终端中Shift+Enter插入换行符时,才将其与
    multiLine
    结合使用。
删除表情符号导致可见输入损坏或垂直导航位置错误
  • 使用
    ComposerPrimitive.Input
    或导出的
    TextInput
    组件。它们的缓冲区使用grapheme分割和终端显示宽度计算。请勿使用字符索引运算替代。
重启CLI后会话数据消失
  • useLocalRuntime
    仅存储在进程内存中。如需单本地进程存储,请使用
    createFileStorageAdapter
    ;如需后端托管元数据,请使用
    RemoteThreadListAdapter
    。
Markdown从Web包导入
  • 请从
    @assistant-ui/react-ink-markdown
    导入
    MarkdownText
    ,它会生成终端格式。Web Markdown渲染器针对DOM设计。

Related Skills

相关技能

  • setup -- CLI scaffolding, browser setup, and package installation
  • runtime -- shared assistant-ui runtime, state, threads, and transport design
  • primitives -- browser unstyled primitives when the target is not a terminal
  • tools -- toolkit definitions and tool call renderers that also work in Ink
  • markdown -- browser markdown renderers and source display
  • setup -- CLI脚手架、浏览器设置及包安装
  • runtime -- 共享assistant-ui运行时、状态、会话及传输层设计
  • primitives -- 浏览器无样式primitives(当目标不是终端时使用)
  • tools -- 工具包定义及工具调用渲染器(同样适用于Ink)
  • markdown -- 浏览器Markdown渲染器及源显示