Files
yoyo/taolun.md

382 lines
12 KiB
Markdown
Raw Permalink Normal View History

# 讨论记录 (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](changelog.md#001)
---
### [2026-03-28 23:00] 版本 0.0.1 - 设计OOP架构
**原因**: 用户要求面向对象开发模式
**分析**:
- Go不是传统OOP语言但可通过结构体和接口实现
- 需要三个核心类:配置、厂商、翻译器
**解决方案**:
- `Config`类:全局配置管理
- `Provider`接口:厂商抽象
- `Translator`类:核心翻译逻辑
- `ProviderFactory`:工厂模式创建厂商实例
**相关链接**:
- [AGENTS.md#OOP设计模式](AGENTS.md#oop设计模式)
- [changelog.md#0.0.1](changelog.md#001)
---
### [2026-03-28 23:30] 版本 0.0.1 - 制定开发规范
**原因**: 建立规范的开发流程
**分析**:
- 需要记录讨论过程、版本变更和知识积累
- 版本号需要遵循语义化版本规范
**解决方案**:
- 创建taolun.md记录讨论
- 创建changelog.md记录版本
- 创建memory.md记录知识纠正
- 版本号格式:大版本.新功能.小修复(00-99)
**关联文档**:
- [changelog.md#0.0.1](changelog.md#001)
- [memory.md#版本管理](memory.md#版本管理)
---
### [2026-03-28 23:45] 版本 0.0.1 - 创建项目初衷文档
**原因**: 需要一个地方记录项目初衷和愿景
**分析**:
- 项目需要明确的目标和方向
- 创始人需要记录个人想法和灵感
- 与其他文档taolun.md、changelog.md、memory.md区分
**解决方案**:
- 创建`why.md`文件专门记录项目初衷
- 规定只能由项目所有者编辑
- 提供基本结构建议,但不强制内容
**文档规范**:
- 文件位置:项目根目录
- 权限:仅用户可编辑
- 内容:项目愿景、目标、个人笔记等
**关联文档**:
- [AGENTS.md#文档管理](AGENTS.md#开发规范)
- [changelog.md#0.0.1](changelog.md#001)
---
### [2026-03-28 23:50] 版本 0.0.2 - 实现核心架构
**原因**: 开始实现项目核心功能
**分析**:
- 根据OOP设计模式实现三个核心类
- 需要先实现配置加载和厂商接口
- 创建基本的CLI入口点
**解决方案**:
1. **Config类实现**
- 支持YAML配置文件加载
- 环境变量替换
- 配置验证和默认值
2. **Provider接口实现**
- 定义统一的翻译接口
- 工厂模式创建厂商实例
- 实现硅基流动厂商作为示例
3. **Translator类实现**
- 核心翻译逻辑
- Prompt管理
- 超时控制
4. **CLI入口点**
- 命令行参数解析
- 配置加载
- 翻译执行
**技术细节**:
- 使用`gopkg.in/yaml.v3`处理YAML
- 实现工厂模式注册机制
- 使用context处理超时和取消
- 添加基本单元测试
**关联文档**:
- [AGENTS.md#OOP设计模式](AGENTS.md#oop设计模式)
- [changelog.md#0.0.2](changelog.md#002)
---
### [2026-03-29 00:00] 版本 0.0.3 - 环境变量加载修复
**原因**: 测试CLI时发现环境变量没有正确加载
**分析**:
- 配置文件中使用`${ENV_VAR}`语法
- Go的`os.ExpandEnv`只在加载时替换
- 需要先加载.env文件到环境变量
**解决方案**:
1. 添加`github.com/joho/godotenv`依赖
2. 在main函数开始时调用`godotenv.Load()`
3. 更新memory.md记录踩坑经验
**技术细节**:
- godotenv会自动查找当前目录的.env文件
- 如果文件不存在会返回错误,可以忽略
- 不影响已有的环境变量
**关联文档**:
- [memory.md#环境变量加载问题](memory.md#环境变量加载问题)
- [changelog.md#0.0.3](changelog.md#003)
---
### [2026-03-29 10:00] 版本 0.2.0 - 语言代码解析设计
**原因**: 用户需要通过 `--lang` 参数指定目标语言,支持多种语言代码格式
**分析**:
- 需要支持标准BCP47格式`zh-CN``en-US`
- 需要支持简短别名(如 `cn``en`
- 需要支持中文名称(如 `chinese``english`
- 需要智能解析和错误提示
**解决方案**:
1. 创建 `internal/lang/lang.go` 模块
2. 实现语言代码映射表和解析函数
3. 支持大小写不敏感和模糊匹配
4. 提供语言名称获取和建议功能
**技术细节**:
- 使用 `map[string]string` 存储语言代码映射
- 实现 `ParseLanguageCode()` 函数进行智能解析
- 支持30+种语言和变体
- 添加完整的单元测试
**关联文档**:
- [AGENTS.md#语言代码处理](AGENTS.md#语言代码处理)
- [changelog.md#0.2.0](changelog.md#020)
---
### [2026-03-29 10:30] 版本 0.2.0 - onboard配置向导
**原因**: 用户需要友好的配置界面,特别是第一次使用时
**分析**:
- 需要交互式配置向导
- 需要支持选择厂商、输入API密钥、设置默认值
- 需要生成标准的YAML配置文件
- 需要支持强制重新配置
**解决方案**:
1. 使用 `github.com/AlecAivazis/survey/v2`
2. 实现分步配置流程:选择厂商 → 配置厂商 → 全局设置 → 保存
3. 提供友好的错误处理和用户提示
4. 支持 `--force` 参数强制重新配置
**技术细节**:
- 使用 `survey.Select``survey.Input``survey.Confirm` 组件
- 实现厂商默认配置和自定义选项
- 生成完整的配置文件包含所有必要字段
- 支持配置文件存在性检查
**关联文档**:
- [AGENTS.md#Onboard配置向导](AGENTS.md#onboard配置向导)
- [changelog.md#0.2.0](changelog.md#020)
---
### [2026-03-29 11:00] 版本 0.2.0 - 分阶段迁移策略
**原因**: 需要平衡开发便利性和最终上线需求
**分析**:
- 开发阶段需要简单配置方式(`.env` + `configs/config.yaml`
- 上线前需要迁移到用户配置目录(`~/.config/yoo/yoo.yml`
- 需要平滑的迁移路径和向后兼容性
**解决方案**:
1. **第一阶段(当前)**: 继续使用 `.env` + `configs/config.yaml`
2. **第二阶段(上线前)**: 实现配置文件路径查找和迁移工具
3. **第三阶段(最终)**: 移除 `.env` 依赖,完全使用配置文件
**技术细节**:
- 配置文件路径优先级:命令行 > 环境变量 > 用户目录 > 当前目录
- 保持向后兼容性,支持旧配置格式
- 提供配置验证和错误提示
- 实现配置迁移工具(计划)
**关联文档**:
- [AGENTS.md#分阶段迁移策略](AGENTS.md#分阶段迁移策略)
- [changelog.md#0.2.0](changelog.md#020)
---
### [2026-03-29 12:00] 版本 0.4.0 - 管道符功能
**原因**: 用户需要与其他命令行工具联合使用
**分析**:
- 用户希望支持管道符功能,如 `cat a.txt | yoyo | grep "who are you"`
- 需要检测管道输入并从stdin读取内容
- 需要控制统计信息输出,避免污染管道输出
- 需要保持向后兼容性
**解决方案**:
1. **管道输入检测**: 实现 `isPipeInput()` 函数,使用 `os.Stdin.Stat()` 检测管道
2. **stdin读取**: 实现 `readFromStdin()` 函数,使用 `bufio.Scanner` 读取所有输入
3. **静默模式**: 添加 `--quiet``-q` 参数,控制统计信息输出
4. **输出重定向**: 将统计信息输出到stderr避免污染管道输出
**技术细节**:
- 使用 `os.ModeCharDevice` 检测是否为管道设备
- 使用 `strings.Join()` 合并多行输入为单个字符串
- 统计信息输出到 `os.Stderr` 而不是 `os.Stdout`
- 修复 content/filter.go 中的正则表达式转义问题
**使用示例**:
```bash
# 基本管道功能
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
```
**关联文档**:
- [AGENTS.md#管道符功能](AGENTS.md#管道符功能)
- [changelog.md#0.4.0](changelog.md#040)
---
### [2026-03-29 15:00] 版本 0.5.0 - 本地缓存功能设计
**原因**: 用户希望减少API调用添加本地缓存功能
**分析**:
- 需要存储翻译结果避免重复调用API
- 需要设计缓存键策略,确保缓存准确性
- 需要考虑数据库选择、事务处理、性能优化
- 需要设计缓存管理策略
**解决方案**:
1. **数据库选择**: 使用SQLite
- 轻量级,无需服务器
- 支持ACID事务
- Go生态支持良好 (`github.com/mattn/go-sqlite3`)
- 适合嵌入式应用
2. **缓存键设计**:
- 使用文本内容 + 源语言 + 目标语言
- 生成SHA256哈希作为缓存键
- 规范化输入:移除多余空白字符,统一语言代码格式
3. **事务处理**:
- 使用事务保证数据一致性
- 插入操作在事务中执行
- 查询操作不需要显式事务
4. **保存时机**:
- 在输出结果之前保存到数据库
- 确保数据持久化
- 异步保存,不阻塞翻译结果返回
5. **性能优化**:
- 为缓存键创建索引
- 使用哈希键减少存储空间
- 限制缓存表大小(可配置)
- 使用WAL模式提高并发性能
6. **缓存策略**:
- 采用组合策略:数量限制+时间过期
- 默认启用缓存功能
- 提供手动清理命令
7. **存储位置**:
- 数据库文件存储在用户配置目录 `~/.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 (...)`
**关联文档**:
- [AGENTS.md#本地缓存功能设计](AGENTS.md#本地缓存功能设计)
- [changelog.md#0.5.0](changelog.md#050)
---
### [2026-03-29 20:00] 版本 0.5.1 - 缓存功能修复
**原因**: 缓存功能测试中发现的问题
**分析**:
1. **VACUUM事务错误**: 缓存清空命令中VACUUM不能在事务中执行
2. **NULL值转换错误**: 缓存统计查询在空表时MIN(created_at)返回NULL导致转换错误
3. **过期清理策略**: 当expire_days=0时清理逻辑不工作
**解决方案**:
1. **修复VACUUM事务错误**:
- 将VACUUM移到事务之外执行
- 先删除记录再执行VACUUM
2. **修复NULL值转换错误**:
- 使用 `sql.NullString``sql.NullFloat64` 类型
- 检查 `Valid` 字段判断是否为NULL
- 只在有记录时才查询时间范围
3. **修复过期清理策略**:
- 当cleanupTTL为0时清理所有记录
- 添加条件判断:`if c.cleanupTTL == 0 { ... }`
**技术细节**:
```go
// 修复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)
}
```
**关联文档**:
- [changelog.md#0.5.1](changelog.md#051)
- [memory.md#本地缓存实现经验](memory.md#本地缓存实现经验)