diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index 8851642a5d..f27ad1e2e3 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -56,7 +56,7 @@ export const RETRIEVAL_OUTPUT_STRUCT = `{ }` export const SUPPORT_OUTPUT_VARS_NODE = [ - BlockEnum.Start, BlockEnum.TriggerWebhook, BlockEnum.LLM, BlockEnum.KnowledgeRetrieval, BlockEnum.Code, BlockEnum.TemplateTransform, + BlockEnum.Start, BlockEnum.TriggerWebhook, BlockEnum.TriggerPlugin, BlockEnum.LLM, BlockEnum.KnowledgeRetrieval, BlockEnum.Code, BlockEnum.TemplateTransform, BlockEnum.HttpRequest, BlockEnum.Tool, BlockEnum.VariableAssigner, BlockEnum.VariableAggregator, BlockEnum.QuestionClassifier, BlockEnum.ParameterExtractor, BlockEnum.Iteration, BlockEnum.Loop, BlockEnum.DocExtractor, BlockEnum.ListFilter, diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index 5cdbd74f3c..533c628bfc 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -40,6 +40,8 @@ import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/v import type { Field as StructField } from '@/app/components/workflow/nodes/llm/types' import type { RAGPipelineVariable } from '@/models/pipeline' import type { WebhookTriggerNodeType } from '@/app/components/workflow/nodes/trigger-webhook/types' +import type { PluginTriggerNodeType } from '@/app/components/workflow/nodes/trigger-plugin/types' +import PluginTriggerNodeDefault from '@/app/components/workflow/nodes/trigger-plugin/default' import { AGENT_OUTPUT_STRUCT, @@ -633,6 +635,17 @@ const formatItem = ( break } + case BlockEnum.TriggerPlugin: { + const outputSchema = PluginTriggerNodeDefault.getOutputVars?.( + data as PluginTriggerNodeType, + allPluginInfoList, + [], + { schemaTypeDefinitions }, + ) || [] + res.vars = outputSchema + break + } + case 'env': { res.vars = data.envList.map((env: EnvironmentVariable) => { return { diff --git a/web/app/components/workflow/nodes/trigger-plugin/default.ts b/web/app/components/workflow/nodes/trigger-plugin/default.ts index e31a4afade..2e125a8879 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/default.ts +++ b/web/app/components/workflow/nodes/trigger-plugin/default.ts @@ -1,7 +1,211 @@ -import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import { BlockEnum, VarType } from '../../types' +import type { NodeDefault, Var } from '../../types' import { genNodeMetaData } from '../../utils' import type { PluginTriggerNodeType } from './types' +import { getMatchedSchemaType } from '../_base/components/variable/use-match-schema-type' +import type { SchemaTypeDefinition } from '@/service/use-common' +import { type Field, type StructuredOutput, Type } from '../llm/types' + +const normalizeJsonSchemaType = (schema: any): string | undefined => { + if (!schema) return undefined + const { type, properties, items, oneOf, anyOf, allOf } = schema + + if (Array.isArray(type)) + return type.find((item: string | null) => item && item !== 'null') || type[0] + + if (typeof type === 'string') + return type + + const compositeCandidates = [oneOf, anyOf, allOf] + .filter((entry): entry is any[] => Array.isArray(entry)) + .flat() + + for (const candidate of compositeCandidates) { + const normalized = normalizeJsonSchemaType(candidate) + if (normalized) + return normalized + } + + if (properties) + return 'object' + + if (items) + return 'array' + + return undefined +} + +const pickItemSchema = (schema: any) => { + if (!schema || !schema.items) + return undefined + return Array.isArray(schema.items) ? schema.items[0] : schema.items +} + +const resolveVarType = (schema: any, schemaTypeDefinitions?: SchemaTypeDefinition[]): { type: VarType; schemaType: string } => { + const schemaType = getMatchedSchemaType(schema, schemaTypeDefinitions) + const normalizedType = normalizeJsonSchemaType(schema) + + if (schemaType === 'file') { + if (normalizedType === 'array') + return { type: VarType.arrayFile, schemaType } + return { type: VarType.file, schemaType } + } + + switch (normalizedType) { + case 'string': + return { type: VarType.string, schemaType } + case 'number': + return { type: VarType.number, schemaType } + case 'integer': + return { type: VarType.integer, schemaType } + case 'boolean': + return { type: VarType.boolean, schemaType } + case 'object': + return { type: VarType.object, schemaType } + case 'array': { + const itemSchema = pickItemSchema(schema) + if (!itemSchema) + return { type: VarType.array, schemaType } + + const { type: itemType, schemaType: itemSchemaType } = resolveVarType(itemSchema, schemaTypeDefinitions) + + if (itemSchemaType === 'file') + return { type: VarType.arrayFile, schemaType } + + switch (itemType) { + case VarType.string: + return { type: VarType.arrayString, schemaType } + case VarType.number: + case VarType.integer: + return { type: VarType.arrayNumber, schemaType } + case VarType.boolean: + return { type: VarType.arrayBoolean, schemaType } + case VarType.object: + return { type: VarType.arrayObject, schemaType } + case VarType.file: + return { type: VarType.arrayFile, schemaType } + default: + return { type: VarType.array, schemaType } + } + } + default: + return { type: VarType.any, schemaType } + } +} + +const toFieldType = (normalizedType: string | undefined, schemaType?: string): Type => { + if (schemaType === 'file') + return normalizedType === 'array' ? Type.array : Type.file + + switch (normalizedType) { + case 'number': + case 'integer': + return Type.number + case 'boolean': + return Type.boolean + case 'object': + return Type.object + case 'array': + return Type.array + case 'string': + default: + return Type.string + } +} + +const toArrayItemType = (type: Type): Exclude => { + if (type === Type.array) + return Type.object + return type as Exclude +} + +const convertJsonSchemaToField = (schema: any, schemaTypeDefinitions?: SchemaTypeDefinition[]): Field => { + const schemaType = getMatchedSchemaType(schema, schemaTypeDefinitions) + const normalizedType = normalizeJsonSchemaType(schema) + const fieldType = toFieldType(normalizedType, schemaType) + + const field: Field = { + type: fieldType, + } + + if (schema?.description) + field.description = schema.description + + if (schemaType) + field.schemaType = schemaType + + if (Array.isArray(schema?.enum)) + field.enum = schema.enum + + if (fieldType === Type.object) { + const properties = schema?.properties || {} + field.properties = Object.entries(properties).reduce((acc, [key, value]) => { + acc[key] = convertJsonSchemaToField(value, schemaTypeDefinitions) + return acc + }, {} as Record) + + const required = Array.isArray(schema?.required) ? schema.required.filter(Boolean) : undefined + field.required = required && required.length > 0 ? required : undefined + field.additionalProperties = false + } + + if (fieldType === Type.array) { + const itemSchema = pickItemSchema(schema) + if (itemSchema) { + const itemField = convertJsonSchemaToField(itemSchema, schemaTypeDefinitions) + const { type, ...rest } = itemField + field.items = { + ...rest, + type: toArrayItemType(type), + } + } + } + + return field +} + +const buildOutputVars = (schema: Record, schemaTypeDefinitions?: SchemaTypeDefinition[]): Var[] => { + if (!schema || typeof schema !== 'object') + return [] + + const properties = schema.properties as Record | undefined + if (!properties) + return [] + + return Object.entries(properties).map(([name, propertySchema]) => { + const { type, schemaType } = resolveVarType(propertySchema, schemaTypeDefinitions) + const normalizedType = normalizeJsonSchemaType(propertySchema) + + const varItem: Var = { + variable: name, + type, + des: propertySchema?.description, + schemaType, + } + + if (normalizedType === 'object') { + const childProperties = propertySchema?.properties + ? Object.entries(propertySchema.properties).reduce((acc, [key, value]) => { + acc[key] = convertJsonSchemaToField(value, schemaTypeDefinitions) + return acc + }, {} as Record) + : {} + + const required = Array.isArray(propertySchema?.required) ? propertySchema.required.filter(Boolean) : undefined + + varItem.children = { + schema: { + type: Type.object, + properties: childProperties, + required: required && required.length > 0 ? required : undefined, + additionalProperties: false, + }, + } as StructuredOutput + } + + return varItem + }) +} const metaData = genNodeMetaData({ sort: 1, @@ -23,6 +227,10 @@ const nodeDefault: NodeDefault = { errorMessage: '', } }, + getOutputVars(payload, _allPluginInfoList, _ragVars, { schemaTypeDefinitions } = { schemaTypeDefinitions: [] }) { + const schema = payload.output_schema || {} + return buildOutputVars(schema, schemaTypeDefinitions) + }, } export default nodeDefault