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:
103
dashboard/src/routes/config/bot/hooks/JsonFieldHookFactory.tsx
Normal file
103
dashboard/src/routes/config/bot/hooks/JsonFieldHookFactory.tsx
Normal 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
|
||||
}
|
||||
49
dashboard/src/routes/config/bot/hooks/complexFieldHooks.tsx
Normal file
49
dashboard/src/routes/config/bot/hooks/complexFieldHooks.tsx
Normal 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]',
|
||||
})
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user