From a5e4ac853152fb165384f1528675c775f3f68f2c Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 5 May 2026 17:57:19 +0800 Subject: [PATCH 1/3] =?UTF-8?q?pref=EF=BC=9A=E4=BC=98=E5=8C=96webui?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=EF=BC=8C=E5=A2=9E=E5=8A=A0prompt=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=85=83=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 3 + .../dynamic-form/DynamicConfigForm.tsx | 4 + dashboard/src/lib/field-hooks.ts | 1 + dashboard/src/lib/prompt-api.ts | 3 + dashboard/src/lib/version.ts | 2 +- dashboard/src/routes/config/bot.tsx | 5 +- .../bot/hooks/ListItemEditorHookFactory.tsx | 46 ++- .../config/bot/hooks/complexFieldHooks.tsx | 296 +++++++++++++++++- .../config/bot/sections/BotInfoSection.tsx | 38 +-- .../config/bot/sections/FeaturesSection.tsx | 17 - dashboard/src/routes/config/bot/types.ts | 1 - dashboard/src/routes/config/prompts.tsx | 64 +++- prompts/en-US/.meta.toml | 79 +++++ prompts/en-US/emoji_content_filtration.prompt | 2 +- prompts/en-US/hippo_topic_analysis.prompt | 27 -- prompts/en-US/hippo_topic_summary.prompt | 22 -- .../en-US/maidairy_knowledge_category.prompt | 18 -- .../en-US/maidairy_knowledge_extract.prompt | 17 - .../en-US/maidairy_knowledge_retrieve.prompt | 19 -- prompts/ja-JP/.meta.toml | 79 +++++ prompts/ja-JP/emoji_content_filtration.prompt | 2 +- prompts/ja-JP/hippo_topic_analysis.prompt | 27 -- prompts/ja-JP/hippo_topic_summary.prompt | 22 -- .../ja-JP/maidairy_knowledge_category.prompt | 18 -- .../ja-JP/maidairy_knowledge_extract.prompt | 17 - .../ja-JP/maidairy_knowledge_retrieve.prompt | 19 -- prompts/zh-CN/.meta.toml | 74 +++++ prompts/zh-CN/emoji_content_filtration.prompt | 2 +- prompts/zh-CN/hippo_topic_analysis.prompt | 27 -- prompts/zh-CN/hippo_topic_summary.prompt | 22 -- .../zh-CN/maidairy_knowledge_category.prompt | 18 -- .../zh-CN/maidairy_knowledge_extract.prompt | 17 - .../zh-CN/maidairy_knowledge_retrieve.prompt | 19 -- pytests/image_sys_test/emoji_manager_test.py | 10 +- pytests/prompt_test/test_prompt_i18n.py | 41 ++- src/common/prompt_i18n.py | 101 +++++- src/config/config.py | 2 +- src/config/official_configs.py | 13 +- src/emoji_system/emoji_manager.py | 7 +- src/maisaka/builtin_tool/send_emoji.py | 21 +- src/prompt/prompt_manager.py | 4 +- src/webui/routers/config.py | 10 + 42 files changed, 826 insertions(+), 410 deletions(-) create mode 100644 prompts/en-US/.meta.toml delete mode 100644 prompts/en-US/hippo_topic_analysis.prompt delete mode 100644 prompts/en-US/hippo_topic_summary.prompt delete mode 100644 prompts/en-US/maidairy_knowledge_category.prompt delete mode 100644 prompts/en-US/maidairy_knowledge_extract.prompt delete mode 100644 prompts/en-US/maidairy_knowledge_retrieve.prompt create mode 100644 prompts/ja-JP/.meta.toml delete mode 100644 prompts/ja-JP/hippo_topic_analysis.prompt delete mode 100644 prompts/ja-JP/hippo_topic_summary.prompt delete mode 100644 prompts/ja-JP/maidairy_knowledge_category.prompt delete mode 100644 prompts/ja-JP/maidairy_knowledge_extract.prompt delete mode 100644 prompts/ja-JP/maidairy_knowledge_retrieve.prompt create mode 100644 prompts/zh-CN/.meta.toml delete mode 100644 prompts/zh-CN/hippo_topic_analysis.prompt delete mode 100644 prompts/zh-CN/hippo_topic_summary.prompt delete mode 100644 prompts/zh-CN/maidairy_knowledge_category.prompt delete mode 100644 prompts/zh-CN/maidairy_knowledge_extract.prompt delete mode 100644 prompts/zh-CN/maidairy_knowledge_retrieve.prompt diff --git a/AGENTS.md b/AGENTS.md index f164dc95..e977b378 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,6 +44,9 @@ # 关于 A_memorix 修改 如果修改涉及 `src/A_memorix`,请先阅读 `src/A_memorix/MODIFICATION_POLICY.md`。 +# prompt模板、 +涉及对prompt模板的修改,要同步修改英文和日文的文件,对齐到中文 + 默认原则: 1. `src/A_memorix` 的实现层改动应优先遵守 `src/A_memorix/MODIFICATION_POLICY.md` 中的归属约束。 2. 不要提交无边界的 `ruff`、格式化、导入整理或大面积实现整理。 diff --git a/dashboard/src/components/dynamic-form/DynamicConfigForm.tsx b/dashboard/src/components/dynamic-form/DynamicConfigForm.tsx index 44704bda..4bc866ff 100644 --- a/dashboard/src/components/dynamic-form/DynamicConfigForm.tsx +++ b/dashboard/src/components/dynamic-form/DynamicConfigForm.tsx @@ -175,6 +175,7 @@ export const DynamicConfigForm: React.FC = ({ value={values[field.name]} onChange={(v) => onChange(field.name, v)} schema={field} + parentValues={values} /> ) } @@ -185,6 +186,7 @@ export const DynamicConfigForm: React.FC = ({ value={values[field.name]} onChange={(v) => onChange(field.name, v)} schema={field} + parentValues={values} > = ({ onChange={(v) => onChange(key, v)} schema={nestedField ?? nestedSchema} nestedSchema={nestedSchema} + parentValues={values} /> ) @@ -313,6 +316,7 @@ export const DynamicConfigForm: React.FC = ({ onChange={(v) => onChange(key, v)} schema={nestedField ?? nestedSchema} nestedSchema={nestedSchema} + parentValues={values} > void children?: ReactNode schema?: ConfigSchema | FieldSchema + parentValues?: Record /** * 如果当前字段是 `List[ConfigBase]` 或嵌套 ConfigBase, * 这里会传入对应子配置类的 ConfigSchema,便于自定义编辑器 diff --git a/dashboard/src/lib/prompt-api.ts b/dashboard/src/lib/prompt-api.ts index 79da5248..96e967d8 100644 --- a/dashboard/src/lib/prompt-api.ts +++ b/dashboard/src/lib/prompt-api.ts @@ -8,6 +8,9 @@ export interface PromptFileInfo { name: string size: number modified_at: number + display_name: string + advanced: boolean + description: string } export interface PromptCatalog { diff --git a/dashboard/src/lib/version.ts b/dashboard/src/lib/version.ts index 03625ffb..b9799351 100644 --- a/dashboard/src/lib/version.ts +++ b/dashboard/src/lib/version.ts @@ -5,7 +5,7 @@ * 修改此处的版本号后,所有展示版本的地方都会自动更新 */ -export const APP_VERSION = '1.0.3' +export const APP_VERSION = '1.0.5' export const APP_NAME = 'MaiBot Dashboard' export const APP_FULL_NAME = `${APP_NAME} v${APP_VERSION}` diff --git a/dashboard/src/routes/config/bot.tsx b/dashboard/src/routes/config/bot.tsx index a3a1da29..03fdac29 100644 --- a/dashboard/src/routes/config/bot.tsx +++ b/dashboard/src/routes/config/bot.tsx @@ -48,11 +48,10 @@ const TOAST_DISPLAY_DELAY = 500 /** Tab 标签页的首选排列顺序 (host field name) */ const TAB_ORDER = [ 'bot', - 'personality', 'chat', 'expression', - 'visual', 'a_memorix', + 'visual', 'message_receive', 'emoji', 'voice', @@ -65,10 +64,8 @@ const TAB_ORDER = [ /** 默认展示的主配置栏目 */ const DEFAULT_VISIBLE_TAB_IDS = new Set([ 'bot', - 'personality', 'chat', 'expression', - 'visual', 'a_memorix', ]) diff --git a/dashboard/src/routes/config/bot/hooks/ListItemEditorHookFactory.tsx b/dashboard/src/routes/config/bot/hooks/ListItemEditorHookFactory.tsx index 127564ac..216beb2d 100644 --- a/dashboard/src/routes/config/bot/hooks/ListItemEditorHookFactory.tsx +++ b/dashboard/src/routes/config/bot/hooks/ListItemEditorHookFactory.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, type CSSProperties } from 'react' +import { useCallback, useEffect, useMemo, useState, type CSSProperties } from 'react' import * as LucideIcons from 'lucide-react' import { Plus, Trash2 } from 'lucide-react' @@ -37,6 +37,11 @@ export interface ListItemEditorOptions { fieldSchemaOverrides?: Record> /** 添加按钮位置 */ addButtonPlacement?: 'top' | 'bottom' + /** 根据同级配置决定是否默认折叠 */ + collapseWhen?: (context: { parentValues?: Record }) => boolean + collapsedText?: string + expandLabel?: string + collapseLabel?: string } function resolveLabel(schema?: ConfigSchema | FieldSchema, fieldPath?: string): string { @@ -159,6 +164,7 @@ export function createListItemEditorHook( onChange, schema, nestedSchema, + parentValues, value, }) => { const items = useMemo[]>(() => { @@ -283,6 +289,16 @@ export function createListItemEditorHook( const description = resolveDescription(schema) const iconName = resolveIconName(options.iconName, schema, nestedSchema) const addButtonPlacement = options.addButtonPlacement ?? 'bottom' + const shouldCollapse = options.collapseWhen?.({ parentValues }) ?? false + const [manuallyExpanded, setManuallyExpanded] = useState(false) + const collapsed = shouldCollapse && !manuallyExpanded + + useEffect(() => { + if (!shouldCollapse) { + setManuallyExpanded(false) + } + }, [shouldCollapse]) + const addButton = ( + )} {description && ( {description} @@ -322,6 +352,12 @@ export function createListItemEditorHook( )} + {collapsed ? ( +
+ {options.collapsedText ?? '当前配置已折叠,可手动展开查看或编辑。'} +
+ ) : ( + <> {addButtonPlacement === 'top' && addButton} {items.length === 0 ? (
@@ -360,6 +396,8 @@ export function createListItemEditorHook( }) )} {addButtonPlacement === 'bottom' && addButton} + + )} ) diff --git a/dashboard/src/routes/config/bot/hooks/complexFieldHooks.tsx b/dashboard/src/routes/config/bot/hooks/complexFieldHooks.tsx index 2bb182c3..68de74ce 100644 --- a/dashboard/src/routes/config/bot/hooks/complexFieldHooks.tsx +++ b/dashboard/src/routes/config/bot/hooks/complexFieldHooks.tsx @@ -1,6 +1,33 @@ +import { Plus, Trash2 } from 'lucide-react' + +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import type { FieldHookComponent } from '@/lib/field-hooks' + import { createJsonFieldHook } from './JsonFieldHookFactory' import { createListItemEditorHook } from './ListItemEditorHookFactory' +type ExpressionRuleType = 'group' | 'private' + +interface ExpressionGroupTarget { + platform: string + item_id: string + rule_type: ExpressionRuleType +} + +interface ExpressionGroupValue { + expression_groups: ExpressionGroupTarget[] +} + const ruleTypeLabel = (rule: unknown) => { if (rule === 'private') return '私聊' if (rule === 'group') return '群聊' @@ -28,9 +55,60 @@ const collectStringList = (value: unknown): string[] => { .filter((item) => item.length > 0) } +const normalizeExpressionRuleType = (value: unknown): ExpressionRuleType => { + return value === 'private' ? 'private' : 'group' +} + +const normalizeExpressionTarget = (value: unknown): ExpressionGroupTarget => { + const source = + value && typeof value === 'object' + ? (value as Record) + : {} + return { + platform: + typeof source.platform === 'string' ? source.platform.trim() : 'qq', + item_id: + typeof source.item_id === 'string' ? source.item_id.trim() : '', + rule_type: normalizeExpressionRuleType(source.rule_type), + } +} + +const normalizeExpressionGroups = (value: unknown): ExpressionGroupValue[] => { + if (!Array.isArray(value)) return [] + return value.map((item) => { + const source = + item && typeof item === 'object' + ? (item as Record) + : {} + const members = Array.isArray(source.expression_groups) + ? source.expression_groups.map(normalizeExpressionTarget) + : [] + return { expression_groups: members } + }) +} + +const createExpressionTarget = (): ExpressionGroupTarget => ({ + platform: 'qq', + item_id: '', + rule_type: 'group', +}) + +const formatExpressionTarget = (target: ExpressionGroupTarget): string => { + const platform = target.platform.trim() + const itemId = target.item_id.trim() + const rule = ruleTypeLabel(target.rule_type) + if (!platform && !itemId) return `全局 · ${rule}` + if (!itemId) return `${platform} · ${rule}` + return `${platform}:${itemId} · ${rule}` +} + export const ChatTalkValueRulesHook = createListItemEditorHook({ addLabel: '添加发言频率规则', addButtonPlacement: 'top', + collapseWhen: ({ parentValues }) => parentValues?.enable_talk_value_rules === false, + collapsedText: '动态发言频率规则未启用,规则列表已折叠。展开后仍可查看或编辑已有规则。', + expandLabel: '展开规则', + collapseLabel: '折叠规则', helperText: '可按平台/聊天流/时段分别配置发言频率,留空表示全局。', emptyText: '尚未配置任何规则,将使用全局默认频率。', fieldRows: [ @@ -135,11 +213,219 @@ export const RegexRulesHook = createListItemEditorHook({ }, }) -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 ExpressionGroupsHook: FieldHookComponent = ({ onChange, value }) => { + const groups = normalizeExpressionGroups(value) + + const updateGroups = (nextGroups: ExpressionGroupValue[]) => { + onChange?.(nextGroups) + } + + const addGroup = () => { + updateGroups([...groups, { expression_groups: [] }]) + } + + const removeGroup = (groupIndex: number) => { + updateGroups(groups.filter((_, index) => index !== groupIndex)) + } + + const addMember = (groupIndex: number) => { + updateGroups( + groups.map((group, index) => + index === groupIndex + ? { + expression_groups: [ + ...group.expression_groups, + createExpressionTarget(), + ], + } + : group + ) + ) + } + + const removeMember = (groupIndex: number, memberIndex: number) => { + updateGroups( + groups.map((group, index) => + index === groupIndex + ? { + expression_groups: group.expression_groups.filter( + (_, currentMemberIndex) => currentMemberIndex !== memberIndex + ), + } + : group + ) + ) + } + + const updateMember = ( + groupIndex: number, + memberIndex: number, + patch: Partial + ) => { + updateGroups( + groups.map((group, index) => + index === groupIndex + ? { + expression_groups: group.expression_groups.map( + (member, currentMemberIndex) => + currentMemberIndex === memberIndex + ? { ...member, ...patch } + : member + ), + } + : group + ) + ) + } + + return ( +
+
+
+

表达互通组

+

+ 每个互通组内的聊天流会共享已学习的表达方式。成员会保存为 + expression_groups 数组结构。 +

+
+ +
+ + {groups.length === 0 ? ( +
+ 暂无互通组,点击“添加互通组”开始配置。 +
+ ) : ( +
+ {groups.map((group, groupIndex) => ( +
+
+
+ + 互通组 {groupIndex + 1} + + + {group.expression_groups.length} 个成员 + +
+
+ + +
+
+ + {group.expression_groups.length === 0 ? ( +
+ 这个互通组还没有成员。 +
+ ) : ( +
+ {group.expression_groups.map((member, memberIndex) => ( +
+
+ + + updateMember(groupIndex, memberIndex, { + platform: event.target.value, + }) + } + /> +
+
+ + + updateMember(groupIndex, memberIndex, { + item_id: event.target.value, + }) + } + /> +
+
+ + +
+
+ + {formatExpressionTarget(member)} + + +
+
+ ))} +
+ )} + + {group.expression_groups.length > 0 && ( +
+ {group.expression_groups.map((member, memberIndex) => ( + + {formatExpressionTarget(member)} + + ))} +
+ )} +
+ ))} +
+ )} +
+ ) +} export const MCPRootItemsHook = createJsonFieldHook({ emptyValue: [], diff --git a/dashboard/src/routes/config/bot/sections/BotInfoSection.tsx b/dashboard/src/routes/config/bot/sections/BotInfoSection.tsx index 49808601..d1c64e69 100644 --- a/dashboard/src/routes/config/bot/sections/BotInfoSection.tsx +++ b/dashboard/src/routes/config/bot/sections/BotInfoSection.tsx @@ -63,27 +63,29 @@ export const BotInfoSection = React.memo(function BotInfoSection({ config, onCha return (
-

基本信息

+

基础

-
- - onChange({ ...config, platform: e.target.value })} - placeholder="qq" - /> -
+
+
+ + onChange({ ...config, platform: e.target.value })} + placeholder="qq" + /> +
-
- - onChange({ ...config, qq_account: e.target.value })} - placeholder="123456789" - /> +
+ + onChange({ ...config, qq_account: e.target.value })} + placeholder="123456789" + /> +
diff --git a/dashboard/src/routes/config/bot/sections/FeaturesSection.tsx b/dashboard/src/routes/config/bot/sections/FeaturesSection.tsx index 40b4c2e4..af1432b2 100644 --- a/dashboard/src/routes/config/bot/sections/FeaturesSection.tsx +++ b/dashboard/src/routes/config/bot/sections/FeaturesSection.tsx @@ -311,23 +311,6 @@ export const FeaturesSection = React.memo(function FeaturesSection({ 启用表情包过滤
- - {emojiConfig.content_filtration && ( -
- - - onEmojiChange({ ...emojiConfig, filtration_prompt: e.target.value }) - } - placeholder="符合公序良俗" - /> -

- 只有符合此要求的表情包才会被保存 -

-
- )}
diff --git a/dashboard/src/routes/config/bot/types.ts b/dashboard/src/routes/config/bot/types.ts index 706a9575..5e324468 100644 --- a/dashboard/src/routes/config/bot/types.ts +++ b/dashboard/src/routes/config/bot/types.ts @@ -78,7 +78,6 @@ export interface EmojiConfig { check_interval: number steal_emoji: boolean content_filtration: boolean - filtration_prompt: string } export interface MemoryConfig { diff --git a/dashboard/src/routes/config/prompts.tsx b/dashboard/src/routes/config/prompts.tsx index 81985f5d..b7382950 100644 --- a/dashboard/src/routes/config/prompts.tsx +++ b/dashboard/src/routes/config/prompts.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useState } from 'react' -import { FileText, Loader2, RefreshCw, Save, Search } from 'lucide-react' +import { FileText, Loader2, RefreshCw, Save, Search, SlidersHorizontal } from 'lucide-react' import { CodeEditor } from '@/components/CodeEditor' import { Badge } from '@/components/ui/badge' @@ -36,6 +36,7 @@ export function PromptManagementPage() { const [loadingFile, setLoadingFile] = useState(false) const [saving, setSaving] = useState(false) const [query, setQuery] = useState('') + const [showAdvancedPrompts, setShowAdvancedPrompts] = useState(false) const hasUnsavedChanges = content !== savedContent @@ -44,13 +45,30 @@ export function PromptManagementPage() { return catalog.files[language] ?? [] }, [catalog, language]) + const visiblePromptFiles = useMemo(() => { + return showAdvancedPrompts ? promptFiles : promptFiles.filter((file) => !file.advanced) + }, [promptFiles, showAdvancedPrompts]) + const filteredFiles = useMemo(() => { const normalizedQuery = query.trim().toLowerCase() - if (!normalizedQuery) return promptFiles - return promptFiles.filter((file) => file.name.toLowerCase().includes(normalizedQuery)) - }, [promptFiles, query]) + if (!normalizedQuery) return visiblePromptFiles + return visiblePromptFiles.filter((file) => { + const searchableText = [ + file.name, + file.display_name, + file.description, + ].join(' ').toLowerCase() + return searchableText.includes(normalizedQuery) + }) + }, [visiblePromptFiles, query]) const selectedFile = promptFiles.find((file) => file.name === filename) + useEffect(() => { + if (!filename || showAdvancedPrompts) return + const currentFile = promptFiles.find((file) => file.name === filename) + if (!currentFile?.advanced) return + setFilename(visiblePromptFiles[0]?.name ?? '') + }, [filename, promptFiles, showAdvancedPrompts, visiblePromptFiles]) const loadCatalog = useCallback(async () => { try { @@ -70,7 +88,10 @@ export function PromptManagementPage() { setLanguage(nextLanguage) const nextFiles = nextLanguage ? result.data.files[nextLanguage] ?? [] : [] - setFilename((current) => nextFiles.some((file) => file.name === current) ? current : nextFiles[0]?.name ?? '') + const nextBasicFiles = nextFiles.filter((file) => !file.advanced) + setFilename((current) => + nextFiles.some((file) => file.name === current) ? current : nextBasicFiles[0]?.name ?? nextFiles[0]?.name ?? '' + ) } catch (error) { toast({ title: '加载 Prompt 目录失败', @@ -130,7 +151,8 @@ export function PromptManagementPage() { setLanguage(nextLanguage) setQuery('') const nextFiles = catalog?.files[nextLanguage] ?? [] - setFilename(nextFiles[0]?.name ?? '') + const nextVisibleFiles = showAdvancedPrompts ? nextFiles : nextFiles.filter((file) => !file.advanced) + setFilename(nextVisibleFiles[0]?.name ?? '') } const handleSave = async () => { @@ -181,6 +203,14 @@ export function PromptManagementPage() { 刷新 + )) ) : ( @@ -240,12 +278,18 @@ export function PromptManagementPage() {
- {filename || '未选择文件'} + + {selectedFile?.display_name || filename || '未选择文件'} + {selectedFile?.advanced && 高级} +

{language} {selectedFile ? ` · ${formatFileSize(selectedFile.size)}` : ''} {hasUnsavedChanges ? ' · 有未保存修改' : ''}

+ {selectedFile?.description && ( +

{selectedFile.description}

+ )}
diff --git a/prompts/en-US/.meta.toml b/prompts/en-US/.meta.toml new file mode 100644 index 00000000..6d0fa529 --- /dev/null +++ b/prompts/en-US/.meta.toml @@ -0,0 +1,79 @@ +[maisaka_chat] +display_name = "Planner" +advanced = false +description = "Maisaka 主规划模板,负责整合身份、时间、工具说明和聊天上下文,驱动主循环进行思考、决策与工具调用。" + +[maisaka_replyer] +display_name = "Replyer" +advanced = false +description = "Maisaka 回复生成模板,负责根据人格、表达风格、群聊注意事项和待回复上下文生成最终回复。" + +[default_expressor] +display_name = "默认表达器" +advanced = true +description = "表达方式生成与整理相关模板,通常只在调试表达系统时需要修改。" + +[emoji_content_analysis] +display_name = "表情包内容分析" +advanced = true +description = "用于分析表情包图片内容的模板。" + +[emoji_content_filtration] +display_name = "表情包内容过滤" +advanced = true +description = "用于判断表情包内容是否符合过滤要求的模板。" + +[emoji_replace] +display_name = "表情包替换" +advanced = true +description = "用于根据文本语境选择或替换表情包的模板。" + +[expression_evaluation] +display_name = "表达评价" +advanced = true +description = "用于评价候选表达方式质量的模板。" + +[expression_select] +display_name = "表达选择" +advanced = true +description = "用于从表达库中选择合适表达方式的模板。" + +[image_description] +display_name = "图片描述" +advanced = true +description = "用于将图片内容转换为文本描述的模板。" + +[jargon_compare_inference] +display_name = "术语对比推理" +advanced = true +description = "用于比较和推理群内术语含义的模板。" + +[jargon_explainer_summarize] +display_name = "术语解释总结" +advanced = true +description = "用于总结术语解释结果的模板。" + +[jargon_inference_content_only] +display_name = "术语推理" +advanced = true +description = "用于仅基于内容推理术语含义的模板。" + +[jargon_inference_with_context] +display_name = "术语上下文推理" +advanced = true +description = "用于结合上下文推理术语含义的模板。" + +[learn_style] +display_name = "表达风格学习" +advanced = true +description = "用于从聊天内容中学习表达风格的模板。" + +[maisaka_timing_gate] +display_name = "Timing Gate" +advanced = false +description = "Maisaka 节奏控制模板,负责在每轮主循环前判断当前应该等待、停止,还是继续进入思考与回复流程。" + +[memory_retrieval_react_prompt_head_memory] +display_name = "记忆检索 ReAct 头部" +advanced = true +description = "ReAct 式记忆查询流程使用的高级记忆检索提示词头部。" diff --git a/prompts/en-US/emoji_content_filtration.prompt b/prompts/en-US/emoji_content_filtration.prompt index 65cbd443..9dc46784 100644 --- a/prompts/en-US/emoji_content_filtration.prompt +++ b/prompts/en-US/emoji_content_filtration.prompt @@ -1,5 +1,5 @@ This is a sticker. Please review it according to the following criteria: -1. It must meet the requirement of "{demand}" +1. It must conform to public order and good morals 2. It must not contain pornography, violence, or other illegal or non-compliant content, and it must conform to public order and good morals 3. It must not be any form of screenshot, chat record, or video screenshot 4. It must not contain more than 5 words diff --git a/prompts/en-US/hippo_topic_analysis.prompt b/prompts/en-US/hippo_topic_analysis.prompt deleted file mode 100644 index 0fce62eb..00000000 --- a/prompts/en-US/hippo_topic_analysis.prompt +++ /dev/null @@ -1,27 +0,0 @@ -[Historical Topic Title List] (titles only, no specific content): -{history_topics_block} -[End of Historical Topic Title List] - -[Current Chat Log] (each message has an index before it for later reference): -{messages_block} -[End of Current Chat Log] - -Please complete the following tasks: -**Identify topics** -1. Identify one or more ongoing topics in the [Current Chat Log]; -2. Messages in the [Current Chat Log] may be related to historical topics, or they may be completely unrelated; -3. Determine whether the topics in the [Historical Topic Title List] appear in the [Current Chat Log]. If they do, directly use that historical topic title string; - -**Select messages** -1. For each topic (whether new or historical), select a list of message indices from the numbered messages above that are strongly related to that topic; -2. For each topic, use one sentence to clearly describe the event that is happening. It must include time (approximate is fine), people, the main event, and the theme, ensuring accuracy and distinction; - -Please first output a short piece of reasoning explaining what topics exist, which are not included in the historical topics, which are included in the historical topics, and why; -Then strictly output the topics involved in the [Current Chat Log] in JSON format as follows: -[ - {{ - "topic": "topic", - "message_indices": [1, 2, 5] - }}, - ... -] diff --git a/prompts/en-US/hippo_topic_summary.prompt b/prompts/en-US/hippo_topic_summary.prompt deleted file mode 100644 index a8d843ef..00000000 --- a/prompts/en-US/hippo_topic_summary.prompt +++ /dev/null @@ -1,22 +0,0 @@ -Please summarize the following chat record segment based on the topic and extract the following information: - -**Topic**: {topic} - -**Requirements**: -1. Keywords: extract keywords related to the topic and return them as a list (3-10 keywords) -2. Summary: provide a plain-text summary of this segment (50-200 words). Requirements: - - Carefully retell the event and the chat content; - - Highlight the development process and the result of the event; - - Summarize around the central topic; - - Extract the key information points in the topic, and keep them concise and clear. - -Please return in JSON format as follows: -{{ - "keywords": ["keyword1", "keyword2", ...], - "summary": "summary content" -}} - -Chat record: -{original_text} - -Please return JSON directly and do not include any other content. diff --git a/prompts/en-US/maidairy_knowledge_category.prompt b/prompts/en-US/maidairy_knowledge_category.prompt deleted file mode 100644 index 510de367..00000000 --- a/prompts/en-US/maidairy_knowledge_category.prompt +++ /dev/null @@ -1,18 +0,0 @@ -You are an expert in analyzing user trait categories. Your task is to analyze the conversation content and determine which personal trait categories are involved. - -Please carefully read the following conversation content and determine which personal trait categories are involved. - -[Personal Trait Category List] -{categories_summary} - -[Task Requirements] -1. Analyze the conversation content and determine which personal trait categories are involved -2. Output only the category numbers involved, separated by spaces -3. If the conversation content does not involve any personal trait category, output "none" - -[Output Format Example] -1 3 5 -or -none - -Please start analyzing: diff --git a/prompts/en-US/maidairy_knowledge_extract.prompt b/prompts/en-US/maidairy_knowledge_extract.prompt deleted file mode 100644 index 61667a2d..00000000 --- a/prompts/en-US/maidairy_knowledge_extract.prompt +++ /dev/null @@ -1,17 +0,0 @@ -You are an expert in extracting user trait information. Your task is to extract personal trait information related to the specified category from the conversation content. - -[Target Category] -{category_name} - -[Task Requirements] -1. Carefully read the conversation content and find all information related to "{category_name}" -2. The extracted information should be specific and accurate, avoiding vague descriptions -3. If there are multiple relevant pieces of information, merge them into one concise description -4. If there is no information related to this category in the conversation, output "none" - -[Output Format Example] -The user is rather introverted and does not like speaking when many people are around, but becomes very lively with close friends. -or -none - -Please start extracting: diff --git a/prompts/en-US/maidairy_knowledge_retrieve.prompt b/prompts/en-US/maidairy_knowledge_retrieve.prompt deleted file mode 100644 index 9208377f..00000000 --- a/prompts/en-US/maidairy_knowledge_retrieve.prompt +++ /dev/null @@ -1,19 +0,0 @@ -You are an expert in retrieving user traits. Your task is to determine which categories of personal trait information need to be retrieved based on the current conversation context. - -[Current Conversation Context] -{chat_context} - -[Personal Trait Category List] -{categories_summary} - -[Task Requirements] -1. Analyze the current conversation context and determine which personal trait information is needed to help understand the user -2. Output only the needed category numbers, separated by spaces -3. If the current conversation does not need any personal trait information, output "none" - -[Output Format Example] -2 5 8 -or -none - -Please start analyzing: diff --git a/prompts/ja-JP/.meta.toml b/prompts/ja-JP/.meta.toml new file mode 100644 index 00000000..6d0fa529 --- /dev/null +++ b/prompts/ja-JP/.meta.toml @@ -0,0 +1,79 @@ +[maisaka_chat] +display_name = "Planner" +advanced = false +description = "Maisaka 主规划模板,负责整合身份、时间、工具说明和聊天上下文,驱动主循环进行思考、决策与工具调用。" + +[maisaka_replyer] +display_name = "Replyer" +advanced = false +description = "Maisaka 回复生成模板,负责根据人格、表达风格、群聊注意事项和待回复上下文生成最终回复。" + +[default_expressor] +display_name = "默认表达器" +advanced = true +description = "表达方式生成与整理相关模板,通常只在调试表达系统时需要修改。" + +[emoji_content_analysis] +display_name = "表情包内容分析" +advanced = true +description = "用于分析表情包图片内容的模板。" + +[emoji_content_filtration] +display_name = "表情包内容过滤" +advanced = true +description = "用于判断表情包内容是否符合过滤要求的模板。" + +[emoji_replace] +display_name = "表情包替换" +advanced = true +description = "用于根据文本语境选择或替换表情包的模板。" + +[expression_evaluation] +display_name = "表达评价" +advanced = true +description = "用于评价候选表达方式质量的模板。" + +[expression_select] +display_name = "表达选择" +advanced = true +description = "用于从表达库中选择合适表达方式的模板。" + +[image_description] +display_name = "图片描述" +advanced = true +description = "用于将图片内容转换为文本描述的模板。" + +[jargon_compare_inference] +display_name = "术语对比推理" +advanced = true +description = "用于比较和推理群内术语含义的模板。" + +[jargon_explainer_summarize] +display_name = "术语解释总结" +advanced = true +description = "用于总结术语解释结果的模板。" + +[jargon_inference_content_only] +display_name = "术语推理" +advanced = true +description = "用于仅基于内容推理术语含义的模板。" + +[jargon_inference_with_context] +display_name = "术语上下文推理" +advanced = true +description = "用于结合上下文推理术语含义的模板。" + +[learn_style] +display_name = "表达风格学习" +advanced = true +description = "用于从聊天内容中学习表达风格的模板。" + +[maisaka_timing_gate] +display_name = "Timing Gate" +advanced = false +description = "Maisaka 节奏控制模板,负责在每轮主循环前判断当前应该等待、停止,还是继续进入思考与回复流程。" + +[memory_retrieval_react_prompt_head_memory] +display_name = "记忆检索 ReAct 头部" +advanced = true +description = "ReAct 式记忆查询流程使用的高级记忆检索提示词头部。" diff --git a/prompts/ja-JP/emoji_content_filtration.prompt b/prompts/ja-JP/emoji_content_filtration.prompt index b3d957aa..1bcc98f0 100644 --- a/prompts/ja-JP/emoji_content_filtration.prompt +++ b/prompts/ja-JP/emoji_content_filtration.prompt @@ -1,5 +1,5 @@ これはスタンプです。次の基準に従って審査してください: -1. "{demand}" の要求を満たしていること +1. 公序良俗に反しないこと 2. 色情、暴力などの違法・不適切な内容ではなく、公序良俗に反しないこと 3. いかなる形式のスクリーンショット、チャット履歴、動画のスクリーンショットでもないこと 4. 5文字を超える文字が含まれないこと diff --git a/prompts/ja-JP/hippo_topic_analysis.prompt b/prompts/ja-JP/hippo_topic_analysis.prompt deleted file mode 100644 index 425776b1..00000000 --- a/prompts/ja-JP/hippo_topic_analysis.prompt +++ /dev/null @@ -1,27 +0,0 @@ -【過去の話題タイトル一覧】(タイトルのみ、具体的な内容は含まない): -{history_topics_block} -【過去の話題タイトル一覧ここまで】 - -【今回のチャット記録】(各メッセージの前に番号があり、後で参照するために使う): -{messages_block} -【今回のチャット記録ここまで】 - -以下のタスクを完了してください: -**話題の識別** -1. 【今回のチャット記録】に含まれる進行中の話題を 1 つ以上識別する; -2. 【今回のチャット記録】中のメッセージは、過去の話題に関係している場合もあれば、まったく無関係な場合もある; -3. 【過去の話題タイトル一覧】の話題が【今回のチャット記録】に現れているか判断し、現れている場合はその過去の話題タイトル文字列をそのまま使う; - -**メッセージの選択** -1. 各話題(新規話題または過去話題)について、上記の番号付きメッセージからその話題と強く関係するメッセージ番号一覧を選ぶ; -2. 各話題について、何が起きているのかを 1 文で明確に説明すること。時間(おおまかで可)、人物、主な出来事、テーマを必ず含め、正確で区別しやすい内容にする; - -まず短い思考を出力し、どんな話題があるか、どれが過去話題に含まれず、どれが過去話題に含まれているか、そしてその理由を説明してください; -その後、【今回のチャット記録】に含まれる話題を次の JSON 形式で厳密に出力してください: -[ - {{ - "topic": "話題", - "message_indices": [1, 2, 5] - }}, - ... -] diff --git a/prompts/ja-JP/hippo_topic_summary.prompt b/prompts/ja-JP/hippo_topic_summary.prompt deleted file mode 100644 index 2fa85135..00000000 --- a/prompts/ja-JP/hippo_topic_summary.prompt +++ /dev/null @@ -1,22 +0,0 @@ -以下の話題に基づいて、チャット記録の一部を要約し、次の情報を抽出してください: - -**話題**:{topic} - -**要件**: -1. キーワード:話題に関連するキーワードを抽出し、リスト形式で返す(3〜10 個) -2. 要約:この会話部分の平文要約を行う(50〜200 文字)。要件: - - 起こった出来事とチャット内容を丁寧に言い換える; - - 出来事の展開過程と結果を重点的に示す; - - この話題という中心を軸に要約する; - - 話題内の重要情報を抽出し、簡潔で明確にする。 - -JSON 形式で次のように返してください: -{{ - "keywords": ["キーワード1", "キーワード2", ...], - "summary": "要約内容" -}} - -チャット記録: -{original_text} - -JSON のみを直接返し、ほかの内容は含めないでください。 diff --git a/prompts/ja-JP/maidairy_knowledge_category.prompt b/prompts/ja-JP/maidairy_knowledge_category.prompt deleted file mode 100644 index 7b98eb59..00000000 --- a/prompts/ja-JP/maidairy_knowledge_category.prompt +++ /dev/null @@ -1,18 +0,0 @@ -あなたはユーザー特性カテゴリ分析の専門家です。あなたのタスクは、会話内容を分析し、どの個人特性カテゴリが関係しているかを判断することです。 - -以下の会話内容を注意深く読み、どの個人特性カテゴリが関係しているかを判断してください。 - -【個人特性カテゴリ一覧】 -{categories_summary} - -【タスク要件】 -1. 会話内容を分析し、どの個人特性カテゴリが関係しているかを判断する -2. 関係しているカテゴリ番号だけを、スペース区切りで出力する -3. 会話内容がどの個人特性カテゴリにも関係しない場合は「無」と出力する - -【出力形式の例】 -1 3 5 -または -無 - -分析を開始してください: diff --git a/prompts/ja-JP/maidairy_knowledge_extract.prompt b/prompts/ja-JP/maidairy_knowledge_extract.prompt deleted file mode 100644 index 5f99f57d..00000000 --- a/prompts/ja-JP/maidairy_knowledge_extract.prompt +++ /dev/null @@ -1,17 +0,0 @@ -あなたはユーザー特性情報抽出の専門家です。あなたのタスクは、会話内容から指定カテゴリに関係する個人特性情報を抽出することです。 - -【対象カテゴリ】 -{category_name} - -【タスク要件】 -1. 会話内容を注意深く読み、「{category_name}」に関係するすべての情報を見つける -2. 抽出する情報は具体的かつ正確で、曖昧な表現を避ける -3. 関連情報が複数ある場合は、簡潔な 1 段落にまとめる -4. 会話内にこのカテゴリに関係する情報がない場合は「無」と出力する - -【出力形式の例】 -ユーザーはやや内向的で、人が多い場面では話すのが苦手だが、親しい友人とはとても活発になる。 -または -無 - -抽出を開始してください: diff --git a/prompts/ja-JP/maidairy_knowledge_retrieve.prompt b/prompts/ja-JP/maidairy_knowledge_retrieve.prompt deleted file mode 100644 index b337054b..00000000 --- a/prompts/ja-JP/maidairy_knowledge_retrieve.prompt +++ /dev/null @@ -1,19 +0,0 @@ -あなたはユーザー特性検索の専門家です。あなたのタスクは、現在の会話文脈に基づいて、どの個人特性カテゴリ情報を検索する必要があるかを判断することです。 - -【現在の会話文脈】 -{chat_context} - -【個人特性カテゴリ一覧】 -{categories_summary} - -【タスク要件】 -1. 現在の会話文脈を分析し、ユーザー理解の助けになる個人特性情報が何かを判断する -2. 必要なカテゴリ番号だけを、スペース区切りで出力する -3. 現在の会話で個人特性情報がまったく不要な場合は「無」と出力する - -【出力形式の例】 -2 5 8 -または -無 - -分析を開始してください: diff --git a/prompts/zh-CN/.meta.toml b/prompts/zh-CN/.meta.toml new file mode 100644 index 00000000..1d2f17a2 --- /dev/null +++ b/prompts/zh-CN/.meta.toml @@ -0,0 +1,74 @@ +[maisaka_chat] +display_name = "规划器" +advanced = false +description = "Maisaka 主规划模板,负责整合身份、时间、工具说明和聊天上下文,驱动主循环进行思考、决策与工具调用。" + +[maisaka_replyer] +display_name = "回复" +advanced = false +description = "Maisaka 回复生成模板,负责根据人格、表达风格、群聊注意事项和待回复上下文生成最终回复。" + +[default_expressor] +display_name = "改写器" +advanced = true +description = "表达方式生成与整理相关模板,通常只在调试表达系统时需要修改。" + +[emoji_content_analysis] +display_name = "表情包内容分析" +advanced = true +description = "用于分析表情包图片内容的模板。" + +[emoji_content_filtration] +display_name = "表情包内容过滤" +advanced = true +description = "用于判断表情包内容是否符合过滤要求的模板。" + +[emoji_replace] +display_name = "表情包替换" +advanced = true +description = "用于根据文本语境选择或替换表情包的模板。" + +[expression_evaluation] +display_name = "表达评价" +advanced = true +description = "用于评价候选表达方式质量的模板。" + +[expression_select] +display_name = "表达选择" +advanced = true +description = "用于从表达库中选择合适表达方式的模板。" + +[image_description] +display_name = "图片描述" +advanced = true +description = "用于将图片内容转换为文本描述的模板。" + +[jargon_compare_inference] +display_name = "黑话对比推理" +advanced = true +description = "用于比较和推理群内黑话含义的模板。" + +[jargon_explainer_summarize] +display_name = "黑话解释总结" +advanced = true +description = "用于总结黑话解释结果的模板。" + +[jargon_inference_content_only] +display_name = "黑话推理" +advanced = true +description = "用于仅基于内容推理黑话含义的模板。" + +[jargon_inference_with_context] +display_name = "黑话上下文推理" +advanced = true +description = "用于结合上下文推理黑话含义的模板。" + +[learn_style] +display_name = "表达风格学习" +advanced = true +description = "用于从聊天内容中学习表达风格的模板。" + +[maisaka_timing_gate] +display_name = "时机" +advanced = false +description = "Maisaka 节奏控制模板,负责在每轮主循环前判断当前应该等待、停止,还是继续进入思考与回复流程。" diff --git a/prompts/zh-CN/emoji_content_filtration.prompt b/prompts/zh-CN/emoji_content_filtration.prompt index 6bb73a53..4d32d3ad 100644 --- a/prompts/zh-CN/emoji_content_filtration.prompt +++ b/prompts/zh-CN/emoji_content_filtration.prompt @@ -1,5 +1,5 @@ 这是一个表情包,请对这个表情包进行审核,标准如下: -1. 必须符合"{demand}"的要求 +1. 必须符合"符合公序良俗"的要求 2. 不能是色情、暴力、等违法违规内容,必须符合公序良俗 3. 不能是任何形式的截图,聊天记录或视频截图 4. 不要出现5个以上文字 diff --git a/prompts/zh-CN/hippo_topic_analysis.prompt b/prompts/zh-CN/hippo_topic_analysis.prompt deleted file mode 100644 index 3e0f01b2..00000000 --- a/prompts/zh-CN/hippo_topic_analysis.prompt +++ /dev/null @@ -1,27 +0,0 @@ -【历史话题标题列表】(仅标题,不含具体内容): -{history_topics_block} -【历史话题标题列表结束】 - -【本次聊天记录】(每条消息前有编号,用于后续引用): -{messages_block} -【本次聊天记录结束】 - -请完成以下任务: -**识别话题** -1. 识别【本次聊天记录】中正在进行的一个或多个话题; -2. 【本次聊天记录】的中的消息可能与历史话题有关,也可能毫无关联。 -2. 判断【历史话题标题列表】中的话题是否在【本次聊天记录】中出现,如果出现,则直接使用该历史话题标题字符串; - -**选取消息** -1. 对于每个话题(新话题或历史话题),从上述带编号的消息中选出与该话题强相关的消息编号列表; -2. 每个话题用一句话清晰地描述正在发生的事件,必须包含时间(大致即可)、人物、主要事件和主题,保证精准且有区分度; - -请先输出一段简短思考,说明有什么话题,哪些是不包含在历史话题中的,哪些是包含在历史话题中的,并说明为什么; -然后严格以 JSON 格式输出【本次聊天记录】中涉及的话题,格式如下: -[ - {{ - "topic": "话题", - "message_indices": [1, 2, 5] - }}, - ... -] diff --git a/prompts/zh-CN/hippo_topic_summary.prompt b/prompts/zh-CN/hippo_topic_summary.prompt deleted file mode 100644 index 35c8c887..00000000 --- a/prompts/zh-CN/hippo_topic_summary.prompt +++ /dev/null @@ -1,22 +0,0 @@ -请基于以下话题,对聊天记录片段进行概括,提取以下信息: - -**话题**:{topic} - -**要求**: -1. 关键词:提取与话题相关的关键词,用列表形式返回(3-10个关键词) -2. 概括:对这段话的平文本概括(50-200字),要求: - - 仔细地转述发生的事件和聊天内容; - - 重点突出事件的发展过程和结果; - - 围绕话题这个中心进行概括。 - - 提取话题中的关键信息点,关键信息点应该简洁明了。 - -请以JSON格式返回,格式如下: -{{ - "keywords": ["关键词1", "关键词2", ...], - "summary": "概括内容" -}} - -聊天记录: -{original_text} - -请直接返回JSON,不要包含其他内容。 diff --git a/prompts/zh-CN/maidairy_knowledge_category.prompt b/prompts/zh-CN/maidairy_knowledge_category.prompt deleted file mode 100644 index 738b5a70..00000000 --- a/prompts/zh-CN/maidairy_knowledge_category.prompt +++ /dev/null @@ -1,18 +0,0 @@ -你是一个用户特征分类分析专家。你的任务是分析对话内容,判断其中涉及哪些个人特征分类。 - -请仔细阅读以下对话内容,判断其中涉及了哪些个人特征分类。 - -【个人特征分类列表】 -{categories_summary} - -【任务要求】 -1. 分析对话内容,判断涉及哪些个人特征分类 -2. 只输出涉及到的分类编号,用空格分隔 -3. 如果对话内容不涉及任何个人特征分类,输出"无" - -【输出格式示例】 -1 3 5 -或 -无 - -请开始分析: diff --git a/prompts/zh-CN/maidairy_knowledge_extract.prompt b/prompts/zh-CN/maidairy_knowledge_extract.prompt deleted file mode 100644 index 9a8054b5..00000000 --- a/prompts/zh-CN/maidairy_knowledge_extract.prompt +++ /dev/null @@ -1,17 +0,0 @@ -你是一个用户特征信息提取专家。你的任务是从对话内容中提取与指定分类相关的个人特征信息。 - -【目标分类】 -{category_name} - -【任务要求】 -1. 仔细阅读对话内容,找出与"{category_name}"相关的所有信息 -2. 提取的信息应该具体、准确,避免模糊的描述 -3. 如果有多条相关信息,请整合成一段简洁的描述 -4. 如果对话中没有与该分类相关的信息,输出"无" - -【输出格式示例】 -用户性格比较内向,不喜欢在人多的时候说话,但和熟悉的朋友会变得很活跃。 -或 -无 - -请开始提取: diff --git a/prompts/zh-CN/maidairy_knowledge_retrieve.prompt b/prompts/zh-CN/maidairy_knowledge_retrieve.prompt deleted file mode 100644 index 8519b85c..00000000 --- a/prompts/zh-CN/maidairy_knowledge_retrieve.prompt +++ /dev/null @@ -1,19 +0,0 @@ -你是一个用户特征检索专家。你的任务是根据当前对话上下文,判断需要检索哪些个人特征分类的信息。 - -【当前对话上下文】 -{chat_context} - -【个人特征分类列表】 -{categories_summary} - -【任务要求】 -1. 分析当前对话上下文,判断需要哪些个人特征信息来帮助理解用户 -2. 只输出需要的分类编号,用空格分隔 -3. 如果当前对话不需要任何个人特征信息,输出"无" - -【输出格式示例】 -2 5 8 -或 -无 - -请开始分析: diff --git a/pytests/image_sys_test/emoji_manager_test.py b/pytests/image_sys_test/emoji_manager_test.py index b71ec847..d6c77c95 100644 --- a/pytests/image_sys_test/emoji_manager_test.py +++ b/pytests/image_sys_test/emoji_manager_test.py @@ -178,7 +178,6 @@ def _install_stub_modules(monkeypatch): class _EmojiConfig: max_reg_num = 20 content_filtration = False - filtration_prompt = "" steal_emoji = False do_replace = False check_interval = 1 @@ -1956,7 +1955,6 @@ async def test_build_emoji_description_content_filtration_reject(monkeypatch): logger = emoji_manager_new.logger emoji_manager_new.global_config.emoji.content_filtration = True - emoji_manager_new.global_config.emoji.filtration_prompt = "rule" def _read_bytes(_path): return b"" @@ -1994,13 +1992,15 @@ async def test_build_emoji_description_content_filtration_pass(monkeypatch): logger = emoji_manager_new.logger emoji_manager_new.global_config.emoji.content_filtration = True - emoji_manager_new.global_config.emoji.filtration_prompt = "rule" def _read_bytes(_path): return b"" - async def _vlm_response(prompt, *_args, **_kwargs): - if "rule" in str(prompt): + call_count = {"n": 0} + + async def _vlm_response(*_args, **_kwargs): + call_count["n"] += 1 + if call_count["n"] == 2: return "是", None return "desc", None diff --git a/pytests/prompt_test/test_prompt_i18n.py b/pytests/prompt_test/test_prompt_i18n.py index 807cb8be..66b844dc 100644 --- a/pytests/prompt_test/test_prompt_i18n.py +++ b/pytests/prompt_test/test_prompt_i18n.py @@ -87,7 +87,46 @@ def test_list_prompt_templates_prefers_locale_specific_files(tmp_path: Path) -> prompt_templates = list_prompt_templates(prompts_root=prompts_root) - assert prompt_templates["replyer"].read_text(encoding="utf-8") == "English" + assert prompt_templates["replyer"].path.read_text(encoding="utf-8") == "English" + + +def test_list_prompt_templates_loads_directory_metadata(tmp_path: Path) -> None: + prompts_root = tmp_path / "prompts" + write_prompt(prompts_root, "zh-CN", "replyer", "中文") + metadata_path = prompts_root / "zh-CN" / ".meta.toml" + metadata_path.write_text( + """ +[replyer] +display_name = "回复器" +advanced = true +description = "用于生成回复的主模板" +""".strip(), + encoding="utf-8", + ) + + prompt_templates = list_prompt_templates(prompts_root=prompts_root) + metadata = prompt_templates["replyer"].metadata + + assert metadata.display_name == "回复器" + assert metadata.advanced is True + assert metadata.description == "用于生成回复的主模板" + + +def test_list_prompt_templates_loads_prompt_specific_metadata(tmp_path: Path) -> None: + prompts_root = tmp_path / "prompts" + write_prompt(prompts_root, "zh-CN", "replyer", "中文") + metadata_path = prompts_root / "zh-CN" / "replyer.meta.json" + metadata_path.write_text( + '{"display_name": "Replyer", "advanced": false, "description": "Prompt specific metadata"}', + encoding="utf-8", + ) + + prompt_templates = list_prompt_templates(prompts_root=prompts_root) + metadata = prompt_templates["replyer"].metadata + + assert metadata.display_name == "Replyer" + assert metadata.advanced is False + assert metadata.description == "Prompt specific metadata" def test_list_prompt_templates_reports_duplicate_name_with_custom_root(tmp_path: Path) -> None: diff --git a/src/common/prompt_i18n.py b/src/common/prompt_i18n.py index 358833d1..8075d913 100644 --- a/src/common/prompt_i18n.py +++ b/src/common/prompt_i18n.py @@ -1,8 +1,12 @@ from __future__ import annotations +from dataclasses import dataclass from functools import lru_cache from pathlib import Path +from typing import Any +from tomlkit import parse as parse_toml +import json import logging import os import re @@ -22,6 +26,19 @@ STRICT_ENV_VALUES = {"1", "true", "yes", "on"} extract_prompt_placeholders = extract_placeholders +@dataclass(frozen=True) +class PromptMetadata: + display_name: str = "" + advanced: bool = False + description: str = "" + + +@dataclass(frozen=True) +class PromptTemplateInfo: + path: Path + metadata: PromptMetadata + + def get_prompts_root(prompts_root: Path | None = None) -> Path: return (prompts_root or PROMPTS_ROOT).resolve() @@ -80,24 +97,86 @@ def iter_prompt_files(directory: Path, recursive: bool = True) -> list[Path]: def _raise_duplicate_prompt_name(name: str, first_path: Path, second_path: Path, prompts_root: Path) -> None: + path_a = first_path.relative_to(prompts_root).as_posix() + path_b = second_path.relative_to(prompts_root).as_posix() raise ValueError( t( "prompt.duplicate_template_name", name=name, - path_a=first_path.relative_to(prompts_root), - path_b=second_path.relative_to(prompts_root), + path_a=path_a, + path_b=path_b, ) ) -def _scan_prompt_directory(directory: Path, prompts_root: Path) -> dict[str, Path]: - prompt_paths: dict[str, Path] = {} +def _coerce_metadata(raw_metadata: Any) -> PromptMetadata: + if not isinstance(raw_metadata, dict): + return PromptMetadata() + + display_name = raw_metadata.get("display_name", "") + advanced = raw_metadata.get("advanced", False) + description = raw_metadata.get("description", "") + + return PromptMetadata( + display_name=display_name if isinstance(display_name, str) else "", + advanced=advanced if isinstance(advanced, bool) else False, + description=description if isinstance(description, str) else "", + ) + + +def _read_metadata_file(metadata_path: Path) -> dict[str, Any]: + if not metadata_path.is_file(): + return {} + + try: + if metadata_path.suffix == ".json": + metadata = json.loads(metadata_path.read_text(encoding="utf-8")) + else: + metadata = parse_toml(metadata_path.read_text(encoding="utf-8")) + except Exception as exc: + logger.warning("读取 Prompt 元信息文件 %s 失败:%s", metadata_path, exc) + return {} + + return dict(metadata) if isinstance(metadata, dict) else {} + + +def _extract_template_metadata(metadata: dict[str, Any], prompt_name: str) -> dict[str, Any]: + templates = metadata.get("templates") + if isinstance(templates, dict) and isinstance(templates.get(prompt_name), dict): + return dict(templates[prompt_name]) + + prompt_metadata = metadata.get(prompt_name) + if isinstance(prompt_metadata, dict): + return dict(prompt_metadata) + + return metadata if any(key in metadata for key in ("display_name", "advanced", "description")) else {} + + +def _load_prompt_metadata(prompt_path: Path) -> PromptMetadata: + prompt_name = prompt_path.stem + metadata_sources = ( + prompt_path.with_name(f"{prompt_name}.meta.toml"), + prompt_path.with_name(f"{prompt_name}.meta.json"), + prompt_path.parent / ".meta.toml", + prompt_path.parent / ".meta.json", + ) + + merged_metadata: dict[str, Any] = {} + for metadata_path in reversed(metadata_sources): + raw_metadata = _read_metadata_file(metadata_path) + merged_metadata.update(_extract_template_metadata(raw_metadata, prompt_name)) + + return _coerce_metadata(merged_metadata) + + +def _scan_prompt_directory(directory: Path, prompts_root: Path) -> dict[str, PromptTemplateInfo]: + prompt_paths: dict[str, PromptTemplateInfo] = {} for prompt_path in iter_prompt_files(directory): prompt_name = prompt_path.stem - existing_path = prompt_paths.get(prompt_name) - if existing_path is not None: - _raise_duplicate_prompt_name(prompt_name, existing_path, prompt_path, prompts_root) - prompt_paths[prompt_name] = prompt_path + existing_info = prompt_paths.get(prompt_name) + if existing_info is not None: + _raise_duplicate_prompt_name(prompt_name, existing_info.path, prompt_path, prompts_root) + prompt_paths[prompt_name] = PromptTemplateInfo(path=prompt_path, metadata=_load_prompt_metadata(prompt_path)) return prompt_paths @@ -115,11 +194,11 @@ def _iter_locale_candidates(requested_locale: str) -> list[str]: return locale_candidates -def list_prompt_templates(locale: str | None = None, prompts_root: Path | None = None) -> dict[str, Path]: +def list_prompt_templates(locale: str | None = None, prompts_root: Path | None = None) -> dict[str, PromptTemplateInfo]: resolved_prompts_root = get_prompts_root(prompts_root) requested_locale = normalize_locale(locale or get_locale()) - prompt_paths: dict[str, Path] = {} + prompt_paths: dict[str, PromptTemplateInfo] = {} for directory in _iter_prompt_template_layers(resolved_prompts_root, requested_locale): prompt_paths.update(_scan_prompt_directory(directory, resolved_prompts_root)) @@ -149,7 +228,7 @@ def resolve_prompt_path( else: prompt_paths = list_prompt_templates(locale=requested_locale, prompts_root=resolved_prompts_root) if normalized_name in prompt_paths: - return prompt_paths[normalized_name] + return prompt_paths[normalized_name].path raise FileNotFoundError(t("prompt.template_not_found", locale=requested_locale, name=normalized_name)) diff --git a/src/config/config.py b/src/config/config.py index 042515dd..1944d6e5 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -57,7 +57,7 @@ MODEL_CONFIG_PATH: Path = (CONFIG_DIR / "model_config.toml").resolve().absolute( LEGACY_ENV_PATH: Path = (PROJECT_ROOT / ".env").resolve().absolute() A_MEMORIX_LEGACY_CONFIG_PATH: Path = (CONFIG_DIR / "a_memorix.toml").resolve().absolute() MMC_VERSION: str = "1.0.0-pre.11" -CONFIG_VERSION: str = "8.10.6" +CONFIG_VERSION: str = "8.10.7" MODEL_CONFIG_VERSION: str = "1.15.3" logger = get_logger("config") diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 148dc157..e29743e5 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -30,7 +30,7 @@ class ExampleConfig(ConfigBase): class BotConfig(ConfigBase): """机器人配置类""" - __ui_label__ = "基本信息" + __ui_label__ = "基础" __ui_icon__ = "bot" platform: str = Field( @@ -87,6 +87,7 @@ class BotConfig(ConfigBase): class PersonalityConfig(ConfigBase): """人格配置类""" + __ui_parent__ = "bot" __ui_label__ = "人格" __ui_icon__ = "user-circle" @@ -1299,16 +1300,6 @@ class EmojiConfig(ConfigBase): ) """是否启用表情包过滤,只有符合该要求的表情包才会被保存""" - filtration_prompt: str = Field( - default="符合公序良俗", - json_schema_extra={ - "advanced": True, - "x-widget": "input", - "x-icon": "shield", - }, - ) - """表情包过滤要求,只有符合该要求的表情包才会被保存""" - class KeywordRuleConfig(ConfigBase): """关键词规则配置类""" diff --git a/src/emoji_system/emoji_manager.py b/src/emoji_system/emoji_manager.py index fed8747c..272f2670 100644 --- a/src/emoji_system/emoji_manager.py +++ b/src/emoji_system/emoji_manager.py @@ -915,11 +915,10 @@ class EmojiManager: # 表情包审查 if global_config.emoji.content_filtration: try: - filtration_prompt_template = prompt_manager.get_prompt("emoji_content_filtration") - filtration_prompt_template.add_context("demand", global_config.emoji.filtration_prompt) - filtration_prompt = await prompt_manager.render_prompt(filtration_prompt_template) + review_prompt_template = prompt_manager.get_prompt("emoji_content_filtration") + review_prompt = await prompt_manager.render_prompt(review_prompt_template) filtration_result = await emoji_manager_vlm.generate_response_for_image( - filtration_prompt, + review_prompt, image_base64, image_format, ) diff --git a/src/maisaka/builtin_tool/send_emoji.py b/src/maisaka/builtin_tool/send_emoji.py index e9bd292a..6cb8050b 100644 --- a/src/maisaka/builtin_tool/send_emoji.py +++ b/src/maisaka/builtin_tool/send_emoji.py @@ -13,7 +13,7 @@ from PIL import Image as PILImage from PIL import ImageDraw, ImageFont from pydantic import BaseModel, Field as PydanticField -from src.emoji_system.emoji_manager import emoji_manager +from src.emoji_system.emoji_manager import _is_vlm_task_configured, emoji_manager from src.emoji_system.maisaka_tool import send_emoji_for_maisaka from src.common.data_models.image_data_model import MaiEmoji from src.common.data_models.message_component_data_model import ImageComponent, MessageSequence, TextComponent @@ -38,6 +38,7 @@ _EMOJI_SUB_AGENT_MAX_TOKENS = 240 _EMOJI_MAX_CANDIDATE_COUNT = 64 _EMOJI_CANDIDATE_TILE_SIZE = 256 _EMOJI_SUCCESS_MESSAGE = "表情包发送成功" +_EMOJI_VLM_NOT_CONFIGURED_MESSAGE = "错误,没有配置视觉模型,无法使用表情包功能" class EmojiSelectionResult(BaseModel): @@ -298,6 +299,13 @@ def _resolve_emoji_selector_model_task_name() -> str: return "vlm" +def _is_missing_visual_model_error(exc: Exception) -> bool: + """判断是否为未配置视觉模型导致的选择失败。""" + + error_text = str(exc) + return _EMOJI_VLM_NOT_CONFIGURED_MESSAGE in error_text or "未找到名为 '' 的模型" in error_text + + async def _select_emoji_with_sub_agent( tool_ctx: BuiltinToolRuntimeContext, reasoning: str, @@ -351,13 +359,17 @@ async def _select_emoji_with_sub_agent( request_messages.append(candidate_llm_message) serialized_request_messages = serialize_prompt_messages(request_messages) + model_task_name = _resolve_emoji_selector_model_task_name() + if model_task_name == "vlm" and not _is_vlm_task_configured(): + raise RuntimeError(_EMOJI_VLM_NOT_CONFIGURED_MESSAGE) + selection_started_at = datetime.now() response = await tool_ctx.runtime.run_sub_agent( context_message_limit=_EMOJI_SUB_AGENT_CONTEXT_LIMIT, system_prompt=system_prompt, extra_messages=[prompt_message, candidate_message], max_tokens=_EMOJI_SUB_AGENT_MAX_TOKENS, - model_task_name=_resolve_emoji_selector_model_task_name(), + model_task_name=model_task_name, ) selection_duration_ms = round((datetime.now() - selection_started_at).total_seconds() * 1000, 2) @@ -448,7 +460,10 @@ async def handle_tool( ) except Exception as exc: logger.exception(f"{tool_ctx.runtime.log_prefix} 发送表情包时发生异常: {exc}") - structured_result["message"] = f"发送表情包时发生异常:{exc}" + if _is_missing_visual_model_error(exc): + structured_result["message"] = _EMOJI_VLM_NOT_CONFIGURED_MESSAGE + else: + structured_result["message"] = f"发送表情包时发生异常:{exc}" return tool_ctx.build_failure_result( invocation.tool_name, structured_result["message"], diff --git a/src/prompt/prompt_manager.py b/src/prompt/prompt_manager.py index 5282b7ef..84e2f3a6 100644 --- a/src/prompt/prompt_manager.py +++ b/src/prompt/prompt_manager.py @@ -274,12 +274,12 @@ class PromptManager: Exception: 如果在加载过程中出现任何文件操作错误则引发该异常 """ prompt_templates = list_prompt_templates(prompts_root=PROMPTS_DIR) - for prompt_name, prompt_file in prompt_templates.items(): + for prompt_name, prompt_template in prompt_templates.items(): try: template, need_save = self._load_prompt_template(prompt_name) self.add_prompt(Prompt(prompt_name=prompt_name, template=template), need_save=need_save) except Exception as exc: - logger.error(f"加载 Prompt 文件 '{prompt_file}' 时出错,错误信息: {exc}") + logger.error(f"加载 Prompt 文件 '{prompt_template.path}' 时出错,错误信息: {exc}") raise for prompt_file in CUSTOM_PROMPTS_DIR.glob(f"*{SUFFIX_PROMPT}"): if prompt_file.stem in prompt_templates: diff --git a/src/webui/routers/config.py b/src/webui/routers/config.py index 86ec34a0..1205f56d 100644 --- a/src/webui/routers/config.py +++ b/src/webui/routers/config.py @@ -14,6 +14,7 @@ from pydantic import BaseModel, Field import tomlkit from src.common.logger import get_logger +from src.common.prompt_i18n import list_prompt_templates from src.config.config import CONFIG_DIR, PROJECT_ROOT, Config, ModelConfig from src.config.config_base import AttributeData, ConfigBase from src.config.model_configs import ( @@ -64,6 +65,9 @@ class PromptFileInfo(BaseModel): name: str = Field(..., description="Prompt 文件名") size: int = Field(..., description="文件大小") modified_at: float = Field(..., description="最后修改时间戳") + display_name: str = Field(default="", description="Prompt 展示名称") + advanced: bool = Field(default=False, description="是否为高级 Prompt") + description: str = Field(default="", description="Prompt 描述") class PromptCatalogResponse(BaseModel): @@ -213,14 +217,20 @@ async def list_prompt_files(): continue language = language_dir.name + prompt_template_infos = list_prompt_templates(locale=language, prompts_root=PROMPTS_DIR) prompt_files: List[PromptFileInfo] = [] for prompt_file in sorted(language_dir.glob("*.prompt"), key=lambda item: item.name): stat = prompt_file.stat() + template_info = prompt_template_infos.get(prompt_file.stem) + metadata = template_info.metadata if template_info and template_info.path == prompt_file else None prompt_files.append( PromptFileInfo( name=prompt_file.name, size=stat.st_size, modified_at=stat.st_mtime, + display_name=metadata.display_name if metadata else "", + advanced=metadata.advanced if metadata else False, + description=metadata.description if metadata else "", ) ) From 16de25995528588edaa30048384662d084b19392 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 5 May 2026 18:34:20 +0800 Subject: [PATCH 2/3] =?UTF-8?q?perf=EF=BC=9A=E4=BC=98=E5=8C=96webui?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=B1=95=E7=A4=BA=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?log=E6=98=BE=E7=A4=BA=EF=BC=8C=E4=BF=AE=E5=A4=8D=E8=A1=A8?= =?UTF-8?q?=E8=BE=BE=E5=AE=A1=E6=A0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/expression-reviewer.tsx | 11 +- dashboard/src/lib/expression-api.ts | 4 + dashboard/src/routes/config/bot.tsx | 71 +++++--- .../config/bot/hooks/complexFieldHooks.tsx | 161 +++++++++++++++--- .../src/routes/config/bot/hooks/index.ts | 1 + dashboard/src/routes/resource/emoji/index.tsx | 72 ++++---- locales/zh-CN/startup.json | 6 +- pytests/webui/test_expression_routes.py | 97 +++++++---- src/chat/message_receive/chat_manager.py | 2 +- src/common/logger.py | 25 +-- src/config/official_configs.py | 2 + src/webui/app.py | 7 +- src/webui/routers/expression.py | 31 ++-- 13 files changed, 326 insertions(+), 164 deletions(-) diff --git a/dashboard/src/components/expression-reviewer.tsx b/dashboard/src/components/expression-reviewer.tsx index 35b82819..bced38f1 100644 --- a/dashboard/src/components/expression-reviewer.tsx +++ b/dashboard/src/components/expression-reviewer.tsx @@ -80,6 +80,7 @@ export function ExpressionReviewer({ open, onOpenChange }: ExpressionReviewerPro // 快速审核模式状态 const [quickFilterType, setQuickFilterType] = useState<'unchecked' | 'passed' | 'rejected' | 'all'>('unchecked') const [quickExpressions, setQuickExpressions] = useState([]) + const quickExpressionsRef = useRef([]) const [quickCurrentIndex, setQuickCurrentIndex] = useState(0) const [quickLoading, setQuickLoading] = useState(false) const [quickTotal, setQuickTotal] = useState(0) @@ -92,6 +93,10 @@ export function ExpressionReviewer({ open, onOpenChange }: ExpressionReviewerPro const cardRef = useRef(null) const dragStartRef = useRef<{ x: number; y: number } | null>(null) const isDraggingRef = useRef(false) + + useEffect(() => { + quickExpressionsRef.current = quickExpressions + }, [quickExpressions]) const [loading, setLoading] = useState(false) const [statsLoading, setStatsLoading] = useState(false) const [total, setTotal] = useState(0) @@ -180,9 +185,13 @@ export function ExpressionReviewer({ open, onOpenChange }: ExpressionReviewerPro setQuickLoading(true) const pageToLoad = append ? quickPage + 1 : quickPage const result = await getReviewList({ - page: pageToLoad, + page: quickFilterType === 'unchecked' ? 1 : pageToLoad, page_size: 20, filter_type: quickFilterType, + order: quickFilterType === 'unchecked' ? 'random' : 'latest', + exclude_ids: quickFilterType === 'unchecked' && append + ? quickExpressionsRef.current.map((expr) => expr.id) + : undefined, }) if (result.success) { diff --git a/dashboard/src/lib/expression-api.ts b/dashboard/src/lib/expression-api.ts index 8937f598..030d1614 100644 --- a/dashboard/src/lib/expression-api.ts +++ b/dashboard/src/lib/expression-api.ts @@ -442,16 +442,20 @@ export async function getReviewList(params: { page?: number page_size?: number filter_type?: 'unchecked' | 'passed' | 'rejected' | 'all' + order?: 'latest' | 'random' search?: string chat_id?: string + exclude_ids?: number[] }): Promise> { const queryParams = new URLSearchParams() if (params.page) queryParams.append('page', params.page.toString()) if (params.page_size) queryParams.append('page_size', params.page_size.toString()) if (params.filter_type) queryParams.append('filter_type', params.filter_type) + if (params.order) queryParams.append('order', params.order) if (params.search) queryParams.append('search', params.search) if (params.chat_id) queryParams.append('chat_id', params.chat_id) + params.exclude_ids?.forEach((id) => queryParams.append('exclude_ids', id.toString())) const response = await fetchWithAuth(`${API_BASE}/review/list?${queryParams}`) diff --git a/dashboard/src/routes/config/bot.tsx b/dashboard/src/routes/config/bot.tsx index 03fdac29..53a26153 100644 --- a/dashboard/src/routes/config/bot.tsx +++ b/dashboard/src/routes/config/bot.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { parse as parseToml } from 'smol-toml' import { AlertDescription, Alert } from '@/components/ui/alert' @@ -23,11 +23,13 @@ import { useToast } from '@/hooks/use-toast' import { getBotConfig, getBotConfigRaw, getBotConfigSchema, updateBotConfig, updateBotConfigRaw } from '@/lib/config-api' import { fieldHooks } from '@/lib/field-hooks' import { RestartProvider, useRestart } from '@/lib/restart-context' +import { cn } from '@/lib/utils' import { ChevronDown, ChevronUp, Code2, Info, Layout, Power, RefreshCw, Save } from 'lucide-react' import type { ConfigSchema } from '@/types/config-schema' import { + BotPlatformsHook, ChatPromptsHook, ChatTalkValueRulesHook, ExpressionGroupsHook, @@ -413,6 +415,7 @@ function BotConfigPageContent() { useEffect(() => { const hookEntries = [ + ['bot.platforms', BotPlatformsHook], ['chat.chat_prompts', ChatPromptsHook], ['chat.talk_value_rules', ChatTalkValueRulesHook], ['expression.expression_groups', ExpressionGroupsHook], @@ -773,7 +776,23 @@ function BotConfigPageContent() {

管理麦麦的核心功能和行为设置

{/* 按钮组 - 桌面端靠右 */} -
+
+ handleModeChange(v as 'visual' | 'source')} + className="w-full min-w-[13rem] sm:w-[14rem]" + > + + + + 可视化 + + + + 源代码 + + +
- - {/* 模式切换 - 单独一行 */} -
- handleModeChange(v as 'visual' | 'source')} className="w-full"> - - - - 可视化编辑 - - - - 源代码编辑 - - - -
{/* 重启提示 */} @@ -975,6 +978,9 @@ function DynamicConfigTabs(props: DynamicConfigTabsProps) { ? tabGroups : tabGroups.filter((tab) => DEFAULT_VISIBLE_TAB_IDS.has(tab.id)) const hasCollapsibleTabs = tabGroups.some((tab) => !DEFAULT_VISIBLE_TAB_IDS.has(tab.id)) + const firstExpandedTabId = visibleTabGroups.find( + (tab) => !DEFAULT_VISIBLE_TAB_IDS.has(tab.id) + )?.id const toggleExpanded = () => { setExpanded((current) => { @@ -1033,15 +1039,26 @@ function DynamicConfigTabs(props: DynamicConfigTabsProps) { return ( - {visibleTabGroups.map((tab) => ( - - {tab.label} - - ))} + {visibleTabGroups.map((tab) => { + const isExpandedOnlyTab = !DEFAULT_VISIBLE_TAB_IDS.has(tab.id) + return ( + + {tab.id === firstExpandedTabId && ( + + )} + + {tab.label} + + + ) + })} {hasCollapsibleTabs && ( + + + {rows.length === 0 ? ( +
+ 暂无其他平台账号。 +
+ ) : ( +
+ {rows.map((row, rowIndex) => ( +
+
+ + + updateRow(rowIndex, { platform: event.target.value }) + } + /> +
+
+ + + updateRow(rowIndex, { account: event.target.value }) + } + /> +
+
+ +
+
+ ))} +
+ )} + + ) +} + export const KeywordRulesHook = createListItemEditorHook({ addLabel: '添加关键词规则', helperText: '匹配命中后会用 reaction 内容作为额外上下文。keywords 至少填一条,或使用正则模式。', @@ -279,8 +398,8 @@ export const ExpressionGroupsHook: FieldHookComponent = ({ onChange, value }) => } return ( -
-
+
+

表达互通组

@@ -299,13 +418,13 @@ export const ExpressionGroupsHook: FieldHookComponent = ({ onChange, value }) => 暂无互通组,点击“添加互通组”开始配置。

) : ( -
+
{groups.map((group, groupIndex) => (
-
+
互通组 {groupIndex + 1} @@ -341,15 +460,16 @@ export const ExpressionGroupsHook: FieldHookComponent = ({ onChange, value }) => 这个互通组还没有成员。
) : ( -
+
{group.expression_groups.map((member, memberIndex) => (
-
- +
+ @@ -359,10 +479,10 @@ export const ExpressionGroupsHook: FieldHookComponent = ({ onChange, value }) => } />
-
- +
+ @@ -372,8 +492,8 @@ export const ExpressionGroupsHook: FieldHookComponent = ({ onChange, value }) => } />
-
- +
+
+ + + + {selectedIds.size > 0 && ( + <> + + + + )}
-
+ +
- -
- -
diff --git a/locales/zh-CN/startup.json b/locales/zh-CN/startup.json index cd7e3c99..38808c46 100644 --- a/locales/zh-CN/startup.json +++ b/locales/zh-CN/startup.json @@ -19,10 +19,10 @@ "startup.event_loop_closed": "[主程序] 事件循环已关闭", "startup.file_not_found": "{file_type} 文件不存在", "startup.graceful_shutdown_error": "优雅关闭时发生错误: {error}", - "startup.initialization_completed_banner": "\n--------------------------------\n全部系统初始化完成,{nickname} 已成功唤醒\n--------------------------------\n如果想要自定义 {nickname} 的功能,请查阅:https://docs.mai-mai.org/manual/usage/\n或者遇到了问题,请访问我们的文档:https://docs.mai-mai.org/\n--------------------------------\n如果你想要编写或了解插件相关内容,请访问开发文档 https://docs.mai-mai.org/develop/\n--------------------------------\n如果你需要查阅模型的消耗以及麦麦的统计数据,请访问根目录的 maibot_statistics.html 文件\n", + "startup.initialization_completed_banner": "全部系统初始化完成,{nickname} 已成功唤醒", "startup.initialization_completed_cycles": "初始化完成,神经元放电 {init_time} 次", "startup.interrupt_received": "收到中断信号,正在优雅关闭...", - "startup.launching_script": "正在启动 {script_file}...", + "startup.launching_script": "正在启动MaiBot", "startup.logging_shutdown_error": "关闭日志系统时出错: {error}", "startup.main_error": "主程序发生异常: {error}", "startup.opensource_free_notice": " 本项目是完全免费的开源软件,基于 GPL-3.0 协议发布", @@ -52,7 +52,7 @@ "startup.shutdown_failed": "麦麦关闭失败: {error}", "startup.shutdown_started": "正在优雅关闭麦麦...", "startup.waking_up": "正在唤醒 {nickname}......", - "startup.webui_access_token": "🔑 WebUI Access Token: {token}", + "startup.webui_access_token": "🔑 WebUI 登录 Token: {token}", "startup.webui_access_token_failed": "❌ 获取 Access Token 失败: {error}", "startup.webui_access_token_login_hint": "💡 请使用此 Token 登录 WebUI", "startup.webui_anti_crawler_config_failed": "❌ 配置防爬虫中间件失败: {error}", diff --git a/pytests/webui/test_expression_routes.py b/pytests/webui/test_expression_routes.py index 3dcd9fba..45e476e1 100644 --- a/pytests/webui/test_expression_routes.py +++ b/pytests/webui/test_expression_routes.py @@ -1,16 +1,16 @@ """Expression routes pytest tests""" from typing import Generator -from unittest.mock import MagicMock import pytest -from fastapi import FastAPI, APIRouter +from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient -from sqlalchemy.pool import StaticPool from sqlalchemy import text +from sqlalchemy.pool import StaticPool from sqlmodel import Session, SQLModel, create_engine, select -from src.common.database.database_model import Expression +from src.common.database.database_model import Expression, ModifiedBy +from src.webui.dependencies import require_auth def create_test_app() -> FastAPI: @@ -63,6 +63,7 @@ def client_fixture(test_session: Session, monkeypatch) -> Generator[TestClient, @contextmanager def get_test_db_session(): yield test_session + test_session.commit() monkeypatch.setattr("src.webui.routers.expression.get_db_session", get_test_db_session) @@ -71,10 +72,11 @@ def client_fixture(test_session: Session, monkeypatch) -> Generator[TestClient, @pytest.fixture(name="mock_auth") -def mock_auth_fixture(monkeypatch): +def mock_auth_fixture(): """Mock authentication to always return True""" - mock_verify = MagicMock(return_value=True) - monkeypatch.setattr("src.webui.routers.expression.verify_auth_token_from_cookie_or_header", mock_verify) + app.dependency_overrides[require_auth] = lambda: "test-token" + yield + app.dependency_overrides.clear() @pytest.fixture(name="sample_expression") @@ -82,8 +84,8 @@ def sample_expression_fixture(test_session: Session) -> Expression: """Insert a sample expression into test database""" test_session.execute( text( - "INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) " - "VALUES (1, '测试情景', '测试风格', '测试上下文', '测试上文', '[\"测试内容1\", \"测试内容2\"]', 10, '2026-02-17 12:00:00', '2026-02-15 10:00:00', 'test_chat_001')" + "INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) " + "VALUES (1, '测试情景', '测试风格', '[\"测试内容1\", \"测试内容2\"]', 10, '2026-02-17 12:00:00', '2026-02-15 10:00:00', 'test_chat_001', 0, 0)" ) ) test_session.commit() @@ -131,8 +133,8 @@ def test_list_expressions_pagination(client: TestClient, mock_auth, test_session for i in range(5): test_session.execute( text( - f"INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) " - f"VALUES ({i + 1}, '情景{i}', '风格{i}', '', '', '[]', 0, '2026-02-17 12:0{i}:00', '2026-02-15 10:00:00', 'chat_{i}')" + f"INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) " + f"VALUES ({i + 1}, '情景{i}', '风格{i}', '[]', 0, '2026-02-17 12:0{i}:00', '2026-02-15 10:00:00', 'chat_{i}', 0, 0)" ) ) test_session.commit() @@ -158,14 +160,14 @@ def test_list_expressions_search(client: TestClient, mock_auth, test_session: Se """Test GET /expression/list with search filter""" test_session.execute( text( - "INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) " - "VALUES (1, '找人吃饭', '热情', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_001')" + "INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) " + "VALUES (1, '找人吃饭', '热情', '[]', 0, datetime('now'), datetime('now'), 'chat_001', 0, 0)" ) ) test_session.execute( text( - "INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) " - "VALUES (2, '拒绝邀请', '礼貌', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_002')" + "INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) " + "VALUES (2, '拒绝邀请', '礼貌', '[]', 0, datetime('now'), datetime('now'), 'chat_002', 0, 0)" ) ) test_session.commit() @@ -183,14 +185,14 @@ def test_list_expressions_chat_filter(client: TestClient, mock_auth, test_sessio """Test GET /expression/list with chat_id filter""" test_session.execute( text( - "INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) " - "VALUES (1, '情景A', '风格A', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_A')" + "INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) " + "VALUES (1, '情景A', '风格A', '[]', 0, datetime('now'), datetime('now'), 'chat_A', 0, 0)" ) ) test_session.execute( text( - "INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) " - "VALUES (2, '情景B', '风格B', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_B')" + "INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) " + "VALUES (2, '情景B', '风格B', '[]', 0, datetime('now'), datetime('now'), 'chat_B', 0, 0)" ) ) test_session.commit() @@ -378,8 +380,8 @@ def test_batch_delete_expressions_success(client: TestClient, mock_auth, test_se for i in range(3): test_session.execute( text( - f"INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) " - f"VALUES ({i + 1}, '批量删除{i}', '风格{i}', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_{i}')" + f"INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) " + f"VALUES ({i + 1}, '批量删除{i}', '风格{i}', '[]', 0, datetime('now'), datetime('now'), 'chat_{i}', 0, 0)" ) ) expression_ids.append(i + 1) @@ -416,8 +418,8 @@ def test_get_expression_stats(client: TestClient, mock_auth, test_session: Sessi for i in range(3): test_session.execute( text( - f"INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) " - f"VALUES ({i + 1}, '情景{i}', '风格{i}', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_{i % 2}')" + f"INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) " + f"VALUES ({i + 1}, '情景{i}', '风格{i}', '[]', 0, datetime('now'), datetime('now'), 'chat_{i % 2}', 0, 0)" ) ) test_session.commit() @@ -432,11 +434,11 @@ def test_get_expression_stats(client: TestClient, mock_auth, test_session: Sessi def test_get_review_stats(client: TestClient, mock_auth, test_session: Session): - """Test GET /expression/review/stats returns hardcoded 0 counts""" + """Test GET /expression/review/stats returns review status counts""" test_session.execute( text( - "INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) " - "VALUES (1, '待审核', '风格', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_001')" + "INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) " + "VALUES (1, '待审核', '风格', '[]', 0, datetime('now'), datetime('now'), 'chat_001', 0, 0)" ) ) test_session.commit() @@ -445,9 +447,8 @@ def test_get_review_stats(client: TestClient, mock_auth, test_session: Session): assert response.status_code == 200 data = response.json() - # Verify all review counts are 0 (hardcoded in refactored code) assert data["total"] == 1 # Total expressions exists - assert data["unchecked"] == 0 + assert data["unchecked"] == 1 assert data["passed"] == 0 assert data["rejected"] == 0 assert data["ai_checked"] == 0 @@ -455,14 +456,14 @@ def test_get_review_stats(client: TestClient, mock_auth, test_session: Session): def test_get_review_list_filter_unchecked(client: TestClient, mock_auth, sample_expression: Expression): - """Test GET /expression/review/list with filter_type=unchecked returns empty (legacy behavior)""" - # filter_type=unchecked should return no results (legacy removed) + """Test GET /expression/review/list with filter_type=unchecked returns unchecked expressions""" response = client.get("/api/webui/expression/review/list?filter_type=unchecked") assert response.status_code == 200 data = response.json() assert data["success"] is True - assert data["total"] == 0 # No results (legacy fields removed) + assert data["total"] == 1 + assert len(data["data"]) == 1 def test_get_review_list_filter_all(client: TestClient, mock_auth, sample_expression: Expression): @@ -476,8 +477,8 @@ def test_get_review_list_filter_all(client: TestClient, mock_auth, sample_expres assert len(data["data"]) == 1 -def test_batch_review_expressions_unsupported(client: TestClient, mock_auth, sample_expression: Expression): - """Test POST /expression/review/batch returns failure for require_unchecked=True""" +def test_batch_review_expressions_with_unchecked_marker(client: TestClient, mock_auth, sample_expression: Expression): + """Test POST /expression/review/batch succeeds with require_unchecked=True""" review_payload = {"items": [{"id": sample_expression.id, "rejected": False, "require_unchecked": True}]} response = client.post("/api/webui/expression/review/batch", json=review_payload) @@ -485,8 +486,34 @@ def test_batch_review_expressions_unsupported(client: TestClient, mock_auth, sam data = response.json() assert data["success"] is True - assert data["failed"] == 1 # Should fail because require_unchecked=True - assert "不支持审核状态过滤" in data["results"][0]["message"] + assert data["succeeded"] == 1 + assert data["results"][0]["success"] is True + + +def test_batch_review_expressions_overwrites_ai_checked( + client: TestClient, mock_auth, test_session: Session, sample_expression: Expression +): + """Test POST /expression/review/batch lets manual review override AI checked state""" + sample_expression.checked = True + sample_expression.rejected = True + sample_expression.modified_by = ModifiedBy.AI + test_session.add(sample_expression) + test_session.commit() + + review_payload = {"items": [{"id": sample_expression.id, "rejected": False, "require_unchecked": True}]} + + response = client.post("/api/webui/expression/review/batch", json=review_payload) + assert response.status_code == 200 + + data = response.json() + assert data["success"] is True + assert data["succeeded"] == 1 + test_session.expire_all() + reviewed_expression = test_session.exec(select(Expression).where(Expression.id == sample_expression.id)).first() + assert reviewed_expression is not None + assert reviewed_expression.checked is True + assert reviewed_expression.rejected is False + assert reviewed_expression.modified_by == ModifiedBy.USER def test_batch_review_expressions_no_unchecked_check(client: TestClient, mock_auth, sample_expression: Expression): diff --git a/src/chat/message_receive/chat_manager.py b/src/chat/message_receive/chat_manager.py index 48d89956..f5ab47a9 100644 --- a/src/chat/message_receive/chat_manager.py +++ b/src/chat/message_receive/chat_manager.py @@ -78,7 +78,7 @@ class ChatManager: """初始化聊天管理器""" try: await self.load_all_sessions_from_db() - logger.info(f"已加载 {len(self.sessions)} 个会话记录到内存中") + logger.debug(f"已加载 {len(self.sessions)} 个会话记录到内存中") except Exception as e: logger.error(f"初始化聊天管理器出现错误: {e}") diff --git a/src/common/logger.py b/src/common/logger.py index 507fa59b..e89fc0e7 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -829,20 +829,19 @@ def initialize_logging(verbose: bool = True): reconfigure_existing_loggers() # 启动日志清理任务 - start_log_cleanup_task(verbose=verbose) + start_log_cleanup_task() # 只在 verbose=True 时输出详细的初始化信息 if verbose: logger = get_logger("logger") console_level = LOG_CONFIG.get("console_log_level", LOG_CONFIG.get("log_level", "INFO")) file_level = LOG_CONFIG.get("file_log_level", LOG_CONFIG.get("log_level", "INFO")) - - logger.info("日志系统已初始化:") - logger.info(f" - 控制台级别: {console_level}") - logger.info(f" - 文件级别: {file_level}") max_log_files = max(1, int(LOG_CONFIG.get("max_log_files", 30) or 30)) log_cleanup_days = max(1, int(LOG_CONFIG.get("log_cleanup_days", 30) or 30)) - logger.info(f" - 轮转份数: {max_log_files}个文件|自动清理: {log_cleanup_days}天前的日志") + logger.info( + f"日志系统已初始化:控制台={console_level},文件={file_level}," + f"轮转={max_log_files}个文件,清理={log_cleanup_days}天前" + ) def cleanup_old_logs(): @@ -875,12 +874,8 @@ def cleanup_old_logs(): logger.error(f"清理旧日志文件时出错: {e}") -def start_log_cleanup_task(verbose: bool = True): - """启动日志清理任务 - - Args: - verbose: 是否输出启动信息。默认为 True。 - """ +def start_log_cleanup_task(): + """启动日志清理任务""" global _cleanup_task_started # 防止重复启动清理任务 @@ -897,12 +892,6 @@ def start_log_cleanup_task(verbose: bool = True): cleanup_thread = threading.Thread(target=cleanup_task, daemon=True) cleanup_thread.start() - if verbose: - logger = get_logger("logger") - max_log_files = max(1, int(LOG_CONFIG.get("max_log_files", 30) or 30)) - log_cleanup_days = max(1, int(LOG_CONFIG.get("log_cleanup_days", 30) or 30)) - logger.info(f"已启动日志清理任务,将自动清理{log_cleanup_days}天前的日志文件(轮转份数限制: {max_log_files}个文件)") - def shutdown_logging(): """优雅关闭日志系统,释放所有文件句柄""" diff --git a/src/config/official_configs.py b/src/config/official_configs.py index e29743e5..fb96da9a 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -40,6 +40,7 @@ class BotConfig(ConfigBase): "x-icon": "wifi", "x-layout": "inline-right", "x-input-width": "12rem", + "x-row": "bot-platform-account", }, ) """平台""" @@ -51,6 +52,7 @@ class BotConfig(ConfigBase): "x-icon": "user", "x-layout": "inline-right", "x-input-width": "12rem", + "x-row": "bot-platform-account", }, ) """QQ账号""" diff --git a/src/webui/app.py b/src/webui/app.py index 6a0d5cf2..6fcb8f40 100644 --- a/src/webui/app.py +++ b/src/webui/app.py @@ -134,7 +134,7 @@ def _setup_anti_crawler(app: FastAPI): "basic": t("startup.webui_anti_crawler_mode_basic"), } mode_desc = mode_descriptions.get(anti_crawler_mode, t("startup.webui_anti_crawler_mode_basic")) - logger.info(t("startup.webui_anti_crawler_configured", mode_desc=mode_desc)) + logger.debug(t("startup.webui_anti_crawler_configured", mode_desc=mode_desc)) except Exception as e: logger.error(t("startup.webui_anti_crawler_config_failed", error=e), exc_info=True) @@ -159,7 +159,7 @@ def _register_api_routes(app: FastAPI): for router in get_all_routers(): app.include_router(router) - logger.info(t("startup.webui_api_routes_registered")) + logger.debug(t("startup.webui_api_routes_registered")) except Exception as e: logger.error(t("startup.webui_api_routes_register_failed", error=e), exc_info=True) @@ -217,7 +217,7 @@ def _setup_static_files(app: FastAPI): response.headers["X-Robots-Tag"] = "noindex, nofollow, noarchive" return response - logger.info(t("startup.webui_static_files_configured", static_path=static_path)) + logger.debug(t("startup.webui_static_files_configured", static_path=static_path)) def _resolve_static_path() -> Path | None: @@ -247,6 +247,5 @@ def show_access_token(): token_manager = get_token_manager() current_token = token_manager.get_token() logger.info(t("startup.webui_access_token", token=current_token)) - logger.info(t("startup.webui_access_token_login_hint")) except Exception as e: logger.error(t("startup.webui_access_token_failed", error=e)) diff --git a/src/webui/routers/expression.py b/src/webui/routers/expression.py index 696a1953..7283c350 100644 --- a/src/webui/routers/expression.py +++ b/src/webui/routers/expression.py @@ -15,6 +15,7 @@ from src.common.logger import get_logger from src.webui.dependencies import require_auth logger = get_logger("webui.expression") +EXCLUDE_IDS_QUERY = Query(None, description="需要排除的表达方式 ID") # 创建路由器 router = APIRouter(prefix="/expression", tags=["Expression"], dependencies=[Depends(require_auth)]) @@ -660,8 +661,10 @@ async def get_review_list( page: int = Query(1, ge=1, description="页码"), page_size: int = Query(20, ge=1, le=100, description="每页数量"), filter_type: str = Query("unchecked", description="筛选类型: unchecked/passed/rejected/all"), + order: str = Query("latest", description="排序方式: latest/random"), search: Optional[str] = Query(None, description="搜索关键词"), chat_id: Optional[str] = Query(None, description="聊天ID筛选"), + exclude_ids: Optional[List[int]] = EXCLUDE_IDS_QUERY, ) -> ReviewListResponse: """获取待审核或已审核的表达方式列表。 @@ -669,8 +672,10 @@ async def get_review_list( page: 页码。 page_size: 每页数量。 filter_type: 筛选类型,可选 unchecked、passed、rejected 或 all。 + order: 排序方式,可选 latest 或 random。 search: 搜索关键词。 chat_id: 聊天 ID 筛选条件。 + exclude_ids: 需要排除的表达方式 ID。 Returns: ReviewListResponse: 审核列表响应。 @@ -689,11 +694,17 @@ async def get_review_list( if chat_id: statement = statement.where(col(Expression.session_id) == chat_id) - # 排序:创建时间倒序 - statement = statement.order_by( - case((col(Expression.create_time).is_(None), 1), else_=0), - col(Expression.create_time).desc(), - ) + if exclude_ids: + statement = statement.where(~col(Expression.id).in_(exclude_ids)) + + if order == "random": + statement = statement.order_by(func.random()) + else: + # 排序:创建时间倒序 + statement = statement.order_by( + case((col(Expression.create_time).is_(None), 1), else_=0), + col(Expression.create_time).desc(), + ) offset = (page - 1) * page_size statement = statement.offset(offset).limit(page_size) @@ -731,7 +742,7 @@ class BatchReviewItem(BaseModel): id: int rejected: bool - require_unchecked: bool = True # 默认要求未检查状态 + require_unchecked: bool = True # 前端保留的来源标记,人工审核提交时不再阻断覆盖 class BatchReviewRequest(BaseModel): @@ -790,14 +801,6 @@ async def batch_review_expressions( failed += 1 continue - # 冲突检测:未审核列表发起的操作只允许处理仍处于未审核状态的条目。 - if item.require_unchecked and expression.checked: - results.append( - BatchReviewResultItem(id=item.id, success=False, message="该表达方式已被审核,请刷新列表后重试") - ) - failed += 1 - continue - # 更新状态 with get_db_session() as session: db_expression = session.exec( From ab21f97147d0609790e68ee66360377b7f812046 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 5 May 2026 18:38:28 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=9B=B4=E6=96=B0Webui=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- requirements.txt | 2 +- uv.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7ad5446e..2088a978 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "jieba>=0.42.1", "json-repair>=0.47.6", "maim-message>=0.6.2", - "maibot-dashboard>=1.0.4", + "maibot-dashboard>=1.0.5", "maibot-plugin-sdk>=2.4.0", "matplotlib>=3.10.5", "mcp", diff --git a/requirements.txt b/requirements.txt index afb3c8d6..833be7d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,4 +33,4 @@ tomlkit>=0.13.3 typing-extensions uvicorn>=0.35.0 watchfiles>=1.1.1 -maibot-dashboard>=1.0.4 \ No newline at end of file +maibot-dashboard>=1.0.5 \ No newline at end of file diff --git a/uv.lock b/uv.lock index b233ebd2..cf1133d6 100644 --- a/uv.lock +++ b/uv.lock @@ -1511,7 +1511,7 @@ requires-dist = [ { name = "httpx", extras = ["socks"] }, { name = "jieba", specifier = ">=0.42.1" }, { name = "json-repair", specifier = ">=0.47.6" }, - { name = "maibot-dashboard", specifier = ">=1.0.4" }, + { name = "maibot-dashboard", specifier = ">=1.0.5.dev2026050573" }, { name = "maibot-plugin-sdk", specifier = ">=2.4.0" }, { name = "maim-message", specifier = ">=0.6.2" }, { name = "matplotlib", specifier = ">=3.10.5" }, @@ -1549,11 +1549,11 @@ dev = [ [[package]] name = "maibot-dashboard" -version = "1.0.4" +version = "1.0.5.dev2026050574" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/0b/970a2c0b2cda503fd829aeca7d590a319f0556e9d9de8025cddfff746343/maibot_dashboard-1.0.4.tar.gz", hash = "sha256:c15b50017a923f8575b2d1991d8af96fd682d75bb4084eb21d6a03956b9168b6", size = 2470802, upload-time = "2026-05-04T10:22:01.146Z" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/b9/4cd7c3c78c937a8dd5b30f42889493819df1c56066d66855ea293f5e7171/maibot_dashboard-1.0.5.dev2026050574.tar.gz", hash = "sha256:1b032721ca8f52bc951406932888367e4d73ee06ad8aaaf5879e95b34495decf", size = 2477288, upload-time = "2026-05-05T09:59:02.15Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/99/7d822f14c9ae6f37e0536df239daa52b5e3ea7997d96471c1c8f2dccf248/maibot_dashboard-1.0.4-py3-none-any.whl", hash = "sha256:4b079f87ea537714914f65f53e596fbe166eb527fe27edb42b9dbcfcd115b4a8", size = 2537189, upload-time = "2026-05-04T10:21:59.922Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/7c/4e522a3632e60e4bee7a9a71dd46e2c60b05b1442db66c2e4a04657e780a/maibot_dashboard-1.0.5.dev2026050574-py3-none-any.whl", hash = "sha256:6a1af06ef226328a07b792cff8732a683f511602e428ea8f36b547b43590283a", size = 2541882, upload-time = "2026-05-05T09:59:00.595Z" }, ] [[package]]