feat: improve ai workflow runtime observability
This commit is contained in:
@@ -225,11 +225,10 @@ class TextToSqlNode(BaseNode):
|
|||||||
error='请输入要查询的问题',
|
error='请输入要查询的问题',
|
||||||
)
|
)
|
||||||
|
|
||||||
if not model_id:
|
from ai_platform.services.llm_service import LLMService
|
||||||
return NodeResult(
|
|
||||||
success=False,
|
llm_service = LLMService(context.db_session)
|
||||||
error='请选择 LLM 模型',
|
model_id = await llm_service.resolve_chat_model_id(model_id)
|
||||||
)
|
|
||||||
|
|
||||||
# Step 1: 获取数据库 Schema
|
# Step 1: 获取数据库 Schema
|
||||||
|
|
||||||
@@ -248,10 +247,7 @@ class TextToSqlNode(BaseNode):
|
|||||||
|
|
||||||
# Step 2: 调用 LLM 生成 SQL
|
# Step 2: 调用 LLM 生成 SQL
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from ai_platform.services.llm_service import LLMService
|
|
||||||
|
|
||||||
db_type = await self._get_db_type(db_connection)
|
db_type = await self._get_db_type(db_connection)
|
||||||
llm_service = LLMService(context.db_session)
|
|
||||||
llm_result = None
|
llm_result = None
|
||||||
use_function_calling = self.config.get('use_function_calling', True)
|
use_function_calling = self.config.get('use_function_calling', True)
|
||||||
|
|
||||||
@@ -354,6 +350,7 @@ class TextToSqlNode(BaseNode):
|
|||||||
tokens_used=response.total_tokens,
|
tokens_used=response.total_tokens,
|
||||||
elapsed_time=elapsed_time,
|
elapsed_time=elapsed_time,
|
||||||
metadata={
|
metadata={
|
||||||
|
'model_id': str(model_id),
|
||||||
'model': response.model,
|
'model': response.model,
|
||||||
'thought': thought,
|
'thought': thought,
|
||||||
'suggested_next_node': 'db_sql',
|
'suggested_next_node': 'db_sql',
|
||||||
@@ -404,13 +401,22 @@ class TextToSqlNode(BaseNode):
|
|||||||
)
|
)
|
||||||
return NodeResult(success=False, error='请输入要查询的问题')
|
return NodeResult(success=False, error='请输入要查询的问题')
|
||||||
|
|
||||||
if not model_id:
|
import asyncio
|
||||||
|
from ai_platform.services.llm_service import LLMService
|
||||||
|
|
||||||
|
llm_service = LLMService(context.db_session)
|
||||||
|
try:
|
||||||
|
model_id = asyncio.get_event_loop().run_until_complete(
|
||||||
|
llm_service.resolve_chat_model_id(model_id)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
error_message = str(e)
|
||||||
yield TextToSqlStreamEvent(
|
yield TextToSqlStreamEvent(
|
||||||
event_type='error',
|
event_type='error',
|
||||||
content='请选择 LLM 模型',
|
content=error_message,
|
||||||
is_finished=True,
|
is_finished=True,
|
||||||
)
|
)
|
||||||
return NodeResult(success=False, error='请选择 LLM 模型')
|
return NodeResult(success=False, error=error_message)
|
||||||
|
|
||||||
# Step 1: 获取数据库 Schema
|
# Step 1: 获取数据库 Schema
|
||||||
yield TextToSqlStreamEvent(
|
yield TextToSqlStreamEvent(
|
||||||
@@ -420,7 +426,6 @@ class TextToSqlNode(BaseNode):
|
|||||||
|
|
||||||
table_relations = self.config.get('table_relations', []) # 手动指定的表关系
|
table_relations = self.config.get('table_relations', []) # 手动指定的表关系
|
||||||
|
|
||||||
import asyncio
|
|
||||||
schema_context = asyncio.get_event_loop().run_until_complete(
|
schema_context = asyncio.get_event_loop().run_until_complete(
|
||||||
self._get_schema_context(
|
self._get_schema_context(
|
||||||
db_connection,
|
db_connection,
|
||||||
@@ -449,13 +454,10 @@ class TextToSqlNode(BaseNode):
|
|||||||
)
|
)
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from ai_platform.services.llm_service import LLMService
|
|
||||||
|
|
||||||
db_type = asyncio.get_event_loop().run_until_complete(
|
db_type = asyncio.get_event_loop().run_until_complete(
|
||||||
self._get_db_type(db_connection)
|
self._get_db_type(db_connection)
|
||||||
)
|
)
|
||||||
|
|
||||||
llm_service = LLMService()
|
|
||||||
accumulated_content = ''
|
accumulated_content = ''
|
||||||
total_tokens = 0
|
total_tokens = 0
|
||||||
llm_result = None
|
llm_result = None
|
||||||
@@ -566,6 +568,7 @@ class TextToSqlNode(BaseNode):
|
|||||||
tokens_used=total_tokens,
|
tokens_used=total_tokens,
|
||||||
elapsed_time=elapsed_time,
|
elapsed_time=elapsed_time,
|
||||||
metadata={
|
metadata={
|
||||||
|
'model_id': str(model_id),
|
||||||
'thought': thought,
|
'thought': thought,
|
||||||
'suggested_next_node': 'db_sql',
|
'suggested_next_node': 'db_sql',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -71,6 +71,43 @@ function createLightAiMenuRoute(
|
|||||||
return route as RouteRecordStringComponent;
|
return route as RouteRecordStringComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PREFETCH_LIGHT_PAGE_KEYS = [
|
||||||
|
'../views/_core/agent-chat/index.vue',
|
||||||
|
'../views/_core/menu/index.vue',
|
||||||
|
'../views/_core/page-render/index.vue',
|
||||||
|
'../views/_core/role/index.vue',
|
||||||
|
'../views/_core/user/index.vue',
|
||||||
|
'../views/ai-platform/agent/index.vue',
|
||||||
|
'../views/ai-platform/model/index.vue',
|
||||||
|
'../views/ai-platform/workflow-runs/index.vue',
|
||||||
|
'../views/ai-platform/workflow/index.vue',
|
||||||
|
];
|
||||||
|
|
||||||
|
let lightPagesPrefetched = false;
|
||||||
|
|
||||||
|
function scheduleIdleTask(callback: () => void) {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const requestIdle = (window as any).requestIdleCallback;
|
||||||
|
if (typeof requestIdle === 'function') {
|
||||||
|
requestIdle(callback, { timeout: 3000 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.setTimeout(callback, 1200);
|
||||||
|
}
|
||||||
|
|
||||||
|
function prefetchLightPages(pageMap: ComponentRecordType) {
|
||||||
|
if (lightPagesPrefetched) return;
|
||||||
|
lightPagesPrefetched = true;
|
||||||
|
scheduleIdleTask(() => {
|
||||||
|
for (const key of PREFETCH_LIGHT_PAGE_KEYS) {
|
||||||
|
const loader = pageMap[key];
|
||||||
|
if (typeof loader === 'function') {
|
||||||
|
void loader().catch(() => undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const LIGHT_AI_PLATFORM_MENU_ROUTES = [
|
const LIGHT_AI_PLATFORM_MENU_ROUTES = [
|
||||||
createLightAiMenuRoute({
|
createLightAiMenuRoute({
|
||||||
component: '/_core/agent-chat/index',
|
component: '/_core/agent-chat/index',
|
||||||
@@ -220,7 +257,7 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
|||||||
};
|
};
|
||||||
const routePageMap = normalizePageMap(pageMap);
|
const routePageMap = normalizePageMap(pageMap);
|
||||||
|
|
||||||
return await generateAccessible(preferences.app.accessMode, {
|
const accessible = await generateAccessible(preferences.app.accessMode, {
|
||||||
...options,
|
...options,
|
||||||
fetchMenuListAsync: async () => {
|
fetchMenuListAsync: async () => {
|
||||||
const appContextStore = useAppContextStore();
|
const appContextStore = useAppContextStore();
|
||||||
@@ -235,6 +272,8 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
|||||||
layoutMap,
|
layoutMap,
|
||||||
pageMap,
|
pageMap,
|
||||||
});
|
});
|
||||||
|
prefetchLightPages(pageMap);
|
||||||
|
return accessible;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { generateAccess };
|
export { generateAccess };
|
||||||
|
|||||||
+143
-43
@@ -25,6 +25,8 @@ const emit = defineEmits<{
|
|||||||
select: [nodeId: string];
|
select: [nodeId: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
type TagType = 'danger' | 'info' | 'primary' | 'success' | 'warning';
|
||||||
|
|
||||||
function formatDuration(ms?: number) {
|
function formatDuration(ms?: number) {
|
||||||
if (!ms && ms !== 0) return '-';
|
if (!ms && ms !== 0) return '-';
|
||||||
if (ms < 1000) return `${ms}ms`;
|
if (ms < 1000) return `${ms}ms`;
|
||||||
@@ -50,15 +52,19 @@ function getCommunication(log: ExecutionLogEntry) {
|
|||||||
return log.metadata?.communication || log.event?.communication || {};
|
return log.metadata?.communication || log.event?.communication || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCollaboration(log: ExecutionLogEntry) {
|
||||||
|
return log.metadata?.collaboration || log.event?.collaboration || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEventCollaboration(event: WorkflowRunEvent) {
|
||||||
|
return event.collaboration || event.event?.collaboration || {};
|
||||||
|
}
|
||||||
|
|
||||||
function getMetaItems(log: ExecutionLogEntry) {
|
function getMetaItems(log: ExecutionLogEntry) {
|
||||||
const metadata = log.metadata || {};
|
const metadata = log.metadata || {};
|
||||||
const items: Array<{
|
const items: Array<{ label: string; type?: TagType }> = [];
|
||||||
label: string;
|
|
||||||
type?: 'danger' | 'info' | 'primary' | 'success' | 'warning';
|
|
||||||
}> = [];
|
|
||||||
const communication = getCommunication(log);
|
const communication = getCommunication(log);
|
||||||
const collaboration =
|
const collaboration = getCollaboration(log);
|
||||||
metadata.collaboration || log.event?.collaboration || {};
|
|
||||||
const branchLabel =
|
const branchLabel =
|
||||||
log.branch_label ||
|
log.branch_label ||
|
||||||
log.branch ||
|
log.branch ||
|
||||||
@@ -88,24 +94,14 @@ function getMetaItems(log: ExecutionLogEntry) {
|
|||||||
if (log.type || metadata.stream_event) {
|
if (log.type || metadata.stream_event) {
|
||||||
items.push({
|
items.push({
|
||||||
label: getEventTypeLabel(log.type || log.event?.type),
|
label: getEventTypeLabel(log.type || log.event?.type),
|
||||||
type: log.status === 'failed' ? 'danger' : 'info',
|
type: getLogTone(log),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (branchLabel) {
|
if (branchLabel) items.push({ label: `分支: ${branchLabel}`, type: 'primary' });
|
||||||
items.push({ label: `分支: ${branchLabel}`, type: 'primary' });
|
if (subflowName) items.push({ label: `子流程: ${subflowName}`, type: 'success' });
|
||||||
}
|
if (providerName) items.push({ label: `提供商: ${providerName}`, type: 'info' });
|
||||||
if (subflowName) {
|
if (modelName) items.push({ label: `模型: ${modelName}`, type: 'info' });
|
||||||
items.push({ label: `子流程: ${subflowName}`, type: 'success' });
|
if (log.tokens_used) items.push({ label: `Token ${log.tokens_used}`, type: 'info' });
|
||||||
}
|
|
||||||
if (providerName) {
|
|
||||||
items.push({ label: `提供商: ${providerName}`, type: 'info' });
|
|
||||||
}
|
|
||||||
if (modelName) {
|
|
||||||
items.push({ label: `模型: ${modelName}`, type: 'info' });
|
|
||||||
}
|
|
||||||
if (log.tokens_used) {
|
|
||||||
items.push({ label: `Token ${log.tokens_used}`, type: 'info' });
|
|
||||||
}
|
|
||||||
if (communication.channel) {
|
if (communication.channel) {
|
||||||
items.push({ label: getChannelLabel(communication.channel), type: 'info' });
|
items.push({ label: getChannelLabel(communication.channel), type: 'info' });
|
||||||
}
|
}
|
||||||
@@ -118,6 +114,7 @@ function getMetaItems(log: ExecutionLogEntry) {
|
|||||||
|
|
||||||
function getChannelLabel(channel: string) {
|
function getChannelLabel(channel: string) {
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
|
agent_chat: '智能体对话',
|
||||||
human_input: '人机协作',
|
human_input: '人机协作',
|
||||||
parallel_branch: '并行分支',
|
parallel_branch: '并行分支',
|
||||||
subflow: '子流程',
|
subflow: '子流程',
|
||||||
@@ -132,12 +129,18 @@ function getEventTypeLabel(type?: string) {
|
|||||||
complete: '完成',
|
complete: '完成',
|
||||||
error: '错误',
|
error: '错误',
|
||||||
llm_chunk: 'LLM 输出',
|
llm_chunk: 'LLM 输出',
|
||||||
|
loop_complete: '循环完成',
|
||||||
|
loop_iteration_complete: '循环迭代完成',
|
||||||
|
loop_iteration_error: '循环迭代失败',
|
||||||
|
loop_iteration_start: '循环迭代开始',
|
||||||
|
loop_start: '循环开始',
|
||||||
message: '消息',
|
message: '消息',
|
||||||
node_complete: '节点完成',
|
node_complete: '节点完成',
|
||||||
node_event: '节点事件',
|
node_event: '节点事件',
|
||||||
node_start: '节点开始',
|
node_start: '节点开始',
|
||||||
parallel_complete: '并行完成',
|
parallel_complete: '并行完成',
|
||||||
parallel_start: '并行开始',
|
parallel_start: '并行开始',
|
||||||
|
resume: '恢复执行',
|
||||||
start: '开始',
|
start: '开始',
|
||||||
waiting_input: '等待输入',
|
waiting_input: '等待输入',
|
||||||
};
|
};
|
||||||
@@ -150,13 +153,8 @@ function countItems(value: any) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEventSummary(event: WorkflowRunEvent) {
|
function getWaitingSummary(event: WorkflowRunEvent) {
|
||||||
const eventType = event.type || event.event?.type;
|
|
||||||
const waitingConfig = event.waiting_config || event.config;
|
const waitingConfig = event.waiting_config || event.config;
|
||||||
|
|
||||||
if (event.error_message) return event.error_message;
|
|
||||||
if (event.error) return event.error;
|
|
||||||
if (eventType === 'waiting_input') {
|
|
||||||
return stringifyBrief(
|
return stringifyBrief(
|
||||||
waitingConfig?.title ||
|
waitingConfig?.title ||
|
||||||
waitingConfig?.question ||
|
waitingConfig?.question ||
|
||||||
@@ -165,7 +163,14 @@ function getEventSummary(event: WorkflowRunEvent) {
|
|||||||
'等待用户输入',
|
'等待用户输入',
|
||||||
160,
|
160,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getEventSummary(event: WorkflowRunEvent) {
|
||||||
|
const eventType = event.type || event.event?.type;
|
||||||
|
|
||||||
|
if (event.error_message) return event.error_message;
|
||||||
|
if (event.error) return event.error;
|
||||||
|
if (eventType === 'waiting_input') return getWaitingSummary(event);
|
||||||
if (eventType === 'parallel_start') {
|
if (eventType === 'parallel_start') {
|
||||||
return `启动 ${countItems(event.branches || event.branch_labels)} 个并行分支`;
|
return `启动 ${countItems(event.branches || event.branch_labels)} 个并行分支`;
|
||||||
}
|
}
|
||||||
@@ -196,13 +201,56 @@ function getEventSummary(event: WorkflowRunEvent) {
|
|||||||
return stringifyBrief(event, 140);
|
return stringifyBrief(event, 140);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getEventMetaItems(event: WorkflowRunEvent) {
|
||||||
|
const collaboration = getEventCollaboration(event);
|
||||||
|
const items: Array<{ label: string; type?: TagType }> = [];
|
||||||
|
const branchLabel =
|
||||||
|
event.branch_label ||
|
||||||
|
event.branch_id ||
|
||||||
|
collaboration.branch_label ||
|
||||||
|
collaboration.branch_id;
|
||||||
|
const subflowName = event.subflow_name || collaboration.subflow_name;
|
||||||
|
const providerName = event.provider_name || collaboration.provider_name;
|
||||||
|
const modelName =
|
||||||
|
event.model ||
|
||||||
|
event.model_name ||
|
||||||
|
event.model_id ||
|
||||||
|
collaboration.model ||
|
||||||
|
collaboration.model_name ||
|
||||||
|
collaboration.model_id;
|
||||||
|
|
||||||
|
if (branchLabel) items.push({ label: `分支 ${branchLabel}`, type: 'primary' });
|
||||||
|
if (subflowName) items.push({ label: `子流程 ${subflowName}`, type: 'success' });
|
||||||
|
if (providerName) items.push({ label: providerName, type: 'info' });
|
||||||
|
if (modelName) items.push({ label: `模型 ${modelName}`, type: 'info' });
|
||||||
|
if (event.tokens_used || event.total_tokens) {
|
||||||
|
items.push({
|
||||||
|
label: `Token ${event.tokens_used || event.total_tokens}`,
|
||||||
|
type: 'info',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
function getLogEvents(log: ExecutionLogEntry): WorkflowRunEvent[] {
|
function getLogEvents(log: ExecutionLogEntry): WorkflowRunEvent[] {
|
||||||
const primaryEvent = log.event ? [log.event] : [];
|
const events = [
|
||||||
const directEvents = Array.isArray(log.events) ? log.events : [];
|
...(log.event ? [log.event] : []),
|
||||||
const metadataEvents = Array.isArray(log.metadata?.events)
|
...(Array.isArray(log.events) ? log.events : []),
|
||||||
? log.metadata.events
|
...(Array.isArray(log.metadata?.events) ? log.metadata.events : []),
|
||||||
: [];
|
].filter(Boolean);
|
||||||
return [...primaryEvent, ...directEvents, ...metadataEvents].filter(Boolean);
|
const seen = new Set<string>();
|
||||||
|
return events.filter((event) => {
|
||||||
|
const key = [
|
||||||
|
event.type || event.event?.type || '',
|
||||||
|
event.node_id || event.event?.node_id || '',
|
||||||
|
event.timestamp || event.event?.timestamp || '',
|
||||||
|
stringifyBrief(event.content || event.message || event.error_message, 80),
|
||||||
|
].join('|');
|
||||||
|
if (seen.has(key)) return false;
|
||||||
|
seen.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVisibleEvents(log: ExecutionLogEntry) {
|
function getVisibleEvents(log: ExecutionLogEntry) {
|
||||||
@@ -229,6 +277,33 @@ function getLogDetail(log: ExecutionLogEntry) {
|
|||||||
communication.message,
|
communication.message,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLogTitle(log: ExecutionLogEntry) {
|
||||||
|
if (log.node_label) return log.node_label;
|
||||||
|
if (log.node_id && log.node_id !== 'workflow') return log.node_id;
|
||||||
|
return getEventTypeLabel(log.type || log.event?.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLogTone(log: ExecutionLogEntry): TagType {
|
||||||
|
if (log.status === 'failed' || log.type === 'error') return 'danger';
|
||||||
|
if (log.status === 'waiting' || log.type === 'waiting_input') return 'warning';
|
||||||
|
if (log.status === 'completed' || log.type === 'complete') return 'success';
|
||||||
|
if (log.status === 'running') return 'primary';
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEventTone(event: WorkflowRunEvent): TagType {
|
||||||
|
const type = event.type || event.event?.type;
|
||||||
|
if (type === 'error' || event.error || event.error_message) return 'danger';
|
||||||
|
if (type === 'waiting_input') return 'warning';
|
||||||
|
if (type === 'complete' || type === 'node_complete' || type === 'parallel_complete') {
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
if (type === 'start' || type === 'node_start' || type === 'parallel_start') {
|
||||||
|
return 'primary';
|
||||||
|
}
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -240,8 +315,10 @@ function getLogDetail(log: ExecutionLogEntry) {
|
|||||||
<ElTimeline v-else class="px-2 py-3">
|
<ElTimeline v-else class="px-2 py-3">
|
||||||
<ElTimelineItem
|
<ElTimelineItem
|
||||||
v-for="(log, index) in logs"
|
v-for="(log, index) in logs"
|
||||||
:key="`${log.node_id}-${index}`"
|
:key="`${log.node_id}-${log.type || 'node'}-${index}`"
|
||||||
:timestamp="formatDuration(log.elapsed_time)"
|
:timestamp="formatDuration(log.elapsed_time)"
|
||||||
|
:type="getLogTone(log)"
|
||||||
|
:hollow="log.status === 'running' || log.status === 'waiting'"
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -255,15 +332,15 @@ function getLogDetail(log: ExecutionLogEntry) {
|
|||||||
@click="emit('select', log.node_id)"
|
@click="emit('select', log.node_id)"
|
||||||
>
|
>
|
||||||
<div class="mb-1 flex items-center justify-between gap-2">
|
<div class="mb-1 flex items-center justify-between gap-2">
|
||||||
<span class="text-foreground text-sm font-medium">
|
<span class="text-foreground min-w-0 truncate text-sm font-medium">
|
||||||
{{ log.node_label || log.node_id }}
|
{{ getLogTitle(log) }}
|
||||||
</span>
|
</span>
|
||||||
<RunStatusTag :status="log.status" />
|
<RunStatusTag :status="log.status" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="text-muted-foreground flex flex-wrap items-center gap-1 text-xs"
|
class="text-muted-foreground flex flex-wrap items-center gap-1 text-xs"
|
||||||
>
|
>
|
||||||
<span>{{ log.node_type }}</span>
|
<span v-if="log.node_type">{{ log.node_type }}</span>
|
||||||
<ElTag
|
<ElTag
|
||||||
v-for="item in getMetaItems(log)"
|
v-for="item in getMetaItems(log)"
|
||||||
:key="item.label"
|
:key="item.label"
|
||||||
@@ -291,15 +368,38 @@ function getLogDetail(log: ExecutionLogEntry) {
|
|||||||
<div
|
<div
|
||||||
v-for="(event, eventIndex) in getVisibleEvents(log)"
|
v-for="(event, eventIndex) in getVisibleEvents(log)"
|
||||||
:key="`${log.node_id}-event-${eventIndex}`"
|
:key="`${log.node_id}-event-${eventIndex}`"
|
||||||
class="text-muted-foreground flex gap-2 text-xs"
|
class="border-border/70 bg-background/60 mb-1 rounded border px-2 py-1 last:mb-0"
|
||||||
|
>
|
||||||
|
<div class="flex min-w-0 items-start gap-2">
|
||||||
|
<ElTag
|
||||||
|
class="shrink-0"
|
||||||
|
:type="getEventTone(event)"
|
||||||
|
size="small"
|
||||||
|
effect="plain"
|
||||||
>
|
>
|
||||||
<span class="shrink-0 font-medium">
|
|
||||||
{{ getEventTypeLabel(event.type || event.event?.type) }}
|
{{ getEventTypeLabel(event.type || event.event?.type) }}
|
||||||
</span>
|
</ElTag>
|
||||||
<span class="line-clamp-2 min-w-0 whitespace-pre-wrap">
|
<span
|
||||||
|
class="text-muted-foreground line-clamp-2 min-w-0 whitespace-pre-wrap text-xs"
|
||||||
|
>
|
||||||
{{ getEventSummary(event) }}
|
{{ getEventSummary(event) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="getEventMetaItems(event).length"
|
||||||
|
class="mt-1 flex flex-wrap gap-1 pl-[68px]"
|
||||||
|
>
|
||||||
|
<ElTag
|
||||||
|
v-for="item in getEventMetaItems(event)"
|
||||||
|
:key="item.label"
|
||||||
|
:type="item.type"
|
||||||
|
size="small"
|
||||||
|
effect="plain"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</ElTag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="getHiddenEventCount(log)"
|
v-if="getHiddenEventCount(log)"
|
||||||
class="text-muted-foreground mt-1 text-xs"
|
class="text-muted-foreground mt-1 text-xs"
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
import { markRaw } from 'vue';
|
import { markRaw } from 'vue';
|
||||||
|
|
||||||
import AddButtonEdge from '../editor/edges/AddButtonEdge.vue';
|
import AddButtonEdge from '../editor/edges/AddButtonEdge.vue';
|
||||||
|
import AppCreateNode from '../editor/nodes/AppCreateNode.vue';
|
||||||
|
import AppDesignNode from '../editor/nodes/AppDesignNode.vue';
|
||||||
|
import AppSettingsNode from '../editor/nodes/AppSettingsNode.vue';
|
||||||
|
import AppUpdateNode from '../editor/nodes/AppUpdateNode.vue';
|
||||||
import ChoiceNode from '../editor/nodes/ChoiceNode.vue';
|
import ChoiceNode from '../editor/nodes/ChoiceNode.vue';
|
||||||
import CodeNode from '../editor/nodes/CodeNode.vue';
|
import CodeNode from '../editor/nodes/CodeNode.vue';
|
||||||
import ConditionNode from '../editor/nodes/ConditionNode.vue';
|
import ConditionNode from '../editor/nodes/ConditionNode.vue';
|
||||||
import ConfirmNode from '../editor/nodes/ConfirmNode.vue';
|
import ConfirmNode from '../editor/nodes/ConfirmNode.vue';
|
||||||
|
import DashboardBasicInfoNode from '../editor/nodes/DashboardBasicInfoNode.vue';
|
||||||
|
import DashboardCreateNode from '../editor/nodes/DashboardCreateNode.vue';
|
||||||
|
import DashboardDesignNode from '../editor/nodes/DashboardDesignNode.vue';
|
||||||
|
import DashboardPublishNode from '../editor/nodes/DashboardPublishNode.vue';
|
||||||
import DbDeleteNode from '../editor/nodes/DbDeleteNode.vue';
|
import DbDeleteNode from '../editor/nodes/DbDeleteNode.vue';
|
||||||
import DbInsertNode from '../editor/nodes/DbInsertNode.vue';
|
import DbInsertNode from '../editor/nodes/DbInsertNode.vue';
|
||||||
import DbQueryNode from '../editor/nodes/DbQueryNode.vue';
|
import DbQueryNode from '../editor/nodes/DbQueryNode.vue';
|
||||||
import DbSqlNode from '../editor/nodes/DbSqlNode.vue';
|
import DbSqlNode from '../editor/nodes/DbSqlNode.vue';
|
||||||
import DbUpdateNode from '../editor/nodes/DbUpdateNode.vue';
|
import DbUpdateNode from '../editor/nodes/DbUpdateNode.vue';
|
||||||
import EndNode from '../editor/nodes/EndNode.vue';
|
import EndNode from '../editor/nodes/EndNode.vue';
|
||||||
|
import FormBasicInfoNode from '../editor/nodes/FormBasicInfoNode.vue';
|
||||||
|
import FormCreateNode from '../editor/nodes/FormCreateNode.vue';
|
||||||
|
import FormDatabaseCreateNode from '../editor/nodes/FormDatabaseCreateNode.vue';
|
||||||
|
import FormDatabaseDesignNode from '../editor/nodes/FormDatabaseDesignNode.vue';
|
||||||
|
import FormDataNode from '../editor/nodes/FormDataNode.vue';
|
||||||
|
import FormListDesignNode from '../editor/nodes/FormListDesignNode.vue';
|
||||||
|
import FormPublishNode from '../editor/nodes/FormPublishNode.vue';
|
||||||
|
import FormUIDesignNode from '../editor/nodes/FormUIDesignNode.vue';
|
||||||
import HttpNode from '../editor/nodes/HttpNode.vue';
|
import HttpNode from '../editor/nodes/HttpNode.vue';
|
||||||
import IntentNode from '../editor/nodes/IntentNode.vue';
|
import IntentNode from '../editor/nodes/IntentNode.vue';
|
||||||
import KnowledgeRetrievalNode from '../editor/nodes/KnowledgeRetrievalNode.vue';
|
import KnowledgeRetrievalNode from '../editor/nodes/KnowledgeRetrievalNode.vue';
|
||||||
@@ -51,8 +67,29 @@ export const workflowNodeTypes = {
|
|||||||
snowflake_cortex_llm: markRaw(SnowflakeCortexLLMNode),
|
snowflake_cortex_llm: markRaw(SnowflakeCortexLLMNode),
|
||||||
snowflake_cortex_analyst: markRaw(SnowflakeCortexAnalystNode),
|
snowflake_cortex_analyst: markRaw(SnowflakeCortexAnalystNode),
|
||||||
loop: markRaw(LoopNode),
|
loop: markRaw(LoopNode),
|
||||||
|
form_basic_info: markRaw(FormBasicInfoNode),
|
||||||
|
form_database_design: markRaw(FormDatabaseDesignNode),
|
||||||
|
form_database_create: markRaw(FormDatabaseCreateNode),
|
||||||
|
form_ui_design: markRaw(FormUIDesignNode),
|
||||||
|
form_list_design: markRaw(FormListDesignNode),
|
||||||
|
form_create: markRaw(FormCreateNode),
|
||||||
|
form_publish: markRaw(FormPublishNode),
|
||||||
|
app_create: markRaw(AppCreateNode),
|
||||||
|
app_design: markRaw(AppDesignNode),
|
||||||
|
app_settings: markRaw(AppSettingsNode),
|
||||||
|
app_update: markRaw(AppUpdateNode),
|
||||||
|
dashboard_basic_info: markRaw(DashboardBasicInfoNode),
|
||||||
|
dashboard_design: markRaw(DashboardDesignNode),
|
||||||
|
dashboard_create: markRaw(DashboardCreateNode),
|
||||||
|
dashboard_publish: markRaw(DashboardPublishNode),
|
||||||
system_summary: markRaw(SystemSummaryNode),
|
system_summary: markRaw(SystemSummaryNode),
|
||||||
subflow: markRaw(SubflowNode),
|
subflow: markRaw(SubflowNode),
|
||||||
|
form_data_create: markRaw(FormDataNode),
|
||||||
|
form_data_read: markRaw(FormDataNode),
|
||||||
|
form_data_update: markRaw(FormDataNode),
|
||||||
|
form_data_delete: markRaw(FormDataNode),
|
||||||
|
form_data_list: markRaw(FormDataNode),
|
||||||
|
form_schema_to_llm: markRaw(FormDataNode),
|
||||||
text_to_sql: markRaw(TextToSqlNode),
|
text_to_sql: markRaw(TextToSqlNode),
|
||||||
knowledge_retrieval: markRaw(KnowledgeRetrievalNode),
|
knowledge_retrieval: markRaw(KnowledgeRetrievalNode),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user