# cmdc_tui

基于 [ExRatatui](https://hex.pm/packages/ex_ratatui) 的 [CMDC](https://hex.pm/packages/cmdc) AI Agent 终端交互界面（TUI）。

在终端内与 AI Agent 实时对话，实时查看工具调用过程、Todo 列表、Token 用量。

```
┌─────────────────────────────────┬──────────────────┐
│                                 │  Tools [Tab]     │
│  对话 (3)                        │  ──────────────  │
│  ◆ AI                           │  read_file  done │
│  你好！需要帮忙吗？               │  shell  running  │
│                                 ├──────────────────┤
│  ▶ You                          │                  │
│  帮我列出当前目录的文件            │                  │
│                                 │                  │
│─────────────────────────────────┤                  │
│ 输入 ────────────────────────── │                  │
│ 输入消息... (Ctrl+S 发送)         │                  │
└─────────────────────────────────┴──────────────────┘
│ idle │ session: live │ MiniMax-M2.7 │ Ctrl+S:发送   │
└──────────────────────────────────────────────────────┘
```

---

## 安装

在 `mix.exs` 中添加依赖：

```elixir
def deps do
  [
    {:cmdc_tui, "~> 0.1"}
  ]
end
```

---

## 快速启动（命令行）

`cmdc_tui` 内置 `mix tui` 任务，通过环境变量配置后直接运行：

```bash
# 无 Agent（纯输入测试）
mix tui

# 带 Agent（无工具）
LLM_API_KEY="sk-xxx" \
LLM_BASE_URL="https://api.minimaxi.com/anthropic" \
mix tui

# 带 Agent + 工具（推荐）
LLM_API_KEY="sk-xxx" \
LLM_BASE_URL="https://api.minimaxi.com/anthropic" \
LLM_TOOLS="shell,read_file,write_file,list_dir,glob,grep" \
LLM_WORKING_DIR="/your/project" \
mix tui
```

### 环境变量说明

| 变量 | 必填 | 说明 | 默认值 |
|------|------|------|--------|
| `LLM_API_KEY` | 连接 Agent 时必填 | LLM API 密钥 | — |
| `LLM_BASE_URL` | 否 | API 地址（自定义端点时填写） | Anthropic 官方 |
| `LLM_MODEL_ID` | 否 | 模型 ID | `MiniMax-M2.7` |
| `LLM_SYSTEM_PROMPT` | 否 | 系统提示词 | `你是一个 AI 助手。` |
| `LLM_TOOLS` | 否 | 启用工具，逗号分隔 | 无 |
| `LLM_WORKING_DIR` | 否 | Agent 工作目录（文件工具的根路径） | 当前目录 |

### 可用工具

| 工具名 | 功能 |
|--------|------|
| `shell` | 执行 shell 命令 |
| `read_file` | 读取文件 |
| `write_file` | 写入文件 |
| `edit_file` | 编辑文件（字符串替换） |
| `list_dir` | 列出目录 |
| `glob` | 文件模式匹配 |
| `grep` | 搜索文件内容 |
| `ask_user` | Agent 向用户提问（触发弹窗） |
| `write_todos` | Agent 写入 Todo 列表 |
| `compact_conversation` | 压缩对话历史 |
| `task` | 创建子任务 |

---

## 代码集成

如果需要在 Elixir 代码中嵌入 TUI：

```elixir
# 最简集成
{:ok, session} = CMDC.create_agent(
  model: "anthropic:claude-sonnet-4-5",
  tools: [CMDC.Tool.Shell, CMDC.Tool.ReadFile],
  provider_opts: [api_key: System.get_env("ANTHROPIC_API_KEY")],
  working_dir: "/my/project"
)

CmdcTui.start(session: session)

# 也可以不传 session，仅用作本地输入测试
CmdcTui.start()
```

### `CmdcTui.start/1` 参数

| 参数 | 类型 | 说明 |
|------|------|------|
| `:session` | `pid` | CMDC Agent session pid |
| `:session_id` | `String.t()` | 会话 ID（显示在状态栏） |
| `:model_name` | `String.t()` | 模型名称（显示在状态栏） |

---

## 界面说明

### 布局

```
┌──────────────────────────┬─────────────────┐
│      ChatPanel            │  侧栏 (Tools /  │
│  （消息列表 + 输入框）       │     Todo)       │
├──────────────────────────┴─────────────────┤
│              StatusBar                      │
└─────────────────────────────────────────────┘
```

> 侧栏在终端宽度 **≥ 80 列**时自动显示。

### ChatPanel

- **◆ AI** — AI 回复，自动检测并渲染 Markdown（代码块、标题、列表等）
- **▶ You** — 用户消息
- **⚙ tool_name** — 工具执行结果摘要
- **AI is thinking...** — Agent 思考中的动态 spinner
- **◆ AI (streaming…)** — 流式输出进行中

### Tools 面板（侧栏）

按 `Tab` 切换到 Tools 面板，显示每次工具调用的：

| 列 | 内容 |
|----|------|
| Name | 工具名称 |
| Args | 参数摘要（前 30 字符） |
| Status | `⏳ running` / `✓ done` / `✗ error` / `⊘ blocked` |
| Time | 耗时（ms 或 s） |

### Todo 面板（侧栏）

按 `Tab` 切换到 Todo 面板，显示 Agent 的任务列表（需 Agent 调用 `write_todos` 工具）：

- ☐ 未开始（白色）
- **▶ 进行中**（黄色加粗）
- ✓ 已完成（绿色）
- ✗ 已取消（灰色）

### StatusBar

底部状态栏从左到右依次显示：

```
│ idle │ session: live │ MiniMax-M2.7 │ in: 1024 / out: 256 │ $0.001 │ Enter:发送 ...
```

---

## 快捷键

### 全局

| 快捷键 | 功能 |
|--------|------|
| `Enter` | 发送消息 |
| `Shift+Enter` | 换行（多行输入） |
| `Ctrl+S` | 发送消息（备用） |
| `Ctrl+P` | 打开命令面板 |
| `Tab` | 切换侧栏（Tools ↔ Todo） |
| `↑ / ↓` | 滚动消息历史 |
| `PgUp / PgDn` | 快速滚动（每次 5 条） |
| `Ctrl+C` | 退出 TUI |

### 命令面板（Ctrl+P）

| 操作 | 功能 |
|------|------|
| 输入文字 | 过滤命令（前缀匹配） |
| `↑ / ↓` | 上下选择 |
| `Enter` | 执行选中命令 |
| `Esc` | 关闭面板 |

### Slash 命令（输入框）

在输入框中输入 `/` 触发内联自动补全，继续输入过滤：

| 命令 | 别名 | 功能 |
|------|------|------|
| `/help` | `/h`, `/?` | 显示帮助 |
| `/clear` | `/cls` | 清空对话历史 |
| `/model` | `/m` | 查看当前模型 |
| `/compact` | — | 压缩对话历史 |
| `/stop` | `/abort`, `/cancel` | 终止 Agent |
| `/tools` | — | 切换到 Tools 面板 |
| `/todos` | — | 切换到 Todo 面板 |
| `/quit` | `/exit`, `/q` | 退出 TUI |

### HITL 审批弹窗（ApprovalDialog）

当 Agent 请求操作审批时弹出：

| 按键 | 功能 |
|------|------|
| `y` | 批准 |
| `n` | 拒绝 |
| `Esc` | 拒绝并关闭 |

### Agent 提问弹窗（AskUserDialog）

当 Agent 调用 `ask_user` 工具时弹出：

| 按键 | 功能 |
|------|------|
| 输入文字 | 回答内容 |
| `1`~`9` | 快速选择选项（有选项时） |
| `Enter` | 提交回答 |
| `Esc` | 取消（提交空字符串） |

---

## 要求

- Elixir `~> 1.17`
- [CMDC](https://hex.pm/packages/cmdc) `~> 0.1`
- [ExRatatui](https://hex.pm/packages/ex_ratatui) `~> 0.5.1`（含预编译 NIF，无需 Rust 工具链）
- 终端支持 256 色 / TrueColor（推荐）
- 终端宽度 **≥ 80 列**可显示侧栏

---

## 常见问题

**Q: 侧栏没有显示？**

终端宽度不足 80 列时侧栏自动隐藏。请把终端窗口拉宽后重启 TUI。

**Q: 工具面板没有内容？**

需要在创建 Agent 时配置 `tools` 参数，或在 `mix tui` 时设置 `LLM_TOOLS` 环境变量。Agent 调用工具后侧栏才会有数据。

**Q: 输入中文有延迟？**

请使用 `mix tui` 直接启动，不要用 `iex -S mix`。`iex` 的 IO 系统会与 TUI 争抢终端控制权，导致输入延迟。

**Q: Shift+Enter 无法换行（跟 Enter 一样发送了）？**

你的终端不支持 Kitty keyboard protocol（CSI u）。TUI 启动时会自动尝试启用该协议，但部分终端不支持：

- **macOS Terminal.app** — 不支持，Enter 和 Shift+Enter 无法区分，请用 `Ctrl+S` 发送
- **iTerm2** — 需手动开启：Preferences → Profiles → Keys → 勾选 "Report modifiers using CSI u"
- **WezTerm / Kitty** — 原生支持，无需配置

如果 Shift+Enter 始终无法换行，可以用 `Ctrl+S` 作为备用发送键。

**Q: AI 回复没有渲染 Markdown？**

只有包含换行、代码块（` ``` `）、加粗（`**`）、列表（`- `）或标题（`#`）的内容才会用 Markdown 渲染，纯文本直接显示。
