mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-05 11:50:42 +08:00
array string validation & update
This commit is contained in:
parent
a534e58d00
commit
fe27604bd3
@ -14,6 +14,7 @@ type CodeEditorProps = {
|
|||||||
showFormatButton?: boolean
|
showFormatButton?: boolean
|
||||||
editorWrapperClassName?: string
|
editorWrapperClassName?: string
|
||||||
readOnly?: boolean
|
readOnly?: boolean
|
||||||
|
hideTopMenu?: boolean
|
||||||
} & React.HTMLAttributes<HTMLDivElement>
|
} & React.HTMLAttributes<HTMLDivElement>
|
||||||
|
|
||||||
const CodeEditor: FC<CodeEditorProps> = ({
|
const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
@ -22,6 +23,7 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
|||||||
showFormatButton = true,
|
showFormatButton = true,
|
||||||
editorWrapperClassName,
|
editorWrapperClassName,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
|
hideTopMenu = false,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -75,7 +77,8 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
|||||||
}, [onUpdate])
|
}, [onUpdate])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('flex flex-col h-full bg-components-input-bg-normal overflow-hidden', className)}>
|
<div className={classNames('flex flex-col h-full bg-components-input-bg-normal overflow-hidden', hideTopMenu && 'pt-2', className)}>
|
||||||
|
{!hideTopMenu && (
|
||||||
<div className='flex items-center justify-between pl-2 pr-1 pt-1'>
|
<div className='flex items-center justify-between pl-2 pr-1 pt-1'>
|
||||||
<div className='system-xs-semibold-uppercase py-0.5 text-text-secondary'>
|
<div className='system-xs-semibold-uppercase py-0.5 text-text-secondary'>
|
||||||
<span className='px-1 py-0.5'>JSON</span>
|
<span className='px-1 py-0.5'>JSON</span>
|
||||||
@ -102,6 +105,7 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div className={classNames('relative', editorWrapperClassName)}>
|
<div className={classNames('relative', editorWrapperClassName)}>
|
||||||
<Editor
|
<Editor
|
||||||
height='100%'
|
height='100%'
|
||||||
|
@ -1,21 +1,27 @@
|
|||||||
import React, { type FC } from 'react'
|
import React, { type FC } from 'react'
|
||||||
import CodeEditor from './code-editor'
|
import CodeEditor from './code-editor'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type SchemaEditorProps = {
|
type SchemaEditorProps = {
|
||||||
schema: string
|
schema: string
|
||||||
onUpdate: (schema: string) => void
|
onUpdate: (schema: string) => void
|
||||||
|
hideTopMenu?: boolean
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const SchemaEditor: FC<SchemaEditorProps> = ({
|
const SchemaEditor: FC<SchemaEditorProps> = ({
|
||||||
schema,
|
schema,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
|
hideTopMenu,
|
||||||
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
className='rounded-xl'
|
className={cn('rounded-xl', className)}
|
||||||
editorWrapperClassName='grow'
|
editorWrapperClassName='grow'
|
||||||
value={schema}
|
value={schema}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
|
hideTopMenu={hideTopMenu}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
8
web/app/components/workflow/variable-inspect/utils.tsx
Normal file
8
web/app/components/workflow/variable-inspect/utils.tsx
Normal file
@ -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
|
||||||
|
}
|
@ -1,7 +1,13 @@
|
|||||||
import { useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
// import { useTranslation } from 'react-i18next'
|
// import { useTranslation } from 'react-i18next'
|
||||||
import Textarea from '@/app/components/base/textarea'
|
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 = {
|
export const currentVar = {
|
||||||
id: 'var-jfkldjjfkldaf-dfhekdfj',
|
id: 'var-jfkldjjfkldaf-dfhekdfj',
|
||||||
@ -10,45 +16,94 @@ export const currentVar = {
|
|||||||
// type: 'environment',
|
// type: 'environment',
|
||||||
name: 'out_put',
|
name: 'out_put',
|
||||||
// var_type: 'string',
|
// var_type: 'string',
|
||||||
var_type: 'number',
|
// var_type: 'number',
|
||||||
// var_type: 'object',
|
// var_type: 'object',
|
||||||
// var_type: 'array[string]',
|
var_type: 'array[string]',
|
||||||
// var_type: 'array[number]',
|
// var_type: 'array[number]',
|
||||||
// var_type: 'array[object]',
|
// var_type: 'array[object]',
|
||||||
// var_type: 'file',
|
// var_type: 'file',
|
||||||
// var_type: 'array[file]',
|
// var_type: 'array[file]',
|
||||||
// value: 'tuituitui',
|
// value: 'tuituitui',
|
||||||
value: 123,
|
value: ['aaa', 'bbb', 'ccc'],
|
||||||
|
// value: {
|
||||||
|
// abc: '123',
|
||||||
|
// def: 456,
|
||||||
|
// fff: true,
|
||||||
|
// },
|
||||||
edited: true,
|
edited: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ValueContent = () => {
|
const ValueContent = () => {
|
||||||
const current = currentVar
|
const current = currentVar
|
||||||
const [value, setValue] = useState<any>(current.value ? JSON.stringify(current.value) : '')
|
const contentContainerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const errorMessageRef = useRef<HTMLDivElement>(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<any>(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<Error | null>(null)
|
||||||
|
const [validationError, setValidationError] = useState<string>('')
|
||||||
|
|
||||||
|
const handleTextChange = (value: string) => {
|
||||||
if (current.var_type === 'string')
|
if (current.var_type === 'string')
|
||||||
setValue(value)
|
setValue(value)
|
||||||
|
|
||||||
if (current.var_type === 'number') {
|
if (current.var_type === 'number') {
|
||||||
if (/^-?\d+(\.)?(\d+)?$/.test(value)) {
|
if (/^-?\d+(\.)?(\d+)?$/.test(value))
|
||||||
console.log(value)
|
|
||||||
setValue(value)
|
setValue(value)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if (current.var_type === 'object') {
|
|
||||||
// TODO update object
|
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]') {
|
if (current.var_type === 'array[string]') {
|
||||||
// TODO update array[string]
|
setJson(value)
|
||||||
|
if (arrayStringValidate(value)) {
|
||||||
|
const parsed = JSON.parse(value)
|
||||||
|
setJsonSchema(parsed)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (current.var_type === 'array[number]') {
|
if (current.var_type === 'array[number]') {
|
||||||
// TODO update array[number]
|
// TODO update array[number]
|
||||||
}
|
}
|
||||||
|
if (current.var_type === 'object') {
|
||||||
|
// TODO update object
|
||||||
|
}
|
||||||
if (current.var_type === 'array[object]') {
|
if (current.var_type === 'array[object]') {
|
||||||
// TODO update array[object]
|
// TODO update array[object]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFileChange = (value: string) => {
|
||||||
if (current.var_type === 'file') {
|
if (current.var_type === 'file') {
|
||||||
// TODO update 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 (
|
return (
|
||||||
<div className='flex h-full flex-col gap-3'>
|
<div
|
||||||
{(current.var_type === 'secret' || current.var_type === 'string' || current.var_type === 'number') && (
|
ref={contentContainerRef}
|
||||||
|
className='flex h-full flex-col'
|
||||||
|
>
|
||||||
|
<div className={cn('grow')} style={{ height: `${editorHeight}px` }}>
|
||||||
|
{showTextEditor && (
|
||||||
<Textarea
|
<Textarea
|
||||||
readOnly={current.type === 'environment'}
|
readOnly={current.type === 'environment'}
|
||||||
disabled={current.type === 'environment'}
|
disabled={current.type === 'environment'}
|
||||||
className='h-full grow'
|
className='h-full'
|
||||||
value={value as any}
|
value={value as any}
|
||||||
onChange={e => handleValueChange(e.target.value)}
|
onChange={debounce(e => handleTextChange(e.target.value))}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{showJSONEditor && (
|
||||||
|
<SchemaEditor
|
||||||
|
className='overflow-y-auto'
|
||||||
|
hideTopMenu
|
||||||
|
schema={json}
|
||||||
|
onUpdate={debounce(handleEditorChange)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{showFileEditor && (
|
||||||
|
<div>TODO</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div ref={errorMessageRef} className='shrink-0'>
|
||||||
|
{parseError && <ErrorMessage className='mt-1' message={parseError.message} />}
|
||||||
|
{validationError && <ErrorMessage className='mt-1' message={validationError} />}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user