import type { VarInspectValue } from './value-types' import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { VarInInspect } from '@/types/workflow' import { useDebounceFn } from 'ahooks' import * as React from 'react' import { useEffect, useMemo, useRef, useState } from 'react' import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' import { getProcessedFiles, getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' import Textarea from '@/app/components/base/textarea' import ErrorMessage from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message' import SchemaEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor' import { checkJsonSchemaDepth, getValidationErrorMessage, validateSchemaAgainstDraft7, } from '@/app/components/workflow/nodes/llm/utils' import { useStore } from '@/app/components/workflow/store' import { SupportUploadFileTypes, VarType } from '@/app/components/workflow/types' import { validateJSONSchema, } from '@/app/components/workflow/variable-inspect/utils' import { JSON_SCHEMA_MAX_DEPTH } from '@/config' import { TransferMethod } from '@/types/app' import { VarInInspectType } from '@/types/workflow' import { cn } from '@/utils/classnames' import { PreviewMode } from '../../base/features/types' import BoolValue from '../panel/chat-variable-panel/components/bool-value' import DisplayContent from './display-content' import LargeDataAlert from './large-data-alert' import { CHUNK_SCHEMA_TYPES, PreviewType } from './types' type Props = { currentVar: VarInInspect handleValueChange: (varId: string, value: VarInspectValue) => void isTruncated: boolean } const textValueTypes = new Set([VarType.secret, VarType.string, VarType.number]) const jsonValueTypes = new Set([ VarType.object, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject, VarType.arrayMessage, VarType.arrayAny, ]) const fileValueTypes = new Set([VarType.file, VarType.arrayFile]) type EditorState = { textValue: string | number jsonValue: string fileValue: FileEntity[] } const formatFileValue = (value: VarInInspect, isSysFiles: boolean): FileEntity[] => { if (value.value_type === VarType.file) return value.value ? getProcessedFilesFromResponse([value.value]) : [] if (value.value_type === VarType.arrayFile || (value.type === VarInInspectType.system && isSysFiles)) return value.value && value.value.length > 0 ? getProcessedFilesFromResponse(value.value) : [] return [] } const ValueContent = ({ currentVar, handleValueChange, isTruncated, }: Props) => { const contentContainerRef = useRef(null) const errorMessageRef = useRef(null) const [editorHeight, setEditorHeight] = useState(0) const showTextEditor = textValueTypes.has(currentVar.value_type) const showBoolEditor = typeof currentVar.value === 'boolean' const showBoolArrayEditor = Array.isArray(currentVar.value) && currentVar.value.every(v => typeof v === 'boolean') const isSysFiles = currentVar.type === VarInInspectType.system && currentVar.name === 'files' const showJSONEditor = !isSysFiles && jsonValueTypes.has(currentVar.value_type) const showFileEditor = isSysFiles || fileValueTypes.has(currentVar.value_type) const textEditorDisabled = currentVar.type === VarInInspectType.environment || (currentVar.type === VarInInspectType.system && currentVar.name !== 'query' && currentVar.name !== 'files') const JSONEditorDisabled = currentVar.value_type === VarType.arrayAny const fileUploadConfig = useStore(s => s.fileUploadConfig) const hasChunks = useMemo(() => { if (!currentVar.schemaType) return false return CHUNK_SCHEMA_TYPES.includes(currentVar.schemaType) }, [currentVar.schemaType]) const initialEditorState = useMemo(() => { const textValue = showTextEditor ? ( currentVar.value_type === VarType.number ? JSON.stringify(currentVar.value) : (currentVar.value || '') ) : '' const jsonValue = showJSONEditor ? (currentVar.value != null ? JSON.stringify(currentVar.value, null, 2) : '') : '' const fileValue = showFileEditor ? formatFileValue(currentVar, isSysFiles) : [] return { textValue, jsonValue, fileValue, } }, [currentVar, isSysFiles, showFileEditor, showJSONEditor, showTextEditor]) const [editorState, setEditorState] = useState(initialEditorState) const [parseError, setParseError] = useState(null) const [validationError, setValidationError] = useState('') const { textValue, jsonValue, fileValue } = editorState const { run: debounceValueChange } = useDebounceFn(handleValueChange, { wait: 500 }) // update default value when id or value changed useEffect(() => { setEditorState(initialEditorState) }, [initialEditorState]) const handleTextChange = (value: string) => { if (isTruncated) return if (currentVar.value_type === VarType.string) setEditorState(prev => ({ ...prev, textValue: value })) if (currentVar.value_type === VarType.number) { if (/^-?\d+(\.)?(\d+)?$/.test(value)) setEditorState(prev => ({ ...prev, textValue: Number.parseFloat(value) })) } const newValue = currentVar.value_type === VarType.number ? Number.parseFloat(value) : value debounceValueChange(currentVar.id, newValue) } const jsonValueValidate = (value: string, type: string) => { try { const newJSONSchema = JSON.parse(value) setParseError(null) const result = validateJSONSchema(newJSONSchema, type) if (!result.success) { setValidationError(result.error.message) return false } if (type === 'object' || type === 'array[object]') { const schemaDepth = checkJsonSchemaDepth(newJSONSchema) if (schemaDepth > JSON_SCHEMA_MAX_DEPTH) { setValidationError(`Schema exceeds maximum depth of ${JSON_SCHEMA_MAX_DEPTH}.`) return false } const validationErrors = validateSchemaAgainstDraft7(newJSONSchema) if (validationErrors.length > 0) { setValidationError(getValidationErrorMessage(validationErrors)) return false } } setValidationError('') return true } catch (error) { setValidationError('') if (error instanceof Error) { setParseError(error) return false } else { setParseError(new Error('Invalid JSON')) return false } } } const handleEditorChange = (value: string) => { if (isTruncated) return setEditorState(prev => ({ ...prev, jsonValue: value })) if (jsonValueValidate(value, currentVar.value_type)) { const parsed = JSON.parse(value) debounceValueChange(currentVar.id, parsed) } } type ProcessedFile = ReturnType[number] const fileValueValidate = (fileList: ProcessedFile[]) => fileList.every(file => file.upload_file_id) const handleFileChange = (value: FileEntity[]) => { setEditorState(prev => ({ ...prev, fileValue: value })) const processedFiles = getProcessedFiles(value) // check every file upload progress // invoke update api after every file uploaded if (!fileValueValidate(processedFiles)) return if (currentVar.value_type === VarType.file) debounceValueChange(currentVar.id, processedFiles[0]) if (currentVar.value_type === VarType.arrayFile || isSysFiles) debounceValueChange(currentVar.id, processedFiles) } // get editor height useEffect(() => { if (contentContainerRef.current && errorMessageRef.current) { const errorMessageObserver = new ResizeObserver((entries) => { for (const entry of entries) { const borderBoxSize = Array.isArray(entry.borderBoxSize) ? entry.borderBoxSize[0] : entry.borderBoxSize const errorHeight = borderBoxSize?.blockSize ?? entry.contentRect.height const containerHeight = contentContainerRef.current?.clientHeight ?? 0 setEditorHeight(Math.max(containerHeight - errorHeight, 0)) } }) errorMessageObserver.observe(errorMessageRef.current) return () => { errorMessageObserver.disconnect() } } }, [setEditorHeight]) return (
{showTextEditor && ( <> {isTruncated && } { currentVar.value_type === VarType.string ? ( ) : (