日期:2026-03-27 分支:
feat/web-fancy(基于 masteraa7fa0c) 参会方:方案拥护者、魔鬼代言人、工程实践者、用户视角
| 功能 | 决议 | 备注 |
|---|---|---|
| 代码块语法高亮 + 复制按钮 | ✅ P0 | highlight.js,纯前端,1-2天 |
| 停止生成按钮 | ✅ P0 | 后端 /cancel 已就绪,前端 1天 |
| 结构化进度展示 | ✅ P1 | 补 ProgressEventHandler 管道,3-4天 |
| 文件上传下载 | ✅ P2 | HTTP 通道,10MB 限制,5-8天 |
| 增强输入区 | ✅ P2 | Markdown 工具栏 + 预览,2-3天 |
| Settings 页面 | ✅ P2 | localStorage,主题+字号,2天 |
| WS 断线重连 | ✅ P1 | 前端 exponential backoff,1天 |
| 功能 | 决议 | 理由 |
|---|---|---|
| Tiptap 富文本编辑器 | ❌ 全员反对 | 富文本→纯文本有损、LLM 侧无收益、竞品均用纯文本、维护成本高 |
| framer-motion 动画库 | ❌ | 纯 CSS @keyframes 可替代 |
| shadcn/ui / MUI | ❌ | 过重,手写组件足够 |
| 文件走 WS 传输 | ❌ | WS 64KB 限制(client→server),HTTP 更标准 |
| 功能 | 决议 | 理由 |
|---|---|---|
| 真流式输出(打字机效果) | ⏸️ 延后 | engine.go CollectStream 贪心消费 + Run 循环多轮迭代架构约束,改 engine 核心循环风险大,ROI 不如进度展示 |
| Sidebar 会话列表 | ⏸️ 延后 | 功能填充不足前不做布局重构 |
// engine.go:91-92 — 字段定义
ProgressEventHandler func(event *ProgressEvent)
// engine.go:367-373 — engine 内部调用
if cfg.ProgressEventHandler != nil {
cfg.ProgressEventHandler(&ProgressEvent{...})
}
// engine_wire.go:136-143 — 只注入了 ProgressNotifier
if autoNotify {
cfg.ProgressNotifier = func(lines []string) { ... }
// ← ProgressEventHandler 从未被注入!
}
影响:StructuredProgress(Phase/ActiveTools/CompletedTools)已构建但被丢弃,需在 buildMainRunConfig 中补接。
// agent.go:960 — /cancel 命令拦截
if strings.TrimSpace(strings.ToLower(msg.Content)) == "/cancel" {
cancelKey := msg.Channel + ":" + msg.ChatID + ":" + msg.SenderID
ch.(chan struct{}) <- struct{}{} // → reqCancel() → context cancellation
}
前端只需发送 {"type":"message","content":"/cancel"} 即可触发。
当前前端把每条 type:"progress" WS 消息当作独立气泡追加,导致 5-20 条进度消息淹没聊天区。需改为"进度区域"概念,独立于消息列表。
当前收到任意 WS 消息就关闭 loading,应在收到最终回复(text/card)时才关闭。
// engine.go:245-254
func generateResponse(...) (*llm.LLMResponse, error) {
eventCh, _ := streaming.GenerateStream(...)
return llm.CollectStream(ctx, eventCh) // ← 所有 StreamEvent 被聚合
}
真流式需要穿透此处,但 Run 循环的多轮迭代(tool_calls vs final text)使改造成本巨大。
c.conn.SetReadLimit(65536) 只限 client→server 方向,server→client 无限制。文件传输走 HTTP 不受影响。
| # | 任务 | 层 | 天数 | 依赖 |
|---|---|---|---|---|
| 1 | 代码块语法高亮 + 复制按钮 | 前端 | 1 | 无 |
| 2 | 停止生成按钮 | 前端+后端 | 1 | 无 |
| 3 | 修复进度消息堆积 Bug | 前端 | 0.5 | 无 |
| 4 | 修复 loading 状态时机 | 前端 | 0.25 | 无 |
| 5 | 消息时间戳显示 | 前端 | 0.25 | 无 |
| 6 | 智能滚动(上滚暂停自动滚动) | 前端 | 0.5 | 无 |
| 7 | WS 断线自动重连 | 前端 | 0.5 | 无 |
| # | 任务 | 层 | 天数 | 依赖 |
|---|---|---|---|---|
| 8 | 接通 ProgressEventHandler 管道 | 后端 | 1 | Phase 1 |
| 9 | WS 协议扩展(progress_structured 类型) | 后端 | 0.5 | #8 |
| 10 | 前端进度面板(折叠式工具列表) | 前端 | 2 | #9 |
| 11 | 进度区到最终回复的过渡动画 | 前端 | 0.5 | #10 |
| # | 任务 | 层 | 天数 | 依赖 |
|---|---|---|---|---|
| 12 | 文件下载(GET /api/files/:id) | 后端 | 1 | Phase 2 |
| 13 | 文件上传(POST /api/files/upload + 拖拽+粘贴+按钮) | 后端+前端 | 3 | #12 |
| 14 | 增强输入区(Markdown 工具栏 + 预览切换) | 前端 | 2 | 无 |
| 15 | Settings 页面(主题+字号,localStorage) | 前端 | 2 | 无 |
| 16 | Todo checklist 渲染(前端解析 [x]/[ ]) | 前端 | 1 | 无 |
| 17 | 消息历史分页/虚拟滚动 | 前端+后端 | 2 | 无 |
| # | 任务 | 层 | 天数 | 备注 |
|---|---|---|---|---|
| 18 | 真流式输出(token 级打字机) | 全栈 | 10-15 | 需改造 engine.go 核心 |
| 19 | Sidebar 布局 + 会话列表 | 全栈 | 3-5 | 需后端会话管理 API |
| 20 | 移动端适配 | 前端 | 2-3 | 响应式布局 |
总工期估算:14-19 天(Phase 1-3),不含 Phase 4
// 服务端 → 客户端(wsMessage 扩展)
type wsMessage struct {
Type string `json:"type"` // "text","progress","progress_structured","card","file"
ID string `json:"id,omitempty"`
Content string `json:"content,omitempty"`
TS int64 `json:"ts,omitempty"`
Progress *progressPayload `json:"progress,omitempty"` // 新增:结构化进度
File *filePayload `json:"file,omitempty"` // 新增:文件元数据
}
type progressPayload struct {
Phase string `json:"phase"` // thinking|tool_exec|compressing|done
Iteration int `json:"iteration"`
ActiveTools []toolProgress `json:"active_tools"`
CompletedTools []toolProgress `json:"completed_tools"`
}
type toolProgress struct {
Name string `json:"name"`
Label string `json:"label"`
Status string `json:"status"` // running|done|error
Elapsed int `json:"elapsed_ms"`
}
type filePayload struct {
ID string `json:"id"`
Name string `json:"name"`
Size int64 `json:"size"`
Mime string `json:"mime"`
URL string `json:"url"`
}
// 客户端 → 服务端(wsClientMessage 扩展)
type wsClientMessage struct {
Type string `json:"type"` // "message","cancel"
Content string `json:"content,omitempty"`
FileIDs []string `json:"file_ids,omitempty"` // 新增:附件引用
}
| 端点 | 方法 | 说明 |
|---|---|---|
/api/files/upload | POST | multipart 文件上传,10MB 限制 |
/api/files/:id | GET | 文件下载 |
| 风险 | 严重度 | 缓解 |
|---|---|---|
| 停止按钮只断 WS 不发 /cancel | 🔴 高 | 测试用例覆盖;前端走 WS 消息不 close |
| 进度推送频率过高卡顿 | 🟡 中 | 后端节流 ≥500ms;前端 requestAnimationFrame 合并 |
| ProgressEventHandler 影响飞书渠道 | 🟡 中 | 按 channel 条件注入,飞书不设置则不触发 |
| 打字机动画不完整 Markdown | 🟡 中 | 最终回复直接显示,不做打字机(准流式方案已弃用打字机) |
| 文件上传恶意文件 | 🟡 中 | MIME 校验 + 大小硬限 + 存储隔离 |
新增字段全部 omitempty,新增 type 值不影响旧客户端。旧客户端忽略未知 type 即可。WS 协议为真流式预留 stream_start/stream_delta/stream_end type(不实现,协议兼容)。
会议结束,待陛下批准后启动实施。