针对工作流管理进行组件抽离和与内容创作进行安全隔离

This commit is contained in:
2026-08-04 14:28:57 +08:00
parent e1d369b205
commit 448620ed8a
8 changed files with 608 additions and 577 deletions
+25 -261
View File
@@ -18,19 +18,12 @@
<!-- 左侧配置面板 -->
<NodeConfigPanel
:selected-node="selectedNode"
:available-params="availableParams"
:node-config="currentNodeConfig"
:all-nodes="nodes"
:all-edges="edges"
@update:selected-node="updateSelectedNode"
@add-param="addParam"
@open-model-selector="showModelSelector = true"
@remove-model="handleRemoveModel"
@open-skill-selector="showSkillSelector = true"
@remove-skill="handleRemoveSkill"
@remove-field="handleRemoveField"
@toggle-output="handleToggleOutput"
@add-param-by-value="handleAddParamByValue"
@update:patch-layout="handleTogglePatchLayout"
/>
@@ -81,7 +74,7 @@
/>
<!-- 模型选择器 -->
<ModelSelector v-model="showModelSelector" :default-model="selectedModelData" :model-type="currentNodeModelType" @confirm="handleModelConfirm" />
<ModelSelector v-model="showModelSelector" :default-model="selectedModelData" @confirm="handleModelConfirm" />
<!-- 技能选择器 -->
<SkillSelector v-model="showSkillSelector" :default-skill="selectedSkillData" @confirm="handleSkillConfirm" />
@@ -97,37 +90,30 @@ import { MiniMap } from '@vue-flow/minimap';
import { ElMessage, ElMessageBox } from 'element-plus';
import type { Node, Edge, Connection } from '@vue-flow/core';
import {
getNodeLibraryList,
getWorkflowList,
getWorkflowDetail,
saveWorkflow,
updateWorkflow,
deleteWorkflow,
type NodeLibraryGroup,
type WorkflowItem,
} from '/@/api/settings/creation';
import { getNodeLibraryList, type NodeLibraryGroup } from '/@/api/settings/workflow';
import NodeConfigPanel from './component/NodeConfigPanel.vue';
import NodeLibraryPanel from './component/NodeLibraryPanel.vue';
import WorkflowListPanel from './component/WorkflowListPanel.vue';
import SaveWorkflowDialog from './component/SaveWorkflowDialog.vue';
import ModelSelector from '/@/components/model/ModelSelector.vue';
import SkillSelector from '/@/components/skill/SkillSelector.vue';
import ModelSelector from './component/ModelSelector.vue';
import SkillSelector from './component/SkillSelector.vue';
interface NodeData {
label?: string;
nodeCode?: string;
inputSource?: Array<{ nodeId: string; field: string[]; quoteOutput?: boolean }> | null;
formConfig?: any[];
modelConfig?: any;
skillName?: string;
patchLayout?: boolean;
}
interface ParamRef {
id: string;
label: string;
}
const { addNodes, addEdges, findNode, removeNodes, getNodes, updateNode } = useVueFlow();
// 常量定义
@@ -144,14 +130,26 @@ const filteredNodeLibraryGroups = computed(() => {
// 节点配置映射:nodeCode -> 节点配置
const nodeConfigMap = computed(() => {
const map = new Map<string, { formConfig: any[]; modelConfig: any[]; skillOption: boolean; patchLayout: boolean }>();
const map = new Map<
string,
{
formConfig: any[];
modelConfigOption: boolean;
formConfigOption: boolean;
skillOption: boolean;
promptOption: boolean;
isSaveFileOption: boolean;
}
>();
nodeLibraryGroups.value.forEach((group) => {
group.items.forEach((item) => {
map.set(item.nodeCode, {
formConfig: item.formConfig || [],
modelConfig: item.modelConfig || [],
group.nodes.forEach((item) => {
map.set(item.key, {
formConfig: item.presetOption || [],
modelConfigOption: item.modelConfigOption || false,
formConfigOption: item.formConfigOption || false,
skillOption: item.skillOption || false,
patchLayout: item.patchLayout || false,
promptOption: item.promptOption || false,
isSaveFileOption: item.isSaveFileOption || false,
});
});
});
@@ -186,14 +184,6 @@ let nodeId = 0;
// 模型选择器相关状态
const showModelSelector = ref(false);
const selectedModelData = ref<any>(null);
const currentNodeModelType = computed(() => {
if (!selectedNode.value || !currentNodeConfig.value) return 0;
const modelConfig = currentNodeConfig.value.modelConfig;
if (modelConfig && modelConfig.length > 0) {
return modelConfig[0].modelType || 0;
}
return 0;
});
// 技能选择器相关状态
const showSkillSelector = ref(false);
@@ -266,74 +256,6 @@ const updateSelectedNode = (updatedNode: Node<NodeData, any, string>) => {
}
};
const availableParams = computed(() => {
if (!selectedNode.value) return [];
const parents: ParamRef[] = [];
const findParents = (nodeId: string, visited = new Set<string>()) => {
if (visited.has(nodeId)) return;
visited.add(nodeId);
edges.value
.filter((e) => e.target === nodeId)
.forEach((edge) => {
const parent = findNode(edge.source);
if (parent?.data && parent.data.nodeCode !== '__start__' && parent.data.nodeCode !== 'judge') {
parents.push({ id: parent.id, label: `${parent.data.label}.output` });
}
findParents(edge.source, visited);
});
};
findParents(selectedNode.value.id);
return parents;
});
const addParam = (param: ParamRef) => {
if (!selectedNode.value?.data) return;
const updatedNode: Node<NodeData> = {
...selectedNode.value,
data: {
...selectedNode.value.data,
inputSource: selectedNode.value.data.inputSource || [],
},
};
// 查找是否已存在该节点的引用
const existingIndex = updatedNode.data!.inputSource!.findIndex((item) => item.nodeId === param.id);
if (existingIndex >= 0) {
// 已存在,添加 output 到 field 数组
const existing = updatedNode.data!.inputSource![existingIndex];
if (!existing.field.includes('output')) {
existing.field.push('output');
selectedNode.value = updatedNode;
// 同步更新到 VueFlow 内部状态
updateNode(updatedNode.id, updatedNode);
const index = nodes.value.findIndex((n) => n.id === updatedNode.id);
if (index >= 0) {
nodes.value[index] = updatedNode;
}
ElMessage.success('已添加参数引用');
} else {
ElMessage.info('该参数已被引用');
}
} else {
// 不存在,创建新的引用
updatedNode.data!.inputSource!.push({
nodeId: param.id,
field: ['output'],
quoteOutput: false,
});
selectedNode.value = updatedNode;
// 同步更新到 VueFlow 内部状态
updateNode(updatedNode.id, updatedNode);
const index = nodes.value.findIndex((n) => n.id === updatedNode.id);
if (index >= 0) {
nodes.value[index] = updatedNode;
}
ElMessage.success('已添加参数引用');
}
};
// 模型选择确认
const handleModelConfirm = (model: any) => {
if (!selectedNode.value?.data) return;
@@ -344,6 +266,7 @@ const handleModelConfirm = (model: any) => {
...selectedNode.value.data,
modelConfig: {
modelName: model.modelName,
modelType: model.modelType,
modelApiKey: '',
modelForm: model.modelForm || [],
modelResponse: model.responseBody || {},
@@ -460,146 +383,6 @@ const handleTogglePatchLayout = (value: boolean) => {
}
};
// 删除上级参数字段
const handleRemoveField = (nodeId: string, fieldName: string) => {
if (!selectedNode.value?.data) return;
const inputSource = selectedNode.value.data.inputSource || [];
const nodeIndex = inputSource.findIndex((item) => item.nodeId === nodeId);
if (nodeIndex < 0) return;
const node = inputSource[nodeIndex];
const newField = node.field.filter((f) => f !== fieldName);
let updatedInputSource;
if (newField.length > 0) {
updatedInputSource = [...inputSource];
updatedInputSource[nodeIndex] = { ...node, field: newField };
} else {
updatedInputSource = inputSource.filter((_, idx) => idx !== nodeIndex);
}
const updatedNode: Node<NodeData> = {
...selectedNode.value,
data: {
...selectedNode.value.data,
inputSource: updatedInputSource.length > 0 ? updatedInputSource : null,
},
};
selectedNode.value = updatedNode;
// 同步更新到 VueFlow 内部状态
updateNode(updatedNode.id, updatedNode);
const index = nodes.value.findIndex((n) => n.id === updatedNode.id);
if (index >= 0) {
nodes.value[index] = updatedNode;
}
ElMessage.success(`已删除参数:${fieldName}`);
};
// 切换节点输出引用
const handleToggleOutput = (nodeId: string, enabled: boolean) => {
if (!selectedNode.value?.data) return;
const inputSource = selectedNode.value.data.inputSource || [];
const nodeIndex = inputSource.findIndex((item) => item.nodeId === nodeId);
let updatedInputSource;
if (nodeIndex >= 0) {
updatedInputSource = [...inputSource];
updatedInputSource[nodeIndex] = {
...updatedInputSource[nodeIndex],
quoteOutput: enabled,
};
} else {
updatedInputSource = [
...inputSource,
{
nodeId: nodeId,
field: [],
quoteOutput: enabled,
},
];
}
const updatedNode: Node<NodeData> = {
...selectedNode.value,
data: {
...selectedNode.value.data,
inputSource: updatedInputSource,
},
};
selectedNode.value = updatedNode;
// 同步更新到 VueFlow 内部状态
updateNode(updatedNode.id, updatedNode);
const index = nodes.value.findIndex((n) => n.id === updatedNode.id);
if (index >= 0) {
nodes.value[index] = updatedNode;
}
const parentNode = nodes.value.find((n) => n.id === nodeId);
const nodeName = parentNode?.data?.label || '节点';
ElMessage.success(enabled ? `已开启引入 ${nodeName} 的输出` : `已关闭引入 ${nodeName} 的输出`);
};
// 通过参数值添加上级参数
const handleAddParamByValue = (paramValue: string) => {
if (!selectedNode.value?.data) return;
const match = paramValue.match(/\$\{([^.]+)\.(.+)\}/);
if (!match) return;
const nodeId = match[1];
const paramName = match[2];
const inputSource = selectedNode.value.data.inputSource || [];
const existingIndex = inputSource.findIndex((item) => item.nodeId === nodeId);
let updatedInputSource;
if (existingIndex >= 0) {
const existing = inputSource[existingIndex];
if (!existing.field.includes(paramName)) {
updatedInputSource = [...inputSource];
updatedInputSource[existingIndex] = {
...existing,
field: [...existing.field, paramName],
};
} else {
ElMessage.info('该参数已被引用');
return;
}
} else {
updatedInputSource = [
...inputSource,
{
nodeId: nodeId,
field: [paramName],
quoteOutput: false,
},
];
}
const updatedNode: Node<NodeData> = {
...selectedNode.value,
data: {
...selectedNode.value.data,
inputSource: updatedInputSource,
},
};
selectedNode.value = updatedNode;
// 同步更新到 VueFlow 内部状态
updateNode(updatedNode.id, updatedNode);
const index = nodes.value.findIndex((n) => n.id === updatedNode.id);
if (index >= 0) {
nodes.value[index] = updatedNode;
}
ElMessage.success(`已添加上级参数:${paramName}`);
};
// 辅助函数:判断是否为开始节点
const isStartNode = (node: Node<NodeData, any, string>) => {
return node.data?.nodeCode === START_NODE_CODE;
@@ -686,7 +469,7 @@ const addNodeFromLibrary = (nodeCode: string, nodeName: string) => {
id: `node-${++nodeId}`,
type: 'default',
position: { x: spawnX, y: spawnY },
data: { label: nodeName, nodeCode, inputSource: null, patchLayout: false },
data: { label: nodeName, nodeCode, patchLayout: false },
style: { background: '#fff', border: '2px solid #3b82f6', borderRadius: '8px', padding: '10px 20px' },
},
]);
@@ -714,7 +497,7 @@ const addDefaultStartNode = () => {
id: 'start-node',
type: 'input',
position: { x: 200, y: 200 },
data: { label: '开始', nodeCode: '__start__', inputSource: null },
data: { label: '开始', nodeCode: '__start__' },
style: { background: '#10b981', color: '#fff', border: '2px solid #059669', borderRadius: '8px', padding: '10px 20px' },
};
addNodes([startNode]);
@@ -757,23 +540,6 @@ const loadWorkflowFromDsl = (dsl: any) => {
try {
const loadedNodes = (dsl.nodes || []).map((n: any) => {
// 确保 inputSource 使用新结构
let normalizedInputSource: Array<{ nodeId: string; field: string[]; quoteOutput?: boolean }> | null = null;
if (n.inputSource) {
if (Array.isArray(n.inputSource)) {
// 检查是否为新结构(对象数组)
if (n.inputSource.length > 0 && typeof n.inputSource[0] === 'object' && n.inputSource[0].nodeId) {
// 已经是新结构
normalizedInputSource = n.inputSource;
} else {
// 旧结构(字符串数组),需要转换
normalizedInputSource = [];
// 旧结构不再支持,设为空
}
}
}
return {
id: n.id,
type: 'default',
@@ -781,7 +547,6 @@ const loadWorkflowFromDsl = (dsl: any) => {
data: {
label: n.name || '',
nodeCode: n.nodeCode,
inputSource: normalizedInputSource,
formConfig: n.formConfig || null,
modelConfig: n.modelConfig || null,
skillName: n.skillName || null,
@@ -855,7 +620,6 @@ const confirmSaveWorkflow = async () => {
x: n.position?.x || 0,
y: n.position?.y || 0,
},
inputSource: n.data?.inputSource || null,
formConfig: n.data?.formConfig || null,
modelConfig: n.data?.modelConfig || null,
outputResult: null,