惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Jina AI
Jina AI
Recent Announcements
Recent Announcements
Attack and Defense Labs
Attack and Defense Labs
P
Proofpoint News Feed
WordPress大学
WordPress大学
雷峰网
雷峰网
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 叶小钗
I
Intezer
Hugging Face - Blog
Hugging Face - Blog
博客园 - 司徒正美
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Threat Research - Cisco Blogs
有赞技术团队
有赞技术团队
T
Tailwind CSS Blog
S
Securelist
Cyberwarzone
Cyberwarzone
云风的 BLOG
云风的 BLOG
V
Visual Studio Blog
Stack Overflow Blog
Stack Overflow Blog
T
Tor Project blog
Project Zero
Project Zero
腾讯CDC
Recorded Future
Recorded Future
Help Net Security
Help Net Security
Spread Privacy
Spread Privacy
T
The Blog of Author Tim Ferriss
阮一峰的网络日志
阮一峰的网络日志
The Register - Security
The Register - Security
Google DeepMind News
Google DeepMind News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
L
Lohrmann on Cybersecurity
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Security Latest
Security Latest
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
N
News and Events Feed by Topic
D
DataBreaches.Net
V
Vulnerabilities – Threatpost
L
LangChain Blog
博客园 - 【当耐特】
V2EX - 技术
V2EX - 技术
Simon Willison's Weblog
Simon Willison's Weblog
Engineering at Meta
Engineering at Meta
The Last Watchdog
The Last Watchdog
Security Archives - TechRepublic
Security Archives - TechRepublic
D
Docker
C
CXSECURITY Database RSS Feed - CXSecurity.com
人人都是产品经理
人人都是产品经理

Ruby China

Ruby China Gems 镜像服务停服公告 [Remote][美国 A 轮] Bidbus 招聘高级全栈工程师和 Agent 工程师 Ruby 全栈开发高级工程师 用 rails 做了个小网站,拯救老 Kindle。 [上海][2026 年 07 月 14 日] Ruby Tuesday: OpenCode 企业二开 & AI-DLC 由 ai 驱动的开发流程 OkkProxy——仅需 1 美元即可获得 1GB 住宅代理 +1 个静态 ISP(新用户专享) NiuProxy 动态住宅代理:每 GB0.70 美元起(1TB 最高 0.50 美元/GB,10TB 起 0.30 美元/GB) Show Ruby China: 我用 Rails 8 开发的开源技术社区网站 MZFOSS 写了一个 Ruby 框架来管理个人环境初始化脚本 怎么让 AI 写出更有 Rails 味的代码? 头部跨境团队扩招,期待优秀的你 香港办公 / 远程 | 区块链工程师(SUI 链 DEX · ve3,3) 香港办公 / 远程 | 智能合约工程师(SUI 链 DEX · ve3,3) 有没有招 Rails 开发的 A minimalist agent in ~500 lines of Ruby: ReAct loop + macOS Seatbelt sandbox + security approvals + MCP + Skills + persistent memory 日本久光製藥鎮痛消炎貼成分?40mg 強效配方舒緩痠痛揭秘 独立开发者必看:把软著申报材料的脏活交给自动化工具 咱们老家被机器人刷屏了?https://github.com/ruby-china/homeland/pulls 新书《Rails 8 现代单体架构实战》发布 [远程] Schedar - 资深区块链工程师 使用 1password environments 来管理环境变量 Ontology driven Agent:从“提示词工程(Prompt Engineering)”向“智能体软件工程(Agent Software Engineering)” Ruby on Rails / DiscoursePluginEngineerRemote rails 项目里用 kamal 部署阿里云 ssl 证书流程总结 打听一个人 https://github.com/matzbot 關於 spinel 的原理和侷限 用 Ruby 构建 AI Agent(更新第二篇:工具调用) 在 Reddit 上发了一个 Ruby Agent 开发的帖子有点火了 有没有用 rails+AI 做了审批流的功能模块或 gem,交流下经验 最新的不一定好——预防供应链攻击 [远程] Moonveil Entertainment — web3 游戏工作室招聘 AI+ 区块链 / 区块链全栈工程师 讓 ActiveRecord model 只能在特定 service class 裏被更新 早说了转语言了身体却很诚实... Rails 开发工程师--电商网站 早期程序员囤比特币,核心目的从不是投机暴富,而是看清了婚姻的本质: AI 的经济账根本算不通 坐班宁波,提供食宿,招聘运营/BD、HR Ruby 4.0.4 修复了一个性能问题 招聘客服兼职 接受小白 电商销售 Harness 工程经验分享:实现 100% 缓存命中 OpenClacky(RubyAgent)的 7 个关键决策 寻找“乡村黑客”:向着广阔的乡土出发 matz 用 claude 把 mruby 的 issues 全关了 [成都 / 远程] Python 后端工程师 - 前硅谷大厂资深工程师带队,美资 FinTech 初创投资决策平台 (15k / 月) 11 英寸 MacBook 的第二春:从零开始配置 Ruby on Rails 开发环境 OpenClacky 1.0 正式发布 —— Ruby 圈第一个通用 Agent 来了
一夜重构!我用 18000 行代码打造了完全自研的 AI TUI 终端-IFAI Cli
peterfei · 2026-04-27 · via Ruby China

作者:peterfei 发布时间:2026-04-27 阅读时间:3 分钟 难度:⭐⭐⭐⭐⭐


凌晨 3 点的挑战

深夜,电脑屏幕前的咖啡已经凉了第三遍。

我盯着一段 1200 行的 match 语句——这是 IfAI CLI 的事件处理系统。每次添加新功能,都要在 15 个文件里修修补补,单元测试覆盖率一团糟。

"这不是我想要写的代码。"我对自己说。

凌晨 3 点,我做出了一个疯狂的决定:推倒重来,一夜之间重构整个 TUI 架构

天亮之前,我做到了。


为什么选择自研 TUI?

市面上有现成的 TUI 框架(ratatui、crossterm),为什么还要自研?

因为 AI 工具的交互需求,与传统终端应用完全不同

传统 TUI 应用 AI CLI 工具
静态菜单 实时流式输出
单一模式 多模式切换(输入/搜索/帮助/审批)
简单事件 复杂事件链(键盘 + 鼠标 + 流+工具调用)
固定布局 自适应布局(欢迎页/内容/帮助覆盖层)

现有的 TUI 框架解决的是"如何渲染",但 AI 工具需要的是"如何智能交互"。

所以,我选择在 ratatui 之上,从零构建一套完整的 TUI 应用层架构


一夜之间:从混乱到优雅

重构前的痛点

// ❌ 重构前:1200 行面条式代码
fn handle_event(event: Event, app: &mut App) {
    match event {
        Event::Key(key) => match key.code {
            KeyCode::Char('c') if key.modifiers == CONTROL => {
                // Ctrl+C 处理(嵌套 if-else)
                if app.input_mode {
                    if app.search_mode {
                        // ...
                    } else {
                        // ...
                    }
                }
            }
            // ... 还有 100+ 个按键组合
        }
        Event::Mouse(mouse) => match mouse.kind {
            MouseEventKind::ScrollUp => {
                // 滚动处理(30 行嵌套逻辑)
            }
            // ...
        }
    }
}

问题

  • 添加一个搜索功能,需要改 8 个文件
  • 单元测试难以编写(所有逻辑耦合在一起)
  • 模式切换逻辑混乱(if app.search_mode && !app.input_mode

重构后的架构

// ✅ 重构后:声明式事件路由
fn build_event_router() -> EventRouter<Event> {
    EventRouter::new()
        .on(|e| matches!(e, Event::Key(_)), HelpEnterHandler)
        .on(|e| matches!(e, Event::Key(_)), HelpExitHandler)
        .on(|e| matches!(e, Event::Key(_)), SearchEnterHandler)
        .on(|e| matches!(e, Event::Key(_)), SearchInputHandler)
        .on(|e| matches!(e, Event::Key(_)), CombinedKeyHandler)
        .on(|e| matches!(e, Event::Mouse(_)), MouseScrollHandler::new())
        .fallback(IgnoreHandler)
}

优势

  • 每个处理器平均 30 行代码
  • 添加新功能只需 2 个文件改动
  • 单元测试覆盖率 100%

代码量对比: | 指标 | 重构前 | 重构后 | 改善 | |------|--------|--------|------| | 事件处理代码 | 1200 行 | 400 行 | -67% | | 单元测试覆盖 | 未知 | 404/404 通过 | 100% | | 新功能改动文件 | 15 个 | 2 个 | -87% |


完全自研的 TUI 组件

IfAI CLI 的 TUI 不是简单的 ratatui 封装,而是从应用需求出发,完全自研的组件系统。

1. 自研输入框 (input_composer.rs)

为什么不用 rustyline?因为需要与 ratatui 渲染深度集成。

pub struct InputComposer {
    buffer: String,
    cursor_pos: usize,  // 字节索引,正确处理 UTF-8
    history: Vec<String>,
    history_index: Option<usize>,
    draft_backup: String,  // 浏览历史时保存草稿
}

impl InputComposer {
    // 正确处理 CJK 字符的 Backspace
    pub fn handle_key(&mut self, key: KeyEvent) -> InputAction {
        match key.code {
            KeyCode::Backspace => {
                let prev_char_start = self.buffer[..self.cursor_pos]
                    .char_indices()
                    .last()
                    .map(|(i, _)| i)
                    .unwrap_or(0);
                self.buffer.drain(prev_char_start..self.cursor_pos);
                self.cursor_pos = prev_char_start;
            }
            // ...
        }
    }
}

// 光标列计算(CJK 字符占 2 列)
pub fn cursor_col(composer: &InputComposer) -> u16 {
    let display_width: usize = composer.buffer[..composer.cursor_pos]
        .chars()
        .map(char_width)
        .sum();
    (composer.prompt.len() + display_width + 2) as u16
}

技术亮点

  • 字节级光标位置(正确处理 UTF-8 多字节字符)
  • CJK 字符显示宽度计算
  • 历史记录草稿备份
  • 与 ratatui 的 Widget trait 集成

2. 自研欢迎页 (welcome.rs)

极简设计,无 ASCII art,启动时自动显示:

pub struct WelcomeWidget {
    title: String,
    subtitle: String,
}

impl WelcomeWidget {
    pub fn render(&self) -> Vec<Line<'static>> {
        vec![
            Line::from(""),
            Line::from(vec![
                Span::default(), /* ... 21 个 default spans 用于居中 ... */
                Span::styled(
                    "Welcome to IfAI",
                    Style::default()
                        .fg(Color::Cyan)
                        .add_modifier(Modifier::BOLD),
                ),
            ]),
            // 快捷键提示...
        ]
    }
}

实现细节

  • 空状态检测:app.is_empty() → 显示欢迎页
  • 静态字符串满足 'static 生命周期
  • 居中对齐算法(21 个 Span::default() 硬编码)

3. 自研快捷键帮助系统 (keybindings.rs)

声明式快捷键定义,分类展示:

pub struct KeybindingCategory {
    pub name: &'static str,
    pub bindings: Vec<KeyBinding>,
}

pub fn get_all_categories() -> Vec<KeybindingCategory> {
    vec![
        KeybindingCategory::new(
            "📝 输入操作",
            vec![
                KeyBinding { keys: "Enter", description: "提交输入" },
                KeyBinding { keys: "Ctrl+C", description: "清空输入 / 中断" },
            ],
        ),
        // ...
    ]
}

? 键唤起帮助覆盖层,左对齐 + 缩进布局,比 Codex 的复杂边框更易读。

4. 实时搜索高亮算法

支持大小写不敏感匹配、循环导航、三种高亮样式:

fn highlight_search_term(
    line: &str,
    query: &str,
    is_current: bool,
    is_other: bool
) -> Line<'static> {
    let mut spans = Vec::new();
    let mut last_pos = 0;

    while let Some(pos) = line[last_pos..].to_lowercase()
        .find(&query.to_lowercase()) {

        let style = if is_current {
            Style::default().fg(Color::Black).bg(Color::Yellow)
        } else if is_other {
            Style::default().fg(Color::Black).bg(Color::White)
        } else {
            Style::default().fg(Color::Yellow)
        };

        spans.push(Span::styled(&line[pos..pos + query.len()], style));
        last_pos = pos + query.len();
    }

    Line::from(spans)
}

技术要点

  • 零配置:Ctrl+F 进入搜索模式
  • 实时反馈:输入即显示
  • 循环导航:↑/Enter / ↓/Shift+Enter
  • 状态持久化:搜索结果在匹配项间切换

18000 行代码的工程奇迹

代码结构

src-tauri/src/bin/ifai/
├── main.rs                 (1065 行) — 入口 + 事件循环
├── tui.rs                  (896 行)  — TUI 核心
├── session.rs              (1788 行) — 会话管理
├── render.rs               (1148 行) — 渲染引擎
├── input_composer.rs       (661 行)  — 输入框
├── approval_overlay.rs     (777 行)  — 审批面板
├── permission_store.rs     (1078 行) — 权限存储
├── commands.rs             (1308 行) — 命令处理
├── event/
│   └── handlers.rs         (480 行)  — 事件处理器
├── token/
│   └── stream_status.rs    (795 行)  — Token 追踪
└── ... (20+ 其他模块)

总计:18013 行纯手工打造的 Rust 代码

测试覆盖

$ cargo test -p ifainew
test result: ok. 404 passed; 0 failed; 9 ignored

404 个单元测试,100% 通过率,涵盖:

  • UTF-8 字符边界处理
  • CJK 宽度计算
  • 事件处理器责任链
  • 搜索高亮算法
  • 权限规则匹配

技术亮点

1. 责任链模式事件系统

每个事件处理器都是独立的 struct,实现了 EventHandler trait:

pub trait EventHandler<E> {
    fn handle(&mut self, event: &E, app: &mut App) -> ControlFlow;
}

pub enum ControlFlow {
    Continue,  // 继续传递给下一个处理器
    Break(AppResult),  // 停止处理并返回结果
}

优势

  • 单一职责:每个处理器只做一件事
  • 可测试性:独立 struct,单元测试简单
  • 可扩展性:添加新功能只需新增处理器

2. 零拷贝渲染

使用 Line<'static> 避免字符串分配:

pub fn render(&self) -> Vec<Line<'static>> {
    vec![
        Line::from("Welcome to IfAI"),  // &'static str
        Line::from(vec![
            Span::styled("快捷键:", Style::default().fg(Color::Yellow)),
        ]),
    ]
}

3. 多模态智能切换

自动检测图片内容并切换到视觉模型:

pub fn select_model_with_multimodal_support(
    request: &StreamRequest
) -> String {
    let has_multimodal = request.messages.iter()
        .any(|msg| matches!(msg.content, MessageContent::Image { .. }));

    if has_multimodal {
        if !model.contains("4v") && !model.contains("5v") {
            return "glm-4.5v".to_string();
        }
    }

    request.model.clone()
}

4. Claude Code 风格审批系统

底部弹出面板 + 数字选项选择 + 持久化白名单:

pub enum ApprovalDecision {
    ApproveOnce,         // 本次允许
    ApproveAlways,       // 持久化白名单(Bash 工具)
    ApproveSession,      // 会话级允许(文件编辑)
    Deny,                // 拒绝
    Abort,               // 中止请求
}

持久化到 ~/.ifai/permissions.toml

[[allow]]
tool = "bash"
pattern = "git diff:*"

[[deny]]
tool = "bash"
pattern = "rm -rf /*"

通宵重构的经验总结

如果你也想挑战一夜重构,这几点经验或许有用:

✅ DO - 应该做的

  1. 写测试先行 - 元编程的 bug 往往更难调试
  2. 保持责任链简洁 - 每个处理器只做一件事
  3. 善用 IDE - Rust-Analyzer 对宏支持很好
  4. 记录设计决策 - 为第二天重构的自己留文档

❌ DON'T - 避免做的

  1. 不要过度抽象 - 表驱动不是万能药
  2. 不要忽视性能 - 元数据加载也有开销
  3. 不要忘记 Windows - 条件编译要测试
  4. 不要牺牲可读性 - 代码是写给人看的

性能数据

指标 v0.4.3 v0.4.4 改善
二进制大小 3.2 MB 3.1 MB -3%
编译时间 45s 38s -16%
内存占用 12 MB 10 MB -17%
启动时间 80ms 65ms -19%

为什么 Rust + 元编程是未来?

1. 编译时计算替代运行时开销

Rust 的宏系统 + 零成本抽象,让元编程不像反射那样拖累性能。

2. 类型安全的动态配置

表驱动设计在动态语言中很常见,但 Rust 带来了类型安全 + 内存安全的双重保障。

3. 符合 AI 时代的开发范式

当你在写 AI 代码生成工具时,元编程思维是必备技能。因为:

  • 🤖 AI 本质上是 "元"(Meta)的 - 代码生成代码
  • 📊 表驱动是 AI 配置的基础
  • 🔄 可扩展性决定了 AI 工具的上限

结语

凌晨 5 点,当最后一杯咖啡喝完,我看到测试通过的绿色输出:

test result: ok. 404 passed; 0 failed; 9 ignored

这种满足感,是任何技术热点都给不了的。

因为我知道:这不仅是一个工具,更是对未来开发范式的一次探索

IfAI CLI v0.4.4 是一次通宵重构的成果,更是对"如何构建优雅的 AI 工具"的一次回答。

完全自研的 TUI 架构,不是炫技,而是对用户体验的极致追求


试用 & 参与

如果你对 IfAI CLI 感兴趣,欢迎:

  • ⭐ GitHub:github.com/peterfei/ifai
  • brew 下载:brew install ifai
  • 🐛 提 Issue 或者回复:任何问题都有反馈
  • 💬 加微信群:和更多技术爱好者交流

如果你也想打造自己的 AI CLI,或者对元编程架构有疑问,欢迎在评论区讨论!


作者:peterfei

全栈工程师 / AI 架构师 / Rust 爱好者 / 通宵战士/ IFAI 作者

#AI 工具 #Rust 编程 #TUI 开发 #元编程 #通宵重构 #CLI 工具