// Shared helpers + window globals consumed by the JSX panels. // // The hardcoded INITIAL_AGENTS / SKILLS / MCPS arrays from the design have // been removed — they now come from the live backend via api-client.jsx and // get written onto `window.SKILLS` / `window.MCPS` / `window.AGENTS` once // the App's initial load completes. The globals start as empty arrays so // children that read them at first render (`SKILLS.map(...)`) don't crash. const AGENT_COLORS = ["#10a37f", "#2563eb", "#c026d3", "#d97706", "#ef4444", "#0ea5e9", "#7c3aed"]; // LLM 模型 + 上下文窗口的**权威来源是后端**(GET /v1/models,见 models_catalog.py)。 // 下面这个 const 只是 **bootstrap fallback** —— app 一挂载就 `api.listModels()` 把 // `window.MODELS` 覆盖成后端返回的真实列表(含 context_window)。所以读窗口一律走 // `window.MODELS`(动态),不要读这个 const。 const MODELS = [ { id: "deepseek-v4-flash", label: "deepseek-v4-flash", context_window: 655360, max_output_tokens: 409600, provider: "baizhi", providers: ["baizhi", "volcengine"] }, { id: "glm-5.2", label: "glm-5.2", context_window: 1048576, max_output_tokens: 131072, provider: "baizhi", providers: ["baizhi", "volcengine"] }, { id: "MiniMax/MiniMax-M2.7", label: "MiniMax/MiniMax-M2.7", context_window: 655360, provider: "baizhi" }, { id: "qwen3.6-plus", label: "qwen3.6-plus", context_window: 655360, provider: "baizhi" }, { id: "deepseek-v4-pro", label: "deepseek-v4-pro", context_window: 655360, provider: "baizhi" }, { id: "MiniMax/MiniMax-M3", label: "MiniMax/MiniMax-M3", context_window: 655360, provider: "baizhi" }, { id: "MiniMax-M2.7", label: "MiniMax-M2.7", context_window: 655360 }, { id: "MiniMax-M2.7-highspeed", label: "MiniMax-M2.7 (highspeed)", context_window: 655360 }, ]; // 找不到 model 时的兜底窗口(老 localStorage 里存了已下线 model 等场景)。 const DEFAULT_CONTEXT_WINDOW = 655360; function modelContextWindow(modelId) { // 优先读后端覆盖后的 window.MODELS;它还没 fetch 回来时退回 bootstrap const。 const list = (typeof window !== "undefined" && window.MODELS) ? window.MODELS : MODELS; const m = list.find((x) => x.id === modelId); return (m && m.context_window) || DEFAULT_CONTEXT_WINDOW; } function modelProviderFor(modelId) { const list = (typeof window !== "undefined" && window.MODELS) ? window.MODELS : MODELS; const explicitProvider = list.find((model) => model.id === modelId)?.provider; if (explicitProvider) return explicitProvider; const id = String(modelId || "").toLowerCase(); if (!id) return ""; if (id.startsWith("minimax-")) return "minimax"; if (id.startsWith("gpt-") || id.startsWith("o1") || id.startsWith("o3") || id.startsWith("o4")) return "openai"; if (id.startsWith("claude-")) return "anthropic"; if (id.includes("/")) return "openrouter"; return ""; } function modelOptionsForProvider(provider) { const list = (typeof window !== "undefined" && window.MODELS) ? window.MODELS : MODELS; const filtered = list.filter((m) => !provider || modelProviderFor(m.id) === provider || (m.providers || []).includes(provider)); return filtered; } function defaultModelForProvider(provider) { const providers = window.MODEL_PROVIDERS || MODEL_PROVIDERS; return modelOptionsForProvider(provider)[0]?.id || providers.find((item) => item.id === provider)?.default_model || MODELS[0]?.id || ""; } const MODEL_PROVIDERS = [ { id: "baizhi", label: "Baizhi", default_model: "deepseek-v4-flash", endpoint_display: "http://litellm-test.100wiser.com", credential_status: "server_managed" }, { id: "minimax", label: "MiniMax", default_model: "MiniMax-M2.7", endpoint_display: "https://api.minimaxi.com/v1", credential_status: "server_managed" }, { id: "openrouter", label: "OpenRouter", default_model: "openai/gpt-4o-mini", endpoint_display: "https://openrouter.ai/api/v1", credential_status: "server_managed" }, { id: "litellm", label: "LiteLLM compatible", default_model: "deepseek-v4-flash", endpoint_display: "", credential_status: "server_managed" }, { id: "openai", label: "OpenAI", default_model: "gpt-4o-mini", endpoint_display: "https://api.openai.com/v1", credential_status: "server_managed" }, { id: "openai-compatible", label: "OpenAI compatible", default_model: "gpt-4o-mini", endpoint_display: "", credential_status: "server_managed" }, { id: "volcengine", label: "volcengine", default_model: "deepseek-v4-flash", endpoint_display: "https://ark.cn-beijing.volces.com/api/plan/v3", credential_status: "server_managed" }, { id: "anthropic", label: "Anthropic", default_model: "claude-haiku-4.5", endpoint_display: "https://api.anthropic.com", credential_status: "server_managed" }, ]; window.MODEL_PROVIDERS = MODEL_PROVIDERS; function modelEndpointForProvider(provider) { const list = window.MODEL_PROVIDERS || MODEL_PROVIDERS; return list.find((item) => item.id === provider)?.endpoint_display || ""; } function modelCredentialStatusForProvider(provider) { const list = window.MODEL_PROVIDERS || MODEL_PROVIDERS; return list.find((item) => item.id === provider)?.credential_status || "server_managed"; } // Start empty — api-client overwrites these once data loads. const SKILLS = []; const MCPS = []; const LOCAL_TOOLS = []; const INITIAL_AGENTS = []; // ---------- id + time helpers ---------- function rid(prefix, len = 12) { const chars = "abcdefghijklmnopqrstuvwxyz0123456789"; let s = ""; for (let i = 0; i < len; i++) s += chars[Math.floor(Math.random() * chars.length)]; return prefix + "_" + s; } function nowHHMMSS() { const d = new Date(); const pad = (n) => String(n).padStart(2, "0"); return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; } function fmtMs(ms) { if (!Number.isFinite(ms)) return "0ms"; if (ms < 1000) return `${Math.round(ms)}ms`; return `${(ms / 1000).toFixed(2)}s`; } // ---------- syntax-highlight a JSON string ---------- function highlightJSON(json) { return json .replace(/&/g, "&").replace(//g, ">") .replace(/("(?:\\.|[^"\\])*")(\s*:)/g, '$1$2') .replace(/:\s*("(?:\\.|[^"\\])*")/g, ': $1') .replace(/\b(true|false|null)\b/g, '$1') .replace(/:\s*(-?\d+\.?\d*)/g, ': $1'); } function escapeHTML(value) { return String(value ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function extractModelOutputMarkdown(output) { const texts = []; const visit = (value) => { if (value == null) return; if (typeof value === "string") { if (value.trim()) texts.push(value); return; } if (Array.isArray(value)) { value.forEach(visit); return; } if (typeof value !== "object") return; if (typeof value.text === "string") texts.push(value.text); if (typeof value.content === "string") texts.push(value.content); if (value.parts) visit(value.parts); if (value.content && typeof value.content === "object") visit(value.content); }; visit(output?.content?.parts || output?.parts || output?.content || output?.text || output); return texts.join("\n\n").trim(); } function renderMarkdownLite(md) { const lines = String(md || "").replace(/\r\n/g, "\n").split("\n"); let html = ""; let inCode = false; let code = []; let inList = false; let inTable = false; let tableRows = []; const inline = (text) => escapeHTML(text) .replace(/`([^`]+)`/g, "$1") .replace(/\*\*([^*]+)\*\*/g, "$1") .replace(/\*([^*]+)\*/g, "$1"); const closeList = () => { if (inList) { html += ""; inList = false; } }; const flushTable = () => { if (!inTable) return; if (tableRows.length) { const [head, ...body] = tableRows; html += "" + head.map((c) => ``).join("") + ""; html += "" + body.map((row) => "" + row.map((c) => ``).join("") + "").join("") + "
${inline(c.trim())}
${inline(c.trim())}
"; } inTable = false; tableRows = []; }; const isSep = (cells) => cells.length > 1 && cells.every((c) => /^:?-{3,}:?$/.test(c.trim())); const tableCells = (line) => line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|"); for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.trim().startsWith("```")) { flushTable(); closeList(); if (inCode) { html += `
${escapeHTML(code.join("\n"))}
`; inCode = false; code = []; } else { inCode = true; code = []; } continue; } if (inCode) { code.push(line); continue; } const cells = tableCells(line); if (line.includes("|") && cells.length > 1) { const nextLine = lines[i + 1] || ""; const nextCells = tableCells(nextLine); if (inTable || isSep(nextCells)) { closeList(); if (isSep(cells)) continue; inTable = true; tableRows.push(cells); continue; } } flushTable(); if (!line.trim()) { closeList(); continue; } const heading = /^(#{1,4})\s+(.+)$/.exec(line); if (heading) { closeList(); const level = heading[1].length; html += `${inline(heading[2])}`; continue; } const bullet = /^\s*[-*]\s+(.+)$/.exec(line); if (bullet) { if (!inList) { html += "