175 lines
17 KiB
Markdown
175 lines
17 KiB
Markdown
# aichat 可用性与交互成本研究(2026-09-29)
|
|
|
|
> 三动线走查(提问与恢复/回复消费/多会话与审批)+ 真实异常循环截图实证 + 2026-09-28 走查合并排序。
|
|
> 走查代理产出,主代理汇总核验。守 session-role-diagnose-only,除标注「已修」外未改代码。
|
|
> 前置:《aichat 交互体验走查(2026-09-28)》(同目录)17 项经核验全部未修,本文合并排序不重复展开。
|
|
|
|
**总体定性**:aichat 的交互基建(per-conv 状态机/流式 memo/审批兜底/失败保文)成熟度高,当前使用成本集中在四个结构性热点——**改措辞动线整体断裂**(编辑死链+无重生成+无历史重发)、**停止能力只存在于文本流出的窄窗口**(工具执行期不可干预)、**异常循环无机制拦截**(G2 熔断对写删交替结构性漏网,截图实证)、**多会话「需要我」信号全链缺失**(后台审批黑洞+状态伪装+零通知,事件路由与状态层按单会话设计)。前两者对应高频动作失能与信任伤害,后两者分别对应成本失控与多任务模式失效。
|
|
|
|
## 一、真实异常循环实证:「写完就删」跑到轮次耗尽,熔断全程未触发
|
|
|
|
用户截图(u-switch sn-kimi 渠道):AI 执行「清理临时目录」任务时陷入 `rename→delete→write→list→delete→write` 循环,AI 自己在气泡中承认「我陷入写完就删的异常循环」,系统零介入,直到 5/5 轮次耗尽自然停止。每轮 36k in · 0 cache(kimi 渠道缓存未命中),循环期间每轮全价。
|
|
|
|
### 根因:G2 熔断签名机制对「写删交替」结构性漏网(实证)
|
|
|
|
`agentic/helpers.rs:275` `tool_call_signature`:grep/read_file/run_command/list_dir/http_request/fetch_url 有特化签名,**write_file/delete_file/rename_file 走兜底分支 `name + args 全量 JSON`**。
|
|
|
|
截图序列 6 个调用(恰达 REPETITION_MIN_SAMPLE=6 判定门槛)逐一套签名:
|
|
|
|
| 调用 | 签名形态 |
|
|
|------|---------|
|
|
| rename_file(A→B) | 兜底,唯一 |
|
|
| delete_file(B) | 兜底,唯一 |
|
|
| write_file(A, content v1) | 兜底,content 全文入签名 |
|
|
| list_directory(dir) | path 特化,唯一 |
|
|
| delete_file(A) | 兜底,与 delete_file(B) 路径不同 → 唯一 |
|
|
| write_file(A, content v2) | content 相同则与 v1 同(计 2);微调则唯一 |
|
|
|
|
判定条件(`helpers.rs:325`):唯一率 <0.4 或单签名 ≥3 次。实测:唯一率 5/6≈0.83(全异时 1.0),单签名最高 2 次——**两个条件全程不满足,连 WARN_FIRST 警示都不触发**。采样窗口 STALL_BREAKER_SAMPLE_SIZE=12(`mod.rs:251`)、THRESHOLD=3、两段式先警示再熔断的设计目标死「同调用反复重试」,而本例本质是**目标打架**(旧清理目标反复作用于自己刚产出的文件),签名层面完全等同于「持续翻新的正常探索」。
|
|
|
|
模型侧:kimi 弱规划能力(B 路线范畴)无法维持「刚产出的文件不清理」约束;机制侧:现有熔断模式盲区可治——
|
|
|
|
**改进方向(登记待办)**:G2 增加「破坏性回环」检测——同一规范化路径上「write 后 N 步内 delete / delete 后 N 步内重写」计数,达阈值警示再熔断。该模式与正常探索天然互斥(正常清理不删自己刚写的文件),误杀率极低,可拦住整类自毁循环。
|
|
|
|
### 循环场景的视觉层问题(并入动线清单)
|
|
|
|
- C1. 一轮动作拆成「AI 文本气泡+1 工具卡」碎序列,5 轮循环=10+ 块,滚动成本高(P3,与 R-13 同源)
|
|
- C2. delete_file 与普通卡视觉权重接近,「AI 删自己刚写的文件」异常序列无聚合警示(P2)
|
|
- C3. 右上角 agentic 进度文案实证为 `agenticProgressNoPending`(AiChat.vue:218-224):「🔄 循环 5 · ✅5个工具完成」——只报轮次与完成数,零异常信号;异常循环期间它给用户的观感是「一切正常推进」,与实际自毁形成反差。max_rounds 未透传(9-28 已知 #4 关联),第 5 轮是否到顶不可知
|
|
|
|
## 二、动线一:提问与失败恢复(14 项)
|
|
|
|
正面确认:排队动线完整(入队/编辑/插队/清空/30s 超时强发)、发送失败完整回填文本+图片+mention、生成中可自由切换会话、错误气泡分级出「重试/去设置」、未发送输入有 beforeunload 保护。
|
|
|
|
### P1
|
|
|
|
- I1. **停止按钮只在「文本正在流出」窄窗口存在**——`ChatInput.vue:290-297` stopBtnState 仅 `streaming && !textIdle` 显停止;textIdle 在 300ms 无文本 delta 即 true(`useAiPendingState.ts:19-26`),工具执行期(30s heartbeat 无 delta)与轮间 LLM 等待期按钮回落发送图标;grep stopChat 全组件仅 ChatInput 两处,无其他停止入口。**工具跑错方向时用户不可干预,只能等 90s 看门狗或眼看副作用继续**(截图循环正是此场景:5 轮自毁全程无停止窗口)。方向:停止态判定改读 per-conv generating 态,不依赖文本活跃
|
|
- I2. **编辑已发消息入口死链(=9-28 P1,本轮补全证据)**——`MessageItem.vue:98` 用户气泡仅 copy 无编辑按钮;`MessageList.vue:36` 声明 start-edit 全组件零调用;下游 EditingMsgId/编辑态发送/Esc 取消全链完整但不可达;`.ai-msg-edit-btn` 样式残留。git 实证 2c2b2d7 抽离 MessageItem 时按钮未迁移。改措辞重问只能整条重打
|
|
|
|
### P2
|
|
|
|
- I3. 停止无快捷键,Esc 不作用于生成(`AiChat.vue:351-365` 全局 Esc 仅处理 confirmDialog 与分离窗口);急停必须鼠标移 28px 小按钮(且按 I1 还会消失)
|
|
- I4. AiError 静默清空该会话排队消息(=9-28 #4)——`useAiLifecycleEvents.ts:167-171` filter 整队列,无提示不回填;对照 drainQueue 失败路径专门回填队首(useAiSend.ts:296-298),同域不一致
|
|
- I5. 未匹配错误原文直出(与 9-28 #7 同修点合并)——friendlyError 仅 4 类正则(aiShared.ts:26-32),不命中 return raw;429/额度耗尽落 unknown:英文原文直出+可重试,额度未恢复重试必再败(08-11 实证 429 占 55%)。方向:补 quota/rate_limit 分类+兜底通用文案,原文折叠为详情
|
|
- I6. 重新生成无 UI 入口——仅隐藏快捷键 Ctrl+R(AiChat.vue:324);i18n 文案已备(zh-CN/aiChat.ts:156)但零模板引用
|
|
- I7. 无输入历史快速重发——ArrowUp 仅用于浮层导航,无发送历史栈(grep inputHistory 全仓 0 命中)
|
|
|
|
### P3
|
|
|
|
- I8. 停止后队列继续 drain(`useAiLifecycleEvents.ts:161`),「停止」与「全部停下」预期不符,多轮排队场景继续耗额度;方向:停止时队列非空给一次性提示
|
|
- I9. 附件通道仅图片,文本/代码文件无入口(粘贴仅认 image/、拖入仅过滤图片、@ 仅三类实体)
|
|
- I10. 「可点继续重试」文案名实不符——重试是整轮重跑,已完成工具会重新执行(副作用重复风险);文案先对齐为「重试」
|
|
- I11. 输入框手工拉伸高度被下一次按键压回(autoResize 强制 120px,CSS 却允许 resize:vertical 至 200px)
|
|
- I12. 快捷键零提示无聚合说明(Ctrl+N/R/L/K 无任何 UI 提示)
|
|
- I13. 无「聚焦输入框」快捷键(Ctrl+K 去向是会话搜索)
|
|
|
|
### 本动线最大成本热点(3 条)
|
|
|
|
1. **改措辞/重问动线整体断裂**(I2+I6+I7):高频动作「换个问法」唯一路径是重打整条字;修 I2 一项回收最大成本
|
|
2. **停止能力窄窗**(I1+I3+I8):生成失控时用户实际不可干预,信任层面伤害
|
|
3. **失败恢复的可读性与不丢字承诺**(I4+I5+I10):每次失败额外付理解成本+重打成本
|
|
|
|
## 三、动线二:回复消费(13 项,其中 2 项已修)
|
|
|
|
正面确认:scroll anchor 策略成熟(跟随意图锁存+上滑不拽走+回底按钮)、归档/压缩段折叠克制、JSON 结果弱化为灰块、审批卡自动展开定位、失败命令输出默认展开。
|
|
|
|
### 已修(本会话,工作区待提交)
|
|
|
|
- R1. ~~read_file/list_directory 单行摘要为空~~——已补 TOOL_SUMMARIES 登记(useToolCard.ts),复用现有 i18n key
|
|
- R2. ~~流式与重载后分组折叠态不一致~~——默认折叠改造时已落地「签名变化幂等补齐」watch(ToolCardList.vue:158-168)
|
|
|
|
### P2
|
|
|
|
- R3. **长回复固定点击税**——>1500 字符默认折叠到 400px(MessageList.vue:674/687-693),读完整回复必点「展开」且不知道藏了多少;按钮文案仅「展开/收起」无剩余量提示。日常高频:每次消费长回复 +1 点击 +1 决策。方向:按钮带量提示(「展开 · 约 3 屏」)或首屏+提示条
|
|
- R4. **代码块无复制按钮/语言标注**——useMarkdown.ts:120-125 裸 pre>code;复制单段代码全靠手动划选(长块易断);语言仅存 class 不可见。取用代码是最高频取用物。方向:pre hover 复制按钮+语言角标(marked renderer 注入)
|
|
- R5. **历史回找零工具**——唯一搜索入口匹配仅会话标题(ai.ts:290-295);无会话内消息搜索;叠加多层折叠(工具组/长消息/归档段),找旧结论成本随会话长度线性上涨。方向:消息文本搜索(后端已持久化,加 IPC+高亮跳转)
|
|
- R6. **文件路径/grep 命中位置不可复制不可点击**——ToolResultBody.vue 全部纯 span;想把 AI 读过的文件拿去本地操作只能手动重敲。方向:file-bar path 点击复制+可选「打开」
|
|
- R7. C2(见一章):异常序列无聚合警示;多工具轮无「N 个工具 · M 失败」汇总行,失败信号分散单卡红边,10+ 卡需逐行扫
|
|
|
|
### P3
|
|
|
|
- R8. 流式中裸 diff 渲染为普通段落,完成后突变红绿代码块,阅读锚点丢失(wrapNakedDiff 仅完成态调用;流式 parseBlock 复用纯函数即可)
|
|
- R9. 单条消息内无目录/锚点,长方案型回复线性滚动找章节
|
|
- R10. 上翻阅读时新轮次事件强制收起正浏览的工具卡(collapseInactive 不判 isFollowingBottom)
|
|
- R11. 回到底部按钮无「新内容」信号,非跟随态下新回复到达无提示
|
|
- R12. write_file 完成后无 diff 回看;patch_file diff 截 120 行无展开入口
|
|
- R13. assistant 时间戳每条常显(~14px/条),信息价值低;方向:并入 hover 或间隔 >5min 才显
|
|
|
|
### 本动线最大成本热点(3 条)
|
|
|
|
1. **读完一条长回复的固定点击税**(R3+R4):全动线最高频重复成本
|
|
2. **历史回找零工具**(R5):长会话痛点无解
|
|
3. **单行折叠的信息量缺口**(R6+R7):「扫一眼判断它干了什么」退化为逐卡点开
|
|
|
|
## 四、动线三:多会话与审批(12 条)
|
|
|
|
三条 P1 经主代理独立核验(useAiEvents.ts:95-126、conv_state.rs:59-67 均复读确认)。
|
|
|
|
### P1
|
|
|
|
- V1. **侧栏只有「生成中」一个状态点,等审批伪装成在跑**——`ConversationSidebar.vue:134` 唯一状态指示 `ai-conv-gen-dot`;后端 `conv_state.rs:66` 注释明文「审批挂起期间 ConvState 仍为 Generating(审批是 loop 内暂停点,非独立状态)」;前端 `useAiEvents.ts:124-126` AiApprovalRequired 到达也 setConvState('generating')。多会话并行时「哪个任务卡在我这里」无法从列表判断,用户会无限等一个永远不动的会话。方向:ConvState 增加 awaiting_approval 态(后端设计决策需 revisiting)+侧栏三态徽标
|
|
- V2. **非活跃会话的审批事件被整体丢弃——后台会话审批是黑洞**——`useAiEvents.ts:95-118` isCurrent 守卫仅特判 AiCompleted/AiError/AiHelpRequired,AiApprovalRequired/AiDirAuthRequired 直接 return 不进 pendingApprovals;而浮层(ApprovalOverlay.vue:54)/系统浮窗(useApprovalPopup.ts:69)/顶栏徽标(TopBar.vue:15)全部只读 pendingApprovals;切会话时 useAiConversations.ts:460 还整体替换列表。并行任务每次等审批=任务停摆+人肉轮询。方向:pendingApprovals 改跨会话聚合(per-conv Map+汇总视图)
|
|
- V3. **后台会话完成/失败零通知**——`useAiEvents.ts:97-111` 后台终止仅 refresh+drain;grep unread|setTitle|notification 全仓无命中;托盘仅「显示主窗口/退出」两菜单(tray.rs:18-40),无 OS notification 插件。后台产出沉睡,多任务=人肉轮询。方向:后台终止类事件经现有 ai-toast 通道弹本地通知+托盘 tooltip
|
|
|
|
### P2
|
|
|
|
- V4. 系统浮窗审批跳过高风险二次确认——ToolCard 内 High 风险工具强制 useConfirm(useToolApproval.ts:26-29),浮窗 onApprove 直调 runApproval(ApprovalPopup.vue:421-423)无 HIGH_RISK 检查,且数字键「1」即批准首条(:263-293);注意力分散场景单键放行 run_command/delete_file
|
|
- V5. 审批卡片/浮层/浮窗均不显示所属会话——conversationId 数据已在(useAiToolEvents.ts:67)但 UI 从不渲染;「打开审批面板」落的又是主窗口当前活跃会话,未必是审批所属会话
|
|
- V6. 切会话清空全部审批计时器且重置倒计时;后台审批实际「永不超时」——switchConversation 等均调 clearAllApprovalTimers()(useAiConversations.ts:279),切回重新起算 15min;前端计时器是自动拒绝唯一驱动者(aiShared.ts:160-187),后端 cleanup_expired_approvals 仅 try_continue_agent_loop 入口惰性触发——没人碰永不清理。修 V2 时需一并重做,否则黑洞换位置
|
|
- V7. 排队消息仅活跃会话可见,无侧栏提示;且队列倒计时不走钟——AiChat.vue:69-70 只渲染当前会话队列,队列却按会话保留,切走彻底不可见(回来后 drain 造成重复发送);AiChat.vue:400-407 queueWaitSeconds 是读 Date.now() 的 computed 无响应式时钟,(Ns) 冻结,30s 超时强发按钮可能永不出现
|
|
- V8. 输入草稿全局单份跨会话串台——ChatInput.vue:148 单例 ref,切会话不动草稿;给 A 打半段话切到 B 顺手发出=错发进 B。方向:草稿按 activeConversationId 存取(小改大收益)
|
|
- V9. 会话搜索只匹配标题不含消息内容,placeholder 未披露范围(ai.ts:290-296)——与 R5 同修点合并
|
|
|
|
### P3
|
|
|
|
- V10. 重命名仅双击且提示只在标题为空时显;「...」菜单无重命名项;Ctrl+N 生成中静默失效(AiChat.vue:315-318 有 streaming 守卫)与按钮新建无守卫自相矛盾,与 F-09 真并发语义冲突
|
|
- V11. 分离窗口默认置顶(useAiWindow.ts:86)盖住其他应用;reattachPanel「一键收回全部分离窗」全仓零调用;分离窗切会话写共享 KV df-ai-active-conv,重启后主窗口恢复到分离窗最后看的会话
|
|
- V12. 切会话滚动位置不保留一律回底部(MessageList.vue:745-747),回读被打断需重新滚找
|
|
|
|
### 本动线最大成本热点(3 条)
|
|
|
|
1. **「需要我」信号全链缺失**(V1+V2+V3 同根):审批等待/完成/失败三关键事件在后台会话场景下不改状态点、不进聚合、无通知——事件路由与状态层都按单会话设计,多会话并行实际用法退化为「人肉轮询每个会话」
|
|
2. **审批动线只为当前会话服务**(V2+V5+V6 连带):聚合层单例、计时器切会话清空、条目无来源,须一并重做
|
|
3. **轻量状态跨会话串台**(V7+V8):制造「发错会话/重复发送」直接伤害,改动小收益立竿见影
|
|
|
|
## 五、合并优先级排序(四源:9-28 走查 + 本轮三动线 + 截图实证)
|
|
|
|
按「日常主入口」受伤程度排批次(9-28 的 17 项去重并入,I/R/V 编号沿用上文):
|
|
|
|
### 第一批:日常使用直接受伤(P1 级)
|
|
|
|
1. **I1 停止能力窄窗**——失控不可干预(截图循环实证),改 per-conv generating 判定,与 I3 快捷键一并修;小改大收益
|
|
2. **I2 编辑入口死链**(=9-28 P1)——下游链路完整只缺按钮,MessageItem 用户气泡补回编辑按钮即通,单点修复回收最大成本
|
|
3. **G2 破坏性回环熔断**(一章)——机制层一行模式拦整类自毁循环,后端 helpers/mod 各一小块
|
|
4. **V2+V5+V6 审批聚合链重做**——非活跃会话审批黑洞(V2 是本动线最高成本)+条目会话来源+计时器 per-conv,三者须一并做否则黑洞换位置;工程量最大,独立排期
|
|
5. **V1+V3 后台信号轻量面**——侧栏 awaiting_approval 状态点(需 revisiting conv_state.rs:66 设计决策)+后台终止事件本地通知,与 #4 可分阶段先行
|
|
|
|
### 第二批:高频成本(P2)
|
|
|
|
6. V8 输入草稿 per-conv(小改,消除「发错会话」直接伤害)
|
|
7. R3 长回复展开带量提示 / R4 代码块复制+语言角标(消费端最高频)
|
|
8. I5+9-28#7 错误分类与兜底文案(429 实证 55% 占比)
|
|
9. I4+9-28#4 AiError 静默清队列 → 回填或提示
|
|
10. 9-28#2 rejectedHint key 错(一行)/ 9-28#3 英文环境重试气泡残留(匹配逻辑改 i18n)
|
|
11. 9-28#6 错误气泡按 convId 路由 / 9-28#8 审批徽标跨会话过滤(与 #4 聚合链协同)
|
|
12. V4 浮窗审批高风险二次确认(安全语义对齐 ToolCard)
|
|
13. V7 排队可见性+响应式时钟(强发自救入口修复)
|
|
14. R5+V9 消息内容搜索(会话内查找+会话搜索范围,中等工程量合并一个检索底座)
|
|
15. R6 路径可复制 / 9-28#5 清队列残留两处收口
|
|
16. I6 重新生成按钮(文案已备)/ R7 轮级失败汇总行+C2 异常聚合警示
|
|
|
|
### 第三批:P3 攒批
|
|
|
|
9-28 #9-#17 与 I8-I13、R8-R13、V10-V12、C1/C3 按主题攒批处理;其中 9-28#17(图片默认上传外部服务硬编码域名)涉及开源边界,需单独决策。
|
|
|
|
### 销账核对项
|
|
|
|
- 9-28 报告已知项 #6:B-260619-04 步骤③实际已落地(MessageList.vue:335-341),建议 todo.md 销账
|
|
|
|
## 六、方法与证据
|
|
|
|
- 动线一/二/三:走查代理只读走查,主代理独立核验关键结论——动线一 I1(ChatInput.vue:290-297+useAiPendingState.ts)、动线二 R1/R2(useToolCard.ts+ToolCardList.vue 复读,均已修)、动线三 V1/V2/V3(useAiEvents.ts:95-126 复读+conv_state.rs:59-67 确认审批挂起状态为设计决策)
|
|
- 截图实证:用户提供的真实会话截图,签名判定逐调用套 helpers.rs:275/325 规则复算;常量值读 mod.rs:217/225/251 确认;「5/5」徽标语义经 AiChat.vue:218-224+ai.ts:58 对照确认
|
|
- 9-28 走查 17 项:主代理抽查三项(rejectedHint key、start-edit 死链、硬编码'正在重试(')均确认未修
|
|
- 已修两项(R1/R2)在工作区待提交改动中,连同工具卡默认折叠改造一并提交
|