工作流管理相关调整模型列表接口调整

This commit is contained in:
2026-08-10 14:05:46 +08:00
parent cb8effc769
commit 4796750305
5 changed files with 546 additions and 90 deletions
+2 -2
View File
@@ -49,14 +49,14 @@ export interface WorkflowModelItem {
modelName: string;
modelType: number | string;
baseUrl?: string;
enabled?: number;
enabled?: number | boolean;
isOwner?: number;
[key: string]: any;
}
export function getWorkflowModelList(params?: { pageNum: number; pageSize: number; modelName?: string }) {
return request({
url: '/model-gateway/model/listModel',
url: '/model-gateway/model/manage/listModelManage',
method: 'get',
params,
}) as Promise<{ code: number; message: string; data: { list: WorkflowModelItem[]; total: number } }>;
@@ -0,0 +1,133 @@
<template>
<div class="form-fields-editor">
<div v-for="(field, index) in fields" :key="index" class="custom-field-config">
<el-input v-model="field.label" placeholder="字段名" class="custom-field-input" @input="sync" />
<div class="custom-field-row">
<el-select v-model="field.type" placeholder="类型" class="custom-field-type" @change="sync">
<el-option label="文本" value="input" />
<el-option label="数字" value="number" />
<el-option label="多行文本" value="textarea" />
<el-option label="开关" value="switch" />
<el-option label="文件上传" value="uploadMultiple" />
</el-select>
<el-checkbox v-model="field.required" class="custom-field-required" @change="sync">必填</el-checkbox>
<el-button type="danger" link @click="removeField(index)">删除</el-button>
</div>
<!-- 文件上传类型只配置规则不在此处上传 -->
<div v-if="field.type === 'uploadMultiple'" class="upload-rules">
<el-input v-model="field.fileTypes" placeholder="文件格式(如 .mp4,jpg,png" class="rule-file-types" @input="sync" />
<el-input-number v-model="field.maxFileSize" :min="1" placeholder="最大大小(MB)" class="rule-file-size" @change="sync" />
<el-input-number v-model="field.maxFileCount" :min="1" placeholder="最大数量" class="rule-file-size" @change="sync" />
</div>
<!-- 非文件上传类型显示默认值输入框 -->
<el-input v-else-if="field.type === 'textarea'" v-model="field.value" placeholder="默认值" type="textarea" :rows="2" class="custom-field-value-full" @input="sync" />
<el-input-number v-else-if="field.type === 'number'" v-model="field.value" placeholder="默认值" class="custom-field-value-full" @change="sync" />
<el-switch v-else-if="field.type === 'switch'" v-model="field.value" class="custom-field-value-full" @change="sync" />
<el-input v-else v-model="field.value" placeholder="默认值" class="custom-field-value-full" @input="sync" />
</div>
<el-button type="primary" link class="w100" @click="addField">+ 添加自定义字段</el-button>
</div>
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue';
export interface FormField {
label: string;
type: string; // input | number | textarea | switch | uploadMultiple
value: any; // 默认值(uploadMultiple 时存文件 URL 数组或空)
required: boolean;
fileTypes?: string;
maxFileSize?: number;
maxFileCount?: number;
}
const props = defineProps<{ modelValue: FormField[] }>();
const emit = defineEmits<{ 'update:modelValue': [FormField[]] }>();
const fields = ref<FormField[]>([]);
let internalSync = false; // 由 props 同步触发,避免 emit 循环
const clone = (arr: FormField[]): FormField[] => JSON.parse(JSON.stringify(arr || [])) as FormField[];
watch(
() => props.modelValue,
(val) => {
internalSync = true;
fields.value = clone(val);
nextTick(() => {
internalSync = false;
});
},
{ immediate: true, deep: true }
);
const sync = () => {
if (internalSync) return;
emit('update:modelValue', clone(fields.value));
};
const addField = () => {
fields.value.push({ label: '', type: 'input', value: '', required: false });
sync();
};
const removeField = (index: number) => {
fields.value.splice(index, 1);
sync();
};
</script>
<style scoped lang="scss">
.form-fields-editor {
width: 100%;
.custom-field-config {
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 12px;
margin-bottom: 10px;
background: #f8fafc;
.custom-field-input {
margin-bottom: 8px;
}
.custom-field-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
.custom-field-type {
flex: 1;
min-width: 0;
}
.custom-field-required {
flex-shrink: 0;
}
}
.upload-rules {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 8px;
.rule-file-types,
.rule-file-size {
width: 100%;
}
}
.custom-field-value-full {
width: 100%;
}
}
.w100 {
width: 100%;
}
}
</style>
@@ -31,8 +31,8 @@
<h3 class="model-name">{{ model.modelName }}</h3>
<p class="model-url">{{ model.baseUrl }}</p>
<div class="model-status">
<el-tag :type="model.enabled === 1 ? 'success' : 'info'" size="small">
{{ model.enabled === 1 ? '已启用' : '已禁用' }}
<el-tag :type="isModelEnabled(model) ? 'success' : 'info'" size="small">
{{ isModelEnabled(model) ? '已启用' : '已禁用' }}
</el-tag>
</div>
</div>
@@ -133,6 +133,9 @@ const getModelTypeName = (type: number | string) => {
return typeMap[Number(type)] || '未知类型';
};
// 兼容 enabled 数字(1/0) 与布尔(true/false) 两种返回
const isModelEnabled = (model: WorkflowModelItem) => model.enabled === 1 || model.enabled === true;
const fetchModelList = async () => {
loading.value = true;
try {
@@ -45,63 +45,143 @@
/>
</el-form-item>
<!-- 是否保存文件开关 -->
<el-form-item v-if="nodeConfig?.isSaveFileOption" label="是否保存文件">
<el-switch
:model-value="selectedNode.data?.isSaveFile ?? false"
active-text="开启"
inactive-text="关闭"
@update:model-value="updateIsSaveFile"
/>
</el-form-item>
<!-- 动态表单字段 -->
<template v-if="nodeConfig?.formConfig && nodeConfig.formConfig.length > 0">
<el-divider content-position="left">节点参数</el-divider>
<el-form-item v-for="field in nodeConfig.formConfig" :key="field.field" :label="field.label" :required="field.required">
<el-input
v-if="field.type === 'input' || field.type === 'string'"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
:placeholder="field.required ? '必填' : '选填'"
/>
<el-input-number
v-else-if="field.type === 'number' || field.type === 'inputNumber'"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
class="w100"
/>
<el-input
v-else-if="field.type === 'textarea'"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
type="textarea"
:rows="3"
:placeholder="field.required ? '必填' : '选填'"
/>
<el-switch
v-else-if="field.type === 'switch'"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
/>
<el-select
v-else-if="field.type === 'select' && field.options"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
:placeholder="field.required ? '必填' : '选填'"
class="w100"
>
<el-option v-for="opt in field.options" :key="opt.key || opt.value" :label="opt.value" :value="opt.value" />
</el-select>
<KeyValueEditor
v-else-if="field.type === 'keyValue'"
:model-value="getFieldValue(field.field)"
@update:model-value="(v: any) => updateFieldValue(field.field, v)"
/>
<el-button
v-else-if="field.type === 'schemaJson'"
@click="openSchemaEditor(field.field)"
style="width: 100%"
>配置</el-button>
<el-input
v-else
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
:placeholder="field.required ? '必填' : '选填'"
/>
</el-form-item>
<template v-for="field in nodeConfig.formConfig" :key="field.field">
<el-form-item :label="field.label" :required="field.required">
<el-input
v-if="field.type === 'input' || field.type === 'string'"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
:placeholder="field.required ? '必填' : '选填'"
/>
<el-input-number
v-else-if="field.type === 'number' || field.type === 'inputNumber'"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
class="w100"
/>
<el-input
v-else-if="field.type === 'textarea'"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
type="textarea"
:rows="3"
:placeholder="field.required ? '必填' : '选填'"
/>
<el-switch
v-else-if="field.type === 'switch'"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
/>
<el-select
v-else-if="field.type === 'select' && field.options"
:model-value="getFieldValue(field.field)"
@update:model-value="updateSelectFieldValue(field, $event)"
:placeholder="field.required ? '必填' : '选填'"
class="w100"
>
<el-option
v-for="opt in field.options"
:key="opt.key || opt.value"
:label="opt.key || opt.value"
:value="opt.value"
/>
</el-select>
<KeyValueEditor
v-else-if="field.type === 'keyValue'"
:model-value="getFieldValue(field.field)"
@update:model-value="(v: any) => updateFieldValue(field.field, v)"
/>
<el-button
v-else-if="field.type === 'schemaJson'"
@click="openSchemaEditor(field.field)"
style="width: 100%"
>配置</el-button>
<el-input
v-else
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
:placeholder="field.required ? '必填' : '选填'"
/>
</el-form-item>
<!-- 下拉选中选项的嵌套配置(responseType 级联) -->
<template v-if="getSelectedOptionConfig(field).length > 0">
<el-form-item
v-for="sub in getSelectedOptionConfig(field)"
:key="`${field.field}-${sub.field}`"
:label="sub.label"
:required="sub.required"
>
<el-input
v-if="sub.type === 'input' || sub.type === 'string'"
:model-value="getExpandValue(field.field, sub.field)"
@update:model-value="(v: any) => updateExpandValue(field.field, sub.field, v)"
:placeholder="sub.required ? '必填' : '选填'"
/>
<el-input-number
v-else-if="sub.type === 'number' || sub.type === 'inputNumber'"
:model-value="getExpandValue(field.field, sub.field)"
@update:model-value="(v: any) => updateExpandValue(field.field, sub.field, v)"
class="w100"
/>
<el-switch
v-else-if="sub.type === 'switch'"
:model-value="getExpandValue(field.field, sub.field)"
@update:model-value="(v: any) => updateExpandValue(field.field, sub.field, v)"
/>
<el-select
v-else-if="sub.type === 'select' && sub.options"
:model-value="getExpandValue(field.field, sub.field)"
@update:model-value="(v: any) => updateExpandValue(field.field, sub.field, v)"
:placeholder="sub.required ? '必填' : '选填'"
class="w100"
>
<el-option
v-for="opt in sub.options"
:key="opt.key || opt.value"
:label="opt.key || opt.value"
:value="opt.value"
/>
</el-select>
<KeyValueEditor
v-else-if="sub.type === 'keyValue'"
:model-value="getExpandValue(field.field, sub.field)"
@update:model-value="(v: any) => updateExpandValue(field.field, sub.field, v)"
/>
<el-button
v-else-if="sub.type === 'schemaJson'"
@click="openSchemaEditor(field.field, sub.field)"
style="width: 100%"
>配置</el-button>
<el-input
v-else
:model-value="getExpandValue(field.field, sub.field)"
@update:model-value="(v: any) => updateExpandValue(field.field, sub.field, v)"
:placeholder="sub.required ? '必填' : '选填'"
/>
</el-form-item>
</template>
</template>
</template>
</el-form>
<!-- 自定义表单字段(formConfigOption,如 form 节点) -->
<template v-if="nodeConfig?.formConfigOption">
<el-divider content-position="left">自定义字段</el-divider>
<FormFieldsEditor :model-value="customFormFields" @update:model-value="updateCustomFields" />
</template>
</el-form>
</div>
<el-empty v-else description="请选择一个节点" :image-size="100" />
@@ -122,6 +202,7 @@
import { ref, computed } from 'vue';
import type { Node } from '@vue-flow/core';
import KeyValueEditor from './KeyValueEditor.vue';
import FormFieldsEditor, { type FormField } from './FormFieldsEditor.vue';
import { JsonEditor } from '/@/components/json-schema-editor';
interface NodeData {
@@ -132,6 +213,7 @@ interface NodeData {
modelConfig?: any;
skillName?: string;
patchLayout?: boolean;
isSaveFile?: boolean;
}
interface NodeConfig {
@@ -156,6 +238,9 @@ const isVideoModelSelected = computed(() => {
// 开始节点不展示节点描述
const isStartNode = computed(() => props.selectedNode?.data?.nodeCode === '__start__');
// form 节点自定义字段(formConfigOption
const customFormFields = computed<FormField[]>(() => (props.selectedNode?.data?.formConfig as FormField[]) || []);
const emit = defineEmits<{
(e: 'update:selectedNode', node: Node<NodeData, any, string>): void;
(e: 'openModelSelector'): void;
@@ -168,17 +253,23 @@ const emit = defineEmits<{
// Schema 编辑器弹窗
const schemaEditorVisible = ref(false);
const schemaEditorField = ref('');
const schemaEditorExpandField = ref(''); // 空表示顶层字段,非空表示 responseType 的嵌套 expand 字段
const schemaEditorData = ref<any>({});
function openSchemaEditor(fieldName: string) {
function openSchemaEditor(fieldName: string, expandField = '') {
schemaEditorField.value = fieldName;
const val = getFieldValue(fieldName);
schemaEditorExpandField.value = expandField;
const val = expandField ? getExpandValue(fieldName, expandField) : getFieldValue(fieldName);
schemaEditorData.value = val && typeof val === 'object' ? { ...val } : {};
schemaEditorVisible.value = true;
}
function confirmSchemaEditor() {
updateFieldValue(schemaEditorField.value, schemaEditorData.value);
if (schemaEditorExpandField.value) {
updateExpandValue(schemaEditorField.value, schemaEditorExpandField.value, schemaEditorData.value);
} else {
updateFieldValue(schemaEditorField.value, schemaEditorData.value);
}
schemaEditorVisible.value = false;
}
@@ -206,6 +297,31 @@ const updateNodeDesc = (newDesc: string) => {
emit('update:selectedNode', updatedNode);
};
const updateIsSaveFile = (value: boolean) => {
if (!props.selectedNode?.data) return;
const updatedNode = {
...props.selectedNode,
data: {
...props.selectedNode.data,
isSaveFile: value,
},
};
emit('update:selectedNode', updatedNode);
};
// 自定义表单字段(formConfigOption)变更:写回 node.data.formConfig
const updateCustomFields = (fields: FormField[]) => {
if (!props.selectedNode?.data) return;
const updatedNode = {
...props.selectedNode,
data: {
...props.selectedNode.data,
formConfig: fields,
},
};
emit('update:selectedNode', updatedNode);
};
const getFieldValue = (fieldName: string) => {
if (!props.selectedNode?.data?.formConfig) return '';
const field = props.selectedNode.data.formConfig.find((f: any) => f.field === fieldName);
@@ -240,6 +356,82 @@ const updateFieldValue = (fieldName: string, value: any) => {
};
emit('update:selectedNode', updatedNode);
};
// 取 formConfig 中的某个字段条目
const getFormConfigEntry = (fieldName: string) => {
if (!props.selectedNode?.data?.formConfig) return undefined;
return props.selectedNode.data.formConfig.find((f: any) => f.field === fieldName);
};
// 当前选中下拉选项的嵌套 config 定义(来自节点库 presetOption
const getSelectedOptionConfig = (fieldDef: any) => {
if (fieldDef?.type !== 'select' || !Array.isArray(fieldDef.options)) return [];
const value = getFieldValue(fieldDef.field);
const opt = fieldDef.options.find((o: any) => o.value === value);
return opt?.config || [];
};
// 读嵌套 expand 字段值
const getExpandValue = (hostField: string, subField: string) => {
const entry = getFormConfigEntry(hostField);
const sub = entry?.expand?.find((e: any) => e.field === subField);
return sub?.value ?? '';
};
// 写嵌套 expand 字段值(单次 emit
const updateExpandValue = (hostField: string, subField: string, value: any) => {
if (!props.selectedNode?.data) return;
const formConfig = props.selectedNode.data.formConfig || [];
const entryIndex = formConfig.findIndex((f: any) => f.field === hostField);
if (entryIndex < 0) return;
const entry = formConfig[entryIndex];
const expand = entry.expand || [];
const subIndex = expand.findIndex((e: any) => e.field === subField);
const newExpand = [...expand];
if (subIndex >= 0) {
newExpand[subIndex] = { ...newExpand[subIndex], value };
} else {
newExpand.push({ field: subField, value });
}
const updatedFormConfig = [...formConfig];
updatedFormConfig[entryIndex] = { ...entry, expand: newExpand };
const updatedNode = {
...props.selectedNode,
data: { ...props.selectedNode.data, formConfig: updatedFormConfig },
};
emit('update:selectedNode', updatedNode);
};
// select 变更统一入口:更新 value 并按选中选项的 config 重建 expand(保留已有字段值)
const updateSelectFieldValue = (fieldDef: any, value: any) => {
if (!props.selectedNode?.data) return;
const formConfig = props.selectedNode.data.formConfig || [];
const entryIndex = formConfig.findIndex((f: any) => f.field === fieldDef.field);
const opt = (fieldDef.options || []).find((o: any) => o.value === value);
const configDefs = opt?.config || [];
const oldExpand = entryIndex >= 0 ? formConfig[entryIndex]?.expand || [] : [];
const newExpand = configDefs.map((cd: any) => {
const old = oldExpand.find((e: any) => e.field === cd.field);
return { ...cd, value: old?.value !== undefined ? old.value : (cd.default ?? '') };
});
let updatedFormConfig;
if (entryIndex >= 0) {
updatedFormConfig = [...formConfig];
updatedFormConfig[entryIndex] = { ...updatedFormConfig[entryIndex], value, expand: newExpand };
} else {
updatedFormConfig = [...formConfig, { ...fieldDef, value, expand: newExpand }];
}
const updatedNode = {
...props.selectedNode,
data: { ...props.selectedNode.data, formConfig: updatedFormConfig },
};
emit('update:selectedNode', updatedNode);
};
</script>
<style scoped lang="scss">
+158 -30
View File
@@ -110,9 +110,11 @@ interface NodeData {
nodeCode?: string;
desc?: string;
formConfig?: any[];
modelConfig?: any;
modelConfig?: { modelId?: string; modelName?: string; modelType?: string | number; modelRequestParams?: any } | null;
skillName?: string;
patchLayout?: boolean;
isSaveFile?: boolean;
preTool?: string | null;
}
const { addNodes, addEdges, findNode, removeNodes, getNodes, updateNode } = useVueFlow();
@@ -240,9 +242,9 @@ const onConnect = (connection: Connection) => {
const onNodeClick = (event: { node: Node<NodeData, any, string> }) => {
selectedNode.value = event.node;
// 同步选择器预选项,避免展示上一个节点的模型/技能
// 同步选择器预选项,避免展示上一个节点的模型/技能;回读后无 modelName 时仍按 modelId 高亮
const mc = event.node.data?.modelConfig;
selectedModelData.value = mc?.modelName ? { modelName: mc.modelName, modelType: mc.modelType } : null;
selectedModelData.value = mc?.modelId ? { id: mc.modelId, modelName: mc.modelName || '', modelType: mc.modelType } : null;
const sk = event.node.data?.skillName;
selectedSkillData.value = sk ? { name: sk } : null;
};
@@ -267,11 +269,10 @@ const handleModelConfirm = (model: any) => {
data: {
...selectedNode.value.data,
modelConfig: {
modelId: model.id || '',
modelName: model.modelName,
modelType: model.modelType,
modelApiKey: '',
modelForm: model.modelForm || [],
modelResponse: model.responseBody || {},
modelRequestParams: null,
},
},
};
@@ -527,7 +528,8 @@ const editWorkflow = async (workflow: WorkflowItem) => {
const res = await getWorkflowDetail(workflow.id);
if (res.data?.flowContent) {
loadWorkflowFromDsl(res.data.flowContent);
currentEditingWorkflowId.value = res.data.id;
const detail = res.data as any; // 后端详情可能返回 flowId 而非 id
currentEditingWorkflowId.value = detail.id || detail.flowId || null;
saveForm.value.flowName = res.data.flowName || res.data.flowTemplateName || '';
saveForm.value.description = res.data.description || '';
ElMessage.success('工作流已加载');
@@ -537,26 +539,144 @@ const editWorkflow = async (workflow: WorkflowItem) => {
}
};
// 从 DSL 节点重建 formConfig(新格式:outputConfig;旧格式:formConfig 兜底)
const buildNodeFormConfigFromDsl = (n: any) => {
// form 节点:自定义字段(完整结构 {type,field,label,value,required},兼容命名对象 [{key:value}]
if (n.nodeCode === 'form' && Array.isArray(n.outputConfig)) {
return n.outputConfig.map((o: any) => {
if (o && typeof o === 'object' && o.field !== undefined) {
const constraint = o.fieldConstraint || {};
return {
label: o.label || o.field,
field: o.field,
type: o.type || 'input',
value: o.value ?? '',
required: Boolean(o.required),
...(o.type === 'uploadMultiple'
? {
fileTypes: constraint.fileTypes || '',
maxFileSize: constraint.maxFileSize,
maxFileCount: constraint.maxFileCount,
}
: {}),
};
}
// 命名对象兼容:{key: value}
const key = Object.keys(o || {})[0] || '';
const value = o?.[key];
return {
label: key,
field: key,
type: typeof value === 'number' ? 'number' : typeof value === 'boolean' ? 'switch' : 'input',
value,
required: false,
};
});
}
// 新格式:HTTP 节点 outputConfig 为 {type,field,label,value} 数组
if (Array.isArray(n.outputConfig) && n.outputConfig.length > 0) {
const defs = nodeConfigMap.value.get(n.nodeCode)?.formConfig || [];
// 无 presetOption 定义(如 form 节点为命名值数组)时直接透传
if (defs.length === 0) return n.outputConfig;
return defs.map((def: any) => {
const out = n.outputConfig.find((o: any) => o.field === def.field);
if (def.field === 'responseType') {
// 恢复嵌套配置:定义取自 presetOption 选中选项的 config,值取自 options[0].config
const savedConfig = out?.options?.[0]?.config || [];
const selectedOpt = (def.options || []).find((o: any) => o.value === out?.value);
const expandDefs = selectedOpt?.config || [];
const expand = expandDefs.map((cd: any) => {
const saved = savedConfig.find((c: any) => c.field === cd.field);
return { ...cd, value: saved?.value ?? '' };
});
return { ...def, value: out?.value ?? '', expand };
}
return { ...def, value: out?.value ?? '' };
});
}
// 旧格式兜底
if (Array.isArray(n.formConfig)) return n.formConfig;
return null;
};
// 构建保存用的 outputConfigHTTP/form 节点),其余返回 null
const buildOutputConfig = (node: Node<NodeData>) => {
const nodeCode = node.data?.nodeCode || '';
if (nodeCode === 'http') {
const defs = nodeConfigMap.value.get(nodeCode)?.formConfig || [];
const formConfig = node.data?.formConfig || [];
return defs.map((def: any) => {
const entry = formConfig.find((f: any) => f.field === def.field);
const value = entry?.value ?? '';
if (def.field === 'responseType') {
const expand = entry?.expand || [];
return {
type: 'select',
field: 'responseType',
label: def.label,
value,
options: expand.length
? [{ config: expand.map((e: any) => ({ type: e.type, field: e.field, label: e.label, value: e.value })) }]
: [],
};
}
return { type: def.type, field: def.field, label: def.label, value };
});
}
if (nodeCode === 'form') {
// form 节点 outputConfig 为自定义字段完整结构(含 type/required,便于完整回显)
const fields = Array.isArray(node.data?.formConfig) ? node.data.formConfig : [];
return fields.map((f: any) => ({
type: f.type || 'input',
field: f.label || f.field || '',
label: f.label || f.field || '',
value: f.value ?? '',
required: Boolean(f.required),
...(f.type === 'uploadMultiple'
? {
fieldConstraint: {
...(f.fileTypes ? { fileTypes: f.fileTypes } : {}),
...(f.maxFileSize !== undefined && f.maxFileSize !== null ? { maxFileSize: f.maxFileSize } : {}),
...(f.maxFileCount !== undefined && f.maxFileCount !== null ? { maxFileCount: f.maxFileCount } : {}),
},
}
: {}),
}));
}
return null;
};
// 从 DSL 加载工作流
const loadWorkflowFromDsl = (dsl: any) => {
if (!dsl) return;
try {
const loadedNodes = (dsl.nodes || []).map((n: any) => {
const isStart = n.nodeCode === '__start__';
return {
id: n.id,
type: 'default',
type: isStart ? 'input' : 'default',
position: { x: n.config?.x || 220, y: n.config?.y || 140 },
data: {
label: n.name || '',
nodeCode: n.nodeCode,
desc: n.desc || '',
formConfig: n.formConfig || null,
modelConfig: n.modelConfig || null,
formConfig: buildNodeFormConfigFromDsl(n),
modelConfig: {
modelId: n.modelConfig?.modelId || '',
modelName: '',
modelType: undefined,
modelRequestParams: n.modelConfig?.modelRequestParams ?? null,
},
skillName: n.skillName || null,
patchLayout: n.patchLayout || false,
isSaveFile: Boolean(n.isSaveFile),
preTool: n.preTool ?? null,
},
style: { background: '#fff', border: '2px solid #3b82f6', borderRadius: '8px', padding: '10px 20px' },
style: isStart
? { background: '#10b981', color: '#fff', border: '2px solid #059669', borderRadius: '8px', padding: '10px 20px' }
: { background: '#fff', border: '2px solid #3b82f6', borderRadius: '8px', padding: '10px 20px' },
};
});
@@ -613,29 +733,37 @@ const confirmSaveWorkflow = async () => {
const workflowDsl = {
version: '1.0.0',
startNodeId: startNode?.id || '',
nodes: nodes.value.map((n) => ({
id: n.id,
nodeCode: n.data?.nodeCode || 'unknown',
name: n.data?.label || '',
type: n.type || 'default',
desc: n.data?.desc || null,
skillName: n.data?.skillName || null,
patchLayout: n.data?.patchLayout || false,
config: {
nodeCode: n.data?.nodeCode || 'unknown',
x: n.position?.x || 0,
y: n.position?.y || 0,
},
formConfig: n.data?.formConfig || null,
modelConfig: n.data?.modelConfig || null,
outputResult: null,
})),
nodes: nodes.value.map((n) => {
const gNode = n as any; // VueFlow 运行时会注入 dimensions(渲染尺寸)
const nodeCode = n.data?.nodeCode || 'unknown';
return {
id: n.id,
nodeCode,
name: n.data?.label || '',
config: {
nodeCode,
x: n.position?.x || 0,
y: n.position?.y || 0,
width: gNode.dimensions?.width || gNode.width || 100,
height: gNode.dimensions?.height || gNode.height || 80,
},
...(n.data?.preTool ? { preTool: n.data.preTool } : {}),
isSaveFile: Boolean(n.data?.isSaveFile),
subConfig: null,
modelConfig: {
modelId: n.data?.modelConfig?.modelId || '',
modelRequestParams: n.data?.modelConfig?.modelRequestParams ?? null,
},
outputConfig: buildOutputConfig(n),
...(n.data?.skillName ? { skillName: n.data.skillName } : {}),
...(n.data?.patchLayout ? { patchLayout: n.data.patchLayout } : {}),
outputResult: null,
};
}),
edges: edges.value.map((e) => ({
id: e.id,
from: e.source,
to: e.target,
type: e.type || 'smoothstep',
mapping: {},
})),
};