feat: 添加动态 Tab 分组支持,优化配置界面 UI 元数据
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { parse as parseToml } from 'smol-toml'
|
import { parse as parseToml } from 'smol-toml'
|
||||||
|
|
||||||
import { AlertDescription, Alert } from '@/components/ui/alert'
|
import { AlertDescription, Alert } from '@/components/ui/alert'
|
||||||
@@ -20,12 +20,13 @@ import { CodeEditor } from '@/components'
|
|||||||
import { DynamicConfigForm } from '@/components/dynamic-form'
|
import { DynamicConfigForm } from '@/components/dynamic-form'
|
||||||
import { RestartOverlay } from '@/components/restart-overlay'
|
import { RestartOverlay } from '@/components/restart-overlay'
|
||||||
import { useToast } from '@/hooks/use-toast'
|
import { useToast } from '@/hooks/use-toast'
|
||||||
import { getBotConfig, getBotConfigRaw, updateBotConfig, updateBotConfigRaw } from '@/lib/config-api'
|
import { getBotConfig, getBotConfigRaw, getBotConfigSchema, updateBotConfig, updateBotConfigRaw } from '@/lib/config-api'
|
||||||
import { fieldHooks } from '@/lib/field-hooks'
|
import { fieldHooks } from '@/lib/field-hooks'
|
||||||
import { RestartProvider, useRestart } from '@/lib/restart-context'
|
import { RestartProvider, useRestart } from '@/lib/restart-context'
|
||||||
|
|
||||||
import { Code2, Info, Layout, Power, Save } from 'lucide-react'
|
import { Code2, Info, Layout, Power, Save } from 'lucide-react'
|
||||||
|
|
||||||
|
import type { ConfigSchema } from '@/types/config-schema'
|
||||||
import type {
|
import type {
|
||||||
BotConfig,
|
BotConfig,
|
||||||
ChatConfig,
|
ChatConfig,
|
||||||
@@ -71,6 +72,58 @@ import {
|
|||||||
/** Toast 显示前的延迟时间 (毫秒) */
|
/** Toast 显示前的延迟时间 (毫秒) */
|
||||||
const TOAST_DISPLAY_DELAY = 500
|
const TOAST_DISPLAY_DELAY = 500
|
||||||
|
|
||||||
|
/** Tab 标签页的首选排列顺序 (host field name) */
|
||||||
|
const TAB_ORDER = [
|
||||||
|
'bot', 'personality', 'chat', 'expression', 'emoji',
|
||||||
|
'response_post_process', 'dream', 'lpmm_knowledge', 'webui', 'debug',
|
||||||
|
]
|
||||||
|
|
||||||
|
// ==================== Tab 分组类型与构建 ====================
|
||||||
|
interface TabGroup {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
icon: string
|
||||||
|
sections: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 schema 的 nested 字段解析出 tab 分组信息。
|
||||||
|
* - 有 uiLabel 且无 uiParent → 独立 tab (host)
|
||||||
|
* - 有 uiParent → 归入对应 host tab 的 sections
|
||||||
|
*/
|
||||||
|
function buildTabGroupsFromSchema(schema: ConfigSchema): TabGroup[] {
|
||||||
|
const nested = schema.nested || {}
|
||||||
|
const hosts = new Map<string, TabGroup>()
|
||||||
|
const children: Array<{ fieldName: string; parentId: string }> = []
|
||||||
|
|
||||||
|
for (const [fieldName, fieldSchema] of Object.entries(nested)) {
|
||||||
|
if (fieldSchema.uiLabel && !fieldSchema.uiParent) {
|
||||||
|
hosts.set(fieldName, {
|
||||||
|
id: fieldName,
|
||||||
|
label: fieldSchema.uiLabel,
|
||||||
|
icon: fieldSchema.uiIcon || '',
|
||||||
|
sections: [fieldName],
|
||||||
|
})
|
||||||
|
} else if (fieldSchema.uiParent) {
|
||||||
|
children.push({ fieldName, parentId: fieldSchema.uiParent })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { fieldName, parentId } of children) {
|
||||||
|
const parent = hosts.get(parentId)
|
||||||
|
if (parent) {
|
||||||
|
parent.sections.push(fieldName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按 TAB_ORDER 排序;未列入的 tab 追加到末尾
|
||||||
|
return Array.from(hosts.values()).sort((a, b) => {
|
||||||
|
const ai = TAB_ORDER.indexOf(a.id)
|
||||||
|
const bi = TAB_ORDER.indexOf(b.id)
|
||||||
|
return (ai === -1 ? Infinity : ai) - (bi === -1 ? Infinity : bi)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 主导出组件:包装 RestartProvider
|
// 主导出组件:包装 RestartProvider
|
||||||
export function BotConfigPage() {
|
export function BotConfigPage() {
|
||||||
return (
|
return (
|
||||||
@@ -116,6 +169,9 @@ function BotConfigPageContent() {
|
|||||||
const [telemetryConfig, setTelemetryConfig] = useState<TelemetryConfig | null>(null)
|
const [telemetryConfig, setTelemetryConfig] = useState<TelemetryConfig | null>(null)
|
||||||
const [webuiConfig, setWebuiConfig] = useState<WebUIConfig | null>(null)
|
const [webuiConfig, setWebuiConfig] = useState<WebUIConfig | null>(null)
|
||||||
|
|
||||||
|
// Schema 状态(用于动态 tab 分组)
|
||||||
|
const [configSchema, setConfigSchema] = useState<ConfigSchema | null>(null)
|
||||||
|
|
||||||
// 用于标记初始加载和配置缓存
|
// 用于标记初始加载和配置缓存
|
||||||
const initialLoadRef = useRef(true)
|
const initialLoadRef = useRef(true)
|
||||||
const configRef = useRef<Record<string, unknown>>({})
|
const configRef = useRef<Record<string, unknown>>({})
|
||||||
@@ -292,7 +348,7 @@ function BotConfigPageContent() {
|
|||||||
const loadConfig = useCallback(async () => {
|
const loadConfig = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const result = await getBotConfig()
|
const [result, schemaResult] = await Promise.all([getBotConfig(), getBotConfigSchema()])
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
toast({
|
toast({
|
||||||
title: '加载失败',
|
title: '加载失败',
|
||||||
@@ -303,6 +359,9 @@ function BotConfigPageContent() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
parseAndSetConfig((result.data as Record<string, unknown>).config as Record<string, unknown>)
|
parseAndSetConfig((result.data as Record<string, unknown>).config as Record<string, unknown>)
|
||||||
|
if (schemaResult.success && schemaResult.data) {
|
||||||
|
setConfigSchema((schemaResult.data as unknown as Record<string, unknown>).schema as ConfigSchema)
|
||||||
|
}
|
||||||
setHasUnsavedChanges(false)
|
setHasUnsavedChanges(false)
|
||||||
initialLoadRef.current = false
|
initialLoadRef.current = false
|
||||||
|
|
||||||
@@ -544,6 +603,12 @@ function BotConfigPageContent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据 schema 构建 tab 分组
|
||||||
|
const tabGroups = useMemo(() => {
|
||||||
|
if (!configSchema) return []
|
||||||
|
return buildTabGroupsFromSchema(configSchema)
|
||||||
|
}, [configSchema])
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ScrollArea className="h-full">
|
<ScrollArea className="h-full">
|
||||||
@@ -682,125 +747,31 @@ function BotConfigPageContent() {
|
|||||||
|
|
||||||
{/* 可视化模式 */}
|
{/* 可视化模式 */}
|
||||||
{editMode === 'visual' && (
|
{editMode === 'visual' && (
|
||||||
<>
|
<DynamicConfigTabs
|
||||||
{/* 标签页 */}
|
tabGroups={tabGroups}
|
||||||
<Tabs defaultValue="bot" className="w-full">
|
botConfig={botConfig} setBotConfig={setBotConfig}
|
||||||
<TabsList className="flex flex-wrap h-auto gap-1 p-1 sm:grid sm:grid-cols-5 lg:grid-cols-10">
|
personalityConfig={personalityConfig} setPersonalityConfig={setPersonalityConfig}
|
||||||
<TabsTrigger value="bot" className="text-xs px-2 py-1.5 sm:px-3 sm:py-2 data-[state=active]:shadow-sm">基本信息</TabsTrigger>
|
chatConfig={chatConfig} setChatConfig={setChatConfig}
|
||||||
<TabsTrigger value="personality" className="text-xs px-2 py-1.5 sm:px-3 sm:py-2 data-[state=active]:shadow-sm">人格</TabsTrigger>
|
expressionConfig={expressionConfig} setExpressionConfig={setExpressionConfig}
|
||||||
<TabsTrigger value="chat" className="text-xs px-2 py-1.5 sm:px-3 sm:py-2 data-[state=active]:shadow-sm">聊天</TabsTrigger>
|
emojiConfig={emojiConfig} setEmojiConfig={setEmojiConfig}
|
||||||
<TabsTrigger value="expression" className="text-xs px-2 py-1.5 sm:px-3 sm:py-2 data-[state=active]:shadow-sm">表达</TabsTrigger>
|
memoryConfig={memoryConfig} setMemoryConfig={setMemoryConfig}
|
||||||
<TabsTrigger value="features" className="text-xs px-2 py-1.5 sm:px-3 sm:py-2 data-[state=active]:shadow-sm">功能</TabsTrigger>
|
toolConfig={toolConfig} setToolConfig={setToolConfig}
|
||||||
<TabsTrigger value="processing" className="text-xs px-2 py-1.5 sm:px-3 sm:py-2 data-[state=active]:shadow-sm">处理</TabsTrigger>
|
voiceConfig={voiceConfig} setVoiceConfig={setVoiceConfig}
|
||||||
<TabsTrigger value="dream" className="text-xs px-2 py-1.5 sm:px-3 sm:py-2 data-[state=active]:shadow-sm">做梦</TabsTrigger>
|
messageReceiveConfig={messageReceiveConfig} setMessageReceiveConfig={setMessageReceiveConfig}
|
||||||
<TabsTrigger value="lpmm" className="text-xs px-2 py-1.5 sm:px-3 sm:py-2 data-[state=active]:shadow-sm">知识库</TabsTrigger>
|
dreamConfig={dreamConfig} setDreamConfig={setDreamConfig}
|
||||||
<TabsTrigger value="webui" className="text-xs px-2 py-1.5 sm:px-3 sm:py-2 data-[state=active]:shadow-sm">WebUI</TabsTrigger>
|
lpmmConfig={lpmmConfig} setLpmmConfig={setLpmmConfig}
|
||||||
<TabsTrigger value="other" className="text-xs px-2 py-1.5 sm:px-3 sm:py-2 data-[state=active]:shadow-sm">其他</TabsTrigger>
|
keywordReactionConfig={keywordReactionConfig} setKeywordReactionConfig={setKeywordReactionConfig}
|
||||||
</TabsList>
|
responsePostProcessConfig={responsePostProcessConfig} setResponsePostProcessConfig={setResponsePostProcessConfig}
|
||||||
{/* 基本信息 */}
|
chineseTypoConfig={chineseTypoConfig} setChineseTypoConfig={setChineseTypoConfig}
|
||||||
<TabsContent value="bot" className="space-y-4">
|
responseSplitterConfig={responseSplitterConfig} setResponseSplitterConfig={setResponseSplitterConfig}
|
||||||
{botConfig && <BotInfoSection config={botConfig} onChange={setBotConfig} />}
|
logConfig={logConfig} setLogConfig={setLogConfig}
|
||||||
</TabsContent>
|
debugConfig={debugConfig} setDebugConfig={setDebugConfig}
|
||||||
|
experimentalConfig={experimentalConfig} setExperimentalConfig={setExperimentalConfig}
|
||||||
{/* 人格配置 */}
|
maimMessageConfig={maimMessageConfig} setMaimMessageConfig={setMaimMessageConfig}
|
||||||
<TabsContent value="personality" className="space-y-4">
|
telemetryConfig={telemetryConfig} setTelemetryConfig={setTelemetryConfig}
|
||||||
{personalityConfig && (
|
webuiConfig={webuiConfig} setWebuiConfig={setWebuiConfig}
|
||||||
<PersonalitySection config={personalityConfig} onChange={setPersonalityConfig} />
|
setHasUnsavedChanges={setHasUnsavedChanges}
|
||||||
)}
|
/>
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* 聊天配置 */}
|
|
||||||
<TabsContent value="chat" className="space-y-4">
|
|
||||||
{chatConfig && (
|
|
||||||
<DynamicConfigForm
|
|
||||||
schema={{
|
|
||||||
className: 'ChatConfig',
|
|
||||||
classDoc: '聊天配置',
|
|
||||||
fields: [],
|
|
||||||
nested: {},
|
|
||||||
}}
|
|
||||||
values={{ chat: chatConfig }}
|
|
||||||
onChange={(field, value) => {
|
|
||||||
if (field === 'chat') {
|
|
||||||
setChatConfig(value as ChatConfig)
|
|
||||||
setHasUnsavedChanges(true)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
hooks={fieldHooks}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* 表达配置 */}
|
|
||||||
<TabsContent value="expression" className="space-y-4">
|
|
||||||
{expressionConfig && (
|
|
||||||
<ExpressionSection config={expressionConfig} onChange={setExpressionConfig} />
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* 功能配置(合并表情、记忆、工具) */}
|
|
||||||
<TabsContent value="features" className="space-y-4">
|
|
||||||
{emojiConfig && memoryConfig && toolConfig && voiceConfig && (
|
|
||||||
<FeaturesSection
|
|
||||||
emojiConfig={emojiConfig}
|
|
||||||
memoryConfig={memoryConfig}
|
|
||||||
toolConfig={toolConfig}
|
|
||||||
voiceConfig={voiceConfig}
|
|
||||||
onEmojiChange={setEmojiConfig}
|
|
||||||
onMemoryChange={setMemoryConfig}
|
|
||||||
onToolChange={setToolConfig}
|
|
||||||
onVoiceChange={setVoiceConfig}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* 处理配置(关键词反应和回复后处理) */}
|
|
||||||
<TabsContent value="processing" className="space-y-4">
|
|
||||||
{keywordReactionConfig && responsePostProcessConfig && chineseTypoConfig && responseSplitterConfig && (
|
|
||||||
<ProcessingSection
|
|
||||||
keywordReactionConfig={keywordReactionConfig}
|
|
||||||
responsePostProcessConfig={responsePostProcessConfig}
|
|
||||||
chineseTypoConfig={chineseTypoConfig}
|
|
||||||
responseSplitterConfig={responseSplitterConfig}
|
|
||||||
onKeywordReactionChange={setKeywordReactionConfig}
|
|
||||||
onResponsePostProcessChange={setResponsePostProcessConfig}
|
|
||||||
onChineseTypoChange={setChineseTypoConfig}
|
|
||||||
onResponseSplitterChange={setResponseSplitterConfig}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{messageReceiveConfig && (
|
|
||||||
<MessageReceiveSection
|
|
||||||
config={messageReceiveConfig}
|
|
||||||
onChange={setMessageReceiveConfig}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* 做梦配置 */}
|
|
||||||
<TabsContent value="dream" className="space-y-4">
|
|
||||||
{dreamConfig && <DreamSection config={dreamConfig} onChange={setDreamConfig} />}
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* 知识库配置 */}
|
|
||||||
<TabsContent value="lpmm" className="space-y-4">
|
|
||||||
{lpmmConfig && <LPMMSection config={lpmmConfig} onChange={setLpmmConfig} />}
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* WebUI 配置 */}
|
|
||||||
<TabsContent value="webui" className="space-y-4">
|
|
||||||
{webuiConfig && <WebUISection config={webuiConfig} onChange={setWebuiConfig} />}
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* 其他配置 */}
|
|
||||||
<TabsContent value="other" className="space-y-4">
|
|
||||||
{logConfig && <LogSection config={logConfig} onChange={setLogConfig} />}
|
|
||||||
{debugConfig && <DebugSection config={debugConfig} onChange={setDebugConfig} />}
|
|
||||||
{experimentalConfig && <ExperimentalSection config={experimentalConfig} onChange={setExperimentalConfig} />}
|
|
||||||
{maimMessageConfig && <MaimMessageSection config={maimMessageConfig} onChange={setMaimMessageConfig} />}
|
|
||||||
{telemetryConfig && <TelemetrySection config={telemetryConfig} onChange={setTelemetryConfig} />}
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 重启遮罩层 */}
|
{/* 重启遮罩层 */}
|
||||||
@@ -809,3 +780,154 @@ function BotConfigPageContent() {
|
|||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 动态 Tab 渲染组件 ====================
|
||||||
|
|
||||||
|
interface DynamicConfigTabsProps {
|
||||||
|
tabGroups: TabGroup[]
|
||||||
|
botConfig: BotConfig | null
|
||||||
|
setBotConfig: (c: BotConfig) => void
|
||||||
|
personalityConfig: PersonalityConfig | null
|
||||||
|
setPersonalityConfig: (c: PersonalityConfig) => void
|
||||||
|
chatConfig: ChatConfig | null
|
||||||
|
setChatConfig: (c: ChatConfig) => void
|
||||||
|
expressionConfig: ExpressionConfig | null
|
||||||
|
setExpressionConfig: (c: ExpressionConfig) => void
|
||||||
|
emojiConfig: EmojiConfig | null
|
||||||
|
setEmojiConfig: (c: EmojiConfig) => void
|
||||||
|
memoryConfig: MemoryConfig | null
|
||||||
|
setMemoryConfig: (c: MemoryConfig) => void
|
||||||
|
toolConfig: ToolConfig | null
|
||||||
|
setToolConfig: (c: ToolConfig) => void
|
||||||
|
voiceConfig: VoiceConfig | null
|
||||||
|
setVoiceConfig: (c: VoiceConfig) => void
|
||||||
|
messageReceiveConfig: MessageReceiveConfig | null
|
||||||
|
setMessageReceiveConfig: (c: MessageReceiveConfig) => void
|
||||||
|
dreamConfig: DreamConfig | null
|
||||||
|
setDreamConfig: (c: DreamConfig) => void
|
||||||
|
lpmmConfig: LPMMKnowledgeConfig | null
|
||||||
|
setLpmmConfig: (c: LPMMKnowledgeConfig) => void
|
||||||
|
keywordReactionConfig: KeywordReactionConfig | null
|
||||||
|
setKeywordReactionConfig: (c: KeywordReactionConfig) => void
|
||||||
|
responsePostProcessConfig: ResponsePostProcessConfig | null
|
||||||
|
setResponsePostProcessConfig: (c: ResponsePostProcessConfig) => void
|
||||||
|
chineseTypoConfig: ChineseTypoConfig | null
|
||||||
|
setChineseTypoConfig: (c: ChineseTypoConfig) => void
|
||||||
|
responseSplitterConfig: ResponseSplitterConfig | null
|
||||||
|
setResponseSplitterConfig: (c: ResponseSplitterConfig) => void
|
||||||
|
logConfig: LogConfig | null
|
||||||
|
setLogConfig: (c: LogConfig) => void
|
||||||
|
debugConfig: DebugConfig | null
|
||||||
|
setDebugConfig: (c: DebugConfig) => void
|
||||||
|
experimentalConfig: ExperimentalConfig | null
|
||||||
|
setExperimentalConfig: (c: ExperimentalConfig) => void
|
||||||
|
maimMessageConfig: MaimMessageConfig | null
|
||||||
|
setMaimMessageConfig: (c: MaimMessageConfig) => void
|
||||||
|
telemetryConfig: TelemetryConfig | null
|
||||||
|
setTelemetryConfig: (c: TelemetryConfig) => void
|
||||||
|
webuiConfig: WebUIConfig | null
|
||||||
|
setWebuiConfig: (c: WebUIConfig) => void
|
||||||
|
setHasUnsavedChanges: (v: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function DynamicConfigTabs(props: DynamicConfigTabsProps) {
|
||||||
|
const { tabGroups } = props
|
||||||
|
|
||||||
|
// 每个 tab host field name → 对应的 ReactNode 内容
|
||||||
|
const tabContentMap: Record<string, React.ReactNode> = {
|
||||||
|
bot: props.botConfig && (
|
||||||
|
<BotInfoSection config={props.botConfig} onChange={props.setBotConfig} />
|
||||||
|
),
|
||||||
|
personality: props.personalityConfig && (
|
||||||
|
<PersonalitySection config={props.personalityConfig} onChange={props.setPersonalityConfig} />
|
||||||
|
),
|
||||||
|
chat: props.chatConfig && (
|
||||||
|
<DynamicConfigForm
|
||||||
|
schema={{ className: 'ChatConfig', classDoc: '聊天配置', fields: [], nested: {} }}
|
||||||
|
values={{ chat: props.chatConfig }}
|
||||||
|
onChange={(field, value) => {
|
||||||
|
if (field === 'chat') {
|
||||||
|
props.setChatConfig(value as ChatConfig)
|
||||||
|
props.setHasUnsavedChanges(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
hooks={fieldHooks}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
expression: props.expressionConfig && (
|
||||||
|
<ExpressionSection config={props.expressionConfig} onChange={props.setExpressionConfig} />
|
||||||
|
),
|
||||||
|
emoji: props.emojiConfig && props.memoryConfig && props.toolConfig && props.voiceConfig && (
|
||||||
|
<FeaturesSection
|
||||||
|
emojiConfig={props.emojiConfig}
|
||||||
|
memoryConfig={props.memoryConfig}
|
||||||
|
toolConfig={props.toolConfig}
|
||||||
|
voiceConfig={props.voiceConfig}
|
||||||
|
onEmojiChange={props.setEmojiConfig}
|
||||||
|
onMemoryChange={props.setMemoryConfig}
|
||||||
|
onToolChange={props.setToolConfig}
|
||||||
|
onVoiceChange={props.setVoiceConfig}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
response_post_process: (
|
||||||
|
<>
|
||||||
|
{props.keywordReactionConfig && props.responsePostProcessConfig && props.chineseTypoConfig && props.responseSplitterConfig && (
|
||||||
|
<ProcessingSection
|
||||||
|
keywordReactionConfig={props.keywordReactionConfig}
|
||||||
|
responsePostProcessConfig={props.responsePostProcessConfig}
|
||||||
|
chineseTypoConfig={props.chineseTypoConfig}
|
||||||
|
responseSplitterConfig={props.responseSplitterConfig}
|
||||||
|
onKeywordReactionChange={props.setKeywordReactionConfig}
|
||||||
|
onResponsePostProcessChange={props.setResponsePostProcessConfig}
|
||||||
|
onChineseTypoChange={props.setChineseTypoConfig}
|
||||||
|
onResponseSplitterChange={props.setResponseSplitterConfig}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{props.messageReceiveConfig && (
|
||||||
|
<MessageReceiveSection config={props.messageReceiveConfig} onChange={props.setMessageReceiveConfig} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
dream: props.dreamConfig && (
|
||||||
|
<DreamSection config={props.dreamConfig} onChange={props.setDreamConfig} />
|
||||||
|
),
|
||||||
|
lpmm_knowledge: props.lpmmConfig && (
|
||||||
|
<LPMMSection config={props.lpmmConfig} onChange={props.setLpmmConfig} />
|
||||||
|
),
|
||||||
|
webui: props.webuiConfig && (
|
||||||
|
<WebUISection config={props.webuiConfig} onChange={props.setWebuiConfig} />
|
||||||
|
),
|
||||||
|
debug: (
|
||||||
|
<>
|
||||||
|
{props.logConfig && <LogSection config={props.logConfig} onChange={props.setLogConfig} />}
|
||||||
|
{props.debugConfig && <DebugSection config={props.debugConfig} onChange={props.setDebugConfig} />}
|
||||||
|
{props.experimentalConfig && <ExperimentalSection config={props.experimentalConfig} onChange={props.setExperimentalConfig} />}
|
||||||
|
{props.maimMessageConfig && <MaimMessageSection config={props.maimMessageConfig} onChange={props.setMaimMessageConfig} />}
|
||||||
|
{props.telemetryConfig && <TelemetrySection config={props.telemetryConfig} onChange={props.setTelemetryConfig} />}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tabGroups.length === 0) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs defaultValue={tabGroups[0].id} className="w-full">
|
||||||
|
<TabsList className="flex flex-wrap h-auto gap-1 p-1">
|
||||||
|
{tabGroups.map((tab) => (
|
||||||
|
<TabsTrigger
|
||||||
|
key={tab.id}
|
||||||
|
value={tab.id}
|
||||||
|
className="text-xs px-2 py-1.5 sm:px-3 sm:py-2 data-[state=active]:shadow-sm"
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTrigger>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
{tabGroups.map((tab) => (
|
||||||
|
<TabsContent key={tab.id} value={tab.id} className="space-y-4">
|
||||||
|
{tabContentMap[tab.id]}
|
||||||
|
</TabsContent>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ export interface ConfigSchema {
|
|||||||
classDoc: string
|
classDoc: string
|
||||||
fields: FieldSchema[]
|
fields: FieldSchema[]
|
||||||
nested?: Record<string, ConfigSchema>
|
nested?: Record<string, ConfigSchema>
|
||||||
|
uiParent?: string
|
||||||
|
uiLabel?: string
|
||||||
|
uiIcon?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigSchemaResponse {
|
export interface ConfigSchemaResponse {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import types
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
from typing import Any, Dict, List, Literal, Set, Tuple, Union, cast, get_args, get_origin
|
from typing import Any, ClassVar, Dict, List, Literal, Set, Tuple, Union, cast, get_args, get_origin
|
||||||
|
|
||||||
__all__ = ["ConfigBase", "Field", "AttributeData"]
|
__all__ = ["ConfigBase", "Field", "AttributeData"]
|
||||||
|
|
||||||
@@ -131,6 +131,11 @@ class ConfigBase(BaseModel, AttrDocBase):
|
|||||||
_validate_any: bool = True # 是否验证 Any 类型的使用,默认为 True
|
_validate_any: bool = True # 是否验证 Any 类型的使用,默认为 True
|
||||||
suppress_any_warning: bool = False # 是否抑制 Any 类型使用的警告,默认为 False,仅仅在_validate_any 为 False 时生效
|
suppress_any_warning: bool = False # 是否抑制 Any 类型使用的警告,默认为 False,仅仅在_validate_any 为 False 时生效
|
||||||
|
|
||||||
|
# UI 分组元数据:子类可覆盖以声明所属 Tab 分组
|
||||||
|
__ui_parent__: ClassVar[str] = "" # 父配置类在 Config 中的字段名,空表示独立 Tab
|
||||||
|
__ui_label__: ClassVar[str] = "" # Tab 显示名称(仅做 Tab 主人时使用),空则使用 classDoc
|
||||||
|
__ui_icon__: ClassVar[str] = "" # Tab 图标名称(Lucide 图标名)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, attribute_data: AttributeData, data: dict[str, Any]):
|
def from_dict(cls, attribute_data: AttributeData, data: dict[str, Any]):
|
||||||
"""从字典创建配置对象,并收集缺失和多余的属性信息"""
|
"""从字典创建配置对象,并收集缺失和多余的属性信息"""
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ class ExampleConfig(ConfigBase):
|
|||||||
class BotConfig(ConfigBase):
|
class BotConfig(ConfigBase):
|
||||||
"""机器人配置类"""
|
"""机器人配置类"""
|
||||||
|
|
||||||
|
__ui_label__ = "基本信息"
|
||||||
|
__ui_icon__ = "bot"
|
||||||
|
|
||||||
platform: str = Field(
|
platform: str = Field(
|
||||||
default="",
|
default="",
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -68,6 +71,9 @@ class BotConfig(ConfigBase):
|
|||||||
class PersonalityConfig(ConfigBase):
|
class PersonalityConfig(ConfigBase):
|
||||||
"""人格配置类"""
|
"""人格配置类"""
|
||||||
|
|
||||||
|
__ui_label__ = "人格"
|
||||||
|
__ui_icon__ = "user-circle"
|
||||||
|
|
||||||
personality: str = Field(
|
personality: str = Field(
|
||||||
default="是一个大二在读女大学生,现在正在上网和群友聊天,有时有点攻击性,有时比较温柔",
|
default="是一个大二在读女大学生,现在正在上网和群友聊天,有时有点攻击性,有时比较温柔",
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -160,6 +166,8 @@ class PersonalityConfig(ConfigBase):
|
|||||||
class RelationshipConfig(ConfigBase):
|
class RelationshipConfig(ConfigBase):
|
||||||
"""关系配置类"""
|
"""关系配置类"""
|
||||||
|
|
||||||
|
__ui_parent__ = "debug"
|
||||||
|
|
||||||
enable_relationship: bool = Field(
|
enable_relationship: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -190,6 +198,9 @@ class TalkRulesItem(ConfigBase):
|
|||||||
class ChatConfig(ConfigBase):
|
class ChatConfig(ConfigBase):
|
||||||
"""聊天配置类"""
|
"""聊天配置类"""
|
||||||
|
|
||||||
|
__ui_label__ = "聊天"
|
||||||
|
__ui_icon__ = "message-square"
|
||||||
|
|
||||||
talk_value: float = Field(
|
talk_value: float = Field(
|
||||||
default=1,
|
default=1,
|
||||||
ge=0,
|
ge=0,
|
||||||
@@ -293,6 +304,8 @@ class ChatConfig(ConfigBase):
|
|||||||
class MessageReceiveConfig(ConfigBase):
|
class MessageReceiveConfig(ConfigBase):
|
||||||
"""消息接收配置类"""
|
"""消息接收配置类"""
|
||||||
|
|
||||||
|
__ui_parent__ = "response_post_process"
|
||||||
|
|
||||||
image_parse_threshold: int = Field(
|
image_parse_threshold: int = Field(
|
||||||
default=5,
|
default=5,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -364,6 +377,8 @@ class TargetItem(ConfigBase):
|
|||||||
class MemoryConfig(ConfigBase):
|
class MemoryConfig(ConfigBase):
|
||||||
"""记忆配置类"""
|
"""记忆配置类"""
|
||||||
|
|
||||||
|
__ui_parent__ = "emoji"
|
||||||
|
|
||||||
max_agent_iterations: int = Field(
|
max_agent_iterations: int = Field(
|
||||||
default=5,
|
default=5,
|
||||||
ge=1,
|
ge=1,
|
||||||
@@ -551,6 +566,9 @@ class ExpressionGroup(ConfigBase):
|
|||||||
class ExpressionConfig(ConfigBase):
|
class ExpressionConfig(ConfigBase):
|
||||||
"""表达配置类"""
|
"""表达配置类"""
|
||||||
|
|
||||||
|
__ui_label__ = "表达"
|
||||||
|
__ui_icon__ = "pen-tool"
|
||||||
|
|
||||||
learning_list: list[LearningItem] = Field(
|
learning_list: list[LearningItem] = Field(
|
||||||
default_factory=lambda: [
|
default_factory=lambda: [
|
||||||
LearningItem(
|
LearningItem(
|
||||||
@@ -687,6 +705,8 @@ class ExpressionConfig(ConfigBase):
|
|||||||
class ToolConfig(ConfigBase):
|
class ToolConfig(ConfigBase):
|
||||||
"""工具配置类"""
|
"""工具配置类"""
|
||||||
|
|
||||||
|
__ui_parent__ = "emoji"
|
||||||
|
|
||||||
enable_tool: bool = Field(
|
enable_tool: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -700,6 +720,8 @@ class ToolConfig(ConfigBase):
|
|||||||
class VoiceConfig(ConfigBase):
|
class VoiceConfig(ConfigBase):
|
||||||
"""语音识别配置类"""
|
"""语音识别配置类"""
|
||||||
|
|
||||||
|
__ui_parent__ = "emoji"
|
||||||
|
|
||||||
enable_asr: bool = Field(
|
enable_asr: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -713,6 +735,9 @@ class VoiceConfig(ConfigBase):
|
|||||||
class EmojiConfig(ConfigBase):
|
class EmojiConfig(ConfigBase):
|
||||||
"""表情包配置类"""
|
"""表情包配置类"""
|
||||||
|
|
||||||
|
__ui_label__ = "功能"
|
||||||
|
__ui_icon__ = "puzzle"
|
||||||
|
|
||||||
emoji_chance: float = Field(
|
emoji_chance: float = Field(
|
||||||
default=0.4,
|
default=0.4,
|
||||||
ge=0,
|
ge=0,
|
||||||
@@ -829,6 +854,8 @@ class KeywordRuleConfig(ConfigBase):
|
|||||||
class KeywordReactionConfig(ConfigBase):
|
class KeywordReactionConfig(ConfigBase):
|
||||||
"""关键词配置类"""
|
"""关键词配置类"""
|
||||||
|
|
||||||
|
__ui_parent__ = "response_post_process"
|
||||||
|
|
||||||
keyword_rules: list[KeywordRuleConfig] = Field(
|
keyword_rules: list[KeywordRuleConfig] = Field(
|
||||||
default_factory=lambda: [],
|
default_factory=lambda: [],
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -858,6 +885,9 @@ class KeywordReactionConfig(ConfigBase):
|
|||||||
class ResponsePostProcessConfig(ConfigBase):
|
class ResponsePostProcessConfig(ConfigBase):
|
||||||
"""回复后处理配置类"""
|
"""回复后处理配置类"""
|
||||||
|
|
||||||
|
__ui_label__ = "处理"
|
||||||
|
__ui_icon__ = "settings"
|
||||||
|
|
||||||
enable_response_post_process: bool = Field(
|
enable_response_post_process: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -871,6 +901,8 @@ class ResponsePostProcessConfig(ConfigBase):
|
|||||||
class ChineseTypoConfig(ConfigBase):
|
class ChineseTypoConfig(ConfigBase):
|
||||||
"""中文错别字配置类"""
|
"""中文错别字配置类"""
|
||||||
|
|
||||||
|
__ui_parent__ = "response_post_process"
|
||||||
|
|
||||||
enable: bool = Field(
|
enable: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -929,6 +961,8 @@ class ChineseTypoConfig(ConfigBase):
|
|||||||
class ResponseSplitterConfig(ConfigBase):
|
class ResponseSplitterConfig(ConfigBase):
|
||||||
"""回复分割器配置类"""
|
"""回复分割器配置类"""
|
||||||
|
|
||||||
|
__ui_parent__ = "response_post_process"
|
||||||
|
|
||||||
enable: bool = Field(
|
enable: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -978,6 +1012,8 @@ class ResponseSplitterConfig(ConfigBase):
|
|||||||
class TelemetryConfig(ConfigBase):
|
class TelemetryConfig(ConfigBase):
|
||||||
"""遥测配置类"""
|
"""遥测配置类"""
|
||||||
|
|
||||||
|
__ui_parent__ = "debug"
|
||||||
|
|
||||||
enable: bool = Field(
|
enable: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -991,6 +1027,9 @@ class TelemetryConfig(ConfigBase):
|
|||||||
class DebugConfig(ConfigBase):
|
class DebugConfig(ConfigBase):
|
||||||
"""调试配置类"""
|
"""调试配置类"""
|
||||||
|
|
||||||
|
__ui_label__ = "其他"
|
||||||
|
__ui_icon__ = "more-horizontal"
|
||||||
|
|
||||||
show_prompt: bool = Field(
|
show_prompt: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -1101,6 +1140,8 @@ class ExtraPromptItem(ConfigBase):
|
|||||||
class ExperimentalConfig(ConfigBase):
|
class ExperimentalConfig(ConfigBase):
|
||||||
"""实验功能配置类"""
|
"""实验功能配置类"""
|
||||||
|
|
||||||
|
__ui_parent__ = "debug"
|
||||||
|
|
||||||
private_plan_style: str = Field(
|
private_plan_style: str = Field(
|
||||||
default=(
|
default=(
|
||||||
"1.思考**所有**的可用的action中的**每个动作**是否符合当下条件,如果动作使用条件符合聊天内容就使用"
|
"1.思考**所有**的可用的action中的**每个动作**是否符合当下条件,如果动作使用条件符合聊天内容就使用"
|
||||||
@@ -1136,6 +1177,8 @@ class ExperimentalConfig(ConfigBase):
|
|||||||
class MaimMessageConfig(ConfigBase):
|
class MaimMessageConfig(ConfigBase):
|
||||||
"""maim_message配置类"""
|
"""maim_message配置类"""
|
||||||
|
|
||||||
|
__ui_parent__ = "debug"
|
||||||
|
|
||||||
ws_server_host: str = Field(
|
ws_server_host: str = Field(
|
||||||
default="127.0.0.1",
|
default="127.0.0.1",
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -1230,6 +1273,9 @@ class MaimMessageConfig(ConfigBase):
|
|||||||
class LPMMKnowledgeConfig(ConfigBase):
|
class LPMMKnowledgeConfig(ConfigBase):
|
||||||
"""LPMM知识库配置类"""
|
"""LPMM知识库配置类"""
|
||||||
|
|
||||||
|
__ui_label__ = "知识库"
|
||||||
|
__ui_icon__ = "book-open"
|
||||||
|
|
||||||
enable: bool = Field(
|
enable: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -1397,6 +1443,9 @@ class LPMMKnowledgeConfig(ConfigBase):
|
|||||||
class DreamConfig(ConfigBase):
|
class DreamConfig(ConfigBase):
|
||||||
"""Dream配置类"""
|
"""Dream配置类"""
|
||||||
|
|
||||||
|
__ui_label__ = "做梦"
|
||||||
|
__ui_icon__ = "moon"
|
||||||
|
|
||||||
interval_minutes: int = Field(
|
interval_minutes: int = Field(
|
||||||
default=30,
|
default=30,
|
||||||
ge=1,
|
ge=1,
|
||||||
@@ -1467,6 +1516,9 @@ class DreamConfig(ConfigBase):
|
|||||||
class WebUIConfig(ConfigBase):
|
class WebUIConfig(ConfigBase):
|
||||||
"""WebUI配置类"""
|
"""WebUI配置类"""
|
||||||
|
|
||||||
|
__ui_label__ = "WebUI"
|
||||||
|
__ui_icon__ = "layout"
|
||||||
|
|
||||||
enabled: bool = Field(
|
enabled: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
@@ -1543,6 +1595,8 @@ class WebUIConfig(ConfigBase):
|
|||||||
class DatabaseConfig(ConfigBase):
|
class DatabaseConfig(ConfigBase):
|
||||||
"""数据库配置类"""
|
"""数据库配置类"""
|
||||||
|
|
||||||
|
__ui_parent__ = "debug"
|
||||||
|
|
||||||
save_binary_data: bool = Field(
|
save_binary_data: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
|
|||||||
@@ -28,13 +28,26 @@ class ConfigSchemaGenerator:
|
|||||||
if nested_schema is not None:
|
if nested_schema is not None:
|
||||||
nested[field_name] = nested_schema
|
nested[field_name] = nested_schema
|
||||||
|
|
||||||
return {
|
schema: dict[str, Any] = {
|
||||||
"className": config_class.__name__,
|
"className": config_class.__name__,
|
||||||
"classDoc": (config_class.__doc__ or "").strip(),
|
"classDoc": (config_class.__doc__ or "").strip(),
|
||||||
"fields": fields,
|
"fields": fields,
|
||||||
"nested": nested,
|
"nested": nested,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 将 UI 分组元数据写入 schema
|
||||||
|
ui_parent = getattr(config_class, "__ui_parent__", "")
|
||||||
|
ui_label = getattr(config_class, "__ui_label__", "")
|
||||||
|
ui_icon = getattr(config_class, "__ui_icon__", "")
|
||||||
|
if ui_parent:
|
||||||
|
schema["uiParent"] = ui_parent
|
||||||
|
if ui_label:
|
||||||
|
schema["uiLabel"] = ui_label
|
||||||
|
if ui_icon:
|
||||||
|
schema["uiIcon"] = ui_icon
|
||||||
|
|
||||||
|
return schema
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build_nested_schema(cls, annotation: Any) -> dict[str, Any] | None:
|
def _build_nested_schema(cls, annotation: Any) -> dict[str, Any] | None:
|
||||||
origin = get_origin(annotation)
|
origin = get_origin(annotation)
|
||||||
|
|||||||
Reference in New Issue
Block a user