ink
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseassistant-ui Ink
assistant-ui Ink
Always consult assistant-ui.com/llms.txt for the latest API.
@assistant-ui/react-inkBoxText@assistant-ui/react-ink-markdown请始终参考assistant-ui.com/llms.txt获取最新API。
@assistant-ui/react-inkBoxText@assistant-ui/react-ink-markdownReferences
参考文档
- ./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--inkwith-react-inkwith-react-ink-web--inkFor 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-sdkThe API route must run in a separate backend project. Unlike a browser app, a terminal process cannot use a relative URL. Give a complete URL that the process can reach.
/api/chatAssistantChatTransporttsx
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>
);
}useChatRuntimeAssistantChatTransport@assistant-ui/ai-sdk从零开始时,可通过脚手架搭建终端示例项目:
sh
npx assistant-ui@latest create --ink my-app
cd my-app--inkwith-react-inkwith-react-ink-web--ink对于已有的Node项目,需同时安装运行时、终端渲染器及AI SDK传输层:
sh
npm install @assistant-ui/react-ink @assistant-ui/react-ink-markdown ink react @assistant-ui/ai-sdkAPI路由需在独立的后端项目中运行。与浏览器应用不同,终端进程无法使用相对路径。需为提供一个进程可访问的完整URL。
/api/chatAssistantChatTransporttsx
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>
);
}useChatRuntimeAssistantChatTransport@assistant-ui/ai-sdkCompose a terminal thread
组合终端会话
ThreadPrimitive.Messagess.messageAuiIfLoadingPrimitivetsx
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 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.
MessagePrimitive.PartsThreadPrimitive.Messagess.messageAuiIfLoadingPrimitivetsx
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>
);
}这个简单的消息示例仅渲染文本内容。如需处理工具调用、附件、数据、来源或推理内容,请使用。它默认的终端安全渲染器可处理这些内容类型,primitives文档展示了组件映射关系。
MessagePrimitive.PartsTerminal input behavior
终端输入行为
ComposerPrimitive.InputTextInputsubmitOnEntermultiLinesubmitOnEnterCtrl J- Left, Right, Backspace, Delete, and operate on one grapheme, so an emoji, ZWJ sequence, combining character, or CJK ideograph is never split.
Ctrl D - Home and End select the whole buffer in single line mode or the current line in multiline mode. and
Ctrl Aalways select the current line boundary.Ctrl E - 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, andAlt Fnavigate or delete byAlt Dword boundaries.Intl.SegmenterandCtrl Ukill to the current boundary. At multiline end of line,Ctrl Kjoins the next line.Ctrl K
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 behavior. See primitives for outside a composer.
submitOnEnterTextInputComposerPrimitive.InputTextInputsubmitOnEntermultiLinesubmitOnEnterCtrl+J- 左箭头、右箭头、退格、删除及操作针对单个grapheme,因此表情符号、ZWJ序列、组合字符或CJK表意文字不会被拆分。
Ctrl+D - 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键,并执行的行为。如需在编辑器外使用,请参考primitives文档。
submitOnEnterTextInputMarkdown in the terminal
终端中的Markdown渲染
Pass accumulated text to from . It produces terminal styled output rather than HTML or a React DOM tree. The package also exports , , and theme types for a custom renderer, but start with unless you need to change its rendering pipeline.
MarkdownText@assistant-ui/react-ink-markdownMarkdownTextPrimitiveuseShikiHighlighterMarkdownTexttsx
import { MarkdownText } from "@assistant-ui/react-ink-markdown";
export function AssistantReply({ text }: { text: string }) {
return <MarkdownText text={text} />;
}将累积的文本传递给中的组件。它会生成终端样式的输出,而非HTML或React DOM树。该包还导出了、及主题类型用于自定义渲染器,但除非需要修改渲染流程,否则请先使用。
@assistant-ui/react-ink-markdownMarkdownTextMarkdownTextPrimitiveuseShikiHighlighterMarkdownTexttsx
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
- needs an absolute
AssistantChatTransportURL such asapi. Host the AI SDK route separately from the Ink process.http://localhost:3000/api/chat
A web Thread or shadcn element renders nothing in the CLI
- Browser elements require the DOM and CSS. Rebuild the surface with Ink ,
Box, andTextprimitives.@assistant-ui/react-ink
A primitive or state hook throws about missing runtime context
- Mount it under .
AssistantRuntimeProvider runtime={runtime}andThreadPrimitive.Messagesalso create the item scopes their children read.MessagePrimitive.Parts
Enter does not send or inserts a newline unexpectedly
- is false by default. Combine it with
submitOnEnteronly when Enter should send and Shift Enter should insert a newline on capable terminals.multiLine
Emoji deletion corrupts the visible input or vertical navigation lands in the wrong place
- Use or exported
ComposerPrimitive.Input. Their buffer uses grapheme segmentation and terminal display widths. Do not substitute character index arithmetic.TextInput
Thread data disappears after restarting the CLI
- is process memory. Use
useLocalRuntimefor one local process or acreateFileStorageAdapterfor backend owned metadata.RemoteThreadListAdapter
Markdown was imported from the web package
- Import from
MarkdownText, which emits terminal formatting. Web markdown renderers target the DOM.@assistant-ui/react-ink-markdown
终端应用向自身进程发送请求而非后端
- 需要绝对的
AssistantChatTransportURL,例如api。需将AI SDK路由托管在Ink进程之外的独立服务中。http://localhost:3000/api/chat
Web Thread或shadcn元素在CLI中无渲染结果
- 浏览器元素依赖DOM和CSS。需使用Ink的、
Box及Textprimitives重新构建界面。@assistant-ui/react-ink
Primitive或状态Hook抛出缺少运行时上下文的错误
- 需将其挂载在之下。
AssistantRuntimeProvider runtime={runtime}和ThreadPrimitive.Messages也会创建其子组件读取的项作用域。MessagePrimitive.Parts
Enter键未发送消息或意外插入换行符
- 默认值为false。仅当需要Enter键发送消息且在支持的终端中Shift+Enter插入换行符时,才将其与
submitOnEnter结合使用。multiLine
删除表情符号导致可见输入损坏或垂直导航位置错误
- 使用或导出的
ComposerPrimitive.Input组件。它们的缓冲区使用grapheme分割和终端显示宽度计算。请勿使用字符索引运算替代。TextInput
重启CLI后会话数据消失
- 仅存储在进程内存中。如需单本地进程存储,请使用
useLocalRuntime;如需后端托管元数据,请使用createFileStorageAdapter。RemoteThreadListAdapter
Markdown从Web包导入
- 请从导入
@assistant-ui/react-ink-markdown,它会生成终端格式。Web Markdown渲染器针对DOM设计。MarkdownText
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渲染器及源显示