165 lines
4.5 KiB
TypeScript
165 lines
4.5 KiB
TypeScript
import { FormFieldSchema, RunFormSchema } from '@/lib/types/template-run';
|
||
|
||
// 定义工作流节点的数据结构
|
||
interface WorkflowNode {
|
||
id: string;
|
||
type: 'image' | 'video' | 'text' | 'audio';
|
||
data: {
|
||
label: string;
|
||
description?: string;
|
||
flowType: 'start' | 'normal' | 'end';
|
||
actionData?: {
|
||
prompt?: string;
|
||
aspectRatio?: string;
|
||
selectedModel?: string;
|
||
duration?: string;
|
||
advancedParams?: Record<string, any>;
|
||
};
|
||
output?: {
|
||
images?: Array<{ url: string }>;
|
||
videos?: Array<{ url: string }>;
|
||
coverUrl?: string;
|
||
};
|
||
};
|
||
}
|
||
|
||
// 节点类型到表单字段类型的映射
|
||
const nodeTypeToFieldType = {
|
||
image: 'image',
|
||
video: 'video',
|
||
text: 'textarea',
|
||
audio: 'text', // 暂时用文本字段处理音频
|
||
};
|
||
|
||
// 生成字段名称
|
||
const generateFieldName = (node: WorkflowNode, index: number): string => {
|
||
const typePrefix = node.type;
|
||
const flowType = node.data.flowType;
|
||
return `${typePrefix}_${flowType}_${index}`;
|
||
};
|
||
|
||
// 生成字段标签
|
||
const generateFieldLabel = (node: WorkflowNode): string => {
|
||
const baseLabel = node.data.label || `节点 ${node.id.slice(-8)}`;
|
||
const flowTypeLabel = {
|
||
start: '(输入)',
|
||
normal: '(处理)',
|
||
end: '(输出)'
|
||
};
|
||
return baseLabel + (flowTypeLabel[node.data.flowType] || '');
|
||
};
|
||
|
||
// 转换单个节点为表单字段
|
||
const transformNodeToFormField = (node: WorkflowNode, index: number): FormFieldSchema | null => {
|
||
// 只为start节点生成表单字段,因为用户只需要输入数据
|
||
if (node.data.flowType !== 'start') {
|
||
return null;
|
||
}
|
||
|
||
const fieldName = generateFieldName(node, index);
|
||
const fieldType = nodeTypeToFieldType[node.type] || 'text';
|
||
const fieldLabel = generateFieldLabel(node);
|
||
|
||
const field: FormFieldSchema = {
|
||
name: fieldName,
|
||
label: fieldLabel,
|
||
type: fieldType as any,
|
||
required: true,
|
||
description: node.data.description || `请上传${node.type === 'image' ? '图片' : node.type === 'video' ? '视频' : '内容'}`,
|
||
};
|
||
|
||
// 根据节点类型设置默认值和占位符
|
||
switch (node.type) {
|
||
case 'image':
|
||
field.placeholder = '点击上传图片';
|
||
if (node.data.output?.coverUrl) {
|
||
field.defaultValue = node.data.output.coverUrl;
|
||
}
|
||
break;
|
||
|
||
case 'video':
|
||
field.placeholder = '点击上传视频';
|
||
if (node.data.output?.videos?.[0]?.url) {
|
||
field.defaultValue = node.data.output.videos[0].url;
|
||
}
|
||
break;
|
||
|
||
case 'text':
|
||
field.placeholder = '请输入文本内容';
|
||
field.min = 1;
|
||
field.max = 1000;
|
||
break;
|
||
}
|
||
|
||
// 如果节点有预填的prompt,作为提示信息
|
||
if (node.data.actionData?.prompt) {
|
||
field.description += `\n\n提示:${node.data.actionData.prompt}`;
|
||
}
|
||
|
||
return field;
|
||
};
|
||
|
||
// 主要的转换函数
|
||
export function transformWorkflowToFormSchema(formSchemaData: unknown): RunFormSchema {
|
||
try {
|
||
// 类型守卫:检查数据格式
|
||
if (!formSchemaData || typeof formSchemaData !== 'object') {
|
||
console.warn('formSchema数据格式无效');
|
||
return { fields: [] };
|
||
}
|
||
|
||
const data = formSchemaData as any;
|
||
|
||
// 检查是否包含nodes数组(新格式)或startNodes/endNodes(旧格式)
|
||
let nodesToProcess: WorkflowNode[] = [];
|
||
|
||
if (Array.isArray(data.nodes)) {
|
||
// 新格式:从nodes数组中筛选start节点
|
||
nodesToProcess = data.nodes.filter((node: any) =>
|
||
node.data?.flowType === 'start'
|
||
);
|
||
} else if (Array.isArray(data.startNodes)) {
|
||
// 旧格式:使用startNodes
|
||
nodesToProcess = data.startNodes;
|
||
}
|
||
|
||
if (nodesToProcess.length === 0) {
|
||
return { fields: [] };
|
||
}
|
||
|
||
// 转换节点为表单字段
|
||
const fields: FormFieldSchema[] = [];
|
||
|
||
nodesToProcess.forEach((node: any, index: number) => {
|
||
const formField = transformNodeToFormField(node, index);
|
||
if (formField) {
|
||
fields.push(formField);
|
||
}
|
||
});
|
||
|
||
return {
|
||
fields,
|
||
validation: {}
|
||
};
|
||
|
||
} catch (error) {
|
||
console.error('转换formSchema失败:', error);
|
||
return { fields: [] };
|
||
}
|
||
}
|
||
|
||
// 验证转换后的表单配置
|
||
export function validateFormSchema(schema: RunFormSchema): boolean {
|
||
if (!schema || !Array.isArray(schema.fields)) {
|
||
return false;
|
||
}
|
||
|
||
return schema.fields.every(field => {
|
||
return (
|
||
field.name &&
|
||
field.label &&
|
||
field.type &&
|
||
['text', 'number', 'textarea', 'select', 'image', 'video', 'color'].includes(field.type)
|
||
);
|
||
});
|
||
} |