mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-16 10:45:53 +08:00
feat: enhance JSON Schema visual editor with new components and translations
This commit is contained in:
parent
7a2c831ef3
commit
4333820aa6
@ -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: {
|
||||
|
@ -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<JsonSchemaConfigModalProps> = ({
|
||||
@ -54,6 +53,10 @@ const JsonSchemaConfigModal: FC<JsonSchemaConfigModalProps> = ({
|
||||
|
||||
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<JsonSchemaConfigModalProps> = ({
|
||||
onClose={onClose}
|
||||
className='max-w-[960px] h-[800px] p-0'
|
||||
>
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex flex-col h-full'>
|
||||
{/* Header */}
|
||||
<div className='relative flex p-6 pr-14 pb-3'>
|
||||
<div className='text-text-primary title-2xl-semi-bold grow truncate'>
|
||||
@ -87,7 +90,7 @@ const JsonSchemaConfigModal: FC<JsonSchemaConfigModalProps> = ({
|
||||
<div className='flex items-center justify-between px-6 py-2'>
|
||||
{/* Tab */}
|
||||
<SegmentedControl<SchemaView>
|
||||
options={options}
|
||||
options={VIEW_TABS}
|
||||
value={currentTab}
|
||||
onChange={(value: SchemaView) => {
|
||||
setCurrentTab(value)
|
||||
@ -108,8 +111,15 @@ const JsonSchemaConfigModal: FC<JsonSchemaConfigModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-6 grow'>
|
||||
{currentTab === SchemaView.VisualEditor && <div className='h-full bg-components-input-bg-normal'>Visual Editor</div>}
|
||||
{currentTab === SchemaView.JsonSchema && <div className='h-full bg-components-input-bg-normal'>JSON Schema</div>}
|
||||
{currentTab === SchemaView.VisualEditor && (
|
||||
<VisualEditor
|
||||
schema={jsonSchema}
|
||||
onChange={handleUpdateSchema}
|
||||
/>
|
||||
)}
|
||||
{currentTab === SchemaView.JsonSchema && (
|
||||
<div className='h-full rounded-xl bg-components-input-bg-normal'>JSON Schema</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Footer */}
|
||||
<div className='flex items-center p-6 pt-5 gap-x-2'>
|
||||
|
@ -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
|
||||
|
@ -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<JsonSchemaGeneratorProps> = ({
|
||||
const { theme } = useTheme()
|
||||
const [view, setView] = useState(GeneratorView.promptEditor)
|
||||
const [instruction, setInstruction] = useState('')
|
||||
const [schema, setSchema] = useState<StructuredOutput | null>(null)
|
||||
const [schema, setSchema] = useState<SchemaRoot | null>(null)
|
||||
const SchemaGenerator = theme === Theme.light ? SchemaGeneratorLight : SchemaGeneratorDark
|
||||
|
||||
const handleTrigger = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
@ -47,23 +47,21 @@ export const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({
|
||||
await new Promise<void>((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)
|
||||
|
@ -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<CardProps> = ({
|
||||
name,
|
||||
type,
|
||||
required,
|
||||
description,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex flex-col py-0.5'>
|
||||
<div className='flex items-center gap-x-1 p-0.5 pl-1'>
|
||||
<div className='px-1 py-0.5 text-text-primary system-sm-semibold'>
|
||||
{name}
|
||||
</div>
|
||||
<div className='px-1 py-0.5 text-text-tertiary system-xs-medium'>
|
||||
{type}
|
||||
</div>
|
||||
{
|
||||
required && (
|
||||
<div className='px-1 py-0.5 text-text-warning system-2xs-medium-uppercase'>
|
||||
{t('workflow.nodes.llm.jsonSchema.required')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{description && (
|
||||
<div className='px-2 pb-1 text-text-tertiary system-xs-regular'>
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Card
|
@ -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<ActionsProps> = ({
|
||||
disableAddBtn,
|
||||
onAddChildField,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-x-0.5'>
|
||||
<Tooltip popupContent={t('workflow.nodes.llm.jsonSchema.addChildField')}>
|
||||
<button
|
||||
type='button'
|
||||
className='flex items-center justify-center w-6 h-6 rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary disabled:cursor-not-allowed disabled:text-text-disabled'
|
||||
onClick={onAddChildField}
|
||||
disabled={disableAddBtn}
|
||||
>
|
||||
<RiAddCircleLine className='w-4 h-4'/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip popupContent={t('common.operation.edit')}>
|
||||
<button
|
||||
type='button'
|
||||
className='flex items-center justify-center w-6 h-6 rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary'
|
||||
onClick={onEdit}
|
||||
>
|
||||
<RiEditLine className='w-4 h-4' />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip popupContent={t('common.operation.remove')}>
|
||||
<button
|
||||
type='button'
|
||||
className='flex items-center justify-center w-6 h-6 rounded-md text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive'
|
||||
onClick={onDelete}
|
||||
>
|
||||
<RiDeleteBinLine className='w-4 h-4' />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Actions)
|
@ -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<AdvancedActionsProps> = ({
|
||||
onCancel,
|
||||
onConfirm,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-x-1'>
|
||||
<Button size='small' variant='secondary' onClick={onCancel}>
|
||||
{t('common.operation.cancel')}
|
||||
</Button>
|
||||
<Button size='small' variant='primary' onClick={onConfirm}>
|
||||
{t('common.operation.confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(AdvancedActions)
|
@ -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<AdvancedOptionsProps> = ({
|
||||
onChange,
|
||||
options,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false)
|
||||
const [enumValue, setEnumValue] = useState(options.enum)
|
||||
|
||||
const handleEnumChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setEnumValue(e.target.value)
|
||||
}, [])
|
||||
|
||||
const handleEnumBlur = useCallback((e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||
onChange({ enum: e.target.value })
|
||||
}, [onChange])
|
||||
|
||||
// const handleEnumChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
// const value = e.target.value
|
||||
// onChange({ enum: value })
|
||||
// }, [onChange])
|
||||
|
||||
const handleToggleAdvancedOptions = useCallback(() => {
|
||||
setShowAdvancedOptions(prev => !prev)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='border-t border-divider-subtle'>
|
||||
{showAdvancedOptions ? (
|
||||
<div className='flex flex-col px-2 py-1.5 gap-y-1'>
|
||||
<div className='flex items-center gap-x-2 w-full'>
|
||||
<span className='text-text-tertiary system-2xs-medium-uppercase'>
|
||||
{t('workflow.nodes.llm.jsonSchema.stringValidations')}
|
||||
</span>
|
||||
<div className='grow'>
|
||||
<Divider type='horizontal' className='h-px my-0 bg-line-divider-bg' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex items-center h-6 text-text-secondary system-xs-medium'>
|
||||
Enum
|
||||
</div>
|
||||
<Textarea
|
||||
size='small'
|
||||
className='min-h-6'
|
||||
value={enumValue}
|
||||
onChange={handleEnumChange}
|
||||
onBlur={handleEnumBlur}
|
||||
placeholder={'\'abcd\', 1, 1.5, \'etc\''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type='button'
|
||||
className='flex items-center pl-1.5 pt-2 pr-2 pb-1 gap-x-0.5'
|
||||
onClick={handleToggleAdvancedOptions}
|
||||
>
|
||||
<RiArrowDownDoubleLine className='w-3 h-3 text-text-tertiary' />
|
||||
<span className='text-text-tertiary system-xs-regular'>
|
||||
{t('workflow.nodes.llm.jsonSchema.showAdvancedOptions')}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AdvancedOptions
|
@ -0,0 +1,179 @@
|
||||
import React, { type FC, useCallback, useMemo, useState } from 'react'
|
||||
import type { SchemaEnumType } from '../../../../types'
|
||||
import { ArrayType, Type } from '../../../../types'
|
||||
import type { TypeItem } from './type-selector'
|
||||
import TypeSelector from './type-selector'
|
||||
import RequiredSwitch from './required-switch'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Actions from './actions'
|
||||
import AdvancedActions from './advanced-actions'
|
||||
import AdvancedOptions, { type AdvancedOptionsType } from './advanced-options'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useUnmount } from 'ahooks'
|
||||
import classNames from '@/utils/classnames'
|
||||
|
||||
export type EditData = {
|
||||
name: string
|
||||
type: Type | ArrayType
|
||||
required: boolean
|
||||
description: string
|
||||
enum?: SchemaEnumType
|
||||
}
|
||||
|
||||
type EditCardProps = {
|
||||
fields: EditData
|
||||
onPropertyNameChange: (name: string) => void
|
||||
onTypeChange: (type: Type | ArrayType) => void
|
||||
onRequiredChange: (name: string) => void
|
||||
onDescriptionChange: (description: string) => void
|
||||
onAdvancedOptionsChange: (options: AdvancedOptionsType) => void
|
||||
onDelete: (name: string) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
const TYPE_OPTIONS = [
|
||||
{ value: Type.string, text: 'string' },
|
||||
{ value: Type.number, text: 'number' },
|
||||
{ value: Type.boolean, text: 'boolean' },
|
||||
{ value: Type.object, text: 'object' },
|
||||
{ value: ArrayType.string, text: 'array[string]' },
|
||||
{ value: ArrayType.number, text: 'array[number]' },
|
||||
{ value: ArrayType.boolean, text: 'array[boolean]' },
|
||||
{ value: ArrayType.object, text: 'array[object]' },
|
||||
]
|
||||
|
||||
const EditCard: FC<EditCardProps> = ({
|
||||
fields,
|
||||
onPropertyNameChange,
|
||||
onTypeChange,
|
||||
onRequiredChange,
|
||||
onDescriptionChange,
|
||||
onAdvancedOptionsChange,
|
||||
onDelete,
|
||||
onCancel,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [propertyName, setPropertyName] = useState(fields.name)
|
||||
const [description, setDescription] = useState(fields.description)
|
||||
const [AdvancedEditing, setAdvancedEditing] = useState(!fields)
|
||||
|
||||
const handlePropertyNameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPropertyName(e.target.value)
|
||||
}, [])
|
||||
|
||||
const handlePropertyNameBlur = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
|
||||
onPropertyNameChange(e.target.value)
|
||||
}, [onPropertyNameChange])
|
||||
|
||||
const handleTypeChange = useCallback((item: TypeItem) => {
|
||||
onTypeChange(item.value)
|
||||
}, [onTypeChange])
|
||||
|
||||
const toggleRequired = useCallback(() => {
|
||||
onRequiredChange(propertyName)
|
||||
}, [onRequiredChange, propertyName])
|
||||
|
||||
const handleDescriptionChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDescription(e.target.value)
|
||||
}, [])
|
||||
|
||||
const handleDescriptionBlur = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
|
||||
onDescriptionChange(e.target.value)
|
||||
}, [onDescriptionChange])
|
||||
|
||||
const advancedOptions = useMemo(() => {
|
||||
return { enum: (fields.enum || []).join(', ') }
|
||||
}, [fields.enum])
|
||||
|
||||
const handleAdvancedOptionsChange = useCallback((options: AdvancedOptionsType) => {
|
||||
onAdvancedOptionsChange(options)
|
||||
}, [onAdvancedOptionsChange])
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
setAdvancedEditing(false)
|
||||
}, [])
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
onDelete(propertyName)
|
||||
}, [onDelete, propertyName])
|
||||
|
||||
const handleEdit = useCallback(() => {
|
||||
setAdvancedEditing(true)
|
||||
}, [])
|
||||
|
||||
useUnmount(() => {
|
||||
onPropertyNameChange(propertyName)
|
||||
})
|
||||
|
||||
const disableAddBtn = fields.type !== Type.object && fields.type !== ArrayType.object
|
||||
const hasAdvancedOptions = fields.type === Type.string || fields.type === Type.number
|
||||
|
||||
return (
|
||||
<div className='flex flex-col py-0.5 rounded-lg bg-components-panel-bg shadow-sm shadow-shadow-shadow-4'>
|
||||
<div className='flex items-center pl-1 pr-0.5'>
|
||||
<div className='flex items-center gap-x-1 grow'>
|
||||
<input
|
||||
value={propertyName}
|
||||
className='max-w-20 h-5 rounded-[5px] px-1 py-0.5 text-text-primary system-sm-semibold placeholder:text-text-placeholder
|
||||
placeholder:system-sm-semibold hover:bg-state-base-hover border border-transparent focus:border-components-input-border-active
|
||||
focus:bg-components-input-bg-active focus:shadow-xs shadow-shadow-shadow-3 caret-[#295EFF] outline-none'
|
||||
placeholder={t('workflow.nodes.llm.jsonSchema.fieldNamePlaceholder')}
|
||||
onChange={handlePropertyNameChange}
|
||||
onBlur={handlePropertyNameBlur}
|
||||
/>
|
||||
<TypeSelector
|
||||
currentValue={fields.type}
|
||||
items={TYPE_OPTIONS}
|
||||
onSelect={handleTypeChange}
|
||||
popupClassName={'z-[1000]'}
|
||||
/>
|
||||
{
|
||||
fields.required && (
|
||||
<div className='px-1 py-0.5 text-text-warning system-2xs-medium-uppercase'>
|
||||
{t('workflow.nodes.llm.jsonSchema.required')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<RequiredSwitch
|
||||
defaultValue={fields.required}
|
||||
toggleRequired={toggleRequired}
|
||||
/>
|
||||
<Divider type='vertical' className='h-3' />
|
||||
{AdvancedEditing ? (
|
||||
<AdvancedActions
|
||||
onCancel={() => { }}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
) : (
|
||||
<Actions
|
||||
disableAddBtn={disableAddBtn}
|
||||
onAddChildField={() => { }}
|
||||
onDelete={handleDelete}
|
||||
onEdit={handleEdit}/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(description || AdvancedEditing) && (
|
||||
<div className={classNames(AdvancedEditing ? 'p-2 pt-1' : 'px-2 pb-1')}>
|
||||
<input
|
||||
value={description}
|
||||
className='w-full h-4 p-0 text-text-tertiary system-xs-regular placeholder:text-text-placeholder placeholder:system-xs-regular caret-[#295EFF] outline-none'
|
||||
placeholder={t('workflow.nodes.llm.jsonSchema.descriptionPlaceholder')}
|
||||
onChange={handleDescriptionChange}
|
||||
onBlur={handleDescriptionBlur}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{AdvancedEditing && hasAdvancedOptions && (
|
||||
<AdvancedOptions
|
||||
options={advancedOptions}
|
||||
onChange={handleAdvancedOptionsChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditCard
|
@ -0,0 +1,25 @@
|
||||
import React from 'react'
|
||||
import type { FC } from 'react'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type RequiredSwitchProps = {
|
||||
defaultValue: boolean
|
||||
toggleRequired: () => void
|
||||
}
|
||||
|
||||
const RequiredSwitch: FC<RequiredSwitchProps> = ({
|
||||
defaultValue,
|
||||
toggleRequired,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-x-1 px-1.5 py-1 rounded-[5px] border border-divider-subtle bg-background-default-lighter'>
|
||||
<span className='text-text-secondary system-2xs-medium-uppercase'>{t('workflow.nodes.llm.jsonSchema.required')}</span>
|
||||
<Switch size='xs' defaultValue={defaultValue} onChange={toggleRequired} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(RequiredSwitch)
|
@ -0,0 +1,69 @@
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
import type { ArrayType, Type } from '../../../../types'
|
||||
import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type TypeItem = {
|
||||
value: Type | ArrayType
|
||||
text: string
|
||||
}
|
||||
|
||||
type TypeSelectorProps = {
|
||||
items: TypeItem[]
|
||||
currentValue: Type | ArrayType
|
||||
onSelect: (item: TypeItem) => void
|
||||
popupClassName?: string
|
||||
}
|
||||
|
||||
const TypeSelector: FC<TypeSelectorProps> = ({
|
||||
items,
|
||||
currentValue,
|
||||
onSelect,
|
||||
popupClassName,
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-start'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<div className={cn(
|
||||
'flex items-center p-0.5 pl-1 rounded-[5px] hover:bg-state-base-hover',
|
||||
open && 'bg-state-base-hover',
|
||||
)}>
|
||||
<span className='text-text-tertiary system-xs-medium'>{currentValue}</span>
|
||||
<RiArrowDownSLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className={popupClassName}>
|
||||
<div className='w-40 p-1 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg shadow-shadow-shadow-5'>
|
||||
{items.map((item) => {
|
||||
const isSelected = item.value === currentValue
|
||||
return (<div
|
||||
key={item.value}
|
||||
className={'flex items-center gap-x-1 px-2 py-1 rounded-lg hover:bg-state-base-hover'}
|
||||
onClick={() => {
|
||||
onSelect(item)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<span className='px-1 text-text-secondary system-sm-medium'>{item.text}</span>
|
||||
{isSelected && <RiCheckLine className='w-4 h-4 text-text-accent' />}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default TypeSelector
|
@ -0,0 +1,27 @@
|
||||
import type { FC } from 'react'
|
||||
import type { Field } from '../../../types'
|
||||
import SchemaNode from './schema-node'
|
||||
|
||||
type VisualEditorProps = {
|
||||
schema: Field
|
||||
onChange: (schema: Field) => void
|
||||
}
|
||||
|
||||
const VisualEditor: FC<VisualEditorProps> = ({
|
||||
schema,
|
||||
onChange,
|
||||
}) => {
|
||||
return (
|
||||
<div className='h-full rounded-xl p-1 pl-2 bg-background-section-burn'>
|
||||
<SchemaNode
|
||||
name='structured_output'
|
||||
schema={schema}
|
||||
required={false}
|
||||
onChange={onChange}
|
||||
depth={0}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VisualEditor
|
@ -0,0 +1,375 @@
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { ArrayType } from '../../../types'
|
||||
import { type ArrayItems, type Field, Type } from '../../../types'
|
||||
import type { AdvancedOptionsType } from './edit-card/advanced-options'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { RiArrowDropDownLine, RiArrowDropRightLine } from '@remixicon/react'
|
||||
import { getFieldType, getHasChildren } from '../../../utils'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import EditCard from './edit-card'
|
||||
import Card from './card'
|
||||
import produce from 'immer'
|
||||
|
||||
type SchemaNodeProps = {
|
||||
name: string
|
||||
required: boolean
|
||||
schema: Field
|
||||
depth: number
|
||||
onChange: (schema: Field) => void
|
||||
onPropertyNameChange?: (name: string) => void
|
||||
onRequiredChange?: (name: string) => void
|
||||
onNodeDelete?: (name: string) => void
|
||||
}
|
||||
|
||||
// Support 10 levels of indentation
|
||||
const indentPadding: Record<number, string> = {
|
||||
0: 'pl-0',
|
||||
1: 'pl-[20px]',
|
||||
2: 'pl-[40px]',
|
||||
3: 'pl-[60px]',
|
||||
4: 'pl-[80px]',
|
||||
5: 'pl-[100px]',
|
||||
6: 'pl-[120px]',
|
||||
7: 'pl-[140px]',
|
||||
8: 'pl-[160px]',
|
||||
9: 'pl-[180px]',
|
||||
}
|
||||
|
||||
const indentLeft: Record<number, string> = {
|
||||
1: 'left-0',
|
||||
2: 'left-[20px]',
|
||||
3: 'left-[40px]',
|
||||
4: 'left-[60px]',
|
||||
5: 'left-[80px]',
|
||||
6: 'left-[100px]',
|
||||
7: 'left-[120px]',
|
||||
8: 'left-[140px]',
|
||||
9: 'left-[160px]',
|
||||
}
|
||||
|
||||
const SchemaNode: FC<SchemaNodeProps> = ({
|
||||
name,
|
||||
required,
|
||||
schema,
|
||||
onChange,
|
||||
onPropertyNameChange,
|
||||
onRequiredChange,
|
||||
onNodeDelete,
|
||||
depth,
|
||||
}) => {
|
||||
const [isExpanded, setIsExpanded] = useState(true)
|
||||
const [isHovering, setIsHovering] = useState(false)
|
||||
const hoverTimer = useRef<any>(null)
|
||||
|
||||
const hasChildren = getHasChildren(schema)
|
||||
const isEditing = isHovering && depth > 0
|
||||
const type = getFieldType(schema)
|
||||
|
||||
const handleExpand = () => {
|
||||
setIsExpanded(!isExpanded)
|
||||
}
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
hoverTimer.current = setTimeout(() => {
|
||||
setIsHovering(true)
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
clearTimeout(hoverTimer.current)
|
||||
setIsHovering(false)
|
||||
}
|
||||
|
||||
const handlePropertyNameChange = useCallback((oldName: string, newName: string) => {
|
||||
if (oldName === newName) return
|
||||
|
||||
if (schema.type === Type.object) {
|
||||
const properties = schema.properties || {}
|
||||
if (properties[newName]) {
|
||||
// TODO: Show error message
|
||||
return
|
||||
}
|
||||
|
||||
const newProperties = Object.entries(properties).reduce((acc, [key, value]) => {
|
||||
acc[key === oldName ? newName : key] = value
|
||||
return acc
|
||||
}, {} as Record<string, Field>)
|
||||
|
||||
const required = schema.required || []
|
||||
const newRequired = produce(required, (draft) => {
|
||||
const index = draft.indexOf(oldName)
|
||||
if (index !== -1)
|
||||
draft.splice(index, 1, newName)
|
||||
})
|
||||
|
||||
onChange({
|
||||
...schema,
|
||||
properties: newProperties,
|
||||
required: newRequired,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (schema.type === Type.array && schema.items && schema.items.type === Type.object) {
|
||||
const properties = schema.items.properties || {}
|
||||
if (properties[newName]) {
|
||||
// TODO: Show error message
|
||||
return
|
||||
}
|
||||
|
||||
const newProperties = Object.entries(properties).reduce((acc, [key, value]) => {
|
||||
acc[key === oldName ? newName : key] = value
|
||||
return acc
|
||||
}, {} as Record<string, Field>)
|
||||
const required = schema.items.required || []
|
||||
const newRequired = produce(required, (draft) => {
|
||||
const index = draft.indexOf(oldName)
|
||||
if (index !== -1)
|
||||
draft.splice(index, 1, newName)
|
||||
})
|
||||
onChange({
|
||||
...schema,
|
||||
items: {
|
||||
...schema.items,
|
||||
properties: newProperties,
|
||||
required: newRequired,
|
||||
},
|
||||
})
|
||||
}
|
||||
}, [onChange, schema])
|
||||
|
||||
const handleTypeChange = useCallback((newType: Type | ArrayType) => {
|
||||
if (schema.type === newType) return
|
||||
const newSchema = produce(schema, (draft) => {
|
||||
if (draft.type === Type.object) {
|
||||
delete draft.properties
|
||||
delete draft.required
|
||||
}
|
||||
if (draft.type === Type.array)
|
||||
delete draft.items
|
||||
switch (newType) {
|
||||
case Type.object:
|
||||
draft.type = Type.object
|
||||
draft.properties = {}
|
||||
draft.required = []
|
||||
break
|
||||
case ArrayType.string:
|
||||
draft.type = Type.array
|
||||
draft.items = {
|
||||
type: Type.string,
|
||||
}
|
||||
break
|
||||
case ArrayType.number:
|
||||
draft.type = Type.array
|
||||
draft.items = {
|
||||
type: Type.number,
|
||||
}
|
||||
break
|
||||
case ArrayType.boolean:
|
||||
draft.type = Type.array
|
||||
draft.items = {
|
||||
type: Type.boolean,
|
||||
}
|
||||
break
|
||||
case ArrayType.object:
|
||||
draft.type = Type.array
|
||||
draft.items = {
|
||||
type: Type.object,
|
||||
properties: {},
|
||||
required: [],
|
||||
}
|
||||
break
|
||||
default:
|
||||
draft.type = newType as Type
|
||||
}
|
||||
})
|
||||
onChange(newSchema)
|
||||
}, [onChange, schema])
|
||||
|
||||
const toggleRequired = useCallback((name: string) => {
|
||||
if (schema.type === Type.object) {
|
||||
const required = schema.required || []
|
||||
const newRequired = required.includes(name)
|
||||
? required.filter(item => item !== name)
|
||||
: [...required, name]
|
||||
onChange({
|
||||
...schema,
|
||||
required: newRequired,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (schema.type === Type.array && schema.items && schema.items.type === Type.object) {
|
||||
const required = schema.items.required || []
|
||||
const newRequired = required.includes(name)
|
||||
? required.filter(item => item !== name)
|
||||
: [...required, name]
|
||||
onChange({
|
||||
...schema,
|
||||
items: {
|
||||
...schema.items,
|
||||
required: newRequired,
|
||||
},
|
||||
})
|
||||
}
|
||||
}, [onChange, schema])
|
||||
|
||||
const handleDescriptionChange = useCallback((description: string) => {
|
||||
onChange({
|
||||
...schema,
|
||||
description,
|
||||
})
|
||||
}, [onChange, schema])
|
||||
|
||||
const handleAdvancedOptionsChange = useCallback((advancedOptions: AdvancedOptionsType) => {
|
||||
const newAdvancedOptions = {
|
||||
enum: advancedOptions.enum.replace(' ', '').split(','),
|
||||
}
|
||||
onChange({
|
||||
...schema,
|
||||
...newAdvancedOptions,
|
||||
})
|
||||
}, [onChange, schema])
|
||||
|
||||
const handleNodeDelete = useCallback((name: string) => {
|
||||
const newSchema = produce(schema, (draft) => {
|
||||
if (draft.type === Type.object && draft.properties) {
|
||||
delete draft.properties[name]
|
||||
draft.required = draft.required?.filter(item => item !== name)
|
||||
}
|
||||
if (draft.type === Type.array && draft.items?.properties && draft.items?.type === Type.object) {
|
||||
delete draft.items.properties[name]
|
||||
draft.items.required = draft.items.required?.filter(item => item !== name)
|
||||
}
|
||||
})
|
||||
onChange(newSchema)
|
||||
}, [onChange, schema])
|
||||
|
||||
const handlePropertyChange = useCallback((name: string, propertySchema: Field) => {
|
||||
onChange({
|
||||
...schema,
|
||||
properties: {
|
||||
...(schema.properties || {}),
|
||||
[name]: propertySchema,
|
||||
},
|
||||
})
|
||||
}, [onChange, schema])
|
||||
|
||||
const handleItemsPropertyChange = useCallback((name: string, itemsSchema: Field) => {
|
||||
onChange({
|
||||
...schema,
|
||||
items: {
|
||||
...schema.items,
|
||||
properties: {
|
||||
...(schema.items?.properties || {}),
|
||||
[name]: itemsSchema as ArrayItems,
|
||||
},
|
||||
} as ArrayItems,
|
||||
})
|
||||
}, [onChange, schema])
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<div className={classNames('relative z-10', indentPadding[depth])}>
|
||||
{depth > 0 && hasChildren && (
|
||||
<div className={classNames(
|
||||
'flex items-center absolute top-0 w-5 h-7 px-0.5 z-10 bg-background-section-burn',
|
||||
indentLeft[depth],
|
||||
)}>
|
||||
<button
|
||||
onClick={handleExpand}
|
||||
className='py-0.5 text-text-tertiary hover:text-text-accent'
|
||||
>
|
||||
{
|
||||
isExpanded
|
||||
? <RiArrowDropDownLine className='w-4 h-4' />
|
||||
: <RiArrowDropRightLine className='w-4 h-4' />
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{isEditing ? (
|
||||
<EditCard
|
||||
fields={{
|
||||
name,
|
||||
type,
|
||||
required,
|
||||
description: schema.description || '',
|
||||
enum: schema.enum || [],
|
||||
}}
|
||||
onPropertyNameChange={onPropertyNameChange!}
|
||||
onTypeChange={handleTypeChange}
|
||||
onRequiredChange={onRequiredChange!}
|
||||
onDescriptionChange={handleDescriptionChange}
|
||||
onAdvancedOptionsChange={handleAdvancedOptionsChange}
|
||||
onDelete={onNodeDelete!}
|
||||
onCancel={() => {}}
|
||||
/>
|
||||
) : (
|
||||
<Card
|
||||
name={name}
|
||||
type={type}
|
||||
required={required}
|
||||
description={schema.description}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className={classNames(
|
||||
'flex justify-center w-5 h-[calc(100%-1.75rem)] absolute top-7 z-0',
|
||||
indentLeft[depth + 1],
|
||||
)}>
|
||||
<Divider type='vertical' className='bg-divider-subtle mx-0' />
|
||||
</div>
|
||||
|
||||
{isExpanded && hasChildren && (
|
||||
<>
|
||||
{schema.type === Type.object && schema.properties && (
|
||||
Object.entries(schema.properties).map(([key, childSchema]) => (
|
||||
<SchemaNode
|
||||
key={key}
|
||||
name={key}
|
||||
required={!!schema.required?.includes(key)}
|
||||
schema={childSchema}
|
||||
onChange={handlePropertyChange.bind(null, key)}
|
||||
onPropertyNameChange={handlePropertyNameChange.bind(null, key)}
|
||||
onRequiredChange={toggleRequired}
|
||||
onNodeDelete={handleNodeDelete}
|
||||
depth={depth + 1}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
||||
{schema.type === Type.array
|
||||
&& schema.items
|
||||
&& schema.items.type === Type.object
|
||||
&& schema.items.properties
|
||||
&& (
|
||||
Object.entries(schema.items.properties).map(([key, childSchema]) => (
|
||||
<SchemaNode
|
||||
key={key}
|
||||
name={key}
|
||||
required={!!schema.items?.required?.includes(key)}
|
||||
schema={childSchema}
|
||||
onChange={handleItemsPropertyChange.bind(null, key)}
|
||||
onPropertyNameChange={handlePropertyNameChange.bind(null, key)}
|
||||
onRequiredChange={toggleRequired}
|
||||
onNodeDelete={handleNodeDelete}
|
||||
depth={depth + 1}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(SchemaNode)
|
@ -35,6 +35,9 @@ export enum ArrayType {
|
||||
export type TypeWithArray = Type | ArrayType
|
||||
|
||||
type ArrayItemType = Exclude<Type, Type.array>
|
||||
export type ArrayItems = Omit<Field, 'type'> & { type: ArrayItemType }
|
||||
|
||||
export type SchemaEnumType = string[] | number[]
|
||||
|
||||
export type Field = {
|
||||
type: Type
|
||||
@ -43,18 +46,18 @@ export type Field = {
|
||||
}
|
||||
required?: string[] // Key of required properties in object
|
||||
description?: string
|
||||
items?: { // Array has items. Define the item type
|
||||
type: ArrayItemType
|
||||
}
|
||||
enum?: string[] // Enum values
|
||||
items?: ArrayItems // Array has items. Define the item type
|
||||
enum?: SchemaEnumType // Enum values
|
||||
additionalProperties?: false // Required in object by api. Just set false
|
||||
}
|
||||
|
||||
export type StructuredOutput = {
|
||||
schema: {
|
||||
type: Type.object,
|
||||
properties: Record<string, Field>
|
||||
required: string[]
|
||||
additionalProperties: false
|
||||
}
|
||||
schema: SchemaRoot
|
||||
}
|
||||
|
||||
export type SchemaRoot = {
|
||||
type: Type.object
|
||||
properties: Record<string, Field>
|
||||
required?: string[]
|
||||
additionalProperties: false
|
||||
}
|
||||
|
@ -12,3 +12,13 @@ export const getFieldType = (field: Field) => {
|
||||
|
||||
return ArrayType[items.type]
|
||||
}
|
||||
|
||||
export const getHasChildren = (schema: Field) => {
|
||||
const complexTypes = [Type.object, Type.array]
|
||||
if (!complexTypes.includes(schema.type))
|
||||
return false
|
||||
if (schema.type === Type.object)
|
||||
return schema.properties && Object.keys(schema.properties).length > 0
|
||||
if (schema.type === Type.array)
|
||||
return schema.items && schema.items.type === Type.object && schema.items.properties && Object.keys(schema.items.properties).length > 0
|
||||
}
|
||||
|
@ -1,19 +1,77 @@
|
||||
'use client'
|
||||
|
||||
import { ToolTipContent } from '../components/base/tooltip/content'
|
||||
import { SwitchPluginVersion } from '../components/workflow/nodes/_base/components/switch-plugin-version'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useState } from 'react'
|
||||
import { type Field, Type } from '../components/workflow/nodes/llm/types'
|
||||
import JsonSchemaConfigModal from '../components/workflow/nodes/llm/components/json-schema-config-modal'
|
||||
|
||||
export default function Page() {
|
||||
const { t } = useTranslation()
|
||||
return <div className="p-20">
|
||||
<SwitchPluginVersion
|
||||
uniqueIdentifier={'langgenius/openai:12'}
|
||||
tooltip={<ToolTipContent
|
||||
title={t('workflow.nodes.agent.unsupportedStrategy')}
|
||||
>
|
||||
{t('workflow.nodes.agent.strategyNotFoundDescAndSwitchVersion')}
|
||||
</ToolTipContent>}
|
||||
/>
|
||||
const [show, setShow] = useState(false)
|
||||
const [schema, setSchema] = useState<Field>({
|
||||
type: Type.object,
|
||||
properties: {
|
||||
userId: {
|
||||
type: Type.number,
|
||||
description: 'The user ID',
|
||||
},
|
||||
id: {
|
||||
type: Type.number,
|
||||
},
|
||||
title: {
|
||||
type: Type.string,
|
||||
},
|
||||
completed: {
|
||||
type: Type.boolean,
|
||||
},
|
||||
locations: {
|
||||
type: Type.array,
|
||||
items: {
|
||||
type: Type.object,
|
||||
properties: {
|
||||
x: {
|
||||
type: Type.object,
|
||||
properties: {
|
||||
x1: {
|
||||
type: Type.array,
|
||||
items: {
|
||||
type: Type.number,
|
||||
},
|
||||
},
|
||||
},
|
||||
required: [
|
||||
'x1',
|
||||
],
|
||||
},
|
||||
y: {
|
||||
type: Type.number,
|
||||
},
|
||||
},
|
||||
required: [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: [
|
||||
'userId',
|
||||
'id',
|
||||
'title',
|
||||
],
|
||||
additionalProperties: false,
|
||||
})
|
||||
|
||||
return <div className='flex flex-col p-20 h-full w-full overflow-hidden'>
|
||||
<button onClick={() => setShow(true)} className='shrink-0'>Open Json Schema Config</button>
|
||||
{show && <JsonSchemaConfigModal
|
||||
isShow={show}
|
||||
defaultSchema={schema}
|
||||
onSave={(schema) => {
|
||||
setSchema(schema)
|
||||
}}
|
||||
onClose={() => setShow(false)}
|
||||
/>}
|
||||
<pre className='bg-gray-50 p-4 rounded-lg overflow-auto grow'>
|
||||
{JSON.stringify(schema, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
}
|
||||
|
@ -426,6 +426,13 @@ const translation = {
|
||||
apply: 'Apply',
|
||||
doc: 'Learn more about structured output',
|
||||
resetDefaults: 'Reset Defaults',
|
||||
required: 'required',
|
||||
addField: 'Add Field',
|
||||
addChildField: 'Add Child Field',
|
||||
showAdvancedOptions: 'Show advanced options',
|
||||
stringValidations: 'String Validations',
|
||||
fieldNamePlaceholder: 'Field Name',
|
||||
descriptionPlaceholder: 'Add description',
|
||||
},
|
||||
},
|
||||
knowledgeRetrieval: {
|
||||
|
@ -426,6 +426,13 @@ const translation = {
|
||||
apply: '应用',
|
||||
doc: '了解有关结构化输出的更多信息',
|
||||
resetDefaults: '恢复默认值',
|
||||
required: '必填',
|
||||
addField: '添加字段',
|
||||
addChildField: '添加子字段',
|
||||
showAdvancedOptions: '显示高级选项',
|
||||
stringValidations: '字符串验证',
|
||||
fieldNamePlaceholder: '字段名',
|
||||
descriptionPlaceholder: '添加描述',
|
||||
},
|
||||
},
|
||||
knowledgeRetrieval: {
|
||||
|
@ -113,6 +113,7 @@ const config = {
|
||||
'dataset-option-card-purple-gradient': 'var(--color-dataset-option-card-purple-gradient)',
|
||||
'dataset-option-card-orange-gradient': 'var(--color-dataset-option-card-orange-gradient)',
|
||||
'dataset-chunk-list-mask-bg': 'var(--color-dataset-chunk-list-mask-bg)',
|
||||
'line-divider-bg': 'var(--color-line-divider-bg)',
|
||||
},
|
||||
animation: {
|
||||
'spin-slow': 'spin 2s linear infinite',
|
||||
|
@ -33,7 +33,7 @@ html[data-theme="dark"] {
|
||||
rgba(240, 68, 56, 0.3) 0%,
|
||||
rgba(0, 0, 0, 0) 100%);
|
||||
--color-toast-info-bg: linear-gradient(92deg,
|
||||
rgba(11, 165, 236, 0.3) 0%),
|
||||
rgba(11, 165, 236, 0.3) 0%);
|
||||
--color-account-teams-bg: linear-gradient(271deg,
|
||||
rgba(34, 34, 37, 0.9) -0.1%,
|
||||
rgba(29, 29, 32, 0.9) 98.26%
|
||||
@ -61,4 +61,5 @@ html[data-theme="dark"] {
|
||||
180deg,
|
||||
rgba(24, 24, 27, 0.08) 0%,
|
||||
rgba(0, 0, 0, 0) 100%);
|
||||
--color-line-divider-bg: linear-gradient(90deg, rgba(200, 206, 218, 0.14) 0%, rgba(0, 0, 0, 0) 100%,);
|
||||
}
|
@ -33,7 +33,7 @@ html[data-theme="light"] {
|
||||
rgba(240, 68, 56, 0.25) 0%,
|
||||
rgba(255, 255, 255, 0) 100%);
|
||||
--color-toast-info-bg: linear-gradient(92deg,
|
||||
rgba(11, 165, 236, 0.25) 0%),
|
||||
rgba(11, 165, 236, 0.25) 0%);
|
||||
--color-account-teams-bg: linear-gradient(271deg,
|
||||
rgba(249, 250, 251, 0.9) -0.1%,
|
||||
rgba(242, 244, 247, 0.9) 98.26%
|
||||
@ -61,4 +61,5 @@ html[data-theme="light"] {
|
||||
180deg,
|
||||
rgba(200, 206, 218, 0.2) 0%,
|
||||
rgba(255, 255, 255, 0) 100%);
|
||||
--color-line-divider-bg: linear-gradient(90deg, rgba(16, 24, 40, 0.08) 0%, rgba(255, 255, 255, 0) 100%);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user