v0.1.4-p2

main
mula.liu 2026-03-13 15:40:30 +08:00
parent a806ffcabf
commit 5673cb49b6
4 changed files with 36 additions and 13 deletions

View File

@ -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. # We keep process-provided env untouched, while allowing .env.prod to override backend/.env.
_process_env_keys = set(os.environ.keys()) _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) 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: if _v is None:
continue continue
if _k in _process_env_keys: 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)) 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: def _normalize_extension(raw: str) -> str:
text = str(raw or "").strip().lower() text = str(raw or "").strip().lower()
if not text: 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_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) 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() PANEL_ACCESS_PASSWORD: Final[str] = str(os.getenv("PANEL_ACCESS_PASSWORD") or "").strip()
TOPIC_MCP_INTERNAL_URL: Final[str] = str( _topic_mcp_default = "http://host.docker.internal:8000/api/mcp/topic"
os.getenv("TOPIC_MCP_INTERNAL_URL") or "http://host.docker.internal:8000/api/mcp/topic" _topic_mcp_from_env = str(os.getenv("TOPIC_MCP_INTERNAL_URL") or "").strip()
).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() TEMPLATE_ROOT: Final[Path] = (BACKEND_ROOT / "templates").resolve()
AGENT_MD_TEMPLATES_FILE: Final[Path] = TEMPLATE_ROOT / "agent_md_templates.json" AGENT_MD_TEMPLATES_FILE: Final[Path] = TEMPLATE_ROOT / "agent_md_templates.json"

View File

@ -517,7 +517,6 @@ async def on_startup():
with Session(engine) as session: with Session(engine) as session:
for bot in session.exec(select(BotInstance)).all(): for bot in session.exec(select(BotInstance)).all():
_migrate_bot_resources_store(bot.id) _migrate_bot_resources_store(bot.id)
_ensure_topic_mcp_server(bot.id)
running_bots = session.exec(select(BotInstance).where(BotInstance.docker_status == "RUNNING")).all() running_bots = session.exec(select(BotInstance).where(BotInstance.docker_status == "RUNNING")).all()
for bot in running_bots: for bot in running_bots:
docker_manager.ensure_monitor(bot.id, docker_callback) docker_manager.ensure_monitor(bot.id, docker_callback)
@ -1471,7 +1470,6 @@ def _sync_workspace_channels(
bot_data=bot_data, bot_data=bot_data,
channels=normalized_channels, channels=normalized_channels,
) )
_ensure_topic_mcp_server(bot_id)
_write_bot_resources( _write_bot_resources(
bot_id, bot_id,
bot_data.get("cpu_cores"), 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): if not isinstance(tools_cfg, dict):
tools_cfg = {} tools_cfg = {}
mcp_servers = _normalize_mcp_servers(payload.mcp_servers or {}) 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 tools_cfg["mcpServers"] = mcp_servers
config_data["tools"] = tools_cfg config_data["tools"] = tools_cfg
_write_bot_config(bot_id, config_data) _write_bot_config(bot_id, config_data)
@ -2401,7 +2397,7 @@ def update_bot_mcp_config(bot_id: str, payload: BotMcpConfigUpdateRequest, sessi
"status": "updated", "status": "updated",
"bot_id": bot_id, "bot_id": bot_id,
"mcp_servers": _annotate_locked_mcp_servers(mcp_servers), "mcp_servers": _annotate_locked_mcp_servers(mcp_servers),
"locked_servers": [TOPIC_MCP_SERVER_NAME], "locked_servers": [],
"restart_required": True, "restart_required": True,
} }

View File

@ -24,8 +24,7 @@ services:
REDIS_PREFIX: ${REDIS_PREFIX:-dashboard_nanobot} REDIS_PREFIX: ${REDIS_PREFIX:-dashboard_nanobot}
REDIS_DEFAULT_TTL: ${REDIS_DEFAULT_TTL:-60} REDIS_DEFAULT_TTL: ${REDIS_DEFAULT_TTL:-60}
PANEL_ACCESS_PASSWORD: ${PANEL_ACCESS_PASSWORD:-} PANEL_ACCESS_PASSWORD: ${PANEL_ACCESS_PASSWORD:-}
AGENT_MD_TEMPLATES_FILE: ${AGENT_MD_TEMPLATES_FILE:-templates/agent_md_templates.json} TOPIC_MCP_INTERNAL_URL: ${TOPIC_MCP_INTERNAL_URL:-http://host.docker.internal:8000/api/mcp/topic}
TOPIC_PRESETS_TEMPLATES_FILE: ${TOPIC_PRESETS_TEMPLATES_FILE:-templates/topic_presets.json}
STT_ENABLED: ${STT_ENABLED:-true} STT_ENABLED: ${STT_ENABLED:-true}
STT_MODEL: ${STT_MODEL:-ggml-small-q8_0.bin} STT_MODEL: ${STT_MODEL:-ggml-small-q8_0.bin}
STT_MODEL_DIR: ${STT_MODEL_DIR:-${HOST_DATA_ROOT}/model} STT_MODEL_DIR: ${STT_MODEL_DIR:-${HOST_DATA_ROOT}/model}

View File

@ -2854,9 +2854,21 @@ export function BotDashboardModule({
setMcpTestByIndex((prev) => ({ ...prev, [index]: { status: 'idle', message: '' } })); 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 removeMcpServer = (index: number) => {
const row = mcpServers[index]; const row = mcpServers[index];
if (row?.locked) { if (!canRemoveMcpServer(row)) {
notify(isZh ? '内置 MCP 服务不可删除。' : 'Built-in MCP server cannot be removed.', { tone: 'warning' }); notify(isZh ? '内置 MCP 服务不可删除。' : 'Built-in MCP server cannot be removed.', { tone: 'warning' });
return; return;
} }
@ -6120,7 +6132,7 @@ export function BotDashboardModule({
</button> </button>
<LucentIconButton <LucentIconButton
className="btn btn-danger btn-sm wizard-icon-btn" className="btn btn-danger btn-sm wizard-icon-btn"
disabled={row.locked} disabled={isSavingMcp || !canRemoveMcpServer(row)}
onClick={() => removeMcpServer(idx)} onClick={() => removeMcpServer(idx)}
tooltip={t.removeSkill} tooltip={t.removeSkill}
aria-label={t.removeSkill} aria-label={t.removeSkill}