清理: 移除代码注释中的走查编号前缀(UX/B/SW/CR/ID/P0-P2-26xxxx,共102处)

This commit is contained in:
lxy
2026-10-02 01:00:37 +08:00
parent ea20a465d6
commit 79ca55620b
44 changed files with 157 additions and 103 deletions
+1 -1
View File
@@ -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;
}
+4 -4
View File
@@ -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<Database>,无 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)。
@@ -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("新对话");
+3 -3
View File
@@ -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<String> },
}
@@ -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<Database>,无 AppHandle/State
// (registry/event_bus/workflows Repo),经 ai_tools.execute 必然 Err。ai_approve 识别 run_workflow
+2 -2
View File
@@ -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 在跑」与「真断」(避免空气泡误报中断)
+1 -1
View File
@@ -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());
+1 -1
View File
@@ -73,7 +73,7 @@ pub async fn create_idea(
state: State<'_, AppState>,
input: CreateIdeaInput,
) -> Result<IdeaRecord, String> {
// 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) {
+1 -1
View File
@@ -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<boolean>` 契约不变零破坏,
+2 -2
View File
@@ -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" {
+2 -2
View File
@@ -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)) => {
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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 字面量。 */
+3 -3
View File
@@ -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<void> {
}
.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;
+1 -1
View File
@@ -136,7 +136,7 @@
@result="answerConfirm"
/>
<!-- UX-260617-14:审批 loading 超时兜底 toast(零依赖 reactive,复用 AiChat 同款样式) -->
<!-- 审批 loading 超时兜底 toast(零依赖 reactive,复用 AiChat 同款样式) -->
<Transition name="ai-tool-toast">
<div v-if="approveToast.visible" class="ai-tool-toast ai-tool-toast--warning">{{ approveToast.msg }}</div>
</Transition>
+35 -7
View File
@@ -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) }"
/>
</template>
</div>
@@ -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。
真正的高度动画需 <Transition> JS 钩子(高度 auto→0 不可纯 CSS transition),
+2 -2
View File
@@ -121,7 +121,7 @@
</div>
<!-- patch_file 结果:文件路径/字节增减 + unified diff 红删绿增行级预览(复用 ai-tool-diff-pre/--add/--del/--ctx 既有样式)
UX-260618-06:后端 generate_diff 产完整 diff,result.diff 字段此前被丢弃,用户无法验证改了什么 → 此分支渲染 diff -->
后端 generate_diff 产完整 diff,result.diff 字段此前被丢弃,用户无法验证改了什么 → 此分支渲染 diff -->
<div v-else-if="tc.name === 'patch_file' && tc.result && tc.status === 'completed' && parsed" class="ai-tool-patch-result">
<div class="ai-tool-file-bar">
<span class="ai-tool-file-icon" v-html="fileIcon"></span>
@@ -153,7 +153,7 @@
<pre v-if="cmdOutputExpanded && cmdOutput" class="ai-tool-cmd-output"><code>{{ cmdOutput }}</code></pre>
</div>
<!-- http_request 结果:method + url + 状态码徽标 + 可折叠响应 body(对齐 run_command 模式 UX-260618-08) -->
<!-- http_request 结果:method + url + 状态码徽标 + 可折叠响应 body(对齐 run_command 模式 ) -->
<div
v-else-if="tc.name === 'http_request' && tc.result && tc.status === 'completed' && parsed"
class="ai-tool-http-result"
+8 -8
View File
@@ -279,7 +279,7 @@ watch(() => 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 分钟或跨天的居中时间分隔线。 -->
<template v-for="item in renderItems" :key="item.key">
@@ -1119,7 +1119,7 @@ defineExpose({
<span class="ai-retry-text">{{ item.msg.content }}</span>
</div>
</div>
<!-- B-260618-19: 纯空 assistant(无 content/error/toolCalls 且非末条流式)整 slot 不渲染,
<!-- 纯空 assistant(无 content/error/toolCalls 且非末条流式)整 slot 不渲染,
消除空 div + flex gap 堆积的大片空白;sep 始终渲染;有 toolCalls 的空 assistant
仍渲染(工具卡可见——修复 agentic 工具轮 LLM 无文本时工具卡消失)。
UX 密度改造:per-block 相对时间已移除,精确时刻上移到消息容器 title(hover 可查)。 -->
@@ -1152,8 +1152,8 @@ defineExpose({
</span>
</div>
<!-- 消息(正常段 + 展开的折叠段内消息)— 虚拟滚动已移除(UX-260617-01:IO/RO 时序致重叠),消息恒渲染 -->
<!-- B-260618-19: 纯空 assistant 过滤已上提到外层 .ai-msg-slot 的 v-if(shouldRenderMsg),
<!-- 消息(正常段 + 展开的折叠段内消息)— 虚拟滚动已移除(IO/RO 时序致重叠),消息恒渲染 -->
<!-- 纯空 assistant 过滤已上提到外层 .ai-msg-slot 的 v-if(shouldRenderMsg),
此处 v-else 恒渲染 .ai-msg(sep 已在上方 v-if 分支处理,此处必为 msg)。 -->
<div
v-else
@@ -1237,7 +1237,7 @@ defineExpose({
<span class="ai-cursor"></span>
</div>
<!-- UX-03 / UX-260616-01:错误气泡操作入口(重试 / 去设置) -->
<!-- UX-03 / 错误气泡操作入口(重试 / 去设置) -->
<div v-if="item.msg.isError" class="ai-msg-actions ai-msg-actions--error">
<span v-if="!canRetry(item.msg)" class="ai-msg-not-retryable">{{ $t('ai.notRetryable') }}</span>
<button
@@ -1341,7 +1341,7 @@ defineExpose({
</div>
</div>
</div>
</div><!-- /.ai-msg-slot(B-260618-01:虚拟滚动已移除,恒渲染容器) -->
</div><!-- /.ai-msg-slot(虚拟滚动已移除,恒渲染容器) -->
</template>
</div>
@@ -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 下
+2 -2
View File
@@ -22,7 +22,7 @@
<span class="status-tag" :class="'status-' + idea.status">{{ $t(ideaStatusLabelKey(idea.status)) }}</span>
</div>
</div>
<!-- B-260615-25:灵感描述 Markdown 渲染,复用 useMarkdown composable(同 B-24 TaskDetail),空值回退 — -->
<!-- 灵感描述 Markdown 渲染,复用 useMarkdown composable(同 B-24 TaskDetail),空值回退 — -->
<template v-if="editing">
<textarea v-model="editDesc" class="detail-desc-edit" rows="4"></textarea>
<div class="desc-edit-actions">
@@ -725,7 +725,7 @@ watch(() => props.idea.id, loadHistory)
margin-bottom: var(--df-gap-page);
}
/* ===== 灵感描述 Markdown 渲染(B-260615-25,基础样式收敛至全局 ai-md.css) ===== */
/* ===== 灵感描述 Markdown 渲染(,基础样式收敛至全局 ai-md.css) ===== */
.detail-desc.ai-md { white-space: normal; color: var(--df-text-secondary); }
.detail-section {
+15 -7
View File
@@ -30,7 +30,7 @@
<div class="detail-field">
<label>{{ t('knowledge.contentLabel') }}</label>
<!-- B-260615-25:知识内容 Markdown 渲染(展示态),复用 useMarkdown composable(同 B-24 TaskDetail);编辑态保持 textarea -->
<!-- 知识内容 Markdown 渲染(展示态),复用 useMarkdown composable(同 B-24 TaskDetail);编辑态保持 textarea -->
<div
v-if="!editing && detail.knowledge.content"
ref="contentEl"
@@ -150,7 +150,7 @@ import { useRendered } from '@/composables/useMarkdown'
import { formatRelative } from '@/utils/time'
import type { KnowledgeDetailPayload, KnowledgeEventRecord } from '@/api/types'
// 详情面板子组件(Knowledge.vue 拆分,SW-260618-20):
// 详情面板子组件(Knowledge.vue 拆分,):
// 接收 detail 渲染只读四段(基本信息/溯源/引用/时间线),内部自管 editing/editForm/refLimit;
// 编辑保存与审核操作通过 emit 回传父级执行 IPC(publish/reject/archive/save)。
const props = defineProps<{
@@ -202,7 +202,7 @@ function saveEdit() {
editing.value = false
}
// ===== Markdown 渲染(B-260615-25,复用模块单例渲染器)=====
// ===== Markdown 渲染(,复用模块单例渲染器)=====
const { rendered: renderedContent, ensureLoaded } = useRendered(
() => props.detail?.knowledge.content ?? '',
)
@@ -342,11 +342,11 @@ watch(renderedContent, () => {
</script>
<style scoped>
/* 详情面板四段(SW-260618-20 从 Knowledge.vue 迁移,样式与原一致) */
/* 详情面板四段(从 Knowledge.vue 迁移,样式与原一致) */
.detail-section { padding-bottom: 18px; margin-bottom: 18px; border-bottom: 0.5px solid var(--df-border); }
.detail-section:last-child { border-bottom: none; margin-bottom: 0; }
.detail-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 12px; min-width: 0; }
.detail-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 12px; min-width: 0; flex-wrap: wrap; }
.detail-title-row { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1; overflow: hidden; }
.detail-kind-icon { font-size: 20px; flex-shrink: 0; }
.detail-title { font-size: 18px; font-weight: 500; color: var(--df-text); word-break: break-word; overflow-wrap: break-word; min-width: 0; }
@@ -358,7 +358,7 @@ watch(renderedContent, () => {
.detail-field label { display: block; font-size: 12px; color: var(--df-text-secondary); margin-bottom: 4px; }
.detail-content { font-size: 13px; color: var(--df-text); line-height: 1.6; white-space: pre-wrap; }
/* ===== 知识内容 Markdown 渲染(B-260615-25,基础样式收敛至全局 ai-md.css)===== */
/* ===== 知识内容 Markdown 渲染(,基础样式收敛至全局 ai-md.css)===== */
.detail-content.ai-md { white-space: normal; }
.detail-tags { display: flex; gap: 6px; flex-wrap: wrap; }
.tag { font-size: 11px; padding: 2px 8px; border-radius: var(--df-radius-xs); background: var(--df-accent-bg); color: var(--df-accent); }
@@ -448,10 +448,18 @@ watch(renderedContent, () => {
/* 按钮基础样式 (.btn / .btn-sm / .btn-primary / .btn-ghost) 已收敛至 global.css */
/* ===== 响应式:窄屏详情标题防竖线化(B-260619)===== */
/* ===== 响应式:窄屏详情标题防竖线化()===== */
/* 双闸:@media 管窗口窄;@container 管「视口宽但面板被 AI 平分挤压」场景。
注意:@container 需要 .knowledge(父链上有 container-type 的祖先)声明容器,
已在 Knowledge.vue 根节点加 container-type: inline-size。 */
@media (max-width: 760px) {
.detail-head { flex-direction: column; align-items: flex-start; }
.detail-actions { width: 100%; }
.detail-title { font-size: 15px; }
}
@container (max-width: 420px) {
.detail-head { flex-direction: column; align-items: flex-start; }
.detail-actions { width: 100%; }
.detail-title { font-size: 15px; }
}
</style>
+3 -3
View File
@@ -1,5 +1,5 @@
<template>
<!-- 人工审批对话框(从 ProjectDetail.vue 抽离 UX-260618-17 submitting 保留)-->
<!-- 人工审批对话框(从 ProjectDetail.vue 抽离 submitting 保留)-->
<div v-if="visible" class="modal-overlay">
<div class="modal-box" style="width: 500px">
<h3 class="modal-title">{{ $t('projectDetail.approvalTitle') }}</h3>
@@ -76,7 +76,7 @@ import { useToast } from '@/composables/useToast'
// 抽离自 ProjectDetail.vue(L175-225 模板 + L416-471 审批逻辑/watch)
// store 共享:父级 useProjectStore() 实例传入(同源 store,响应式共享 pendingApproval)
// submitting 自治:本组件持有禁用态(UX-260618-17 双击防抖)
// submitting 自治:本组件持有禁用态(双击防抖)
// ProjectStore 类型未 export(store.ts:135 内部 type),用 ReturnType 自推导同源类型
type ProjectStore = ReturnType<typeof useProjectStore>
@@ -135,7 +135,7 @@ async function handleApproval(decision: string) {
}
}
// 多选审批(UX-260618-17 submitting 防双击保留)
// 多选审批(submitting 防双击保留)
async function handleApprovalMulti() {
if (multiDecisions.value.length === 0) return
submitting.value = true
+16 -4
View File
@@ -71,10 +71,19 @@
</div>
</div>
</div>
<!-- 图片(外部 diff 存在时让位) -->
<!-- 图片(外部 diff 存在时让位;加载失败显示错误占位,不静默空白) -->
<div v-else-if="isImage && !isExternalDiff" class="preview-image">
<img :src="imageUrl ?? ''" :alt="filePath ?? ''" />
<img
v-if="imageUrl && !imageLoadError"
:src="imageUrl"
:alt="filePath ?? ''"
@error="imageLoadError = true"
/>
<div v-else class="preview-image-error">
<div class="preview-placeholder-icon">🖼️</div>
<div class="preview-placeholder-text">图片加载失败(路径: {{ filePath }})</div>
</div>
</div>
<!-- Diff 视图(外部注入的 diff 优先展示;置于 Markdown 之前,使 .md 文件也支持 Diff) -->
@@ -171,6 +180,8 @@ const isBinary = ref(false)
const fileSize = ref<number | null>(null)
const truncated = ref(false)
const imageUrl = ref<string | null>(null)
/** 图片加载失败标记(asset 协议不可用/文件被删等);@error 置位,文件切换时复位 */
const imageLoadError = ref(false)
/** 符号概览(fileOutline 结果;仅支持语言 + 非 diff 视图时展示)。 */
const outlineSupported = ref(false)
@@ -312,7 +323,7 @@ const hljsClass = computed(() => {
})
/** 图片后缀白名单。 */
const IMAGE_EXT = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg', '.ico']
const IMAGE_EXT = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg', '.ico', '.avif']
/** Markdown 后缀判定。 */
const isMarkdown = computed(() => {
@@ -486,6 +497,7 @@ async function loadFile() {
URL.revokeObjectURL(imageUrl.value)
imageUrl.value = null
}
imageLoadError.value = false
// 重置上一文件的符号概览(新文件结果到达后再填充)
outlineSupported.value = false
outlineSymbols.value = []
+1 -1
View File
@@ -87,7 +87,7 @@ const reviewSuggestions = computed<string[]>(() => {
<style scoped>
/* ===== F-AiNodeSelfReview: output_json 区块(产出 + 自审结论卡) ===== */
/* 基础布局 .info-item / .info-block / .label / .value 已收敛至全局 components.css
(DRY 收口 B-260619)。全局类对子组件内部 DOM 生效(全局非 scoped 无渗透限制),
(DRY 收口)。全局类对子组件内部 DOM 生效(全局非 scoped 无渗透限制),
故本子组件无需复制等价基础布局,仅保留本组件特有的 output/review 样式。 */
.output-block .output-body {
+4 -4
View File
@@ -2,7 +2,7 @@
//!
//! 审批链(approveToolCall/batchApprove)已拆分到 useAiApproval.ts(fe-arch P0-1)。
//!
//! B-260616-02 L2 发送韧性:三级降级
//! L2 发送韧性:三级降级
//! L0 normal:后端 idle → 直接发送
//! L1 queued: 后端 busy → 入队等待(≤30s 正常排队)
//! L2 force: 排队>30s → 弹 confirm → 用户确认后调 ai_chat_force_send 复位残留并强制发出
@@ -510,7 +510,7 @@ async function stopChat() {
flushCurrentText()
setStreaming(false, { convId: state.activeConversationId, reason: 'stopChat' }) // 本地先复位,不依赖后端 AiCompleted(审批态看门狗已 clear,竞态丢失则卡死)
clearStreamWatchdog() // 清残留看门狗
// UX-260616-05 决策 a(逐条续发):不再清队列,保留供 AiCompleted 链式触发 drainQueue 续发。
// 决策 a(逐条续发):不再清队列,保留供 AiCompleted 链式触发 drainQueue 续发。
// 后端 agentic/mod.rs emit AiCompleted 注释明确「保证前端收事件时后端已可接下一条,队列续发不被拒」;
// 当前未完成回复已入库(agentic/mod.rs save_conversation)保留为截断回复。
try {
@@ -540,8 +540,8 @@ export function useAiSend() {
drainQueue,
cancelQueued,
clearQueue,
editQueued, // UX-260616-06
sendQueuedNow, // UX-260616-07
editQueued, //
sendQueuedNow, //
stopChat,
isQueueTimedOut,
tryForceSend,
+1 -1
View File
@@ -188,7 +188,7 @@ export function useToolApproval(tc: Ref<AiToolCallInfo>, emit: ToolApprovalEmit)
/**
* 拒绝按钮处理(risk 类专用)。
* loading + 超时兜底复用 B-260616-08 机制。拒绝不做 High 风险二次确认(无害,加确认反增摩擦)。
* loading + 超时兜底复用 机制。拒绝不做 High 风险二次确认(无害,加确认反增摩擦)。
*/
async function onReject() {
if (approving.value) return // 防重入
+3 -3
View File
@@ -70,7 +70,7 @@ export interface ToolResult {
has_more?: boolean
warning?: string
review_rounds?: number
// UX-260618-05~12:补全后端返回前端消费的字段(review_rounds/truncated/bytes_moved/has_more/total/entries 已存在)
// ~12:补全后端返回前端消费的字段(review_rounds/truncated/bytes_moved/has_more/total/entries 已存在)
task_id?: string
execution_id?: string
target_status?: string
@@ -146,7 +146,7 @@ export function shouldKeepOpen(tc: AiToolCallInfo): boolean {
* - 通用工具 raw 文本:AR-6 路径后端塞 serde_json::Value::String("工具 X 执行失败: ..."),
* 前端 parseResult 返回 null(非 JSON 对象)→ 走 raw 文本兜底,无结构信号
* 此时按行首前缀 "执行失败"/"Error:"/"Failed:" 启发式判定(对齐后端 err_msg 格式,
* 行首锚定避免误伤含这些子串的正常产出,见 UX-260617-18)。
* 行首锚定避免误伤含这些子串的正常产出,见 )。
*
* 注意:status==='rejected'(用户拒绝)走独立分支,不算工具失败,不进此函数。
* running/pending_approval 自然 false(无 result 或 result 未稳定)。
@@ -631,7 +631,7 @@ export function formatFetchUrl(r: ToolResult): string {
/** list_* summary 版:n=0 返空串(折叠态无噪音),按 name 选 i18n key */
function summaryList(r: ToolResult, name: string): string {
// UX-260618-10/12: 后端返 {items,total,has_more} 对象(非数组),原 Array.isArray(r) 恒 false→折叠态无计数
// 后端返 {items,total,has_more} 对象(非数组),原 Array.isArray(r) 恒 false→折叠态无计数
const n = Array.isArray(r) ? r.length
: (typeof r.total === 'number' ? r.total
: (Array.isArray(r.items) ? r.items.length : 0))
+1 -1
View File
@@ -21,7 +21,7 @@ export interface DiffLine {
/**
* 解析 unified diff 文本 → DiffLine[]。
*
* 来源(AE-2025-03 / UX-260618-06):后端 generate_diff 输出 '+新行/-旧行/(空格)上下文',
* 来源(AE-2025-03 / ):后端 generate_diff 输出 '+新行/-旧行/(空格)上下文',
* 逐行拆 → {kind, text} 供模板按 kind 着色(add 绿/del 红/ctx 灰)。
*
* - 空行过滤(generate_diff 末尾保留单尾换行 → split 末尾有空串元素)
+3 -3
View File
@@ -37,7 +37,7 @@ import shell from 'highlight.js/lib/languages/shell'
import java from 'highlight.js/lib/languages/java'
import csharp from 'highlight.js/lib/languages/csharp'
import cpp from 'highlight.js/lib/languages/cpp'
// B-260616-11(②子项): diff 语言注册,```diff 代码块 +/- 行获 hljs-addition/deletion 红绿(github-dark.css 自带)
// (②子项): diff 语言注册,```diff 代码块 +/- 行获 hljs-addition/deletion 红绿(github-dark.css 自带)
import diff from 'highlight.js/lib/languages/diff'
// 主题:github-dark 与现有深色 UI(#0c0e1a)贴合;亮色主题下 code 块仍可读(深底浅字)。
// 注意:该 CSS 在 ai-md.css 之前/之后导入均可,hljs 用 .hljs/.hljs-* token class 着色,
@@ -185,7 +185,7 @@ function renderMd(text: string): string {
if (!mdReady.value || !_marked || !_purify) {
return escapeFallback(text)
}
// B-260616-11(①子项): 裸 diff 文本预处理。渲染前将无围栏的连续 +/- 行包成 ```diff
// (①子项): 裸 diff 文本预处理。渲染前将无围栏的连续 +/- 行包成 ```diff
// 围栏代码块,复用②子项已注册的 diff 语言 hljs 红绿样式,零额外 CSS。
// 防御性:即使现网 AI 多带围栏,裸行也正确处理(围栏内整段不动)。
const wrapped = wrapNakedDiff(text)
@@ -198,7 +198,7 @@ function renderMd(text: string): string {
}
/**
* B-260616-11(①子项,方案A wrapNakedDiff): 裸 +/- diff 文本预处理。
* (①子项,方案A wrapNakedDiff): 裸 +/- diff 文本预处理。
*
* 现网 AI(尤其 deepseek/glm/qwen 等)在描述「改动/对比」时常直接吐裸 -/+ 行而忘记
* 围栏(或仅用普通文本),导致 marked 当成普通段落/list 渲染,丢失 diff 视觉语义。
+2 -2
View File
@@ -94,7 +94,7 @@ export default {
// ── Pending queue ──
queueTitle: 'Queued {n}/10',
clearQueue: 'Clear',
// UX-260616-06/07: queue item edit + send now
// queue item edit + send now
queueEdit: 'Edit',
queueSave: 'Save',
queueCancel: 'Cancel',
@@ -164,7 +164,7 @@ export default {
editNotLastUser: 'Only the last user message can be edited',
placeholderEditing: 'Edit message then Enter to regenerate (Esc to cancel)',
// ── Error bubble actions (UX-03 / UX-260616-01) ──
// ── Error bubble actions (UX-03 / ) ──
retry: 'Retry',
goToSettingsAction: 'Open settings',
+2 -2
View File
@@ -27,7 +27,7 @@ export default {
statusRunning: 'Running',
statusPending: 'Pending approval',
statusRejected: 'Rejected',
// UX-260618-08/11: truncation hints (read_file paged truncation / list_directory 1000-entry cap)
// truncation hints (read_file paged truncation / list_directory 1000-entry cap)
linesTruncated: '{shown}/{total} lines, truncated',
dirTruncated: 'Truncated, first {n} items only',
@@ -55,7 +55,7 @@ export default {
taskCount: '{n} tasks',
projectCount: '{n} projects',
ideaCount: '{n} ideas',
// UX-260618-10/12: list_trash collapsed header count + paged has_more hint (backend returns {items,total,has_more} object)
// list_trash collapsed header count + paged has_more hint (backend returns {items,total,has_more} object)
trashCount: '{n} in trash',
listHasMore: '({n} shown, more available)',
createdWithName: 'Created: {name}',
+1 -1
View File
@@ -82,7 +82,7 @@ export default {
fetchFailedNotFound: 'Endpoint not found, this vendor may not support model listing: {msg}',
toastModelEditLocalOnly: 'Enabled / weight are local edits, overwritten on next fetch',
// Model trait tags (modality + capability + probe source; cost/intel 去除见 UX-260618-04)
// Model trait tags (modality + capability + probe source; cost/intel 去除见 )
tagModality: { text: 'Text', vision: 'Vision', audio: 'Audio', video: 'Video' },
tagCapability: { tool_use: 'Tool use', embedding: 'Embedding', code_gen: 'Code' },
tagSource: { user_set: 'Manual', preset_table: 'Preset', heuristic: 'Heuristic', default: 'Default' },
+2 -2
View File
@@ -94,7 +94,7 @@ export default {
// ── 待发送队列 ──
queueTitle: '待发送 {n}/10',
clearQueue: '清空',
// UX-260616-06/07:队列项编辑 + 立即发送
// 队列项编辑 + 立即发送
queueEdit: '编辑',
queueSave: '保存',
queueCancel: '取消',
@@ -165,7 +165,7 @@ export default {
editNotLastUser: '只能编辑最后一条用户消息',
placeholderEditing: '编辑消息后回车重新生成(Esc 取消)',
// ── 错误气泡操作入口(UX-03 / UX-260616-01) ──
// ── 错误气泡操作入口(UX-03 / ) ──
retry: '重试',
goToSettingsAction: '去设置',
+2 -2
View File
@@ -26,7 +26,7 @@ export default {
statusRunning: '进行中',
statusPending: '待审批',
statusRejected: '已拒绝',
// UX-260618-08/11:截断提示(read_file 分页截断 / list_directory 1000 上限截断)
// 截断提示(read_file 分页截断 / list_directory 1000 上限截断)
linesTruncated: '{shown}/{total} 行,已截断',
dirTruncated: '已截断,仅前 {n} 项',
@@ -56,7 +56,7 @@ export default {
taskCount: '{n} 项任务',
projectCount: '{n} 个项目',
ideaCount: '{n} 条灵感',
// UX-260618-10/12:list_trash 折叠态计数 + 分页 has_more 提示(后端返 {items,total,has_more} 对象)
// list_trash 折叠态计数 + 分页 has_more 提示(后端返 {items,total,has_more} 对象)
trashCount: '回收站 {n} 项',
listHasMore: '(已显示 {n} 项,还有更多)',
createdWithName: '已创建:{name}',
+1 -1
View File
@@ -81,7 +81,7 @@ export default {
fetchFailedNotFound: '端点不存在,该厂商可能不支持模型列表拉取:{msg}',
toastModelEditLocalOnly: '已启用 / 权重为本地编辑,下次拉取时覆盖',
// 模型特征标签(模态 + 能力 + 探测来源;cost/intel 去除见 UX-260618-04)
// 模型特征标签(模态 + 能力 + 探测来源;cost/intel 去除见 )
tagModality: { text: '文本', vision: '视觉', audio: '音频', video: '视频' },
tagCapability: { tool_use: '工具调用', embedding: '向量', code_gen: '代码' },
tagSource: { user_set: '手动', preset_table: '预设', heuristic: '推断', default: '默认' },
+1 -1
View File
@@ -231,7 +231,7 @@ watch(
)
/**
* 估算单条消息 parts 的字符体积(base64/url 字符串长度之和,UX-260617-20)。
* 估算单条消息 parts 的字符体积(base64/url 字符串长度之和,)。
*
* 只计 image 片的 base64 与 url(大体量载荷);text 片(正文/工具结果文本)不在此预算口径内,
* 避免对常规长文本消息误伤。无 parts 或非 image 片返回 0。
+1 -1
View File
@@ -52,7 +52,7 @@ export function parseTags(tags: string | null | undefined): string[] {
}
// ── 知识 label 文案辅助(列表 Knowledge.vue 与详情 KnowledgeDetail.vue 共用) ──
// 原两文件各内联一份重复实现(SW-260618-20 详情迁移时复制),提取至此单一来源。
// 原两文件各内联一份重复实现(详情迁移时复制),提取至此单一来源。
// 全部依赖 i18n,故用模块级 t(同文件 parseTags 之上的全局 t)。
/** 知识类型文案(无 icon):${icon} ${text} 组合的文本部分,亦供下拉选项 */
+1 -1
View File
@@ -29,7 +29,7 @@ function createStore() {
// 取代工具执行后需手动刷新。
//
// task 分支不再无参 loadTasks(会把 store.tasks 冲成全量,破坏
// B-260615-29「store.tasks 反映当前筛选」契约)。改为读 _activeTaskProject:
// 「store.tasks 反映当前筛选」契约)。改为读 _activeTaskProject:
// - undefined: Tasks 视图未挂载/从未设过筛选 → 不触发 task load(避免破坏筛选,
// 让 B-29 watch/handle 主导;此时改动由用户下次进 Tasks 自然刷新)
// - 'all' : 全量拉取
+3 -3
View File
@@ -1,5 +1,5 @@
/* ═══ Markdown 渲染(全局) ═══ */
/* CR-260615-09: 从 AiChat.vue / ProjectDetail.vue / Ideas.vue / Knowledge.vue / TaskDetail.vue
/* 从 AiChat.vue / ProjectDetail.vue / Ideas.vue / Knowledge.vue / TaskDetail.vue
的重复 .ai-md 样式块抽取为全局 CSS,消除 ~350 行重复。
各文件独有覆盖保留在原文件 scoped style 中。 */
@@ -56,11 +56,11 @@
border-top: 0.5px solid var(--df-border);
margin: 8px 0;
}
/* B-260618-06:移除 display:block(破坏 table 布局上下文致 tr/td 塌堆叠丢列对齐,用户截图
/* 移除 display:block(破坏 table 布局上下文致 tr/td 塌堆叠丢列对齐,用户截图
反馈测试报告表格错乱根因)+ 移除 overflow-x:auto(display:table 下浏览器忽略无效)。
table 恢复默认 display:table 列对齐恢复;width:100%→max-width:100% 允许窄表格按内容
自适应不强制拉满。宽表格横向滚动交由各页面容器承载(AiChat 气泡 .ai-msg-bubble--ai.ai-md
加 overflow-x:auto,见 AiChat.vue)。原 UX-260617-21 display:block 方案治标破坏本体。 */
加 overflow-x:auto,见 AiChat.vue)。display:block 方案治标破坏本体。 */
.ai-md table {
max-width: 100%;
border-collapse: collapse;
+1 -1
View File
@@ -13,7 +13,7 @@
- global.css 已有的 .btn / .modal 系列 / .page-header / .empty-hint / .empty-state
不在此重复,仅补充尚未全局化的类
落地批:DRY 收口(B-260619)
落地批:DRY 收口
- .info-item / .info-item.info-block / .info-item .label / .info-item .value
← ProjectDetail.vue / TaskDetail.vue / TaskOutputCard.vue 三处复制
- .panel / .panel-header ← ProjectDetail.vue / TaskDetail.vue 两处复制
+1 -1
View File
@@ -13,7 +13,7 @@
import { t, currentLocale } from '@/i18n/i18n-helpers'
// CR-260615-08相对时间走 i18n(原硬编码中文,en locale 时间恒中文)。
// 相对时间走 i18n(原硬编码中文,en locale 时间恒中文)。
// 类型安全访问经 i18n-helpers 收口(规避 vue-i18n TS2589)。
/** 解析任意时间值为毫秒时间戳;无效或空返回 null(仅本模块内部使用) */
+1 -1
View File
@@ -483,7 +483,7 @@ async function onUpdateTags(tags: string): Promise<boolean> {
onMounted(async () => {
await loadCurrentView()
// 从路由参数恢复选中灵感(修复 /ideas/:id 直接访问空白页 B-260615-36)
// 从路由参数恢复选中灵感(修复 /ideas/:id 直接访问空白页 )
if (route.params.id) {
const id = route.params.id as string
const exists = store.ideas.some(i => i.id === id)
+9 -3
View File
@@ -88,7 +88,7 @@
</div>
</div>
<!-- ===== 右侧:详情(KnowledgeDetail 子组件,SW-260618-20 拆分)===== -->
<!-- ===== 右侧:详情(KnowledgeDetail 子组件,拆分)===== -->
<div class="kn-detail-panel">
<KnowledgeDetail
v-if="detail"
@@ -417,7 +417,7 @@ onMounted(() => {
</script>
<style scoped>
.knowledge { padding: 16px 20px 20px; height: 100%; display: flex; flex-direction: column; }
.knowledge { padding: 16px 20px 20px; height: 100%; display: flex; flex-direction: column; container-type: inline-size; }
/* ===== 顶部 Toast 提示(⑤ 操作反馈,对齐 Settings.vue)===== */
.toast {
@@ -452,7 +452,7 @@ onMounted(() => {
/* ===== 左右分栏 ===== */
.kn-layout {
display: grid; grid-template-columns: minmax(240px, 360px) 1fr; gap: var(--df-gap-page);
display: grid; grid-template-columns: minmax(240px, 360px) minmax(280px, 1fr); gap: var(--df-gap-page);
flex: 1; min-height: 0;
}
.kn-list-panel, .kn-detail-panel {
@@ -526,7 +526,13 @@ onMounted(() => {
.form-textarea { min-height: 80px; resize: vertical; font-family: inherit; }
/* ===== 响应式:窄屏单列布局(详情窄屏样式随 KnowledgeDetail 子组件迁移) ===== */
/* 双闸:@media 管窗口本身窄;@container 管「视口不窄但主区被 AI 面板挤压」场景
(AI 面板平分内容时 .knowledge 实际宽度 < 620px,media query 感知不到)。 */
@media (max-width: 760px) {
.kn-layout { grid-template-columns: 1fr; }
}
@container (max-width: 620px) {
.kn-layout { grid-template-columns: 1fr; }
.kn-detail-panel { overflow-y: visible; }
}
</style>
+5 -5
View File
@@ -204,7 +204,7 @@
</div>
<div class="info-item info-block">
<span class="label">{{ $t('projectDetail.description') }}</span>
<!-- B-260615-25:项目描述 Markdown 渲染,复用 useMarkdown composable(同 B-24 TaskDetail)
<!-- 项目描述 Markdown 渲染,复用 useMarkdown composable(同 B-24 TaskDetail)
P1-g+ 问题10③:长描述折叠(对齐 TaskDetail 长内容),默认折叠截 4 行,展开看全文 -->
<div v-if="currentProject.description" class="description-wrap" :class="{ 'is-collapsed': !descExpanded }">
<span
@@ -493,7 +493,7 @@ const { t, locale } = useI18n()
const logListRef = ref<HTMLElement | null>(null)
// A1[PD-P1-4]:任务列表随 AI 改动刷新 —— 本地 df-data-changed 监听(entity=task)重拉 loadProjectTasks。
// 对齐 TaskDetail B-260616-18 模式:onMounted 注册、onBeforeUnmount 释放;连发事件防抖合并,
// 对齐 TaskDetail 模式:onMounted 注册、onBeforeUnmount 释放;连发事件防抖合并,
// 且仅概览 tab 活跃(任务列表面板在概览)才刷新。
let _unlistenDataChanged: UnlistenFn | null = null
let _taskReloadTimer: ReturnType<typeof setTimeout> | null = null
@@ -1115,7 +1115,7 @@ onUnmounted(() => {
}
/* 字段同行布局(label 固定宽 + 值占余),描述字段 info-block 保持块状)
基础 .info-item/.label/.value 已收敛至全局 components.css(DRY 收口 B-260619),
基础 .info-item/.label/.value 已收敛至全局 components.css(DRY 收口),
此处仅保留本组件特有覆盖。 */
.info-item a.idea-link {
color: var(--df-accent);
@@ -1314,7 +1314,7 @@ onUnmounted(() => {
/* .info-tags .tech-tag 与全局 .tech-tag(components.css)逐字一致,移除局部冗余,
标签样式由全局提供。 */
/* ===== 项目描述 Markdown 渲染(B-260615-25,基础样式收敛至全局 ai-md.css) ===== */
/* ===== 项目描述 Markdown 渲染(,基础样式收敛至全局 ai-md.css) ===== */
.description.ai-md { font-size: 14px; color: var(--df-text); line-height: 1.6; }
/* P1-g+ 问题10③:描述折叠(对齐 TaskDetail 长内容)— 折叠时 max-height 截断 + 渐变遮罩 */
.description-wrap { position: relative; }
@@ -1475,7 +1475,7 @@ onUnmounted(() => {
.wf-progress-hint-fail { color: var(--df-danger); }
/* ===== 面板 ===== */
/* .panel / .panel-header 基础样式已收敛至全局 components.css(DRY 收口 B-260619),
/* .panel / .panel-header 基础样式已收敛至全局 components.css(DRY 收口 ),
此处仅保留本组件特有 .task-count。 */
.task-count { font-size: 12px; color: var(--df-text-dim); }
.task-filter-chips {
+1 -1
View File
@@ -533,7 +533,7 @@ async function runImport() {
transition: width 0.3s;
}
/* ===== 技术栈标签:基础样式已收敛至全局 components.css(DRY 收口 B-260619),
/* ===== 技术栈标签:基础样式已收敛至全局 components.css(DRY 收口 ),
创建/导入模态与本页 ProjectCard 渲染的标签均走全局 .tech-tag/.card-tags。 ===== */
/* ===== 目录选择行 ===== */
+2 -2
View File
@@ -895,7 +895,7 @@ onBeforeUnmount(() => {
min-width: 0; /* 网格项防溢出(长 url/代码块) */
}
/* .panel / .panel-header 基础样式已收敛至全局 components.css(DRY 收口 B-260619)。 */
/* .panel / .panel-header 基础样式已收敛至全局 components.css(DRY 收口 )。 */
/* ===== 信息列表 ===== */
.task-info {
@@ -904,7 +904,7 @@ onBeforeUnmount(() => {
gap: var(--df-gap-grid);
}
/* 字段同行布局(label 固定宽 + value 占余),描述字段 info-block 保持块状)
基础 .info-item/.label/.value 已收敛至全局 components.css(DRY 收口 B-260619),
基础 .info-item/.label/.value 已收敛至全局 components.css(DRY 收口),
此处仅保留本组件特有覆盖。 */
/* B-24 漏 white-space 覆盖致 v-html 后 HTML 标签间 \n 被 pre-wrap 渲染为空行间距——移除 pre-wrap,描述由 .ai-md 接管(line-height 1.5 对齐 AiChat li)。
Problem4 重设计:描述区从 .info-item 内移到独立 .description-wrap(可折叠容器),