feat: 优化任务轮询和聊天区域渲染机制
This commit is contained in:
parent
78df2249cf
commit
3219df388b
@ -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消息
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
});
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user