Skip to content

Commit 1f0b8fa

Browse files
author
王璨
committed
feat: add Web UI with React + Vite + Tailwind
- Abstract UiBackend interface to support multiple UI backends - Add TuiBackend adapter wrapping existing TuiApp for CLI mode - Implement WebUiBackend with HTTP + WebSocket server - Build React SPA with streaming chat, tool cards, permission dialogs - Add sidebar with session management, MCP browser, settings panels - Support image upload (drag/paste/click), slash command panel, toast notifications - Add --web and --web-port CLI flags for web mode - Integrate frontend build into main build pipeline - Update README with Web UI documentation
1 parent ff61db3 commit 1f0b8fa

39 files changed

Lines changed: 5998 additions & 73 deletions

README.md

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</p>
1414

1515
<p align="center">
16-
面向数字工作的 DeepSeek 交互式命令行 Agent Harness。<br />
16+
面向数字工作的 DeepSeek 交互式 AI Agent Harness。支持 **终端 (TUI)****浏览器 (Web UI)** 两种交互方式。<br />
1717
从 Agentic Workflow 到上下文管理再到记忆系统,dscode 只做 DeepSeek 模型的 Harness。
1818
</p>
1919

@@ -58,6 +58,7 @@ dscode --version
5858
dscode
5959
```
6060

61+
6162
安装包:<https://www.npmjs.com/package/@wangcan26/dscode>
6263

6364
</td>
@@ -71,6 +72,7 @@ npm run build
7172
node dist/dscode.mjs
7273
```
7374

75+
7476
适合本地开发、调试和二次修改。
7577

7678
</td>
@@ -86,10 +88,46 @@ node dist/dscode.mjs
8688
/config model deepseek-v4-pro
8789
```
8890

91+
8992
`/config` 命令写入的配置会持久化到 `~/.dscode/config.json`,后续启动无需重复设置。
9093
其中 `cwd` 会记录本次启动对应的项目目录;只有在你手动执行 `/config cwd <path>` 时,才会对当前项目复用该目录。
9194
声明式配置单独放在 `settings.json`:用户级 `~/.dscode/settings.json`,项目级 `<project>/.dscode/settings.json`
9295

96+
## Web UI
97+
98+
除了终端交互,dscode 也提供简洁现代的 **浏览器界面**,完整支持 TUI 的全部功能:
99+
100+
**npm 全局安装:**
101+
102+
```bash
103+
dscode --web # 默认端口 3000
104+
dscode --web --web-port 8080 # 自定义端口
105+
```
106+
107+
**本地构建运行:**
108+
109+
```bash
110+
npm run build:web # 先构建前端
111+
node dist/dscode.mjs --web # 启动 Web 模式
112+
```
113+
114+
浏览器打开 `http://localhost:3000` 即可使用。Web UI 包含:
115+
116+
| 功能 | 说明 |
117+
| --- | --- |
118+
| 流式对话 | 实时显示 thinking + 文本输出,与 TUI 体验一致 |
119+
| 工具调用 | 内联卡片展示工具名、参数和结果(成功/失败图标) |
120+
| 权限确认 | 图形化弹窗,Allow / Always Allow / Deny |
121+
| Slash 命令 | 输入 `/` 弹出命令面板,支持键盘导航和点击 |
122+
| 会话管理 | 侧边栏可视化浏览、保存、加载、删除会话 |
123+
| MCP 浏览器 | 查看已连接 MCP 服务器和工具列表 |
124+
| 配置管理 | 图形化切换模型、Thinking Level 和 API Key |
125+
| 图片上传 | 支持拖拽 / 粘贴 / 点击上传图片 |
126+
| 响应式布局 | 适配桌面和移动端 |
127+
| 深色主题 | 默认 GitHub 风格暗色主题,保护眼睛 |
128+
129+
> Web UI 与 CLI 共享同一套 Agent 后端,所有功能完全一致。切换模式时无需重新配置。
130+
93131
## What you get
94132

95133
| 能力 | 说明 |
@@ -104,6 +142,7 @@ node dist/dscode.mjs
104142
| MCP 协议 | 作为 MCP Client 连接外部工具服务器(stdio / streamable-http / legacy SSE) |
105143
| 图片 OCR | 基于 tesseract.js 的文字提取,支持中英文 |
106144
| 两级配置 | 用户级 + 项目级配置,环境变量覆盖 |
145+
| Web UI | 浏览器图形界面,实时流式对话,权限弹窗,侧边栏管理 |
107146

108147
## Digital work, not just coding
109148

@@ -131,6 +170,7 @@ dscode 通过 **MCP (Model Context Protocol)** 连接外部工具,把 DeepSeek
131170
/config help # 查看帮助
132171
```
133172

173+
134174
默认使用 `deepseek-v4-flash`,推荐切换为 `deepseek-v4-pro` 获得更强的 reasoning / thinking 能力:
135175

136176
| 模型 | 特点 |
@@ -156,6 +196,7 @@ DeepSeek reasoning 模型通过 `thinkingLevel` 控制思考强度,切换模
156196
/config thinking xhigh
157197
```
158198

199+
159200
## MCP connector
160201

161202
| 能力 | 说明 |
@@ -192,6 +233,7 @@ DeepSeek reasoning 模型通过 `thinkingLevel` 控制思考强度,切换模
192233
}
193234
```
194235

236+
195237
| 字段 | 类型 | 必填 | 说明 |
196238
| --- | --- | --- | --- |
197239
| `command` | string | stdio 必填 | 启动命令 |
@@ -221,6 +263,7 @@ dscode 采用 **Agent as OS** 架构:Drivers(内核模块,始终加载)
221263
└── SKILL.md
222264
```
223265

266+
224267
**SKILL.md 格式:**
225268

226269
```yaml
@@ -238,6 +281,7 @@ When the user asks about git workflows, use these tools.
238281
Always push the branch before creating a PR.
239282
```
240283

284+
241285
- YAML frontmatter 声明 `name``description``tools` 白名单
242286
- 激活时 instructions 注入 system prompt,根据白名单从已加载 Driver 中筛选工具
243287
- 启动时自动激活所有扫描到的 Skill,也支持 `/skills activate <name>` 手动激活
@@ -258,7 +302,7 @@ Always push the branch before creating a PR.
258302
| `/cost` | 显示 token 用量 |
259303
| `/compact` | 手动压缩上下文 |
260304

261-
退出:输入 `exit` 或按 `Ctrl+C` 两次。中断生成:单次 `Ctrl+C`
305+
退出:输入 `exit` 或按 `Ctrl+C` 两次。中断生成:单次 `Ctrl+C`Web UI 中命令对应侧边栏和按钮操作。
262306

263307
## Configuration reference
264308

@@ -282,6 +326,7 @@ Always push the branch before creating a PR.
282326
}
283327
```
284328

329+
285330
### `settings.json`
286331

287332
```jsonc
@@ -299,6 +344,7 @@ Always push the branch before creating a PR.
299344
}
300345
```
301346

347+
302348
### Environment variables
303349

304350
| 环境变量 | 对应配置 |
@@ -336,9 +382,10 @@ src/
336382
├── skills/ # Skill 管理器 + SKILL.md 加载器
337383
├── mcp/ # MCP 客户端(stdio / streamable-http / legacy SSE)+ 管理器 + MCP App host/runtime
338384
├── permissions/ # 权限拦截
339-
└── ui/ # REPL、流式渲染、slash commands
385+
└── ui/ # TUI (终端)、Web UI (浏览器)、流式渲染、slash commands
340386
```
341387

388+
342389
## Permission model
343390

344391
| 工具 | 默认策略 |
@@ -357,9 +404,22 @@ src/
357404
npm start
358405
npm run typecheck
359406
npm test
360-
```
361407

362-
**技术栈:** TypeScript + tsx · [pi-agent-core](https://www.npmjs.com/package/@mariozechner/pi-agent-core) · [pi-ai](https://www.npmjs.com/package/@mariozechner/pi-ai) · DeepSeek API
408+
**Web UI 开发:**
409+
410+
```bash
411+
# 安装前端依赖
412+
cd web && npm install && cd ..
413+
414+
# 构建前端 + 启动 Web 模式
415+
npm run build:web
416+
dscode --web
417+
418+
# 开发模式(前端 HMR + 后端热重载)
419+
npm run dev:web
420+
``````
421+
422+
**技术栈:** TypeScript + tsx · React + Vite + Tailwind CSS (Web UI) · [pi-agent-core](https://www.npmjs.com/package/@mariozechner/pi-agent-core) · [pi-ai](https://www.npmjs.com/package/@mariozechner/pi-ai) · DeepSeek API
363423
364424
欢迎贡献!请先阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 了解理念对齐和 PR 流程。
365425
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-05-25
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
## Context
2+
3+
dscode 目前是纯 TUI 应用,使用 `@earendil-works/pi-tui` 渲染终端界面。Harness(`src/core/harness.ts`)直接实例化 `TuiApp` 并与之耦合。要支持 Web 界面,需要在 Harness 与 UI 之间引入抽象层,使同一套 Agent 逻辑可以驱动不同的 UI 后端。
4+
5+
当前架构:
6+
```
7+
main.ts → Harness → [Agent + 各层级] → TuiApp (pi-tui)
8+
```
9+
10+
目标架构:
11+
```
12+
main.ts → Harness → [Agent + 各层级] → UiBackend (interface)
13+
├── TuiApp (CLI, 现有)
14+
└── WebUiBackend (新增)
15+
├── HTTP Server (静态资源 + REST API)
16+
└── WebSocket Server (实时事件流)
17+
18+
React SPA (浏览器)
19+
```
20+
21+
## Goals / Non-Goals
22+
23+
**Goals:**
24+
- 抽象 `UiBackend` 接口,使 Harness 不依赖具体 UI 实现
25+
- 实现 Web 版本,完整复现 TUI 所有交互功能(对话、流式输出、thinking、工具调用、权限确认、slash 命令、MCP 浏览、配置管理、图片上传、会话管理)
26+
- WebSocket 实时双向通信,支持流式文本 delta、thinking 展示、工具调用状态
27+
- 前端界面简洁现代,响应式布局,适合桌面和移动端
28+
- CLI 模式保持完全不变,`dscode` 默认行为不变,新增 `--web` 参数启动 Web 模式
29+
- 复用现有所有层级(Session、Context、Memory、Skills、Permissions、MCP),不做任何行为变更
30+
31+
**Non-Goals:**
32+
- 不修改现有 TUI 的功能或行为
33+
- 不做多用户支持(单用户、单会话,与 CLI 一致)
34+
- 不做用户认证/授权
35+
- 不重构 Agent loop 或工具执行逻辑
36+
- 不做 SSE 或 HTTP polling(统一走 WebSocket)
37+
38+
## Decisions
39+
40+
### 1. UI 抽象层:`UiBackend` 接口
41+
42+
**决策**:定义 `UiBackend` 接口,包含 Harness 需要的所有 UI 回调方法。`TuiApp``WebUiBackend` 各自实现。
43+
44+
**替代方案**
45+
- 事件总线(EventEmitter):让 Harness emit 事件,UI 订阅。但是 Harness 有 `getPromptPermission()` 这种需要返回值的方法,纯事件不够用。
46+
- 不抽象,Web 模式另起 `WebHarness`:代码重复过多,后续维护成本高。
47+
48+
**接口设计**(核心方法):
49+
```typescript
50+
interface UiBackend {
51+
// 生命周期
52+
start(): Promise<void>;
53+
waitForExit(): Promise<void>;
54+
shutdown(): Promise<void>;
55+
56+
// 对话渲染
57+
addUserMessage(text: string): void;
58+
startAssistantMessage(): void;
59+
thinkingDelta(delta: string): void;
60+
textDelta(delta: string): void;
61+
toolStart(name: string, args: unknown): void;
62+
toolEnd(name: string, result: unknown, isError: boolean): void;
63+
finishAssistantMessage(): void;
64+
65+
// 系统消息
66+
addInfo(text: string): void;
67+
addError(text: string): void;
68+
69+
// 权限
70+
getPromptPermission(): (toolName: string, preview: string, args: unknown) => Promise<PermissionPromptResult>;
71+
72+
// 图片
73+
addPendingImage(image: ImageContent): void;
74+
clearPendingImages(): ImageContent[];
75+
76+
// 控制
77+
focusEditor(): void;
78+
clearConversationView(): void;
79+
80+
// MCP
81+
setMcpManager(mcpManager?: MCPManager): void;
82+
openMcpBrowser(): void;
83+
84+
// 加载状态
85+
showLoader(text: string): void;
86+
hideLoader(): void;
87+
setLoaderText(text: string): void;
88+
setAbortHandler(handler: () => void): void;
89+
}
90+
```
91+
92+
### 2. WebSocket 协议设计
93+
94+
**决策**:基于 JSON 的简单消息协议,客户端到服务器叫 `Command`,服务器到客户端叫 `Event`
95+
96+
**替代方案**
97+
- Server-Sent Events (SSE):只支持单向推送,权限确认等需要双向交互时还要额外 HTTP 请求,增加复杂度。
98+
- Socket.io:功能丰富但依赖重,dscode 目标是轻量 Harness,用原生 `ws` 库即可。
99+
100+
**消息类型**
101+
102+
Client → Server (Commands):
103+
```typescript
104+
type ClientCommand =
105+
| { type: "chat"; text: string; images?: ImageAttachment[] }
106+
| { type: "abort" }
107+
| { type: "permission"; decision: "allow" | "always_allow" | "deny"; persistRule?: boolean }
108+
| { type: "slash"; command: string }
109+
| { type: "config"; key: string; value: string }
110+
| { type: "session"; action: "list" | "save" | "load" | "delete"; id?: string }
111+
| { type: "mcp"; action: "list" | "refresh" }
112+
```
113+
114+
Server → Client (Events):
115+
```typescript
116+
type ServerEvent =
117+
| { type: "user_message"; text: string }
118+
| { type: "assistant_start" }
119+
| { type: "thinking_delta"; delta: string }
120+
| { type: "text_delta"; delta: string }
121+
| { type: "tool_start"; name: string; args: unknown }
122+
| { type: "tool_end"; name: string; result: string; isError: boolean }
123+
| { type: "assistant_end" }
124+
| { type: "info"; text: string }
125+
| { type: "error"; text: string }
126+
| { type: "permission_prompt"; toolName: string; preview: string }
127+
| { type: "loader"; state: "show" | "hide"; text?: string }
128+
| { type: "config"; data: ConfigData }
129+
| { type: "sessions"; data: SessionInfo[] }
130+
| { type: "mcp_state"; servers: McpServerInfo[] }
131+
| { type: "model"; name: string }
132+
| { type: "ready" } // 连接就绪
133+
```
134+
135+
### 3. 前端技术选型
136+
137+
**决策**:React 18 + Vite + Tailwind CSS
138+
139+
**替代方案**:
140+
- Vue 3:同样优秀,但 React 生态更丰富,npm 下载量更高,社区组件更多。
141+
- Svelte:编译体积小,但生态较小,不适合需要复杂状态管理的场景。
142+
- 纯 HTML/JS:开发效率低,难以维护复杂交互状态。
143+
144+
**状态管理**:使用 React Context + useReducer,不引入 Redux/Zustand 等第三方库(状态不复杂,避免过度工程)。
145+
146+
**UI 风格**:简洁现代,浅色/深色双主题。参考 Claude/ChatGPT 的聊天界面布局,侧边栏 + 主对话区。
147+
148+
### 4. 构建与部署
149+
150+
**决策**:前端构建产物输出到 `dist/web/`,后端 server 代码与现有 TypeScript 源码一起编译。`dscode --web` 启动时,HTTP server 从 `dist/web/` 提供静态文件。
151+
152+
**替代方案**:
153+
- 前端独立部署:增加运维复杂度,单机本地工具应自包含。
154+
- 开发模式代理:`npm run dev:web` 启动 Vite dev server + 后端,前端 HMR 开发体验好。
155+
156+
### 5. Harness 重构策略
157+
158+
**决策**:`Harness.run()` 根据启动参数创建对应的 `UiBackend` 实现。构造时注入 `UiBackend`
159+
160+
改动最小化:主要在 `src/core/harness.ts``src/core/main.ts` 两个文件。
161+
162+
## Risks / Trade-offs
163+
164+
- **[风险] WebSocket 连接断开时正在进行的 Agent 调用会丢失状态** → 服务器端保留 Agent 状态,客户端重连后通过 WebSocket 重新同步当前对话历史
165+
- **[风险] 大体积前端 bundle 影响首次加载体验** → Vite 代码分割 + gzip 压缩,首次加载控制在 500KB 以内
166+
- **[权衡] React 增加 node_modules 体积** → 仅 devDependencies,构建产物不包含 React 源码
167+
- **[权衡] WebSocket 协议设计为 JSON 文本而非二进制** → 流式文本场景 JSON 开销可接受(每个 delta 一条消息),实现简单、可调试
168+
- **[风险] 图片上传大文件可能导致内存压力** → 前端限制 20MB,base64 编码前校验
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
## Why
2+
3+
目前 dscode 仅有 TUI(终端交互)版本,用户必须在终端中操作,对不熟悉命令行的用户门槛较高,也无法在移动设备或浏览器环境中使用。提供一个 Web 版本能让更多用户便捷地使用 dscode 的全部功能,扩大受众面,同时保持与 CLI 版本一致的能力深度。
4+
5+
## What Changes
6+
7+
- **新增 Web 服务器**:基于 Node.js HTTP 服务器的 Web 后端,复用现有 Harness 全部层级(Agent Loop、Session、Context、Memory、Skills、Permissions、MCP)
8+
- **新增 Web 前端**:单页应用(SPA),提供简洁舒适的现代化 UI,完整呈现 TUI 的所有交互功能
9+
- **WebSocket 实时通信**:通过 WebSocket 实现流式输出(thinking + text delta)、工具调用状态、权限弹窗等实时交互
10+
- **会话管理界面**:可视化的会话列表、切换、保存、加载、删除
11+
- **Slash 命令面板**:将 TUI 的 `/` 命令转化为图形化的命令面板和快捷操作
12+
- **MCP 浏览器面板**:可视化浏览 MCP 服务器和工具列表
13+
- **配置管理界面**:图形化的模型切换、API Key 设置、thinking level 调整
14+
- **图片上传支持**:支持拖拽/粘贴/点击上传图片,替代 CLI 的 `/image` 命令
15+
- **权限确认弹窗**:将 TUI 的键盘选择转化为图形化的确认对话框
16+
17+
## Capabilities
18+
19+
### New Capabilities
20+
21+
- `web-server`: HTTP + WebSocket 服务器,负责服务静态前端资源、处理 API 请求、通过 WebSocket 转发 Agent 事件流
22+
- `web-frontend`: 单页 Web 前端应用,包含对话界面、侧边栏、命令面板、设置面板、MCP 浏览器等全部 UI 组件
23+
- `websocket-protocol`: WebSocket 消息协议定义,涵盖流式文本 delta、thinking、工具调用开始/结束、权限请求/响应、系统消息等事件类型
24+
25+
### Modified Capabilities
26+
27+
<!-- 现有 spec 无需修改,Web 版本是新增能力,在 Harness 层之上增加新的 UI 通道,不影响现有各层的行为 -->
28+
29+
## Impact
30+
31+
- **新增依赖**:前端框架(React/Vue)、构建工具(Vite)、WebSocket 库(ws)、HTTP 服务器
32+
- **Harness 层**:需小幅度重构以支持多个 UI 后端(CLI + Web),主要是抽象 UI 接口
33+
- **现有代码**`src/core/harness.ts` 需要支持双模式启动(`--web` 参数);`src/ui/` 下新增 `web/` 目录
34+
- **构建流程**:新增前端构建步骤,与现有 `scripts/build.mjs` 集成
35+
- **无破坏性变更**:CLI 模式保持不变,Web 模式通过 `--web``--web-port` 参数启动

0 commit comments

Comments
 (0)