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 2f83945dc2..c3a3201bcd 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -60,10 +60,7 @@ import { import { VAR_REGEX } from '@/config' import { AppModeEnum } from '@/types/app' import { OUTPUT_FILE_SUB_VARIABLES } from '../../../constants' -import { - - Type, -} from '../../../llm/types' +import { FILE_REF_FORMAT, Type } from '../../../llm/types' import { VarType as ToolVarType } from '../../../tool/types' export const isSystemVar = (valueSelector: ValueSelector) => { @@ -122,7 +119,16 @@ export const inputVarTypeToVarType = (type: InputVarType): VarType => { ) } -const structTypeToVarType = (type: Type, isArray?: boolean): VarType => { +const structTypeToVarType = ( + type: Type, + isArray?: boolean, + format?: string, + itemsFormat?: string, +): VarType => { + if (isArray && itemsFormat === FILE_REF_FORMAT) + return VarType.arrayFile + if (!isArray && format === FILE_REF_FORMAT) + return VarType.file if (isArray) { return ( ( @@ -175,6 +181,7 @@ const findExceptVarInStructuredProperties = ( const isObj = item.type === Type.object const isArray = item.type === Type.array const arrayType = item.items?.type + const arrayFormat = item.items?.format if ( !isObj @@ -184,6 +191,8 @@ const findExceptVarInStructuredProperties = ( type: structTypeToVarType( isArray ? arrayType! : item.type, isArray, + item.format, + arrayFormat, ), }, [key], @@ -215,6 +224,7 @@ const findExceptVarInStructuredOutput = ( const isObj = item.type === Type.object const isArray = item.type === Type.array const arrayType = item.items?.type + const arrayFormat = item.items?.format if ( !isObj && !filterVar( @@ -223,6 +233,8 @@ const findExceptVarInStructuredOutput = ( type: structTypeToVarType( isArray ? arrayType! : item.type, isArray, + item.format, + arrayFormat, ), }, [key], @@ -1144,8 +1156,14 @@ export const getVarType = ({ return currProperties = currProperties.properties[key] - if (isLast) - type = structTypeToVarType(currProperties?.type) + if (isLast) { + if (currProperties?.format === FILE_REF_FORMAT) + type = VarType.file + else if (currProperties?.type === Type.array && currProperties?.items?.format === FILE_REF_FORMAT) + type = VarType.arrayFile + else + type = structTypeToVarType(currProperties?.type) + } }) return type } @@ -1946,15 +1964,20 @@ const varToValueSelectorList = ( Object.keys( (v.children as StructuredOutput)?.schema?.properties || {}, ).forEach((key) => { - const type = (v.children as StructuredOutput)?.schema?.properties[key].type + const schemaProperty = (v.children as StructuredOutput)?.schema?.properties[key] + const type = schemaProperty?.type const isArray = type === Type.array - const arrayType = (v.children as StructuredOutput)?.schema?.properties[ - key - ].items?.type + const arrayType = schemaProperty?.items?.type + const arrayFormat = schemaProperty?.items?.format varToValueSelectorList( { variable: key, - type: structTypeToVarType(isArray ? arrayType! : type, isArray), + type: structTypeToVarType( + isArray ? arrayType! : type, + isArray, + schemaProperty?.format, + arrayFormat, + ), }, [...parentValueSelector, v.variable], res, diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx index 3cf9003051..b3713ae60a 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx @@ -44,17 +44,21 @@ const TYPE_OPTIONS = [ { value: Type.number, text: 'number' }, { value: Type.boolean, text: 'boolean' }, { value: Type.object, text: 'object' }, + { value: Type.file, text: 'file' }, { value: ArrayType.string, text: 'array[string]' }, { value: ArrayType.number, text: 'array[number]' }, { value: ArrayType.object, text: 'array[object]' }, + { value: ArrayType.file, text: 'array[file]' }, ] const MAXIMUM_DEPTH_TYPE_OPTIONS = [ { value: Type.string, text: 'string' }, { value: Type.number, text: 'number' }, { value: Type.boolean, text: 'boolean' }, + { value: Type.file, text: 'file' }, { value: ArrayType.string, text: 'array[string]' }, { value: ArrayType.number, text: 'array[number]' }, + { value: ArrayType.file, text: 'array[file]' }, ] const EditCard: FC = ({ diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts index 6159028c21..a2b2d8e2b8 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts @@ -4,7 +4,7 @@ import type { EditData } from './edit-card' import { noop } from 'es-toolkit/function' import { produce } from 'immer' import Toast from '@/app/components/base/toast' -import { ArrayType, Type } from '../../../types' +import { ArrayType, FILE_REF_FORMAT, Type } from '../../../types' import { findPropertyWithPath } from '../../../utils' import { useMittContext } from './context' import { useVisualEditorStore } from './store' @@ -133,6 +133,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => { } if (schema.type === Type.array) delete schema.items + delete schema.format switch (newType) { case Type.object: schema.type = Type.object @@ -140,6 +141,10 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => { schema.required = [] schema.additionalProperties = false break + case Type.file: + schema.type = Type.string + schema.format = FILE_REF_FORMAT + break case ArrayType.string: schema.type = Type.array schema.items = { @@ -167,6 +172,13 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => { additionalProperties: false, } break + case ArrayType.file: + schema.type = Type.array + schema.items = { + type: Type.string, + format: FILE_REF_FORMAT, + } + break default: schema.type = newType as Type } @@ -309,6 +321,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => { } if (schema.type === Type.array) delete schema.items + delete schema.format switch (newType) { case Type.object: schema.type = Type.object @@ -316,6 +329,10 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => { schema.required = [] schema.additionalProperties = false break + case Type.file: + schema.type = Type.string + schema.format = FILE_REF_FORMAT + break case ArrayType.string: schema.type = Type.array schema.items = { @@ -343,6 +360,13 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => { additionalProperties: false, } break + case ArrayType.file: + schema.type = Type.array + schema.items = { + type: Type.string, + format: FILE_REF_FORMAT, + } + break default: schema.type = newType as Type } @@ -398,6 +422,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => { } if (schema.type === Type.array) delete schema.items + delete schema.format switch (newType) { case Type.object: schema.type = Type.object @@ -405,6 +430,10 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => { schema.required = [] schema.additionalProperties = false break + case Type.file: + schema.type = Type.string + schema.format = FILE_REF_FORMAT + break case ArrayType.string: schema.type = Type.array schema.items = { @@ -432,6 +461,13 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => { additionalProperties: false, } break + case ArrayType.file: + schema.type = Type.array + schema.items = { + type: Type.string, + format: FILE_REF_FORMAT, + } + break default: schema.type = newType as Type } diff --git a/web/app/components/workflow/nodes/llm/types.ts b/web/app/components/workflow/nodes/llm/types.ts index 5b15f83ac6..9738b74ea2 100644 --- a/web/app/components/workflow/nodes/llm/types.ts +++ b/web/app/components/workflow/nodes/llm/types.ts @@ -20,6 +20,8 @@ export type LLMNodeType = CommonNodeType & { reasoning_format?: 'tagged' | 'separated' } +export const FILE_REF_FORMAT = 'dify-file-ref' + export enum Type { string = 'string', number = 'number', @@ -38,12 +40,13 @@ export enum ArrayType { number = 'array[number]', boolean = 'array[boolean]', object = 'array[object]', + file = 'array[file]', } export type TypeWithArray = Type | ArrayType type ArrayItemType = Exclude -export type ArrayItems = Omit & { type: ArrayItemType } +export type ArrayItems = Omit & { type: ArrayItemType; format?: string } export type SchemaEnumType = string[] | number[] @@ -54,6 +57,7 @@ export type Field = { } required?: string[] // Key of required properties in object description?: string + format?: string items?: ArrayItems // Array has items. Define the item type enum?: SchemaEnumType // Enum values additionalProperties?: false // Required in object by api. Just set false diff --git a/web/app/components/workflow/nodes/llm/utils.ts b/web/app/components/workflow/nodes/llm/utils.ts index 604a1f8408..9922529cb5 100644 --- a/web/app/components/workflow/nodes/llm/utils.ts +++ b/web/app/components/workflow/nodes/llm/utils.ts @@ -2,21 +2,24 @@ import type { ValidationError } from 'jsonschema' import type { ArrayItems, Field, LLMNodeType } from './types' import { z } from 'zod' import { draft07Validator, forbidBooleanProperties } from '@/utils/validators' -import { ArrayType, Type } from './types' +import { ArrayType, FILE_REF_FORMAT, Type } from './types' export const checkNodeValid = (_payload: LLMNodeType) => { return true } export const getFieldType = (field: Field) => { - const { type, items, enum: enums } = field + const { type, items, enum: enums, format } = field + if (format === FILE_REF_FORMAT) + return Type.file if (field.schemaType === 'file') return Type.file if (enums && enums.length > 0) return Type.enumType if (type !== Type.array || !items) return type - + if (items.format === FILE_REF_FORMAT || items.type === Type.file) + return ArrayType.file return ArrayType[items.type as keyof typeof ArrayType] }