feat: add SchemaEditor component and integrate it into JSON Schema config modal

This commit is contained in:
twwu 2025-03-14 17:05:52 +08:00
parent a07831bc05
commit 183edf0fd5
5 changed files with 143 additions and 3 deletions

View File

@ -9,6 +9,7 @@ import JsonImporter from './json-importer'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import VisualEditor from './visual-editor'
import SchemaEditor from './schema-editor'
type JsonSchemaConfigModalProps = {
isShow: boolean
@ -43,6 +44,7 @@ const JsonSchemaConfigModal: FC<JsonSchemaConfigModalProps> = ({
const { t } = useTranslation()
const [currentTab, setCurrentTab] = useState(SchemaView.VisualEditor)
const [jsonSchema, setJsonSchema] = useState(defaultSchema || DEFAULT_SCHEMA)
const [json, setJson] = useState(JSON.stringify(jsonSchema, null, 2))
const [btnWidth, setBtnWidth] = useState(0)
const updateBtnWidth = useCallback((width: number) => {
@ -57,6 +59,10 @@ const JsonSchemaConfigModal: FC<JsonSchemaConfigModalProps> = ({
setJsonSchema(schema)
}, [])
const handleSchemaEditorUpdate = useCallback((schema: string) => {
setJson(schema)
}, [])
const handleResetDefaults = useCallback(() => {
setJsonSchema(defaultSchema || DEFAULT_SCHEMA)
}, [defaultSchema])
@ -118,7 +124,10 @@ const JsonSchemaConfigModal: FC<JsonSchemaConfigModalProps> = ({
/>
)}
{currentTab === SchemaView.JsonSchema && (
<div className='h-full rounded-xl bg-components-input-bg-normal'>JSON Schema</div>
<SchemaEditor
schema={json}
onUpdate={handleSchemaEditorUpdate}
/>
)}
</div>
{/* Footer */}

View File

@ -166,7 +166,6 @@ const JsonImporter: FC<JsonImporterProps> = ({
alwaysConsumeMouseWheel: false,
},
}}
/>
</div>
</div>

View File

@ -131,7 +131,6 @@ const GeneratedResult: FC<GeneratedResultProps> = ({
alwaysConsumeMouseWheel: false,
},
}}
/>
</div>
</div>

View File

@ -0,0 +1,102 @@
import { Editor } from '@monaco-editor/react'
import { RiClipboardLine, RiIndentIncrease } from '@remixicon/react'
import copy from 'copy-to-clipboard'
import React, { type FC, useCallback, useRef } from 'react'
type SchemaEditorProps = {
schema: string
onUpdate: (schema: string) => void
}
const SchemaEditor: FC<SchemaEditorProps> = ({
schema,
onUpdate,
}) => {
const monacoRef = useRef<any>(null)
const editorRef = useRef<any>(null)
const handleEditorDidMount = useCallback((editor: any, monaco: any) => {
editorRef.current = editor
monacoRef.current = monaco
monaco.editor.defineTheme('light-theme', {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.background': '#00000000',
'editor.lineHighlightBackground': '#00000000',
'focusBorder': '#00000000',
},
})
monaco.editor.setTheme('light-theme')
}, [])
const formatJsonContent = useCallback(() => {
if (editorRef.current)
editorRef.current.getAction('editor.action.formatDocument')?.run()
}, [])
const handleEditorChange = useCallback((value: string | undefined) => {
if (!value)
return
onUpdate(value)
}, [onUpdate])
return (
<div className='flex flex-col h-full rounded-xl bg-components-input-bg-normal overflow-hidden'>
<div className='flex items-center justify-between pl-2 pt-1 pr-1'>
<div className='py-0.5 text-text-secondary system-xs-semibold-uppercase'>
<span className='px-1 py-0.5'>JSON</span>
</div>
<div className='flex items-center gap-x-0.5'>
<button
type='button'
className='flex items-center justify-center h-6 w-6'
onClick={formatJsonContent}
>
<RiIndentIncrease className='w-4 h-4 text-text-tertiary' />
</button>
<button
type='button'
className='flex items-center justify-center h-6 w-6'
onClick={() => copy(schema)}>
<RiClipboardLine className='w-4 h-4 text-text-tertiary' />
</button>
</div>
</div>
<div className='relative grow'>
<Editor
height='100%'
defaultLanguage='json'
value={schema}
onChange={handleEditorChange}
onMount={handleEditorDidMount}
options={{
readOnly: false,
domReadOnly: true,
minimap: { enabled: false },
tabSize: 2,
scrollBeyondLastLine: false,
wordWrap: 'on',
wrappingIndent: 'same',
// Add these options
overviewRulerBorder: false,
hideCursorInOverviewRuler: true,
renderLineHighlightOnlyWhenFocus: false,
renderLineHighlight: 'none',
// Hide scrollbar borders
scrollbar: {
vertical: 'hidden',
horizontal: 'hidden',
verticalScrollbarSize: 0,
horizontalScrollbarSize: 0,
alwaysConsumeMouseWheel: false,
},
}}
/>
</div>
</div>
)
}
export default SchemaEditor

View File

@ -0,0 +1,31 @@
import { type FC, useState } from 'react'
import type { Field } from '../../../types'
import Button from '@/app/components/base/button'
import { RiAddCircleFill } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
type AddToRootProps = {
addField: (path: string[], updates: Field) => void
}
const AddField: FC<AddToRootProps> = ({
addField,
}) => {
const { t } = useTranslation()
const [isEditing, setIsEditing] = useState(false)
const handleAddField = () => {
setIsEditing(true)
}
return (
<>
<Button size='small' className='flex items-center gap-x-[1px]' onClick={handleAddField}>
<RiAddCircleFill className='w-3.5 h-3.5'/>
<span className='px-[3px]'>{t('workflow.nodes.llm.jsonSchema.addField')}</span>
</Button>
</>
)
}
export default AddField