1. 当 AI 开始画界面,为什么老语言都不够用了
你可能已经习惯了让大模型帮你写一段 HTML,或者吐一段 JSON 再交给前端组件库渲染。这套流程在“一次性生成”的场景里跑得挺顺,但只要你想让界面边生成边显示,问题立刻暴露:HTML 标签太长,Token 烧得心疼;JSON 结构一旦被切断,整个解析直接崩掉;Markdown 倒是能流式输出,可它没法表达按钮、表格、表单这些可交互组件。
TokUI 想解决的就是这件事——它给 AI 发明了一种专门用来描述 UI 的语言,一套面向流式渲染的 DSL。简单说,TokUI 是一套 AI 原生的 UI 描述语言,用极短的标识符描述组件、属性和嵌套关系,解析器能在任意字符位置被切断后继续工作,适合大模型逐 Token 输出界面的场景。它适合谁?适合正在做 AI 对话产品、智能体界面、实时数据看板,并且被“流式渲染 UI”卡住的前端和后端同学。
我试过用传统方案硬扛流式 UI:让模型输出 JSON,前端用增量解析库去补全括号。结果是模型偶尔漏个引号,整个界面就白屏;换成 HTML,Token 消耗直接翻倍,长对话里成本肉眼可见地涨。TokUI 的思路不是改造 HTML 或 JSON,而是从 AI 的生成特征出发重新设计语法。这篇文章我会交付一套可复制的 DSL 配置骨架,再带你走一遍流式渲染的验证步骤,让你能自己跑通“AI 输出界面结构、前端实时解析渲染”这条链路。
2. 前置准备:拿到 TaoToken 的调用凭证
TokUI 本身是 UI 语言和渲染引擎,它不负责模型调用。你要让 AI 生成 TokUI DSL,得先有一个能稳定调用大模型的入口。这里我用 TaoToken 来做模型接入,它的接口兼容主流协议,配置成本低,适合拿来跑这类生成式 UI 的实验。
开始之前你需要准备两样东西:一个可用的 API Key,以及确认你要调用的模型名称。TaoToken 的 API 地址是https://taotoken.net/api,这个地址在配置里会反复用到。API Key 的创建入口在控制台的 API Keys 页面,你可以直接访问https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite去生成。
注意:API Key 只在创建时完整显示一次,复制后妥善保存。不要把它写进前端代码或提交到公开仓库,建议放在服务端环境变量里,由后端代理转发请求。
如果你还没决定用哪个模型,可以先到模型对话页面体验一下不同模型的输出风格,入口是https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite。对于 TokUI DSL 这种结构化输出,建议选指令遵循能力强的模型,生成出来的标签闭合和属性格式会更规整。
环境上你只需要 Node.js 18 以上,以及一个能发 HTTP 请求的终端。下面所有示例我都用 Node.js 写,方便你直接复制运行。
3. 可复制的 TokUI DSL 配置骨架
这一节是核心。我会先给你一份最小可用的 DSL 骨架,再解释每个部分为什么这么设计,最后给出让模型稳定输出这套 DSL 的提示词配置。
3.1 DSL 骨架长什么样
TokUI DSL 的写法非常紧凑。一个带标题、文本和按钮的卡片,大概是这样:
[card tt:"设备状态" stripe [p tx:"当前在线设备 12 台"] [btn tx:"刷新" clk:"refresh" v:"primary,sm"] ]对照一下 HTML 版本,同样的结构要写div、h3、p、button加上一堆class,Token 数量差了好几倍。TokUI 用tt代表 title,tx代表 text,clk代表 onclick,v代表 variant,布尔属性像stripe只写 key 不写值。内容直接跟在标签后面,不需要开闭标签成对包裹。
3.2 三层结构:语法、语义、协议
理解 TokUI 要抓住它的三层设计。最底层是 DSL 语法,定义组件怎么写、属性什么格式、怎么嵌套。中间层是组件语义,定义有哪些组件类型、各自接受什么属性。TokUI 注册了 150 多个组件,覆盖基础组件、表格、表单、布局、图表、AI 对话组件和灯箱组件七大类。最上层是协议层,定义前后端怎么传输、前端怎么解析、渲染引擎怎么绑定事件。
协议层支持三种模式:一次性渲染、流式渲染、SSE 连接。我们这篇重点验证流式渲染,因为它最能体现 TokUI 的价值。
3.3 让模型稳定输出 DSL 的提示词配置
模型不会天生就会写 TokUI DSL,你需要在系统提示里把语法规则喂给它。下面这份配置可以直接用:
你是一个 UI 生成器,只输出 TokUI DSL,不要输出任何解释文字。 语法规则: 1. 组件用方括号包裹,格式为 [组件名 属性:值 属性:值] 2. 高频属性使用短标识符:tt=title, tx=text, clk=onclick, ph=placeholder, v=value 3. 布尔属性只写 key:req=必填, stripe=斑马纹, dis=禁用 4. 多值属性用逗号分隔,例如 v:"primary,sm" 5. 文本内容直接写在标签后,例如 [p 这是一段文字] 6. 嵌套组件直接写在父组件内部,解析器支持隐式闭合 可用组件示例: card, p, btn, table, form, input, chart, progress 输出要求: - 只输出 DSL,不要 Markdown 代码块包裹 - 每个组件独占一行,便于流式解析 - 不要输出 HTML 或 JSON这份提示词的关键在于“只输出 DSL”和“每个组件独占一行”。前者避免模型夹带解释文字污染解析,后者让流式渲染时每一行到达就能立即处理,不用等整个结构闭合。
3.4 流式请求的代码骨架
下面这段 Node.js 代码把 TaoToken 的流式接口和 TokUI 的解析串起来。你可以直接保存成stream-ui.mjs运行:
const API_URL = "https://taotoken.net/api/v1/chat/completions"; const API_KEY = process.env.TAOTOKEN_API_KEY; const systemPrompt = `你是一个 UI 生成器,只输出 TokUI DSL...`; // 填入 3.3 的完整提示词 async function generateUI(userInput) { const resp = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${API_KEY}`, }, body: JSON.stringify({ model: "你的模型名称", stream: true, messages: [ { role: "system", content: systemPrompt }, { role: "user", content: userInput }, ], }), }); const reader = resp.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop(); for (const line of lines) { if (!line.startsWith("data: ")) continue; const payload = line.slice(6).trim(); if (payload === "[DONE]") continue; const json = JSON.parse(payload); const delta = json.choices?.[0]?.delta?.content; if (delta) { process.stdout.write(delta); // 这里替换成 TokUI 解析器调用 } } } } generateUI("生成一个展示服务器状态的卡片,包含标题、在线数量和一个刷新按钮");这段代码做了三件事:发起流式请求、按行切分 SSE 数据、把每个增量 Token 交给下游处理。真正接 TokUI 渲染引擎时,把process.stdout.write(delta)换成解析器的feed(delta)方法即可。
4. 验证请求:跑通流式渲染
代码写好了,接下来验证它是否真的能流式产出可解析的 UI 结构。
4.1 运行并观察输出
先在终端设置好 Key,再运行脚本:
export TAOTOKEN_API_KEY="你的Key" node stream-ui.mjs如果配置正确,你会看到 DSL 一行一行地打印出来,类似:
[card tt:"服务器状态" stripe [p tx:"当前在线 12 台"] [progress id:"load" v:35] [btn tx:"刷新" clk:"refresh" v:"primary,sm"] ]注意观察输出的节奏:它不是等所有内容生成完才一次性出现,而是逐行、逐段地冒出来。这就是流式渲染的基础——每个组件行到达时,解析器就能立即处理并交给渲染引擎。
4.2 验证增量更新指令
TokUI 有一个很实用的upd指令,用来更新已渲染组件的属性,不需要重绘整个界面。你可以在提示词里追加一句“生成一个进度条,然后用 upd 指令把进度从 35 更新到 67”,观察模型输出:
[progress id:"load" v:35] [upd id:"load" v:67]前端解析到upd时,只更新id为load的组件属性值。这个机制让实时数据推送变得很轻——股票价格变动、设备状态刷新、订单进度推进,都只需要发一条极短的指令。
4.3 验证容错能力
TokUI 解析器内置了隐式闭合机制。你可以故意构造一段不完整的 DSL 来测试:
[card tt:"测试" [p tx:"第一段"] [p tx:"第二段"第二个p标签没有闭合。在 HTML 里这可能导致结构错乱,但 TokUI 解析器会在遇到新的同级标签或流结束时自动补全。你可以在解析器里加一行日志,确认它没有抛异常,而是正常输出了两个段落组件。
4.4 成功结果的判断标准
一次成功的流式渲染验证,应该满足三个条件:第一,DSL 逐行到达时解析器不报错;第二,界面组件随 Token 到达逐步出现,而不是最后一次性刷新;第三,upd指令能正确更新指定组件的属性。三条都通过,说明你的 TokUI 流式链路已经打通。
5. 本篇常见错误排查
跑不通的时候,大概率是下面几个问题之一。
模型输出夹带了 Markdown 代码块。表现是解析器收到```text这样的行直接报错。原因是系统提示里没有明确禁止代码块包裹。解决办法是在提示词里加一句“不要用 Markdown 代码块包裹输出”,并且在解析器里对```开头的行做过滤。
流式数据被截断导致 JSON 解析失败。表现是JSON.parse抛异常。这通常是因为 SSE 数据按\n切分时,某一行还没接收完整就被处理了。注意上面代码里buffer = lines.pop()这一行,它把最后一段不完整的行留到下一轮,这是处理流式 SSE 的标准做法,别省掉。
API 返回 401 或 403。检查TAOTOKEN_API_KEY是否设置成功,以及请求头里Authorization的格式是不是Bearer加 Key。如果 Key 是在控制台刚创建的,确认没有多余空格。
模型不按 DSL 格式输出。表现是返回自然语言或 HTML。这多半是模型选择问题,指令遵循弱的模型对自定义语法的遵守度差。换一个指令遵循能力强的模型,或者把提示词里的语法规则再精简、再强调“只输出 DSL”。
组件不渲染但解析没报错。检查组件名是否在渲染引擎的注册表里。TokUI 注册了 150 多个组件,但如果你用了未注册的组件名,解析器可能静默跳过。对照组件文档确认名称拼写。
流式输出卡住不动。检查stream: true是否设置,以及服务端是否支持流式。另外确认读取循环里没有阻塞操作,reader.read()是异步的,别在里面做同步耗时计算。
6. 把 TokUI 接进你的 AI 产品
走到这里,你已经有了可复制的 DSL 骨架、可运行的流式请求代码,以及一套排障清单。接下来就是把它接进真实产品。如果你主要在做长期编码和智能体方向,需要更稳定的调用配额和更完整的工程支持,可以了解一下 Coding Plan,入口在https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite。接入过程中遇到协议细节问题,接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite,里面有完整的接口说明和参数对照。
我的建议是先把上面那段stream-ui.mjs跑通,确认 DSL 能逐行产出,再去接 TokUI 的渲染引擎。很多同学一上来就搭完整前端,结果解析层的问题和渲染层的问题混在一起,排查起来很痛苦。分层验证,先通链路再调样式,会省下大量时间。