feat: 优化任务轮询和聊天区域渲染机制

This commit is contained in:
JOJO 2026-04-13 16:23:01 +08:00
parent 78df2249cf
commit 3219df388b
5 changed files with 294 additions and 25 deletions

View File

@ -26,6 +26,18 @@ function parseSystemNoticeLabel(rawContent: any): string | null {
return parseSubAgentDoneLabel(rawContent) || parseBackgroundRunCommandDoneLabel(rawContent);
}
function isSystemAutoUserMessageMeta(meta: any): boolean {
if (!meta || typeof meta !== 'object') {
return false;
}
return !!(
meta.is_auto_generated ||
meta.auto_message_type ||
meta.sub_agent_notice ||
meta.background_command_notice
);
}
export const historyMethods = {
// ==========================================
// 关键功能:获取并显示历史对话内容
@ -199,6 +211,14 @@ export const historyMethods = {
debugLog('跳过系统代发的图片/视频消息(仅用于模型查看,不在前端展示)');
return;
}
if (message.role === 'user' && isSystemAutoUserMessageMeta(meta)) {
debugLog('跳过系统自动代发的 user 消息(前端不渲染)', {
auto_message_type: meta.auto_message_type || null,
sub_agent_notice: !!meta.sub_agent_notice,
background_command_notice: !!meta.background_command_notice
});
return;
}
if (message.role === 'user') {
// 用户消息 - 先结束之前的assistant消息

View File

@ -10,12 +10,60 @@ const keyNotifyLog = (...args: any[]) => {
const jsonDebug = (...args: any[]) => {
console.log('[JSONDEBUG]', ...args);
};
const userMDebug = (...args: any[]) => {
console.log('[USERMDEBUG]', ...args);
};
function isSystemAutoUserMessagePayload(data: any): boolean {
if (!data || typeof data !== 'object') {
return false;
}
const meta = data.metadata || {};
return !!(
data.is_auto_generated ||
meta.is_auto_generated ||
data.auto_message_type ||
meta.auto_message_type ||
data.sub_agent_notice ||
meta.sub_agent_notice ||
data.background_command_notice ||
meta.background_command_notice
);
}
function isEmptyAssistantPlaceholderMessage(message: any): boolean {
if (!message || message.role !== 'assistant') {
return false;
}
const actions = Array.isArray(message.actions) ? message.actions : [];
return actions.length === 0 && !!message.awaitingFirstContent;
}
/**
*
* REST API
*/
export const taskPollingMethods = {
cleanupTrailingEmptyAssistantPlaceholder(reason = 'unspecified') {
if (!Array.isArray(this.messages) || !this.messages.length) {
return false;
}
const last = this.messages[this.messages.length - 1];
if (!isEmptyAssistantPlaceholderMessage(last)) {
return false;
}
this.messages.pop();
if (typeof this.currentMessageIndex === 'number') {
this.currentMessageIndex = this.messages.length - 1;
}
userMDebug('taskPolling.cleanupTrailingEmptyAssistantPlaceholder:removed', {
reason,
messagesLengthAfter: this.messages.length,
currentMessageIndex: this.currentMessageIndex
});
return true;
},
stopWaitingTaskProbe() {
if (this.waitingTaskProbeTimer) {
debugNotifyLog('[DEBUG_NOTIFY][ui] stopWaitingTaskProbe');
@ -369,12 +417,34 @@ export const taskPollingMethods = {
// 判断是否是刷新恢复的情况:
// 1. 有assistant消息
// 2. 且该消息正在streaming或者没有任何内容说明是刚创建的
// 3. 且不是历史加载完成的消息历史消息的awaitingFirstContent应该是false
// 2. 且最后一条 assistant 仍处于“未完成流式状态”(而不是普通已完成消息)
// 3. 且任务确实仍在进行中
const lastActions = Array.isArray(lastMessage?.actions) ? lastMessage.actions : [];
const hasUnfinishedAction = lastActions.some((action: any) => {
if (!action) return false;
if (action.streaming) return true;
if (action.type === 'tool' && action.tool) {
const status = String(action.tool.status || '').toLowerCase();
return ['preparing', 'running', 'pending', 'queued', 'awaiting_approval'].includes(status);
}
return false;
});
const hasNoActionsYet = !lastActions.length;
const isRefreshRestore =
hasAssistantMessage &&
(this.streamingMessage || !lastMessage.actions || lastMessage.actions.length === 0) &&
(lastMessage.awaitingFirstContent === true || this.taskInProgress);
(hasNoActionsYet || hasUnfinishedAction || lastMessage.awaitingFirstContent === true) &&
(this.taskInProgress || this.streamingMessage);
userMDebug('taskPolling.handleAiMessageStart:decision', {
eventIdx,
hasAssistantMessage,
hasNoActionsYet,
hasUnfinishedAction,
awaitingFirstContent: !!lastMessage?.awaitingFirstContent,
taskInProgress: this.taskInProgress,
streamingMessage: this.streamingMessage,
isRefreshRestore
});
console.log('[AiMessageStart] 场景判断', {
isRefreshRestore,
@ -942,6 +1012,7 @@ export const taskPollingMethods = {
this.clearProcessedEvents();
this.startWaitingTaskProbe();
} else {
this.cleanupTrailingEmptyAssistantPlaceholder('task_complete');
// 主任务已结束:若有遗留工具块处于 running/preparing会导致发送按钮继续显示“停止”。
// 这里统一清理遗留中的工具状态,避免前端忙碌态卡死。
if (typeof this.clearPendingTools === 'function') {
@ -988,6 +1059,7 @@ export const taskPollingMethods = {
debugLog('[TaskPolling] 任务已停止, idx:', eventIdx, data);
this.markLatestUserWorkCompleted();
this.cleanupTrailingEmptyAssistantPlaceholder('task_stopped');
this.streamingMessage = false;
this.taskInProgress = false;
this.stopRequested = false;
@ -1043,10 +1115,18 @@ export const taskPollingMethods = {
sub_agent_notice: !!data?.sub_agent_notice,
background_command_notice: !!data?.background_command_notice
});
this.chatAddUserMessage(message, data?.images || [], data?.videos || []);
this.taskInProgress = true;
this.streamingMessage = false;
this.stopRequested = false;
const isAutoUserMessage = isSystemAutoUserMessagePayload(data);
if (!isAutoUserMessage) {
this.chatAddUserMessage(message, data?.images || [], data?.videos || []);
this.taskInProgress = true;
this.streamingMessage = false;
this.stopRequested = false;
} else {
debugLog('[TaskPolling] 跳过自动代发 user_message 渲染', {
messagePreview: message.slice(0, 80),
data
});
}
if (data?.sub_agent_notice) {
if (typeof data?.has_running_sub_agents === 'boolean') {
this.waitingForSubAgent = data.has_running_sub_agents;
@ -1070,16 +1150,23 @@ export const taskPollingMethods = {
handleSystemMessage(data: any) {
const content = (data?.content || data?.message || '').trim();
console.log('[DEBUG_SYSTEM][polling] 收到 system_message 事件', {
userMDebug('taskPolling.handleSystemMessage:incoming', {
raw: data,
normalizedContent: content,
currentConversationId: this.currentConversationId
currentConversationId: this.currentConversationId,
currentMessagesLength: Array.isArray(this.messages) ? this.messages.length : -1
});
if (!content) {
console.log('[DEBUG_SYSTEM][polling] system_message 内容为空,跳过');
userMDebug('taskPolling.handleSystemMessage:empty-skip');
return;
}
this.cleanupTrailingEmptyAssistantPlaceholder('before_system_message_append');
this.appendSystemAction(content);
userMDebug('taskPolling.handleSystemMessage:after-append', {
currentMessagesLength: Array.isArray(this.messages) ? this.messages.length : -1,
lastRole: this.messages?.[this.messages.length - 1]?.role || null,
lastActions: this.messages?.[this.messages.length - 1]?.actions?.map((a: any) => a?.type) || []
});
},
handleTaskError(data: any) {

View File

@ -19,6 +19,9 @@ import { debugLog } from './common';
const SUB_AGENT_DONE_PREFIX_RE = /^(?:✅\s*)?子智能体\s*#?\s*(\d+)\s*任务摘要[:]/;
const BG_RUN_COMMAND_DONE_PREFIX_RE = /^\[后台\s*run_command\s*完成\]/;
const userMDebug = (...args: any[]) => {
console.log('[USERMDEBUG]', ...args);
};
function parseSubAgentDoneLabel(rawContent: any): string | null {
const content = (rawContent || '').toString().trim();
@ -1114,14 +1117,14 @@ export const uiMethods = {
},
addSystemMessage(content) {
console.log('[DEBUG_SYSTEM][ui] addSystemMessage 入参', { content });
userMDebug('ui.addSystemMessage:incoming', { content });
const systemNoticeLabel = parseSystemNoticeLabel(content);
if (!systemNoticeLabel) {
// 其他 system 消息全部隐藏,且不进入渲染链路,避免出现空白块
console.log('[DEBUG_SYSTEM][ui] addSystemMessage 被拦截(非完成通知)', { content });
userMDebug('ui.addSystemMessage:blocked', { content });
return;
}
console.log('[DEBUG_SYSTEM][ui] addSystemMessage 通过,写入 action', {
userMDebug('ui.addSystemMessage:accepted', {
original: content,
normalizedLabel: systemNoticeLabel
});

View File

@ -579,7 +579,7 @@
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import ToolAction from '@/components/chat/actions/ToolAction.vue';
import StackedBlocks from './StackedBlocks.vue';
import MinimalBlocks from './MinimalBlocks.vue';
@ -622,6 +622,26 @@ const userName = computed(() => {
}
return personalization.form.user_name || '用户';
});
const isSystemAutoUserMessage = (message: any) => {
if (!message || message.role !== 'user') {
return false;
}
const meta = message.metadata || {};
return !!(
meta.is_auto_generated ||
meta.auto_message_type ||
meta.sub_agent_notice ||
meta.background_command_notice
);
};
const isEmptyAssistantMessage = (message: any) => {
if (!message || message.role !== 'assistant') {
return false;
}
const actions = Array.isArray(message.actions) ? message.actions : [];
const hasRenderable = actions.some((action) => isActionVisible(action));
return !hasRenderable && !message.awaitingFirstContent;
};
const filteredMessages = computed(() => {
const source = props.messages || [];
const droppedRoleSystem = source.filter((m) => m && m.role === 'system');
@ -631,9 +651,33 @@ const filteredMessages = computed(() => {
samples: droppedRoleSystem.slice(0, 3)
});
}
return source.filter(
(m) => !(m && m.metadata && m.metadata.system_injected_image) && m.role !== 'system'
);
const result = source.filter((m) => {
if (m && m.metadata && m.metadata.system_injected_image) {
return false;
}
if (m?.role === 'system') {
return false;
}
if (isSystemAutoUserMessage(m)) {
return false;
}
if (isEmptyAssistantMessage(m)) {
userMDebug('ChatArea.filteredMessages:drop-empty-assistant', {
actions: Array.isArray(m?.actions) ? m.actions.map((a: any) => a?.type) : [],
awaitingFirstContent: !!m?.awaitingFirstContent
});
return false;
}
return true;
});
if (source.length !== result.length) {
userMDebug('ChatArea.filteredMessages:dropped', {
sourceLength: source.length,
resultLength: result.length,
dropped: source.length - result.length
});
}
return result;
});
const latestMessageIndex = computed(() => filteredMessages.value.length - 1);
const SUB_AGENT_DONE_LABEL_RE = /^子智能体\d+\s*任务完成$/;
@ -651,6 +695,16 @@ const nowMs = ref(Date.now());
let timerHandle: number | null = null;
const debugLoggedSystemActionKeys = new Set<string>();
const debugLoggedUnknownActionKeys = new Set<string>();
const userMDebug = (...args: any[]) => {
console.log('[USERMDEBUG]', ...args);
};
const safeJson = (value: any) => {
try {
return JSON.stringify(value);
} catch {
return '[unserializable]';
}
};
const DEFAULT_GENERATING_TEXT = '生成中…';
const rootEl = ref<HTMLElement | null>(null);
@ -788,12 +842,27 @@ function isSubAgentNoticeOnlyMessage(msg: any): boolean {
return false;
}
const onlyAction = visibleActions[0];
return onlyAction?.type === 'system' && !!getSubAgentSystemNoticeLabel(onlyAction);
const yes = onlyAction?.type === 'system' && !!getSubAgentSystemNoticeLabel(onlyAction);
if (yes) {
userMDebug('ChatArea.isSubAgentNoticeOnlyMessage:hit', {
actionId: onlyAction?.id,
content: onlyAction?.content
});
}
return yes;
}
function isFollowedBySubAgentNotice(index: number): boolean {
const next = filteredMessages.value[index + 1];
return !!next && isSubAgentNoticeOnlyMessage(next);
const yes = !!next && isSubAgentNoticeOnlyMessage(next);
if (yes) {
userMDebug('ChatArea.isFollowedBySubAgentNotice:hit', {
index,
currentRole: filteredMessages.value[index]?.role,
nextRole: next?.role
});
}
return yes;
}
function assistantWorkLabel(index: number): string {
@ -829,11 +898,72 @@ function assistantWorkLabel(index: number): string {
}
onMounted(() => {
userMDebug('ChatArea.mounted:tail-shape', {
filteredLength: filteredMessages.value.length,
tail: filteredMessages.value.slice(-8).map((m, i) => ({
idx: filteredMessages.value.length - 8 + i,
role: m?.role,
actionTypes: Array.isArray(m?.actions) ? m.actions.map((a: any) => a?.type) : []
}))
});
timerHandle = window.setInterval(() => {
nowMs.value = Date.now();
}, 1000);
});
watch(
filteredMessages,
(next) => {
const tail = (next || []).slice(-6).map((m: any, i: number) => {
const actionTypes = Array.isArray(m?.actions)
? m.actions.map((a: any) => `${a?.type}${a?.streaming ? ':streaming' : ''}`)
: [];
const idx = (next?.length || 0) - Math.min(6, next?.length || 0) + i;
return {
idx,
role: m?.role,
actionTypes,
isSubAgentNoticeOnlyMessage: isSubAgentNoticeOnlyMessage(m),
followedBySubAgentNotice: isFollowedBySubAgentNotice(idx)
};
});
userMDebug('ChatArea.watch:filteredMessages-changed', {
length: next?.length || 0,
tail
});
userMDebug('ChatArea.watch:filteredMessages-changed:json', safeJson({
length: next?.length || 0,
tail
}));
nextTick(() => {
requestAnimationFrame(() => {
const root = rootEl.value;
if (!root) return;
const blocks = Array.from(root.querySelectorAll('.message-block')) as HTMLElement[];
const last = blocks.slice(-4);
const geometry = last.map((el, i) => {
const r = el.getBoundingClientRect();
const prev = i > 0 ? last[i - 1].getBoundingClientRect() : null;
const gapFromPrev = prev ? Math.round((r.top - prev.bottom) * 100) / 100 : null;
return {
className: el.className,
top: Math.round(r.top * 100) / 100,
bottom: Math.round(r.bottom * 100) / 100,
height: Math.round(r.height * 100) / 100,
gapFromPrev
};
});
userMDebug('ChatArea.layout:last-blocks', {
totalBlocks: blocks.length,
geometry
});
});
});
},
{ deep: true }
);
onBeforeUnmount(() => {
if (timerHandle !== null) {
clearInterval(timerHandle);

View File

@ -70,6 +70,9 @@ function clearAwaitingFirstContent(message: any) {
message.awaitingFirstContent = false;
}
}
const userMDebug = (...args: any[]) => {
console.log('[USERMDEBUG]', ...args);
};
export const useChatStore = defineStore('chat', {
state: (): ChatState => ({
@ -290,22 +293,48 @@ export const useChatStore = defineStore('chat', {
msg.currentStreamingType = null;
},
addSystemMessage(content: string, meta: any = null) {
const msg = this.ensureAssistantMessage();
// 与历史重建保持一致:子智能体/后台完成通知作为独立 assistant 消息渲染,
// 避免运行时与刷新后在垂直间距上不一致。
const useStandaloneMessage = meta?.variant === 'sub_agent_done';
if (useStandaloneMessage && Array.isArray(this.messages) && this.messages.length > 0) {
const last = this.messages[this.messages.length - 1];
const lastActions = Array.isArray(last?.actions) ? last.actions : [];
const isEmptyAssistantPlaceholder =
last?.role === 'assistant' && lastActions.length === 0 && !!last?.awaitingFirstContent;
if (isEmptyAssistantPlaceholder) {
userMDebug('chat.addSystemMessage:cleanup-empty-assistant-placeholder', {
currentMessageIndex: this.currentMessageIndex,
messagesLengthBeforeCleanup: this.messages.length
});
this.messages.pop();
this.currentMessageIndex = this.messages.length - 1;
}
}
const msg = useStandaloneMessage ? createAssistantMessage() : this.ensureAssistantMessage();
clearAwaitingFirstContent(msg);
console.log('[DEBUG_SYSTEM][store] addSystemMessage 写入 actions', {
userMDebug('chat.addSystemMessage:before', {
content,
meta,
useStandaloneMessage,
messagesLengthBefore: this.messages.length,
currentMessageIndex: this.currentMessageIndex,
actionsBefore: Array.isArray(msg?.actions) ? msg.actions.length : -1
});
msg.actions.push({
const action = {
id: randomId('system'),
type: 'system',
content,
variant: meta?.variant || null,
timestamp: Date.now()
});
console.log('[DEBUG_SYSTEM][store] addSystemMessage 写入完成', {
};
msg.actions.push(action);
if (useStandaloneMessage) {
this.messages.push(msg);
this.currentMessageIndex = this.messages.length - 1;
}
userMDebug('chat.addSystemMessage:after', {
messagesLengthAfter: this.messages.length,
currentMessageIndex: this.currentMessageIndex,
actionsAfter: Array.isArray(msg?.actions) ? msg.actions.length : -1
});
},