feat: expose agent collaboration metadata

This commit is contained in:
2026-06-14 15:35:24 +08:00
parent 3f75c966c2
commit 7b7c82309c
6 changed files with 546 additions and 107 deletions
@@ -192,6 +192,39 @@ const getStepColor = (step: ReasoningStep) => {
return colors[step.type] || 'text-gray-500';
};
const getStepMetaItems = (step: ReasoningStep) => {
const items: string[] = [];
if (step.agent_name || step.agent_code) {
items.push(`智能体: ${step.agent_name || step.agent_code}`);
}
if (step.branch_label || step.branch_id) {
items.push(`分支: ${step.branch_label || step.branch_id}`);
}
if (step.subflow_name) {
items.push(`子流程: ${step.subflow_name}`);
}
if (step.model || step.model_id) {
items.push(`模型: ${step.model || step.model_id}`);
}
return items;
};
const messageMetaItems = computed(() => {
if (isUser.value) return [];
const items: string[] = [];
if (props.message.agent_name || props.message.agent_code) {
items.push(`智能体: ${props.message.agent_name || props.message.agent_code}`);
}
if (props.message.model_name || props.message.model_id) {
items.push(`模型: ${props.message.model_name || props.message.model_id}`);
}
if (props.message.collaboration_mode) {
items.push(`模式: ${props.message.collaboration_mode}`);
}
return items;
});
// 格式化文件大小
const formatFileSize = (bytes?: number) => {
if (!bytes) return '';
@@ -293,6 +326,16 @@ const formatVoiceDuration = (seconds: number) => {
</div>
<!-- 消息气泡 -->
<div v-if="messageMetaItems.length" class="chat-message-meta">
<span
v-for="item in messageMetaItems"
:key="item"
class="chat-message-meta-item"
>
{{ item }}
</span>
</div>
<div class="chat-bubble" :class="{ 'is-error': isFailed }">
<!-- 执行步骤执行中展开显示完成后折叠 -->
<div v-if="message.reasoning_steps?.length" class="bubble-steps">
@@ -320,6 +363,15 @@ const formatVoiceDuration = (seconds: number) => {
<span class="step-text" :title="step.content">
{{ step.content }}
</span>
<div v-if="getStepMetaItems(step).length" class="step-meta">
<span
v-for="item in getStepMetaItems(step)"
:key="item"
class="step-meta-item"
>
{{ item }}
</span>
</div>
</div>
</div>
</div>
@@ -608,6 +660,27 @@ const formatVoiceDuration = (seconds: number) => {
}
/* 推理步骤 */
.chat-message-meta {
display: flex;
min-width: 0;
flex-wrap: wrap;
gap: 4px;
}
.chat-message-meta-item {
max-width: 100%;
overflow: hidden;
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
padding: 1px 5px;
background: var(--el-fill-color-blank);
color: var(--el-text-color-secondary);
font-size: 12px;
line-height: 18px;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-reasoning {
width: 100%;
margin-bottom: 8px;
@@ -1257,9 +1330,11 @@ const formatVoiceDuration = (seconds: number) => {
}
.bubble-step {
display: flex;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: center;
gap: 8px;
column-gap: 8px;
row-gap: 4px;
padding: 3px 0;
font-size: 12px;
color: var(--el-text-color-secondary);
@@ -1285,4 +1360,24 @@ const formatVoiceDuration = (seconds: number) => {
text-overflow: ellipsis;
white-space: nowrap;
}
.step-meta {
grid-column: 2;
display: flex;
min-width: 0;
flex-wrap: wrap;
gap: 4px;
}
.step-meta-item {
max-width: 100%;
overflow: hidden;
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
padding: 1px 5px;
background: var(--el-fill-color-blank);
color: var(--el-text-color-secondary);
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
@@ -62,6 +62,16 @@ export interface ReasoningStep {
result?: any;
node_id?: string;
node_type?: string;
branch_id?: string;
branch_label?: string;
agent_code?: string;
agent_name?: string;
model?: string;
model_id?: string;
subflow_name?: string;
from_subflow?: boolean;
collaboration_role?: string;
collaboration_mode?: string;
output?: any;
/** 步骤状态:running 执行中,completed 已完成 */
status?: 'completed' | 'running';
@@ -137,6 +147,10 @@ export interface ChatMessage {
error_message?: string;
feedback?: 'dislike' | 'like' | null;
model_name?: string;
agent_code?: string;
agent_name?: string;
collaboration_mode?: string;
model_id?: string;
/** 语音消息 */
voice?: VoiceMessage;
/** 对话流交互配置(等待用户输入时) */
@@ -170,6 +170,48 @@ export function useEventHandler(config: EventHandlerConfig) {
return 0;
};
const buildReasoningMeta = (event: StreamEvent): Partial<ReasoningStep> => {
const collaboration = event.collaboration || {};
return {
branch_id: event.branch_id,
branch_label: event.branch_label,
agent_code: collaboration.agent_code || event.agent_code,
agent_name: collaboration.agent_name || event.agent_name,
model: collaboration.model || event.model,
model_id: collaboration.model_id || event.model_id,
subflow_name:
event.subflow_name || collaboration.subflow_name || event.event?.subflow_name,
from_subflow: Boolean(
event.from_subflow || collaboration.from_subflow || event.event?.from_subflow,
),
collaboration_role:
collaboration.collaboration_role || collaboration.role || event.collaboration_role,
collaboration_mode: event.collaboration_mode || collaboration.collaboration_mode,
};
};
const buildMessageMeta = (event: StreamEvent): Partial<ChatMessage> => {
const collaboration = event.collaboration || {};
const modelName =
event.model ||
event.model_name ||
collaboration.model ||
collaboration.model_id;
return {
...(modelName ? { model_name: modelName } : {}),
...(collaboration.model_id ? { model_id: collaboration.model_id } : {}),
...(collaboration.agent_code
? { agent_code: collaboration.agent_code }
: {}),
...(collaboration.agent_name
? { agent_name: collaboration.agent_name }
: {}),
...(collaboration.collaboration_mode
? { collaboration_mode: collaboration.collaboration_mode }
: {}),
};
};
/** 处理流式事件 */
const handleStreamEvent = (event: StreamEvent, msgId: string) => {
switch (event.type) {
@@ -194,6 +236,7 @@ export function useEventHandler(config: EventHandlerConfig) {
updateAssistantMessage(msgId, {
status: 'completed',
content: event.content || '',
...buildMessageMeta(event),
});
} else {
const answerMsg: ChatMessage = {
@@ -202,6 +245,7 @@ export function useEventHandler(config: EventHandlerConfig) {
content: event.content || '',
timestamp: new Date(),
status: 'completed',
...buildMessageMeta(event),
};
messages.value.push(answerMsg);
}
@@ -228,6 +272,7 @@ export function useEventHandler(config: EventHandlerConfig) {
content: endOutput,
elapsed_time: event.elapsed_time,
tokens_used: event.tokens_used || event.total_tokens,
...buildMessageMeta(event),
});
} else {
const endMsg: ChatMessage = {
@@ -238,6 +283,7 @@ export function useEventHandler(config: EventHandlerConfig) {
status: 'completed',
elapsed_time: event.elapsed_time,
tokens_used: event.tokens_used || event.total_tokens,
...buildMessageMeta(event),
};
messages.value.push(endMsg);
}
@@ -259,6 +305,7 @@ export function useEventHandler(config: EventHandlerConfig) {
elapsed_time: event.elapsed_time,
tokens_used: event.tokens_used || event.total_tokens,
content: '工作流执行完成',
...buildMessageMeta(event),
});
}
} else if (initialMsg) {
@@ -267,6 +314,7 @@ export function useEventHandler(config: EventHandlerConfig) {
status: 'completed',
elapsed_time: event.elapsed_time,
tokens_used: event.tokens_used || event.total_tokens,
...buildMessageMeta(event),
});
}
@@ -283,6 +331,7 @@ export function useEventHandler(config: EventHandlerConfig) {
content: event.message || event.content || '执行失败',
node_id: event.node_id,
node_type: event.node_type,
...buildReasoningMeta(event),
status: 'completed',
timestamp: new Date().toISOString(),
});
@@ -302,6 +351,7 @@ export function useEventHandler(config: EventHandlerConfig) {
updateAssistantMessage(msgId, {
content: event.accumulated_content || event.content || '',
status: 'streaming',
...buildMessageMeta(event),
});
} else {
// 如果消息不存在,创建新消息
@@ -311,6 +361,7 @@ export function useEventHandler(config: EventHandlerConfig) {
content: event.accumulated_content || event.content || '',
timestamp: new Date(),
status: 'streaming',
...buildMessageMeta(event),
};
messages.value.push(streamMsg);
}
@@ -342,6 +393,7 @@ export function useEventHandler(config: EventHandlerConfig) {
content: '循环执行完成',
node_id: loopEvent.node_id,
node_type: 'loop',
...buildReasoningMeta(event),
output: outputs,
status: 'completed',
timestamp: new Date().toISOString(),
@@ -369,6 +421,7 @@ export function useEventHandler(config: EventHandlerConfig) {
content: `循环第 ${event.iteration || '-'} 次完成`,
node_id: event.node_id,
node_type: 'loop',
...buildReasoningMeta(event),
output: event.output,
status: 'completed',
timestamp: new Date().toISOString(),
@@ -395,6 +448,7 @@ export function useEventHandler(config: EventHandlerConfig) {
content: `循环第 ${event.iteration || '-'} 次失败:${event.error || '执行失败'}`,
node_id: event.node_id,
node_type: 'loop',
...buildReasoningMeta(event),
status: 'completed',
timestamp: new Date().toISOString(),
});
@@ -420,6 +474,7 @@ export function useEventHandler(config: EventHandlerConfig) {
content: `循环第 ${event.iteration || '-'} 次开始`,
node_id: event.node_id,
node_type: 'loop',
...buildReasoningMeta(event),
params: { item: event.item, total: event.total },
status: 'running',
timestamp: new Date().toISOString(),
@@ -455,6 +510,7 @@ export function useEventHandler(config: EventHandlerConfig) {
content: completeLabel,
node_id: event.node_id,
node_type: event.node_type,
...buildReasoningMeta(event),
output: event.outputs,
status: 'completed',
timestamp: new Date().toISOString(),
@@ -464,6 +520,7 @@ export function useEventHandler(config: EventHandlerConfig) {
...currentSteps.value[existingIndex]!,
type: 'node_complete',
content: completeLabel,
...buildReasoningMeta(event),
output: event.outputs,
status: 'completed',
};
@@ -501,6 +558,7 @@ export function useEventHandler(config: EventHandlerConfig) {
content,
node_id: event.node_id,
node_type: 'parallel',
...buildReasoningMeta(event),
output: {
branch_results: branchResults,
total_tokens: event.total_tokens,
@@ -513,6 +571,7 @@ export function useEventHandler(config: EventHandlerConfig) {
...currentSteps.value[existingIndex]!,
type: 'parallel_complete',
content,
...buildReasoningMeta(event),
output: {
branch_results: branchResults,
total_tokens: event.total_tokens,
@@ -533,7 +592,8 @@ export function useEventHandler(config: EventHandlerConfig) {
content: `并行分支开始:${getItemCount(branches)} 个分支`,
node_id: event.node_id,
node_type: 'parallel',
params: { branches },
...buildReasoningMeta(event),
params: { branches, branch_labels: event.branch_labels },
status: 'running',
timestamp: new Date().toISOString(),
});
@@ -551,6 +611,7 @@ export function useEventHandler(config: EventHandlerConfig) {
updateAssistantMessage(msgId, {
status: 'completed',
content: nodeEvent.content || '',
...buildMessageMeta(event),
});
} else {
const newMsg: ChatMessage = {
@@ -559,6 +620,7 @@ export function useEventHandler(config: EventHandlerConfig) {
content: nodeEvent.content || '',
timestamp: new Date(),
status: 'completed',
...buildMessageMeta(event),
};
messages.value.push(newMsg);
}
@@ -581,6 +643,7 @@ export function useEventHandler(config: EventHandlerConfig) {
),
node_id: event.node_id,
node_type: event.node_type,
...buildReasoningMeta(event),
status: 'running',
timestamp: new Date().toISOString(),
});
@@ -622,6 +685,7 @@ export function useEventHandler(config: EventHandlerConfig) {
'等待用户输入',
node_id: event.node_id,
node_type: event.node_type,
...buildReasoningMeta(event),
params: configData,
status: 'running',
timestamp: new Date().toISOString(),
@@ -91,13 +91,14 @@ const agentSummaries = computed(() => {
for (const log of executionLogs.value) {
const meta = getLogMeta(log);
const code = meta.agent_code;
const collaboration = meta.collaboration || {};
const code = meta.agent_code || collaboration.agent_code;
if (!code) continue;
const current = agents.get(code) || {
code,
model: meta.model || meta.model_id || '-',
name: meta.agent_name || code,
model: meta.model || collaboration.model || meta.model_id || collaboration.model_id || '-',
name: meta.agent_name || collaboration.agent_name || code,
steps: 0,
tokens: 0,
};
@@ -112,6 +113,43 @@ const agentSummaries = computed(() => {
return [...agents.values()];
});
const collaborationTimeline = computed(() => {
return executionLogs.value
.map((log, index) => {
const meta = getLogMeta(log);
const collaboration = meta.collaboration || {};
const agentCode = meta.agent_code || collaboration.agent_code;
const agentName = meta.agent_name || collaboration.agent_name || agentCode;
const branch = log.branch_label || log.branch || collaboration.branch_label;
const subflowName = collaboration.subflow_name || meta.subflow_name;
const model = meta.model || collaboration.model || meta.model_id || collaboration.model_id;
const title =
agentName ||
subflowName ||
branch ||
log.node_label ||
log.node_id ||
`#${index + 1}`;
const summaryParts = [
branch ? `分支:${branch}` : '',
subflowName ? `子流程:${subflowName}` : '',
model ? `模型:${model}` : '',
].filter(Boolean);
return {
key: `${log.node_id || 'node'}-${index}`,
nodeId: log.node_id,
title,
subtitle: log.node_label || log.node_id,
status: log.status,
summary: summaryParts.join(' · '),
elapsed: log.elapsed_time,
tokens: log.tokens_used || 0,
hasSignal: Boolean(agentName || branch || subflowName || model),
};
})
.filter((item) => item.hasSignal);
});
async function loadDetail() {
if (!runId.value) return;
@@ -341,6 +379,40 @@ defineExpose({ open });
<div v-else class="text-muted-foreground text-sm">
{{ $t('ai-platform.workflowRuns.detail.noAgentLogs') }}
</div>
<div class="mt-3 border-t pt-3">
<div class="mb-2 flex items-center justify-between">
<span class="text-sm font-medium">协作通信</span>
<ElTag size="small">{{ collaborationTimeline.length }}</ElTag>
</div>
<div v-if="collaborationTimeline.length" class="collaboration-timeline">
<button
v-for="item in collaborationTimeline"
:key="item.key"
class="collaboration-item"
type="button"
@click="selectLog(item.nodeId)"
>
<span
class="collaboration-dot"
:class="`is-${getLogStatusType(item.status)}`"
></span>
<span class="min-w-0">
<span class="block truncate text-xs font-medium">
{{ item.title }}
</span>
<span class="text-muted-foreground block truncate text-xs">
{{ item.summary || item.subtitle }}
</span>
</span>
<span class="text-muted-foreground shrink-0 text-xs">
{{ formatDuration(item.elapsed) }}
</span>
</button>
</div>
<div v-else class="text-muted-foreground text-sm">
暂无协作通信记录
</div>
</div>
</div>
</div>
@@ -509,6 +581,54 @@ defineExpose({ open });
max-height: 140px;
}
.workflow-run-detail-dialog .collaboration-timeline {
display: flex;
flex-direction: column;
gap: 6px;
}
.workflow-run-detail-dialog .collaboration-item {
display: grid;
grid-template-columns: 8px minmax(0, 1fr) auto;
align-items: center;
gap: 8px;
width: 100%;
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
background: var(--el-fill-color-blank);
padding: 8px 10px;
text-align: left;
cursor: pointer;
}
.workflow-run-detail-dialog .collaboration-item:hover {
border-color: var(--el-color-primary-light-5);
background: var(--el-fill-color-light);
}
.workflow-run-detail-dialog .collaboration-dot {
width: 8px;
height: 8px;
border-radius: 999px;
background: var(--el-text-color-placeholder);
}
.workflow-run-detail-dialog .collaboration-dot.is-success {
background: var(--el-color-success);
}
.workflow-run-detail-dialog .collaboration-dot.is-danger {
background: var(--el-color-danger);
}
.workflow-run-detail-dialog .collaboration-dot.is-warning {
background: var(--el-color-warning);
}
.workflow-run-detail-dialog .collaboration-dot.is-primary {
background: var(--el-color-primary);
}
@media (max-width: 1024px) {
.workflow-run-detail-dialog .run-report-grid,
.workflow-run-detail-dialog .run-detail-main {