Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 95338962cb | |||
| 063b14add7 | |||
| e6ff8a887c | |||
| 5b7475860a | |||
| 7a758aa549 | |||
| 13298931fd | |||
| 0a40258d9a | |||
| a9b7a69224 | |||
| 9acbc834a4 | |||
| 2ea749e0a8 |
@@ -15,8 +15,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
apk add git
|
||||
git clone -b dev https://hub.gaomia.site/titor/yoyo.git /workspace/titor/yoyo/project
|
||||
apk add git bash
|
||||
git clone https://hub.gaomia.site/titor/yoyo.git /workspace/titor/yoyo/project
|
||||
cp -r /workspace/titor/yoyo/project/* /workspace/titor/yoyo/
|
||||
cp -r /workspace/titor/yoyo/project/.* /workspace/titor/yoyo/ 2>/dev/null || true
|
||||
|
||||
@@ -25,12 +25,14 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cd /workspace/titor/yoyo
|
||||
chmod +x ./build.sh
|
||||
for p in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64; do
|
||||
os=${p%/*}
|
||||
arch=${p#*/}
|
||||
ext=""
|
||||
[ "$os" = "windows" ] && ext=".exe"
|
||||
GOOS=$os GOARCH=$arch go build -buildvcs=false -o "yoo-${os}-${arch}${ext}" ./cmd/yoyo
|
||||
./build.sh "$p" -o "yoo-${os}-${arch}${ext}"
|
||||
done
|
||||
|
||||
- name: Checksums
|
||||
@@ -40,15 +42,18 @@ jobs:
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.release_token }}
|
||||
run: |
|
||||
apk add curl
|
||||
apk add curl jq
|
||||
|
||||
TAG_NAME="${GITHUB_REF#refs/tags/}"
|
||||
|
||||
# 获取 tag 注释或 commit message 作为 release 说明
|
||||
RELEASE_BODY=$(git tag -l --format='%(contents)' "$TAG_NAME" 2>/dev/null || git log -1 --format="%s%n%n%b" "$TAG_NAME" 2>/dev/null || echo "Release $TAG_NAME")
|
||||
|
||||
# 创建 Release 并获取 release_id
|
||||
RELEASE_RESPONSE=$(curl -s -X POST "https://hub.gaomia.site/api/v1/repos/titor/yoyo/releases" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"${TAG_NAME}\",\"name\":\"${TAG_NAME}\",\"body\":\"Automated release\"}")
|
||||
-d "{\"tag_name\":\"${TAG_NAME}\",\"name\":\"${TAG_NAME}\",\"body\":\"${RELEASE_BODY}\"}")
|
||||
|
||||
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
|
||||
|
||||
|
||||
55
AGENTS.md
55
AGENTS.md
@@ -590,9 +590,58 @@ func TestTranslator_Translate(t *testing.T) {
|
||||
- 知识纠正: [memory.md](memory.md)
|
||||
|
||||
### 版本号管理
|
||||
- 格式:主版本.次版本.修订版本(00-99)
|
||||
- 更新时机:测试完成后,git操作前
|
||||
- 递增规则:小修复第三位+1,新功能第二位+1,重大变更第一位+1
|
||||
|
||||
#### 版本格式
|
||||
- 格式:`主版本.次版本.修订版本`(如 1.2.0)
|
||||
- 修订版本:00-99,超过99时递增次版本
|
||||
|
||||
#### 版本阶段定义
|
||||
- **开发版 (0.x)**: 功能开发中,可能有API变更
|
||||
- **测试版 (x.0.0-beta/N)**: 功能冻结,准备测试
|
||||
- **正式版 (1.0+)**: 稳定版,API向后兼容
|
||||
|
||||
#### 版本号含义
|
||||
| 位置 | 含义 | 何时递增 |
|
||||
|------|------|----------|
|
||||
| 主版本 | 重大架构变化/不兼容变更 | 重大重构、API不兼容 |
|
||||
| 次版本 | 新功能、向后兼容 | 添加新功能模块 |
|
||||
| 修订版本 | Bug修复、细节优化 | 小修复、样式调整 |
|
||||
|
||||
#### v1.2.0 发布标准(TUI基础功能完成)
|
||||
- ✅ TUI交互界面
|
||||
- ✅ 多行输入(textarea + Ctrl+J换行)
|
||||
- ✅ 翻译卡片展示
|
||||
- ✅ 状态栏信息
|
||||
- ✅ 帮助信息(Ctrl+H)
|
||||
- ✅ Logo版本号自动注入
|
||||
- ✅ 支持至少1个厂商(SiliconFlow)
|
||||
- ✅ 跨平台编译支持(build.sh)
|
||||
- ✅ CI自动构建Release
|
||||
|
||||
#### 分支策略(简化版)
|
||||
- **main**: 稳定分支,所有功能合并到这里
|
||||
- **feature-xxx**: 功能分支,从main创建,开发完成后合并回main
|
||||
- **合并方式**: 使用 `--no-ff` 保留分支历史
|
||||
- **流程**: feature -> 合并main -> 测试 -> 打标签 -> 推送 -> 删除feature
|
||||
|
||||
#### 标签命名规范
|
||||
- 正式版:`v1.2.3`
|
||||
- 测试版:`v1.0.0-beta`、`v1.0.0-rc1`
|
||||
|
||||
#### 版本发布流程
|
||||
1. 功能开发完成
|
||||
2. 更新 changelog.md(添加版本记录)
|
||||
3. 更新 taolun.md(添加讨论记录)
|
||||
4. 本地测试通过
|
||||
5. 合并到 main(使用 --no-ff)
|
||||
6. 创建 annotated 标签:`git tag -a v1.2.0 -m "版本说明"`
|
||||
7. 推送代码和标签:`git push && git push origin v1.2.0`
|
||||
8. CI 自动构建 Release
|
||||
|
||||
#### 禁止事项
|
||||
- 禁止删除已发布的正式版本标签
|
||||
- 禁止修改已发布的 Release 内容
|
||||
- 禁止在 main 分支直接发布正式版(需经过功能分支测试)
|
||||
|
||||
### 分支策略
|
||||
- main: 稳定上线版
|
||||
|
||||
97
build.sh
Executable file
97
build.sh
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
||||
|
||||
show_help() {
|
||||
echo "YOYO 编译脚本"
|
||||
echo ""
|
||||
echo "用法:"
|
||||
echo " ./build.sh 构建当前平台,输出 yoyo"
|
||||
echo " ./build.sh <平台> 交叉编译指定平台"
|
||||
echo " ./build.sh -h 显示帮助"
|
||||
echo ""
|
||||
echo "参数:"
|
||||
echo " -h 显示帮助信息"
|
||||
echo " -o <文件名> 指定输出文件名"
|
||||
echo ""
|
||||
echo "支持的平台:"
|
||||
echo " linux/amd64, linux/arm64"
|
||||
echo " darwin/amd64, darwin/arm64"
|
||||
echo " windows/amd64"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " ./build.sh # 构建当前平台"
|
||||
echo " ./build.sh linux/amd64 # 编译 Linux x64"
|
||||
echo " ./build.sh darwin/arm64 # 编译 macOS ARM"
|
||||
echo " ./build.sh windows/amd64 -o app.exe # 编译 Windows x64,自定义输出名"
|
||||
echo ""
|
||||
echo "输出文件名格式: yoo-<os>-<arch>[.exe]"
|
||||
echo " yoo-linux-amd64"
|
||||
echo " yoo-darwin-arm64"
|
||||
echo " yoo-windows-amd64.exe"
|
||||
}
|
||||
|
||||
OUTPUT_NAME="yoyo"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-o|--output)
|
||||
OUTPUT_NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
linux/amd64)
|
||||
GOOS=linux GOARCH=amd64
|
||||
PLATFORM="linux-amd64"
|
||||
shift
|
||||
;;
|
||||
linux/arm64)
|
||||
GOOS=linux GOARCH=arm64
|
||||
PLATFORM="linux-arm64"
|
||||
shift
|
||||
;;
|
||||
darwin/amd64)
|
||||
GOOS=darwin GOARCH=amd64
|
||||
PLATFORM="darwin-amd64"
|
||||
shift
|
||||
;;
|
||||
darwin/arm64)
|
||||
GOOS=darwin GOARCH=arm64
|
||||
PLATFORM="darwin-arm64"
|
||||
shift
|
||||
;;
|
||||
windows/amd64)
|
||||
GOOS=windows GOARCH=amd64
|
||||
CGO_ENABLED=0
|
||||
PLATFORM="windows-amd64"
|
||||
if [[ "$OUTPUT_NAME" == "yoyo" ]]; then
|
||||
OUTPUT_NAME="yoo-windows-amd64.exe"
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "未知平台: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "Building yoyo version: $VERSION"
|
||||
|
||||
BUILD_CMD="go build -buildvcs=false -ldflags \"-s -w -X github.com/titor/fanyi/internal/logo.version=${VERSION}\" -o \"$OUTPUT_NAME\" ./cmd/yoyo"
|
||||
|
||||
if [ -n "$GOOS" ]; then
|
||||
echo "Target: $PLATFORM"
|
||||
eval CGO_ENABLED=${CGO_ENABLED:-0} $BUILD_CMD
|
||||
else
|
||||
eval CGO_ENABLED=${CGO_ENABLED:-0} $BUILD_CMD
|
||||
fi
|
||||
|
||||
echo "Build complete: ./$OUTPUT_NAME"
|
||||
520
changelog.md
520
changelog.md
@@ -2,500 +2,76 @@
|
||||
|
||||
> 本文档记录所有版本变更,包含功能、修复和讨论链接。
|
||||
|
||||
## 使用说明
|
||||
- 版本号格式:`主版本.次版本.修订版本`(如 `0.0.1`)
|
||||
- 第三位为00-99,超过99时增加第二位
|
||||
- 每个版本包含指向讨论记录的链接
|
||||
## 版本号格式
|
||||
- 格式:`主版本.次版本.修订版本`(如 `1.2.0`)
|
||||
- 修订版本:00-99,超过99时递增次版本
|
||||
|
||||
## 未来架构想法
|
||||
- [ ] 支持流式翻译输出
|
||||
- [x] 添加本地缓存减少API调用 ✅ 已完成
|
||||
- [x] 添加本地缓存减少API调用 ✅
|
||||
- [ ] 实现插件系统支持自定义厂商
|
||||
- [ ] 支持批量翻译文件
|
||||
- [ ] 添加Web界面(可选)
|
||||
|
||||
## 待实现
|
||||
- [ ] 实现硅基流动厂商
|
||||
- [ ] 实现火山引擎厂商
|
||||
- [ ] 实现国家超算厂商
|
||||
- [ ] 实现Qwen厂商
|
||||
- [ ] 实现OpenAI兼容厂商
|
||||
## 待实现功能
|
||||
- [ ] 弹出框组件(通用modal)
|
||||
- [ ] 斜杠命令菜单(/ 触发命令选择器)
|
||||
- [ ] 复制功能(clipboard集成)
|
||||
- [ ] 配置文件热重载
|
||||
- [ ] 翻译历史记录
|
||||
|
||||
## 当前正实现
|
||||
- [x] TUI界面模块拆分计划 ✅ 已制定
|
||||
- [x] 模块1: TUI框架搭建 ✅ 已完成
|
||||
- [x] 模块2: 输入组件 ✅ 已完成
|
||||
- [x] 模块3: 翻译显示区 ✅ 已完成
|
||||
- [x] 模块4: 状态栏/主题 ✅ 已完成
|
||||
- [x] 模块5: 快捷键系统 ✅ 已完成
|
||||
- [x] 模块6: 集成翻译 ✅ 已完成
|
||||
|
||||
## TUI界面实现计划 (v0.6.0) - 全部完成!
|
||||
| 步骤 | 模块 | 内容 | 状态 |
|
||||
|------|------|------|------|
|
||||
| 1 | TUI框架搭建 | bubbletea基础App结构、运行循环 | ✅ 已完成 |
|
||||
| 2 | 输入组件 | 文本输入框,光标、基础编辑 | ✅ 已完成 |
|
||||
| 3 | 翻译显示区 | 结果展示、格式化、滚动 | ✅ 已完成 |
|
||||
| 4 | 状态栏/主题 | 底部状态栏、语言选择、主题配色 | ✅ 已完成 |
|
||||
| 5 | 快捷键系统 | 退出、清空、切换语言等 | ✅ 已完成 |
|
||||
| 6 | 集成翻译 | 对接现有Translator、加载动画 | ✅ 已完成 |
|
||||
|
||||
## TUI界面重构计划 (v0.8.0) - 聊天风格
|
||||
| 步骤 | 模块 | 内容 | 状态 |
|
||||
|------|------|------|------|
|
||||
| 1 | 模块结构拆分 | 创建 view/components/styles/keys 子目录 | ✅ 已完成 |
|
||||
| 2 | 消息数据结构 | ChatMessage, ChatGroup 结构定义 | ✅ 已完成 |
|
||||
| 3 | 消息列表组件 | 可滚动的消息历史展示 (viewport) | ✅ 已完成 |
|
||||
| 4 | 原文+译文样式 | 区分显示用户输入和翻译结果 | ✅ 已完成 |
|
||||
| 5 | 固定底部输入框 | textarea + Ctrl+J 换行 + 固定高度5行 | ✅ 已完成 |
|
||||
| 6 | 状态栏 | 完整信息显示 | ✅ 已完成 |
|
||||
| 7 | 翻译逻辑集成 | 对接 Translator | ✅ 已完成 |
|
||||
| 8 | 输入框背景色 | 使用 FocusedStyle/BlurredStyle 设置背景 | ✅ 已完成 |
|
||||
| 9 | 输入框修复 | 修复Ctrl+J换行后第一行被遮住的问题 | ✅ 已完成 |
|
||||
|
||||
## TUI界面改进计划 (v0.7.0)
|
||||
| 步骤 | 模块 | 内容 | 状态 |
|
||||
|------|------|------|------|
|
||||
| 7 | 多行输入 | textarea组件替换textinput | ✅ 已完成 |
|
||||
| 8 | 弹出框组件 | 通用modal组件 | ⏳ 待实现 |
|
||||
| 9 | 斜杠命令菜单 | / 触发命令选择器,模糊匹配 | ⏳ 待实现 |
|
||||
| 10 | 翻译结果滚动 | viewport组件支持长文本 | ⏳ 待实现 |
|
||||
| 11 | 复制功能 | clipboard集成 | ⏳ 待实现 |
|
||||
| 12 | 状态栏扩展 | 显示耗时、token用量 | ⏳ 待实现 |
|
||||
|
||||
## 待修复BUG
|
||||
- 无
|
||||
## TUI基础功能 (v1.2.0) ✅
|
||||
| 模块 | 内容 | 状态 |
|
||||
|------|------|------|
|
||||
| TUI框架 | bubbletea基础App结构 | ✅ |
|
||||
| 多行输入 | textarea + Ctrl+J换行 | ✅ |
|
||||
| 翻译显示 | viewport + 卡片样式 | ✅ |
|
||||
| 状态栏 | 语言/模型/记录数 | ✅ |
|
||||
| 帮助信息 | Ctrl+H切换 | ✅ |
|
||||
| 快捷键 | 退出/清空/切换语言 | ✅ |
|
||||
| 版本注入 | build.sh + ldflags | ✅ |
|
||||
| CI构建 | 自动Release | ✅ |
|
||||
|
||||
## 版本历史
|
||||
|
||||
### 0.7.0 (2026-04-06) - TUI界面改进
|
||||
**类型**: 功能版本
|
||||
**状态**: 开发中
|
||||
|
||||
**改进内容**:
|
||||
- ✅ 模块7: 多行输入 - textarea组件替换textinput
|
||||
- ✅ 模块7补充: 布局和样式优化 - 全宽/自适应高度/深色背景
|
||||
- ⏳ 模块8: 弹出框组件 - 通用modal
|
||||
- ⏳ 模块9: 斜杠命令菜单 - / 命令选择器
|
||||
- ⏳ 模块10: 翻译结果滚动 - viewport
|
||||
- ⏳ 模块11: 复制功能 - clipboard
|
||||
- ⏳ 模块12: 状态栏扩展 - 耗时/token
|
||||
|
||||
**讨论记录**:
|
||||
- [TUI界面改进计划](taolun.md#2026-04-06-1400-版本-070---tui界面改进计划)
|
||||
|
||||
**下一步**:
|
||||
- 实现模块8: 弹出框组件
|
||||
|
||||
---
|
||||
|
||||
### 0.6.0 (2026-04-06) - TUI交互界面
|
||||
**类型**: 功能版本
|
||||
**状态**: 已完成
|
||||
|
||||
**变更内容**:
|
||||
- ✅ 模块1: TUI框架搭建 - 添加bubbletea依赖,实现基础App结构
|
||||
- ✅ 模块2: 输入组件 - textinput组件、基础输入处理
|
||||
- ✅ 模块3: 翻译显示区 - 结果显示区域、样式定义
|
||||
- ✅ 模块4: 状态栏/主题 - 底部状态栏、语言显示、配色完善
|
||||
- ✅ 模块5: 快捷键系统 - Ctrl+L清空、Ctrl+T切换语言
|
||||
- ✅ 模块6: 集成翻译 - Enter触发翻译、异步执行、加载状态、错误处理
|
||||
|
||||
**技术实现**:
|
||||
- 使用 `github.com/charmbracelet/bubbletea` v1.3.10
|
||||
- 使用 `github.com/charmbracelet/bubbles` (textinput组件)
|
||||
- 使用 `github.com/charmbracelet/lipgloss` v1.1.0
|
||||
- 基础model结构: config、translator字段
|
||||
|
||||
**讨论记录**:
|
||||
- [TUI界面模块拆分计划](taolun.md#2026-04-06-1000-版本-060---tui界面模块拆分计划)
|
||||
|
||||
**下一步**:
|
||||
- 实现模块7: 多行输入
|
||||
|
||||
---
|
||||
|
||||
### 0.5.0 (2026-03-29) - 本地缓存功能
|
||||
**类型**: 功能版本
|
||||
### v1.2.0 (2026-04-08) - TUI基础功能完成
|
||||
**类型**: 正式版
|
||||
**状态**: 已发布
|
||||
|
||||
**变更内容**:
|
||||
- ✅ 添加本地缓存模块 (internal/cache/)
|
||||
- ✅ 实现SQLite缓存存储层,支持高效查询和存储
|
||||
- ✅ 实现缓存键生成策略(基于原文+语言对的SHA256哈希)
|
||||
- ✅ 修改Translator类集成缓存功能,先查缓存再调用API
|
||||
- ✅ 添加缓存配置到Config结构,支持自定义缓存参数
|
||||
- ✅ 实现缓存管理命令:cache clear, cache stats, cache cleanup
|
||||
- ✅ 添加组合缓存清理策略(数量限制+时间过期)
|
||||
- ✅ 更新配置文件模板,添加缓存配置示例
|
||||
- ✅ 更新帮助文档,添加缓存相关命令说明
|
||||
**功能完成**:
|
||||
- ✅ TUI交互界面 (bubbletea)
|
||||
- ✅ 多行输入 (textarea + Ctrl+J换行)
|
||||
- ✅ 翻译卡片展示 (viewport + 卡片样式)
|
||||
- ✅ 状态栏信息 (语言/模型/记录数)
|
||||
- ✅ 帮助信息栏 (Ctrl+H切换)
|
||||
- ✅ Logo版本号自动注入 (build.sh + ldflags)
|
||||
- ✅ 跨平台编译支持 (build.sh)
|
||||
- ✅ CI自动构建Release
|
||||
|
||||
**技术实现**:
|
||||
- 使用 `github.com/mattn/go-sqlite3` 作为SQLite驱动
|
||||
- 实现缓存接口 (Cache interface),支持多种存储后端
|
||||
- 缓存表包含完整字段:原文、译文、语言对、模型、Prompt、用量统计
|
||||
- 自动清理过期缓存和超出数量限制的缓存
|
||||
- 异步保存缓存,不阻塞翻译结果返回
|
||||
**构建改进**:
|
||||
- ✅ Windows 交叉编译添加 CGO_ENABLED=0
|
||||
|
||||
**使用示例**:
|
||||
```bash
|
||||
# 基本翻译(自动使用缓存)
|
||||
yoyo "Hello world"
|
||||
yoyo "Hello world" # 第二次调用将从缓存返回
|
||||
**构建改进**:
|
||||
- ✅ 扩展 build.sh 支持跨平台编译
|
||||
- ✅ 添加 -h 帮助选项、-o 自定义输出文件名
|
||||
- ✅ 支持 linux/darwin/windows amd64/arm64
|
||||
- ✅ 修复 CI 环境兼容性 (安装bash、添加-buildvcs=false)
|
||||
- ✅ Release 说明自动获取 tag 注释
|
||||
|
||||
# 缓存管理命令
|
||||
yoyo cache clear # 清空翻译缓存
|
||||
yoyo cache stats # 查看缓存统计信息
|
||||
yoyo cache cleanup # 清理过期缓存
|
||||
```
|
||||
|
||||
**配置示例**:
|
||||
```yaml
|
||||
cache:
|
||||
enabled: true # 是否启用缓存
|
||||
max_records: 10000 # 最大缓存记录数
|
||||
expire_days: 30 # 缓存过期天数
|
||||
db_path: "~/.config/yoyo/cache.db" # 缓存数据库文件路径
|
||||
```
|
||||
**样式优化**:
|
||||
- ✅ 翻译结果增加上方空隙 (Padding(1,3,1,3))
|
||||
- ✅ Viewport 第一个卡片添加上边距
|
||||
- ✅ 翻译卡片使用紫→青渐变Logo
|
||||
|
||||
**讨论记录**:
|
||||
- [本地缓存功能设计](taolun.md#2026-03-29-1500-版本-050-本地缓存功能设计)
|
||||
- [帮助功能和样式改进](taolun.md#2026-04-08-tui界面帮助功能与样式改进)
|
||||
- [版本号管理规则制定](taolun.md#2026-04-08-版本号管理规则制定)
|
||||
|
||||
**下一步**:
|
||||
- 完善缓存功能测试
|
||||
- 添加缓存命中率统计
|
||||
- 实现按语言清理缓存功能
|
||||
- 优化缓存性能
|
||||
**关联版本**: v1.2.0
|
||||
|
||||
---
|
||||
|
||||
### 0.5.1 (2026-03-29) - 缓存功能修复
|
||||
**类型**: 修复版本
|
||||
**状态**: 已发布
|
||||
|
||||
**变更内容**:
|
||||
- ✅ 修复缓存清空命令中的VACUUM事务错误
|
||||
- ✅ 修复缓存统计中的NULL值处理错误
|
||||
- ✅ 修复缓存过期清理策略,支持expire_days=0时清理所有记录
|
||||
- ✅ 添加缓存模块单元测试
|
||||
- ✅ 更新版本号到v0.5.1
|
||||
|
||||
**修复问题**:
|
||||
- 缓存清空命令执行时出现"cannot VACUUM from within a transaction"错误
|
||||
- 缓存统计查询在空表时出现NULL值转换错误
|
||||
- 缓存过期清理策略在expire_days=0时不工作
|
||||
|
||||
**测试结果**:
|
||||
- 所有缓存模块测试通过
|
||||
- 缓存命令功能正常
|
||||
- 缓存集成功能正常
|
||||
|
||||
**下一步**:
|
||||
- 完善缓存功能测试
|
||||
- 添加缓存命中率统计
|
||||
- 实现按语言清理缓存功能
|
||||
- 优化缓存性能
|
||||
|
||||
---
|
||||
|
||||
### 0.4.0 (2026-03-29) - 管道符功能
|
||||
**类型**: 功能版本
|
||||
**状态**: 已发布
|
||||
|
||||
**变更内容**:
|
||||
- ✅ 添加管道符支持,允许与其他命令行工具联合使用
|
||||
- ✅ 实现管道输入检测 (isPipeInput函数)
|
||||
- ✅ 实现从标准输入读取 (readFromStdin函数)
|
||||
- ✅ 添加 --quiet 和 -q 参数控制统计信息输出
|
||||
- ✅ 更新帮助文档,添加管道使用示例
|
||||
- ✅ 修复 content/filter.go 中的正则表达式错误
|
||||
- ✅ 更新版本号到 v0.3.0
|
||||
|
||||
**使用示例**:
|
||||
```bash
|
||||
# 管道翻译文件
|
||||
cat file.txt | yoyo
|
||||
cat file.txt | yoyo --lang=en
|
||||
|
||||
# 管道翻译命令输出
|
||||
echo "Hello world" | yoyo --lang=cn
|
||||
|
||||
# 静默模式,只输出翻译结果
|
||||
echo "Hello world" | yoyo -q
|
||||
|
||||
# 与其他命令组合使用
|
||||
cat file.txt | yoyo | grep "关键词"
|
||||
yoyo "Hello" | wc -l
|
||||
```
|
||||
|
||||
**讨论记录**:
|
||||
- [管道符功能设计](taolun.md#管道符功能设计)
|
||||
|
||||
**下一步**:
|
||||
- 实现更多厂商(火山引擎、国家超算、Qwen、OpenAI兼容)
|
||||
- 添加配置文件路径查找机制
|
||||
- 实现配置文件迁移工具
|
||||
- 完善错误处理和用户体验
|
||||
|
||||
### 0.3.0 (2026-03-29) - 内容过滤与代码处理
|
||||
**类型**: 功能版本
|
||||
**状态**: 已发布
|
||||
|
||||
**变更内容**:
|
||||
- ✅ 添加内容过滤模块 (internal/content/)
|
||||
- ✅ 实现基础字符过滤(移除控制字符、规范化换行符、截断超长符号)
|
||||
- ✅ 实现代码块和行内代码识别
|
||||
- ✅ 实现代码注释智能识别(支持 JS/TS/Java/Python/Go/HTML 等 30+ 语言)
|
||||
- ✅ 添加 go-enry 依赖实现编程语言智能检测
|
||||
- ✅ 添加 SkipKeywords 配置项,默认保留 TODO/FIXME/HACK 等关键词不翻译
|
||||
- ✅ 集成内容处理到 Translator 模块
|
||||
|
||||
**新增文件**:
|
||||
- `internal/content/content.go` - 模块入口
|
||||
- `internal/content/filter.go` - 基础字符过滤
|
||||
- `internal/content/parser.go` - 内容解析器和语言检测
|
||||
|
||||
**配置更新**:
|
||||
- `configs/config.yaml` 新增 `skip_keywords` 配置项
|
||||
- 支持用户自定义不翻译的关键词列表
|
||||
|
||||
**使用示例**:
|
||||
```bash
|
||||
# 翻译包含代码的文档,自动识别代码和注释
|
||||
yoyo "这是一个文档 ```js // TODO: fix this ```"
|
||||
# 代码块保持不变,只翻译注释中的词汇
|
||||
# TODO: 修复这个
|
||||
```
|
||||
|
||||
**讨论记录**:
|
||||
- [内容过滤与代码处理设计](taolun.md#内容过滤与代码处理设计)
|
||||
|
||||
**下一步**:
|
||||
- 实现更多厂商(火山引擎、国家超算、Qwen、OpenAI兼容)
|
||||
- 添加配置文件路径查找机制
|
||||
- 实现配置文件迁移工具
|
||||
- 完善错误处理和用户体验
|
||||
|
||||
### 0.2.0 (2026-03-29) - 语言支持和配置向导
|
||||
**类型**: 功能版本
|
||||
**状态**: 开发中
|
||||
|
||||
**变更内容**:
|
||||
- ✅ 添加语言代码智能解析模块 (internal/lang)
|
||||
- ✅ 支持 `--lang` 参数指定目标语言
|
||||
- ✅ 支持多种语言代码格式(标准BCP47、别名、中文名称)
|
||||
- ✅ 实现 onboard 交互式配置向导
|
||||
- ✅ 更新配置结构添加语言字段
|
||||
- ✅ 添加 survey 库依赖用于交互式界面
|
||||
- ✅ 改进CLI命令行接口
|
||||
- ✅ 添加语言模块单元测试
|
||||
|
||||
**新增文件**:
|
||||
- `internal/lang/lang.go` - 语言代码解析模块
|
||||
- `internal/lang/lang_test.go` - 语言模块测试
|
||||
- `internal/onboard/onboard.go` - 配置向导实现
|
||||
|
||||
**支持的语言代码**:
|
||||
- 标准格式: zh-CN, zh-TW, en-US, en-GB, ja, ko, es, fr, de 等
|
||||
- 简短别名: cn(中文), en(英文), jp(日文), kr(韩文) 等
|
||||
- 中文名称: chinese(中文), english(英文), japanese(日文) 等
|
||||
|
||||
**使用示例**:
|
||||
```bash
|
||||
# 基本翻译
|
||||
yoyo "Hello world"
|
||||
yoyo --lang=cn "Hello world"
|
||||
yoyo --lang=en "你好世界"
|
||||
yoyo --lang=zh-TW "Hello world"
|
||||
|
||||
# 配置向导
|
||||
yoyo onboard
|
||||
yoyo onboard --force
|
||||
```
|
||||
|
||||
**讨论记录**:
|
||||
- [语言代码解析设计](taolun.md#语言代码解析设计)
|
||||
- [onboard配置向导](taolun.md#onboard配置向导)
|
||||
|
||||
**下一步**:
|
||||
- 实现更多厂商(火山引擎、国家超算、Qwen、OpenAI兼容)
|
||||
- 添加配置文件路径查找机制
|
||||
- 实现配置文件迁移工具
|
||||
- 完善错误处理和用户体验
|
||||
|
||||
### 0.0.3 (2026-03-29) - 环境变量加载修复
|
||||
**类型**: 修复版本
|
||||
**状态**: 开发中
|
||||
|
||||
**变更内容**:
|
||||
- ✅ 修复环境变量加载问题
|
||||
- ✅ 添加godotenv依赖
|
||||
- ✅ 更新memory.md记录踩坑经验
|
||||
- ✅ 测试CLI基本功能
|
||||
|
||||
**讨论记录**:
|
||||
- [环境变量加载修复](taolun.md#2026-03-29-0000-版本-003-环境变量加载修复)
|
||||
|
||||
**下一步**:
|
||||
- 实现更多厂商
|
||||
- 添加更多测试
|
||||
- 完善错误处理
|
||||
|
||||
### 0.0.2 (2026-03-28) - 核心架构实现
|
||||
**类型**: 功能版本
|
||||
**状态**: 开发中
|
||||
|
||||
**变更内容**:
|
||||
- ✅ 实现Config配置类(internal/config/config.go)
|
||||
- ✅ 实现Provider接口和工厂模式(internal/provider/)
|
||||
- ✅ 实现硅基流动厂商(internal/provider/siliconflow.go)
|
||||
- ✅ 实现Translator核心翻译类(internal/translator/)
|
||||
- ✅ 实现Prompt管理器(internal/translator/prompt.go)
|
||||
- ✅ 创建CLI入口点(cmd/yoyo/main.go)
|
||||
- ✅ 添加配置文件模板(configs/config.yaml)
|
||||
- ✅ 添加单元测试(internal/config/config_test.go)
|
||||
- ✅ 初始化Git仓库和版本标签
|
||||
|
||||
**讨论记录**:
|
||||
- [实现核心架构](taolun.md#2026-03-28-2350-版本-002-实现核心架构)
|
||||
|
||||
**下一步**:
|
||||
- 实现其他厂商(火山引擎、国家超算、Qwen、OpenAI兼容)
|
||||
- 添加更多测试
|
||||
- 实现批量翻译功能
|
||||
- 添加翻译历史记录
|
||||
- 实现配置文件热重载
|
||||
|
||||
### 0.0.1 (2026-03-28) - 项目初始化
|
||||
**类型**: 初始化版本
|
||||
**状态**: 开发中
|
||||
|
||||
**变更内容**:
|
||||
- ✅ 确定技术栈为Go语言
|
||||
- ✅ 设计OOP架构(Config、Provider、Translator)
|
||||
- ✅ 制定开发规范(taolun.md、changelog.md、memory.md、why.md)
|
||||
- ✅ 确定分支策略(main、dev)
|
||||
- ✅ 设计项目结构
|
||||
- ✅ 创建项目初衷文档(why.md)
|
||||
|
||||
**讨论记录**:
|
||||
- [确定技术栈](taolun.md#2026-03-28-2230-版本-001-确定技术栈)
|
||||
- [设计OOP架构](taolun.md#2026-03-28-2300-版本-001-设计oop架构)
|
||||
- [制定开发规范](taolun.md#2026-03-28-2330-版本-001-制定开发规范)
|
||||
- [创建项目初衷文档](taolun.md#2026-03-28-2345-版本-001-创建项目初衷文档)
|
||||
|
||||
**下一步**:
|
||||
- 创建项目目录结构
|
||||
- 初始化Go模块
|
||||
- 实现Config类
|
||||
- 实现Provider接口
|
||||
- 用户填写why.md内容
|
||||
|
||||
---
|
||||
|
||||
## 版本号管理规则
|
||||
|
||||
### 版本号格式
|
||||
`主版本.次版本.修订版本`(例如:`1.2.3`)
|
||||
|
||||
### 更新规则
|
||||
1. **主版本**(第一位):重大架构变更、不兼容更新
|
||||
2. **次版本**(第二位):新功能、重要特性
|
||||
3. **修订版本**(第三位):小修复、优化(00-99)
|
||||
|
||||
### 更新流程
|
||||
1. 完成开发并测试
|
||||
2. 更新changelog.md
|
||||
3. 更新taolun.md(如有讨论)
|
||||
4. 更新memory.md(如有新知识)
|
||||
5. 更新版本号
|
||||
6. 提交到dev分支
|
||||
7. 测试通过后合并到main
|
||||
8. 创建版本标签:`git tag v0.0.1`
|
||||
|
||||
### 示例版本递增
|
||||
- `0.0.1` → `0.0.2`:小修复
|
||||
- `0.0.99` → `0.1.0`:新功能(修订版本溢出)
|
||||
- `1.2.3` → `2.0.0`:重大架构变更
|
||||
|
||||
---
|
||||
|
||||
## v0.8.1 (2026-04-07)
|
||||
|
||||
### 新功能
|
||||
- 使用 lipgloss v2 设计翻译结果卡片组件
|
||||
- 卡片包含三部分:元信息行(Tokens/耗时/模型)、用户输入(碳黑背景)、翻译结果
|
||||
|
||||
### 升级
|
||||
- 升级 `charm.land/lipgloss/v2` v1.1.0 → v2.0.2
|
||||
- 升级 `charm.land/bubbletea/v2` v1.3.10 → v2.0.2
|
||||
- 升级 `charm.land/bubbles/v2` → v2.1.0
|
||||
- 更新所有模块路径为 `charm.land/xxx/v2` 格式
|
||||
|
||||
### 技术细节
|
||||
- 背景色: #1A1A1A (碳黑色)
|
||||
- 用户输入区域无边框,纯背景色
|
||||
- 组件间距: 5px marginBottom
|
||||
- View() 方法返回 `tea.View` 类型
|
||||
- KeyMsg 改为 KeyPressMsg,使用 `msg.String()` 判断键位
|
||||
|
||||
**讨论记录**: [taolun.md#版本-0.8.1-翻译结果卡片组件设计](taolun.md#版本-081---翻译结果卡片组件设计)
|
||||
|
||||
---
|
||||
|
||||
## v1.0.0-beta (2026-04-07)
|
||||
|
||||
### 新功能
|
||||
- ASCII艺术Logo标题,带紫色→粉色渐变效果
|
||||
- 输入框改造:
|
||||
- 使用 `:::` 紫色分隔符替代上下边框
|
||||
- Ctrl+J 换行功能
|
||||
- 信息栏改造:
|
||||
- 合并显示:语言、模型名、缓存记录数
|
||||
- 添加翻译状态 Spinner 动画 (MiniDot)
|
||||
- 翻译结果卡片优化:
|
||||
- 底部 `▣` 图标左侧边距从3减少到2
|
||||
- `▣` 与文字间距从3减少到2
|
||||
|
||||
### 技术细节
|
||||
- 使用 lipgloss 实现 True Color 渐变效果
|
||||
- 使用 charmbracelet/bubbles spinner 组件实现加载动画
|
||||
- 版本号显示在Logo右侧 [v1.0.0-beta]
|
||||
- 动态调整 viewport 高度适应终端
|
||||
|
||||
### 版本号规则
|
||||
- 版本号需与 git 标签、changelog.md 中的版本号保持三方同步
|
||||
|
||||
**讨论记录**: [taolun.md#版本-100-beta-Logo和信息栏改造](taolun.md#版本-100-beta-logo和信息栏改造)
|
||||
|
||||
---
|
||||
|
||||
## v1.1.0 (2026-04-07)
|
||||
|
||||
### BUG修复
|
||||
- 修复配置文件路径使用相对路径导致管道模式下无法找到配置的问题
|
||||
- 修复onboard配置保存到错误路径的问题
|
||||
- 修复.env文件只在当前目录加载的问题
|
||||
|
||||
### 新功能
|
||||
- 配置文件路径智能解析:`~/.config/yoyo/config.yaml` → `./configs/config.yaml`
|
||||
- onboard配置向导迁移到 `charm.land/huh/v2`,替代 `survey` 库
|
||||
- 新增路径解析工具 `internal/config/path.go`
|
||||
|
||||
### 改进
|
||||
- 配置查找优先级:`--config` 参数 > `~/.config/yoyo/config.yaml` > `./configs/config.yaml`
|
||||
- 配置文件统一保存到 `~/.config/yoyo/config.yaml`(符合XDG规范)
|
||||
- .env文件统一从 `~/.config/yoyo/.env` 加载
|
||||
- onboard使用huh的Form+Group模式,更美观的交互体验
|
||||
- 移除 `github.com/AlecAivazis/survey/v2` 依赖
|
||||
|
||||
### 技术细节
|
||||
- 新增 `config.ResolveConfigPath()` 函数处理路径解析
|
||||
- 新增 `config.GetUserConfigPath()` 返回标准配置路径
|
||||
- 新增 `config.GetUserEnvPath()` 返回标准环境变量路径
|
||||
- 支持 `~` 路径展开
|
||||
- huh使用v2版本,支持泛型和链式API
|
||||
|
||||
**讨论记录**: [taolun.md#2026-04-07-配置路径修复和huh迁移](taolun.md)
|
||||
- 实现弹出框组件
|
||||
- 实现斜杠命令菜单
|
||||
- 添加复制功能
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/titor/fanyi/internal/cache"
|
||||
"github.com/titor/fanyi/internal/config"
|
||||
"github.com/titor/fanyi/internal/lang"
|
||||
"github.com/titor/fanyi/internal/logo"
|
||||
"github.com/titor/fanyi/internal/onboard"
|
||||
"github.com/titor/fanyi/internal/provider"
|
||||
"github.com/titor/fanyi/internal/translator"
|
||||
@@ -25,6 +26,7 @@ var (
|
||||
version = flag.Bool("version", false, "显示版本信息")
|
||||
help = flag.Bool("help", false, "显示帮助信息")
|
||||
h = flag.Bool("h", false, "显示帮助信息")
|
||||
question = flag.Bool("?", false, "显示帮助信息")
|
||||
langFlag = flag.String("lang", "", "目标语言代码(如 zh-CN, en-US, cn, en 等)")
|
||||
langLong = flag.String("language", "", "目标语言代码(--lang的长格式)")
|
||||
configFile = flag.String("config", "", "配置文件路径")
|
||||
@@ -38,8 +40,6 @@ var (
|
||||
interactiveShort = flag.Bool("i", false, "启动交互式翻译界面(-i的短格式)")
|
||||
)
|
||||
|
||||
const versionString = "YOYO翻译工具 v1.1.0"
|
||||
|
||||
// isPipeInput 检测是否有管道输入
|
||||
func isPipeInput() bool {
|
||||
fileInfo, err := os.Stdin.Stat()
|
||||
@@ -104,6 +104,17 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if *help || *h || *question {
|
||||
printHelp()
|
||||
return
|
||||
}
|
||||
|
||||
// 处理 -? 作为位置参数的情况
|
||||
if flag.NArg() > 0 && (flag.Arg(0) == "-?" || flag.Arg(0) == "?") {
|
||||
printHelp()
|
||||
return
|
||||
}
|
||||
|
||||
// 处理交互式模式
|
||||
if *interactive || *interactiveShort || shouldStartInteractive() {
|
||||
startInteractiveMode()
|
||||
@@ -112,14 +123,8 @@ func main() {
|
||||
|
||||
// 处理管道输入情况
|
||||
if isPipeInput() {
|
||||
// 管道模式下,即使没有参数也继续执行
|
||||
if *help || *h {
|
||||
printHelp()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 非管道模式下,没有参数则显示帮助
|
||||
if *help || *h || flag.NArg() == 0 {
|
||||
// 管道模式下,没有参数则显示帮助
|
||||
if flag.NArg() == 0 {
|
||||
printHelp()
|
||||
return
|
||||
}
|
||||
@@ -372,12 +377,14 @@ func runCacheCommand(subcommand string) {
|
||||
|
||||
// printVersion 显示版本信息
|
||||
func printVersion() {
|
||||
fmt.Println(versionString)
|
||||
logo.PrintLogoWithVersion()
|
||||
}
|
||||
|
||||
// printHelp 显示帮助信息
|
||||
func printHelp() {
|
||||
fmt.Printf(`%s
|
||||
logo.PrintLogoWithVersion()
|
||||
|
||||
fmt.Printf(`
|
||||
|
||||
使用方法:
|
||||
yoyo [选项] <文本>
|
||||
@@ -445,8 +452,8 @@ func printHelp() {
|
||||
- 默认厂商: siliconflow
|
||||
- 默认目标语言: zh-CN (简体中文)
|
||||
|
||||
更多信息请访问: https://github.com/titor/fanyi
|
||||
`, versionString)
|
||||
更多信息请访问: https://github.com/titor/fanyi
|
||||
`)
|
||||
}
|
||||
|
||||
// shouldStartInteractive 判断是否应该启动交互式模式
|
||||
|
||||
79
internal/logo/logo.go
Normal file
79
internal/logo/logo.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package logo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var version string
|
||||
|
||||
func SetVersion(v string) { version = v }
|
||||
|
||||
func GetVersion() string { return version }
|
||||
|
||||
func GradientText(text string, startColor, endColor string) string {
|
||||
startR, startG, startB := parseHexColor(startColor)
|
||||
endR, endG, endB := parseHexColor(endColor)
|
||||
|
||||
lines := strings.Split(text, "\n")
|
||||
if len(lines) == 0 {
|
||||
return text
|
||||
}
|
||||
|
||||
var result []string
|
||||
for _, line := range lines {
|
||||
if len(line) == 0 {
|
||||
result = append(result, line)
|
||||
continue
|
||||
}
|
||||
|
||||
var coloredLine string
|
||||
for i, char := range line {
|
||||
ratio := float64(i) / float64(len(line)-1)
|
||||
r := int(float64(startR) + float64(endR-startR)*ratio)
|
||||
g := int(float64(startG) + float64(endG-startG)*ratio)
|
||||
b := int(float64(startB) + float64(endB-startB)*ratio)
|
||||
coloredLine += fmt.Sprintf("\033[38;2;%d;%d;%dm%s\033[0m", r, g, b, string(char))
|
||||
}
|
||||
result = append(result, coloredLine)
|
||||
}
|
||||
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
func parseHexColor(hex string) (int, int, int) {
|
||||
hex = strings.TrimPrefix(hex, "#")
|
||||
if len(hex) != 6 {
|
||||
return 0, 0, 0
|
||||
}
|
||||
var r, g, b int
|
||||
fmt.Sscanf(hex, "%02x%02x%02x", &r, &g, &b)
|
||||
return r, g, b
|
||||
}
|
||||
|
||||
func GetLogoPattern() string {
|
||||
return " _ _ _____ _____ " + "\n" +
|
||||
"( \\/ ( _ ( _ ) " + "\n" +
|
||||
" \\ / )(_)( )(_)( " + "\n" +
|
||||
" (__)(_____(_____("
|
||||
}
|
||||
|
||||
func GetVersionSuffix() string {
|
||||
v := GetVersion()
|
||||
if v != "" {
|
||||
return " (" + v + " )"
|
||||
}
|
||||
return " ( )"
|
||||
}
|
||||
|
||||
func PrintLogoWithVersion() {
|
||||
logoPattern := " _ _ _____ _____\n" +
|
||||
"( \\/ ( _ ( _ )\n" +
|
||||
" \\ / )(_)( )(_)( \n" +
|
||||
" (__)(_____(_____("
|
||||
|
||||
patternWithVersion := logoPattern + GetVersionSuffix()
|
||||
|
||||
colored := GradientText(patternWithVersion, "#B413DC", "#00C8C8")
|
||||
fmt.Println(colored)
|
||||
}
|
||||
@@ -12,6 +12,7 @@ type KeyMap struct {
|
||||
ScrollDown key.Binding
|
||||
ScrollTop key.Binding
|
||||
ScrollBottom key.Binding
|
||||
Help key.Binding
|
||||
}
|
||||
|
||||
func NewKeyMap() KeyMap {
|
||||
@@ -44,5 +45,21 @@ func NewKeyMap() KeyMap {
|
||||
key.WithKeys("end"),
|
||||
key.WithHelp("End", "底部"),
|
||||
),
|
||||
Help: key.NewBinding(
|
||||
key.WithKeys("ctrl+h"),
|
||||
key.WithHelp("Ctrl+H", "帮助"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (k KeyMap) ShortHelp() []key.Binding {
|
||||
return []key.Binding{k.Help, k.Quit}
|
||||
}
|
||||
|
||||
func (k KeyMap) FullHelp() [][]key.Binding {
|
||||
return [][]key.Binding{
|
||||
{k.Quit, k.Clear, k.SwitchLang},
|
||||
{k.ScrollUp, k.ScrollDown, k.ScrollTop, k.ScrollBottom},
|
||||
{k.Help},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,23 +7,20 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"charm.land/bubbles/v2/help"
|
||||
"charm.land/bubbles/v2/key"
|
||||
"charm.land/bubbles/v2/spinner"
|
||||
"charm.land/bubbles/v2/textarea"
|
||||
"charm.land/bubbles/v2/viewport"
|
||||
"charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/titor/fanyi/internal/config"
|
||||
"github.com/titor/fanyi/internal/content"
|
||||
"github.com/titor/fanyi/internal/logo"
|
||||
"github.com/titor/fanyi/internal/translator"
|
||||
)
|
||||
|
||||
var supportedLangs = []string{"zh-CN", "en-US", "ja", "ko", "zh-TW", "es", "fr", "de"}
|
||||
|
||||
var logoPattern = "l_ _ _____ _____ " + "\n" +
|
||||
"( \\/ ( _ ( _ ) " + "\n" +
|
||||
" \\ / )(_)( )(_)( " + "\n" +
|
||||
" (__)(_____(_____((() [v" + content.Version + "]"
|
||||
|
||||
type translateMsg struct {
|
||||
result string
|
||||
tokens int
|
||||
@@ -38,6 +35,7 @@ type model struct {
|
||||
input textarea.Model
|
||||
viewport viewport.Model
|
||||
spinner spinner.Model
|
||||
help help.Model
|
||||
keys KeyMap
|
||||
|
||||
targetLang string
|
||||
@@ -72,16 +70,22 @@ func NewApp(cfg *config.Config, t *translator.Translator) *tea.Program {
|
||||
sp.Spinner = spinner.MiniDot
|
||||
sp.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("#8B5CF6"))
|
||||
|
||||
return tea.NewProgram(model{
|
||||
hp := help.New()
|
||||
|
||||
m := model{
|
||||
config: cfg,
|
||||
translator: t,
|
||||
messages: make([]ChatMessage, 0),
|
||||
input: ta,
|
||||
viewport: vp,
|
||||
spinner: sp,
|
||||
help: hp,
|
||||
keys: keys,
|
||||
targetLang: getDefaultLang(cfg),
|
||||
})
|
||||
}
|
||||
|
||||
p := tea.NewProgram(m)
|
||||
return p
|
||||
}
|
||||
|
||||
func getDefaultLang(cfg *config.Config) string {
|
||||
@@ -105,6 +109,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
m.help.SetWidth(msg.Width)
|
||||
m.updateLayout()
|
||||
|
||||
case translateMsg:
|
||||
@@ -142,6 +147,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case "end":
|
||||
m.viewport.GotoBottom()
|
||||
}
|
||||
if key.Matches(msg, m.keys.Help) {
|
||||
m.help.ShowAll = !m.help.ShowAll
|
||||
}
|
||||
}
|
||||
|
||||
m.input, cmd = m.input.Update(msg)
|
||||
@@ -211,7 +219,12 @@ func (m *model) updateLayout() {
|
||||
|
||||
m.input.SetWidth(contentWidth)
|
||||
m.viewport.SetWidth(contentWidth)
|
||||
m.viewport.SetHeight(m.height - 12)
|
||||
|
||||
helpLines := 2
|
||||
if m.help.ShowAll {
|
||||
helpLines = 4
|
||||
}
|
||||
m.viewport.SetHeight(m.height - 12 - helpLines)
|
||||
if m.viewport.Height() < 5 {
|
||||
m.viewport.SetHeight(10)
|
||||
}
|
||||
@@ -222,6 +235,10 @@ func (m *model) updateLayout() {
|
||||
func (m *model) updateViewportContent() {
|
||||
var b strings.Builder
|
||||
|
||||
if len(m.messages) > 0 {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
for _, msg := range m.messages {
|
||||
b.WriteString(m.renderTranslationCard(msg))
|
||||
}
|
||||
@@ -257,7 +274,7 @@ func (m *model) renderTranslationCard(msg ChatMessage) string {
|
||||
Render(msg.Error)
|
||||
outputBlock = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#F87171")).
|
||||
Padding(0, 3, 1, 3).
|
||||
Padding(1, 3, 1, 3).
|
||||
Width(m.viewport.Width()).
|
||||
Render(outputContent)
|
||||
} else {
|
||||
@@ -265,7 +282,7 @@ func (m *model) renderTranslationCard(msg ChatMessage) string {
|
||||
Width(m.viewport.Width() - 2).
|
||||
Render(msg.Output)
|
||||
outputBlock = lipgloss.NewStyle().
|
||||
Padding(0, 3, 1, 3).
|
||||
Padding(1, 3, 1, 3).
|
||||
Width(m.viewport.Width()).
|
||||
Render(outputContent)
|
||||
}
|
||||
@@ -304,67 +321,26 @@ func (m model) View() tea.View {
|
||||
messages := m.viewport.View()
|
||||
inputArea := m.renderInputArea()
|
||||
infoBar := m.renderInfoBar()
|
||||
spinnerView := m.renderSpinner()
|
||||
helpView := m.help.View(m.keys)
|
||||
|
||||
content := header + "\n" + messages + inputArea + infoBar + spinnerView
|
||||
content := header + "\n" + messages + inputArea + infoBar + helpView
|
||||
v := tea.NewView(content)
|
||||
v.AltScreen = true
|
||||
return v
|
||||
}
|
||||
|
||||
func gradientText(text string, startColor, endColor string) string {
|
||||
startR, startG, startB := parseHexColor(startColor)
|
||||
endR, endG, endB := parseHexColor(endColor)
|
||||
|
||||
lines := strings.Split(text, "\n")
|
||||
if len(lines) == 0 {
|
||||
return text
|
||||
}
|
||||
|
||||
var result []string
|
||||
for _, line := range lines {
|
||||
if len(line) == 0 {
|
||||
result = append(result, line)
|
||||
continue
|
||||
}
|
||||
|
||||
var coloredLine string
|
||||
for i, char := range line {
|
||||
ratio := float64(i) / float64(len(line)-1)
|
||||
r := int(float64(startR) + float64(endR-startR)*ratio)
|
||||
g := int(float64(startG) + float64(endG-startG)*ratio)
|
||||
b := int(float64(startB) + float64(endB-startB)*ratio)
|
||||
coloredLine += fmt.Sprintf("\033[38;2;%d;%d;%dm%s\033[0m", r, g, b, string(char))
|
||||
}
|
||||
result = append(result, coloredLine)
|
||||
}
|
||||
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
func parseHexColor(hex string) (int, int, int) {
|
||||
hex = strings.TrimPrefix(hex, "#")
|
||||
r, _ := strconv.ParseInt(hex[0:2], 16, 64)
|
||||
g, _ := strconv.ParseInt(hex[2:4], 16, 64)
|
||||
b, _ := strconv.ParseInt(hex[4:6], 16, 64)
|
||||
return int(r), int(g), int(b)
|
||||
}
|
||||
|
||||
func (m model) renderHeader() string {
|
||||
title := gradientText(logoPattern, "#8B5CF6", "#EC4899")
|
||||
title := logo.GradientText(logo.GetLogoPattern(), "#B413DC", "#00C8C8")
|
||||
titleWithVersion := title + logo.GetVersionSuffix()
|
||||
|
||||
width := m.width - 4
|
||||
if width < 20 {
|
||||
width = 60
|
||||
}
|
||||
|
||||
right := lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#6B7280")).
|
||||
Render("[Ctrl+C 退出]")
|
||||
|
||||
return lipgloss.NewStyle().
|
||||
Width(width).
|
||||
Render(title + strings.Repeat(" ", width-29-len(right)-1) + right)
|
||||
Render(titleWithVersion)
|
||||
}
|
||||
|
||||
func (m model) renderInputArea() string {
|
||||
|
||||
93
memory.md
93
memory.md
@@ -894,4 +894,95 @@ v2:
|
||||
```go
|
||||
p := tea.NewProgram(model{})
|
||||
p.Run()
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Logo模块化设计
|
||||
|
||||
**决策**: 创建 `internal/logo/logo.go` 统一管理logo,TUI和CLI共享
|
||||
|
||||
**原因**:
|
||||
1. 避免代码重复:TUI和CLI都需要显示logo
|
||||
2. 统一渐变色方案:紫→青 (`#B413DC` → `#00C8C8`)
|
||||
3. 统一版本号格式:` ( v1.x.x )` 或 ` ( )`
|
||||
|
||||
**实现**:
|
||||
```go
|
||||
// 导出函数供外部调用
|
||||
func GradientText(text string, startColor, endColor string) string
|
||||
func GetLogoPattern() string // ASCII art图案
|
||||
func GetVersionSuffix() string // " (v1.x.x )" 或 " ( )"
|
||||
func PrintLogoWithVersion() // 打印完整logo
|
||||
```
|
||||
|
||||
**版本注入**:
|
||||
```go
|
||||
// 编译时通过 ldflags 注入
|
||||
// -X packagepath.variable=value
|
||||
go build -ldflags "-X github.com/titor/fanyi/internal/logo.version=${VERSION}" -o yoyo ./cmd/yoyo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CI构建环境问题
|
||||
|
||||
**问题1**: `./build.sh: not found
|
||||
|
||||
**原因**:
|
||||
1. 远程CI使用 `golang:1.26-alpine` 镜像,默认没有 bash
|
||||
2. build.sh 脚本 shebang 是 `#!/bin/bash`
|
||||
|
||||
**解决方案**:
|
||||
```yaml
|
||||
# .gitea/workflows/release.yaml
|
||||
- name: Checkout
|
||||
run: |
|
||||
apk add git bash # 添加 bash
|
||||
git clone ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**问题2**: `error obtaining VCS status: exit status 128`
|
||||
|
||||
**原因**: CI 中 git 仓库信息不完整,导致 Go 获取 VCS 状态失败
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# build.sh 中添加 -buildvcs=false
|
||||
go build -buildvcs=false -ldflags "-s -w -X ..." -o yoyo ./cmd/yoyo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**问题3**: Release 说明只有 "Automated release"
|
||||
|
||||
**原因**: 创建 release 时 body 写死了固定文本
|
||||
|
||||
**解决方案**:
|
||||
使用 annotated tag 的注释内容作为 release 说明:
|
||||
```bash
|
||||
git tag -a v1.2.0 -m "版本说明\n- 功能1\n- 功能2"
|
||||
```
|
||||
|
||||
CI 中获取:
|
||||
```bash
|
||||
RELEASE_BODY=$(git tag -l --format='%(contents)' "$TAG_NAME")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 终端颜色输出问题
|
||||
|
||||
**问题**: 在非TTY环境(如管道)下,ANSI转义序列可能显示为明文
|
||||
|
||||
**观察**:
|
||||
- 使用 `script` 命令可以正确显示颜色
|
||||
- `od -c` 检查输出包含正确的 `\033` 转义字符
|
||||
- zsh 可能对某些颜色转义处理不同
|
||||
|
||||
**解决方案**:
|
||||
- 使用 `GradientText` 函数逐字符应用渐变
|
||||
- 每个字符后使用 `\033[0m` 重置
|
||||
- 渐变使用 24-bit 颜色 `\033[38;2;R;G;Bm`
|
||||
150
taolun.md
150
taolun.md
@@ -831,4 +831,152 @@ config.GetUserEnvPath() // ~/.config/yoyo/.env
|
||||
- `survey.Confirm` → `huh.NewConfirm().Affirmative("是").Negative("否")`
|
||||
- 分步表单 → `huh.NewForm(huh.NewGroup(...), huh.NewGroup(...))`
|
||||
|
||||
**关联版本**: [changelog.md#1.1.0](changelog.md#110-2026-04-07)
|
||||
**关联版本**: [changelog.md#1.1.0](changelog.md#110-2026-04-07)
|
||||
|
||||
---
|
||||
|
||||
### [2026-04-08 00:40] 版本 1.1.1 - Logo模块化与渐变色统一
|
||||
**原因**:
|
||||
1. `--help` `-h` `-?` `--version` 在默认交互式模式下无响应
|
||||
2. 帮助信息头部需要彩色logo展示
|
||||
|
||||
**问题分析**:
|
||||
1. 交互模式判断优先于help/version检查,导致flags被忽略
|
||||
2. 帮助信息使用硬编码版本号 `YOYO翻译工具 v1.1.0`,无logo
|
||||
|
||||
**解决方案**:
|
||||
1. 修复flag检查顺序:在进入交互模式前先检查 `-h` `--help` `-?` `--version`
|
||||
2. 新增 `internal/logo/logo.go` 模块统一管理logo
|
||||
3. 编译时通过 `ldflags` 注入版本号
|
||||
4. 渐变色使用与TUI一致的紫→青色方案
|
||||
|
||||
**技术实现**:
|
||||
```go
|
||||
// logo模块核心函数
|
||||
func GradientText(text string, startColor, endColor string) string { ... }
|
||||
func GetLogoPattern() string // 返回4行ascii art
|
||||
func GetVersionSuffix() string // 返回 " (v1.1.1-dirty )" 或 " ( )"
|
||||
func PrintLogoWithVersion() // 打印完整logo(--help/--version使用)
|
||||
```
|
||||
|
||||
**build.sh版本注入**:
|
||||
```bash
|
||||
VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "")
|
||||
go build -ldflags "-X github.com/titor/fanyi/internal/logo.version=${VERSION}" -o yoyo ./cmd/yoyo
|
||||
```
|
||||
|
||||
**TUI整合**:
|
||||
- 移除 `internal/tui/model.go` 中的本地 `logoPattern` 和 `gradientText`
|
||||
- 直接调用 `logo.GradientText(logo.GetLogoPattern(), "#B413DC", "#00C8C8")`
|
||||
- 移除 `[Ctrl+C 退出]` 显示
|
||||
|
||||
**最终输出格式**(所有位置统一):
|
||||
```
|
||||
_ _ _____ _____
|
||||
( \/ ( _ ( _ )
|
||||
\ / )(_)( )(_)(
|
||||
(__)(_____(_____( v1.1.1-dirty )
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### [2026-04-08] TUI界面帮助功能与样式改进
|
||||
|
||||
**原因**:
|
||||
1. 用户需要在TUI底部添加帮助信息栏
|
||||
2. 优化翻译卡片的显示样式
|
||||
|
||||
**分析**:
|
||||
- 使用 bubbletea 的 help 组件实现帮助栏
|
||||
- 翻译卡片需要与输入区域保持间距
|
||||
|
||||
**解决方案**:
|
||||
|
||||
1. **帮助功能实现**:
|
||||
- 导入 `charm.land/bubbles/v2/help`
|
||||
- 在 `model` 结构体添加 `help help.Model` 字段
|
||||
- `KeyMap` 实现 `ShortHelp()` 和 `FullHelp()` 接口
|
||||
- 按键绑定: Help -> Ctrl+H (原 ? 和 ctrl+? 不兼容)
|
||||
- View 中添加 help.View(m.keys) 到底部
|
||||
|
||||
2. **翻译卡片样式**:
|
||||
- 翻译结果 outputBlock 的 Padding 从 `(0,3,1,3)` 改为 `(1,3,1,3)` 增加上方空隙
|
||||
- viewport 内容区域第一个卡片前添加 `"\n"` 作为上边距
|
||||
|
||||
3. **版本号注入**:
|
||||
- 扩展 build.sh 支持跨平台编译
|
||||
- 添加 -h 帮助选项
|
||||
- 添加 -o 自定义输出文件名
|
||||
- release.yaml 使用 build.sh 构建
|
||||
|
||||
**按键绑定历史**:
|
||||
- 初始: `?` → shift+? 切换,且会输入到文本框
|
||||
- 尝试: `ctrl+?` → 不兼容,无响应
|
||||
- 最终: `ctrl+h` → 正常工作
|
||||
|
||||
**相关文件**:
|
||||
- `internal/tui/keys.go` - KeyMap 定义和 ShortHelp/FullHelp
|
||||
- `internal/tui/model.go` - help 组件集成、样式调整
|
||||
- `build.sh` - 跨平台编译支持
|
||||
- `.gitea/workflows/release.yaml` - CI 构建脚本
|
||||
|
||||
**关联版本**: [changelog.md#0.7.1](changelog.md#071-2026-04-08---tui界面改进)
|
||||
|
||||
**关联版本**: [changelog.md#1.1.1](changelog.md#111-2026-04-08)
|
||||
|
||||
---
|
||||
|
||||
### [2026-04-08] 版本号管理规则制定
|
||||
|
||||
**原因**: 版本号历史混乱,需要规范化管理
|
||||
|
||||
**讨论内容**:
|
||||
1. **版本现状分析**:
|
||||
- 历史标签混乱: v0.0.1 → v1.1.1 → v6.0 等
|
||||
- changelog 中版本状态标记混乱
|
||||
|
||||
2. **确定版本规则**:
|
||||
- 当前版本: v1.2.0 (TUI基础功能完成)
|
||||
- 采用简化分支策略: main + feature 分支
|
||||
- 合并时使用 --no-ff 保留分支历史
|
||||
- 在 AGENTS.md 中详细定义版本号规则
|
||||
|
||||
3. **v1.2.0 发布标准**:
|
||||
- TUI交互界面 ✅
|
||||
- 多行输入 ✅
|
||||
- 翻译卡片展示 ✅
|
||||
- 状态栏 ✅
|
||||
- 帮助信息 ✅
|
||||
- 版本注入 ✅
|
||||
- 跨平台编译 ✅
|
||||
- CI构建 ✅
|
||||
|
||||
4. **保留历史标签**:
|
||||
- 过去的版本作为来时的路,不再删除
|
||||
- 从现在开始规范维护
|
||||
|
||||
**相关文件**:
|
||||
- `AGENTS.md` - 版本号管理规则(扩展)
|
||||
- `changelog.md` - 版本历史整理
|
||||
- `build.sh` - 跨平台编译脚本
|
||||
|
||||
**关联版本**: [changelog.md#v1.2.0](changelog.md#v120-2026-04-08)
|
||||
|
||||
---
|
||||
|
||||
### [2026-04-08] Windows 交叉编译修复
|
||||
|
||||
**问题**: 交叉编译的 Windows 版本在 Windows 上提示"不支持16位应用程序"
|
||||
|
||||
**原因**:
|
||||
- Linux CI 交叉编译 Windows 时,缺少 `CGO_ENABLED=0` 参数
|
||||
- 原生编译(go build 在 Windows 上)没有这个问题
|
||||
|
||||
**解决方案**:
|
||||
- 在 build.sh 的 `windows/amd64` 分支添加 `CGO_ENABLED=0`
|
||||
- 统一使用 `CGO_ENABLED=0` 进行构建
|
||||
|
||||
**相关文件**:
|
||||
- `build.sh` - 添加 CGO_ENABLED=0
|
||||
|
||||
**关联版本**: [changelog.md#v1.2.0](changelog.md#v120-2026-04-08)
|
||||
Reference in New Issue
Block a user