优化: 批A前端(timer所有权+会话族+审批conv-scoped+FilePreview+失败反馈)

A1-B1 timer所有权:新建 useTimerOwnership composable(每实例独立ref+onUnmounted清理)+ useToast _timer 下沉 + Ideas debounce 补清理(治跨实例串扰)

A1-B2 FilePreview:reqSeq 双计数器守卫(loadFile/loadDiff 最新seq才写,防乱序覆盖)+ mermaid securityLevel strict + filePath 比对(防跨文件SVG注入)

A1-B3 会话族:load_more 滚顶加载接线(switch透传has_more/earliest_seq+prepend去重+scrollTop恢复)+ switch失败保留视图+报错(仅对话不存在才create-new)+ new/delete失败反馈(withConvOp helper收敛)+ delete清ai_messages孤儿

A1-B5前端契约:isToolFailure 三处加 success===false 判定(useToolCard/ToolResultBody/ToolCard,git只读失败不再绿框)

A2-B10 审批conv-scoped:pendingApprovals 补conversationId + cleanup 按convId filter + 移除全局清(AiError不再误清其他conv)+ :676 dir auth filter方向修

usage打标前端:is_estimated 字段+事件透传+MessageList『估算』角标(详情面板说明)

A2-B9前端:clearChat try/catch 错误气泡
This commit is contained in:
lxy
2026-08-05 22:10:50 +08:00
parent 5667da6cf4
commit c480627ba6
14 changed files with 837 additions and 177 deletions
+13
View File
@@ -301,6 +301,19 @@ export const aiApi = {
return invoke('ai_conversation_switch', { conversationId })
},
/**
* 加载更早历史消息(G3.5 滚顶分页)。
* beforeSeq=当前已加载最早消息的 seq(游标,后端返 earliest_seq),返回更早的 PAGE_SIZE 条。
* 返 messages JSON 数组 + has_more(是否还有更早)+ earliest_seq(新游标,null=已到尽头)。
*/
loadMoreMessages(conversationId: string, beforeSeq: number): Promise<{
messages: string
has_more: boolean
earliest_seq: number | null
}> {
return invoke('ai_conversation_load_more', { conversationId, beforeSeq })
},
/** 删除对话 */
deleteConversation(conversationId: string): Promise<void> {
return invoke('ai_conversation_delete', { conversationId })
+56
View File
@@ -175,10 +175,38 @@ export interface TaskRecord {
output_json?: string
/** 关联灵感 ID(F-260619-01,1对1 单向,任务→灵感)。未关联时为 undefined。 */
idea_id?: string
/**
* 管理维度池标记(知识图谱 Phase 1,V29 列)。与 status(执行维度)正交。
* 取值 backlog(需求池)/todo(待办池)/decision(待决策池)/active(执行中)/done(已完成)。
* 后端 DEFAULT 'todo' 保证老任务迁移后取值确定,前端始终收到该字段。
*/
queue: string
/** 父任务 ID(1 级嵌套,无孙任务,由 IPC 校验)。顶层任务(无父)为 null/undefined。 */
parent_id?: string | null
/**
* 结构化需求规格 JSON 字符串(知识图谱 Phase 1,V29 列)。
* 结构 { background, acceptance_criteria[], scope[], technical_design, custom_fields }。
* 老任务无结构化规格时为 undefined。
*/
content_json?: string
created_at: string
updated_at: string
}
export interface TaskTreeNode {
/** 父任务记录(含 parent_id/queue 等全字段) */
parent: TaskRecord
/** 父任务的直接子任务(1 级嵌套,无孙任务) */
children: TaskRecord[]
}
/** delete_task 新返回结构(破坏性变更):删除父任务时级联软删其子任务 */
export interface TaskDeleteResult {
ok: boolean
/** 级联软删的子任务数 */
cascaded: number
}
export interface CreateTaskInput {
project_id: ProjectId
title: string
@@ -188,6 +216,10 @@ export interface CreateTaskInput {
assignee?: string
/** 关联灵感 ID(1对1 单向,可空)。空字符串视为不关联(与后端一致)。 */
idea_id?: string
/** 管理池标记(backlog/todo/decision/active/done,可空)。不传后端默认 todo。 */
queue?: string
/** 父任务 ID(1 级嵌套,可空)。顶层任务不传;空字符串后端视为 None。 */
parent_id?: string | null
}
/**
@@ -214,6 +246,10 @@ export interface TaskQuery {
order_by?: string | null
limit?: number | null
offset?: number | null
/** 管理池标记精确匹配(backlog/todo/decision/active/done,可空) */
queue?: string | null
/** 父任务 ID 精确匹配(取某父任务的全部子任务,可空) */
parent_id?: string | null
}
// ============================================================
@@ -400,6 +436,12 @@ export type AiChatEvent = ({
prompt_cache_miss_tokens: number
/** 思考(deepseek-reasoner/o1 reasoning_tokens,隐藏输出)。0=非 reasoning 模型 */
reasoning_tokens: number
/**
* G4.3:该轮 token 用量是否估算值(后端 agentic 运行时兜底:round_usage.prompt_tokens==0
* 时用 estimated_prompt 填充并打标)。true=per-response 估算(prompt 非真实,provider 未报),
* undefined/false=真实值。前端 MessageList 据此标注『估算』区分 estimated vs real。
*/
is_estimated?: boolean
incomplete?: boolean; pinned_goals?: GoalEntry[]
} | {
type: 'AiError'; error: string; error_type?: AiErrorType
@@ -533,6 +575,11 @@ export interface AiMessage {
cache_miss?: number
/** 思考 token(隐藏输出) */
reasoning?: number
/**
* G4.3:该轮 token 用量是否估算值(后端 AiCompleted.is_estimated 透传)。
* true=prompt_tokens 缺失按估算展示,MessageList 标注『估算』;undefined=false=真实值。
*/
is_estimated?: boolean
}
/**
* 本轮输入 token(消息级持久化,后端 ChatMessage.prompt_tokens 镜像)。
@@ -646,6 +693,15 @@ export interface AiToolCallInfo {
/** path_auth 审批链阶段3b:'path' 类挂起携带的原始路径(工具调用参数中的 path)。
* ToolCard 审批提示文案展示用(LLM 想访问 path,父目录为 dir)。'risk' 类为 undefined。 */
path?: string
/**
* A2-B10 conv-scoped 审批收尾:挂起审批所属的会话 id(前端判定用,仅挂起项设置)。
* 来源:AiApprovalRequired / AiDirAuthRequired(统一审批归一)事件透传 conversation_id
* (缺失时兜底 activeConversationId);switchConversation 恢复历史挂起时填目标会话 id。
* cleanupTerminatedConversation 据此仅清目标会话的待审批项 + 对称清其审批超时计时器,
* 不连累并发会话正在审批的卡(conv-scoped,误清其他会话审批 = 回归)。
* undefined = 非挂起项或无法归属(保守保留,不清)。对齐 PendingDirAuth.conversationId。
*/
conversationId?: string
}
/** 对话列表摘要 */