feat: 工作流适配数据中枢查询节点

- 新增查询数据与写入数据节点并移除旧数据中心节点入口

- 将查询数据节点切换为连接服务加 SQL 的执行模型

- 同步更新工作流校验、提示词上下文与设计器交互
This commit is contained in:
2026-04-02 18:56:34 +08:00
parent 798effbd5b
commit 1ecc28e498
40 changed files with 1973 additions and 692 deletions

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {type NodeProps, useNodesData, useSvelteFlow} from '@xyflow/svelte';
import {type NodeProps, useNodesData, useStore, useSvelteFlow} from '@xyflow/svelte';
import {Button, FloatingTrigger, Heading, Select} from '../base';
import {MenuButton} from '../base/index.js';
import RefParameterList from '../core/RefParameterList.svelte';
@@ -20,8 +20,42 @@
const currentNodeId = getCurrentNodeId();
let currentNode = useNodesData(currentNodeId);
const { addParameter } = useAddParameter();
const { nodes } = $derived(useStore());
const editorParameters = $derived.by(() => {
return (currentNode?.current?.data?.parameters as Array<any>) || data.parameters || [];
const parameters = [
...(((currentNode?.current?.data?.parameters as Array<any>) || data.parameters || []) as Array<any>)
];
if (queryContextNodeIds.length > 0) {
parameters.push({
id: 'queryDataContext',
name: 'queryDataContext',
dataType: 'String',
description: '数据查询规则与连接表摘要',
required: false,
nameDisabled: true,
dataTypeDisabled: true,
deleteDisabled: true
});
}
return parameters;
});
const queryContextOptions = $derived.by(() => {
return (nodes || [])
.filter((node: any) => node?.id !== currentNodeId && node?.type === 'search-dataset-node')
.map((node: any) => ({
label: node?.data?.title || '查询数据',
value: node.id,
displayLabel: node?.data?.title || '查询数据',
description: node?.data?.sourceName ? '连接服务:' + node.data.sourceName : '未选择连接服务',
}));
});
const queryContextNodeIds = $derived.by(() => {
const ids = (currentNode?.current?.data?.queryContextNodeIds as Array<any>) || data.queryContextNodeIds || [];
return Array.isArray(ids)
? ids
.map((item: any) => (item == null ? '' : String(item)))
.filter((item: string) => item.trim().length > 0)
: [];
});
const options = getOptions();
@@ -29,33 +63,29 @@
let llmArray = $state<SelectItem[]>([]);
onMount(async () => {
const newLLMs = await options.provider?.llm?.();
const isFlat = newLLMs?.every(item => !item.children);
if (isFlat && newLLMs && newLLMs.length > 0) {
const grouped = new Map<string, SelectItem[]>();
for (const llm of newLLMs) {
// If it still has a slash, parse it; otherwise, check if there's a custom logic we can infer brand.
// In WorkflowDesign we pass `item.modelProvider?.providerName` via some other way, but here it's flat.
// Actually, the label is just the title now (e.g. 'deepseek-chat').
// Wait, LLMNode doesn't know the brand unless it's in the string or we modify WorkflowDesign to pass `brand`.
// Let's modify WorkflowDesign to pass `brand` instead.
let brand = (llm as any).brand || '其他';
let modelName = llm.label;
const modelName = typeof llm.label === 'string'
? llm.label
: ((llm.displayLabel as string | undefined) || '模型');
if (!grouped.has(brand)) {
grouped.set(brand, []);
}
grouped.get(brand)!.push({
...llm,
label: modelName,
displayLabel: modelName // 外部选中时也只显示模型名称
displayLabel: modelName
});
}
const treeArray: SelectItem[] = [];
for (const [brand, models] of grouped) {
// Try to get a representative icon for the brand from its children
let groupIcon = undefined;
if (models.length > 0) {
const modelWithIcon = models.find(m => m.icon);
@@ -72,9 +102,9 @@
children: models
});
}
llmArray.push(...treeArray);
llmArray = treeArray;
} else {
llmArray.push(...(newLLMs || []));
llmArray = [...(newLLMs || [])];
}
});
@@ -121,6 +151,26 @@
}
});
$effect(() => {
const validIds = new Set(queryContextOptions.map((item) => String(item.value)));
const normalized = queryContextNodeIds.filter((item) => validIds.has(item));
if (normalized.length !== queryContextNodeIds.length) {
updateNodeData(currentNodeId, {
queryContextNodeIds: normalized
});
}
});
const toggleQueryContextNode = (nodeId: string) => {
const currentIds = [...queryContextNodeIds];
const exists = currentIds.includes(nodeId);
updateNodeData(currentNodeId, {
queryContextNodeIds: exists
? currentIds.filter((item) => item !== nodeId)
: [...currentIds, nodeId]
});
};
</script>
@@ -159,6 +209,20 @@
<RefParameterList dataKeyName="images" noneParameterText="无图片参数" />
<Heading level={3} mt="10px">查询数据信息</Heading>
<div class="setting-item">
<Select
items={queryContextOptions}
multiple={true}
style="width: 100%"
placeholder="请选择查询数据节点"
onSelect={(item)=>{
toggleQueryContextNode(String(item.value));
}}
value={queryContextNodeIds}
/>
</div>
<Heading level={3} mt="10px">模型设置</Heading>
<div class="setting-title">模型</div>
<div class="setting-item">