/* WAREHOUSE 2.0 · 設置 — Swiss 版式,真後端(只讀駕駛艙,改動交秘書) */
(() => {
const W2 = window.W2;
const { t } = window.W2_LANG;
window.W2_LANG.addEN({
  "設置": "Settings",
  "系統配置一頁盡覽:AI 密鑰 · 監測規則 · 倉庫 · 分類 · 導航 · 提示詞 · 通用開關;頁面只讀,改動一律交秘書執行留痕。": "Every system setting on one page: AI keys, rules, warehouses, categories, nav, prompts, general switches. Read-only — all changes go through the Secretary, fully audited.",
  "問秘書": "Ask Secretary",
  "刷新": "Refresh",
  "檢查系統設置:AI 密鑰連接、監測規則、倉庫與分類配置,有什麼需要完善的?": "Review system settings: AI key connections, monitoring rules, warehouse and category setup — what needs attention?",
  "在管倉庫": "Warehouses",
  "物資分類": "Material categories",
  "權限角色": "Permission roles",
  "AI 連接": "AI connections",
  "個": "sites",
  "類": "types",
  "/3 已連接": "/3 connected",
  "共 {n} 個功能區": "{n} zones in total",
  "需歸還 {n} 類": "{n} returnable",
  "分級授權 · 高危可見": "Tiered grants · risk visible",
  "全部就緒": "All connected",
  "有連接失敗": "Connection failed",
  "有待配置": "Setup pending",
  /* 01 AI 密鑰 */
  "AI 引擎與全局密鑰": "AI engines & global keys",
  "管理員配置一次 · 全員共用 · 密鑰入庫保存": "Configured once by an admin · shared by everyone · keys stored in the database",
  "智能引擎": "AI Engine",
  "圖片識別": "Vision",
  "語音功能": "Voice",
  "驅動秘書對話與智能分析,全公司共用一把全局 key。": "Powers the Secretary and analytics; one global key for the whole company.",
  "圖片識別共用密鑰,後端按接入地址自動識別供應商。": "Shared key for image recognition; provider auto-detected from the base URL.",
  "語音識別與朗讀共用密鑰,未配置時自動降級瀏覽器原生語音。": "Shared key for ASR + TTS; falls back to native browser speech when unset.",
  "可連接": "Connected",
  "連接失敗": "Failed",
  "待驗證": "Unverified",
  "未配置": "Not configured",
  "共用 Key": "Shared key",
  "模型": "Model",
  "最近驗證": "Last checked",
  "識別": "ASR",
  "朗讀": "TTS",
  "交秘書換密鑰": "Change key",
  "重新驗證": "Re-validate",
  "我要更換{name}的全局 API Key,請告訴我怎麼安全提交,並保存後驗證連通": "I want to replace the global API key for {name}. Walk me through submitting it safely, then save and validate the connection",
  "重新驗證{name}的 API 連接,把延遲和結果告訴我": "Re-validate the {name} API connection and report latency and result",
  "AI 運行時": "AI RUNTIME",
  "狀態": "Status",
  "知識文檔": "Docs",
  "行動計劃": "Plans",
  "活躍對話": "Conversations",
  "記憶": "Memories",
  "經驗": "Lessons",
  "運行時暫無數據": "No runtime data yet",
  "自檢": "Self-check",
  "跑一次 AI 資料庫自檢,把結果彙報給我": "Run an AI database self-check and report the results to me",
  /* 02 規則與邊界 */
  "AI 監測規則與操作邊界": "AI monitoring rules & boundaries",
  "規則開關持久化 · 邊界不可逾越": "Switches persist server-side · boundaries are absolute",
  "低庫存自動預警": "Low-stock auto alert",
  "庫存低於安全庫存時自動生成預警並推送倉管": "Auto-generate an alert and notify keepers when stock falls below the safety line",
  "超期未檢提醒": "Inspection-due reminder",
  "距檢驗到期 ≤15 天自動推送計量班": "Push to the metering team when inspection is due within 15 days",
  "異常出庫檢測": "Abnormal outbound detection",
  "出庫頻率超正常波動 3σ 觸發盤點建議": "Suggest a stocktake when outbound frequency exceeds 3σ of normal",
  "應急缺口預測": "Emergency-gap forecast",
  "結合關聯地點與需求記錄預測應急物資缺口": "Forecast emergency supply gaps from linked sites and demand history",
  "AI 自動草擬單據": "AI drafts documents",
  "AI 可自動草擬補貨/調撥單(高風險仍進覆核)": "AI may draft replenishment/transfer orders (high-risk still reviewed)",
  "AI 托管分庫": "AI-managed sub-warehouses",
  "AI 可根據庫存資料整理倉庫、庫區與庫位關聯": "AI may organise warehouse, zone and location links from inventory data",
  "開啟": "ON",
  "關閉": "OFF",
  "交秘書開啟": "Ask to enable",
  "交秘書關閉": "Ask to disable",
  "交秘書調整": "Ask to adjust",
  "把 AI 監測規則「{name}」打開": "Turn ON the AI monitoring rule \"{name}\"",
  "把 AI 監測規則「{name}」關閉": "Turn OFF the AI monitoring rule \"{name}\"",
  "查一下 AI 監測規則「{name}」現在的狀態,告訴我要不要開": "Check the current state of AI rule \"{name}\" and advise whether to enable it",
  "後端未返回規則配置,以下為規則目錄;可讓秘書逐條查證。": "Backend returned no rule config; catalog shown below — ask the Secretary to verify each.",
  "AI 操作邊界": "AI HARD BOUNDARIES",
  "安全紅線 · 任何人不可越過": "Safety red lines · no one may cross",
  "禁止任意刪庫": "No arbitrary DB deletion",
  "禁止自動大額出庫": "No automatic bulk outbound",
  "禁止私自改權限": "No unauthorised permission edits",
  "禁止繞過審批": "No bypassing approvals",
  "禁止替人簽字": "No signing on someone's behalf",
  /* 03 倉庫與分類 */
  "倉庫與物資分類": "Warehouses & categories",
  "倉庫檔案 · 分類驅動庫存組織與 AI": "Warehouse files · categories drive inventory & AI",
  "新增倉庫": "Add warehouse",
  "新增一個倉庫,幫我登記名稱、編碼和容量使用率": "Add a new warehouse — register its name, code and capacity usage for me",
  "默認庫": "Default",
  "{n} 個功能區": "{n} zones",
  "容量": "Capacity",
  "編輯": "Edit",
  "刪除": "Delete",
  "受保護": "Protected",
  "編輯倉庫「{name}」的資料(名稱/編碼/容量使用率),先展示現在的值": "Edit warehouse \"{name}\" (name / code / capacity usage) — show me the current values first",
  "刪除倉庫「{name}」,先告訴我會影響哪些數據再執行": "Delete warehouse \"{name}\" — tell me what data is affected before executing",
  "暫無倉庫檔案": "No warehouses yet",
  "對秘書說「新增倉庫」即可建立。": "Tell the Secretary \"add a warehouse\" to create one.",
  "新增分類": "Add category",
  "新增一個物資分類,幫我定分類代碼、名稱和是否需歸還": "Add a material category — help me set its code, name and whether it must be returned",
  "需歸還": "Returnable",
  "消耗": "Consumable",
  "編輯分類「{name}」(名稱/是否需歸還/說明),先展示現在的設定": "Edit category \"{name}\" (name / returnable / description) — show current settings first",
  "刪除分類「{name}」,若分類下還有數據先告訴我影響": "Delete category \"{name}\" — if it still holds data, tell me the impact first",
  "暫無分類": "No categories yet",
  "對秘書說「幫我建物資分類」即可開始。": "Tell the Secretary \"create material categories\" to get started.",
  /* 04 角色 */
  "分級授權 · CLI 高危能力一眼可見": "Tiered authorisation · risky CLI powers at a glance",
  "新增角色": "Add role",
  "新增一個權限角色,幫我配置名稱、等級和權限清單": "Create a new permission role — help me set its name, level and permission list",
  "業務權限 {n} 項": "{n} business perms",
  "CLI {n} 項": "{n} CLI",
  "高危 {n} 項": "{n} high-risk",
  "無 CLI 高危能力": "No CLI capabilities",
  "交秘書配置": "Configure",
  "調整角色「{name}」的權限配置,先列出它現在有哪些權限": "Adjust permissions for role \"{name}\" — list its current permissions first",
  "暫無角色": "No roles yet",
  "對秘書說「新增角色」即可配置分級授權。": "Tell the Secretary \"add a role\" to configure tiered authorisation.",
  /* 05 導航 */
  "導航設計": "Navigation design",
  "AI 依行業編排側欄:改名 / 排序 / 隱藏": "AI arranges the nav for your industry: rename / reorder / hide",
  "AI 設計導航": "AI design nav",
  "讓 AI 根據我們公司的行業重新設計側欄導航(改名/排序/隱藏),先給我預覽再保存": "Have the AI redesign our navigation for our industry (rename / reorder / hide) — preview before saving",
  "還原默認": "Reset default",
  "把側欄導航還原成默認配置": "Reset the navigation to its default configuration",
  "{n} 個導航項": "{n} nav items",
  "改名 {n}": "{n} renamed",
  "隱藏 {n}": "{n} hidden",
  "ERP 工作台": "ERP workbench",
  "庫存作業": "Warehouse ops",
  "系統管理": "System admin",
  "物資台賬": "Material ledger",
  "紅塊=已改名 · 劃線=已隱藏 · 「設置」受保護不可隱藏": "Red block = renamed · struck = hidden · Settings is protected",
  "導航目錄未返回": "Nav catalog unavailable",
  "後端未返回導航目錄;可直接讓秘書設計或還原導航。": "Backend returned no nav catalog; ask the Secretary to design or reset it directly.",
  /* 06 提示詞 */
  "提示詞層": "Prompt layers",
  "L0/L1/L2 版本化 · 改動可回滾": "L0/L1/L2 versioned · changes can roll back",
  "作用域": "Scope",
  "層": "Layer",
  "現行版本": "Active ver.",
  "版本數": "Versions",
  "字數": "Chars",
  "更新於": "Updated",
  "更新人": "By",
  "交給秘書": "Via Secretary",
  "調整": "Adjust",
  "回滾": "Rollback",
  "調整提示詞「{scope}」:先給我看現行版本的內容,再說怎麼改": "Adjust prompt \"{scope}\": show me the active version first, then discuss changes",
  "把提示詞「{scope}」回滾到上一個版本,先列出版本歷史": "Roll prompt \"{scope}\" back one version — list the version history first",
  "暫無提示詞版本": "No prompt versions",
  "運行時以資料庫為準;讓秘書初始化或調整提示詞即可。": "Runtime reads from the database; ask the Secretary to initialise or adjust prompts.",
  /* 07 通用與系統 */
  "通用與系統": "General & system",
  "交互開關 · 系統信息 · 數據導出": "Interaction switches · system info · data export",
  "通知與交互": "NOTIFICATIONS & INTERACTION",
  "掃碼槍快速入口": "Scanner quick entry",
  "現場 PDA / 掃碼槍一鍵入出庫": "One-tap in/out with on-site PDA / scanner",
  "預警消息推送": "Alert push",
  "高風險預警即時推送至負責人": "Push high-risk alerts to owners instantly",
  "預警提示音": "Alert sound",
  "深色駕駛艙模式": "Dark cockpit mode",
  "切換為深色科技底色": "Switch to a dark technical theme",
  "把通用設置「{name}」打開": "Turn ON the general setting \"{name}\"",
  "把通用設置「{name}」關閉": "Turn OFF the general setting \"{name}\"",
  "查一下通用設置「{name}」現在的狀態,告訴我要不要開": "Check the general setting \"{name}\" and advise whether to enable it",
  "系統信息": "SYSTEM INFO",
  "後端未返回系統信息。": "Backend returned no system info.",
  "系統版本": "System version",
  "AI 模型": "AI model",
  "接入倉庫": "Warehouses linked",
  "在線人員": "People online",
  "資料庫 CSV 導出": "DATABASE CSV EXPORT",
  "整庫導出 zip:每表一個 CSV + schema + manifest;含敏感表,注意保密。": "Full export as zip: one CSV per table + schema + manifest; includes sensitive tables — handle with care.",
  "交秘書導出": "Export via Secretary",
  "把當前公司資料庫導出成 CSV 壓縮包,告訴我怎麼拿到文件": "Export the current company database as a CSV zip and tell me how to get the file",
  "2.0 約定:頁面只讀,改動經秘書確認執行,全程留痕。": "The 2.0 contract: pages are read-only; changes run through the Secretary with a full audit trail.",
});
const { useState: _s, useEffect: _e, useMemo: _mm } = React;
const { Icon: I, Btn: B, Tag: T, Label: LB, Empty: EM, Kpi, Meter, Folio, Band, pad2, num } = W2;
const ask = (p) => W2.openSecretary(p);

/* 連接狀態歸一化(deepseek / vision / voice 三卡同構,任何字段可缺) */
const connState = (st) => {
  st = st || {};
  const conn = st.connection || {};
  const configured = !!st.configured;
  const connected = configured && !!(st.connected || conn.ok || st.connection_status === "connected");
  const failed = configured && (st.connection_status === "failed" || conn.status === "failed");
  return {
    configured, connected, failed,
    tone: connected ? "ok" : failed ? "bad" : configured ? "warn" : "plain",
    text: connected ? "可連接" : failed ? "連接失敗" : configured ? "待驗證" : "未配置",
    model: connected ? (st.model || conn.model || "—") : "—",
    key: st.masked_key || "—",
    checked: conn.checked_at || st.updated_at || "—",
    error: conn.error || "",
  };
};

const KVLine = ({ k, v }) => (
  <div className="row spread" style={{ borderTop: "1px solid var(--hair-soft)", padding: "7px 0", gap: 10 }}>
    <LB dim style={{ fontSize: 8.5 }}>{k}</LB>
    <span className="num" style={{ fontSize: 12, fontWeight: 650, textAlign: "right", wordBreak: "break-all" }}>{v}</span>
  </div>
);

const ConnPanel = ({ icon, title, sub, st, modelOverride, extra }) => {
  const s = connState(st);
  const name = t(title);
  return (
    <div className="panel col" style={{ padding: 18, gap: 12 }}>
      <div className="row spread">
        <div className="row g8"><I name={icon} size={15}/><span style={{ fontWeight: 700, fontSize: 13.5 }}>{name}</span></div>
        <T tone={s.tone} dot>{t(s.text)}</T>
      </div>
      <div className="muted" style={{ fontSize: 11.5, lineHeight: 1.55, minHeight: 36 }}>{sub}</div>
      <div className="col">
        <KVLine k={t("共用 Key")} v={s.key}/>
        <KVLine k={t("模型")} v={modelOverride ? (s.configured ? modelOverride : "—") : s.model}/>
        <KVLine k={t("最近驗證")} v={s.checked}/>
      </div>
      {s.failed && !!s.error && (
        <div style={{ border: "1px solid var(--red)", padding: "8px 10px", fontSize: 11.5, color: "var(--red)", lineHeight: 1.5 }}>{s.error}</div>
      )}
      {extra}
      <div className="row g6" style={{ marginTop: "auto", paddingTop: 4 }}>
        <B size="sm" icon="swap" onClick={() => ask(t("我要更換{name}的全局 API Key,請告訴我怎麼安全提交,並保存後驗證連通", { name }))}>{t("交秘書換密鑰")}</B>
        <B size="sm" icon="refresh" onClick={() => ask(t("重新驗證{name}的 API 連接,把延遲和結果告訴我", { name }))}>{t("重新驗證")}</B>
      </div>
    </div>
  );
};

const RULE_META = [
  ["low", "低庫存自動預警", "庫存低於安全庫存時自動生成預警並推送倉管"],
  ["expire", "超期未檢提醒", "距檢驗到期 ≤15 天自動推送計量班"],
  ["abnormal", "異常出庫檢測", "出庫頻率超正常波動 3σ 觸發盤點建議"],
  ["repair", "應急缺口預測", "結合關聯地點與需求記錄預測應急物資缺口"],
  ["auto", "AI 自動草擬單據", "AI 可自動草擬補貨/調撥單(高風險仍進覆核)"],
  ["gis", "AI 托管分庫", "AI 可根據庫存資料整理倉庫、庫區與庫位關聯"],
];
const BOUNDARIES = ["禁止任意刪庫", "禁止自動大額出庫", "禁止私自改權限", "禁止繞過審批", "禁止替人簽字"];
const GENERAL_META = [
  ["scan", "掃碼槍快速入口", "現場 PDA / 掃碼槍一鍵入出庫"],
  ["push", "預警消息推送", "高風險預警即時推送至負責人"],
  ["sound", "預警提示音", ""],
  ["dark", "深色駕駛艙模式", "切換為深色科技底色"],
];

/* 開關行(只讀呈現 + 交秘書切換);rules 為 null 時狀態未知 */
const SwitchRow = ({ idx, title, desc, val, known, onPrompt, offPrompt, checkPrompt }) => (
  <div className="ledger-row">
    <span className="lr-idx">{pad2(idx + 1)}</span>
    <div className="col g4" style={{ flex: 1, minWidth: 0 }}>
      <span style={{ fontWeight: 650, fontSize: 13.5 }}>{t(title)}</span>
      {!!desc && <span className="muted" style={{ fontSize: 11.5 }}>{t(desc)}</span>}
    </div>
    {known
      ? (val ? <T tone="ok" dot>{t("開啟")}</T> : <T tone="plain">{t("關閉")}</T>)
      : <span className="num muted" style={{ fontSize: 12 }}>—</span>}
    <B size="sm" onClick={() => ask(known ? (val ? offPrompt : onPrompt) : checkPrompt)}>
      {known ? (val ? t("交秘書關閉") : t("交秘書開啟")) : t("交秘書調整")}
    </B>
  </div>
);

const MiniHead = ({ label, count, right }) => (
  <div className="row spread" style={{ borderTop: "2px solid var(--rule)", padding: "10px 0", marginBottom: 2 }}>
    <div className="row g10"><LB>{label}</LB><span className="num muted" style={{ fontSize: 11 }}>{count}</span></div>
    {right}
  </div>
);

const Page = ({ boot }) => {
  const bt = boot || {};
  const [cfg, setCfg] = _s(null);          // /api/settings
  const [ds, setDs] = _s(null);            // /api/integrations/deepseek
  const [vis, setVis] = _s({});
  const [voi, setVoi] = _s({});
  const [voiceStat, setVoiceStat] = _s({});
  const [health, setHealth] = _s({});
  const [nav, setNav] = _s({});
  const [prompts, setPrompts] = _s({});
  const [permMeta, setPermMeta] = _s({});
  const [rev, setRev] = _s(0);

  _e(() => {
    W2.json("/api/settings").then(d => setCfg(d || {})).catch(() => setCfg({}));
    W2.json("/api/integrations/deepseek").then(d => setDs((d && d.deepseek) || {})).catch(() => setDs({}));
    W2.json("/api/integrations/vision").then(d => setVis((d && d.vision) || {})).catch(() => setVis({}));
    W2.json("/api/integrations/voice").then(d => setVoi((d && d.voice) || {})).catch(() => setVoi({}));
    W2.json("/api/voice/status").then(d => setVoiceStat(d || {})).catch(() => setVoiceStat({}));
    W2.json("/api/ai/health").then(d => setHealth(d || {})).catch(() => setHealth({}));
    W2.json("/api/nav").then(d => setNav(d || {})).catch(() => setNav({}));
    W2.json("/api/prompts").then(d => setPrompts(d || {})).catch(() => setPrompts({}));
    W2.json("/api/permissions").then(d => {
      const m = {};
      ((d && d.permissions) || []).forEach(p => { if (p && p.key) m[p.key] = p; });
      setPermMeta(m);
    }).catch(() => setPermMeta({}));
  }, [rev]);

  const c = cfg || {};
  /* 倉庫:settings 富形狀優先,bootstrap 名單兜底(可能是純字串) */
  const asObj = (x) => (typeof x === "string" ? { name: x } : (x && typeof x === "object" ? x : {}));
  const whs = ((Array.isArray(c.warehouses) && c.warehouses.length)
    ? c.warehouses
    : (Array.isArray(bt.WAREHOUSES) ? bt.WAREHOUSES : [])).map(asObj);
  const cats = ((Array.isArray(c.ledger_categories) && c.ledger_categories.length)
    ? c.ledger_categories
    : (Array.isArray(bt.LEDGER_CATEGORIES) ? bt.LEDGER_CATEGORIES : [])).map(asObj);
  const roles = ((Array.isArray(c.roles) && c.roles.length) ? c.roles : (Array.isArray(bt.ROLES) ? bt.ROLES : []))
    .map(asObj).map(r => ({ id: r.id, name: r.name || r.role_name || "—", level: r.level, perms: Array.isArray(r.perms) ? r.perms : [] }));
  const rules = c.ai_rules && typeof c.ai_rules === "object" ? c.ai_rules : null;
  const gen = c.general && typeof c.general === "object" ? c.general : null;
  const sysInfo = Array.isArray(c.system_info) ? c.system_info : [];
  const dsSt = (ds && Object.keys(ds).length ? ds : c.deepseek) || {};

  const engines = [connState(dsSt), connState(vis), connState(voi)];
  const connectedN = engines.filter(e => e.connected).length;
  const anyFailed = engines.some(e => e.failed);
  const zonesTotal = whs.reduce((s, w) => s + num(w.zones), 0);
  const returnableN = cats.filter(x => x.requires_return || x.requiresReturn).length;

  /* 導航目錄分組 */
  const navGroups = _mm(() => {
    const catalog = (nav && Array.isArray(nav.catalog)) ? nav.catalog : [];
    const items = (nav && nav.config && nav.config.items && typeof nav.config.items === "object") ? nav.config.items : {};
    const order = [], by = {};
    catalog.forEach((it, i) => {
      if (!it || !it.id) return;
      const g = it.group || "—";
      if (!by[g]) { by[g] = []; order.push(g); }
      const ov = items[it.id] || {};
      by[g].push({
        id: it.id,
        label: ov.label || it.default_label || it.id,
        original: it.default_label || it.id,
        renamed: !!ov.label && ov.label !== it.default_label,
        hidden: !!ov.hidden,
        order: ov.order != null ? ov.order : i,
      });
    });
    return order.map(g => ({ group: g, items: by[g].slice().sort((a, b) => a.order - b.order) }));
  }, [nav]);
  const navFlat = navGroups.reduce((a, g) => a.concat(g.items), []);
  const renamedN = navFlat.filter(x => x.renamed).length;
  const hiddenN = navFlat.filter(x => x.hidden).length;

  /* 提示詞:按 scope 聚合(現行版 + 版本數) */
  const promptScopes = _mm(() => {
    const rows = (prompts && Array.isArray(prompts.rows)) ? prompts.rows : [];
    const order = [], map = {};
    rows.forEach(r => {
      if (!r) return;
      const k = r.scope_key || "—";
      if (!map[k]) { map[k] = { scope: k, layer: r.layer || "—", versions: 0, active: null, latest: null }; order.push(k); }
      const e = map[k];
      e.versions += 1;
      if (r.active) e.active = r;
      if (!e.latest || num(r.version) > num(e.latest.version)) e.latest = r;
    });
    return order.map(k => { const e = map[k]; return { ...e, cur: e.active || e.latest || {} }; });
  }, [prompts]);

  const roleStats = (perms) => {
    let cli = 0, danger = 0;
    perms.forEach(k => {
      const p = permMeta[k];
      if (p && p.kind === "cli") { cli += 1; if (p.critical || p.risk === "critical" || p.risk === "high") danger += 1; }
    });
    return { total: perms.length, cli, danger };
  };

  const healthReady = health && (health.status || health.model || health.search_documents != null);
  const healthCells = [
    ["狀態", healthReady ? (health.status || "—") : "—"],
    ["知識文檔", healthReady ? num(health.search_documents) : "—"],
    ["行動計劃", healthReady ? num(health.plans) : "—"],
    ["活躍對話", healthReady ? num(health.conversations) : "—"],
    ["記憶", healthReady ? num(health.memories) : "—"],
    ["經驗", healthReady ? num(health.lessons) : "—"],
  ];

  return (
    <>
      <Folio no="15" en="SETTINGS" title={t("設置")}
        sub={t("系統配置一頁盡覽:AI 密鑰 · 監測規則 · 倉庫 · 分類 · 導航 · 提示詞 · 通用開關;頁面只讀,改動一律交秘書執行留痕。")}
        right={<>
          <B icon="refresh" onClick={() => setRev(r => r + 1)}>{t("刷新")}</B>
          <B kind="primary" icon="sparkle" onClick={() => ask(t("檢查系統設置:AI 密鑰連接、監測規則、倉庫與分類配置,有什麼需要完善的?"))}>{t("問秘書")}</B>
        </>}/>

      <div className="kpi-band">
        <Kpi label={t("在管倉庫")} value={whs.length} unit={t("個")} delay={0}
          foot={<span className="muted" style={{ fontSize: 11.5 }}>{t("共 {n} 個功能區", { n: zonesTotal })}</span>}/>
        <Kpi label={t("物資分類")} value={cats.length} unit={t("類")} delay={.05}
          foot={<span className="muted" style={{ fontSize: 11.5 }}>{t("需歸還 {n} 類", { n: returnableN })}</span>}/>
        <Kpi label={t("權限角色")} value={roles.length} unit={t("類")} delay={.1}
          foot={<span className="muted" style={{ fontSize: 11.5 }}>{t("分級授權 · 高危可見")}</span>}/>
        <Kpi label={t("AI 連接")} value={connectedN} unit={t("/3 已連接")} red={anyFailed} delay={.15}
          foot={connectedN === 3 ? <T tone="ok" dot>{t("全部就緒")}</T> : anyFailed ? <T tone="bad" dot>{t("有連接失敗")}</T> : <T tone="warn" dot>{t("有待配置")}</T>}/>
      </div>

      {/* 01 · AI 引擎與全局密鑰 */}
      <Band no="01" title={t("AI 引擎與全局密鑰")} sub={t("管理員配置一次 · 全員共用 · 密鑰入庫保存")} delay={.05}
        right={<B size="sm" icon="sparkle" onClick={() => ask(t("跑一次 AI 資料庫自檢,把結果彙報給我"))}>{t("自檢")}</B>}>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 16 }}>
          <ConnPanel icon="cpu" title="智能引擎" st={dsSt} modelOverride={t("智能引擎")}
            sub={t("驅動秘書對話與智能分析,全公司共用一把全局 key。")}/>
          <ConnPanel icon="eye" title="圖片識別" st={vis}
            sub={t("圖片識別共用密鑰,後端按接入地址自動識別供應商。")}/>
          <ConnPanel icon="bell" title="語音功能" st={voi}
            sub={t("語音識別與朗讀共用密鑰,未配置時自動降級瀏覽器原生語音。")}
            extra={<div className="row g6">
              <T tone={voiceStat.asr ? "ok" : "plain"} dot>{t("識別")}</T>
              <T tone={voiceStat.tts ? "ok" : "plain"} dot>{t("朗讀")}</T>
            </div>}/>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "110px repeat(6, 1fr)", borderTop: "1px solid var(--hair)", marginTop: 18, paddingTop: 12, gap: 8, alignItems: "center" }}>
          <LB dim>{t("AI 運行時")}</LB>
          {healthCells.map(([k, v]) => (
            <div key={k} className="col g4">
              <LB dim style={{ fontSize: 8.5 }}>{t(k)}</LB>
              <span className="num" style={{ fontSize: 15, fontWeight: 700 }}>{v}</span>
            </div>
          ))}
        </div>
        {!healthReady && <div className="muted" style={{ fontSize: 11, marginTop: 8 }}>{t("運行時暫無數據")}</div>}
      </Band>

      {/* 02 · AI 監測規則與操作邊界 */}
      <Band no="02" title={t("AI 監測規則與操作邊界")} sub={t("規則開關持久化 · 邊界不可逾越")} delay={.1}>
        <div style={{ display: "grid", gridTemplateColumns: "1.7fr 1fr", gap: 28, alignItems: "start" }}>
          <div>
            {!rules && <div className="muted" style={{ fontSize: 11.5, padding: "0 0 10px" }}>{t("後端未返回規則配置,以下為規則目錄;可讓秘書逐條查證。")}</div>}
            <div style={{ borderTop: "2px solid var(--rule)" }}>
              {RULE_META.map(([k, title, desc], i) => (
                <SwitchRow key={k} idx={i} title={title} desc={desc}
                  known={!!rules && rules[k] != null} val={!!(rules && rules[k])}
                  onPrompt={t("把 AI 監測規則「{name}」打開", { name: t(title) })}
                  offPrompt={t("把 AI 監測規則「{name}」關閉", { name: t(title) })}
                  checkPrompt={t("查一下 AI 監測規則「{name}」現在的狀態,告訴我要不要開", { name: t(title) })}/>
              ))}
            </div>
          </div>
          <div className="panel col" style={{ padding: 18, gap: 12 }}>
            <div className="row g8"><I name="shield" size={15} color="var(--red)"/><LB red>{t("AI 操作邊界")}</LB></div>
            <div className="muted" style={{ fontSize: 11.5 }}>{t("安全紅線 · 任何人不可越過")}</div>
            <div className="col g8">
              {BOUNDARIES.map(b => (
                <div key={b} className="row g8" style={{ borderTop: "1px solid var(--hair-soft)", paddingTop: 8 }}>
                  <span style={{ width: 6, height: 6, background: "var(--red)", flexShrink: 0 }}/>
                  <span style={{ fontSize: 12.5, fontWeight: 650 }}>{t(b)}</span>
                </div>
              ))}
            </div>
          </div>
        </div>
      </Band>

      {/* 03 · 倉庫與物資分類 */}
      <Band no="03" title={t("倉庫與物資分類")} sub={t("倉庫檔案 · 分類驅動庫存組織與 AI")} delay={.12}>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 28, alignItems: "start" }}>
          <div>
            <MiniHead label={t("在管倉庫")} count={whs.length}
              right={<B size="sm" icon="plus" onClick={() => ask(t("新增一個倉庫,幫我登記名稱、編碼和容量使用率"))}>{t("新增倉庫")}</B>}/>
            {whs.length ? whs.map((w, i) => (
              <div key={w.id || w.name || i} className="ledger-row">
                <span className="lr-idx">{pad2(i + 1)}</span>
                <div className="col g4" style={{ flex: 1, minWidth: 0 }}>
                  <span className="row g8" style={{ fontWeight: 650, fontSize: 13.5 }}>
                    {w.name || "—"}{(w.main || w.is_default) && <T tone="inv">{t("默認庫")}</T>}
                  </span>
                  <span className="num muted" style={{ fontSize: 11 }}>
                    {(w.code || "—") + " · " + t("{n} 個功能區", { n: num(w.zones) })}
                  </span>
                </div>
                <div className="col g4" style={{ width: 120 }}>
                  <span className="muted num" style={{ fontSize: 10.5 }}>{t("容量")} {w.cap != null ? num(w.cap) + "%" : "—"}</span>
                  <div className="bar"><i style={{ width: Math.min(100, num(w.cap)) + "%", background: num(w.cap) >= 90 ? "var(--red)" : "var(--ink)" }}/></div>
                </div>
                <div className="row g4">
                  <B size="sm" onClick={() => ask(t("編輯倉庫「{name}」的資料(名稱/編碼/容量使用率),先展示現在的值", { name: w.name || "—" }))}>{t("編輯")}</B>
                  {w.can_delete === false
                    ? <T tone="plain">{t("受保護")}</T>
                    : <B size="sm" kind="red" onClick={() => ask(t("刪除倉庫「{name}」,先告訴我會影響哪些數據再執行", { name: w.name || "—" }))}>{t("刪除")}</B>}
                </div>
              </div>
            )) : <EM icon="building" title={t("暫無倉庫檔案")} sub={t("對秘書說「新增倉庫」即可建立。")}/>}
          </div>
          <div>
            <MiniHead label={t("物資分類")} count={cats.length}
              right={<B size="sm" icon="plus" onClick={() => ask(t("新增一個物資分類,幫我定分類代碼、名稱和是否需歸還"))}>{t("新增分類")}</B>}/>
            {cats.length ? cats.map((cItem, i) => (
              <div key={cItem.id || i} className="ledger-row">
                <span className="lr-idx">{pad2(i + 1)}</span>
                <div className="col g4" style={{ flex: 1, minWidth: 0 }}>
                  <span className="row g8" style={{ fontWeight: 650, fontSize: 13.5 }}>
                    {cItem.name || "—"}
                    {(cItem.requires_return || cItem.requiresReturn)
                      ? <T tone="warn">{t("需歸還")}</T>
                      : <T tone="plain">{t("消耗")}</T>}
                  </span>
                  <span className="num muted" style={{ fontSize: 11 }}>{cItem.id || "—"}{cItem.description ? " · " + cItem.description : ""}</span>
                </div>
                <div className="row g4">
                  <B size="sm" onClick={() => ask(t("編輯分類「{name}」(名稱/是否需歸還/說明),先展示現在的設定", { name: cItem.name || "—" }))}>{t("編輯")}</B>
                  <B size="sm" kind="red" onClick={() => ask(t("刪除分類「{name}」,若分類下還有數據先告訴我影響", { name: cItem.name || "—" }))}>{t("刪除")}</B>
                </div>
              </div>
            )) : <EM icon="layers" title={t("暫無分類")} sub={t("對秘書說「幫我建物資分類」即可開始。")}/>}
          </div>
        </div>
      </Band>

      {/* 04 · 權限角色 */}
      <Band no="04" title={t("權限角色")} sub={t("分級授權 · CLI 高危能力一眼可見")} delay={.14}
        right={<B size="sm" icon="plus" onClick={() => ask(t("新增一個權限角色,幫我配置名稱、等級和權限清單"))}>{t("新增角色")}</B>}>
        {roles.length ? (
          <div style={{ borderTop: "2px solid var(--rule)" }}>
            {roles.map((r, i) => {
              const st = roleStats(r.perms);
              return (
                <div key={r.id || i} className="ledger-row">
                  <span className="lr-idx">{pad2(i + 1)}</span>
                  <div className="row g10" style={{ flex: 1, minWidth: 0, flexWrap: "wrap" }}>
                    <span style={{ fontWeight: 650, fontSize: 13.5 }}>{r.name}</span>
                    <T tone="inv">L{num(r.level) || "—"}</T>
                  </div>
                  <div className="row g6 wrap">
                    <T tone="plain">{t("業務權限 {n} 項", { n: Math.max(0, st.total - st.cli) })}</T>
                    {st.cli > 0 ? <T tone="warn">{t("CLI {n} 項", { n: st.cli })}</T> : <T tone="plain">{t("無 CLI 高危能力")}</T>}
                    {st.danger > 0 && <T tone="bad" dot>{t("高危 {n} 項", { n: st.danger })}</T>}
                  </div>
                  <B size="sm" icon="shield" onClick={() => ask(t("調整角色「{name}」的權限配置,先列出它現在有哪些權限", { name: r.name }))}>{t("交秘書配置")}</B>
                </div>
              );
            })}
          </div>
        ) : <EM icon="shield" title={t("暫無角色")} sub={t("對秘書說「新增角色」即可配置分級授權。")}/>}
      </Band>

      {/* 05 · 導航設計 */}
      <Band no="05" title={t("導航設計")} sub={t("AI 依行業編排側欄:改名 / 排序 / 隱藏")} delay={.16}
        right={<>
          <B size="sm" icon="sparkle" onClick={() => ask(t("讓 AI 根據我們公司的行業重新設計側欄導航(改名/排序/隱藏),先給我預覽再保存"))}>{t("AI 設計導航")}</B>
          <B size="sm" icon="refresh" onClick={() => ask(t("把側欄導航還原成默認配置"))}>{t("還原默認")}</B>
        </>}>
        {navFlat.length ? (
          <div className="col g14">
            <div className="row g10 wrap">
              <T tone="plain">{t("{n} 個導航項", { n: navFlat.length })}</T>
              <T tone={renamedN ? "warn" : "plain"}>{t("改名 {n}", { n: renamedN })}</T>
              <T tone={hiddenN ? "warn" : "plain"}>{t("隱藏 {n}", { n: hiddenN })}</T>
            </div>
            {navGroups.map(g => (
              <div key={g.group} className="col g8">
                <LB dim style={{ fontSize: 8.5 }}>{t(String(g.group || "—"))}</LB>
                <div className="row g6 wrap">
                  {g.items.map(it => (
                    <span key={it.id} className="chip" title={it.renamed ? it.original + " → " + it.label : it.id}
                      style={it.hidden ? { opacity: .4, textDecoration: "line-through" } : undefined}>
                      {it.renamed && <span style={{ width: 6, height: 6, background: "var(--red)", flexShrink: 0 }}/>}
                      {it.label}
                    </span>
                  ))}
                </div>
              </div>
            ))}
            <div className="muted" style={{ fontSize: 10.5 }}>{t("紅塊=已改名 · 劃線=已隱藏 · 「設置」受保護不可隱藏")}</div>
          </div>
        ) : (
          <EM icon="layers" title={t("導航目錄未返回")} sub={t("後端未返回導航目錄;可直接讓秘書設計或還原導航。")}
            action={<B size="sm" icon="sparkle" onClick={() => ask(t("讓 AI 根據我們公司的行業重新設計側欄導航(改名/排序/隱藏),先給我預覽再保存"))}>{t("AI 設計導航")}</B>}/>
        )}
      </Band>

      {/* 06 · 提示詞層 */}
      <Band no="06" title={t("提示詞層")} sub={t("L0/L1/L2 版本化 · 改動可回滾")} delay={.18}>
        {promptScopes.length ? (
          <div style={{ overflowX: "auto" }}>
            <table className="tbl2">
              <thead><tr>
                <th>{t("作用域")}</th><th>{t("層")}</th><th>{t("現行版本")}</th><th>{t("版本數")}</th><th>{t("字數")}</th><th>{t("更新於")}</th><th>{t("更新人")}</th><th style={{ width: 150 }}>{t("交給秘書")}</th>
              </tr></thead>
              <tbody>
                {promptScopes.map((p) => (
                  <tr key={p.scope}>
                    <td><span className="num" style={{ fontWeight: 650 }}>{p.scope}</span></td>
                    <td><T tone="plain">{p.cur.layer || p.layer}</T></td>
                    <td><span className="num" style={{ fontWeight: 700 }}>v{num(p.cur.version) || "—"}</span></td>
                    <td className="num muted">{p.versions}</td>
                    <td className="num muted">{p.cur.content_length != null ? num(p.cur.content_length) : "—"}</td>
                    <td className="num muted" style={{ fontSize: 12 }}>{p.cur.updated_at || "—"}</td>
                    <td className="muted" style={{ fontSize: 12 }}>{p.cur.updated_by || "—"}</td>
                    <td>
                      <div className="row g4">
                        <B size="sm" onClick={() => ask(t("調整提示詞「{scope}」:先給我看現行版本的內容,再說怎麼改", { scope: p.scope }))}>{t("調整")}</B>
                        <B size="sm" onClick={() => ask(t("把提示詞「{scope}」回滾到上一個版本,先列出版本歷史", { scope: p.scope }))}>{t("回滾")}</B>
                      </div>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        ) : <EM icon="doc" title={t("暫無提示詞版本")} sub={t("運行時以資料庫為準;讓秘書初始化或調整提示詞即可。")}/>}
      </Band>

      {/* 07 · 通用與系統 */}
      <Band no="07" title={t("通用與系統")} sub={t("交互開關 · 系統信息 · 數據導出")} delay={.2}>
        <div style={{ display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 28, alignItems: "start" }}>
          <div>
            <MiniHead label={t("通知與交互")} count={GENERAL_META.length}/>
            {GENERAL_META.map(([k, title, desc], i) => (
              <SwitchRow key={k} idx={i} title={title} desc={desc}
                known={!!gen && gen[k] != null} val={!!(gen && gen[k])}
                onPrompt={t("把通用設置「{name}」打開", { name: t(title) })}
                offPrompt={t("把通用設置「{name}」關閉", { name: t(title) })}
                checkPrompt={t("查一下通用設置「{name}」現在的狀態,告訴我要不要開", { name: t(title) })}/>
            ))}
          </div>
          <div className="col g16">
            <div>
              <MiniHead label={t("系統信息")} count={sysInfo.length}/>
              {sysInfo.length ? (
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
                  {sysInfo.map((pair, i) => (
                    <div key={i} className="col g4" style={{ borderTop: "1px solid var(--hair)", paddingTop: 8 }}>
                      <LB dim style={{ fontSize: 8.5 }}>{t(String((pair && pair[0]) || "—"))}</LB>
                      <span className="num" style={{ fontSize: 13.5, fontWeight: 650 }}>{(pair && pair[1]) || "—"}</span>
                    </div>
                  ))}
                </div>
              ) : <div className="muted" style={{ fontSize: 11.5 }}>{t("後端未返回系統信息。")}</div>}
            </div>
            <div className="panel col" style={{ padding: 16, gap: 10 }}>
              <LB>{t("資料庫 CSV 導出")}</LB>
              <div className="muted" style={{ fontSize: 11.5, lineHeight: 1.6 }}>{t("整庫導出 zip:每表一個 CSV + schema + manifest;含敏感表,注意保密。")}</div>
              <div><B size="sm" icon="outbound" onClick={() => ask(t("把當前公司資料庫導出成 CSV 壓縮包,告訴我怎麼拿到文件"))}>{t("交秘書導出")}</B></div>
            </div>
            <div className="muted" style={{ fontSize: 10.5, lineHeight: 1.6 }}>{t("2.0 約定:頁面只讀,改動經秘書確認執行,全程留痕。")}</div>
          </div>
        </div>
      </Band>
    </>
  );
};

window.W2.PAGES["settings"] = Page;
})();
