Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
c236fba
feat(cli): implement interactive chat with @clack/prompts
MarsQiu007 Mar 31, 2026
64711a4
docs(cli): add chat interface documentation
MarsQiu007 Mar 31, 2026
ef2e515
fix(cli): remove message truncation in chat display
MarsQiu007 Mar 31, 2026
d738b9a
fix(cli): prevent duplicate message display in chat
MarsQiu007 Mar 31, 2026
e458da6
chore: remove test data files
MarsQiu007 Mar 31, 2026
88f2ed4
feat(cli): add real-time command hints in chat
MarsQiu007 Mar 31, 2026
ce91df3
fix(cli): simplify chat input to avoid complexity
MarsQiu007 Mar 31, 2026
d95b701
docs: document Tab autocomplete limitation
MarsQiu007 Mar 31, 2026
0296e62
feat(cli): migrate to Ink framework with Tab autocomplete support
MarsQiu007 Mar 31, 2026
ed613d0
docs: update README for Ink implementation
MarsQiu007 Mar 31, 2026
e7eb6e9
fix(cli): remove duplicate case clause warning in commands parser
MarsQiu007 Mar 31, 2026
b528deb
Merge branch 'feature/tui-redesign' - Complete Ink TUI implementation…
MarsQiu007 Mar 31, 2026
d390a9c
fix(cli): clear input immediately after submission in chat interface
MarsQiu007 Mar 31, 2026
91b5b26
feat(cli): add separator lines around input field in chat interface
MarsQiu007 Mar 31, 2026
6ca19ed
fix(cli): remove message truncation in chat display - show full content
MarsQiu007 Mar 31, 2026
7d2c83b
feat(cli): make input field stretch to full terminal width
MarsQiu007 Mar 31, 2026
3c20375
fix(cli): dynamically adjust separator lines to terminal width on resize
MarsQiu007 Mar 31, 2026
2df276a
fix(cli): prevent separator line wrapping by adjusting width calculation
MarsQiu007 Mar 31, 2026
61a2381
fix(security): address critical vulnerabilities in chat TUI
MarsQiu007 Mar 31, 2026
2dda1f5
chore: address remaining optimization suggestions from PR review
MarsQiu007 Mar 31, 2026
7936161
docs: remove outdated message truncation limitation from README
MarsQiu007 Mar 31, 2026
7d791c1
fix(cli): improve input validation and keyboard handling in chat
MarsQiu007 Mar 31, 2026
5aa33a3
feat(cli): enable conversation context and improve error handling
MarsQiu007 Mar 31, 2026
306d004
Merge remote-tracking branch 'origin/master' into feature/tui-redesign
MarsQiu007 Mar 31, 2026
e5cc90b
fix(cli): address all feedback from latest Copilot PR review
MarsQiu007 Mar 31, 2026
1cf9a49
fix(cli): add exit message before closing chat interface
MarsQiu007 Mar 31, 2026
9d1ee3a
feat(cli): add /exit and /quit to Tab autocomplete suggestions
MarsQiu007 Mar 31, 2026
3861f2c
fix(cli): display /help output in chat history
MarsQiu007 Mar 31, 2026
841d994
fix(cli): improve chat TUI command UX
MarsQiu007 Apr 1, 2026
9bcca91
fix(cli): address chat review feedback
MarsQiu007 Apr 1, 2026
5695d0b
fix(cli): harden chat history persistence
MarsQiu007 Apr 1, 2026
2a39cca
fix(cli): address chat PR review feedback
MarsQiu007 Apr 1, 2026
342c5f1
fix(cli): align chat review follow-ups
MarsQiu007 Apr 1, 2026
aaee611
Update packages/cli/src/chat/history.ts
MarsQiu007 Apr 1, 2026
e8791b2
Update packages/cli/src/chat/history.ts
MarsQiu007 Apr 1, 2026
e51cb46
Update packages/cli/src/chat/index.tsx
MarsQiu007 Apr 1, 2026
efd6b1f
Update packages/cli/src/chat/history.ts
MarsQiu007 Apr 1, 2026
890e634
Update packages/cli/src/chat/index.tsx
MarsQiu007 Apr 1, 2026
afde09d
Update packages/cli/src/chat/session.ts
MarsQiu007 Apr 1, 2026
a590782
Improve chat streaming feedback
MarsQiu007 Apr 1, 2026
d72c666
修复 PR 评论问题:添加命令选项验证和优化性能
MarsQiu007 Apr 1, 2026
0d04e5b
Update packages/cli/src/chat/commands.ts
MarsQiu007 Apr 1, 2026
05e1523
修复命令定义语义矛盾和优化自动补全逻辑
MarsQiu007 Apr 1, 2026
dee0216
修正文档与 master 分支实际功能保持一致
MarsQiu007 Apr 1, 2026
45dcff3
Update packages/cli/src/chat/session.ts
MarsQiu007 Apr 1, 2026
caa346a
Update packages/cli/src/chat/index.tsx
MarsQiu007 Apr 1, 2026
50587ee
启用流式 UI 反馈:连接 onMessage 到 onStreamChunk
MarsQiu007 Apr 1, 2026
561ae2f
Update packages/cli/src/chat/session.ts
MarsQiu007 Apr 1, 2026
60d0ca8
修复错误分类的脆弱对象身份比较
MarsQiu007 Apr 1, 2026
c257795
Update packages/cli/src/chat/commands.ts
MarsQiu007 Apr 1, 2026
7d011c8
Update packages/cli/src/chat/index.tsx
MarsQiu007 Apr 1, 2026
91bf4db
修复并发保存时消息顺序错乱的问题
MarsQiu007 Apr 1, 2026
df53532
Update packages/cli/src/chat/index.tsx
MarsQiu007 Apr 1, 2026
07356bf
Update packages/cli/src/chat/history.ts
MarsQiu007 Apr 1, 2026
98955cb
Update packages/cli/src/chat/history.ts
MarsQiu007 Apr 1, 2026
83c0891
修复 Windows 平台文件替换失败的问题
MarsQiu007 Apr 1, 2026
5f86cbc
Update packages/cli/src/chat/index.tsx
MarsQiu007 Apr 1, 2026
d36571a
改进 Windows 文件替换策略:备份-重命名方案
MarsQiu007 Apr 1, 2026
35cdea0
修复本地命令的错误处理:统一历史持久化冲突处理
MarsQiu007 Apr 1, 2026
1ec4211
修复三个代码质量和兼容性问题
MarsQiu007 Apr 1, 2026
1ad6848
修复 Tab 自动补全的触发条件
MarsQiu007 Apr 1, 2026
3d7a4a3
修复时间源不一致:统一使用 Date.now()
MarsQiu007 Apr 1, 2026
7617a45
修复时间戳解析和备份清理注释问题
MarsQiu007 Apr 1, 2026
73ae6bb
系统性重构历史持久化设计逻辑:解决注释与实现分离问题
MarsQiu007 Apr 2, 2026
d456f4e
添加TUI测试模式:自动化测试 + 手动测试脚本
MarsQiu007 Apr 2, 2026
6b5bd60
修复Copilot审查发现的3个实现问题
MarsQiu007 Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ autoresearch/
.playwright-cli/
.superpowers/
_*.md
package-lock.json
2 changes: 2 additions & 0 deletions packages/cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.test-chat-history/
.test-chat-session/
204 changes: 204 additions & 0 deletions packages/cli/TUI_TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# InkOS TUI 测试指南

## 概述

TUI(Terminal User Interface)测试分为两种模式:
1. **自动化测试**:测试UI组件逻辑(无需真实渲染)
2. **手动测试模式**:在终端中实际运行TUI进行交互测试

---

## 自动化测试

### 运行测试

```bash
# 在packages/cli目录下运行
cd packages/cli
npm test -- src/__tests__/chat-tui.test.ts
```

### 测试覆盖范围

自动化测试覆盖以下关键逻辑:

#### 1. 命令补全逻辑
- ✅ Tab补全零参数命令(/exit, /clear → 无空格)
- ✅ Tab补全需要参数的命令(/write, /switch → 有空格)
- ✅ 命令列表完整性验证

#### 2. 输入处理逻辑
- ✅ 斜杠命令识别
- ✅ 命令名称提取

#### 3. 消息显示逻辑
- ✅ 时间戳格式化(包括无效时间戳处理)
- ✅ Token使用统计计算

#### 4. 状态管理逻辑
- ✅ 命令建议列表导航(↑↓循环)
- ✅ 执行时间计算(MM:SS.T格式)

#### 5. 边界情况处理
- ✅ 空输入处理
- ✅ 超长输入处理
- ✅ 特殊字符输入处理(引号、$、\等)

### 测试结果

```
✓ src/__tests__/chat-tui.test.ts (12 tests) 15ms

Test Files 1 passed (1)
Tests 12 passed (12)
```

---

## 手动测试模式

### 启动测试模式

```bash
# 在项目根目录运行
npx tsx packages/cli/src/chat/test-mode.ts
```

### 测试前准备

测试脚本会自动:
1. 创建测试历史目录 `.test-tui-chat-history`
2. 清理旧的测试数据
3. 预填充测试消息(用于历史消息测试)

### 测试场景清单

启动后,你可以测试以下场景:

#### 1. 基本输入测试
- 输入普通文本:`写下一章`
- 验证消息显示、时间戳格式

#### 2. 命令补全测试
- 输入 `/` 触发命令列表
- 按 `Tab` 补全选中的命令
- 验证补全后是否正确(零参数命令无空格,有参数命令有空格)

#### 3. 命令导航测试
- 输入 `/` 显示命令列表
- 按 `↑` `↓` 导航命令列表
- 验证循环导航是否工作

#### 4. 历史消息测试
- 查看预填充的测试消息
- 验证时间戳显示、token统计

#### 5. 清空测试
- 输入 `/clear`
- 验证历史消息被清空
- 验证确认消息显示

#### 6. 帮助测试
- 输入 `/help`
- 验证帮助信息显示(命令列表、Tab提示)

#### 7. 退出测试
- 输入 `/exit` 或 `/quit`
- 或按 `Esc` 退出
- 验证正在执行时的二次确认(按两次Esc强制退出)

#### 8. 错误处理测试
- 输入不存在的命令:`/invalid`
- 验证错误消息显示
- 输入不存在的book:`/switch missing-book`
- 验证错误消息和历史记录

---

## TUI功能清单

### 快捷键

| 按键 | 功能 |
|------|------|
| `Tab` | 补全选中的命令 |
| `↑` / `↓` | 导航命令建议列表 |
| `Esc` | 退出(执行中按两次强制退出) |
| `Enter` | 提交输入 |

### 可用命令

| 命令 | 参数 | 说明 |
|------|------|------|
| `/write` | `[--guidance '指导']` | 写下一章 |
| `/audit` | `[章节号]` | 审计章节 |
| `/revise` | `<章节号> [--mode polish\|rewrite]` | 修改章节 |
| `/status` | - | 显示项目状态 |
| `/clear` | - | 清空对话历史 |
| `/switch` | `<book-id>` | 切换到其他book |
| `/help` | - | 显示帮助信息 |
| `/exit` `/quit` | - | 退出聊天界面 |

### UI特性

- ✅ 命令自动补全(Tab键)
- ✅ 命令建议列表(输入`/`触发)
- ✅ 执行状态显示(spinner + 计时器)
- ✅ 执行元数据显示(model、tool、provider)
- ✅ 流式内容显示
- ✅ 历史消息加载
- ✅ Token使用统计
- ✅ 错误友好提示

---

## 测试最佳实践

### 自动化测试
- 定期运行自动化测试确保UI逻辑正确
- 修改UI逻辑后立即添加相关测试
- 测试边界情况(空输入、超长输入、特殊字符)

### 手动测试
- 在提交前进行完整的手动测试流程
- 特别关注用户体验细节(补全、导航、错误提示)
- 测试不同终端尺寸下的显示效果

### 调试技巧
- 使用 `console.log` 输出调试信息(会被Ink捕获显示)
- 检查 `.test-tui-chat-history` 目录中的历史文件
- 查看终端输出中的错误栈

---

## 常见问题

### Q: TUI启动失败?
A: 检查以下几点:
1. 是否在项目根目录运行
2. Node.js版本是否符合要求(≥20)
3. 是否有终端交互权限

### Q: 命令补全不工作?
A: 确保:
1. 输入以 `/` 开头
2. 命令建议列表可见
3. 按 `Tab` 时有选中项

### Q: 如何查看测试数据?
A: 查看测试历史目录:
```bash
cat .test-tui-chat-history/test-book.json
```

---

## 贡献测试

如果你发现新的测试场景或边界情况:

1. 在 `chat-tui.test.ts` 中添加自动化测试
2. 在 `test-mode.ts` 中添加测试场景提示
3. 更新本文档的测试清单

Happy Testing! 🧪
11 changes: 8 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,16 @@
"commander": "^13.0.0",
"dotenv": "^16.4.0",
"epub-gen-memory": "^1.0.10",
"marked": "^15.0.0"
"ink": "^6.8.0",
"ink-spinner": "^5.0.0",
"ink-text-input": "^6.0.0",
"marked": "^15.0.0",
"react": "^19.2.4"
},
"devDependencies": {
"@types/node": "^22.0.0",
"@types/react": "^19.2.14",
"typescript": "^5.8.0",
"vitest": "^3.0.0",
"@types/node": "^22.0.0"
"vitest": "^3.0.0"
}
}
150 changes: 150 additions & 0 deletions packages/cli/src/__tests__/chat-commands.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* Tests for slash command parser.
*/

import { describe, test, expect } from "vitest";
import {
getAutocompleteInput,
parseSlashCommand,
SLASH_COMMANDS,
validateCommandArgs,
} from "../chat/commands.js";

describe("Slash Commands", () => {
test("should parse /write command", () => {
const result = parseSlashCommand("/write");

expect(result.valid).toBe(true);
if (result.valid) {
expect(result.command).toBe("write");
expect(result.args).toEqual([]);
}
});

test("should parse /write with guidance", () => {
const result = parseSlashCommand("/write --guidance '增加动作戏'");

expect(result.valid).toBe(true);
if (result.valid) {
expect(result.command).toBe("write");
expect(result.options.guidance).toBe("增加动作戏"); // Quotes are stripped
}
});

test("should parse /audit with chapter number", () => {
const result = parseSlashCommand("/audit 5");

expect(result.valid).toBe(true);
if (result.valid) {
expect(result.command).toBe("audit");
expect(result.args).toEqual(["5"]);
}
});

test("should parse /revise with mode", () => {
const result = parseSlashCommand("/revise 5 --mode polish");

expect(result.valid).toBe(true);
if (result.valid) {
expect(result.command).toBe("revise");
expect(result.args).toEqual(["5"]);
expect(result.options.mode).toBe("polish");
}
});

test("should parse /switch command", () => {
const result = parseSlashCommand("/switch my-book");

expect(result.valid).toBe(true);
if (result.valid) {
expect(result.command).toBe("switch");
expect(result.args).toEqual(["my-book"]);
}
});

test("should reject invalid command", () => {
const result = parseSlashCommand("/invalid");

expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.error).toContain("未知命令");
}
});

test("should require argument for /switch", () => {
const result = parseSlashCommand("/switch");

expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.error).toContain("至少需要");
}
});

test("should reject extra positional args for zero-arg commands", () => {
const result = parseSlashCommand("/status foo");

expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.error).toContain("不接受额外参数");
}
});

test("should reject extra positional args for single-arg commands", () => {
const result = parseSlashCommand("/switch my-book extra");

expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.error).toContain("最多接受 1 个参数");
}
});

test("should reject positional args for option-only commands", () => {
const result = parseSlashCommand("/write draft");

expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.error).toContain("不接受额外参数");
}
});

test("should enforce max positional args in validateCommandArgs", () => {
const result = validateCommandArgs("switch", ["my-book", "extra"]);

expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.error).toContain("最多接受 1 个参数");
}
});

test("should parse /exit even with trailing whitespace", () => {
const result = parseSlashCommand("/exit ");

expect(result.valid).toBe(true);
if (result.valid) {
expect(result.command).toBe("exit");
expect(result.args).toEqual([]);
}
});

test("should not append trailing space for zero-argument autocomplete commands", () => {
expect(getAutocompleteInput("exit")).toBe("/exit");
expect(getAutocompleteInput("clear")).toBe("/clear");
});

test("should append trailing space for autocomplete commands expecting more input", () => {
expect(getAutocompleteInput("write")).toBe("/write ");
expect(getAutocompleteInput("switch")).toBe("/switch ");
});

test("should have all expected commands", () => {
const commands = Object.keys(SLASH_COMMANDS);

expect(commands).toContain("write");
expect(commands).toContain("audit");
expect(commands).toContain("revise");
expect(commands).toContain("status");
expect(commands).toContain("clear");
expect(commands).toContain("switch");
expect(commands).toContain("help");
});
});
Loading