fix: adjust sub-agent notice spacing in minimal mode

This commit is contained in:
JOJO 2026-04-05 16:58:22 +08:00
parent 2700a25702
commit a5412dae34
8 changed files with 316 additions and 26 deletions

View File

@ -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);

View File

@ -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({

View File

@ -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) {

View File

@ -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();
},

View File

@ -1,7 +1,15 @@
<template>
<div class="messages-area" ref="rootEl">
<div class="messages-flow">
<div v-for="(msg, index) in filteredMessages" :key="index" class="message-block">
<div
v-for="(msg, index) in filteredMessages"
:key="index"
class="message-block"
:class="{
'message-block--sub-agent-notice': isSubAgentNoticeOnlyMessage(msg),
'message-block--before-sub-agent-notice': isFollowedBySubAgentNotice(index)
}"
>
<div v-if="msg.role === 'user'" class="user-message">
<div class="message-header icon-label">
<span class="icon icon-sm" :style="iconStyleSafe('user')" aria-hidden="true"></span>
@ -24,7 +32,7 @@
<div v-else-if="msg.role === 'assistant'" class="assistant-message">
<!-- assistant 消息前显示 AI Assistant 头部 -->
<!-- 只有当前一条消息是 user 且当前消息有内容时才显示 -->
<div v-if="index > 0 && filteredMessages[index - 1].role === 'user' && (msg.actions?.length > 0 || msg.awaitingFirstContent)" class="message-header icon-label">
<div v-if="index > 0 && filteredMessages[index - 1].role === 'user' && (hasRenderableAssistantActions(msg.actions || []) || msg.awaitingFirstContent)" class="message-header icon-label">
<span class="icon icon-sm" :style="iconStyleSafe('bot')" aria-hidden="true"></span>
<span>{{ aiAssistantName }}</span>
<span v-if="assistantWorkLabel(index)" class="assistant-work-status">{{ assistantWorkLabel(index) }}</span>
@ -52,6 +60,7 @@
</div>
<template v-if="blockDisplayMode === 'minimal'">
<MinimalBlocks
v-if="hasRenderableAssistantActions(msg.actions || [])"
:actions="msg.actions || []"
:conversation-running="streamingMessage"
:is-latest-message="index === latestMessageIndex"
@ -87,7 +96,7 @@
:format-search-domains="formatSearchDomains"
/>
<div
v-else
v-else-if="isActionVisible(group.action)"
class="action-item"
:key="group.action?.id || `${index}-${group.actionIndex}`"
:class="{
@ -139,10 +148,11 @@
</div>
</div>
<div v-else-if="group.action?.type === 'system'" class="system-action">
<div class="system-action-content">
{{ group.action.content }}
</div>
<div
v-else-if="group.action?.type === 'system' && getSubAgentSystemNoticeLabel(group.action)"
class="sub-agent-system-summary-line"
>
{{ getSubAgentSystemNoticeLabel(group.action) }}
</div>
<div
@ -224,6 +234,7 @@
<template v-else>
<div
v-for="(action, actionIndex) in msg.actions || []"
v-if="isActionVisible(action)"
:key="action.id || `${index}-${actionIndex}`"
class="action-item"
:class="{
@ -274,10 +285,11 @@
</div>
</div>
<div v-else-if="action.type === 'system'" class="system-action">
<div class="system-action-content">
{{ action.content }}
</div>
<div
v-else-if="action.type === 'system' && getSubAgentSystemNoticeLabel(action)"
class="sub-agent-system-summary-line"
>
{{ getSubAgentSystemNoticeLabel(action) }}
</div>
<div
@ -423,12 +435,25 @@ const userName = computed(() => {
}
return personalization.form.user_name || '用户';
});
const filteredMessages = computed(() =>
(props.messages || []).filter(m => !(m && m.metadata && m.metadata.system_injected_image) && m.role !== 'system')
);
const filteredMessages = computed(() => {
const source = props.messages || [];
const droppedRoleSystem = source.filter(m => m && m.role === 'system');
if (droppedRoleSystem.length > 0) {
console.log('[DEBUG_SYSTEM][render] filteredMessages 过滤了 role=system 消息', {
count: droppedRoleSystem.length,
samples: droppedRoleSystem.slice(0, 3)
});
}
return source.filter(m => !(m && m.metadata && m.metadata.system_injected_image) && m.role !== 'system');
});
const latestMessageIndex = computed(() => filteredMessages.value.length - 1);
const SUB_AGENT_DONE_LABEL_RE = /^子智能体\d+\s*任务完成$/;
const SUB_AGENT_DONE_PREFIX_RE = /^(?:✅\s*)?子智能体\s*#?\s*(\d+)\s*任务摘要[:]/;
const RENDERABLE_ACTION_TYPES = new Set(['thinking', 'text', 'tool', 'append', 'append_payload', 'system']);
const nowMs = ref(Date.now());
let timerHandle: number | null = null;
const debugLoggedSystemActionKeys = new Set<string>();
const debugLoggedUnknownActionKeys = new Set<string>();
const DEFAULT_GENERATING_TEXT = '生成中…';
const rootEl = ref<HTMLElement | null>(null);
@ -486,6 +511,88 @@ function formatDurationMs(durationMs: number): string {
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}
function getSubAgentSystemNoticeLabel(action: any): string | null {
if (!action || action.type !== 'system') {
return null;
}
const content = typeof action.content === 'string' ? action.content.trim() : '';
const key = action?.id || `no-id-${content.slice(0, 32)}`;
if (!content) {
if (!debugLoggedSystemActionKeys.has(`${key}-empty`)) {
console.log('[DEBUG_SYSTEM][render] system action 内容为空', { action });
debugLoggedSystemActionKeys.add(`${key}-empty`);
}
return null;
}
if (action.variant === 'sub_agent_done' && SUB_AGENT_DONE_LABEL_RE.test(content)) {
if (!debugLoggedSystemActionKeys.has(`${key}-variant-pass`)) {
console.log('[DEBUG_SYSTEM][render] 命中 variant 渲染', { action, label: content });
debugLoggedSystemActionKeys.add(`${key}-variant-pass`);
}
return content;
}
const m = content.match(SUB_AGENT_DONE_PREFIX_RE);
if (m && /已完成/.test(content)) {
if (!debugLoggedSystemActionKeys.has(`${key}-regex-pass`)) {
console.log('[DEBUG_SYSTEM][render] 命中 regex 渲染', { action, normalized: `子智能体${m[1]} 任务完成` });
debugLoggedSystemActionKeys.add(`${key}-regex-pass`);
}
return `子智能体${m[1]} 任务完成`;
}
if (!debugLoggedSystemActionKeys.has(`${key}-blocked`)) {
console.log('[DEBUG_SYSTEM][render] system action 被渲染层拦截', {
action,
regex: SUB_AGENT_DONE_PREFIX_RE.toString(),
completed: /已完成/.test(content)
});
debugLoggedSystemActionKeys.add(`${key}-blocked`);
}
return null;
}
function isActionVisible(action: any): boolean {
if (!action) {
return false;
}
const actionType = action.type;
if (!RENDERABLE_ACTION_TYPES.has(actionType)) {
const key = action?.id || `unknown-${actionType || 'none'}`;
if (!debugLoggedUnknownActionKeys.has(key)) {
console.log('[DEBUG_SYSTEM][render] 未知 action.type已跳过渲染', {
actionType,
action
});
debugLoggedUnknownActionKeys.add(key);
}
return false;
}
if (action.type !== 'system') {
return true;
}
return !!getSubAgentSystemNoticeLabel(action);
}
function hasRenderableAssistantActions(actions: any[] = []): boolean {
return (actions || []).some((action) => isActionVisible(action));
}
function isSubAgentNoticeOnlyMessage(msg: any): boolean {
if (!msg || msg.role !== 'assistant') {
return false;
}
const visibleActions = (msg.actions || []).filter((action: any) => isActionVisible(action));
if (visibleActions.length !== 1) {
return false;
}
const onlyAction = visibleActions[0];
return onlyAction?.type === 'system' && !!getSubAgentSystemNoticeLabel(onlyAction);
}
function isFollowedBySubAgentNotice(index: number): boolean {
const next = filteredMessages.value[index + 1];
return !!next && isSubAgentNoticeOnlyMessage(next);
}
function assistantWorkLabel(index: number): string {
if (index <= 0) {
return '';

View File

@ -85,6 +85,19 @@
<div v-else v-html="renderMarkdown(group.content || '', false)"></div>
</div>
</div>
<div v-else-if="group.type === 'system'" class="summary-group sub-agent-system-group">
<div class="summary-line-text sub-agent-system-summary-line">
<div class="summary-content-wrapper">
<span class="summary-preview">{{ group.content || '' }}</span>
</div>
<div class="summary-status-icon">
<svg class="check-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10" />
<polyline points="7.5 12 10.5 15 16.5 9" />
</svg>
</div>
</div>
</div>
</template>
</div>
</template>
@ -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;

View File

@ -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)) {

View File

@ -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;