fear:持续获取maisak监控(壳开关
This commit is contained in:
@@ -29,7 +29,6 @@ import { Code2, Info, Layout, Power, Save } from 'lucide-react'
|
|||||||
import type { ConfigSchema } from '@/types/config-schema'
|
import type { ConfigSchema } from '@/types/config-schema'
|
||||||
import {
|
import {
|
||||||
ChatTalkValueRulesHook,
|
ChatTalkValueRulesHook,
|
||||||
ExperimentalChatPromptsHook,
|
|
||||||
ExpressionGroupsHook,
|
ExpressionGroupsHook,
|
||||||
ExpressionLearningListHook,
|
ExpressionLearningListHook,
|
||||||
KeywordRulesHook,
|
KeywordRulesHook,
|
||||||
@@ -53,9 +52,7 @@ const TAB_ORDER = [
|
|||||||
'expression',
|
'expression',
|
||||||
'emoji',
|
'emoji',
|
||||||
'response_post_process',
|
'response_post_process',
|
||||||
'lpmm_knowledge',
|
|
||||||
'webui',
|
'webui',
|
||||||
'maisaka',
|
|
||||||
'plugin_runtime',
|
'plugin_runtime',
|
||||||
'log',
|
'log',
|
||||||
]
|
]
|
||||||
@@ -156,22 +153,18 @@ function BotConfigPageContent() {
|
|||||||
const [expressionConfig, setExpressionConfig] = useState<ConfigSectionData | null>(null)
|
const [expressionConfig, setExpressionConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [emojiConfig, setEmojiConfig] = useState<ConfigSectionData | null>(null)
|
const [emojiConfig, setEmojiConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [memoryConfig, setMemoryConfig] = useState<ConfigSectionData | null>(null)
|
const [memoryConfig, setMemoryConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [relationshipConfig, setRelationshipConfig] = useState<ConfigSectionData | null>(null)
|
|
||||||
const [visualConfig, setVisualConfig] = useState<ConfigSectionData | null>(null)
|
const [visualConfig, setVisualConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [voiceConfig, setVoiceConfig] = useState<ConfigSectionData | null>(null)
|
const [voiceConfig, setVoiceConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [messageReceiveConfig, setMessageReceiveConfig] = useState<ConfigSectionData | null>(null)
|
const [messageReceiveConfig, setMessageReceiveConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [lpmmConfig, setLpmmConfig] = useState<ConfigSectionData | null>(null)
|
|
||||||
const [keywordReactionConfig, setKeywordReactionConfig] = useState<ConfigSectionData | null>(null)
|
const [keywordReactionConfig, setKeywordReactionConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [responsePostProcessConfig, setResponsePostProcessConfig] = useState<ConfigSectionData | null>(null)
|
const [responsePostProcessConfig, setResponsePostProcessConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [chineseTypoConfig, setChineseTypoConfig] = useState<ConfigSectionData | null>(null)
|
const [chineseTypoConfig, setChineseTypoConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [responseSplitterConfig, setResponseSplitterConfig] = useState<ConfigSectionData | null>(null)
|
const [responseSplitterConfig, setResponseSplitterConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [debugConfig, setDebugConfig] = useState<ConfigSectionData | null>(null)
|
const [debugConfig, setDebugConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [experimentalConfig, setExperimentalConfig] = useState<ConfigSectionData | null>(null)
|
|
||||||
const [maimMessageConfig, setMaimMessageConfig] = useState<ConfigSectionData | null>(null)
|
const [maimMessageConfig, setMaimMessageConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [telemetryConfig, setTelemetryConfig] = useState<ConfigSectionData | null>(null)
|
const [telemetryConfig, setTelemetryConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [webuiConfig, setWebuiConfig] = useState<ConfigSectionData | null>(null)
|
const [webuiConfig, setWebuiConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [databaseConfig, setDatabaseConfig] = useState<ConfigSectionData | null>(null)
|
const [databaseConfig, setDatabaseConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [maisakaConfig, setMaisakaConfig] = useState<ConfigSectionData | null>(null)
|
|
||||||
const [mcpConfig, setMcpConfig] = useState<ConfigSectionData | null>(null)
|
const [mcpConfig, setMcpConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [pluginRuntimeConfig, setPluginRuntimeConfig] = useState<ConfigSectionData | null>(null)
|
const [pluginRuntimeConfig, setPluginRuntimeConfig] = useState<ConfigSectionData | null>(null)
|
||||||
const [aMemorixConfig, setAMemorixConfig] = useState<ConfigSectionData | null>(null)
|
const [aMemorixConfig, setAMemorixConfig] = useState<ConfigSectionData | null>(null)
|
||||||
@@ -255,22 +248,18 @@ function BotConfigPageContent() {
|
|||||||
setExpressionConfig((config.expression ?? {}) as ConfigSectionData)
|
setExpressionConfig((config.expression ?? {}) as ConfigSectionData)
|
||||||
setEmojiConfig((config.emoji ?? {}) as ConfigSectionData)
|
setEmojiConfig((config.emoji ?? {}) as ConfigSectionData)
|
||||||
setMemoryConfig((config.memory ?? {}) as ConfigSectionData)
|
setMemoryConfig((config.memory ?? {}) as ConfigSectionData)
|
||||||
setRelationshipConfig((config.relationship ?? {}) as ConfigSectionData)
|
|
||||||
setVisualConfig((config.visual ?? {}) as ConfigSectionData)
|
setVisualConfig((config.visual ?? {}) as ConfigSectionData)
|
||||||
setVoiceConfig((config.voice ?? {}) as ConfigSectionData)
|
setVoiceConfig((config.voice ?? {}) as ConfigSectionData)
|
||||||
setMessageReceiveConfig((config.message_receive ?? {}) as ConfigSectionData)
|
setMessageReceiveConfig((config.message_receive ?? {}) as ConfigSectionData)
|
||||||
setLpmmConfig((config.lpmm_knowledge ?? {}) as ConfigSectionData)
|
|
||||||
setKeywordReactionConfig((config.keyword_reaction ?? {}) as ConfigSectionData)
|
setKeywordReactionConfig((config.keyword_reaction ?? {}) as ConfigSectionData)
|
||||||
setResponsePostProcessConfig((config.response_post_process ?? {}) as ConfigSectionData)
|
setResponsePostProcessConfig((config.response_post_process ?? {}) as ConfigSectionData)
|
||||||
setChineseTypoConfig((config.chinese_typo ?? {}) as ConfigSectionData)
|
setChineseTypoConfig((config.chinese_typo ?? {}) as ConfigSectionData)
|
||||||
setResponseSplitterConfig((config.response_splitter ?? {}) as ConfigSectionData)
|
setResponseSplitterConfig((config.response_splitter ?? {}) as ConfigSectionData)
|
||||||
setDebugConfig((config.debug ?? {}) as ConfigSectionData)
|
setDebugConfig((config.debug ?? {}) as ConfigSectionData)
|
||||||
setExperimentalConfig((config.experimental ?? {}) as ConfigSectionData)
|
|
||||||
setMaimMessageConfig((config.maim_message ?? {}) as ConfigSectionData)
|
setMaimMessageConfig((config.maim_message ?? {}) as ConfigSectionData)
|
||||||
setTelemetryConfig((config.telemetry ?? {}) as ConfigSectionData)
|
setTelemetryConfig((config.telemetry ?? {}) as ConfigSectionData)
|
||||||
setWebuiConfig((config.webui ?? {}) as ConfigSectionData)
|
setWebuiConfig((config.webui ?? {}) as ConfigSectionData)
|
||||||
setDatabaseConfig((config.database ?? {}) as ConfigSectionData)
|
setDatabaseConfig((config.database ?? {}) as ConfigSectionData)
|
||||||
setMaisakaConfig((config.maisaka ?? {}) as ConfigSectionData)
|
|
||||||
setMcpConfig((config.mcp ?? {}) as ConfigSectionData)
|
setMcpConfig((config.mcp ?? {}) as ConfigSectionData)
|
||||||
setPluginRuntimeConfig((config.plugin_runtime ?? {}) as ConfigSectionData)
|
setPluginRuntimeConfig((config.plugin_runtime ?? {}) as ConfigSectionData)
|
||||||
setAMemorixConfig((config.a_memorix ?? {}) as ConfigSectionData)
|
setAMemorixConfig((config.a_memorix ?? {}) as ConfigSectionData)
|
||||||
@@ -289,22 +278,18 @@ function BotConfigPageContent() {
|
|||||||
expression: expressionConfig,
|
expression: expressionConfig,
|
||||||
emoji: emojiConfig,
|
emoji: emojiConfig,
|
||||||
memory: memoryConfig,
|
memory: memoryConfig,
|
||||||
relationship: relationshipConfig,
|
|
||||||
visual: visualConfig,
|
visual: visualConfig,
|
||||||
voice: voiceConfig,
|
voice: voiceConfig,
|
||||||
message_receive: messageReceiveConfig,
|
message_receive: messageReceiveConfig,
|
||||||
lpmm_knowledge: lpmmConfig,
|
|
||||||
keyword_reaction: keywordReactionConfig,
|
keyword_reaction: keywordReactionConfig,
|
||||||
response_post_process: responsePostProcessConfig,
|
response_post_process: responsePostProcessConfig,
|
||||||
chinese_typo: chineseTypoConfig,
|
chinese_typo: chineseTypoConfig,
|
||||||
response_splitter: responseSplitterConfig,
|
response_splitter: responseSplitterConfig,
|
||||||
debug: debugConfig,
|
debug: debugConfig,
|
||||||
experimental: experimentalConfig,
|
|
||||||
maim_message: maimMessageConfig,
|
maim_message: maimMessageConfig,
|
||||||
telemetry: telemetryConfig,
|
telemetry: telemetryConfig,
|
||||||
webui: webuiConfig,
|
webui: webuiConfig,
|
||||||
database: databaseConfig,
|
database: databaseConfig,
|
||||||
maisaka: maisakaConfig,
|
|
||||||
mcp: mcpConfig,
|
mcp: mcpConfig,
|
||||||
plugin_runtime: pluginRuntimeConfig,
|
plugin_runtime: pluginRuntimeConfig,
|
||||||
a_memorix: aMemorixConfig,
|
a_memorix: aMemorixConfig,
|
||||||
@@ -316,22 +301,18 @@ function BotConfigPageContent() {
|
|||||||
expressionConfig,
|
expressionConfig,
|
||||||
emojiConfig,
|
emojiConfig,
|
||||||
memoryConfig,
|
memoryConfig,
|
||||||
relationshipConfig,
|
|
||||||
visualConfig,
|
visualConfig,
|
||||||
voiceConfig,
|
voiceConfig,
|
||||||
messageReceiveConfig,
|
messageReceiveConfig,
|
||||||
lpmmConfig,
|
|
||||||
keywordReactionConfig,
|
keywordReactionConfig,
|
||||||
responsePostProcessConfig,
|
responsePostProcessConfig,
|
||||||
chineseTypoConfig,
|
chineseTypoConfig,
|
||||||
responseSplitterConfig,
|
responseSplitterConfig,
|
||||||
debugConfig,
|
debugConfig,
|
||||||
experimentalConfig,
|
|
||||||
maimMessageConfig,
|
maimMessageConfig,
|
||||||
telemetryConfig,
|
telemetryConfig,
|
||||||
webuiConfig,
|
webuiConfig,
|
||||||
databaseConfig,
|
databaseConfig,
|
||||||
maisakaConfig,
|
|
||||||
mcpConfig,
|
mcpConfig,
|
||||||
pluginRuntimeConfig,
|
pluginRuntimeConfig,
|
||||||
aMemorixConfig,
|
aMemorixConfig,
|
||||||
@@ -414,7 +395,6 @@ function BotConfigPageContent() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hookEntries = [
|
const hookEntries = [
|
||||||
['chat.talk_value_rules', ChatTalkValueRulesHook],
|
['chat.talk_value_rules', ChatTalkValueRulesHook],
|
||||||
['experimental.chat_prompts', ExperimentalChatPromptsHook],
|
|
||||||
['expression.expression_groups', ExpressionGroupsHook],
|
['expression.expression_groups', ExpressionGroupsHook],
|
||||||
['expression.learning_list', ExpressionLearningListHook],
|
['expression.learning_list', ExpressionLearningListHook],
|
||||||
['keyword_reaction.keyword_rules', KeywordRulesHook],
|
['keyword_reaction.keyword_rules', KeywordRulesHook],
|
||||||
@@ -450,22 +430,18 @@ function BotConfigPageContent() {
|
|||||||
useConfigAutoSave(expressionConfig, 'expression', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(expressionConfig, 'expression', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(emojiConfig, 'emoji', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(emojiConfig, 'emoji', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(memoryConfig, 'memory', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(memoryConfig, 'memory', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(relationshipConfig, 'relationship', initialLoadRef.current, triggerAutoSave)
|
|
||||||
useConfigAutoSave(visualConfig, 'visual', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(visualConfig, 'visual', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(voiceConfig, 'voice', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(voiceConfig, 'voice', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(messageReceiveConfig, 'message_receive', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(messageReceiveConfig, 'message_receive', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(lpmmConfig, 'lpmm_knowledge', initialLoadRef.current, triggerAutoSave)
|
|
||||||
useConfigAutoSave(keywordReactionConfig, 'keyword_reaction', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(keywordReactionConfig, 'keyword_reaction', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(responsePostProcessConfig, 'response_post_process', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(responsePostProcessConfig, 'response_post_process', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(chineseTypoConfig, 'chinese_typo', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(chineseTypoConfig, 'chinese_typo', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(responseSplitterConfig, 'response_splitter', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(responseSplitterConfig, 'response_splitter', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(debugConfig, 'debug', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(debugConfig, 'debug', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(experimentalConfig, 'experimental', initialLoadRef.current, triggerAutoSave)
|
|
||||||
useConfigAutoSave(maimMessageConfig, 'maim_message', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(maimMessageConfig, 'maim_message', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(telemetryConfig, 'telemetry', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(telemetryConfig, 'telemetry', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(webuiConfig, 'webui', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(webuiConfig, 'webui', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(databaseConfig, 'database', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(databaseConfig, 'database', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(maisakaConfig, 'maisaka', initialLoadRef.current, triggerAutoSave)
|
|
||||||
useConfigAutoSave(mcpConfig, 'mcp', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(mcpConfig, 'mcp', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(pluginRuntimeConfig, 'plugin_runtime', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(pluginRuntimeConfig, 'plugin_runtime', initialLoadRef.current, triggerAutoSave)
|
||||||
useConfigAutoSave(aMemorixConfig, 'a_memorix', initialLoadRef.current, triggerAutoSave)
|
useConfigAutoSave(aMemorixConfig, 'a_memorix', initialLoadRef.current, triggerAutoSave)
|
||||||
@@ -667,22 +643,18 @@ function BotConfigPageContent() {
|
|||||||
expression: expressionConfig,
|
expression: expressionConfig,
|
||||||
emoji: emojiConfig,
|
emoji: emojiConfig,
|
||||||
memory: memoryConfig,
|
memory: memoryConfig,
|
||||||
relationship: relationshipConfig,
|
|
||||||
visual: visualConfig,
|
visual: visualConfig,
|
||||||
voice: voiceConfig,
|
voice: voiceConfig,
|
||||||
message_receive: messageReceiveConfig,
|
message_receive: messageReceiveConfig,
|
||||||
lpmm_knowledge: lpmmConfig,
|
|
||||||
keyword_reaction: keywordReactionConfig,
|
keyword_reaction: keywordReactionConfig,
|
||||||
response_post_process: responsePostProcessConfig,
|
response_post_process: responsePostProcessConfig,
|
||||||
chinese_typo: chineseTypoConfig,
|
chinese_typo: chineseTypoConfig,
|
||||||
response_splitter: responseSplitterConfig,
|
response_splitter: responseSplitterConfig,
|
||||||
debug: debugConfig,
|
debug: debugConfig,
|
||||||
experimental: experimentalConfig,
|
|
||||||
maim_message: maimMessageConfig,
|
maim_message: maimMessageConfig,
|
||||||
telemetry: telemetryConfig,
|
telemetry: telemetryConfig,
|
||||||
webui: webuiConfig,
|
webui: webuiConfig,
|
||||||
database: databaseConfig,
|
database: databaseConfig,
|
||||||
maisaka: maisakaConfig,
|
|
||||||
mcp: mcpConfig,
|
mcp: mcpConfig,
|
||||||
plugin_runtime: pluginRuntimeConfig,
|
plugin_runtime: pluginRuntimeConfig,
|
||||||
a_memorix: aMemorixConfig,
|
a_memorix: aMemorixConfig,
|
||||||
@@ -694,22 +666,18 @@ function BotConfigPageContent() {
|
|||||||
expressionConfig,
|
expressionConfig,
|
||||||
emojiConfig,
|
emojiConfig,
|
||||||
memoryConfig,
|
memoryConfig,
|
||||||
relationshipConfig,
|
|
||||||
visualConfig,
|
visualConfig,
|
||||||
voiceConfig,
|
voiceConfig,
|
||||||
messageReceiveConfig,
|
messageReceiveConfig,
|
||||||
lpmmConfig,
|
|
||||||
keywordReactionConfig,
|
keywordReactionConfig,
|
||||||
responsePostProcessConfig,
|
responsePostProcessConfig,
|
||||||
chineseTypoConfig,
|
chineseTypoConfig,
|
||||||
responseSplitterConfig,
|
responseSplitterConfig,
|
||||||
debugConfig,
|
debugConfig,
|
||||||
experimentalConfig,
|
|
||||||
maimMessageConfig,
|
maimMessageConfig,
|
||||||
telemetryConfig,
|
telemetryConfig,
|
||||||
webuiConfig,
|
webuiConfig,
|
||||||
databaseConfig,
|
databaseConfig,
|
||||||
maisakaConfig,
|
|
||||||
mcpConfig,
|
mcpConfig,
|
||||||
pluginRuntimeConfig,
|
pluginRuntimeConfig,
|
||||||
aMemorixConfig,
|
aMemorixConfig,
|
||||||
@@ -724,22 +692,18 @@ function BotConfigPageContent() {
|
|||||||
expression: setExpressionConfig,
|
expression: setExpressionConfig,
|
||||||
emoji: setEmojiConfig,
|
emoji: setEmojiConfig,
|
||||||
memory: setMemoryConfig,
|
memory: setMemoryConfig,
|
||||||
relationship: setRelationshipConfig,
|
|
||||||
visual: setVisualConfig,
|
visual: setVisualConfig,
|
||||||
voice: setVoiceConfig,
|
voice: setVoiceConfig,
|
||||||
message_receive: setMessageReceiveConfig,
|
message_receive: setMessageReceiveConfig,
|
||||||
lpmm_knowledge: setLpmmConfig,
|
|
||||||
keyword_reaction: setKeywordReactionConfig,
|
keyword_reaction: setKeywordReactionConfig,
|
||||||
response_post_process: setResponsePostProcessConfig,
|
response_post_process: setResponsePostProcessConfig,
|
||||||
chinese_typo: setChineseTypoConfig,
|
chinese_typo: setChineseTypoConfig,
|
||||||
response_splitter: setResponseSplitterConfig,
|
response_splitter: setResponseSplitterConfig,
|
||||||
debug: setDebugConfig,
|
debug: setDebugConfig,
|
||||||
experimental: setExperimentalConfig,
|
|
||||||
maim_message: setMaimMessageConfig,
|
maim_message: setMaimMessageConfig,
|
||||||
telemetry: setTelemetryConfig,
|
telemetry: setTelemetryConfig,
|
||||||
webui: setWebuiConfig,
|
webui: setWebuiConfig,
|
||||||
database: setDatabaseConfig,
|
database: setDatabaseConfig,
|
||||||
maisaka: setMaisakaConfig,
|
|
||||||
mcp: setMcpConfig,
|
mcp: setMcpConfig,
|
||||||
plugin_runtime: setPluginRuntimeConfig,
|
plugin_runtime: setPluginRuntimeConfig,
|
||||||
a_memorix: setAMemorixConfig,
|
a_memorix: setAMemorixConfig,
|
||||||
|
|||||||
@@ -101,12 +101,6 @@ export const ExpressionGroupsHook = createJsonFieldHook({
|
|||||||
placeholder: '[\n {\n "expression_groups": [\n {\n "platform": "qq",\n "item_id": "123456",\n "rule_type": "group"\n }\n ]\n }\n]',
|
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 MCPRootItemsHook = createJsonFieldHook({
|
export const MCPRootItemsHook = createJsonFieldHook({
|
||||||
emptyValue: [],
|
emptyValue: [],
|
||||||
helperText: 'MCP Roots 条目为对象数组,使用 JSON 编辑。',
|
helperText: 'MCP Roots 条目为对象数组,使用 JSON 编辑。',
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export type {
|
|||||||
} from './useAutoSave'
|
} from './useAutoSave'
|
||||||
export {
|
export {
|
||||||
ChatTalkValueRulesHook,
|
ChatTalkValueRulesHook,
|
||||||
ExperimentalChatPromptsHook,
|
|
||||||
ExpressionGroupsHook,
|
ExpressionGroupsHook,
|
||||||
ExpressionLearningListHook,
|
ExpressionLearningListHook,
|
||||||
KeywordRulesHook,
|
KeywordRulesHook,
|
||||||
|
|||||||
@@ -1,311 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Label } from '@/components/ui/label'
|
|
||||||
import { Textarea } from '@/components/ui/textarea'
|
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { Switch } from '@/components/ui/switch'
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select'
|
|
||||||
import {
|
|
||||||
AlertDialog,
|
|
||||||
AlertDialogAction,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
AlertDialogTrigger,
|
|
||||||
} from '@/components/ui/alert-dialog'
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from '@/components/ui/popover'
|
|
||||||
import { Plus, Trash2, AlertTriangle, Eye, Code2 } from 'lucide-react'
|
|
||||||
import type { ExperimentalConfig } from '../types'
|
|
||||||
|
|
||||||
interface ChatPromptData {
|
|
||||||
platform: string
|
|
||||||
id: string
|
|
||||||
type: 'group' | 'private'
|
|
||||||
prompt: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ExperimentalSectionProps {
|
|
||||||
config: ExperimentalConfig
|
|
||||||
onChange: (config: ExperimentalConfig) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ExperimentalSection = React.memo(function ExperimentalSection({ config, onChange }: ExperimentalSectionProps) {
|
|
||||||
// 解析 chat_prompt 字符串为结构化数据
|
|
||||||
const parseChatPrompt = (promptStr: string): ChatPromptData => {
|
|
||||||
const parts = promptStr.split(':')
|
|
||||||
if (parts.length >= 4) {
|
|
||||||
const platform = parts[0]
|
|
||||||
const id = parts[1]
|
|
||||||
const type = parts[2] as 'group' | 'private'
|
|
||||||
const prompt = parts.slice(3).join(':') // 处理 prompt 中可能包含的冒号
|
|
||||||
return { platform, id, type, prompt }
|
|
||||||
}
|
|
||||||
return { platform: 'qq', id: '', type: 'group', prompt: '' }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将结构化数据转换为字符串
|
|
||||||
const stringifyChatPrompt = (data: ChatPromptData): string => {
|
|
||||||
return `${data.platform}:${data.id}:${data.type}:${data.prompt}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const addChatPrompt = () => {
|
|
||||||
onChange({ ...config, chat_prompts: [...config.chat_prompts, 'qq::group:'] })
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeChatPrompt = (index: number) => {
|
|
||||||
onChange({
|
|
||||||
...config,
|
|
||||||
chat_prompts: config.chat_prompts.filter((_, i) => i !== index),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateChatPrompt = (index: number, data: Partial<ChatPromptData>) => {
|
|
||||||
const currentData = parseChatPrompt(config.chat_prompts[index])
|
|
||||||
const newData = { ...currentData, ...data }
|
|
||||||
const newPrompts = [...config.chat_prompts]
|
|
||||||
newPrompts[index] = stringifyChatPrompt(newData)
|
|
||||||
onChange({ ...config, chat_prompts: newPrompts })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预览组件
|
|
||||||
const ChatPromptPreview = ({ promptStr }: { promptStr: string }) => {
|
|
||||||
return (
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
<Eye className="h-4 w-4 mr-1" />
|
|
||||||
预览
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-80 sm:w-96">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h4 className="font-medium text-sm">配置预览</h4>
|
|
||||||
<div className="rounded-md bg-muted p-3 font-mono text-xs break-all">
|
|
||||||
"{promptStr}"
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
这是保存到 bot_config.toml 文件中的格式
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rounded-lg border bg-card p-4 sm:p-6 space-y-6">
|
|
||||||
<div className="flex items-start gap-3 p-3 rounded-lg bg-orange-500/10 border border-orange-500/20">
|
|
||||||
<AlertTriangle className="h-5 w-5 text-orange-500 shrink-0 mt-0.5" />
|
|
||||||
<div className="space-y-1">
|
|
||||||
<h4 className="font-medium text-orange-500">实验性功能</h4>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
此部分包含实验性功能,可能不稳定或在未来版本中发生变化。请谨慎使用,并注意不推荐在生产环境中修改私聊规则。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-semibold mb-4">实验性设置</h3>
|
|
||||||
|
|
||||||
<div className="grid gap-6">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Switch
|
|
||||||
id="lpmm_memory"
|
|
||||||
checked={config.lpmm_memory ?? false}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
onChange({ ...config, lpmm_memory: checked })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="lpmm_memory" className="cursor-pointer">
|
|
||||||
将聊天历史总结导入到 LPMM 知识库
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground -mt-4">
|
|
||||||
开启后,chat_history_summarizer 总结出的历史记录会同时导入到知识库
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="private_plan_style">私聊规则(实验性)</Label>
|
|
||||||
<Textarea
|
|
||||||
id="private_plan_style"
|
|
||||||
value={config.private_plan_style}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => onChange({ ...config, private_plan_style: e.target.value })}
|
|
||||||
placeholder="私聊的说话规则和行为风格(不推荐修改)"
|
|
||||||
rows={4}
|
|
||||||
/>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
⚠️ 不推荐修改此项,可能会影响私聊对话的稳定性
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<Label>特定聊天 Prompt 配置</Label>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
|
||||||
为指定聊天添加额外的 prompt,用于定制特定场景的对话行为
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button onClick={addChatPrompt} size="sm" variant="outline">
|
|
||||||
<Plus className="h-4 w-4 mr-1" />
|
|
||||||
添加配置
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
{config.chat_prompts.map((promptStr, index) => {
|
|
||||||
const data = parseChatPrompt(promptStr)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={index} className="rounded-lg border p-4 space-y-4 bg-card">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
Prompt 配置 {index + 1}
|
|
||||||
</span>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<ChatPromptPreview promptStr={promptStr} />
|
|
||||||
<AlertDialog>
|
|
||||||
<AlertDialogTrigger asChild>
|
|
||||||
<Button size="sm" variant="ghost">
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</AlertDialogTrigger>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>确认删除</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
确定要删除这个 prompt 配置吗?此操作无法撤销。
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
|
||||||
<AlertDialogAction onClick={() => removeChatPrompt(index)}>
|
|
||||||
删除
|
|
||||||
</AlertDialogAction>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-4">
|
|
||||||
{/* 平台选择 */}
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label className="text-xs font-medium">平台</Label>
|
|
||||||
<Select
|
|
||||||
value={data.platform}
|
|
||||||
onValueChange={(value) => updateChatPrompt(index, { platform: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="选择平台" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="qq">QQ</SelectItem>
|
|
||||||
<SelectItem value="wx">微信</SelectItem>
|
|
||||||
<SelectItem value="webui">WebUI</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ID 输入 */}
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label className="text-xs font-medium">
|
|
||||||
{data.type === 'group' ? '群号' : '用户ID'}
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
value={data.id}
|
|
||||||
onChange={(e) => updateChatPrompt(index, { id: e.target.value })}
|
|
||||||
placeholder={data.type === 'group' ? '输入群号' : '输入用户ID'}
|
|
||||||
className="font-mono"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 类型选择 */}
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label className="text-xs font-medium">类型</Label>
|
|
||||||
<Select
|
|
||||||
value={data.type}
|
|
||||||
onValueChange={(value: 'group' | 'private') => updateChatPrompt(index, { type: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="group">群聊 (group)</SelectItem>
|
|
||||||
<SelectItem value="private">私聊 (private)</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Prompt 内容 */}
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label className="text-xs font-medium">Prompt 内容</Label>
|
|
||||||
<Textarea
|
|
||||||
value={data.prompt}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => updateChatPrompt(index, { prompt: e.target.value })}
|
|
||||||
placeholder="输入额外的 prompt 内容,例如:这是一个摄影群,你精通摄影知识"
|
|
||||||
rows={3}
|
|
||||||
/>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
这段文本会作为系统提示添加到该聊天的上下文中
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 原始格式显示 */}
|
|
||||||
<div className="rounded-md bg-muted/50 p-3">
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
<Code2 className="h-3 w-3 text-muted-foreground" />
|
|
||||||
<span className="text-xs font-medium text-muted-foreground">原始格式</span>
|
|
||||||
</div>
|
|
||||||
<code className="text-xs font-mono text-muted-foreground break-all">
|
|
||||||
{promptStr || '(未配置)'}
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
|
|
||||||
{config.chat_prompts.length === 0 && (
|
|
||||||
<div className="text-center py-8 text-muted-foreground">
|
|
||||||
<p className="text-sm">暂无特定聊天 prompt 配置</p>
|
|
||||||
<p className="text-xs mt-1">点击上方"添加配置"按钮创建新配置</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 使用说明 */}
|
|
||||||
<div className="text-xs text-muted-foreground space-y-2 p-4 rounded-lg bg-muted/30 border">
|
|
||||||
<p className="font-medium text-foreground">💡 使用说明</p>
|
|
||||||
<ul className="list-disc list-inside space-y-1 pl-2">
|
|
||||||
<li>为不同的聊天环境配置专属的行为提示</li>
|
|
||||||
<li>支持多个平台:QQ、微信、WebUI</li>
|
|
||||||
<li>可为群聊或私聊分别配置</li>
|
|
||||||
<li>Prompt 会自动注入到该聊天的上下文中</li>
|
|
||||||
</ul>
|
|
||||||
<p className="font-medium text-foreground mt-3">📝 配置示例</p>
|
|
||||||
<ul className="list-disc list-inside space-y-1 pl-2">
|
|
||||||
<li>摄影群:<code className="text-xs bg-muted px-1 py-0.5 rounded">这是一个摄影群,你精通摄影知识</code></li>
|
|
||||||
<li>二次元群:<code className="text-xs bg-muted px-1 py-0.5 rounded">这是一个二次元交流群</code></li>
|
|
||||||
<li>好友私聊:<code className="text-xs bg-muted px-1 py-0.5 rounded">这是你与好朋友的私聊</code></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
@@ -8,7 +8,6 @@ export { DreamSection } from './DreamSection'
|
|||||||
export { LPMMSection } from './LPMMSection'
|
export { LPMMSection } from './LPMMSection'
|
||||||
export { LogSection } from './LogSection'
|
export { LogSection } from './LogSection'
|
||||||
export { DebugSection } from './DebugSection'
|
export { DebugSection } from './DebugSection'
|
||||||
export { ExperimentalSection } from './ExperimentalSection'
|
|
||||||
export { MaimMessageSection } from './MaimMessageSection'
|
export { MaimMessageSection } from './MaimMessageSection'
|
||||||
export { TelemetrySection } from './TelemetrySection'
|
export { TelemetrySection } from './TelemetrySection'
|
||||||
export { FeaturesSection } from './FeaturesSection'
|
export { FeaturesSection } from './FeaturesSection'
|
||||||
|
|||||||
@@ -189,12 +189,6 @@ export interface DebugConfig {
|
|||||||
show_lpmm_paragraph: boolean
|
show_lpmm_paragraph: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExperimentalConfig {
|
|
||||||
private_plan_style: string
|
|
||||||
chat_prompts: string[]
|
|
||||||
lpmm_memory: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MaimMessageConfig {
|
export interface MaimMessageConfig {
|
||||||
auth_token: string[]
|
auth_token: string[]
|
||||||
enable_api_server: boolean
|
enable_api_server: boolean
|
||||||
@@ -239,14 +233,12 @@ export interface AllBotConfigs {
|
|||||||
voiceConfig: VoiceConfig | null
|
voiceConfig: VoiceConfig | null
|
||||||
messageReceiveConfig: MessageReceiveConfig | null
|
messageReceiveConfig: MessageReceiveConfig | null
|
||||||
dreamConfig: DreamConfig | null
|
dreamConfig: DreamConfig | null
|
||||||
lpmmConfig: LPMMKnowledgeConfig | null
|
|
||||||
keywordReactionConfig: KeywordReactionConfig | null
|
keywordReactionConfig: KeywordReactionConfig | null
|
||||||
responsePostProcessConfig: ResponsePostProcessConfig | null
|
responsePostProcessConfig: ResponsePostProcessConfig | null
|
||||||
chineseTypoConfig: ChineseTypoConfig | null
|
chineseTypoConfig: ChineseTypoConfig | null
|
||||||
responseSplitterConfig: ResponseSplitterConfig | null
|
responseSplitterConfig: ResponseSplitterConfig | null
|
||||||
logConfig: LogConfig | null
|
logConfig: LogConfig | null
|
||||||
debugConfig: DebugConfig | null
|
debugConfig: DebugConfig | null
|
||||||
experimentalConfig: ExperimentalConfig | null
|
|
||||||
maimMessageConfig: MaimMessageConfig | null
|
maimMessageConfig: MaimMessageConfig | null
|
||||||
telemetryConfig: TelemetryConfig | null
|
telemetryConfig: TelemetryConfig | null
|
||||||
}
|
}
|
||||||
@@ -261,25 +253,21 @@ export type ConfigSectionName =
|
|||||||
| 'expression'
|
| 'expression'
|
||||||
| 'emoji'
|
| 'emoji'
|
||||||
| 'memory'
|
| 'memory'
|
||||||
| 'relationship'
|
|
||||||
| 'visual'
|
| 'visual'
|
||||||
| 'tool'
|
| 'tool'
|
||||||
| 'voice'
|
| 'voice'
|
||||||
| 'message_receive'
|
| 'message_receive'
|
||||||
| 'dream'
|
| 'dream'
|
||||||
| 'lpmm_knowledge'
|
|
||||||
| 'keyword_reaction'
|
| 'keyword_reaction'
|
||||||
| 'response_post_process'
|
| 'response_post_process'
|
||||||
| 'chinese_typo'
|
| 'chinese_typo'
|
||||||
| 'response_splitter'
|
| 'response_splitter'
|
||||||
| 'log'
|
| 'log'
|
||||||
| 'debug'
|
| 'debug'
|
||||||
| 'experimental'
|
|
||||||
| 'maim_message'
|
| 'maim_message'
|
||||||
| 'telemetry'
|
| 'telemetry'
|
||||||
| 'webui'
|
| 'webui'
|
||||||
| 'database'
|
| 'database'
|
||||||
| 'maisaka'
|
|
||||||
| 'mcp'
|
| 'mcp'
|
||||||
| 'plugin_runtime'
|
| 'plugin_runtime'
|
||||||
| 'a_memorix'
|
| 'a_memorix'
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
Gauge,
|
Gauge,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
PauseCircle,
|
PauseCircle,
|
||||||
|
Radio,
|
||||||
Timer,
|
Timer,
|
||||||
Wrench,
|
Wrench,
|
||||||
XCircle,
|
XCircle,
|
||||||
@@ -585,6 +586,8 @@ export function MaisakaMonitor() {
|
|||||||
selectedSession,
|
selectedSession,
|
||||||
setSelectedSession,
|
setSelectedSession,
|
||||||
connected,
|
connected,
|
||||||
|
backgroundCollection,
|
||||||
|
setBackgroundCollectionEnabled,
|
||||||
clearTimeline,
|
clearTimeline,
|
||||||
} = useMaisakaMonitor()
|
} = useMaisakaMonitor()
|
||||||
|
|
||||||
@@ -697,6 +700,16 @@ export function MaisakaMonitor() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-auto flex items-center gap-2">
|
<div className="ml-auto flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant={backgroundCollection ? 'secondary' : 'ghost'}
|
||||||
|
size="sm"
|
||||||
|
className="h-7 text-xs"
|
||||||
|
onClick={() => setBackgroundCollectionEnabled(!backgroundCollection)}
|
||||||
|
title={backgroundCollection ? '关闭离开页面后的持续获取' : '开启离开页面后的持续获取'}
|
||||||
|
>
|
||||||
|
<Radio className={cn('h-3.5 w-3.5 mr-1', backgroundCollection && 'text-primary')} />
|
||||||
|
持续获取
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant={showCycleMarkers ? 'secondary' : 'ghost'}
|
variant={showCycleMarkers ? 'secondary' : 'ghost'}
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -746,17 +759,19 @@ export function MaisakaMonitor() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
(() => {
|
(() => {
|
||||||
const displayedTimingGateCycles = new Set<string>()
|
const continuedTimingGateCycles = new Set<string>()
|
||||||
|
|
||||||
return timeline.map((entry) => {
|
return timeline.map((entry) => {
|
||||||
if (entry.type === 'timing_gate.result') {
|
if (entry.type === 'timing_gate.result') {
|
||||||
const data = entry.data as TimingGateResultEvent
|
const data = entry.data as TimingGateResultEvent
|
||||||
displayedTimingGateCycles.add(buildCycleKey(data.session_id, data.cycle_id))
|
if (data.action === 'continue') {
|
||||||
|
continuedTimingGateCycles.add(buildCycleKey(data.session_id, data.cycle_id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.type === 'planner.response' || entry.type === 'planner.finalized') {
|
if (entry.type === 'planner.response' || entry.type === 'planner.finalized') {
|
||||||
const data = entry.data as PlannerResponseEvent | PlannerFinalizedEvent
|
const data = entry.data as PlannerResponseEvent | PlannerFinalizedEvent
|
||||||
if (!displayedTimingGateCycles.has(buildCycleKey(data.session_id, data.cycle_id))) {
|
if (!continuedTimingGateCycles.has(buildCycleKey(data.session_id, data.cycle_id))) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* 管理 WebSocket 订阅与事件流的状态。
|
* 管理 WebSocket 订阅与事件流的状态。
|
||||||
*/
|
*/
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import type { MaisakaMonitorEvent } from '@/lib/maisaka-monitor-client'
|
import type { MaisakaMonitorEvent } from '@/lib/maisaka-monitor-client'
|
||||||
import { maisakaMonitorClient } from '@/lib/maisaka-monitor-client'
|
import { maisakaMonitorClient } from '@/lib/maisaka-monitor-client'
|
||||||
@@ -36,6 +36,7 @@ export interface SessionInfo {
|
|||||||
|
|
||||||
/** 最大保留的时间线条目数 */
|
/** 最大保留的时间线条目数 */
|
||||||
const MAX_TIMELINE_ENTRIES = 500
|
const MAX_TIMELINE_ENTRIES = 500
|
||||||
|
const BACKGROUND_COLLECTION_STORAGE_KEY = 'maisaka-monitor-background-collection'
|
||||||
|
|
||||||
function resolveSessionDisplayName({
|
function resolveSessionDisplayName({
|
||||||
fallbackName,
|
fallbackName,
|
||||||
@@ -72,152 +73,219 @@ let entryCounter = 0
|
|||||||
let cachedTimeline: TimelineEntry[] = []
|
let cachedTimeline: TimelineEntry[] = []
|
||||||
let cachedSessions: Map<string, SessionInfo> = new Map()
|
let cachedSessions: Map<string, SessionInfo> = new Map()
|
||||||
let cachedSelectedSession: string | null = null
|
let cachedSelectedSession: string | null = null
|
||||||
|
let cachedConnected = false
|
||||||
|
let backgroundCollectionEnabled = false
|
||||||
|
let backgroundCollectionPreferenceLoaded = false
|
||||||
|
let activeConsumerCount = 0
|
||||||
|
let monitorSubscriptionStarted = false
|
||||||
|
let monitorSubscriptionPromise: Promise<void> | null = null
|
||||||
|
let monitorUnsubscribe: (() => Promise<void>) | null = null
|
||||||
|
const storeListeners = new Set<() => void>()
|
||||||
|
|
||||||
|
function notifyStoreListeners() {
|
||||||
|
storeListeners.forEach((listener) => listener())
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadBackgroundCollectionPreference() {
|
||||||
|
if (backgroundCollectionPreferenceLoaded) {
|
||||||
|
return backgroundCollectionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
backgroundCollectionPreferenceLoaded = true
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
backgroundCollectionEnabled = window.localStorage.getItem(BACKGROUND_COLLECTION_STORAGE_KEY) === 'true'
|
||||||
|
}
|
||||||
|
return backgroundCollectionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldKeepMonitorActive() {
|
||||||
|
return activeConsumerCount > 0 || backgroundCollectionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendTimelineEntry(entry: TimelineEntry) {
|
||||||
|
const next = [...cachedTimeline, entry]
|
||||||
|
cachedTimeline = next.length > MAX_TIMELINE_ENTRIES
|
||||||
|
? next.slice(next.length - MAX_TIMELINE_ENTRIES)
|
||||||
|
: next
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSessionInfo(event: MaisakaMonitorEvent, sessionId: string, timestamp: number) {
|
||||||
|
const dataRecord = event.data as unknown as Record<string, unknown>
|
||||||
|
const isGroupChat = typeof dataRecord.is_group_chat === 'boolean'
|
||||||
|
? dataRecord.is_group_chat
|
||||||
|
: undefined
|
||||||
|
const groupId = typeof dataRecord.group_id === 'string' ? dataRecord.group_id : null
|
||||||
|
const userId = typeof dataRecord.user_id === 'string' ? dataRecord.user_id : null
|
||||||
|
const platform = typeof dataRecord.platform === 'string' ? dataRecord.platform : undefined
|
||||||
|
const sessionName = typeof dataRecord.session_name === 'string'
|
||||||
|
? dataRecord.session_name
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
const next = new Map(cachedSessions)
|
||||||
|
const existing = next.get(sessionId)
|
||||||
|
|
||||||
|
if (event.type === 'session.start' || !existing) {
|
||||||
|
next.set(sessionId, {
|
||||||
|
sessionId,
|
||||||
|
sessionName: resolveSessionDisplayName({
|
||||||
|
fallbackName: sessionName,
|
||||||
|
groupId,
|
||||||
|
isGroupChat,
|
||||||
|
sessionId,
|
||||||
|
userId,
|
||||||
|
}),
|
||||||
|
isGroupChat,
|
||||||
|
groupId,
|
||||||
|
userId,
|
||||||
|
platform,
|
||||||
|
lastActivity: timestamp,
|
||||||
|
eventCount: (existing?.eventCount ?? 0) + 1,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
next.set(sessionId, {
|
||||||
|
...existing,
|
||||||
|
sessionName: resolveSessionDisplayName({
|
||||||
|
fallbackName: sessionName ?? existing.sessionName,
|
||||||
|
groupId: groupId ?? existing.groupId,
|
||||||
|
isGroupChat: isGroupChat ?? existing.isGroupChat,
|
||||||
|
sessionId,
|
||||||
|
userId: userId ?? existing.userId,
|
||||||
|
}),
|
||||||
|
isGroupChat: isGroupChat ?? existing.isGroupChat,
|
||||||
|
groupId: groupId ?? existing.groupId,
|
||||||
|
userId: userId ?? existing.userId,
|
||||||
|
platform: platform ?? existing.platform,
|
||||||
|
lastActivity: timestamp,
|
||||||
|
eventCount: existing.eventCount + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedSessions = next
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMonitorEvent(event: MaisakaMonitorEvent) {
|
||||||
|
const dataRecord = event.data as unknown as Record<string, unknown>
|
||||||
|
const sessionId = dataRecord.session_id as string
|
||||||
|
const timestamp = dataRecord.timestamp as number
|
||||||
|
|
||||||
|
if (!sessionId || typeof timestamp !== 'number') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appendTimelineEntry({
|
||||||
|
id: `evt_${++entryCounter}_${Date.now()}`,
|
||||||
|
type: event.type,
|
||||||
|
data: event.data,
|
||||||
|
timestamp,
|
||||||
|
sessionId,
|
||||||
|
})
|
||||||
|
|
||||||
|
updateSessionInfo(event, sessionId, timestamp)
|
||||||
|
|
||||||
|
if (cachedSelectedSession === null) {
|
||||||
|
cachedSelectedSession = sessionId
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyStoreListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureMonitorSubscription() {
|
||||||
|
if (monitorSubscriptionStarted || monitorSubscriptionPromise !== null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
monitorSubscriptionPromise = maisakaMonitorClient
|
||||||
|
.subscribe(handleMonitorEvent)
|
||||||
|
.then((unsub) => {
|
||||||
|
monitorUnsubscribe = unsub
|
||||||
|
if (!shouldKeepMonitorActive()) {
|
||||||
|
monitorUnsubscribe = null
|
||||||
|
void unsub()
|
||||||
|
cachedConnected = false
|
||||||
|
notifyStoreListeners()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
monitorSubscriptionStarted = true
|
||||||
|
cachedConnected = true
|
||||||
|
notifyStoreListeners()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('MaiSaka 监控订阅失败:', error)
|
||||||
|
cachedConnected = false
|
||||||
|
notifyStoreListeners()
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
monitorSubscriptionPromise = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopMonitorSubscriptionIfIdle() {
|
||||||
|
if (shouldKeepMonitorActive()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monitorUnsubscribe) {
|
||||||
|
const unsub = monitorUnsubscribe
|
||||||
|
monitorUnsubscribe = null
|
||||||
|
monitorSubscriptionStarted = false
|
||||||
|
cachedConnected = false
|
||||||
|
notifyStoreListeners()
|
||||||
|
void unsub()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function useMaisakaMonitor() {
|
export function useMaisakaMonitor() {
|
||||||
const [timeline, setTimeline] = useState<TimelineEntry[]>(cachedTimeline)
|
const [timeline, setTimeline] = useState<TimelineEntry[]>(cachedTimeline)
|
||||||
const [sessions, setSessions] = useState<Map<string, SessionInfo>>(new Map(cachedSessions))
|
const [sessions, setSessions] = useState<Map<string, SessionInfo>>(new Map(cachedSessions))
|
||||||
const [selectedSession, setSelectedSessionState] = useState<string | null>(cachedSelectedSession)
|
const [selectedSession, setSelectedSessionState] = useState<string | null>(cachedSelectedSession)
|
||||||
const [connected, setConnected] = useState(false)
|
const [connected, setConnected] = useState(cachedConnected)
|
||||||
const unsubRef = useRef<(() => Promise<void>) | null>(null)
|
const [backgroundCollection, setBackgroundCollection] = useState(loadBackgroundCollectionPreference)
|
||||||
|
|
||||||
const handleEvent = useCallback((event: MaisakaMonitorEvent) => {
|
|
||||||
const dataRecord = event.data as unknown as Record<string, unknown>
|
|
||||||
const sessionId = dataRecord.session_id as string
|
|
||||||
const timestamp = dataRecord.timestamp as number
|
|
||||||
const isGroupChat = typeof dataRecord.is_group_chat === 'boolean'
|
|
||||||
? dataRecord.is_group_chat
|
|
||||||
: undefined
|
|
||||||
const groupId = typeof dataRecord.group_id === 'string' ? dataRecord.group_id : null
|
|
||||||
const userId = typeof dataRecord.user_id === 'string' ? dataRecord.user_id : null
|
|
||||||
const platform = typeof dataRecord.platform === 'string' ? dataRecord.platform : undefined
|
|
||||||
const sessionName = typeof dataRecord.session_name === 'string'
|
|
||||||
? dataRecord.session_name
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
const entry: TimelineEntry = {
|
|
||||||
id: `evt_${++entryCounter}_${Date.now()}`,
|
|
||||||
type: event.type,
|
|
||||||
data: event.data,
|
|
||||||
timestamp,
|
|
||||||
sessionId,
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeline((prev) => {
|
|
||||||
const next = [...prev, entry]
|
|
||||||
const trimmed = next.length > MAX_TIMELINE_ENTRIES
|
|
||||||
? next.slice(next.length - MAX_TIMELINE_ENTRIES)
|
|
||||||
: next
|
|
||||||
cachedTimeline = trimmed
|
|
||||||
return trimmed
|
|
||||||
})
|
|
||||||
|
|
||||||
// 更新会话信息
|
|
||||||
if (event.type === 'session.start') {
|
|
||||||
setSessions((prev) => {
|
|
||||||
const next = new Map(prev)
|
|
||||||
next.set(sessionId, {
|
|
||||||
sessionId,
|
|
||||||
sessionName: resolveSessionDisplayName({
|
|
||||||
fallbackName: sessionName,
|
|
||||||
groupId,
|
|
||||||
isGroupChat,
|
|
||||||
sessionId,
|
|
||||||
userId,
|
|
||||||
}),
|
|
||||||
isGroupChat,
|
|
||||||
groupId,
|
|
||||||
userId,
|
|
||||||
platform,
|
|
||||||
lastActivity: timestamp,
|
|
||||||
eventCount: (prev.get(sessionId)?.eventCount ?? 0) + 1,
|
|
||||||
})
|
|
||||||
cachedSessions = next
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
setSessions((prev) => {
|
|
||||||
const existing = prev.get(sessionId)
|
|
||||||
if (!existing) {
|
|
||||||
const next = new Map(prev)
|
|
||||||
next.set(sessionId, {
|
|
||||||
sessionId,
|
|
||||||
sessionName: resolveSessionDisplayName({
|
|
||||||
fallbackName: sessionName,
|
|
||||||
groupId,
|
|
||||||
isGroupChat,
|
|
||||||
sessionId,
|
|
||||||
userId,
|
|
||||||
}),
|
|
||||||
isGroupChat,
|
|
||||||
groupId,
|
|
||||||
userId,
|
|
||||||
platform,
|
|
||||||
lastActivity: timestamp,
|
|
||||||
eventCount: 1,
|
|
||||||
})
|
|
||||||
cachedSessions = next
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
const next = new Map(prev)
|
|
||||||
next.set(sessionId, {
|
|
||||||
...existing,
|
|
||||||
sessionName: resolveSessionDisplayName({
|
|
||||||
fallbackName: sessionName ?? existing.sessionName,
|
|
||||||
groupId: groupId ?? existing.groupId,
|
|
||||||
isGroupChat: isGroupChat ?? existing.isGroupChat,
|
|
||||||
sessionId,
|
|
||||||
userId: userId ?? existing.userId,
|
|
||||||
}),
|
|
||||||
isGroupChat: isGroupChat ?? existing.isGroupChat,
|
|
||||||
groupId: groupId ?? existing.groupId,
|
|
||||||
userId: userId ?? existing.userId,
|
|
||||||
platform: platform ?? existing.platform,
|
|
||||||
lastActivity: timestamp,
|
|
||||||
eventCount: existing.eventCount + 1,
|
|
||||||
})
|
|
||||||
cachedSessions = next
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自动选中第一个会话
|
|
||||||
setSelectedSessionState((current) => {
|
|
||||||
const next = current ?? sessionId
|
|
||||||
cachedSelectedSession = next
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
activeConsumerCount += 1
|
||||||
|
ensureMonitorSubscription()
|
||||||
maisakaMonitorClient.subscribe(handleEvent).then((unsub) => {
|
const syncFromStore = () => {
|
||||||
if (cancelled) {
|
setTimeline(cachedTimeline)
|
||||||
void unsub()
|
setSessions(new Map(cachedSessions))
|
||||||
return
|
setSelectedSessionState(cachedSelectedSession)
|
||||||
}
|
setConnected(cachedConnected)
|
||||||
unsubRef.current = unsub
|
setBackgroundCollection(backgroundCollectionEnabled)
|
||||||
setConnected(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
cancelled = true
|
|
||||||
if (unsubRef.current) {
|
|
||||||
void unsubRef.current()
|
|
||||||
unsubRef.current = null
|
|
||||||
}
|
|
||||||
setConnected(false)
|
|
||||||
}
|
}
|
||||||
}, [handleEvent])
|
|
||||||
|
storeListeners.add(syncFromStore)
|
||||||
|
syncFromStore()
|
||||||
|
return () => {
|
||||||
|
storeListeners.delete(syncFromStore)
|
||||||
|
activeConsumerCount = Math.max(0, activeConsumerCount - 1)
|
||||||
|
stopMonitorSubscriptionIfIdle()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const clearTimeline = useCallback(() => {
|
const clearTimeline = useCallback(() => {
|
||||||
cachedTimeline = []
|
cachedTimeline = []
|
||||||
setTimeline([])
|
setTimeline([])
|
||||||
|
notifyStoreListeners()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const setSelectedSession = useCallback((sessionId: string | null) => {
|
const setSelectedSession = useCallback((sessionId: string | null) => {
|
||||||
cachedSelectedSession = sessionId
|
cachedSelectedSession = sessionId
|
||||||
setSelectedSessionState(sessionId)
|
setSelectedSessionState(sessionId)
|
||||||
|
notifyStoreListeners()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const setBackgroundCollectionEnabled = useCallback((enabled: boolean) => {
|
||||||
|
backgroundCollectionEnabled = enabled
|
||||||
|
backgroundCollectionPreferenceLoaded = true
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.localStorage.setItem(BACKGROUND_COLLECTION_STORAGE_KEY, String(enabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
ensureMonitorSubscription()
|
||||||
|
} else {
|
||||||
|
stopMonitorSubscriptionIfIdle()
|
||||||
|
}
|
||||||
|
notifyStoreListeners()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
/** 当前选中会话的时间线 */
|
/** 当前选中会话的时间线 */
|
||||||
@@ -232,6 +300,8 @@ export function useMaisakaMonitor() {
|
|||||||
selectedSession,
|
selectedSession,
|
||||||
setSelectedSession,
|
setSelectedSession,
|
||||||
connected,
|
connected,
|
||||||
|
backgroundCollection,
|
||||||
|
setBackgroundCollectionEnabled,
|
||||||
clearTimeline,
|
clearTimeline,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,44 @@ from src.maisaka.builtin_tool import reply as reply_tool_module
|
|||||||
from src.maisaka.builtin_tool import send_emoji as send_emoji_tool_module
|
from src.maisaka.builtin_tool import send_emoji as send_emoji_tool_module
|
||||||
from src.maisaka.monitor_events import emit_planner_finalized
|
from src.maisaka.monitor_events import emit_planner_finalized
|
||||||
from src.maisaka.reasoning_engine import MaisakaReasoningEngine
|
from src.maisaka.reasoning_engine import MaisakaReasoningEngine
|
||||||
|
from src.maisaka import runtime as runtime_module
|
||||||
from src.maisaka.runtime import MaisakaHeartFlowChatting
|
from src.maisaka.runtime import MaisakaHeartFlowChatting
|
||||||
|
|
||||||
|
|
||||||
|
def test_runtime_maps_expression_config_flags_to_correct_fields(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
fake_chat_stream = SimpleNamespace(
|
||||||
|
is_group_session=True,
|
||||||
|
group_id="group-1",
|
||||||
|
user_id="user-1",
|
||||||
|
platform="test",
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
runtime_module.chat_manager,
|
||||||
|
"get_session_by_session_id",
|
||||||
|
lambda session_id: fake_chat_stream,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(runtime_module.chat_manager, "get_session_name", lambda session_id: "测试会话")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
runtime_module.ExpressionConfigUtils,
|
||||||
|
"get_expression_config_for_chat",
|
||||||
|
staticmethod(lambda session_id: (True, False, True)),
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(runtime_module, "ExpressionLearner", lambda session_id: SimpleNamespace())
|
||||||
|
monkeypatch.setattr(runtime_module, "JargonMiner", lambda session_id, session_name: SimpleNamespace())
|
||||||
|
monkeypatch.setattr(runtime_module, "MaisakaReasoningEngine", lambda runtime: SimpleNamespace())
|
||||||
|
monkeypatch.setattr(runtime_module, "ToolRegistry", lambda: SimpleNamespace())
|
||||||
|
monkeypatch.setattr(runtime_module, "ReplyEffectTracker", lambda **kwargs: SimpleNamespace())
|
||||||
|
monkeypatch.setattr(MaisakaHeartFlowChatting, "_register_tool_providers", lambda self: None)
|
||||||
|
monkeypatch.setattr(MaisakaHeartFlowChatting, "_emit_monitor_session_start", lambda self: None)
|
||||||
|
|
||||||
|
runtime = MaisakaHeartFlowChatting("session-1")
|
||||||
|
|
||||||
|
assert runtime._enable_expression_use is True
|
||||||
|
assert runtime._enable_expression_learning is False
|
||||||
|
assert runtime._enable_jargon_learning is True
|
||||||
|
|
||||||
|
|
||||||
class _FakeLLMResult:
|
class _FakeLLMResult:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.response = "测试回复"
|
self.response = "测试回复"
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ httpx
|
|||||||
jieba>=0.42.1
|
jieba>=0.42.1
|
||||||
json-repair>=0.47.6
|
json-repair>=0.47.6
|
||||||
maim-message>=0.6.2
|
maim-message>=0.6.2
|
||||||
maibot-dashboard>=1.0.2.dev2026050359
|
|
||||||
maibot-plugin-sdk>=2.4.0
|
maibot-plugin-sdk>=2.4.0
|
||||||
matplotlib>=3.10.5
|
matplotlib>=3.10.5
|
||||||
mcp
|
mcp
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class MaisakaHeartFlowChatting:
|
|||||||
int(global_config.chat.planner_interrupt_max_consecutive_count),
|
int(global_config.chat.planner_interrupt_max_consecutive_count),
|
||||||
)
|
)
|
||||||
|
|
||||||
expr_use, jargon_learn, expr_learn = ExpressionConfigUtils.get_expression_config_for_chat(session_id)
|
expr_use, expr_learn, jargon_learn = ExpressionConfigUtils.get_expression_config_for_chat(session_id)
|
||||||
self._enable_expression_use = expr_use
|
self._enable_expression_use = expr_use
|
||||||
self._enable_expression_learning = expr_learn
|
self._enable_expression_learning = expr_learn
|
||||||
self._enable_jargon_learning = jargon_learn
|
self._enable_jargon_learning = jargon_learn
|
||||||
|
|||||||
Reference in New Issue
Block a user