feat: Add file type support to LLM node JSON schema editor

This commit is contained in:
zhsama
2026-01-27 19:39:32 +08:00
parent ea37904c75
commit 39ec2b3277
5 changed files with 87 additions and 17 deletions

View File

@@ -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,

View File

@@ -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<EditCardProps> = ({

View File

@@ -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
}

View File

@@ -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<Type, Type.array>
export type ArrayItems = Omit<Field, 'type'> & { type: ArrayItemType }
export type ArrayItems = Omit<Field, 'type' | 'format'> & { 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

View File

@@ -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]
}