diff --git a/static/src/app/lifecycle.ts b/static/src/app/lifecycle.ts index b72378e..60f0818 100644 --- a/static/src/app/lifecycle.ts +++ b/static/src/app/lifecycle.ts @@ -23,6 +23,7 @@ export function created() { export async function mounted() { debugLog('Vue应用已挂载'); + console.log('[DEBUG_SYSTEM][boot] 前端调试版本已加载 2026-04-05'); if (window.ensureCsrfToken) { window.ensureCsrfToken().catch((err) => { console.warn('CSRF token 初始化失败:', err); diff --git a/static/src/app/methods/history.ts b/static/src/app/methods/history.ts index eb82300..16a113e 100644 --- a/static/src/app/methods/history.ts +++ b/static/src/app/methods/history.ts @@ -1,6 +1,16 @@ // @ts-nocheck import { debugLog } from './common'; +const SUB_AGENT_DONE_PREFIX_RE = /^(?:✅\s*)?子智能体\s*#?\s*(\d+)\s*任务摘要[::]/; + +function parseSubAgentDoneLabel(rawContent: any): string | null { + const content = (rawContent || '').toString().trim(); + if (!content) return null; + const match = content.match(SUB_AGENT_DONE_PREFIX_RE); + if (!match || !/已完成/.test(content)) return null; + return `子智能体${match[1]} 任务完成`; +} + export const historyMethods = { // ========================================== // 关键功能:获取并显示历史对话内容 @@ -315,6 +325,41 @@ export const historyMethods = { this.messages.push(currentAssistantMessage); currentAssistantMessage = null; } + if (message.role === 'system') { + const rawContent = message.content || ''; + const label = parseSubAgentDoneLabel(rawContent); + console.log('[DEBUG_SYSTEM][history] 命中 role=system 历史消息', { + rawContent, + matchedSubAgentDone: !!label + }); + if (label) { + // 历史中的 system 通知转换为 assistant system action,避免被 role=system 过滤掉 + this.messages.push({ + role: 'assistant', + actions: [ + { + id: `history-system-${Date.now()}-${Math.random()}`, + type: 'system', + content: label, + variant: 'sub_agent_done', + streaming: false, + timestamp: Date.now() + } + ], + streamingThinking: '', + streamingText: '', + currentStreamingType: null, + activeThinkingId: null, + awaitingFirstContent: false, + generatingLabel: '' + }); + } else { + console.log('[DEBUG_SYSTEM][history] role=system 被历史加载拦截(非子智能体完成)', { + rawContent + }); + } + return; + } debugLog('处理其他类型消息:', message.role); this.messages.push({ diff --git a/static/src/app/methods/taskPolling.ts b/static/src/app/methods/taskPolling.ts index de7f713..48bcbe3 100644 --- a/static/src/app/methods/taskPolling.ts +++ b/static/src/app/methods/taskPolling.ts @@ -17,6 +17,17 @@ export const taskPollingMethods = { const eventType = event.type; const eventData = event.data || {}; const eventIdx = event.idx; + if ( + eventType === 'system_message' || + eventType === 'sub_agent_waiting' || + (eventType === 'user_message' && eventData?.sub_agent_notice) + ) { + console.log('[DEBUG_SYSTEM][event] 捕获关键事件', { + eventType, + eventIdx, + eventData + }); + } // 事件去重检查 if (typeof eventIdx === 'number') { @@ -145,6 +156,10 @@ export const taskPollingMethods = { this.handleUserMessage(eventData, eventIdx); break; + case 'system_message': + this.handleSystemMessage(eventData, eventIdx); + break; + default: debugLog(`[TaskPolling] 未知事件类型: ${eventType}`); } @@ -767,6 +782,20 @@ export const taskPollingMethods = { this.conditionalScrollToBottom(); }, + handleSystemMessage(data: any) { + const content = (data?.content || data?.message || '').trim(); + console.log('[DEBUG_SYSTEM][polling] 收到 system_message 事件', { + raw: data, + normalizedContent: content, + currentConversationId: this.currentConversationId + }); + if (!content) { + console.log('[DEBUG_SYSTEM][polling] system_message 内容为空,跳过'); + return; + } + this.appendSystemAction(content); + }, + handleTaskError(data: any) { const shouldRetry = Boolean(data?.retry); if (shouldRetry) { diff --git a/static/src/app/methods/ui.ts b/static/src/app/methods/ui.ts index ed8e3a8..05592ff 100644 --- a/static/src/app/methods/ui.ts +++ b/static/src/app/methods/ui.ts @@ -16,6 +16,30 @@ import { } from '../../composables/usePanelResize'; import { debugLog } from './common'; +const SUB_AGENT_DONE_PREFIX_RE = /^(?:✅\s*)?子智能体\s*#?\s*(\d+)\s*任务摘要[::]/; + +function parseSubAgentDoneLabel(rawContent: any): string | null { + const content = (rawContent || '').toString().trim(); + if (!content) { + console.log('[DEBUG_SYSTEM][ui] parseSubAgentDoneLabel: 空内容'); + return null; + } + const match = content.match(SUB_AGENT_DONE_PREFIX_RE); + const isCompleted = /已完成/.test(content); + console.log('[DEBUG_SYSTEM][ui] parseSubAgentDoneLabel', { + content, + regex: SUB_AGENT_DONE_PREFIX_RE.toString(), + matched: !!match, + matchAgentId: match?.[1], + isCompleted + }); + if (!match || !isCompleted) { + return null; + } + const agentId = match[1]; + return `子智能体${agentId} 任务完成`; +} + export const uiMethods = { ensureScrollListener() { if (this._scrollListenerReady) { @@ -759,10 +783,18 @@ export const uiMethods = { }, addSystemMessage(content) { - if (this.hideSystemMessages) { + console.log('[DEBUG_SYSTEM][ui] addSystemMessage 入参', { content }); + const subAgentDoneLabel = parseSubAgentDoneLabel(content); + if (!subAgentDoneLabel) { + // 其他 system 消息全部隐藏,且不进入渲染链路,避免出现空白块 + console.log('[DEBUG_SYSTEM][ui] addSystemMessage 被拦截(非子智能体完成)', { content }); return; } - this.chatAddSystemMessage(content); + console.log('[DEBUG_SYSTEM][ui] addSystemMessage 通过,写入 action', { + original: content, + normalizedLabel: subAgentDoneLabel + }); + this.chatAddSystemMessage(subAgentDoneLabel, { variant: 'sub_agent_done' }); this.$forceUpdate(); this.conditionalScrollToBottom(); }, diff --git a/static/src/components/chat/ChatArea.vue b/static/src/components/chat/ChatArea.vue index ae5cab6..b07aefb 100644 --- a/static/src/components/chat/ChatArea.vue +++ b/static/src/components/chat/ChatArea.vue @@ -1,7 +1,15 @@ @@ -118,7 +131,7 @@ interface Step { } interface BlockGroup { - type: 'summary' | 'text'; + type: 'summary' | 'text' | 'system'; id: string; actions?: Action[]; content?: string; @@ -204,7 +217,7 @@ const blockGroups = computed(() => { props.actions.forEach((action, idx) => { if (action.type === 'thinking' || action.type === 'tool') { currentGroup.push(action); - } else if (action.type === 'text') { + } else if (action.type === 'text' || action.type === 'system') { // 先保存当前的 thinking/tool 组 if (currentGroup.length > 0) { // 使用第一个 action 的 id 作为 group id,保证稳定性 @@ -217,13 +230,26 @@ const blockGroups = computed(() => { groupIndex++; currentGroup = []; } - // 添加 text 组 - groups.push({ - type: 'text', - id: `text-${idx}`, - content: action.content, - streaming: action.streaming - }); + if (action.type === 'text') { + const text = typeof action.content === 'string' ? action.content : ''; + if (!text.trim()) { + return; + } + // 添加 text 组 + groups.push({ + type: 'text', + id: `text-${idx}`, + content: text, + streaming: action.streaming + }); + } else { + // 添加 system 组(用于子智能体完成通知,保持顺序) + groups.push({ + type: 'system', + id: `system-${idx}`, + content: action.content + }); + } } }); @@ -494,6 +520,18 @@ watch(() => props.actions, () => { margin: 16px 0; } +.sub-agent-system-group { + margin: 0; +} + +.sub-agent-system-group .sub-agent-system-summary-line { + cursor: default; +} + +.sub-agent-system-group .sub-agent-system-summary-line:hover { + background-color: transparent; +} + /* 摘要行(只有文字) */ .summary-line-text { display: grid; diff --git a/static/src/stores/chat.ts b/static/src/stores/chat.ts index 65254a5..4afa71c 100644 --- a/static/src/stores/chat.ts +++ b/static/src/stores/chat.ts @@ -289,15 +289,25 @@ export const useChatStore = defineStore('chat', { msg.streamingText = ''; msg.currentStreamingType = null; }, - addSystemMessage(content: string) { + addSystemMessage(content: string, meta: any = null) { const msg = this.ensureAssistantMessage(); clearAwaitingFirstContent(msg); + console.log('[DEBUG_SYSTEM][store] addSystemMessage 写入 actions', { + content, + meta, + currentMessageIndex: this.currentMessageIndex, + actionsBefore: Array.isArray(msg?.actions) ? msg.actions.length : -1 + }); msg.actions.push({ id: randomId('system'), type: 'system', content, + variant: meta?.variant || null, timestamp: Date.now() }); + console.log('[DEBUG_SYSTEM][store] addSystemMessage 写入完成', { + actionsAfter: Array.isArray(msg?.actions) ? msg.actions.length : -1 + }); }, getActiveThinkingAction(msg: any) { if (!msg || !Array.isArray(msg.actions)) { diff --git a/static/src/styles/components/chat/_chat-area.scss b/static/src/styles/components/chat/_chat-area.scss index fc82856..719e7e6 100644 --- a/static/src/styles/components/chat/_chat-area.scss +++ b/static/src/styles/components/chat/_chat-area.scss @@ -363,6 +363,26 @@ margin-bottom: 24px; } +.message-block.message-block--sub-agent-notice { + margin-bottom: 8px; +} + +.message-block.message-block--before-sub-agent-notice { + margin-bottom: 8px; +} + +.message-block.message-block--before-sub-agent-notice .minimal-blocks-container { + margin-bottom: 8px; +} + +.message-block.message-block--before-sub-agent-notice .minimal-blocks-container > :last-child { + margin-bottom: 0; +} + +.message-block.message-block--sub-agent-notice .minimal-blocks-container { + margin: 0; +} + .user-message { display: flex; flex-direction: column; @@ -937,6 +957,14 @@ body[data-theme='dark'] .more-icon { gap: 8px; } +.sub-agent-system-summary-line { + padding: 0 20px 0 15px; + margin: 0; + font-size: 15px; + line-height: 1.7; + color: var(--claude-text-secondary); +} + .append-block, .append-placeholder { margin: 12px 0;