针对工作流管理进行组件抽离和与内容创作进行安全隔离
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user