From 5673cb49b64ab95c9066c568fc0e168d25a60ac4 Mon Sep 17 00:00:00 2001 From: "mula.liu" Date: Fri, 13 Mar 2026 15:40:30 +0800 Subject: [PATCH] v0.1.4-p2 --- backend/core/settings.py | 24 +++++++++++++++---- backend/main.py | 6 +---- docker-compose.prod.yml | 3 +-- .../modules/dashboard/BotDashboardModule.tsx | 16 +++++++++++-- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/backend/core/settings.py b/backend/core/settings.py index 9a07cef..7c6f9b4 100644 --- a/backend/core/settings.py +++ b/backend/core/settings.py @@ -18,8 +18,11 @@ PROJECT_ROOT: Final[Path] = BACKEND_ROOT.parent # # We keep process-provided env untouched, while allowing .env.prod to override backend/.env. _process_env_keys = set(os.environ.keys()) +_backend_env_values = dotenv_values(BACKEND_ROOT / ".env") +_prod_env_values = dotenv_values(PROJECT_ROOT / ".env.prod") + load_dotenv(BACKEND_ROOT / ".env", override=False) -for _k, _v in dotenv_values(PROJECT_ROOT / ".env.prod").items(): +for _k, _v in _prod_env_values.items(): if _v is None: continue if _k in _process_env_keys: @@ -52,6 +55,10 @@ def _env_int(name: str, default: int, min_value: int, max_value: int) -> int: return max(min_value, min(max_value, value)) +def _is_truthy(raw: object) -> bool: + return str(raw or "").strip().lower() in {"1", "true", "yes", "on", "y"} + + def _normalize_extension(raw: str) -> str: text = str(raw or "").strip().lower() if not text: @@ -216,9 +223,18 @@ REDIS_URL: Final[str] = str(os.getenv("REDIS_URL") or "").strip() REDIS_PREFIX: Final[str] = str(os.getenv("REDIS_PREFIX") or "dashboard_nanobot").strip() or "dashboard_nanobot" REDIS_DEFAULT_TTL: Final[int] = _env_int("REDIS_DEFAULT_TTL", 60, 1, 86400) PANEL_ACCESS_PASSWORD: Final[str] = str(os.getenv("PANEL_ACCESS_PASSWORD") or "").strip() -TOPIC_MCP_INTERNAL_URL: Final[str] = str( - os.getenv("TOPIC_MCP_INTERNAL_URL") or "http://host.docker.internal:8000/api/mcp/topic" -).strip() +_topic_mcp_default = "http://host.docker.internal:8000/api/mcp/topic" +_topic_mcp_from_env = str(os.getenv("TOPIC_MCP_INTERNAL_URL") or "").strip() +_topic_mcp_from_backend_env = str(_backend_env_values.get("TOPIC_MCP_INTERNAL_URL") or "").strip() +_dev_mode = _is_truthy(os.getenv("APP_RELOAD")) or str(os.getenv("APP_ENV") or "").strip().lower() in { + "dev", + "development", + "local", +} +if _dev_mode and _topic_mcp_from_backend_env: + TOPIC_MCP_INTERNAL_URL: Final[str] = _topic_mcp_from_backend_env +else: + TOPIC_MCP_INTERNAL_URL: Final[str] = _topic_mcp_from_env or _topic_mcp_default TEMPLATE_ROOT: Final[Path] = (BACKEND_ROOT / "templates").resolve() AGENT_MD_TEMPLATES_FILE: Final[Path] = TEMPLATE_ROOT / "agent_md_templates.json" diff --git a/backend/main.py b/backend/main.py index 64f3a36..2be3e1a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -517,7 +517,6 @@ async def on_startup(): with Session(engine) as session: for bot in session.exec(select(BotInstance)).all(): _migrate_bot_resources_store(bot.id) - _ensure_topic_mcp_server(bot.id) running_bots = session.exec(select(BotInstance).where(BotInstance.docker_status == "RUNNING")).all() for bot in running_bots: docker_manager.ensure_monitor(bot.id, docker_callback) @@ -1471,7 +1470,6 @@ def _sync_workspace_channels( bot_data=bot_data, channels=normalized_channels, ) - _ensure_topic_mcp_server(bot_id) _write_bot_resources( bot_id, bot_data.get("cpu_cores"), @@ -2391,8 +2389,6 @@ def update_bot_mcp_config(bot_id: str, payload: BotMcpConfigUpdateRequest, sessi if not isinstance(tools_cfg, dict): tools_cfg = {} mcp_servers = _normalize_mcp_servers(payload.mcp_servers or {}) - locked_server = _ensure_topic_mcp_server(bot_id, config_data=config_data, persist=False) - mcp_servers[TOPIC_MCP_SERVER_NAME] = locked_server tools_cfg["mcpServers"] = mcp_servers config_data["tools"] = tools_cfg _write_bot_config(bot_id, config_data) @@ -2401,7 +2397,7 @@ def update_bot_mcp_config(bot_id: str, payload: BotMcpConfigUpdateRequest, sessi "status": "updated", "bot_id": bot_id, "mcp_servers": _annotate_locked_mcp_servers(mcp_servers), - "locked_servers": [TOPIC_MCP_SERVER_NAME], + "locked_servers": [], "restart_required": True, } diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 7c69539..c23e2b0 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -24,8 +24,7 @@ services: REDIS_PREFIX: ${REDIS_PREFIX:-dashboard_nanobot} REDIS_DEFAULT_TTL: ${REDIS_DEFAULT_TTL:-60} PANEL_ACCESS_PASSWORD: ${PANEL_ACCESS_PASSWORD:-} - AGENT_MD_TEMPLATES_FILE: ${AGENT_MD_TEMPLATES_FILE:-templates/agent_md_templates.json} - TOPIC_PRESETS_TEMPLATES_FILE: ${TOPIC_PRESETS_TEMPLATES_FILE:-templates/topic_presets.json} + TOPIC_MCP_INTERNAL_URL: ${TOPIC_MCP_INTERNAL_URL:-http://host.docker.internal:8000/api/mcp/topic} STT_ENABLED: ${STT_ENABLED:-true} STT_MODEL: ${STT_MODEL:-ggml-small-q8_0.bin} STT_MODEL_DIR: ${STT_MODEL_DIR:-${HOST_DATA_ROOT}/model} diff --git a/frontend/src/modules/dashboard/BotDashboardModule.tsx b/frontend/src/modules/dashboard/BotDashboardModule.tsx index 0c11427..af1bca7 100644 --- a/frontend/src/modules/dashboard/BotDashboardModule.tsx +++ b/frontend/src/modules/dashboard/BotDashboardModule.tsx @@ -2854,9 +2854,21 @@ export function BotDashboardModule({ setMcpTestByIndex((prev) => ({ ...prev, [index]: { status: 'idle', message: '' } })); }; + const isTopicMcpServerRow = (row?: MCPServerDraft | null) => { + if (!row) return false; + const name = String(row.name || row.originName || '').trim().toLowerCase(); + return name === 'topic_mcp'; + }; + + const canRemoveMcpServer = (row?: MCPServerDraft | null) => { + if (!row) return false; + if (!row.locked) return true; + return isTopicMcpServerRow(row); + }; + const removeMcpServer = (index: number) => { const row = mcpServers[index]; - if (row?.locked) { + if (!canRemoveMcpServer(row)) { notify(isZh ? '内置 MCP 服务不可删除。' : 'Built-in MCP server cannot be removed.', { tone: 'warning' }); return; } @@ -6120,7 +6132,7 @@ export function BotDashboardModule({ removeMcpServer(idx)} tooltip={t.removeSkill} aria-label={t.removeSkill}