diff --git a/changelog.md b/changelog.md index af10564..be29119 100644 --- a/changelog.md +++ b/changelog.md @@ -36,17 +36,47 @@ | 步骤 | 模块 | 内容 | 状态 | |------|------|------|------| | 1 | TUI框架搭建 | bubbletea基础App结构、运行循环 | ✅ 已完成 | -| 2 | 输入组件 | 文本输入框、光标、基础编辑 | ✅ 已完成 | +| 2 | 输入组件 | 文本输入框,光标、基础编辑 | ✅ 已完成 | | 3 | 翻译显示区 | 结果展示、格式化、滚动 | ✅ 已完成 | | 4 | 状态栏/主题 | 底部状态栏、语言选择、主题配色 | ✅ 已完成 | | 5 | 快捷键系统 | 退出、清空、切换语言等 | ✅ 已完成 | | 6 | 集成翻译 | 对接现有Translator、加载动画 | ✅ 已完成 | +## TUI界面改进计划 (v0.7.0) +| 步骤 | 模块 | 内容 | 状态 | +|------|------|------|------| +| 7 | 多行输入 | textarea组件替换textinput | ✅ 已完成 | +| 8 | 弹出框组件 | 通用modal组件 | ⏳ 待实现 | +| 9 | 斜杠命令菜单 | / 触发命令选择器,模糊匹配 | ⏳ 待实现 | +| 10 | 翻译结果滚动 | viewport组件支持长文本 | ⏳ 待实现 | +| 11 | 复制功能 | clipboard集成 | ⏳ 待实现 | +| 12 | 状态栏扩展 | 显示耗时、token用量 | ⏳ 待实现 | + ## 待修复BUG - 无 ## 版本历史 +### 0.7.0 (2026-04-06) - TUI界面改进 +**类型**: 功能版本 +**状态**: 开发中 + +**改进内容**: +- ✅ 模块7: 多行输入 - textarea组件替换textinput +- ⏳ 模块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交互界面 **类型**: 功能版本 **状态**: 已完成 @@ -69,9 +99,7 @@ - [TUI界面模块拆分计划](taolun.md#2026-04-06-1000-版本-060---tui界面模块拆分计划) **下一步**: -- 测试TUI交互界面 -- 优化用户体验 -- 添加更多功能(如复制翻译结果) +- 实现模块7: 多行输入 --- diff --git a/internal/tui/app.go b/internal/tui/app.go index d22a91e..93a1270 100644 --- a/internal/tui/app.go +++ b/internal/tui/app.go @@ -3,7 +3,7 @@ package tui import ( "context" - "github.com/charmbracelet/bubbles/textinput" + "github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/titor/fanyi/internal/config" @@ -13,7 +13,7 @@ import ( type model struct { config *config.Config translator *translator.Translator - textInput textinput.Model + textArea textarea.Model result string errMsg string targetLang string @@ -34,7 +34,8 @@ var ( Foreground(lipgloss.Color("#00D9FF")) inputStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#FAFAFA")). - Background(lipgloss.Color("#1A1A2E")) + Background(lipgloss.Color("#1A1A2E")). + Width(50) resultStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#98FB98")). Background(lipgloss.Color("#0D1B2A")) @@ -64,16 +65,17 @@ func NewApp(cfg *config.Config, t *translator.Translator) *tea.Program { targetLang = cfg.DefaultTargetLang } - ti := textinput.New() - ti.Placeholder = "输入要翻译的文本..." - ti.Focus() - ti.Prompt = "> " - ti.TextStyle = inputStyle + ta := textarea.New() + ta.Placeholder = "输入要翻译的文本..." + ta.Focus() + ta.Prompt = "| " + ta.SetWidth(50) + ta.SetHeight(5) return tea.NewProgram(model{ config: cfg, translator: t, - textInput: ti, + textArea: ta, targetLang: targetLang, }) } @@ -103,7 +105,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.loading { return m, nil } - text := m.textInput.Value() + text := m.textArea.Value() if text == "" { return m, nil } @@ -115,7 +117,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Quit case tea.KeyCtrlL: - m.textInput.SetValue("") + m.textArea.SetValue("") m.result = "" m.errMsg = "" return m, nil @@ -130,7 +132,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } - m.textInput, cmd = m.textInput.Update(msg) + m.textArea, cmd = m.textArea.Update(msg) return m, cmd } @@ -153,39 +155,33 @@ func (m model) doTranslate(text, toLang string) tea.Cmd { func (m model) View() string { resultBox := m.renderResult() - helpText := helpStyle.Render("\n " + - keyStyle.Render("Ctrl+L") + " 清空 " + - keyStyle.Render("Ctrl+T") + " 切换语言 " + - keyStyle.Render("Enter") + " 翻译 " + - keyStyle.Render("Ctrl+C") + " 退出") return "\n" + " " + headerStyle.Render("YOYO翻译") + "\n" + " " + dividerStyle.Render("─────────────────────") + "\n\n" + - " " + m.textInput.View() + "\n\n" + + m.textArea.View() + "\n\n" + resultBox + - helpText + - "\n" + m.renderStatusBar() } func (m model) renderResult() string { if m.loading { - return " " + loadingStyle.Render("正在翻译...") + "\n" + return " " + loadingStyle.Render("正在翻译...") + "\n\n" } if m.errMsg != "" { - return " " + errorStyle.Render("错误: "+m.errMsg) + "\n" + return " " + errorStyle.Render("错误: "+m.errMsg) + "\n\n" } if m.result == "" { - return " " + helpStyle.Render("翻译结果将显示在这里...") + "\n" + return " " + helpStyle.Render("翻译结果将显示在这里...") + "\n\n" } - return " " + resultStyle.Render(m.result) + "\n" + return " " + resultStyle.Render(m.result) + "\n\n" } func (m model) renderStatusBar() string { divider := dividerStyle.Render("─") langInfo := langStyle.Render("目标: " + m.targetLang) + hint := helpStyle.Render("按 / 显示命令") - return "\n " + divider + "\n" + - " " + statusBarStyle.Render(" "+langInfo+" ") + return " " + divider + "\n" + + " " + statusBarStyle.Render(" "+langInfo+" ") + " " + hint } diff --git a/memory.md b/memory.md index 1f52116..f8876d0 100644 --- a/memory.md +++ b/memory.md @@ -696,4 +696,53 @@ func (m model) View() string { return m.result } ``` + +--- + +## TUI界面改进知识 + +### TextInput vs Textarea +- **textinput**: 单行输入,适合短文本 +- **textarea**: 多行输入,适合长段落 +- 切换时需要调整布局和样式 + +### Modal/弹出框设计 +```go +type model struct { + showModal bool + modalType string // "help", "command", "info" +} + +// View中渲染modal +func (m model) View() string { + s := "主界面..." + if m.showModal { + s += m.renderModal() + } + return s +} +``` + +### 斜杠命令菜单 +```go +type command struct { + name string + desc string + handler func() +} + +var commands = []command{ + {"help", "显示帮助", handleHelp}, + {"clear", "清空内容", handleClear}, + {"copy", "复制结果", handleCopy}, +} + +// 模糊匹配 +func matchCommand(input string) []command { + // 过滤匹配的命令 +} +``` + +### Viewport组件 +用于长文本滚动显示,配合scrollbar展示滚动位置。 ``` \ No newline at end of file diff --git a/taolun.md b/taolun.md index 332d5ac..bb9a180 100644 --- a/taolun.md +++ b/taolun.md @@ -583,4 +583,67 @@ func (m model) doTranslate(text, toLang string) tea.Cmd { **下一步**: 测试TUI界面、优化体验 **关联文档**: -- [changelog.md#0.6.0](changelog.md#060) \ No newline at end of file +- [changelog.md#0.6.0](changelog.md#060) + +--- + +### [2026-04-06 14:00] 版本 0.7.0 - TUI界面改进计划 +**原因**: TUI基础功能完成后,讨论改进方向和用户体验优化 +**分析**: +- 当前单行textinput无法输入多行文本 +- 快捷键固定显示在底部不够美观 +- 缺少命令菜单系统 +- 长翻译结果无法滚动 + +**解决方案 - 新增模块**: + +| 步骤 | 模块 | 内容 | +|------|------|------| +| 7 | 多行输入 | textarea组件替换textinput | +| 8 | 弹出框组件 | 通用modal,支持快捷键帮助 | +| 9 | 斜杠命令菜单 | / 触发命令选择器,类似opencode | +| 10 | 翻译结果滚动 | viewport组件 | +| 11 | 复制功能 | clipboard集成 | +| 12 | 状态栏扩展 | 耗时、token用量 | + +**斜杠命令设计**: +| 命令 | 功能 | +|------|------| +| `/help` | 显示快捷键帮助 | +| `/clear` | 清空内容 | +| `/copy` | 复制翻译结果 | +| `/lang` | 切换语言 | +| `/history` | 翻译历史 | +| `/quit` | 退出 | + +**设计亮点**: +1. 隐藏底部快捷键提示,改为按 ? 或 F1 弹出帮助框 +2. 输入 / 触发命令菜单,上下键选择,回车执行 +3. 命令菜单支持模糊匹配 +4. modal组件通用化,可复用于其他弹窗场景 + +**关联文档**: +- [changelog.md#0.7.0](changelog.md#070) + +--- + +### [2026-04-06 14:30] 版本 0.7.0 - 模块7: 多行输入 (已完成) +**原因**: 当前textinput只支持单行,需要支持多行文本输入 +**分析**: +- 长段落、多行文本无法输入 +- 需要换用bubbles的textarea组件 + +**解决方案**: +1. 将textinput替换为textarea +2. 调整样式和布局(宽50,高5) +3. 底部状态栏提示按 / 显示命令 + +**技术实现**: +- 使用 `github.com/charmbracelet/bubbles/textarea` +- textarea.SetWidth(50)、SetHeight(5) 设置尺寸 +- 移除底部固定快捷键提示,改为按需显示 + +**下一步**: 实现模块8: 弹出框组件 + +**关联文档**: +- [changelog.md#0.7.0](changelog.md#070) \ No newline at end of file