feat: expose agent collaboration metadata
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user