feat: 模块7 - 多行输入支持 (textarea替换textinput)
This commit is contained in:
36
changelog.md
36
changelog.md
@@ -36,17 +36,47 @@
|
|||||||
| 步骤 | 模块 | 内容 | 状态 |
|
| 步骤 | 模块 | 内容 | 状态 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| 1 | TUI框架搭建 | bubbletea基础App结构、运行循环 | ✅ 已完成 |
|
| 1 | TUI框架搭建 | bubbletea基础App结构、运行循环 | ✅ 已完成 |
|
||||||
| 2 | 输入组件 | 文本输入框、光标、基础编辑 | ✅ 已完成 |
|
| 2 | 输入组件 | 文本输入框,光标、基础编辑 | ✅ 已完成 |
|
||||||
| 3 | 翻译显示区 | 结果展示、格式化、滚动 | ✅ 已完成 |
|
| 3 | 翻译显示区 | 结果展示、格式化、滚动 | ✅ 已完成 |
|
||||||
| 4 | 状态栏/主题 | 底部状态栏、语言选择、主题配色 | ✅ 已完成 |
|
| 4 | 状态栏/主题 | 底部状态栏、语言选择、主题配色 | ✅ 已完成 |
|
||||||
| 5 | 快捷键系统 | 退出、清空、切换语言等 | ✅ 已完成 |
|
| 5 | 快捷键系统 | 退出、清空、切换语言等 | ✅ 已完成 |
|
||||||
| 6 | 集成翻译 | 对接现有Translator、加载动画 | ✅ 已完成 |
|
| 6 | 集成翻译 | 对接现有Translator、加载动画 | ✅ 已完成 |
|
||||||
|
|
||||||
|
## TUI界面改进计划 (v0.7.0)
|
||||||
|
| 步骤 | 模块 | 内容 | 状态 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 7 | 多行输入 | textarea组件替换textinput | ✅ 已完成 |
|
||||||
|
| 8 | 弹出框组件 | 通用modal组件 | ⏳ 待实现 |
|
||||||
|
| 9 | 斜杠命令菜单 | / 触发命令选择器,模糊匹配 | ⏳ 待实现 |
|
||||||
|
| 10 | 翻译结果滚动 | viewport组件支持长文本 | ⏳ 待实现 |
|
||||||
|
| 11 | 复制功能 | clipboard集成 | ⏳ 待实现 |
|
||||||
|
| 12 | 状态栏扩展 | 显示耗时、token用量 | ⏳ 待实现 |
|
||||||
|
|
||||||
## 待修复BUG
|
## 待修复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交互界面
|
### 0.6.0 (2026-04-06) - TUI交互界面
|
||||||
**类型**: 功能版本
|
**类型**: 功能版本
|
||||||
**状态**: 已完成
|
**状态**: 已完成
|
||||||
@@ -69,9 +99,7 @@
|
|||||||
- [TUI界面模块拆分计划](taolun.md#2026-04-06-1000-版本-060---tui界面模块拆分计划)
|
- [TUI界面模块拆分计划](taolun.md#2026-04-06-1000-版本-060---tui界面模块拆分计划)
|
||||||
|
|
||||||
**下一步**:
|
**下一步**:
|
||||||
- 测试TUI交互界面
|
- 实现模块7: 多行输入
|
||||||
- 优化用户体验
|
|
||||||
- 添加更多功能(如复制翻译结果)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package tui
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
"github.com/charmbracelet/bubbles/textarea"
|
||||||
"github.com/charmbracelet/bubbletea"
|
"github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/titor/fanyi/internal/config"
|
"github.com/titor/fanyi/internal/config"
|
||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
type model struct {
|
type model struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
translator *translator.Translator
|
translator *translator.Translator
|
||||||
textInput textinput.Model
|
textArea textarea.Model
|
||||||
result string
|
result string
|
||||||
errMsg string
|
errMsg string
|
||||||
targetLang string
|
targetLang string
|
||||||
@@ -34,7 +34,8 @@ var (
|
|||||||
Foreground(lipgloss.Color("#00D9FF"))
|
Foreground(lipgloss.Color("#00D9FF"))
|
||||||
inputStyle = lipgloss.NewStyle().
|
inputStyle = lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("#FAFAFA")).
|
Foreground(lipgloss.Color("#FAFAFA")).
|
||||||
Background(lipgloss.Color("#1A1A2E"))
|
Background(lipgloss.Color("#1A1A2E")).
|
||||||
|
Width(50)
|
||||||
resultStyle = lipgloss.NewStyle().
|
resultStyle = lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("#98FB98")).
|
Foreground(lipgloss.Color("#98FB98")).
|
||||||
Background(lipgloss.Color("#0D1B2A"))
|
Background(lipgloss.Color("#0D1B2A"))
|
||||||
@@ -64,16 +65,17 @@ func NewApp(cfg *config.Config, t *translator.Translator) *tea.Program {
|
|||||||
targetLang = cfg.DefaultTargetLang
|
targetLang = cfg.DefaultTargetLang
|
||||||
}
|
}
|
||||||
|
|
||||||
ti := textinput.New()
|
ta := textarea.New()
|
||||||
ti.Placeholder = "输入要翻译的文本..."
|
ta.Placeholder = "输入要翻译的文本..."
|
||||||
ti.Focus()
|
ta.Focus()
|
||||||
ti.Prompt = "> "
|
ta.Prompt = "| "
|
||||||
ti.TextStyle = inputStyle
|
ta.SetWidth(50)
|
||||||
|
ta.SetHeight(5)
|
||||||
|
|
||||||
return tea.NewProgram(model{
|
return tea.NewProgram(model{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
translator: t,
|
translator: t,
|
||||||
textInput: ti,
|
textArea: ta,
|
||||||
targetLang: targetLang,
|
targetLang: targetLang,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -103,7 +105,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
if m.loading {
|
if m.loading {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
text := m.textInput.Value()
|
text := m.textArea.Value()
|
||||||
if text == "" {
|
if text == "" {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
@@ -115,7 +117,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
|
|
||||||
case tea.KeyCtrlL:
|
case tea.KeyCtrlL:
|
||||||
m.textInput.SetValue("")
|
m.textArea.SetValue("")
|
||||||
m.result = ""
|
m.result = ""
|
||||||
m.errMsg = ""
|
m.errMsg = ""
|
||||||
return m, nil
|
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
|
return m, cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,39 +155,33 @@ func (m model) doTranslate(text, toLang string) tea.Cmd {
|
|||||||
|
|
||||||
func (m model) View() string {
|
func (m model) View() string {
|
||||||
resultBox := m.renderResult()
|
resultBox := m.renderResult()
|
||||||
helpText := helpStyle.Render("\n " +
|
|
||||||
keyStyle.Render("Ctrl+L") + " 清空 " +
|
|
||||||
keyStyle.Render("Ctrl+T") + " 切换语言 " +
|
|
||||||
keyStyle.Render("Enter") + " 翻译 " +
|
|
||||||
keyStyle.Render("Ctrl+C") + " 退出")
|
|
||||||
|
|
||||||
return "\n" +
|
return "\n" +
|
||||||
" " + headerStyle.Render("YOYO翻译") + "\n" +
|
" " + headerStyle.Render("YOYO翻译") + "\n" +
|
||||||
" " + dividerStyle.Render("─────────────────────") + "\n\n" +
|
" " + dividerStyle.Render("─────────────────────") + "\n\n" +
|
||||||
" " + m.textInput.View() + "\n\n" +
|
m.textArea.View() + "\n\n" +
|
||||||
resultBox +
|
resultBox +
|
||||||
helpText +
|
|
||||||
"\n" +
|
|
||||||
m.renderStatusBar()
|
m.renderStatusBar()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) renderResult() string {
|
func (m model) renderResult() string {
|
||||||
if m.loading {
|
if m.loading {
|
||||||
return " " + loadingStyle.Render("正在翻译...") + "\n"
|
return " " + loadingStyle.Render("正在翻译...") + "\n\n"
|
||||||
}
|
}
|
||||||
if m.errMsg != "" {
|
if m.errMsg != "" {
|
||||||
return " " + errorStyle.Render("错误: "+m.errMsg) + "\n"
|
return " " + errorStyle.Render("错误: "+m.errMsg) + "\n\n"
|
||||||
}
|
}
|
||||||
if m.result == "" {
|
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 {
|
func (m model) renderStatusBar() string {
|
||||||
divider := dividerStyle.Render("─")
|
divider := dividerStyle.Render("─")
|
||||||
langInfo := langStyle.Render("目标: " + m.targetLang)
|
langInfo := langStyle.Render("目标: " + m.targetLang)
|
||||||
|
hint := helpStyle.Render("按 / 显示命令")
|
||||||
|
|
||||||
return "\n " + divider + "\n" +
|
return " " + divider + "\n" +
|
||||||
" " + statusBarStyle.Render(" "+langInfo+" ")
|
" " + statusBarStyle.Render(" "+langInfo+" ") + " " + hint
|
||||||
}
|
}
|
||||||
|
|||||||
49
memory.md
49
memory.md
@@ -696,4 +696,53 @@ func (m model) View() string {
|
|||||||
return m.result
|
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展示滚动位置。
|
||||||
```
|
```
|
||||||
63
taolun.md
63
taolun.md
@@ -584,3 +584,66 @@ func (m model) doTranslate(text, toLang string) tea.Cmd {
|
|||||||
|
|
||||||
**关联文档**:
|
**关联文档**:
|
||||||
- [changelog.md#0.6.0](changelog.md#060)
|
- [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)
|
||||||
Reference in New Issue
Block a user