feat:优化webui多个页面的人机交互,修复插件地址问题,放宽插件id限制,增加高级页面缩进,统计页面快捷按钮,优化新手引导
This commit is contained in:
@@ -58,7 +58,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"
|
||||
CONFIG_VERSION: str = "8.9.21"
|
||||
CONFIG_VERSION: str = "8.10.1"
|
||||
MODEL_CONFIG_VERSION: str = "1.14.6"
|
||||
|
||||
logger = get_logger("config")
|
||||
|
||||
@@ -63,6 +63,7 @@ class BotConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "custom",
|
||||
"x-icon": "tags",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""别名列表"""
|
||||
@@ -472,6 +473,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "message-circle-warning",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""是否启用反馈驱动的延迟记忆纠错任务"""
|
||||
@@ -482,6 +484,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "clock-4",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""反馈窗口时长(小时),以 query_memory 执行时间为起点"""
|
||||
@@ -492,6 +495,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "timer",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""反馈纠错定时任务轮询间隔(分钟)"""
|
||||
@@ -503,6 +507,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "list-ordered",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""反馈纠错每轮最大处理任务数"""
|
||||
@@ -515,6 +520,7 @@ class MemoryConfig(ConfigBase):
|
||||
"x-widget": "slider",
|
||||
"x-icon": "gauge",
|
||||
"step": 0.01,
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""自动应用纠错动作的最低置信度阈值"""
|
||||
@@ -526,6 +532,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "messages-square",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""每个纠错任务最多使用的窗口内用户反馈消息数"""
|
||||
@@ -535,6 +542,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "filter",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""是否启用纠错前置预筛(用于减少不必要的模型调用)"""
|
||||
@@ -544,6 +552,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "sticky-note",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""是否为受影响 paragraph 写入已纠正旧事实标记"""
|
||||
@@ -553,6 +562,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "eye-off",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""是否在用户侧查询中硬过滤带有 stale 标记的 paragraph"""
|
||||
@@ -562,6 +572,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "user-round-search",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""是否在反馈纠错后将受影响人物画像加入刷新队列"""
|
||||
@@ -571,6 +582,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "refresh-ccw",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""人物画像处于脏队列时,读取是否强制刷新而不直接复用旧快照"""
|
||||
@@ -580,6 +592,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "clapperboard",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""是否在反馈纠错后将受影响 source 加入 episode 重建队列"""
|
||||
@@ -589,6 +602,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "switch",
|
||||
"x-icon": "ban",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""episode source 处于重建队列时,是否对用户侧查询做屏蔽"""
|
||||
@@ -599,6 +613,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "repeat",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""反馈纠错二阶段一致性后台协调任务轮询间隔(分钟)"""
|
||||
@@ -610,6 +625,7 @@ class MemoryConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "list-restart",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""反馈纠错二阶段一致性每轮处理 profile/episode 队列的批大小"""
|
||||
@@ -1350,6 +1366,7 @@ class ChineseTypoConfig(ConfigBase):
|
||||
"x-widget": "slider",
|
||||
"x-icon": "percent",
|
||||
"step": 0.01,
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""单字替换概率"""
|
||||
@@ -1359,6 +1376,7 @@ class ChineseTypoConfig(ConfigBase):
|
||||
json_schema_extra={
|
||||
"x-widget": "input",
|
||||
"x-icon": "hash",
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""最小字频阈值"""
|
||||
@@ -1371,6 +1389,7 @@ class ChineseTypoConfig(ConfigBase):
|
||||
"x-widget": "slider",
|
||||
"x-icon": "percent",
|
||||
"step": 0.1,
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""声调错误概率"""
|
||||
@@ -1383,6 +1402,7 @@ class ChineseTypoConfig(ConfigBase):
|
||||
"x-widget": "slider",
|
||||
"x-icon": "percent",
|
||||
"step": 0.001,
|
||||
"advanced": True,
|
||||
},
|
||||
)
|
||||
"""整词替换概率"""
|
||||
|
||||
@@ -143,6 +143,33 @@ def _serialize_messages(messages: List[Any]) -> List[Dict[str, Any]]:
|
||||
return [_serialize_message(message) for message in messages]
|
||||
|
||||
|
||||
def _enrich_session_identity(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""为监控事件补充会话展示所需的群/用户标识。"""
|
||||
|
||||
session_id = data.get("session_id")
|
||||
if not session_id:
|
||||
return data
|
||||
|
||||
try:
|
||||
from src.chat.message_receive.chat_manager import chat_manager
|
||||
|
||||
chat_stream = chat_manager.get_session_by_session_id(str(session_id))
|
||||
except Exception:
|
||||
return data
|
||||
|
||||
if chat_stream is None:
|
||||
return data
|
||||
|
||||
session_name = chat_manager.get_session_name(str(session_id))
|
||||
if session_name:
|
||||
data.setdefault("session_name", session_name)
|
||||
data.setdefault("is_group_chat", chat_stream.is_group_session)
|
||||
data.setdefault("group_id", chat_stream.group_id)
|
||||
data.setdefault("user_id", chat_stream.user_id)
|
||||
data.setdefault("platform", chat_stream.platform)
|
||||
return data
|
||||
|
||||
|
||||
def _serialize_tool_results(tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""标准化最终 planner 卡中的工具结果列表。"""
|
||||
|
||||
@@ -266,6 +293,7 @@ async def _broadcast(event: str, data: Dict[str, Any]) -> None:
|
||||
try:
|
||||
from src.webui.routers.websocket.manager import websocket_manager
|
||||
|
||||
data = _enrich_session_identity(data)
|
||||
subscription_key = f"{MONITOR_DOMAIN}:{MONITOR_TOPIC}"
|
||||
total_connections = len(websocket_manager.connections)
|
||||
subscriber_count = sum(
|
||||
@@ -291,12 +319,24 @@ async def _broadcast(event: str, data: Dict[str, Any]) -> None:
|
||||
logger.warning(f"MaiSaka 监控事件广播失败: {exc}", exc_info=True)
|
||||
|
||||
|
||||
async def emit_session_start(session_id: str, session_name: str) -> None:
|
||||
async def emit_session_start(
|
||||
session_id: str,
|
||||
session_name: str,
|
||||
*,
|
||||
is_group_chat: bool,
|
||||
group_id: Optional[str],
|
||||
user_id: Optional[str],
|
||||
platform: str,
|
||||
) -> None:
|
||||
"""广播会话开始事件。"""
|
||||
|
||||
await _broadcast("session.start", {
|
||||
"session_id": session_id,
|
||||
"session_name": session_name,
|
||||
"is_group_chat": is_group_chat,
|
||||
"group_id": group_id,
|
||||
"user_id": user_id,
|
||||
"platform": platform,
|
||||
"timestamp": time.time(),
|
||||
})
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ from .display.display_utils import build_tool_call_summary_lines, format_token_c
|
||||
from .display.prompt_cli_renderer import PromptCLIVisualizer
|
||||
from .display.stage_status_board import remove_stage_status, update_stage_status
|
||||
from .history_utils import drop_leading_orphan_tool_results
|
||||
from .monitor_events import emit_session_start
|
||||
from .reasoning_engine import MaisakaReasoningEngine
|
||||
from .reply_effect import ReplyEffectTracker
|
||||
from .reply_effect.image_utils import extract_visual_attachments_from_sequence
|
||||
@@ -136,6 +137,7 @@ class MaisakaHeartFlowChatting:
|
||||
self._jargon_miner = JargonMiner(session_id, session_name=session_name)
|
||||
|
||||
self._reasoning_engine = MaisakaReasoningEngine(self)
|
||||
self._monitor_session_start_task: Optional[asyncio.Task[None]] = None
|
||||
self._tool_registry = ToolRegistry()
|
||||
self._reply_effect_tracker = ReplyEffectTracker(
|
||||
session_id=self.session_id,
|
||||
@@ -144,6 +146,24 @@ class MaisakaHeartFlowChatting:
|
||||
judge_runner=self._run_reply_effect_judge,
|
||||
)
|
||||
self._register_tool_providers()
|
||||
self._emit_monitor_session_start()
|
||||
|
||||
def _emit_monitor_session_start(self) -> None:
|
||||
"""向 WebUI 监控面板同步当前会话的展示标识。"""
|
||||
|
||||
try:
|
||||
self._monitor_session_start_task = asyncio.create_task(
|
||||
emit_session_start(
|
||||
session_id=self.session_id,
|
||||
session_name=self.session_name,
|
||||
is_group_chat=self.chat_stream.is_group_session,
|
||||
group_id=self.chat_stream.group_id,
|
||||
user_id=self.chat_stream.user_id,
|
||||
platform=self.chat_stream.platform,
|
||||
)
|
||||
)
|
||||
except RuntimeError:
|
||||
logger.debug("MaiSaka 监控会话开始事件未发送:当前没有运行中的事件循环")
|
||||
|
||||
@staticmethod
|
||||
def _is_reply_effect_tracking_enabled() -> bool:
|
||||
|
||||
@@ -24,7 +24,7 @@ from src.common.logger import get_logger
|
||||
logger = get_logger("plugin_runtime.runner.manifest_validator")
|
||||
|
||||
_SEMVER_PATTERN = re.compile(r"^\d+\.\d+\.\d+$")
|
||||
_PLUGIN_ID_PATTERN = re.compile(r"^[a-z0-9]+(?:[.-][a-z0-9]+)+$")
|
||||
_PLUGIN_ID_PATTERN = re.compile(r"^[A-Za-z0-9_]+(?:[.-][A-Za-z0-9_]+)+$")
|
||||
_PACKAGE_NAME_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
|
||||
_HTTP_URL_PATTERN = re.compile(r"^https?://.+$")
|
||||
|
||||
@@ -379,7 +379,7 @@ class PluginDependencyDefinition(_StrictManifestModel):
|
||||
ValueError: 当 ID 不符合规则时抛出。
|
||||
"""
|
||||
if not _PLUGIN_ID_PATTERN.fullmatch(value):
|
||||
raise ValueError("必须使用小写字母/数字,并以点号或横线分隔,例如 github.author.plugin")
|
||||
raise ValueError("必须使用字母/数字/下划线,并以点号或横线分隔,例如 github.author.plugin")
|
||||
return value
|
||||
|
||||
@field_validator("version_spec")
|
||||
@@ -548,7 +548,7 @@ class PluginManifest(_StrictManifestModel):
|
||||
if not value:
|
||||
raise ValueError("不能为空")
|
||||
if info.field_name == "id" and not _PLUGIN_ID_PATTERN.fullmatch(value):
|
||||
raise ValueError("必须使用小写字母/数字,并以点号或横线分隔,例如 github.author.plugin")
|
||||
raise ValueError("必须使用字母/数字/下划线,并以点号或横线分隔,例如 github.author.plugin")
|
||||
return value
|
||||
|
||||
@field_validator("capabilities")
|
||||
|
||||
@@ -185,6 +185,16 @@ def _setup_static_files(app: FastAPI):
|
||||
logger.warning(t("startup.webui_dashboard_package_hint", command=_MANUAL_INSTALL_COMMAND))
|
||||
return
|
||||
|
||||
@app.get("/maibot_statistics.html", include_in_schema=False)
|
||||
async def serve_statistics_report():
|
||||
report_path = (_get_project_root() / "maibot_statistics.html").resolve()
|
||||
if not report_path.exists() or not report_path.is_file():
|
||||
raise HTTPException(status_code=404, detail=t("core.not_found"))
|
||||
|
||||
response = FileResponse(report_path, media_type="text/html")
|
||||
response.headers["X-Robots-Tag"] = "noindex, nofollow, noarchive"
|
||||
return response
|
||||
|
||||
@app.get("/{full_path:path}", include_in_schema=False)
|
||||
async def serve_spa(full_path: str):
|
||||
if not full_path or full_path == "/":
|
||||
|
||||
@@ -19,8 +19,6 @@ class ConfigSchemaGenerator:
|
||||
for field_name, field_info in config_class.model_fields.items():
|
||||
if field_name in {"field_docs", "_validate_any", "suppress_any_warning"}:
|
||||
continue
|
||||
if cls._is_advanced_field(field_info):
|
||||
continue
|
||||
|
||||
field_schema = cls._build_field_schema(config_class, field_name, field_info.annotation, field_info)
|
||||
fields.append(field_schema)
|
||||
@@ -50,13 +48,6 @@ class ConfigSchemaGenerator:
|
||||
|
||||
return schema
|
||||
|
||||
@staticmethod
|
||||
def _is_advanced_field(field_info: Any) -> bool:
|
||||
extra = getattr(field_info, "json_schema_extra", None)
|
||||
if not isinstance(extra, dict):
|
||||
return False
|
||||
return extra.get("advanced", False) is True
|
||||
|
||||
@classmethod
|
||||
def _build_nested_schema(cls, annotation: Any) -> Dict[str, Any] | None:
|
||||
origin = get_origin(annotation)
|
||||
|
||||
Reference in New Issue
Block a user