diff --git a/src-tauri/src/commands/ai/audit/approval.rs b/src-tauri/src/commands/ai/audit/approval.rs index 993cce0..e75cec1 100644 --- a/src-tauri/src/commands/ai/audit/approval.rs +++ b/src-tauri/src/commands/ai/audit/approval.rs @@ -365,7 +365,7 @@ pub(super) async fn handle_approval_tool( conversation_id: Some(conv_id.to_string()), }; emit_ai_chat_event(&app_handle, ev); - // 审计:去重命中记一条(status 透传缓存来源 completed/rejected/failed,SW-260618-16;decided_by=auto_dedup),不进 pending + // 审计:去重命中记一条(status 透传缓存来源 completed/rejected/failed,;decided_by=auto_dedup),不进 pending audit_tool_call(audit_repo, conv_id, &draft.id, &draft.name, &draft.args, &status, risk_level, Some(warned_content), Some("auto_dedup"), current_message_id).await; return; } diff --git a/src-tauri/src/commands/ai/commands/chat.rs b/src-tauri/src/commands/ai/commands/chat.rs index 53467ee..3ad128e 100644 --- a/src-tauri/src/commands/ai/commands/chat.rs +++ b/src-tauri/src/commands/ai/commands/chat.rs @@ -1078,7 +1078,7 @@ pub async fn ai_approve( log_slow_lock(__lock_t534, "ai_approve:534"); drop(session); // 释放锁后再执行 - // B-260617-01 决策 a(方案 a2 后端分支):run_workflow 特殊处理。 + // 决策 a(方案 a2 后端分支):run_workflow 特殊处理。 // run_workflow handler(tool_registry.rs:544)仅持 db:Arc,无 AppHandle/State // (registry/event_bus/workflows Repo),经 ai_tools.execute 调用必然 Err。原行为:审批通过 → // handler Err → tool_result=错误提示 → LLM 重试 → 又进审批 → 死循环(工具未标 no_retry)。 @@ -1144,7 +1144,7 @@ pub async fn ai_approve( // 前端 store listen 刷新列表(仅命中映射的工具 emit,见 emit_data_changed)。 if exec_result.is_ok() { emit_data_changed(&app, &approval.tool_name); - // MED-1(CR-260620-01 审查): path 绑定工具(Medium)审批执行后 reload 白名单。 + // MED-1(审查): path 绑定工具(Medium)审批执行后 reload 白名单。 // bind_dir_to_project / repo.update_field 直写 DB 绕过 project.rs IPC reload, // 此处单点补全 → AI 绑定目录即时进白名单,防后续文件工具误弹窗。 if matches!(approval.tool_name.as_str(), "bind_directory" | "create_project" | "update_project") { @@ -1486,7 +1486,7 @@ pub async fn ai_authorize_dir( audit_finalize(&state, &tool_call_id, audit_status, Some(result_val.to_string())).await; if exec_result.is_ok() { emit_data_changed(&app, &approval.tool_name); - // MED-1(CR-260620-01 审查): path 绑定工具(Medium)审批执行后 reload 白名单。 + // MED-1(审查): path 绑定工具(Medium)审批执行后 reload 白名单。 // bind_dir_to_project / repo.update_field 直写 DB 绕过 project.rs IPC reload, // 此处单点补全 → AI 绑定目录即时进白名单,防后续文件工具误弹窗。 if matches!(approval.tool_name.as_str(), "bind_directory" | "create_project" | "update_project") { @@ -2320,7 +2320,7 @@ pub async fn ai_chat_stop( log_slow_lock(__lock_t1611, "ai_chat_stop:1611"); drop(session); - // B-260615-13 兜底任务(F2 加固,2026-08-05):loop 若 panic/异常退出漏发收尾,stop_flag 无人读, + // 兜底任务(F2 加固,2026-08-05):loop 若 panic/异常退出漏发收尾,stop_flag 无人读, // 用户点 stop 无反应、generating 卡 true。 // 原实现 sleep 3s 后仍 active 就强制复位 + emit AiCompleted——但用户点停时 loop 可能在长工具执行 // (>3s),原兜底误触发 → conv 假完成但旧 loop 仍活 → 双完成 + 用户可再发消息(竞态 F2)。 diff --git a/src-tauri/src/commands/ai/commands/conversation.rs b/src-tauri/src/commands/ai/commands/conversation.rs index 2f51a66..fe6c627 100644 --- a/src-tauri/src/commands/ai/commands/conversation.rs +++ b/src-tauri/src/commands/ai/commands/conversation.rs @@ -180,7 +180,7 @@ pub async fn ai_conversation_create( // 后台跑着的 conv。决策 e:旧 conv 的 per_conv 完整保留(后台 loop 继续跑自己的 conv), // 仅切换 active_conversation_id 到新会话 + 为新会话建独立 per_conv。 // - // B-260618-06 保留:切换 active 前先持久化旧 active conv(防其内存 messages 因后续操作丢失)。 + // 保留:切换 active 前先持久化旧 active conv(防其内存 messages 因后续操作丢失)。 // 注意:旧 conv 若有后台 loop 在跑,loop 自身的 save_conversation 也会持久化,此处 save 幂等。 let old_conv = { let session = state.ai_session.lock().await; @@ -317,7 +317,7 @@ pub async fn ai_conversation_switch( None }; let title = record.title.clone(); - // B-260617-17 续:历史会话 title 空(显"新对话")→ 切入后触发重新生成(用户诉求)。 + // 续:历史会话 title 空(显"新对话")→ 切入后触发重新生成(用户诉求)。 // 含 "新对话" 占位(Some 但未生成):title.rs ensure :40 同步排除"新对话"占位不跳过, // 否则 title 落"新对话"(LLM 返空兜底/默认)后 is_some()=true 永远跳过 → 标题卡死。 let need_title_regen = record.title.is_none() || record.title.as_deref() == Some("新对话"); diff --git a/src-tauri/src/commands/ai/mod.rs b/src-tauri/src/commands/ai/mod.rs index ba1a21a..6de4114 100644 --- a/src-tauri/src/commands/ai/mod.rs +++ b/src-tauri/src/commands/ai/mod.rs @@ -497,7 +497,7 @@ pub struct ConvSummary { /// /// 优先级:`pending_approvals` 非空(有挂起审批优先报 AwaitingApproval, /// 即使同时在流式)> `generating`(Streaming)> 都不满足(Idle)。 -// SW-260618-21 b:原预留读视图(0 消费者)。path_auth 审批链阶段1 接入后有消费者 +// b:原预留读视图(0 消费者)。path_auth 审批链阶段1 接入后有消费者 // (try_continue_agent_loop / ai_chat_stop 经 session_state 替代手写 has_pending),不再 dead_code。 #[derive(Debug, Clone, PartialEq, Eq)] pub enum SessionState { @@ -1142,7 +1142,7 @@ pub enum ApprovalKind { Path(PathAuthRequest), /// 普通 RiskLevel 审批(Med/High 风险工具)。 /// diff 仅 write_file 挂起审批前预读注入;其余工具 / recovered 审批为 None。 - #[allow(dead_code)] // IPC 活跃(useAiEvents:252+ToolCard·UX-260618-06)·cargo Rust never-read 误报 + #[allow(dead_code)] // IPC 活跃(useAiEvents:252+ToolCard·)·cargo Rust never-read 误报 Risk { diff: Option }, } @@ -1166,7 +1166,7 @@ pub(crate) struct ToolCallDraft { } // ============================================================ -// B-260617-01 决策 a(方案 a2):run_workflow 工具经审批后的真实执行入口 +// 决策 a(方案 a2):run_workflow 工具经审批后的真实执行入口 // // 背景:run_workflow handler(tool_registry.rs)仅持 db:Arc,无 AppHandle/State // (registry/event_bus/workflows Repo),经 ai_tools.execute 必然 Err。ai_approve 识别 run_workflow diff --git a/src-tauri/src/commands/ai/stream_recv.rs b/src-tauri/src/commands/ai/stream_recv.rs index 950a833..bb7f340 100644 --- a/src-tauri/src/commands/ai/stream_recv.rs +++ b/src-tauri/src/commands/ai/stream_recv.rs @@ -167,7 +167,7 @@ fn flush_delta(app_handle: &AppHandle, conv_id: &str, pending_delta: &mut String /// 流式接收 LLM 响应。 /// /// 三类异常处理(返回 StreamResult 显式区分出口): -/// - Init 失败(provider.stream() Err):UX-260618-15 **不 emit AiError**,返回 +/// - Init 失败(provider.stream() Err):**不 emit AiError**,返回 /// InitFailed{retryable, error}。retryable 据状态码分类(retry::is_status_retryable 镜像): /// 5xx/429/timeout/connect=true,4xx(非429)/鉴权/参数错=false Fatal。调用方走流前重试(仅 /// retryable=true,≤max_retries 次, 复用 retry::backoff_delay 退避 + 30s 总预算),重试耗尽或 @@ -177,7 +177,7 @@ fn flush_delta(app_handle: &AppHandle, conv_id: &str, pending_delta: &mut String /// + 系统提示网络中断。空文本仍返回 InitFailed{retryable=true}(无文可保,交重试,emit 权归调用方)。 /// - 用户停止(stop_flag):返回 Complete(incomplete 语义非异常中断)。 /// -/// 心跳与停止响应(B-260615-02 / B-260615-04):单 `tokio::select!` 三分支 +/// 心跳与停止响应(/ ):单 `tokio::select!` 三分支 /// - `stream.next()`:正常 chunk 处理 /// - `heartbeat.tick()`(30s):静默期(如工具执行后等下一轮首 chunk)emit `AiHeartbeat`, /// 前端 watchdog 据此 reset,区分「LLM 在跑」与「真断」(避免空气泡误报中断) diff --git a/src-tauri/src/commands/ai/title.rs b/src-tauri/src/commands/ai/title.rs index 0de26ae..0a84421 100644 --- a/src-tauri/src/commands/ai/title.rs +++ b/src-tauri/src/commands/ai/title.rs @@ -88,7 +88,7 @@ pub(crate) async fn ensure_conversation_title( // 在传给 LLM 前合并连续同 role(拼接 content),不影响其他逻辑(本函数 summary 派生数据)。 let summary_msgs = merge_consecutive_roles(summary_msgs); - // B-260617-17 修复:先用 extract 兜底 emit 让侧栏即时更新(不落库), + // 修复:先用 extract 兜底 emit 让侧栏即时更新(不落库), // 下方 LLM 生成成功后覆盖。这样 LLM 失败时有重试机会, // 不会永久锁定一个机械截取的差标题。 let _fallback_title = extract_title(&all_msgs).unwrap_or_else(|| "新对话".to_string()); diff --git a/src-tauri/src/commands/idea.rs b/src-tauri/src/commands/idea.rs index 7b25420..49cd5e9 100644 --- a/src-tauri/src/commands/idea.rs +++ b/src-tauri/src/commands/idea.rs @@ -73,7 +73,7 @@ pub async fn create_idea( state: State<'_, AppState>, input: CreateIdeaInput, ) -> Result { - // priority 值域校验 ∈ 0..=3 (对标 tasks B-260615-15)。 + // priority 值域校验 ∈ 0..=3 (对标 tasks )。 // priority_from_i32 对越界值兜底归并(>=3→Critical),但 IPC 入口应显式拒非法值, // 防 LLM/前端传 99 等被静默吞为 Critical。 if !(0..=3).contains(&input.priority) { diff --git a/src-tauri/src/commands/project.rs b/src-tauri/src/commands/project.rs index bc99b3b..e03a896 100644 --- a/src-tauri/src/commands/project.rs +++ b/src-tauri/src/commands/project.rs @@ -338,7 +338,7 @@ pub async fn get_project( /// 更新项目单个字段(字段名走 df-storage 白名单校验) /// -/// B-260801-010 行(id 不存在 / 已软删)→ 返 Err 而非假成功。原实现无视 +/// 行(id 不存在 / 已软删)→ 返 Err 而非假成功。原实现无视 /// update_field 返回值恒返 Ok(true),前端误以为成功但库无变更(UI/DB 不一致)。 /// /// 返回值保持 bool(成功恒 true):前端 api 包装 `Promise` 契约不变零破坏, diff --git a/src-tauri/src/commands/task.rs b/src-tauri/src/commands/task.rs index 31d369a..035ee66 100644 --- a/src-tauri/src/commands/task.rs +++ b/src-tauri/src/commands/task.rs @@ -365,7 +365,7 @@ pub async fn create_task( /// 更新任务单个字段(字段名走 df-storage 白名单校验;status 值走枚举校验)。 /// -/// B-260801-010 行(id 不存在 / 已软删)→ 返 Err 而非 false,杜绝「假成功」 +/// 行(id 不存在 / 已软删)→ 返 Err 而非 false,杜绝「假成功」 /// (前端 updateTask 仅 await 不读 bool,但 store.runWithCatch 把 Err 转 toast,用户能感知; /// 若返 false 则 store 静默忽略,本地状态前端手动改但库无变更,造成 UI 与 DB 不一致)。 /// @@ -406,7 +406,7 @@ pub async fn update_task( } } } - // project_id 跨表存在性校验(B-260616-16 收尾)。 + // project_id 跨表存在性校验(收尾)。 // crud.rs tasks 白名单保留 project_id(支持跨项目移动),跨表约束由本命令层兜底。 // 拦截移动到不存在的项目(手输脏 id / 已物理删除的项目),避免 tasks.project_id 悬空。 if field == "project_id" { diff --git a/src-tauri/src/commands/workflow.rs b/src-tauri/src/commands/workflow.rs index a3edda0..ce01a72 100644 --- a/src-tauri/src/commands/workflow.rs +++ b/src-tauri/src/commands/workflow.rs @@ -109,7 +109,7 @@ pub async fn run_workflow( run_workflow_inner(&app, &state, name, dag, config, task_id, target_status, "manual", None).await } -/// 工作流执行核心逻辑(B-260617-01 抽取,命令层瘦转发)。 +/// 工作流执行核心逻辑(抽取,命令层瘦转发)。 /// /// 原 `run_workflow` 命令体拆出,使 ai_approve 经 execute_run_workflow_for_tool 直接调用 /// (持 &AppState 而非 tauri::State,绕开 tauri::State 在非命令上下文不可构造的限制)。 @@ -249,7 +249,7 @@ pub async fn run_workflow_inner( // broadcast 不暴露被丢事件的具体类型,无法在此精确判断是否为关键终态事件。 // 风险:若关键终态事件(WorkflowCompleted/WorkflowFailed)被丢,finished 判定不触发, // 循环不会 break,前端永久收不到工作流结束信号。 - // B-260615-35 兜底:收到 Lagged 时,累计次数或单次 n 达阈值则查 DB 终态, + // 兜底:收到 Lagged 时,累计次数或单次 n 达阈值则查 DB 终态, // 若工作流已终态则补发对应 workflow-event 并 break 退出 forward。 // 避免依赖实时性差的 DB 轮询兜底导致前端审批/完成弹窗卡死。 Err(RecvError::Lagged(n)) => { diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index 993b107..6b0e565 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -156,12 +156,12 @@ pub struct AppState { pub project_modules: ProjectModuleRepo, /// 工程依赖关系 Repo(V35 module_dependencies,依赖图边数据)。 pub module_dependencies: ModuleDependencyRepo, - /// 发布表 Repo(预留:ReleaseRepo 持久化已就位·IPC/逻辑未接入·SW-260618-21 b 保留) + /// 发布表 Repo(预留:ReleaseRepo 持久化已就位·IPC/逻辑未接入·b 保留) #[allow(dead_code)] pub releases: ReleaseRepo, /// 工作流执行表 Repo pub workflows: WorkflowRepo, - /// 节点执行表 Repo(预留:NodeExecutionRepo 持久化已就位·IPC/逻辑未接入·SW-260618-21 b 保留) + /// 节点执行表 Repo(预留:NodeExecutionRepo 持久化已就位·IPC/逻辑未接入·b 保留) #[allow(dead_code)] pub node_executions: NodeExecutionRepo, /// 工作流事件总线(tokio broadcast) diff --git a/src/api/types.ts b/src/api/types.ts index 532e74d..2f7c4dc 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -350,10 +350,10 @@ export type Modality = 'text' | 'vision' | 'audio' | 'video' /** 维度 2 — 能力(模型能做什么)。serde snake_case 字面量。 */ export type Capability = 'tool_use' | 'embedding' | 'code_gen' -/** 维度 3 — 价格分级(成本)。serde snake_case 字面量。'free' 已删(B-260618-05 死档,预设/启发式从不赋值)。 */ +/** 维度 3 — 价格分级(成本)。serde snake_case 字面量。'free' 已删(死档,预设/启发式从不赋值)。 */ export type CostTier = 'low' | 'medium' | 'high' -/** 维度 4 — 智力分级。serde snake_case 字面量。CR-260618-11#3:与 CostTier 同,cost/intel 已从路由硬过滤及前端标签去除,字段保留仅 DB 兼容,不再参与路由选模型。 */ +/** 维度 4 — 智力分级。serde snake_case 字面量。与 CostTier 同,cost/intel 已从路由硬过滤及前端标签去除,字段保留仅 DB 兼容,不再参与路由选模型。 */ export type IntelligenceTier = 'lite' | 'standard' | 'plus' | 'ultra' /** 探测来源(只读,由后端探测器填充)。serde snake_case 字面量。 */ diff --git a/src/components/AiChat.vue b/src/components/AiChat.vue index 63aa65e..438c1fd 100644 --- a/src/components/AiChat.vue +++ b/src/components/AiChat.vue @@ -265,7 +265,7 @@ async function confirmClearChat() { * 同属真并发语义——不弹「中断并新建」确认(与并行矛盾)。对齐 switchConversation:89-91 * 「允许生成中切换:后台继续生成,事件按 conversation_id 路由」。 * - * 历史:B-260615-10 单 loop 软复位弹中断确认,F-09 决策e 上线后已废弃(A 路线遗留)。 + * 历史:单 loop 软复位弹中断确认,F-09 决策e 上线后已废弃(A 路线遗留)。 */ async function confirmNewConversation() { try { @@ -476,7 +476,7 @@ onBeforeUnmount(() => { store.stopListener() // F-15 阶段2: 释放上下文管理事件监听器 store.stopContextListener() - if (_unlistenToolSlow) { _unlistenToolSlow(); _unlistenToolSlow = null } // B-260616-12 + if (_unlistenToolSlow) { _unlistenToolSlow(); _unlistenToolSlow = null } // if (_unlistenAutoApproved) { _unlistenAutoApproved(); _unlistenAutoApproved = null } // AE-2025-04 if (_unlistenAiToast) { _unlistenAiToast(); _unlistenAiToast = null } // P1-e-d if (_unlistenSkillInjected) { _unlistenSkillInjected(); _unlistenSkillInjected = null } // 技能注入反馈 toast @@ -802,7 +802,7 @@ async function initContextEventListeners(): Promise { } .ai-queue-x { background: none; border: none; color: var(--df-text-dim); cursor: pointer; font-size: 14px; line-height: 1; padding: 0 2px; flex-shrink: 0; } .ai-queue-x:hover { color: var(--df-danger); } -/* UX-260616-06 队列项编辑/立即发送按钮 + 编辑态 input */ +/* 队列项编辑/立即发送按钮 + 编辑态 input */ .ai-queue-btn { background: none; border: none; color: var(--df-text-dim); cursor: pointer; font-size: 11px; line-height: 1; padding: 0 4px; flex-shrink: 0; diff --git a/src/components/ToolCard.vue b/src/components/ToolCard.vue index 7819b75..974abc3 100644 --- a/src/components/ToolCard.vue +++ b/src/components/ToolCard.vue @@ -136,7 +136,7 @@ @result="answerConfirm" /> - +
{{ approveToast.msg }}
diff --git a/src/components/ToolCardList.vue b/src/components/ToolCardList.vue index 93a2c36..c006ebb 100644 --- a/src/components/ToolCardList.vue +++ b/src/components/ToolCardList.vue @@ -31,7 +31,7 @@ @expand-content="toggleExpand" @approve="(e) => emit('approve', e)" @install-obscura="() => emit('install-obscura')" - :class="{ 'ai-tool-card--group-hidden': group.calls.length >= 2 && isGroupCollapsed(gi) }" + :class="{ 'ai-tool-card--group-hidden': isGroupHidden(tc, group, gi) }" /> @@ -123,19 +123,36 @@ const pendingCount = computed(() => * 响应式而非一次性初始化——流式场景第一张卡到达即触发旧一次性逻辑, * 此后组从 1 卡长到 N 卡不再折叠,批量读取的 N 张单行卡全展开刷屏; * 签名(组名:卡数)变化即幂等补齐,新达到阈值的组收起,已 touched 的组不动。 + * + * 活组豁免:含 running/pending_approval 卡或用户展开过卡的组(groupHasLiveCard) + * 不自动收;已被收的活组自动复活展开。治"执行轮整组(读取文件/搜索内容)被收只剩组头, + * running 卡强制展开也被组 hidden 压住,用户想看进度要点两层"。 */ watch( () => groupedToolCalls.value.map(g => `${g.name}:${g.calls.length}`).join('|'), () => { const next = new Set(collapsedGroups.value) groupedToolCalls.value.forEach((g, gi) => { - if (g.calls.length >= 2 && !userTouchedGroups.value.has(g.name)) next.add(gi) + if (g.calls.length < 2 || userTouchedGroups.value.has(g.name)) return + if (groupHasLiveCard(g)) next.delete(gi) + else next.add(gi) }) collapsedGroups.value = next }, { immediate: true } ) +/** 组内是否存在"活着"的卡:running/pending_approval 进行中 或 用户主动展开过(userExpandedCards)。 + * 自动收起补齐对这类组跳过(不误收用户正在看的/正在执行的), + * 且补齐时自动展开复活——治"组从 1 卡长到 2 卡瞬间整组被收、用户点开也被压回去"。 */ +function groupHasLiveCard(g: ToolCallGroup): boolean { + return g.calls.some(tc => + tc.status === 'running' + || tc.status === 'pending_approval' + || userExpandedCards.value.has(tc.id) + ) +} + function isGroupCollapsed(gi: number): boolean { return collapsedGroups.value.has(gi) } @@ -180,7 +197,9 @@ function groupDisplayName(group: ToolCallGroup): string { /** * 单卡是否展开: - * - 分组已折叠 → false(整组隐藏) + * - 分组已折叠 → false(整组隐藏)。**例外**: 用户主动展开过的卡(userExpandedCards) + * 不被组折叠压制()——组收起只隐藏"无用户记忆"的卡, + * 用户点开过的卡保持展开,否则"点开又被组补齐自动压回去"死循环。 * - 未手动操作过 → 默认折叠(U-260629):completed 卡一律单行 header(工具名+目标+结果摘要), * 杜绝"AI 一轮多工具各铺开一整卡"刷屏;点 header 才展开。 * running/pending/rejected/write_file 不受此值影响——ToolCard 内部 @@ -189,17 +208,26 @@ function groupDisplayName(group: ToolCallGroup): string { * 清了 expandedCards,记忆态仍保持展开(用户读过的卡不强制折叠)。 */ function isCardExpanded(tc: AiToolCallInfo, gi: number): boolean { - if (isGroupCollapsed(gi)) return false - // 用户主动折叠最优先(覆盖用户展开记忆) + // 用户主动折叠最优先(覆盖一切,含组折叠与展开记忆) if (userCollapsedCards.value.has(tc.id)) return false - // 用户主动展开的卡片记忆态优先(不被自动收起影响) + // 用户主动展开记忆次优先:压过组折叠(),点开过的卡组收起时仍展开 if (userExpandedCards.value.has(tc.id)) return true + // 组折叠(只影响无用户记忆的卡) + if (isGroupCollapsed(gi)) return false // 用户手动切换过该卡(当前态) if (expandedCards.value.has(tc.id)) return true // 兜底:默认折叠(单行 header 已有 toolDisplayName 路径/pattern/host + toolResultSummary 摘要) return false } +/** 组收起时卡片是否整卡隐藏(display:none)。 + * 用户主动展开过的卡豁免——组收起只隐藏无用户记忆的卡, + * 否则模板 class 的 display:none 会把 isCardExpanded 已放行的展开卡重新藏掉。 */ +function isGroupHidden(tc: AiToolCallInfo, group: ToolCallGroup, gi: number): boolean { + if (group.calls.length < 2 || !isGroupCollapsed(gi)) return false + return !userExpandedCards.value.has(tc.id) +} + function toggleCardExpand(tc: AiToolCallInfo, gi: number) { const id = tc.id // 用 isCardExpanded 判当前真实态(含用户操作记忆),而非 expandedCards(记忆态不在其中, @@ -399,7 +427,7 @@ function groupIcon(name: string): string { } /* -- 分组隐藏态的卡片。 - UX-260616-03: 保持 display:none(原行为)。 + 保持 display:none(原行为)。 不改 max-height/opacity 动画的原因:flex 容器 gap:8px 对零高 item 仍贡献 gap, 会留双倍间距致布局回归;display:none 移出流才无 gap。 真正的高度动画需 JS 钩子(高度 auto→0 不可纯 CSS transition), diff --git a/src/components/ToolResultBody.vue b/src/components/ToolResultBody.vue index 72fa638..30329d3 100644 --- a/src/components/ToolResultBody.vue +++ b/src/components/ToolResultBody.vue @@ -121,7 +121,7 @@ + 后端 generate_diff 产完整 diff,result.diff 字段此前被丢弃,用户无法验证改了什么 → 此分支渲染 diff -->
@@ -153,7 +153,7 @@
{{ cmdOutput }}
- +
store.state.streaming, (s) => { // 收起旧的已完成/已拒绝卡片,保留 running/pending/write_file 活跃卡片。 // 折叠态由 ToolCardList 自治,父级仅经 expose 调 collapseInactive ── // -// B-260616-13 性能:快照仅含 toolCalls 的 (id,status)+消息条数,剔除 content.length。 +// 性能:快照仅含 toolCalls 的 (id,status)+消息条数,剔除 content.length。 // 收起判定只依赖 active Set(由 toolCalls 状态推导),与文本长度无关。 // 原快照含 len → 流式每 token 增长末条消息 content → snap 变 → body 每帧重算 // (deep watch 检测仍每帧跑,但 body 跳过)。剔除 len 后流式 delta 不再触发收起重算, @@ -1049,7 +1049,7 @@ defineExpose({ 正常消息 + 展开的折叠段内消息 emit 'msg' 项。连续 archived_segment/compressed 合并一条分隔条,点击展开/收起看原文) A 过程压缩:trace 项=完成轮次执行轨迹卡,step 项=执行中实时步骤摘要,均先于 msg 分支拦截。 - UX-2025-19: 每项外层包 .ai-msg-slot(B-260618-01 虚拟滚动已移除,现恒渲染; + 每项外层包 .ai-msg-slot(虚拟滚动已移除,现恒渲染; slot 仅作 flex 子占位 + 消息分组的语义容器,gap 由 .ai-messages 承担)。 UX 密度改造:timesep 项=相邻消息间隔 ≥30 分钟或跨天的居中时间分隔线。 -->
@@ -1468,7 +1468,7 @@ defineExpose({ color: var(--df-danger); border-color: rgba(240,101,101,0.3); } -/* 修正 UX-260617-21 的 display:block 方案——display:block 让 table 失去表格 +/* 修正 的 display:block 方案——display:block 让 table 失去表格 布局上下文,tr/td 脱离 table 上下文塌成块级堆叠丢列对齐(用户截图反馈测试报告四列表格错乱)。 正解:table 保持默认 display:table(列对齐恢复)+ max-width:100%;宽表格横向滚动交由气泡 容器 .ai-msg-bubble--ai.ai-md 的 overflow-x:auto 承载(CSS spec 中 table-display 下 diff --git a/src/components/ideas/IdeaDetail.vue b/src/components/ideas/IdeaDetail.vue index 4538dc0..36f54b1 100644 --- a/src/components/ideas/IdeaDetail.vue +++ b/src/components/ideas/IdeaDetail.vue @@ -22,7 +22,7 @@ {{ $t(ideaStatusLabelKey(idea.status)) }}
- +