Trim legacy workflow node runtime metadata

This commit is contained in:
2026-06-11 07:29:37 +08:00
parent a3229f2f71
commit 2b80f7c3df
2 changed files with 133 additions and 226 deletions
@@ -474,25 +474,6 @@ const availableNodes = computed(() => {
];
break;
}
case 'text_to_sql': {
const textToSqlOutputVar =
node.data.output_variable || 'text_to_sql_result';
variables = [
{
key: `${textToSqlOutputVar}_sql`,
label: $t(
'ai-platform.workflow.editor.variableSelector.generatedSqlStatement',
),
},
{
key: `${textToSqlOutputVar}_thought`,
label: $t(
'ai-platform.workflow.editor.variableSelector.sqlThought',
),
},
];
break;
}
default: {
// 其他未知节点类型,尝试从 output_variable 获取
if (node.data.output_variable) {
@@ -10,8 +10,13 @@ import { ElTooltip } from 'element-plus';
import NodeResultCard from '../components/NodeResultCard.vue';
type NodeColorConfig = {
headerBg: string;
iconBg: string;
iconColor: string;
};
const props = defineProps<{
// 新增:完整的执行结果(用于节点下方展示)
executionResult?: NodeExecutionResult;
executionStatus?: {
elapsed_time?: number;
@@ -29,12 +34,120 @@ const props = defineProps<{
}>();
const addButtonRef = ref<HTMLElement | null>(null);
const onNodeAddClick = inject<
(nodeId: string, position: { x: number; y: number }) => void
>('onNodeAddClick');
// 从父组件注入的添加节点函数
const onNodeAddClick =
inject<(nodeId: string, position: { x: number; y: number }) => void>(
'onNodeAddClick',
);
const nodeColorConfigs: Record<string, NodeColorConfig> = {
start: {
headerBg:
'bg-gradient-to-r from-emerald-50 to-green-50 dark:from-emerald-950 dark:to-green-950',
iconBg: 'bg-emerald-200 dark:bg-emerald-800',
iconColor: 'text-emerald-600 dark:text-emerald-400',
},
end: {
headerBg:
'bg-gradient-to-r from-rose-50 to-red-50 dark:from-rose-950 dark:to-red-950',
iconBg: 'bg-rose-200 dark:bg-rose-800',
iconColor: 'text-rose-600 dark:text-rose-400',
},
condition: {
headerBg:
'bg-gradient-to-r from-amber-50 to-yellow-50 dark:from-amber-950 dark:to-yellow-950',
iconBg: 'bg-amber-200 dark:bg-amber-800',
iconColor: 'text-amber-600 dark:text-amber-400',
},
parallel: {
headerBg:
'bg-gradient-to-r from-cyan-50 to-sky-50 dark:from-cyan-950 dark:to-sky-950',
iconBg: 'bg-cyan-200 dark:bg-cyan-800',
iconColor: 'text-cyan-600 dark:text-cyan-400',
},
merge: {
headerBg:
'bg-gradient-to-r from-slate-50 to-gray-100 dark:from-slate-900 dark:to-gray-900',
iconBg: 'bg-slate-300 dark:bg-slate-700',
iconColor: 'text-slate-600 dark:text-slate-400',
},
llm: {
headerBg:
'bg-gradient-to-r from-violet-50 to-purple-50 dark:from-violet-950 dark:to-purple-950',
iconBg: 'bg-violet-200 dark:bg-violet-800',
iconColor: 'text-violet-600 dark:text-violet-400',
},
intent: {
headerBg:
'bg-gradient-to-r from-fuchsia-50 to-pink-50 dark:from-fuchsia-950 dark:to-pink-950',
iconBg: 'bg-fuchsia-200 dark:bg-fuchsia-800',
iconColor: 'text-fuchsia-600 dark:text-fuchsia-400',
},
code: {
headerBg:
'bg-gradient-to-r from-orange-50 to-amber-50 dark:from-orange-950 dark:to-amber-950',
iconBg: 'bg-orange-200 dark:bg-orange-800',
iconColor: 'text-orange-600 dark:text-orange-400',
},
template: {
headerBg:
'bg-gradient-to-r from-teal-50 to-emerald-50 dark:from-teal-950 dark:to-emerald-950',
iconBg: 'bg-teal-200 dark:bg-teal-800',
iconColor: 'text-teal-600 dark:text-teal-400',
},
http: {
headerBg:
'bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-950 dark:to-indigo-950',
iconBg: 'bg-blue-200 dark:bg-blue-800',
iconColor: 'text-blue-600 dark:text-blue-400',
},
question: {
headerBg:
'bg-gradient-to-r from-sky-50 to-blue-50 dark:from-sky-950 dark:to-blue-950',
iconBg: 'bg-sky-200 dark:bg-sky-800',
iconColor: 'text-sky-600 dark:text-sky-400',
},
choice: {
headerBg:
'bg-gradient-to-r from-lime-50 to-green-50 dark:from-lime-950 dark:to-green-950',
iconBg: 'bg-lime-200 dark:bg-lime-800',
iconColor: 'text-lime-600 dark:text-lime-400',
},
message: {
headerBg:
'bg-gradient-to-r from-pink-50 to-rose-50 dark:from-pink-950 dark:to-rose-950',
iconBg: 'bg-pink-200 dark:bg-pink-800',
iconColor: 'text-pink-600 dark:text-pink-400',
},
confirm: {
headerBg:
'bg-gradient-to-r from-orange-50 to-red-50 dark:from-orange-950 dark:to-red-950',
iconBg: 'bg-orange-200 dark:bg-orange-800',
iconColor: 'text-orange-600 dark:text-orange-400',
},
loop: {
headerBg:
'bg-gradient-to-r from-blue-50 to-cyan-50 dark:from-blue-950 dark:to-cyan-950',
iconBg: 'bg-blue-200 dark:bg-blue-800',
iconColor: 'text-blue-600 dark:text-blue-400',
},
subflow: {
headerBg:
'bg-gradient-to-r from-indigo-50 to-purple-50 dark:from-indigo-950 dark:to-purple-950',
iconBg: 'bg-indigo-200 dark:bg-indigo-800',
iconColor: 'text-indigo-600 dark:text-indigo-400',
},
knowledge_retrieval: {
headerBg:
'bg-gradient-to-r from-teal-50 to-cyan-50 dark:from-teal-950 dark:to-cyan-950',
iconBg: 'bg-teal-200 dark:bg-teal-800',
iconColor: 'text-teal-600 dark:text-teal-400',
},
};
const defaultNodeColorConfig: NodeColorConfig = {
headerBg: 'bg-gradient-to-r from-card to-muted',
iconBg: 'bg-card border border-border',
iconColor: 'text-primary',
};
const handleClick = () => {
const rect = addButtonRef.value?.getBoundingClientRect();
@@ -43,190 +156,13 @@ const handleClick = () => {
}
};
// 节点类型配色 - 支持 dark 模式,使用 Tailwind dark: 前缀
const nodeColorConfig = computed(() => {
const configs: Record<
string,
{ headerBg: string; iconBg: string; iconColor: string }
> = {
// 流程控制
start: {
headerBg:
'bg-gradient-to-r from-emerald-50 to-green-50 dark:from-emerald-950 dark:to-green-950',
iconBg: 'bg-emerald-200 dark:bg-emerald-800',
iconColor: 'text-emerald-600 dark:text-emerald-400',
},
end: {
headerBg:
'bg-gradient-to-r from-rose-50 to-red-50 dark:from-rose-950 dark:to-red-950',
iconBg: 'bg-rose-200 dark:bg-rose-800',
iconColor: 'text-rose-600 dark:text-rose-400',
},
condition: {
headerBg:
'bg-gradient-to-r from-amber-50 to-yellow-50 dark:from-amber-950 dark:to-yellow-950',
iconBg: 'bg-amber-200 dark:bg-amber-800',
iconColor: 'text-amber-600 dark:text-amber-400',
},
parallel: {
headerBg:
'bg-gradient-to-r from-cyan-50 to-sky-50 dark:from-cyan-950 dark:to-sky-950',
iconBg: 'bg-cyan-200 dark:bg-cyan-800',
iconColor: 'text-cyan-600 dark:text-cyan-400',
},
merge: {
headerBg:
'bg-gradient-to-r from-slate-50 to-gray-100 dark:from-slate-900 dark:to-gray-900',
iconBg: 'bg-slate-300 dark:bg-slate-700',
iconColor: 'text-slate-600 dark:text-slate-400',
},
// AI 相关
llm: {
headerBg:
'bg-gradient-to-r from-violet-50 to-purple-50 dark:from-violet-950 dark:to-purple-950',
iconBg: 'bg-violet-200 dark:bg-violet-800',
iconColor: 'text-violet-600 dark:text-violet-400',
},
intent: {
headerBg:
'bg-gradient-to-r from-fuchsia-50 to-pink-50 dark:from-fuchsia-950 dark:to-pink-950',
iconBg: 'bg-fuchsia-200 dark:bg-fuchsia-800',
iconColor: 'text-fuchsia-600 dark:text-fuchsia-400',
},
// 代码和模板
code: {
headerBg:
'bg-gradient-to-r from-orange-50 to-amber-50 dark:from-orange-950 dark:to-amber-950',
iconBg: 'bg-orange-200 dark:bg-orange-800',
iconColor: 'text-orange-600 dark:text-orange-400',
},
template: {
headerBg:
'bg-gradient-to-r from-teal-50 to-emerald-50 dark:from-teal-950 dark:to-emerald-950',
iconBg: 'bg-teal-200 dark:bg-teal-800',
iconColor: 'text-teal-600 dark:text-teal-400',
},
// HTTP
http: {
headerBg:
'bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-950 dark:to-indigo-950',
iconBg: 'bg-blue-200 dark:bg-blue-800',
iconColor: 'text-blue-600 dark:text-blue-400',
},
// 数据库
db_query: {
headerBg:
'bg-gradient-to-r from-indigo-50 to-blue-50 dark:from-indigo-950 dark:to-blue-950',
iconBg: 'bg-indigo-200 dark:bg-indigo-800',
iconColor: 'text-indigo-600 dark:text-indigo-400',
},
db_insert: {
headerBg:
'bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-950 dark:to-emerald-950',
iconBg: 'bg-green-200 dark:bg-green-800',
iconColor: 'text-green-600 dark:text-green-400',
},
db_update: {
headerBg:
'bg-gradient-to-r from-yellow-50 to-amber-50 dark:from-yellow-950 dark:to-amber-950',
iconBg: 'bg-yellow-200 dark:bg-yellow-800',
iconColor: 'text-yellow-600 dark:text-yellow-400',
},
db_delete: {
headerBg:
'bg-gradient-to-r from-red-50 to-rose-50 dark:from-red-950 dark:to-rose-950',
iconBg: 'bg-red-200 dark:bg-red-800',
iconColor: 'text-red-600 dark:text-red-400',
},
db_sql: {
headerBg:
'bg-gradient-to-r from-purple-50 to-violet-50 dark:from-purple-950 dark:to-violet-950',
iconBg: 'bg-purple-200 dark:bg-purple-800',
iconColor: 'text-purple-600 dark:text-purple-400',
},
// Text-to-SQL
text_to_sql: {
headerBg:
'bg-gradient-to-r from-cyan-50 to-teal-50 dark:from-cyan-950 dark:to-teal-950',
iconBg: 'bg-cyan-200 dark:bg-cyan-800',
iconColor: 'text-cyan-600 dark:text-cyan-400',
},
// 对话流
question: {
headerBg:
'bg-gradient-to-r from-sky-50 to-blue-50 dark:from-sky-950 dark:to-blue-950',
iconBg: 'bg-sky-200 dark:bg-sky-800',
iconColor: 'text-sky-600 dark:text-sky-400',
},
choice: {
headerBg:
'bg-gradient-to-r from-lime-50 to-green-50 dark:from-lime-950 dark:to-green-950',
iconBg: 'bg-lime-200 dark:bg-lime-800',
iconColor: 'text-lime-600 dark:text-lime-400',
},
message: {
headerBg:
'bg-gradient-to-r from-pink-50 to-rose-50 dark:from-pink-950 dark:to-rose-950',
iconBg: 'bg-pink-200 dark:bg-pink-800',
iconColor: 'text-pink-600 dark:text-pink-400',
},
confirm: {
headerBg:
'bg-gradient-to-r from-orange-50 to-red-50 dark:from-orange-950 dark:to-red-950',
iconBg: 'bg-orange-200 dark:bg-orange-800',
iconColor: 'text-orange-600 dark:text-orange-400',
},
// Snowflake
snowflake_cortex_llm: {
headerBg:
'bg-gradient-to-r from-cyan-50 to-blue-50 dark:from-cyan-950 dark:to-blue-950',
iconBg: 'bg-cyan-200 dark:bg-cyan-800',
iconColor: 'text-cyan-600 dark:text-cyan-400',
},
snowflake_cortex_analyst: {
headerBg:
'bg-gradient-to-r from-blue-50 to-cyan-50 dark:from-blue-950 dark:to-cyan-950',
iconBg: 'bg-blue-200 dark:bg-blue-800',
iconColor: 'text-blue-600 dark:text-blue-400',
},
// 子流程
subflow: {
headerBg:
'bg-gradient-to-r from-indigo-50 to-purple-50 dark:from-indigo-950 dark:to-purple-950',
iconBg: 'bg-indigo-200 dark:bg-indigo-800',
iconColor: 'text-indigo-600 dark:text-indigo-400',
},
// 知识库
knowledge_retrieval: {
headerBg:
'bg-gradient-to-r from-teal-50 to-cyan-50 dark:from-teal-950 dark:to-cyan-950',
iconBg: 'bg-teal-200 dark:bg-teal-800',
iconColor: 'text-teal-600 dark:text-teal-400',
},
};
return (
configs[props.nodeType || ''] || {
headerBg: 'bg-gradient-to-r from-card to-muted',
iconBg: 'bg-card border border-border',
iconColor: 'text-primary',
}
);
});
const nodeColorConfig = computed(
() => nodeColorConfigs[props.nodeType || ''] || defaultNodeColorConfig,
);
</script>
<template>
<div class="flex flex-col">
<!-- 节点主体 -->
<div
class="bg-card group relative w-[280px] max-w-[280px] rounded-xl border shadow-sm transition-all duration-200 hover:shadow-md"
:class="[
@@ -243,7 +179,6 @@ const nodeColorConfig = computed(() => {
!selected && !executionStatus?.status ? 'border-border' : '',
]"
>
<!-- Status Badge (Absolute Top Right) -->
<div
v-if="executionStatus"
class="absolute -top-3 right-4 z-20 flex items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] shadow-sm"
@@ -280,10 +215,11 @@ const nodeColorConfig = computed(() => {
/>
<span v-if="executionStatus.status === 'running'">Running...</span>
<span v-else-if="executionStatus.elapsed_time !== undefined">{{ executionStatus.elapsed_time }}ms</span>
<span v-else-if="executionStatus.elapsed_time !== undefined">
{{ executionStatus.elapsed_time }}ms
</span>
</div>
<!-- Header -->
<div
class="border-border/50 flex items-center gap-3 rounded-t-xl border-b px-4 py-3"
:class="nodeColorConfig.headerBg"
@@ -302,7 +238,6 @@ const nodeColorConfig = computed(() => {
<slot name="desc"></slot>
</div>
</div>
<!-- Token Usage -->
<div
v-if="executionStatus?.tokens_used"
class="text-muted-foreground bg-muted border-border flex items-center rounded border px-1.5 py-0.5 text-[10px]"
@@ -314,40 +249,34 @@ const nodeColorConfig = computed(() => {
</div>
</div>
<!-- Body -->
<div class="text-muted-foreground min-w-0 space-y-2 overflow-hidden p-4 text-xs">
<slot></slot>
</div>
<!-- Input Handles (Left) -->
<div v-if="inputs && inputs.length > 0">
<Handle
v-for="(input, index) in inputs"
:key="input.id"
type="target"
:position="Position.Left"
:id="input.id"
:key="input.id"
class="!bg-primary !z-20 !h-3 !w-3 !border-2 !border-white transition-transform hover:scale-125"
:position="Position.Left"
:style="{ top: `${((index + 1) * 100) / (inputs.length + 1)}%` }"
type="target"
/>
</div>
<!-- Default single input if not specified but needed logic can be handled in parent -->
<!-- Output Handles (Right) -->
<div v-if="outputs && outputs.length > 0">
<Handle
v-for="(output, index) in outputs"
:key="output.id"
type="source"
:position="Position.Right"
:id="output.id"
:key="output.id"
class="!bg-primary !z-20 !h-3 !w-3 !border-2 !border-white transition-transform hover:scale-125"
:position="Position.Right"
:style="{ top: `${((index + 1) * 100) / (outputs.length + 1)}%` }"
type="source"
/>
</div>
<!-- 悬停时显示的添加按钮 - 放在节点更右侧不覆盖 Handle -->
<!-- 分支类型节点condition/intent/parallel/choice不显示通用添加按钮需要从具体分支 Handle 拖线 -->
<div
v-if="
nodeType !== 'end' &&
@@ -357,25 +286,23 @@ const nodeColorConfig = computed(() => {
"
ref="addButtonRef"
class="bg-primary hover:bg-primary/90 absolute -right-8 top-1/2 flex size-6 -translate-y-1/2 cursor-pointer items-center justify-center rounded-full text-white opacity-0 shadow-md transition-opacity hover:scale-110 group-hover:opacity-100"
title="{{ $t('ai-platform.workflow.nodes.common.addNextNode') }}"
:title="$t('ai-platform.workflow.nodes.common.addNextNode')"
@click.stop="handleClick"
>
<Plus class="pointer-events-none size-3.5" />
</div>
</div>
<!-- 节点下方的执行结果卡片(独立显示,不在节点内部) -->
<NodeResultCard
v-if="executionResult"
:result="executionResult"
:node-type="nodeType"
:result="executionResult"
class="mt-3"
/>
</div>
</template>
<style scoped>
/* 自定义 Handle 样式 */
:deep(.vue-flow__handle) {
z-index: 20;
width: 10px;
@@ -383,7 +310,6 @@ const nodeColorConfig = computed(() => {
background: hsl(var(--primary));
}
/* 运行中边框呼吸动画 */
@keyframes pulse-border {
0%,
100% {