Skip to content

Commit 39ec2b3

Browse files
committed
feat: Add file type support to LLM node JSON schema editor
1 parent ea37904 commit 39ec2b3

File tree

5 files changed

+87
-17
lines changed

5 files changed

+87
-17
lines changed

web/app/components/workflow/nodes/_base/components/variable/utils.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,7 @@ import {
6060
import { VAR_REGEX } from '@/config'
6161
import { AppModeEnum } from '@/types/app'
6262
import { OUTPUT_FILE_SUB_VARIABLES } from '../../../constants'
63-
import {
64-
65-
Type,
66-
} from '../../../llm/types'
63+
import { FILE_REF_FORMAT, Type } from '../../../llm/types'
6764
import { VarType as ToolVarType } from '../../../tool/types'
6865

6966
export const isSystemVar = (valueSelector: ValueSelector) => {
@@ -122,7 +119,16 @@ export const inputVarTypeToVarType = (type: InputVarType): VarType => {
122119
)
123120
}
124121

125-
const structTypeToVarType = (type: Type, isArray?: boolean): VarType => {
122+
const structTypeToVarType = (
123+
type: Type,
124+
isArray?: boolean,
125+
format?: string,
126+
itemsFormat?: string,
127+
): VarType => {
128+
if (isArray && itemsFormat === FILE_REF_FORMAT)
129+
return VarType.arrayFile
130+
if (!isArray && format === FILE_REF_FORMAT)
131+
return VarType.file
126132
if (isArray) {
127133
return (
128134
(
@@ -175,6 +181,7 @@ const findExceptVarInStructuredProperties = (
175181
const isObj = item.type === Type.object
176182
const isArray = item.type === Type.array
177183
const arrayType = item.items?.type
184+
const arrayFormat = item.items?.format
178185

179186
if (
180187
!isObj
@@ -184,6 +191,8 @@ const findExceptVarInStructuredProperties = (
184191
type: structTypeToVarType(
185192
isArray ? arrayType! : item.type,
186193
isArray,
194+
item.format,
195+
arrayFormat,
187196
),
188197
},
189198
[key],
@@ -215,6 +224,7 @@ const findExceptVarInStructuredOutput = (
215224
const isObj = item.type === Type.object
216225
const isArray = item.type === Type.array
217226
const arrayType = item.items?.type
227+
const arrayFormat = item.items?.format
218228
if (
219229
!isObj
220230
&& !filterVar(
@@ -223,6 +233,8 @@ const findExceptVarInStructuredOutput = (
223233
type: structTypeToVarType(
224234
isArray ? arrayType! : item.type,
225235
isArray,
236+
item.format,
237+
arrayFormat,
226238
),
227239
},
228240
[key],
@@ -1144,8 +1156,14 @@ export const getVarType = ({
11441156
return
11451157

11461158
currProperties = currProperties.properties[key]
1147-
if (isLast)
1148-
type = structTypeToVarType(currProperties?.type)
1159+
if (isLast) {
1160+
if (currProperties?.format === FILE_REF_FORMAT)
1161+
type = VarType.file
1162+
else if (currProperties?.type === Type.array && currProperties?.items?.format === FILE_REF_FORMAT)
1163+
type = VarType.arrayFile
1164+
else
1165+
type = structTypeToVarType(currProperties?.type)
1166+
}
11491167
})
11501168
return type
11511169
}
@@ -1946,15 +1964,20 @@ const varToValueSelectorList = (
19461964
Object.keys(
19471965
(v.children as StructuredOutput)?.schema?.properties || {},
19481966
).forEach((key) => {
1949-
const type = (v.children as StructuredOutput)?.schema?.properties[key].type
1967+
const schemaProperty = (v.children as StructuredOutput)?.schema?.properties[key]
1968+
const type = schemaProperty?.type
19501969
const isArray = type === Type.array
1951-
const arrayType = (v.children as StructuredOutput)?.schema?.properties[
1952-
key
1953-
].items?.type
1970+
const arrayType = schemaProperty?.items?.type
1971+
const arrayFormat = schemaProperty?.items?.format
19541972
varToValueSelectorList(
19551973
{
19561974
variable: key,
1957-
type: structTypeToVarType(isArray ? arrayType! : type, isArray),
1975+
type: structTypeToVarType(
1976+
isArray ? arrayType! : type,
1977+
isArray,
1978+
schemaProperty?.format,
1979+
arrayFormat,
1980+
),
19581981
},
19591982
[...parentValueSelector, v.variable],
19601983
res,

web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,21 @@ const TYPE_OPTIONS = [
4444
{ value: Type.number, text: 'number' },
4545
{ value: Type.boolean, text: 'boolean' },
4646
{ value: Type.object, text: 'object' },
47+
{ value: Type.file, text: 'file' },
4748
{ value: ArrayType.string, text: 'array[string]' },
4849
{ value: ArrayType.number, text: 'array[number]' },
4950
{ value: ArrayType.object, text: 'array[object]' },
51+
{ value: ArrayType.file, text: 'array[file]' },
5052
]
5153

5254
const MAXIMUM_DEPTH_TYPE_OPTIONS = [
5355
{ value: Type.string, text: 'string' },
5456
{ value: Type.number, text: 'number' },
5557
{ value: Type.boolean, text: 'boolean' },
58+
{ value: Type.file, text: 'file' },
5659
{ value: ArrayType.string, text: 'array[string]' },
5760
{ value: ArrayType.number, text: 'array[number]' },
61+
{ value: ArrayType.file, text: 'array[file]' },
5862
]
5963

6064
const EditCard: FC<EditCardProps> = ({

web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { EditData } from './edit-card'
44
import { noop } from 'es-toolkit/function'
55
import { produce } from 'immer'
66
import Toast from '@/app/components/base/toast'
7-
import { ArrayType, Type } from '../../../types'
7+
import { ArrayType, FILE_REF_FORMAT, Type } from '../../../types'
88
import { findPropertyWithPath } from '../../../utils'
99
import { useMittContext } from './context'
1010
import { useVisualEditorStore } from './store'
@@ -133,13 +133,18 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
133133
}
134134
if (schema.type === Type.array)
135135
delete schema.items
136+
delete schema.format
136137
switch (newType) {
137138
case Type.object:
138139
schema.type = Type.object
139140
schema.properties = {}
140141
schema.required = []
141142
schema.additionalProperties = false
142143
break
144+
case Type.file:
145+
schema.type = Type.string
146+
schema.format = FILE_REF_FORMAT
147+
break
143148
case ArrayType.string:
144149
schema.type = Type.array
145150
schema.items = {
@@ -167,6 +172,13 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
167172
additionalProperties: false,
168173
}
169174
break
175+
case ArrayType.file:
176+
schema.type = Type.array
177+
schema.items = {
178+
type: Type.string,
179+
format: FILE_REF_FORMAT,
180+
}
181+
break
170182
default:
171183
schema.type = newType as Type
172184
}
@@ -309,13 +321,18 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
309321
}
310322
if (schema.type === Type.array)
311323
delete schema.items
324+
delete schema.format
312325
switch (newType) {
313326
case Type.object:
314327
schema.type = Type.object
315328
schema.properties = {}
316329
schema.required = []
317330
schema.additionalProperties = false
318331
break
332+
case Type.file:
333+
schema.type = Type.string
334+
schema.format = FILE_REF_FORMAT
335+
break
319336
case ArrayType.string:
320337
schema.type = Type.array
321338
schema.items = {
@@ -343,6 +360,13 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
343360
additionalProperties: false,
344361
}
345362
break
363+
case ArrayType.file:
364+
schema.type = Type.array
365+
schema.items = {
366+
type: Type.string,
367+
format: FILE_REF_FORMAT,
368+
}
369+
break
346370
default:
347371
schema.type = newType as Type
348372
}
@@ -398,13 +422,18 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
398422
}
399423
if (schema.type === Type.array)
400424
delete schema.items
425+
delete schema.format
401426
switch (newType) {
402427
case Type.object:
403428
schema.type = Type.object
404429
schema.properties = {}
405430
schema.required = []
406431
schema.additionalProperties = false
407432
break
433+
case Type.file:
434+
schema.type = Type.string
435+
schema.format = FILE_REF_FORMAT
436+
break
408437
case ArrayType.string:
409438
schema.type = Type.array
410439
schema.items = {
@@ -432,6 +461,13 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
432461
additionalProperties: false,
433462
}
434463
break
464+
case ArrayType.file:
465+
schema.type = Type.array
466+
schema.items = {
467+
type: Type.string,
468+
format: FILE_REF_FORMAT,
469+
}
470+
break
435471
default:
436472
schema.type = newType as Type
437473
}

web/app/components/workflow/nodes/llm/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export type LLMNodeType = CommonNodeType & {
2020
reasoning_format?: 'tagged' | 'separated'
2121
}
2222

23+
export const FILE_REF_FORMAT = 'dify-file-ref'
24+
2325
export enum Type {
2426
string = 'string',
2527
number = 'number',
@@ -38,12 +40,13 @@ export enum ArrayType {
3840
number = 'array[number]',
3941
boolean = 'array[boolean]',
4042
object = 'array[object]',
43+
file = 'array[file]',
4144
}
4245

4346
export type TypeWithArray = Type | ArrayType
4447

4548
type ArrayItemType = Exclude<Type, Type.array>
46-
export type ArrayItems = Omit<Field, 'type'> & { type: ArrayItemType }
49+
export type ArrayItems = Omit<Field, 'type' | 'format'> & { type: ArrayItemType; format?: string }
4750

4851
export type SchemaEnumType = string[] | number[]
4952

@@ -54,6 +57,7 @@ export type Field = {
5457
}
5558
required?: string[] // Key of required properties in object
5659
description?: string
60+
format?: string
5761
items?: ArrayItems // Array has items. Define the item type
5862
enum?: SchemaEnumType // Enum values
5963
additionalProperties?: false // Required in object by api. Just set false

web/app/components/workflow/nodes/llm/utils.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,24 @@ import type { ValidationError } from 'jsonschema'
22
import type { ArrayItems, Field, LLMNodeType } from './types'
33
import { z } from 'zod'
44
import { draft07Validator, forbidBooleanProperties } from '@/utils/validators'
5-
import { ArrayType, Type } from './types'
5+
import { ArrayType, FILE_REF_FORMAT, Type } from './types'
66

77
export const checkNodeValid = (_payload: LLMNodeType) => {
88
return true
99
}
1010

1111
export const getFieldType = (field: Field) => {
12-
const { type, items, enum: enums } = field
12+
const { type, items, enum: enums, format } = field
13+
if (format === FILE_REF_FORMAT)
14+
return Type.file
1315
if (field.schemaType === 'file')
1416
return Type.file
1517
if (enums && enums.length > 0)
1618
return Type.enumType
1719
if (type !== Type.array || !items)
1820
return type
19-
21+
if (items.format === FILE_REF_FORMAT || items.type === Type.file)
22+
return ArrayType.file
2023
return ArrayType[items.type as keyof typeof ArrayType]
2124
}
2225

0 commit comments

Comments
 (0)