ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

AI Coding 为什么选择 TUI:用 Ink + React 给前端开一条新路,TaoToken 配置骨架先跑通

AI Coding 为什么选择 TUI:用 Ink + React 给前端开一条新路,TaoToken 配置骨架先跑通 1. 为什么 AI Coding 场景下 TUI 突然成了前端的新战场如果你最近半年在折腾 AI 编程工具大概率会发现一个反直觉的现象命令行界面TUI正在被重新发明。Claude Code、Gemini CLI、Codex CLI、Aider 这些工具不约而同地把主界面放在了终端里而不是做一个漂亮的 Electron 窗口。作为一个写了多年 React 的前端我第一次打开 Claude Code 的时候是有点懵的——这不就是把 JSX 渲染到字符网格里吗TUI 全称 Text User Interface文本用户界面。它和 GUI 的核心区别在于GUI 的渲染单元是像素TUI 的渲染单元是字符单元格。听起来像是倒退但放到 AI Coding 场景里逻辑完全变了。AI 编程助手的高频交互是「输入意图 → 模型流式输出 → 工具调用 → 结果回显」这个循环它需要的是低延迟、高信息密度、键盘优先、上下文连续。GUI 的多面板布局在这个循环里反而是负担你的注意力被文件树、编辑器、终端、Copilot 侧边栏同时争夺而 TUI 把所有信息压进一个垂直滚动的字符流里认知负荷反而更低。对前端来说这个机会的入口是 Ink。Ink 是一个把 React 的组件模型搬到终端的渲染器你写 JSX它渲染成 ANSI 转义序列。它的底层是 React Reconciler Yoga 布局引擎和 React Native 共享几乎相同的架构。也就是说你为 React Native 积累的 Flexbox 布局经验、Hooks 心智模型、组件生命周期理解可以几乎零成本迁移到 TUI 开发。Claude Code 甚至在src/ink/目录下自研了一套完整的终端渲染系统包括对象池化的 CharPool、双缓冲屏幕、DECSTBM 硬件滚屏优化——这些工程细节说明 TUI 不是玩具而是一个正在快速成熟的渲染目标。这篇文章不打算空谈趋势。我会先给你一套可以直接复制运行的 TaoToken 配置骨架把统一 Key 接入跑通然后用 Ink React 写一个最小的终端界面最后附一条启动验证命令。你跑完这一套就能自己判断 TUI 方向值不值得投入。2. TaoToken 前置统一 Key 接入与配置骨架在开始写 Ink 代码之前先把模型接入这一层搞定。AI Coding 工具通常需要你配置模型提供商的 API Key、Base URL、模型名称。如果你同时用 Claude Code、Cursor、Aider 或者自己写的 Agent每个工具都要单独配一遍 Key管理起来很烦。TaoToken 的思路是提供一个统一的 API 入口你只需要维护一份 Key各个工具通过不同的配置格式指向同一个 Base URL。TaoToken 的 API 地址是https://taotoken.net/api官网是https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。你需要先去控制台创建一个 API Key地址是https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteKey 管理页面在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite。创建好之后你会拿到一个以sk-开头的字符串后面所有配置都用它。这里要强调一点TaoToken 是合规的 API 聚合入口不是灰色中转。你通过它调用的是官方模型能力计费和调用日志都可以在控制台查到。配置的时候只需要改 Base URL 和 Key不需要动任何网络层的东西。2.1 Claude Code 的 settings.json 骨架Claude Code 的配置走settings.json通常放在~/.claude/settings.json或者项目根目录的.claude/settings.json。核心是env字段把 Anthropic 的 Base URL 指向 TaoToken 的兼容端点。{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: sk-你的TaoToken密钥, ANTHROPIC_MODEL: claude-sonnet-4-20250514, ANTHROPIC_SMALL_FAST_MODEL: claude-haiku-4-20250514 }, permissions: { allow: [ Read, Write, Bash(npm run *), Bash(git status), Bash(git diff *) ] } }这里ANTHROPIC_BASE_URL填https://taotoken.net/api不要加 UTM 参数API 端点保持干净。ANTHROPIC_AUTH_TOKEN填你刚才创建的 Key。ANTHROPIC_MODEL和ANTHROPIC_SMALL_FAST_MODEL分别指定主模型和快速模型你可以根据控制台里可用的模型列表调整。注意settings.json里的 Key 是明文存储的。如果你在团队环境里用建议把 Key 放到环境变量里然后在settings.json里用${ANTHROPIC_AUTH_TOKEN}引用避免提交到 Git。2.2 通用 config.toml 骨架如果你用的是 Aider、Codex CLI 或者其他支持 TOML 配置的工具可以用下面这个骨架。以 Aider 为例配置文件在~/.aider.conf.yml或者项目根目录的.aider.conf.yml但很多工具也支持config.toml格式。[model] provider anthropic name claude-sonnet-4-20250514 api_base https://taotoken.net/api api_key sk-你的TaoToken密钥 [model.fast] name claude-haiku-4-20250514 api_base https://taotoken.net/api api_key sk-你的TaoToken密钥 [ui] stream true dark_mode true pretty true [git] auto_commits false dirty_commits true这个骨架的关键是api_base统一指向https://taotoken.net/apiapi_key用同一个 TaoToken Key。这样你在不同工具之间切换的时候只需要维护一份 Key不用每个工具都去申请一遍。2.3 环境变量方式推荐用于 CI/CD如果你在 CI 或者容器环境里跑 AI Coding 工具环境变量是最干净的方式。export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_AUTH_TOKENsk-你的TaoToken密钥 export ANTHROPIC_MODELclaude-sonnet-4-20250514把这三行写进你的~/.bashrc或者~/.zshrc或者放在 CI 的 secrets 里。这样所有支持 Anthropic 协议的工具都能自动读取不需要每个工具单独配。3. 用 Ink React 写一个最小 TUI 界面配置跑通之后我们来写代码。这一节的目标是用 Ink React 做一个终端里的交互式界面包含一个输入框、一个消息列表、一个状态栏。你会看到 JSX 是怎么被渲染成字符网格的以及 Flexbox 布局在终端里是怎么工作的。3.1 初始化项目先建一个空目录初始化 npm 项目安装依赖。mkdir ink-tui-demo cd ink-tui-demo npm init -y npm install ink react npm install -D typescript tsx types/reactInk 的当前主版本是 5.x要求 React 18。TypeScript 配置里需要把jsx设为react-jsx。{ compilerOptions: { target: ES2022, module: ESNext, moduleResolution: bundler, jsx: react-jsx, strict: true, esModuleInterop: true, skipLibCheck: true, outDir: dist }, include: [src] }3.2 第一个 Ink 组件Hello Terminal创建src/hello.tsx写一个最简单的 Ink 组件。import React from react; import { render, Text, Box } from ink; function App() { return ( Box flexDirectioncolumn padding{1} borderStyleround Text colorgreen bold TaoToken TUI Demo /Text Text dimColor Base URL: https://taotoken.net/api /Text Box marginTop{1} Text 当前时间: {new Date().toLocaleTimeString()} /Text /Box /Box ); } render(App /);运行npx tsx src/hello.tsx你会看到终端里出现一个圆角边框的盒子里面有绿色的标题和灰色的副标题。这就是 Ink 的基本能力Box对应 Flex 容器Text对应文本节点borderStyle支持round、single、double、bold等样式。3.3 加入输入框和消息列表现在做一个更像 AI Coding 助手的界面。创建src/chat.tsx。import React, { useState } from react; import { render, Text, Box, useInput, useApp } from ink; type Message { role: user | assistant; content: string; }; function MessageList({ messages }: { messages: Message[] }) { return ( Box flexDirectioncolumn marginBottom{1} {messages.map((msg, i) ( Box key{i} marginBottom{0} Text color{msg.role user ? cyan : green} bold {msg.role user ? 你 : AI}: /Text Text {msg.content}/Text /Box ))} /Box ); } function InputBox({ onSubmit }: { onSubmit: (text: string) void }) { const [value, setValue] useState(); useInput((input, key) { if (key.return) { if (value.trim()) { onSubmit(value.trim()); setValue(); } return; } if (key.backspace || key.delete) { setValue((v) v.slice(0, -1)); return; } if (input !key.ctrl !key.meta) { setValue((v) v input); } }); return ( Box borderStylesingle borderColorgray paddingX{1} Text colorcyan{ }/Text Text{value}/Text Text colorgray█/Text /Box ); } function App() { const { exit } useApp(); const [messages, setMessages] useStateMessage[]([ { role: assistant, content: 你好我是 TaoToken TUI 助手。输入消息试试。 }, ]); useInput((input, key) { if (key.ctrl input c) { exit(); } }); const handleSubmit (text: string) { setMessages((prev) [ ...prev, { role: user, content: text }, { role: assistant, content: 收到: ${text}这里是模型回复占位 }, ]); }; return ( Box flexDirectioncolumn padding{1} Box marginBottom{1} Text bold colorgreen TaoToken TUI · Ink React /Text /Box MessageList messages{messages} / InputBox onSubmit{handleSubmit} / Box marginTop{1} Text dimColorCtrlC 退出/Text /Box /Box ); } render(App /);运行npx tsx src/chat.tsx你会看到一个完整的终端聊天界面。输入文字按回车消息会追加到列表里。useInput是 Ink 提供的键盘输入 Hook它把终端的原始按键事件抽象成了input字符串和key对象和浏览器里的onKeyDown非常像。3.4 接入 TaoToken 流式输出上面的回复是占位的。现在把真实的模型调用接进来。Ink 组件里可以用useEffect发起请求用useState更新流式内容。import React, { useState, useEffect } from react; import { render, Text, Box, useInput, useApp } from ink; type Message { role: user | assistant; content: string; streaming?: boolean; }; async function streamChat( userMessage: string, onDelta: (text: string) void, onDone: () void ) { const response await fetch(https://taotoken.net/api/v1/messages, { method: POST, headers: { Content-Type: application/json, x-api-key: process.env.ANTHROPIC_AUTH_TOKEN || , anthropic-version: 2023-06-01, }, body: JSON.stringify({ model: claude-sonnet-4-20250514, max_tokens: 1024, stream: true, messages: [{ role: user, content: userMessage }], }), }); if (!response.body) { throw new Error(No response body); } const reader response.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 data line.slice(6); if (data [DONE]) continue; try { const parsed JSON.parse(data); if (parsed.type content_block_delta) { onDelta(parsed.delta.text); } } catch { // 忽略非 JSON 行 } } } onDone(); }然后在App组件里调用这个函数。注意streamChat里的 URL 是https://taotoken.net/api/v1/messages这是 Anthropic 兼容的 Messages API 端点。请求头里x-api-key用你的 TaoToken Keyanthropic-version固定为2023-06-01。function App() { const { exit } useApp(); const [messages, setMessages] useStateMessage[]([ { role: assistant, content: 你好我是 TaoToken TUI 助手。 }, ]); const [loading, setLoading] useState(false); useInput((input, key) { if (key.ctrl input c) exit(); }); const handleSubmit async (text: string) { setMessages((prev) [...prev, { role: user, content: text }]); setLoading(true); const assistantIndex messages.length 1; setMessages((prev) [ ...prev, { role: assistant, content: , streaming: true }, ]); try { await streamChat( text, (delta) { setMessages((prev) { const next [...prev]; const last next[next.length - 1]; if (last.role assistant) { next[next.length - 1] { ...last, content: last.content delta, }; } return next; }); }, () { setMessages((prev) { const next [...prev]; const last next[next.length - 1]; if (last.role assistant) { next[next.length - 1] { ...last, streaming: false }; } return next; }); setLoading(false); } ); } catch (err) { setMessages((prev) [ ...prev, { role: assistant, content: 请求失败: ${(err as Error).message} }, ]); setLoading(false); } }; return ( Box flexDirectioncolumn padding{1} Box marginBottom{1} Text bold colorgreen TaoToken TUI · Ink React /Text /Box MessageList messages{messages} / {loading ( Box marginBottom{1} Text coloryellow思考中.../Text /Box )} InputBox onSubmit{handleSubmit} / Box marginTop{1} Text dimColorCtrlC 退出/Text /Box /Box ); }运行之前确保环境变量已经设置好export ANTHROPIC_AUTH_TOKENsk-你的TaoToken密钥 npx tsx src/chat.tsx现在你在终端里输入问题就能看到模型流式返回的回复逐字追加到消息列表里。这就是一个最小可用的 AI Coding TUI 原型。4. 验证请求与成功结果配置和代码都写完之后怎么确认整条链路是通的我建议分三步验证从底层到上层。4.1 用 curl 验证 API 连通性先不跑 Ink直接用 curl 打 TaoToken 的 API确认 Key 和 Base URL 没问题。curl -s https://taotoken.net/api/v1/messages \ -H Content-Type: application/json \ -H x-api-key: $ANTHROPIC_AUTH_TOKEN \ -H anthropic-version: 2023-06-01 \ -d { model: claude-sonnet-4-20250514, max_tokens: 64, messages: [{role: user, content: 回复 OK 两个字母}] }如果返回的 JSON 里有content字段且文本内容包含OK说明 Key 和 Base URL 都正确。如果返回 401检查 Key 是否复制完整如果返回 404检查 Base URL 是否写成了https://taotoken.net/api而不是其他路径。4.2 验证 Claude Code 配置如果你用 Claude Code配置好settings.json之后在项目目录里运行claude -p 用一句话说明当前目录是什么项目-p是 print 模式直接输出结果不进入交互界面。如果能看到模型返回的项目描述说明 Claude Code 已经通过 TaoToken 正常调用模型。如果报错ANTHROPIC_AUTH_TOKEN is not set检查settings.json的env字段是否被正确加载。4.3 验证 Ink TUI 启动最后跑我们的 Ink 应用npx tsx src/chat.tsx终端里应该出现一个带边框的界面顶部是绿色标题中间是消息列表底部是输入框。输入「你好」按回车如果看到「思考中...」然后逐字出现模型回复说明整条链路——Ink 渲染 → fetch 请求 → TaoToken API → 流式返回 → 状态更新 → 重新渲染——全部跑通。提示如果你在 Ink 里看到中文乱码或者边框错位通常是终端字体不支持宽字符或者终端宽度不够。把终端窗口拉宽到至少 80 列并确保使用支持 CJK 的等宽字体。5. 本篇常见错误排查这一节整理我在配置和开发过程中踩过的坑按错误现象分类。5.1 401 Unauthorized最常见的原因是 Key 没设置或者设置错了。检查三个地方settings.json里的ANTHROPIC_AUTH_TOKEN、环境变量ANTHROPIC_AUTH_TOKEN、代码里x-api-key请求头。注意 Claude Code 用的是ANTHROPIC_AUTH_TOKEN而直接调 Messages API 用的是x-api-key两者不要混。另外 Key 前后不要有空格复制的时候容易带上换行符。5.2 404 Not FoundBase URL 写错了。TaoToken 的 API 根地址是https://taotoken.net/apiMessages 端点是https://taotoken.net/api/v1/messages。如果你在settings.json里把ANTHROPIC_BASE_URL写成了https://taotoken.net/api/v1Claude Code 会拼成/v1/v1/messages导致 404。记住ANTHROPIC_BASE_URL只写到/api不要带/v1。5.3 Ink 渲染闪烁或残影Ink 默认使用差异渲染只更新变化的单元格。但如果你在组件里直接修改外部可变对象或者用了console.log在渲染过程中输出会破坏 Ink 的屏幕缓冲区导致残影。解决办法所有状态更新走useState不要在渲染函数里做副作用。如果确实需要调试输出用 Ink 的useStderr或者把日志写到文件。5.4 流式输出卡顿如果你发现模型回复不是逐字出现而是等很久然后一次性全出来通常是 fetch 的流式读取没有正确处理。检查streamChat里的buffer逻辑SSE 数据可能跨 chunk 分割必须用buffer累积按\n切分最后一行留在 buffer 里等下一个 chunk。另外确保response.body存在某些运行环境如旧版 Node的 fetch 实现不支持流式 body。5.5 终端宽度变化导致布局错乱Ink 的 Yoga 布局是基于终端宽度的。如果用户在运行过程中调整了终端窗口大小Ink 会收到resize事件并重新布局。但如果你在组件里硬编码了宽度值比如width{100}窗口变窄时内容会被截断。建议用useStdout获取终端尺寸或者用百分比和flexGrow做自适应布局。import { useStdout } from ink; function App() { const { stdout } useStdout(); const width stdout?.columns || 80; // 用 width 做自适应计算 }5.6 Claude Code 权限拒绝Claude Code 默认会询问是否允许执行 Bash 命令、写文件等操作。如果你在settings.json里配置了permissions.allow但格式不对会导致所有操作都被拒绝。allow数组里的每一项是一个权限字符串比如Bash(npm run *)表示允许所有npm run开头的命令。注意通配符*的位置Bash(git diff *)和Bash(git diff*)语义不同前者匹配git diff后有空格的情况。6. 下一步把 TUI 骨架扩展成真正的 AI Coding 工具到这里你已经有了一个能跑的 TaoToken 配置骨架和一个能流式对话的 Ink TUI。接下来怎么把它变成真正有用的工具我给你几个具体的扩展方向。第一个方向是加工具调用。AI Coding 助手的核心能力是读写文件、执行命令、搜索代码。你可以在streamChat里解析tool_use类型的 content block然后在 Ink 组件里渲染工具执行状态比如「正在读取 src/index.ts...」执行完把结果作为tool_result追加到消息历史里继续下一轮请求。这个循环就是 Agent 的基本骨架。第二个方向是加多会话管理。用useReducer管理一个会话列表每个会话有自己的消息历史和上下文。终端左侧用Box渲染会话列表右侧渲染当前会话。这就是一个终端版的 Cursor 侧边栏。第三个方向是加语法高亮。Ink 生态里有ink-markdown、ink-syntax-highlight等社区包可以把模型返回的代码块渲染成带颜色的文本。如果你想要更精细的控制可以自己解析 Markdown 代码块用cli-highlight生成 ANSI 序列再通过 Ink 的Text组件输出。如果你想把这条链路用到长期编码和 Agent 场景可以了解一下 TaoToken 的 Coding Plan地址是https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite。如果你只是想先验证模型对话能力可以直接用模型对话页面https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面有针对不同工具的详细配置说明。TUI 这个方向值不值得投入我的判断是如果你做的是开发者工具、AI Agent、CLI 应用TUI 是当前投入产出比最高的界面形态。它的渲染模型简单字符网格交互模型清晰键盘事件而且有 React 这样的成熟抽象层可以复用。前端工程师在这个领域的优势不是写 ANSI 转义序列而是组件化思维、状态管理、布局系统——这些能力在 TUI 里同样适用只是渲染目标从 DOM 变成了字符矩阵。
返回列表