diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx index a3c2552b45..2058a86a0d 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx @@ -14,6 +14,7 @@ type CodeEditorProps = { showFormatButton?: boolean editorWrapperClassName?: string readOnly?: boolean + hideTopMenu?: boolean } & React.HTMLAttributes const CodeEditor: FC = ({ @@ -22,6 +23,7 @@ const CodeEditor: FC = ({ showFormatButton = true, editorWrapperClassName, readOnly = false, + hideTopMenu = false, className, }) => { const { t } = useTranslation() @@ -75,33 +77,35 @@ const CodeEditor: FC = ({ }, [onUpdate]) return ( -
-
-
- JSON -
-
- {showFormatButton && ( - +
+ {!hideTopMenu && ( +
+
+ JSON +
+
+ {showFormatButton && ( + + + + )} + - )} - - - +
-
+ )}
void + hideTopMenu?: boolean + className?: string } const SchemaEditor: FC = ({ schema, onUpdate, + hideTopMenu, + className, }) => { return ( ) } diff --git a/web/app/components/workflow/variable-inspect/utils.tsx b/web/app/components/workflow/variable-inspect/utils.tsx new file mode 100644 index 0000000000..521a6c03ec --- /dev/null +++ b/web/app/components/workflow/variable-inspect/utils.tsx @@ -0,0 +1,8 @@ +import { z } from 'zod' + +const arrayStringSchemaParttern = z.array(z.string()) + +export const validateArrayString = (schema: any) => { + const result = arrayStringSchemaParttern.safeParse(schema) + return result +} diff --git a/web/app/components/workflow/variable-inspect/value-content.tsx b/web/app/components/workflow/variable-inspect/value-content.tsx index 81115b1234..cce0e9b6b9 100644 --- a/web/app/components/workflow/variable-inspect/value-content.tsx +++ b/web/app/components/workflow/variable-inspect/value-content.tsx @@ -1,7 +1,13 @@ -import { useState } from 'react' +import { useEffect, useRef, useState } from 'react' // import { useTranslation } from 'react-i18next' import Textarea from '@/app/components/base/textarea' -// import cn from '@/utils/classnames' +import SchemaEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor' +import ErrorMessage from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message' +import { + validateArrayString, +} from '@/app/components/workflow/variable-inspect/utils' +import { debounce } from 'lodash-es' +import cn from '@/utils/classnames' export const currentVar = { id: 'var-jfkldjjfkldaf-dfhekdfj', @@ -10,45 +16,94 @@ export const currentVar = { // type: 'environment', name: 'out_put', // var_type: 'string', - var_type: 'number', + // var_type: 'number', // var_type: 'object', - // var_type: 'array[string]', + var_type: 'array[string]', // var_type: 'array[number]', // var_type: 'array[object]', // var_type: 'file', // var_type: 'array[file]', // value: 'tuituitui', - value: 123, + value: ['aaa', 'bbb', 'ccc'], + // value: { + // abc: '123', + // def: 456, + // fff: true, + // }, edited: true, } const ValueContent = () => { const current = currentVar - const [value, setValue] = useState(current.value ? JSON.stringify(current.value) : '') + const contentContainerRef = useRef(null) + const errorMessageRef = useRef(null) + const [editorHeight, setEditorHeight] = useState(0) + const showTextEditor = current.var_type === 'secret' || current.var_type === 'string' || current.var_type === 'number' + const showJSONEditor = current.var_type === 'object' || current.var_type === 'array[string]' || current.var_type === 'array[number]' || current.var_type === 'array[object]' + const showFileEditor = current.var_type === 'file' || current.var_type === 'array[file]' - const handleValueChange = (value: string) => { + const [value, setValue] = useState(current.value ? JSON.stringify(current.value) : '') + const [jsonSchema, setJsonSchema] = useState(current.value || null) + const [json, setJson] = useState(JSON.stringify(jsonSchema, null, 2)) + const [parseError, setParseError] = useState(null) + const [validationError, setValidationError] = useState('') + + const handleTextChange = (value: string) => { if (current.var_type === 'string') setValue(value) if (current.var_type === 'number') { - if (/^-?\d+(\.)?(\d+)?$/.test(value)) { - console.log(value) + if (/^-?\d+(\.)?(\d+)?$/.test(value)) setValue(value) + } + } + + const arrayStringValidate = (value: string) => { + try { + const newJSONSchema = JSON.parse(value) + setParseError(null) + const result = validateArrayString(newJSONSchema) + if (!result.success) { + setValidationError(result.error.message) + 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 (current.var_type === 'array[string]') { + setJson(value) + if (arrayStringValidate(value)) { + const parsed = JSON.parse(value) + setJsonSchema(parsed) } return } - if (current.var_type === 'object') { - // TODO update object - } - if (current.var_type === 'array[string]') { - // TODO update array[string] - } if (current.var_type === 'array[number]') { // TODO update array[number] } + if (current.var_type === 'object') { + // TODO update object + } if (current.var_type === 'array[object]') { // TODO update array[object] } + } + + const handleFileChange = (value: string) => { if (current.var_type === 'file') { // TODO update file } @@ -57,17 +112,54 @@ const ValueContent = () => { } } + // get editor height + useEffect(() => { + if (contentContainerRef.current && errorMessageRef.current) { + const errorMessageObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + const { inlineSize } = entry.borderBoxSize[0] + const height = (contentContainerRef.current as any).clientHeight - inlineSize + setEditorHeight(height) + } + }) + errorMessageObserver.observe(errorMessageRef.current) + return () => { + errorMessageObserver.disconnect() + } + } + }, [errorMessageRef.current, setEditorHeight]) + return ( -
- {(current.var_type === 'secret' || current.var_type === 'string' || current.var_type === 'number') && ( -