ink
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese@json-render/ink
@json-render/ink
Ink terminal renderer that converts JSON specs into interactive terminal component trees with standard components, data binding, visibility, actions, and dynamic props.
这是一款Ink终端渲染器,可将JSON规格转换为包含标准组件、数据绑定、可见性控制、动作处理和动态属性的交互式终端组件树。
Quick Start
快速开始
typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/ink/schema";
import {
standardComponentDefinitions,
standardActionDefinitions,
} from "@json-render/ink/catalog";
import { defineRegistry, Renderer, type Components } from "@json-render/ink";
import { z } from "zod";
// Create catalog with standard + custom components
const catalog = defineCatalog(schema, {
components: {
...standardComponentDefinitions,
CustomWidget: {
props: z.object({ title: z.string() }),
slots: [],
description: "Custom widget",
},
},
actions: standardActionDefinitions,
});
// Register only custom components (standard ones are built-in)
const { registry } = defineRegistry(catalog, {
components: {
CustomWidget: ({ props }) => <Text>{props.title}</Text>,
} as Components<typeof catalog>,
});
// Render
function App({ spec }) {
return (
<JSONUIProvider initialState={{}}>
<Renderer spec={spec} registry={registry} />
</JSONUIProvider>
);
}typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/ink/schema";
import {
standardComponentDefinitions,
standardActionDefinitions,
} from "@json-render/ink/catalog";
import { defineRegistry, Renderer, type Components } from "@json-render/ink";
import { z } from "zod";
// 创建包含标准组件和自定义组件的目录
const catalog = defineCatalog(schema, {
components: {
...standardComponentDefinitions,
CustomWidget: {
props: z.object({ title: z.string() }),
slots: [],
description: "Custom widget",
},
},
actions: standardActionDefinitions,
});
// 仅注册自定义组件(标准组件已内置)
const { registry } = defineRegistry(catalog, {
components: {
CustomWidget: ({ props }) => <Text>{props.title}</Text>,
} as Components<typeof catalog>,
});
// 渲染
function App({ spec }) {
return (
<JSONUIProvider initialState={{}}>
<Renderer spec={spec} registry={registry} />
</JSONUIProvider>
);
}Spec Structure (Flat Element Map)
规格结构(扁平元素映射)
The Ink schema uses a flat element map with a root key:
json
{
"root": "main",
"elements": {
"main": {
"type": "Box",
"props": { "flexDirection": "column", "padding": 1 },
"children": ["heading", "content"]
},
"heading": {
"type": "Heading",
"props": { "text": "Dashboard", "level": "h1" },
"children": []
},
"content": {
"type": "Text",
"props": { "text": "Hello from the terminal!" },
"children": []
}
}
}Ink 采用带根键的扁平元素映射结构:
json
{
"root": "main",
"elements": {
"main": {
"type": "Box",
"props": { "flexDirection": "column", "padding": 1 },
"children": ["heading", "content"]
},
"heading": {
"type": "Heading",
"props": { "text": "Dashboard", "level": "h1" },
"children": []
},
"content": {
"type": "Text",
"props": { "text": "Hello from the terminal!" },
"children": []
}
}
}Standard Components
标准组件
Layout
布局类
- - Flexbox layout container (like a terminal
Box). Use for grouping, spacing, borders, alignment. Default flexDirection is row.<div> - - Text output with optional styling (color, bold, italic, etc.)
Text - - Inserts blank lines. Must be inside a Box with flexDirection column.
Newline - - Flexible empty space that expands along the main axis.
Spacer
- - Flexbox布局容器(类似终端中的
Box)。用于分组、间距设置、边框和对齐。默认flexDirection为row。<div> - - 支持可选样式(颜色、加粗、斜体等)的文本输出
Text - - 插入空行。必须放在flexDirection为column的Box内。
Newline - - 可沿主轴扩展的灵活空白区域
Spacer
Content
内容类
- - Section heading (h1: bold+underlined, h2: bold, h3: bold+dimmed, h4: dimmed)
Heading - - Horizontal separator with optional centered title
Divider - - Colored inline label (variants: default, info, success, warning, error)
Badge - - Animated loading spinner with optional label
Spinner - - Horizontal progress bar (0-1)
ProgressBar - - Inline chart using Unicode block characters
Sparkline - - Horizontal bar chart with labels and values
BarChart - - Tabular data with headers and rows
Table - - Bulleted or numbered list
List - - Structured list row with title, subtitle, leading/trailing text
ListItem - - Bordered container with optional title
Card - - Key-value pair display
KeyValue - - Clickable URL with optional label
Link - - Status message with colored icon (info, success, warning, error)
StatusLine - - Renders markdown text with terminal styling
Markdown
- - 章节标题(h1:加粗+下划线,h2:加粗,h3:加粗+变暗,h4:变暗)
Heading - - 带可选居中标题的水平分隔线
Divider - - 彩色内联标签(变体:default、info、success、warning、error)
Badge - - 带动画的加载指示器,支持可选标签
Spinner - - 水平进度条(取值范围0-1)
ProgressBar - - 使用Unicode块字符的内联图表
Sparkline - - 带标签和数值的水平柱状图
BarChart - - 表格数据展示,包含表头和行
Table - - 项目符号或编号列表
List - - 结构化列表行,包含标题、副标题、前置/后置文本
ListItem - - 带可选标题的边框容器
Card - - 键值对展示
KeyValue - - 可点击的URL,支持可选标签
Link - - 带彩色图标(info、success、warning、error)的状态消息
StatusLine - - 以终端样式渲染Markdown文本
Markdown
Interactive
交互类
- - Text input field (events: submit, change)
TextInput - - Selection menu with arrow key navigation (events: change)
Select - - Multi-selection with space to toggle (events: change, submit)
MultiSelect - - Yes/No confirmation prompt (events: confirm, deny)
ConfirmInput - - Tab bar navigation with left/right arrow keys (events: change)
Tabs
- - 文本输入框(事件:submit、change)
TextInput - - 支持箭头键导航的选择菜单(事件:change)
Select - - 支持空格键切换的多选菜单(事件:change、submit)
MultiSelect - - 是/否确认提示框(事件:confirm、deny)
ConfirmInput - - 支持左右箭头键导航的标签栏(事件:change)
Tabs
Visibility Conditions
可见性条件
Use on elements to show/hide based on state. Syntax: , , , for AND, for OR.
visible{ "$state": "/path" }{ "$state": "/path", "eq": value }{ "$state": "/path", "not": true }{ "$and": [cond1, cond2] }{ "$or": [cond1, cond2] }可在元素上使用属性,根据状态显示/隐藏元素。语法:、、,使用表示逻辑与,表示逻辑或。
visible{ "$state": "/path" }{ "$state": "/path", "eq": value }{ "$state": "/path", "not": true }{ "$and": [cond1, cond2] }{ "$or": [cond1, cond2] }Dynamic Prop Expressions
动态属性表达式
Any prop value can be a data-driven expression resolved at render time:
- - reads from state model (one-way read)
{ "$state": "/state/key" } - - two-way binding: use on the natural value prop of form components
{ "$bindState": "/path" } - - two-way binding to a repeat item field
{ "$bindItem": "field" } - - conditional value
{ "$cond": <condition>, "$then": <value>, "$else": <value> } - - interpolates state values into strings
{ "$template": "Hello, ${/name}!" }
Components do not use a prop for two-way binding. Use on the natural value prop instead.
statePath{ "$bindState": "/path" }任何属性值都可以是在渲染时解析的数据驱动表达式:
- - 从状态模型读取数据(单向读取)
{ "$state": "/state/key" } - - 双向绑定:用于表单组件的天然值属性
{ "$bindState": "/path" } - - 双向绑定到重复项的字段
{ "$bindItem": "field" } - - 条件值
{ "$cond": <condition>, "$then": <value>, "$else": <value> } - - 将状态值插入字符串中
{ "$template": "Hello, ${/name}!" }
组件不使用属性进行双向绑定,而是在天然值属性上使用。
statePath{ "$bindState": "/path" }Event System
事件系统
Components use to fire named events. The element's field maps events to action bindings:
emitontsx
CustomButton: ({ props, emit }) => (
<Box>
<Text>{props.label}</Text>
{/* emit("press") triggers the action bound in the spec's on.press */}
</Box>
),json
{
"type": "CustomButton",
"props": { "label": "Submit" },
"on": { "press": { "action": "submit" } },
"children": []
}组件使用触发命名事件。元素的字段将事件映射到动作绑定:
emitontsx
CustomButton: ({ props, emit }) => (
<Box>
<Text>{props.label}</Text>
{/* emit("press") 会触发规格中on.press绑定的动作 */}
</Box>
),json
{
"type": "CustomButton",
"props": { "label": "Submit" },
"on": { "press": { "action": "submit" } },
"children": []
}Built-in Actions
内置动作
setStatepushStateremoveStatejson
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }setStatepushStateremoveStatejson
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }Repeat (Dynamic Lists)
重复渲染(动态列表)
Use the field on a container element to render items from a state array:
repeatjson
{
"type": "Box",
"props": { "flexDirection": "column" },
"repeat": { "statePath": "/items", "key": "id" },
"children": ["item-row"]
}Inside repeated children, use to read from the current item and for the current index.
{ "$item": "field" }{ "$index": true }可在容器元素上使用字段,从状态数组中渲染项:
repeatjson
{
"type": "Box",
"props": { "flexDirection": "column" },
"repeat": { "statePath": "/items", "key": "id" },
"children": ["item-row"]
}在重复的子元素中,使用读取当前项的数据,使用获取当前索引。
{ "$item": "field" }{ "$index": true }Streaming
流式渲染
Use to progressively render specs from JSONL patch streams:
useUIStreamtsx
import { useUIStream } from "@json-render/ink";
const { spec, send, isStreaming } = useUIStream({ api: "/api/generate" });使用从JSONL补丁流逐步渲染规格:
useUIStreamtsx
import { useUIStream } from "@json-render/ink";
const { spec, send, isStreaming } = useUIStream({ api: "/api/generate" });Server-Side Prompt Generation
服务端提示生成
Use the export to generate AI system prompts from your catalog:
./servertypescript
import { catalog } from "./catalog";
const systemPrompt = catalog.prompt({ system: "You are a terminal assistant." });使用导出,从你的组件目录生成AI系统提示词:
./servertypescript
import { catalog } from "./catalog";
const systemPrompt = catalog.prompt({ system: "You are a terminal assistant." });Providers
提供者(Providers)
| Provider | Purpose |
|---|---|
| Share state across components (JSON Pointer paths). Accepts optional |
| Handle actions dispatched via the event system |
| Enable conditional rendering based on state |
| Form field validation |
| Manage focus across interactive components |
| Combined provider for all contexts |
| 提供者 | 用途 |
|---|---|
| 在组件间共享状态(使用JSON Pointer路径)。接受可选的 |
| 处理通过事件系统分发的动作 |
| 启用基于状态的条件渲染 |
| 表单字段验证 |
| 管理交互式组件间的焦点 |
| 包含所有上下文的组合提供者 |
External Store (Controlled Mode)
外部存储(受控模式)
Pass a to (or ) to use external state management:
StateStoreStateProviderJSONUIProvidertsx
import { createStateStore, type StateStore } from "@json-render/ink";
const store = createStateStore({ count: 0 });
<StateProvider store={store}>{children}</StateProvider>
store.set("/count", 1); // React re-renders automaticallyWhen is provided, and are ignored.
storeinitialStateonStateChange将传递给(或)以使用外部状态管理:
StateStoreStateProviderJSONUIProvidertsx
import { createStateStore, type StateStore } from "@json-render/ink";
const store = createStateStore({ count: 0 });
<StateProvider store={store}>{children}</StateProvider>
store.set("/count", 1); // React会自动重新渲染当提供时,和会被忽略。
storeinitialStateonStateChangecreateRenderer (Higher-Level API)
createRenderer(高级API)
tsx
import { createRenderer } from "@json-render/ink";
import { standardComponents } from "@json-render/ink";
import { catalog } from "./catalog";
const InkRenderer = createRenderer(catalog, {
...standardComponents,
// custom component overrides here
});
// InkRenderer includes all providers (state, visibility, actions, focus)
render(
<InkRenderer spec={spec} state={{ activeTab: "overview" }} />
);tsx
import { createRenderer } from "@json-render/ink";
import { standardComponents } from "@json-render/ink";
import { catalog } from "./catalog";
const InkRenderer = createRenderer(catalog, {
...standardComponents,
// 在此处覆盖自定义组件
});
// InkRenderer包含所有提供者(状态、可见性、动作、焦点)
render(
<InkRenderer spec={spec} state={{ activeTab: "overview" }} />
);Key Exports
核心导出
| Export | Purpose |
|---|---|
| Create a type-safe component registry from a catalog |
| Render a spec using a registry |
| Higher-level: creates a component with built-in providers |
| Combined provider for all contexts |
| Ink flat element map schema (includes built-in state actions) |
| Catalog definitions for all standard components |
| Catalog definitions for standard actions |
| Pre-built component implementations |
| Access state context |
| Get single value from state |
| Two-way binding for |
| Access actions context |
| Get a single action dispatch function |
| Non-throwing variant of useValidation |
| Stream specs from an API endpoint |
| Create a framework-agnostic in-memory |
| Interface for plugging in external state management |
| Typed component map (catalog-aware) |
| Typed action map (catalog-aware) |
| Typed component context (catalog-aware) |
| Convert flat element map to tree structure |
| 导出项 | 用途 |
|---|---|
| 从组件目录创建类型安全的组件注册表 |
| 使用注册表渲染规格 |
| 高级API:创建包含内置提供者的组件 |
| 包含所有上下文的组合提供者 |
| Ink扁平元素映射的规格(包含内置状态动作) |
| 所有标准组件的目录定义 |
| 标准动作的目录定义 |
| 预构建的组件实现 |
| 访问状态上下文 |
| 从状态中获取单个值 |
| 为 |
| 访问动作上下文 |
| 获取单个动作的调度函数 |
| |
| 从API端点流式获取规格 |
| 创建与框架无关的内存 |
| 用于接入外部状态管理的接口 |
| 类型化的组件映射(感知目录) |
| 类型化的动作映射(感知目录) |
| 类型化的组件上下文(感知目录) |
| 将扁平元素映射转换为树结构 |
Terminal UI Design Guidelines
终端UI设计指南
- Use Box for layout (flexDirection, padding, gap). Default flexDirection is row.
- Terminal width is ~80-120 columns. Prefer vertical layouts (flexDirection: column) for main structure.
- Use borderStyle on Box for visual grouping (single, double, round, bold).
- Use named terminal colors: red, green, yellow, blue, magenta, cyan, white, gray.
- Use Heading for section titles, Divider to separate sections, Badge for status, KeyValue for labeled data, Card for bordered groups.
- Use Tabs for multi-view UIs with visible conditions on child content.
- Use Sparkline for inline trends and BarChart for comparing values.
- 使用Box进行布局(flexDirection、padding、gap)。默认flexDirection为row。
- 终端宽度约为80-120列。主结构优先使用垂直布局(flexDirection: column)。
- 在Box上使用borderStyle进行视觉分组(single、double、round、bold)。
- 使用终端命名颜色:red、green、yellow、blue、magenta、cyan、white、gray。
- 使用Heading作为章节标题,Divider分隔章节,Badge显示状态,KeyValue展示带标签的数据,Card作为边框容器。
- 使用Tabs实现多视图UI,结合子内容的可见性条件。
- 使用Sparkline展示内联趋势,使用BarChart比较数值。