feat:conversation variable support file array (#21174)

Co-authored-by: kino.lu <kino.lu@vipshop.com>
This commit is contained in:
kinoooolu
2025-06-19 10:26:38 +08:00
committed by GitHub
parent 17fe62cf91
commit 2bb19f85c6
5 changed files with 89 additions and 76 deletions

View File

@@ -8,4 +8,5 @@ EMPTY_VALUE_MAPPING = {
SegmentType.ARRAY_STRING: [], SegmentType.ARRAY_STRING: [],
SegmentType.ARRAY_NUMBER: [], SegmentType.ARRAY_NUMBER: [],
SegmentType.ARRAY_OBJECT: [], SegmentType.ARRAY_OBJECT: [],
SegmentType.ARRAY_FILE: [],
} }

View File

@@ -1,5 +1,6 @@
from typing import Any from typing import Any
from core.file import File
from core.variables import SegmentType from core.variables import SegmentType
from .enums import Operation from .enums import Operation
@@ -85,6 +86,8 @@ def is_input_value_valid(*, variable_type: SegmentType, operation: Operation, va
return isinstance(value, int | float) return isinstance(value, int | float)
case SegmentType.ARRAY_OBJECT if operation == Operation.APPEND: case SegmentType.ARRAY_OBJECT if operation == Operation.APPEND:
return isinstance(value, dict) return isinstance(value, dict)
case SegmentType.ARRAY_FILE if operation == Operation.APPEND:
return isinstance(value, File)
# Array & Extend / Overwrite # Array & Extend / Overwrite
case SegmentType.ARRAY_ANY if operation in {Operation.EXTEND, Operation.OVER_WRITE}: case SegmentType.ARRAY_ANY if operation in {Operation.EXTEND, Operation.OVER_WRITE}:
@@ -95,6 +98,8 @@ def is_input_value_valid(*, variable_type: SegmentType, operation: Operation, va
return isinstance(value, list) and all(isinstance(item, int | float) for item in value) return isinstance(value, list) and all(isinstance(item, int | float) for item in value)
case SegmentType.ARRAY_OBJECT if operation in {Operation.EXTEND, Operation.OVER_WRITE}: case SegmentType.ARRAY_OBJECT if operation in {Operation.EXTEND, Operation.OVER_WRITE}:
return isinstance(value, list) and all(isinstance(item, dict) for item in value) return isinstance(value, list) and all(isinstance(item, dict) for item in value)
case SegmentType.ARRAY_FILE if operation in {Operation.EXTEND, Operation.OVER_WRITE}:
return isinstance(value, list) and all(isinstance(item, File) for item in value)
case _: case _:
return False return False

View File

@@ -101,6 +101,8 @@ def _build_variable_from_mapping(*, mapping: Mapping[str, Any], selector: Sequen
result = ArrayNumberVariable.model_validate(mapping) result = ArrayNumberVariable.model_validate(mapping)
case SegmentType.ARRAY_OBJECT if isinstance(value, list): case SegmentType.ARRAY_OBJECT if isinstance(value, list):
result = ArrayObjectVariable.model_validate(mapping) result = ArrayObjectVariable.model_validate(mapping)
case SegmentType.ARRAY_FILE if isinstance(value, list):
result = ArrayFileVariable.model_validate(mapping)
case _: case _:
raise VariableError(f"not supported value type {value_type}") raise VariableError(f"not supported value type {value_type}")
if result.size > dify_config.MAX_VARIABLE_SIZE: if result.size > dify_config.MAX_VARIABLE_SIZE:

View File

@@ -37,6 +37,7 @@ const typeList = [
ChatVarType.ArrayString, ChatVarType.ArrayString,
ChatVarType.ArrayNumber, ChatVarType.ArrayNumber,
ChatVarType.ArrayObject, ChatVarType.ArrayObject,
ChatVarType.ArrayFile,
] ]
const objectPlaceholder = `# example const objectPlaceholder = `# example
@@ -127,6 +128,7 @@ const ChatVariableModal = ({
case ChatVarType.ArrayString: case ChatVarType.ArrayString:
case ChatVarType.ArrayNumber: case ChatVarType.ArrayNumber:
case ChatVarType.ArrayObject: case ChatVarType.ArrayObject:
case ChatVarType.ArrayFile:
return value?.filter(Boolean) || [] return value?.filter(Boolean) || []
} }
} }
@@ -294,84 +296,86 @@ const ChatVariableModal = ({
</div> </div>
</div> </div>
{/* default value */} {/* default value */}
<div className='mb-4'> {type !== ChatVarType.ArrayFile && (
<div className='system-sm-semibold mb-1 flex h-6 items-center justify-between text-text-secondary'> <div className='mb-4'>
<div>{t('workflow.chatVariable.modal.value')}</div> <div className='system-sm-semibold mb-1 flex h-6 items-center justify-between text-text-secondary'>
{(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) && ( <div>{t('workflow.chatVariable.modal.value')}</div>
<Button {(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) && (
variant='ghost' <Button
size='small' variant='ghost'
className='text-text-tertiary' size='small'
onClick={() => handleEditorChange(!editInJSON)} className='text-text-tertiary'
> onClick={() => handleEditorChange(!editInJSON)}
{editInJSON ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />} >
{editInJSON ? t('workflow.chatVariable.modal.oneByOne') : t('workflow.chatVariable.modal.editInJSON')} {editInJSON ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />}
</Button> {editInJSON ? t('workflow.chatVariable.modal.oneByOne') : t('workflow.chatVariable.modal.editInJSON')}
)} </Button>
{type === ChatVarType.Object && ( )}
<Button {type === ChatVarType.Object && (
variant='ghost' <Button
size='small' variant='ghost'
className='text-text-tertiary' size='small'
onClick={() => handleEditorChange(!editInJSON)} className='text-text-tertiary'
> onClick={() => handleEditorChange(!editInJSON)}
{editInJSON ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />} >
{editInJSON ? t('workflow.chatVariable.modal.editInForm') : t('workflow.chatVariable.modal.editInJSON')} {editInJSON ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />}
</Button> {editInJSON ? t('workflow.chatVariable.modal.editInForm') : t('workflow.chatVariable.modal.editInJSON')}
)} </Button>
</div> )}
<div className='flex'> </div>
{type === ChatVarType.String && ( <div className='flex'>
// Input will remove \n\r, so use Textarea just like description area {type === ChatVarType.String && (
<textarea // Input will remove \n\r, so use Textarea just like description area
className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs' <textarea
value={value} className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'
placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''} value={value}
onChange={e => setValue(e.target.value)} placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''}
/> onChange={e => setValue(e.target.value)}
)}
{type === ChatVarType.Number && (
<Input
placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''}
value={value}
onChange={e => setValue(Number(e.target.value))}
type='number'
/>
)}
{type === ChatVarType.Object && !editInJSON && (
<ObjectValueList
list={objectValue}
onChange={setObjectValue}
/>
)}
{type === ChatVarType.ArrayString && !editInJSON && (
<ArrayValueList
isString
list={value || [undefined]}
onChange={setValue}
/>
)}
{type === ChatVarType.ArrayNumber && !editInJSON && (
<ArrayValueList
isString={false}
list={value || [undefined]}
onChange={setValue}
/>
)}
{editInJSON && (
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}>
<CodeEditor
isExpand
noWrapper
language={CodeLanguage.json}
value={editorContent}
placeholder={<div className='whitespace-pre'>{placeholder}</div>}
onChange={handleEditorValueChange}
/> />
</div> )}
)} {type === ChatVarType.Number && (
<Input
placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''}
value={value}
onChange={e => setValue(Number(e.target.value))}
type='number'
/>
)}
{type === ChatVarType.Object && !editInJSON && (
<ObjectValueList
list={objectValue}
onChange={setObjectValue}
/>
)}
{type === ChatVarType.ArrayString && !editInJSON && (
<ArrayValueList
isString
list={value || [undefined]}
onChange={setValue}
/>
)}
{type === ChatVarType.ArrayNumber && !editInJSON && (
<ArrayValueList
isString={false}
list={value || [undefined]}
onChange={setValue}
/>
)}
{editInJSON && (
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}>
<CodeEditor
isExpand
noWrapper
language={CodeLanguage.json}
value={editorContent}
placeholder={<div className='whitespace-pre'>{placeholder}</div>}
onChange={handleEditorValueChange}
/>
</div>
)}
</div>
</div> </div>
</div> )}
{/* description */} {/* description */}
<div className=''> <div className=''>
<div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.chatVariable.modal.description')}</div> <div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.chatVariable.modal.description')}</div>

View File

@@ -5,4 +5,5 @@ export enum ChatVarType {
ArrayString = 'array[string]', ArrayString = 'array[string]',
ArrayNumber = 'array[number]', ArrayNumber = 'array[number]',
ArrayObject = 'array[object]', ArrayObject = 'array[object]',
ArrayFile = 'array[file]',
} }