18 KiB
讨论记录 (taolun.md)
本文档记录开发过程中的重要讨论,以时间轴方式存储,便于版本追溯。
使用说明
- 每次重要讨论后更新此文件
- 使用上下文压缩总结,突出重点
- 格式:时间 - 版本号 - 主题
时间轴记录
[2026-03-28 22:30] 版本 0.0.1 - 确定技术栈
原因: 项目启动,需要确定技术栈 分析:
- 比较Go、Deno+TS、Node.js+TS
- Go优势:原生二进制、性能好、CLI工具友好
- 用户不会Go但愿意学习
解决方案:
- 使用Go语言开发
- 采用面向对象设计模式
- 支持多个大模型厂商
相关决策:
- 项目结构采用
cmd/和internal/布局 - 使用YAML配置文件
- 实现工厂模式和策略模式
关联版本: changelog.md#0.0.1
[2026-03-28 23:00] 版本 0.0.1 - 设计OOP架构
原因: 用户要求面向对象开发模式 分析:
- Go不是传统OOP语言,但可通过结构体和接口实现
- 需要三个核心类:配置、厂商、翻译器
解决方案:
Config类:全局配置管理Provider接口:厂商抽象Translator类:核心翻译逻辑ProviderFactory:工厂模式创建厂商实例
相关链接:
[2026-03-28 23:30] 版本 0.0.1 - 制定开发规范
原因: 建立规范的开发流程 分析:
- 需要记录讨论过程、版本变更和知识积累
- 版本号需要遵循语义化版本规范
解决方案:
- 创建taolun.md记录讨论
- 创建changelog.md记录版本
- 创建memory.md记录知识纠正
- 版本号格式:大版本.新功能.小修复(00-99)
关联文档:
[2026-03-28 23:45] 版本 0.0.1 - 创建项目初衷文档
原因: 需要一个地方记录项目初衷和愿景 分析:
- 项目需要明确的目标和方向
- 创始人需要记录个人想法和灵感
- 与其他文档(taolun.md、changelog.md、memory.md)区分
解决方案:
- 创建
why.md文件专门记录项目初衷 - 规定只能由项目所有者编辑
- 提供基本结构建议,但不强制内容
文档规范:
- 文件位置:项目根目录
- 权限:仅用户可编辑
- 内容:项目愿景、目标、个人笔记等
关联文档:
[2026-03-28 23:50] 版本 0.0.2 - 实现核心架构
原因: 开始实现项目核心功能 分析:
- 根据OOP设计模式实现三个核心类
- 需要先实现配置加载和厂商接口
- 创建基本的CLI入口点
解决方案:
-
Config类实现:
- 支持YAML配置文件加载
- 环境变量替换
- 配置验证和默认值
-
Provider接口实现:
- 定义统一的翻译接口
- 工厂模式创建厂商实例
- 实现硅基流动厂商作为示例
-
Translator类实现:
- 核心翻译逻辑
- Prompt管理
- 超时控制
-
CLI入口点:
- 命令行参数解析
- 配置加载
- 翻译执行
技术细节:
- 使用
gopkg.in/yaml.v3处理YAML - 实现工厂模式注册机制
- 使用context处理超时和取消
- 添加基本单元测试
关联文档:
[2026-03-29 00:00] 版本 0.0.3 - 环境变量加载修复
原因: 测试CLI时发现环境变量没有正确加载 分析:
- 配置文件中使用
${ENV_VAR}语法 - Go的
os.ExpandEnv只在加载时替换 - 需要先加载.env文件到环境变量
解决方案:
- 添加
github.com/joho/godotenv依赖 - 在main函数开始时调用
godotenv.Load() - 更新memory.md记录踩坑经验
技术细节:
- godotenv会自动查找当前目录的.env文件
- 如果文件不存在会返回错误,可以忽略
- 不影响已有的环境变量
关联文档:
[2026-03-29 10:00] 版本 0.2.0 - 语言代码解析设计
原因: 用户需要通过 --lang 参数指定目标语言,支持多种语言代码格式
分析:
- 需要支持标准BCP47格式(如
zh-CN、en-US) - 需要支持简短别名(如
cn、en) - 需要支持中文名称(如
chinese、english) - 需要智能解析和错误提示
解决方案:
- 创建
internal/lang/lang.go模块 - 实现语言代码映射表和解析函数
- 支持大小写不敏感和模糊匹配
- 提供语言名称获取和建议功能
技术细节:
- 使用
map[string]string存储语言代码映射 - 实现
ParseLanguageCode()函数进行智能解析 - 支持30+种语言和变体
- 添加完整的单元测试
关联文档:
[2026-03-29 10:30] 版本 0.2.0 - onboard配置向导
原因: 用户需要友好的配置界面,特别是第一次使用时 分析:
- 需要交互式配置向导
- 需要支持选择厂商、输入API密钥、设置默认值
- 需要生成标准的YAML配置文件
- 需要支持强制重新配置
解决方案:
- 使用
github.com/AlecAivazis/survey/v2库 - 实现分步配置流程:选择厂商 → 配置厂商 → 全局设置 → 保存
- 提供友好的错误处理和用户提示
- 支持
--force参数强制重新配置
技术细节:
- 使用
survey.Select、survey.Input、survey.Confirm组件 - 实现厂商默认配置和自定义选项
- 生成完整的配置文件包含所有必要字段
- 支持配置文件存在性检查
关联文档:
[2026-03-29 11:00] 版本 0.2.0 - 分阶段迁移策略
原因: 需要平衡开发便利性和最终上线需求 分析:
- 开发阶段需要简单配置方式(
.env+configs/config.yaml) - 上线前需要迁移到用户配置目录(
~/.config/yoo/yoo.yml) - 需要平滑的迁移路径和向后兼容性
解决方案:
- 第一阶段(当前): 继续使用
.env+configs/config.yaml - 第二阶段(上线前): 实现配置文件路径查找和迁移工具
- 第三阶段(最终): 移除
.env依赖,完全使用配置文件
技术细节:
- 配置文件路径优先级:命令行 > 环境变量 > 用户目录 > 当前目录
- 保持向后兼容性,支持旧配置格式
- 提供配置验证和错误提示
- 实现配置迁移工具(计划)
关联文档:
[2026-03-29 12:00] 版本 0.4.0 - 管道符功能
原因: 用户需要与其他命令行工具联合使用 分析:
- 用户希望支持管道符功能,如
cat a.txt | yoyo | grep "who are you" - 需要检测管道输入并从stdin读取内容
- 需要控制统计信息输出,避免污染管道输出
- 需要保持向后兼容性
解决方案:
- 管道输入检测: 实现
isPipeInput()函数,使用os.Stdin.Stat()检测管道 - stdin读取: 实现
readFromStdin()函数,使用bufio.Scanner读取所有输入 - 静默模式: 添加
--quiet和-q参数,控制统计信息输出 - 输出重定向: 将统计信息输出到stderr,避免污染管道输出
技术细节:
- 使用
os.ModeCharDevice检测是否为管道设备 - 使用
strings.Join()合并多行输入为单个字符串 - 统计信息输出到
os.Stderr而不是os.Stdout - 修复 content/filter.go 中的正则表达式转义问题
使用示例:
# 基本管道功能
echo "Hello world" | yoyo
cat file.txt | yoyo --lang=en
# 静默模式
echo "Hello world" | yoyo -q
echo "Hello world" | yoyo --quiet
# 与其他命令组合
cat file.txt | yoyo | grep "你好"
yoyo "Hello" | wc -l
关联文档:
[2026-03-29 15:00] 版本 0.5.0 - 本地缓存功能设计
原因: 用户希望减少API调用,添加本地缓存功能 分析:
- 需要存储翻译结果,避免重复调用API
- 需要设计缓存键策略,确保缓存准确性
- 需要考虑数据库选择、事务处理、性能优化
- 需要设计缓存管理策略
解决方案:
-
数据库选择: 使用SQLite
- 轻量级,无需服务器
- 支持ACID事务
- Go生态支持良好 (
github.com/mattn/go-sqlite3) - 适合嵌入式应用
-
缓存键设计:
- 使用文本内容 + 源语言 + 目标语言
- 生成SHA256哈希作为缓存键
- 规范化输入:移除多余空白字符,统一语言代码格式
-
事务处理:
- 使用事务保证数据一致性
- 插入操作在事务中执行
- 查询操作不需要显式事务
-
保存时机:
- 在输出结果之前保存到数据库
- 确保数据持久化
- 异步保存,不阻塞翻译结果返回
-
性能优化:
- 为缓存键创建索引
- 使用哈希键减少存储空间
- 限制缓存表大小(可配置)
- 使用WAL模式提高并发性能
-
缓存策略:
- 采用组合策略:数量限制+时间过期
- 默认启用缓存功能
- 提供手动清理命令
-
存储位置:
- 数据库文件存储在用户配置目录
~/.config/yoyo/cache.db - 符合XDG规范
- 支持自定义路径配置
- 数据库文件存储在用户配置目录
技术细节:
- 使用
github.com/mattn/go-sqlite3驱动 - 实现
internal/cache/cache.go模块 - 缓存表结构:
id,cache_key,original_text,translated_text,from_lang,to_lang,model,prompt,created_at - 缓存键生成:
sha256(text + "|" + fromLang + "|" + toLang) - 查询缓存时使用
SELECT translated_text FROM cache WHERE cache_key = ? - 插入缓存时使用
INSERT OR IGNORE INTO cache (...) VALUES (...)
关联文档:
[2026-03-29 20:00] 版本 0.5.1 - 缓存功能修复
原因: 缓存功能测试中发现的问题 分析:
- VACUUM事务错误: 缓存清空命令中,VACUUM不能在事务中执行
- NULL值转换错误: 缓存统计查询在空表时,MIN(created_at)返回NULL导致转换错误
- 过期清理策略: 当expire_days=0时,清理逻辑不工作
解决方案:
-
修复VACUUM事务错误:
- 将VACUUM移到事务之外执行
- 先删除记录,再执行VACUUM
-
修复NULL值转换错误:
- 使用
sql.NullString和sql.NullFloat64类型 - 检查
Valid字段判断是否为NULL - 只在有记录时才查询时间范围
- 使用
-
修复过期清理策略:
- 当cleanupTTL为0时,清理所有记录
- 添加条件判断:
if c.cleanupTTL == 0 { ... }
技术细节:
// 修复VACUUM事务错误
func (c *SQLiteCache) Clear(ctx context.Context) error {
// 先删除所有记录
_, err := c.db.ExecContext(ctx, `DELETE FROM translation_cache`)
if err != nil {
return fmt.Errorf("清空缓存失败: %w", err)
}
// 然后执行VACUUM(不能在事务中执行)
_, err = c.db.ExecContext(ctx, `VACUUM`)
if err != nil {
return fmt.Errorf("清理数据库失败: %w", err)
}
return nil
}
// 修复NULL值转换错误
var oldestStr, newestStr sql.NullString
var avgTokens sql.NullFloat64
err = c.db.QueryRowContext(ctx, `SELECT MIN(created_at), MAX(created_at), AVG(total_tokens) FROM translation_cache`).Scan(&oldestStr, &newestStr, &avgTokens)
if oldestStr.Valid {
stats.OldestRecord, _ = time.Parse("2006-01-02 15:04:05", oldestStr.String)
}
关联文档:
[2026-04-06 10:00] 版本 0.6.0 - TUI界面模块拆分计划
原因: 当前TUI目录(tui/components、tui/theme)已创建但完全为空,需要从零实现终端交互界面 分析:
- 采用分模块逐步实现策略,减少Token消耗和上下文负担
- 每次只实现一个模块,完成后再进入下一个
- 讨论内容仅保存到taolun.md/changelog.md/memory.md,不新增md文件
解决方案 - TUI模块拆分:
| 步骤 | 模块 | 内容 | 预计工作量 |
|---|---|---|---|
| 1 | TUI框架搭建 | 选择库(bubbletea)、基础App结构、运行循环 | 小 |
| 2 | 输入组件 | 文本输入框、光标、基础编辑 | 中 |
| 3 | 翻译显示区 | 结果展示、格式化、滚动 | 中 |
| 4 | 状态栏/主题 | 底部状态栏、语言选择、主题配色 | 小 |
| 5 | 快捷键系统 | 退出、清空、切换语言等 | 小 |
| 6 | 集成翻译 | 对接现有Translator、加载动画 | 中 |
技术选型:
- 优先使用
charmbracelet/bubbletea(Elm架构、Go生态最流行) - 配合
charmbracelet/lipgloss实现样式和主题 - 配合
charmbracelet/bubbles/textinput实现输入框
当前状态: ✅ 模块1已完成,模块2待实现
关联文档:
[2026-04-06 10:30] 版本 0.6.0 - 模块1: TUI框架搭建 (已完成)
原因: 实现TUI界面的第一步,建立基础框架结构 分析:
- 需要创建基本的App结构和model
- 需要支持config和translator的注入
- 需要修复main.go中版本检查顺序问题
解决方案:
- 创建
internal/tui/app.go基础文件 - 定义model结构体,包含config和translator字段
- 实现Init/Update/View三个基本方法
- 添加bubbletea、bubbles、lipgloss依赖
- 修复main.go中--version在interactive之前检查
技术实现:
type model struct {
config *config.Config
translator *translator.Translator
}
func NewApp(cfg *config.Config, t *translator.Translator) *tea.Program {
return tea.NewProgram(model{...})
}
下一步: 实现模块2: 输入组件
关联文档:
[2026-04-06 11:00] 版本 0.6.0 - 模块2: 输入组件 (已完成)
原因: 实现TUI输入功能 分析:
- 使用bubbletea的textinput组件
- 需要焦点管理和键盘事件处理
解决方案:
- 添加textinput字段到model
- 初始化时设置placeholder和prompt
- Update中处理KeyMsg
- View中渲染输入框
- 支持Ctrl+C和Esc退出
技术实现:
type model struct {
textInput textinput.Model
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.Type == tea.KeyCtrlC || msg.Type == tea.KeyEsc {
return m, tea.Quit
}
}
m.textInput, cmd = m.textInput.Update(msg)
return m, cmd
}
下一步: 实现模块3: 翻译显示区
关联文档:
[2026-04-06 11:30] 版本 0.6.0 - 模块3: 翻译显示区 (已完成)
原因: 添加翻译结果显示区域 分析:
- 需要定义结果展示区域
- 需要为不同区域定义不同样式
解决方案:
- 添加result字段到model
- 定义headerStyle、resultStyle、helpStyle
- 实现renderResult()辅助方法
- View中组合各个区域
下一步: 实现模块4: 状态栏/主题
关联文档:
[2026-04-06 12:00] 版本 0.6.0 - 模块4: 状态栏/主题 (已完成)
原因: 添加底部状态栏和主题配色 分析:
- 需要显示当前目标语言
- 需要完善配色方案
- 需要定义状态栏样式
解决方案:
- 添加targetLang字段到model
- 定义statusBarStyle、langStyle等新样式
- 实现renderStatusBar()方法
- View中渲染状态栏
下一步: 实现模块5: 快捷键系统
关联文档:
[2026-04-06 12:30] 版本 0.6.0 - 模块5: 快捷键系统 (已完成)
原因: 添加键盘快捷键提升用户体验 分析:
- 需要常用操作快捷键
- 需要清晰显示快捷键提示
解决方案:
- 添加Ctrl+L: 清空输入和结果
- 添加Ctrl+T: 循环切换语言
- 添加keyStyle样式高亮快捷键
- 更新帮助提示显示所有快捷键
快捷键列表:
Ctrl+L: 清空输入框和翻译结果Ctrl+T: 循环切换目标语言 (zh-CN→en-US→ja→ko→...)Ctrl+C/Esc: 退出程序Enter: 翻译 (后续模块实现)
下一步: 实现模块6: 集成翻译
关联文档:
[2026-04-06 13:00] 版本 0.6.0 - 模块6: 集成翻译 (已完成)
原因: 将Translator集成到TUI,实现真正的翻译功能 分析:
- 需要在Enter键时调用翻译API
- 需要异步执行避免阻塞UI
- 需要显示loading状态和错误处理
解决方案:
- 添加translateMsg消息类型处理异步结果
- 添加loading和errMsg字段
- 实现doTranslate()函数执行异步翻译
- Update中处理translateMsg消息
- View中显示loading状态或错误信息
技术实现:
type translateMsg struct {
result string
err error
}
func (m model) doTranslate(text, toLang string) tea.Cmd {
return func() tea.Msg {
result, err := m.translator.Translate(...)
if err != nil {
return translateMsg{err: err}
}
return translateMsg{result: result.Translated}
}
}
下一步: 测试TUI界面、优化体验
关联文档: