feat: enhance bot configuration with new sections and JSON field hooks

- Added support for new configuration sections: relationship, database, maisaka, mcp, and plugin_runtime.
- Introduced complex field hooks for handling JSON configurations in chat talk value rules, expression learning lists, and more.
- Updated the field hooks to include schema metadata for better UI representation.
- Refactored the bot configuration page to utilize a more dynamic approach for managing section values and state.
- Improved the configuration schema generation to ensure all top-level sections have UI metadata.
- Added tests to validate the new configuration schema and ensure proper functionality of the JSON field hooks.
This commit is contained in:
DrSmoothl
2026-04-03 02:46:07 +08:00
parent 4ec06ece56
commit aea87e18f1
15 changed files with 881 additions and 321 deletions

View File

@@ -0,0 +1,103 @@
import { useEffect, useMemo, useState } from 'react'
import { Textarea } from '@/components/ui/textarea'
import type { FieldHookComponent } from '@/lib/field-hooks'
import type { ConfigSchema, FieldSchema } from '@/types/config-schema'
interface JsonFieldHookOptions {
emptyValue: unknown
helperText: string
placeholder: string
}
function resolveLabel(schema?: ConfigSchema | FieldSchema, fieldPath?: string): string {
if (!schema) {
return fieldPath?.split('.').at(-1) || 'JSON 配置'
}
if ('label' in schema && schema.label) {
return schema.label
}
if ('uiLabel' in schema && schema.uiLabel) {
return schema.uiLabel
}
if ('classDoc' in schema && schema.classDoc) {
return schema.classDoc
}
if ('className' in schema && schema.className) {
return schema.className
}
return fieldPath?.split('.').at(-1) || 'JSON 配置'
}
function resolveDescription(schema?: ConfigSchema | FieldSchema): string {
if (!schema) {
return ''
}
if ('description' in schema) {
return schema.description || ''
}
if ('classDoc' in schema) {
return schema.classDoc || ''
}
return ''
}
export function createJsonFieldHook(options: JsonFieldHookOptions): FieldHookComponent {
const JsonFieldHook: FieldHookComponent = ({ fieldPath, onChange, schema, value }) => {
const normalizedValue = useMemo(() => {
if (value === undefined) {
return options.emptyValue
}
return value
}, [value])
const [editorValue, setEditorValue] = useState(() => JSON.stringify(normalizedValue, null, 2))
const [errorMessage, setErrorMessage] = useState('')
useEffect(() => {
setEditorValue(JSON.stringify(normalizedValue, null, 2))
setErrorMessage('')
}, [normalizedValue])
const label = resolveLabel(schema, fieldPath)
const description = resolveDescription(schema)
return (
<div className="space-y-3 rounded-lg border bg-card p-4 sm:p-6">
<div className="space-y-1">
<h3 className="text-base font-semibold">{label}</h3>
{description && (
<p className="text-sm text-muted-foreground">{description}</p>
)}
<p className="text-xs text-muted-foreground">{options.helperText}</p>
</div>
<Textarea
className="min-h-[220px] font-mono text-sm"
placeholder={options.placeholder}
value={editorValue}
onChange={(event) => {
const nextValue = event.target.value
setEditorValue(nextValue)
try {
const parsed = JSON.parse(nextValue)
setErrorMessage('')
onChange?.(parsed)
} catch (error) {
setErrorMessage(error instanceof Error ? error.message : 'JSON 格式错误')
}
}}
/>
{errorMessage ? (
<p className="text-sm text-destructive">JSON {errorMessage}</p>
) : (
<p className="text-sm text-muted-foreground">JSON 稿</p>
)}
</div>
)
}
return JsonFieldHook
}