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
}

View File

@@ -0,0 +1,49 @@
import { createJsonFieldHook } from './JsonFieldHookFactory'
export const ChatTalkValueRulesHook = createJsonFieldHook({
emptyValue: [],
helperText: '复杂对象数组使用 JSON 编辑。每一项对应一个聊天频率规则对象。',
placeholder: '[\n {\n "platform": "",\n "item_id": "",\n "rule_type": "group",\n "time": "00:00-23:59",\n "value": 1.0\n }\n]',
})
export const ExpressionLearningListHook = createJsonFieldHook({
emptyValue: [],
helperText: '表达学习配置较复杂,使用 JSON 编辑更稳妥。每一项对应一个学习规则。',
placeholder: '[\n {\n "platform": "",\n "item_id": "",\n "rule_type": "group",\n "use_expression": true,\n "enable_learning": true,\n "enable_jargon_learning": true\n }\n]',
})
export const ExpressionGroupsHook = createJsonFieldHook({
emptyValue: [],
helperText: '表达互通组使用 JSON 编辑。每一项包含一个 expression_groups 数组。',
placeholder: '[\n {\n "expression_groups": [\n {\n "platform": "qq",\n "item_id": "123456",\n "rule_type": "group"\n }\n ]\n }\n]',
})
export const ExperimentalChatPromptsHook = createJsonFieldHook({
emptyValue: [],
helperText: '实验配置中的定向 Prompt 列表使用 JSON 编辑。每一项应包含 platform、item_id、rule_type、prompt。',
placeholder: '[\n {\n "platform": "qq",\n "item_id": "123456",\n "rule_type": "group",\n "prompt": "这里填写额外提示词"\n }\n]',
})
export const KeywordRulesHook = createJsonFieldHook({
emptyValue: [],
helperText: '关键词规则为对象数组,建议直接编辑 JSON。',
placeholder: '[\n {\n "keywords": ["早安"],\n "regex": [],\n "reaction": "早安呀"\n }\n]',
})
export const RegexRulesHook = createJsonFieldHook({
emptyValue: [],
helperText: '正则规则为对象数组,建议直接编辑 JSON。',
placeholder: '[\n {\n "keywords": [],\n "regex": ["https?://[^\\\\s]+"],\n "reaction": "检测到链接:[0]"\n }\n]',
})
export const MCPRootItemsHook = createJsonFieldHook({
emptyValue: [],
helperText: 'MCP Roots 条目为对象数组,使用 JSON 编辑。',
placeholder: '[\n {\n "enabled": true,\n "uri": "file:///Users/example/project",\n "name": "project-root"\n }\n]',
})
export const MCPServersHook = createJsonFieldHook({
emptyValue: [],
helperText: 'MCP 服务器配置结构较复杂,使用 JSON 编辑。',
placeholder: '[\n {\n "name": "example-server",\n "enabled": true,\n "transport": "stdio",\n "command": "uvx",\n "args": ["example-server"],\n "env": {},\n "url": "",\n "headers": {},\n "http_timeout_seconds": 30.0,\n "read_timeout_seconds": 300.0,\n "authorization": {\n "mode": "none",\n "bearer_token": ""\n }\n }\n]',
})

View File

@@ -10,6 +10,16 @@ export type {
UseAutoSaveConfig,
UseAutoSaveReturnGeneric,
} from './useAutoSave'
export {
ChatTalkValueRulesHook,
ExperimentalChatPromptsHook,
ExpressionGroupsHook,
ExpressionLearningListHook,
KeywordRulesHook,
MCPRootItemsHook,
MCPServersHook,
RegexRulesHook,
} from './complexFieldHooks'
export { ChatSectionHook } from './ChatSectionHook'
export { PersonalitySectionHook } from './PersonalitySectionHook'
export { DebugSectionHook } from './DebugSectionHook'