From 4333820aa66de60a775d76868b7e4ba8f0ef60a7 Mon Sep 17 00:00:00 2001 From: twwu Date: Fri, 14 Mar 2025 16:43:43 +0800 Subject: [PATCH] feat: enhance JSON Schema visual editor with new components and translations --- web/app/components/base/textarea/index.tsx | 5 +- .../json-schema-config-modal/index.tsx | 40 +- .../generated-result.tsx | 4 +- .../json-schema-generator/index.tsx | 34 +- .../visual-editor/card.tsx | 46 +++ .../visual-editor/edit-card/actions.tsx | 56 +++ .../edit-card/advanced-actions.tsx | 28 ++ .../edit-card/advanced-options.tsx | 83 ++++ .../visual-editor/edit-card/index.tsx | 179 +++++++++ .../edit-card/required-switch.tsx | 25 ++ .../visual-editor/edit-card/type-selector.tsx | 69 ++++ .../visual-editor/index.tsx | 27 ++ .../visual-editor/schema-node.tsx | 375 ++++++++++++++++++ .../components/workflow/nodes/llm/types.ts | 23 +- .../components/workflow/nodes/llm/utils.ts | 10 + web/app/dev-preview/page.tsx | 84 +++- web/i18n/en-US/workflow.ts | 7 + web/i18n/zh-Hans/workflow.ts | 7 + web/tailwind-common-config.ts | 1 + web/themes/manual-dark.css | 3 +- web/themes/manual-light.css | 3 +- 21 files changed, 1047 insertions(+), 62 deletions(-) create mode 100644 web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/card.tsx create mode 100644 web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx create mode 100644 web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx create mode 100644 web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-options.tsx create mode 100644 web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx create mode 100644 web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/required-switch.tsx create mode 100644 web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/type-selector.tsx create mode 100644 web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx create mode 100644 web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx diff --git a/web/app/components/base/textarea/index.tsx b/web/app/components/base/textarea/index.tsx index 13e7af9603..5c72461532 100644 --- a/web/app/components/base/textarea/index.tsx +++ b/web/app/components/base/textarea/index.tsx @@ -8,8 +8,9 @@ const textareaVariants = cva( { variants: { size: { - regular: 'px-3 radius-md system-sm-regular', - large: 'px-4 radius-lg system-md-regular', + small: 'py-1 rounded-md system-xs-regular', + regular: 'px-3 rounded-md system-sm-regular', + large: 'px-4 rounded-lg system-md-regular', }, }, defaultVariants: { diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx index 39bafb9abb..befed2ef1d 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx @@ -1,6 +1,6 @@ import React, { type FC, useCallback, useState } from 'react' import Modal from '../../../../../base/modal' -import { type StructuredOutput, Type } from '../../types' +import { type Field, Type } from '../../types' import { RiBracesLine, RiCloseLine, RiExternalLinkLine, RiTimelineView } from '@remixicon/react' import { SegmentedControl } from '../../../../../base/segmented-control' import JsonSchemaGenerator from './json-schema-generator' @@ -8,11 +8,12 @@ import Divider from '@/app/components/base/divider' import JsonImporter from './json-importer' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' +import VisualEditor from './visual-editor' type JsonSchemaConfigModalProps = { isShow: boolean - defaultSchema: StructuredOutput - onSave: (schema: StructuredOutput) => void + defaultSchema?: Field + onSave: (schema: Field) => void onClose: () => void } @@ -21,18 +22,16 @@ enum SchemaView { JsonSchema = 'jsonSchema', } -const options = [ +const VIEW_TABS = [ { Icon: RiTimelineView, text: 'Visual Editor', value: SchemaView.VisualEditor }, { Icon: RiBracesLine, text: 'JSON Schema', value: SchemaView.JsonSchema }, ] -const DEFAULT_SCHEMA = { - schema: { - type: Type.object, - properties: {}, - required: [], - additionalProperties: false, - }, +const DEFAULT_SCHEMA: Field = { + type: Type.object, + properties: {}, + required: [], + additionalProperties: false, } const JsonSchemaConfigModal: FC = ({ @@ -54,6 +53,10 @@ const JsonSchemaConfigModal: FC = ({ const handleSubmit = useCallback(() => {}, []) + const handleUpdateSchema = useCallback((schema: Field) => { + setJsonSchema(schema) + }, []) + const handleResetDefaults = useCallback(() => { setJsonSchema(defaultSchema || DEFAULT_SCHEMA) }, [defaultSchema]) @@ -73,7 +76,7 @@ const JsonSchemaConfigModal: FC = ({ onClose={onClose} className='max-w-[960px] h-[800px] p-0' > -
+
{/* Header */}
@@ -87,7 +90,7 @@ const JsonSchemaConfigModal: FC = ({
{/* Tab */} - options={options} + options={VIEW_TABS} value={currentTab} onChange={(value: SchemaView) => { setCurrentTab(value) @@ -108,8 +111,15 @@ const JsonSchemaConfigModal: FC = ({
- {currentTab === SchemaView.VisualEditor &&
Visual Editor
} - {currentTab === SchemaView.JsonSchema &&
JSON Schema
} + {currentTab === SchemaView.VisualEditor && ( + + )} + {currentTab === SchemaView.JsonSchema && ( +
JSON Schema
+ )}
{/* Footer */}
diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx index f068b9f25b..5a50a4afb8 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx @@ -1,5 +1,5 @@ import React, { type FC, useCallback, useRef, useState } from 'react' -import type { StructuredOutput } from '../../../types' +import type { SchemaRoot } from '../../../types' import { RiArrowLeftLine, RiClipboardLine, RiCloseLine, RiSparklingLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' import Editor from '@monaco-editor/react' @@ -7,7 +7,7 @@ import copy from 'copy-to-clipboard' import Button from '@/app/components/base/button' type GeneratedResultProps = { - schema: StructuredOutput + schema: SchemaRoot onBack: () => void onRegenerate: () => void onClose: () => void diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx index de5343ff3d..1ce1b79f3f 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx @@ -1,5 +1,5 @@ import React, { type FC, useCallback, useState } from 'react' -import { type StructuredOutput, Type } from '../../../types' +import { type SchemaRoot, Type } from '../../../types' import { PortalToFollowElem, PortalToFollowElemContent, @@ -13,7 +13,7 @@ import PromptEditor from './prompt-editor' import GeneratedResult from './generated-result' type JsonSchemaGeneratorProps = { - onApply: (schema: StructuredOutput) => void + onApply: (schema: SchemaRoot) => void crossAxisOffset?: number } @@ -30,7 +30,7 @@ export const JsonSchemaGenerator: FC = ({ const { theme } = useTheme() const [view, setView] = useState(GeneratorView.promptEditor) const [instruction, setInstruction] = useState('') - const [schema, setSchema] = useState(null) + const [schema, setSchema] = useState(null) const SchemaGenerator = theme === Theme.light ? SchemaGeneratorLight : SchemaGeneratorDark const handleTrigger = useCallback((e: React.MouseEvent) => { @@ -47,23 +47,21 @@ export const JsonSchemaGenerator: FC = ({ await new Promise((resolve) => { setTimeout(() => { setSchema({ - schema: { - type: Type.object, - properties: { - string_field_1: { - type: Type.string, - description: '可为空可为空可为空可为空可为空可为空可为空可为空可为空可为空', - }, - string_field_2: { - type: Type.string, - description: '可为空可为空可为空可为空可为空可为空可为空可为空可为空可为空', - }, + type: Type.object, + properties: { + string_field_1: { + type: Type.string, + description: '可为空可为空可为空可为空可为空可为空可为空可为空可为空可为空', + }, + string_field_2: { + type: Type.string, + description: '可为空可为空可为空可为空可为空可为空可为空可为空可为空可为空', }, - required: [ - 'string_field_1', - ], - additionalProperties: false, }, + required: [ + 'string_field_1', + ], + additionalProperties: false, }) resolve() }, 1000) diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/card.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/card.tsx new file mode 100644 index 0000000000..f785b29b87 --- /dev/null +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/card.tsx @@ -0,0 +1,46 @@ +import React, { type FC } from 'react' +import { useTranslation } from 'react-i18next' + +type CardProps = { + name: string + type: string + required: boolean + description?: string +} + +const Card: FC = ({ + name, + type, + required, + description, +}) => { + const { t } = useTranslation() + + return ( +
+
+
+ {name} +
+
+ {type} +
+ { + required && ( +
+ {t('workflow.nodes.llm.jsonSchema.required')} +
+ ) + } +
+ + {description && ( +
+ {description} +
+ )} +
+ ) +} + +export default Card diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx new file mode 100644 index 0000000000..3fa76b45ea --- /dev/null +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx @@ -0,0 +1,56 @@ +import type { FC } from 'react' +import React from 'react' +import Tooltip from '@/app/components/base/tooltip' +import { RiAddCircleLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react' +import { useTranslation } from 'react-i18next' + +type ActionsProps = { + disableAddBtn: boolean + onAddChildField: () => void + onEdit: () => void + onDelete: () => void +} + +const Actions: FC = ({ + disableAddBtn, + onAddChildField, + onEdit, + onDelete, +}) => { + const { t } = useTranslation() + + return ( +
+ + + + + + + + + +
+ ) +} + +export default React.memo(Actions) diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx new file mode 100644 index 0000000000..709b9f9bc0 --- /dev/null +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx @@ -0,0 +1,28 @@ +import React, { type FC } from 'react' +import Button from '@/app/components/base/button' +import { useTranslation } from 'react-i18next' + +type AdvancedActionsProps = { + onCancel: () => void + onConfirm: () => void +} + +const AdvancedActions: FC = ({ + onCancel, + onConfirm, +}) => { + const { t } = useTranslation() + + return ( +
+ + +
+ ) +} + +export default React.memo(AdvancedActions) diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-options.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-options.tsx new file mode 100644 index 0000000000..3d9bbbafbe --- /dev/null +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-options.tsx @@ -0,0 +1,83 @@ +import React, { type FC, useCallback, useState } from 'react' +import { RiArrowDownDoubleLine } from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import Divider from '@/app/components/base/divider' +import Textarea from '@/app/components/base/textarea' + +export type AdvancedOptionsType = { + enum: string +} + +type AdvancedOptionsProps = { + options: AdvancedOptionsType + onChange: (options: AdvancedOptionsType) => void +} + +const AdvancedOptions: FC = ({ + onChange, + options, +}) => { + const { t } = useTranslation() + const [showAdvancedOptions, setShowAdvancedOptions] = useState(false) + const [enumValue, setEnumValue] = useState(options.enum) + + const handleEnumChange = useCallback((e: React.ChangeEvent) => { + setEnumValue(e.target.value) + }, []) + + const handleEnumBlur = useCallback((e: React.FocusEvent) => { + onChange({ enum: e.target.value }) + }, [onChange]) + + // const handleEnumChange = useCallback((e: React.ChangeEvent) => { + // const value = e.target.value + // onChange({ enum: value }) + // }, [onChange]) + + const handleToggleAdvancedOptions = useCallback(() => { + setShowAdvancedOptions(prev => !prev) + }, []) + + return ( +
+ {showAdvancedOptions ? ( +
+
+ + {t('workflow.nodes.llm.jsonSchema.stringValidations')} + +
+ +
+
+
+
+ Enum +
+