PageSourceSearch

https://xagent.id/assets/dashboard-report-stage1.js?v=tenant-20260920

js xagent.id collected 2026-10-05 06:32:51 UTC 10,658 bytes, 187 lines download raw bytes

1/**
2 * dashboard-report-stage1.js — Dashboard 報告頁 Stage 1 渲染
3 *
4 * 掛入點:`dashboard.html` 在 demo.js 之後載入本檔;demo.js 的 loadDashboard()
5 * 解鎖 `data-dashboard-content` 後,由 MutationObserver 觸發本檔的 init(),
6 * 避免與 dashboard-client.js 的既有 bundle 初始化競合(順序不假設,只觀察)。
7 *
8 * 區塊:
9 * - report-hero      → Hero 總覽卡(總分 + Δ 退化 + freshness + agentic browsing)
10 *                      + AI 名片右欄(合併進既有 identity-panel 位置)
11 * - report-cc-band   → Common Crawl 語料存在帶
12 *
13 * 合規政策(不可移除):
14 * 1. pending_not_zero:資料缺失一律 pending chip,不顯示 0 或空白數字。
15 * 2. Δ 退化展示:comparison.comparable 為 false 時不假裝有變化趨勢,
16 *    顯示 reason 的白話解釋(同 docs/dashboard-frontend-architecture rev.2 §9.3)。
17 * 3. claim limits:本檔不新增任何 KPI 話術;所有文案只讀 payload 自有欄位。
18 */
19(() => {
20  "use strict";
21
22  /* Language follows document.body.dataset.lang at call time; demo.js rewrites
23   * that attribute from the visitor's preference, so a top-level snapshot would
24   * go stale. Copy resolves through the shared eight-locale catalog, keyed by
25   * the English string; the Chinese argument is the fallback when the catalog
26   * has no entry. A missing English argument means the string is Chinese-only. */
27  const currentLang = () => document.body.dataset.lang || document.documentElement.lang || "en";
28  const t = (c, e) => {
29    const catalog = window.XAgentI18nCatalog;
30    const source = e !== undefined ? e : c;
31    const key = catalog?.aliases?.[source] || source;
32    const translated = catalog?.messages?.[key]?.[currentLang()];
33    if (translated) return translated;
34    return currentLang() === "zh-Hant" ? c : source;
35  };
36
37  /*  helpers  ------------------------------------------------------------- */
38
39  const el = (sel, root) => (root || document).querySelector(sel);
40  const pendingChip = (c, e) => `<span class="pending-chip">${t(c, e)}</span>`;
41  const escapeText = (v) => String(v ?? "").replace(/[&<>"']/g, (m) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[m]));
42
43  const read = (obj, ...path) => path.reduce((acc, k) => (acc && typeof acc === "object" ? acc[k] : undefined), obj);
44
45  /*  區塊 1+2:Hero 總覽卡 -------------------------------------------------- */
46
47  const renderHero = (p) => {
48    const overall = read(p, "readiness", "overall");
49    const comp = read(p, "comparison") || {};
50    const fresh = read(p, "readiness", "freshness") || {};
51    const agentic = read(p, "readiness", "agentic_browsing");
52    const id = read(p, "public_state", "identity") || {};
53    const cust = read(p, "customer") || {};
54    const inst = read(p, "installation") || {};
55    const ageHours = fresh.age_seconds != null ? Math.round(read(fresh, "age_seconds") / 3600) : null;
56
57    /* Δ 退化展示:comparable 為 false 時說明原因,不渲染假趨勢 */
58    let deltaHtml;
59    if (comp.comparable && typeof comp.overall_delta === "number") {
60      const up = comp.overall_delta > 0;
61      deltaHtml = `<span class="hero-delta ${up ? "up" : "down"}">
62        <span aria-hidden="true">${up ? "▲" : "▼"}</span>${Math.abs(comp.overall_delta).toFixed(1)}
63        <span class="delta-note">${t("vs 上次可比量測", "vs last comparable audit")}</span></span>`;
64    } else {
65      const reason = {
66        baseline_has_no_capture_time: t("首次可比量測後即自動建立基線", "Auto baseline after first comparable audit"),
67        comparable_baseline_pending: t("等待可比基線建立", "Comparable baseline pending"),
68        same_deterministic_public_audit_contract: t("使用同一公開巡檢合約", "Same deterministic public audit contract"),
69      }[comp.reason] || t("暫無可比基線", "No comparable baseline yet");
70      deltaHtml = `<span class="hero-delta pending">—<span class="delta-note">${escapeText(reason)}</span></span>`;
71    }
72
73    const freshPill = fresh.status === "fresh"
74      ? `<span class="freshness-pill fresh">${t(`${ageHours != null ? `${ageHours}h 內` : "已更新"} · 資料新鮮`, `${ageHours != null ? `within ${ageHours}h` : "updated"} · data fresh`)}</span>`
75      : `<span class="freshness-pill">${t("資料新鮮度未確認", "Freshness unverified")}</span>`;
76
77    const agenticHtml = agentic && typeof read(agentic, "score") === "number"
78      ? `<p class="hero-note">${t("Agentic Browsing", "Agentic Browsing")} <b>${read(agentic, "score")}</b> — ${t("Lighthouse 公開信號,非 Google 官方數據", "Public Lighthouse signal, not official Google data")}</p>`
79      : "";
80
81    const verified = id.verified === true || id.status === "verified";
82    return `
83    <section class="report-hero" data-report-hero>
84      <article class="panel hero-card">
85        <div class="hero-top">
86          <div>
87            <p class="hero-score-label"><span class="lang-zh">AI 準備度總分</span><span class="lang-en">AI readiness score</span></p>
88            <div class="hero-score-wrap"><span class="hero-score">${overall != null ? escapeText(overall) : "—"}</span>
89            ${deltaHtml}</div>
90          </div>
91        </div>
92        <p class="hero-meta-row">${freshPill}
93          ${comp.comparable ? `<span>${t("趨勢可比", "Trend comparable")}</span>` : ""}</p>
94        ${agenticHtml}
95        <p class="hero-note">${t("總分僅描述公開頁面的可讀取性;分數提升不代表 AI 排名、引用或成交提升。", "The score only describes public-page readability; it never proves ranking, citation, or revenue lift.")}</p>
96      </article>
97      <aside class="panel identity-panel">
98        <p class="label">AgentX portable alias</p>
99        <h2 style="margin:12px 0 18px;font-size:22px"><span class="lang-zh">AI 名片狀態</span><span class="lang-en">Agent card status</span></h2>
100        <div class="identity-row"><span>Agent ID</span><b>${escapeText(cust.agent_id || "—")}</b></div>
101        <div class="identity-row"><span>Status</span><b>${verified ? t("已驗證", "Verified") : escapeText(read(cust, "verification_status") || id.status || "pending")}</b></div>
102        <div class="identity-row"><span>Public proof</span><a href="${escapeText(read(cust, "public_identity_url") || "/.well-known/agent-id.json")}" target="_blank" rel="noopener">/.well-known/agent-id.json ↗</a></div>
103        <div class="identity-row"><span>${t("安裝", "Installation")}</span><b>${escapeText(read(inst, "package") || "—")}${inst.version ? ` @${escapeText(inst.version)}` : ""}</b></div>
104        <div class="identity-row"><span>Authority</span><b>local_policy_only</b></div>
105      </aside>
106    </section>`;
107  };
108
109  /*  區塊 3:Common Crawl 存在帶 ------------------------------------------- */
110
111  const renderCommonCrawl = (cc) => {
112    if (!cc || cc.error) {
113      return `<section class="cc-band" data-report-cc data-report-block="common-crawl">
114        <p class="report-pending-block">${t("Common Crawl 語料查詢目前不可用", "Common Crawl presence query unavailable")} — ${pendingChip(t("維持待取得,不視為無收錄", "Treated as pending, never as absent"))}</p></section>`;
115    }
116    const statusZh = cc.status === "present" ? t("已收錄於 AI 訓練語料", "Present in AI training corpora")
117      : cc.status === "absent" ? t("尚未觀測到收錄", "No observed captures") : "";
118    const extras = [];
119    if (typeof cc.captures === "number") extras.push(`${cc.captures} ${t("次收錄", "captures")}`);
120    if (typeof cc.http_200_pages === "number") extras.push(`${cc.http_200_pages} ${t("頁正常回傳", "HTTP 200 pages")}`);
121
122    return `
123    <section class="cc-band" data-report-cc data-report-block="common-crawl">
124      <div class="cc-head">
125        <div>
126          <h2><span class="lang-zh">AI 訓練語料裡有你嗎</span><span class="lang-en">Common Crawl presence</span></h2>
127          <p class="cc-answer"><span class="cc-status ${cc.status}">${escapeText(statusZh)}</span>
128            ${extras.map(escapeText).join(" · ")}</p>
129        </div>
130        ${cc.crawl_id ? `<p class="cc-meta">crawl: ${escapeText(cc.crawl_id)}</p>` : ""}
131      </div>
132      <details class="cc-verify">
133        <summary><span class="lang-zh">自己驗證 → 複製即跑</span><span class="lang-en">Verify yourself — copy &amp; run</span></summary>
134        <pre><code>${escapeText(cc.verify_command || "")}</code></pre>
135        <p class="cc-meaning">${escapeText(read(cc, "meaning") || "")}</p>
136      </details>
137      <p class="cc-nonclaims">${(read(cc, "non_claims") || []).map((c) => pendingChip(c)).join(" ")}</p>
138    </section>`;
139  };
140
141  /*  掛載 ------------------------------------------------------------------ */
142
143  const mountSections = (p, cc) => {
144    const root = el("[data-dashboard-content]");
145    if (!root) return false;
146
147    /* Hero 插入 dash-grid 前(全寬敘事起點) */
148    const hero = el(".dash-grid", root);
149    if (hero) {
150      const wrap = document.createElement("div");
151      wrap.innerHTML = renderHero(p);
152      hero.before(wrap.firstElementChild);
153    }
154
155    /* Common Crawl 帶插 dash-grid 後、billing 前 */
156    const ccEl = document.createElement("div");
157    ccEl.innerHTML = renderCommonCrawl(cc);
158    const billing = el("[data-billing-panel]", root);
159    if (billing) billing.before(ccEl.firstElementChild);
160    else root.append(ccEl.firstElementChild);
161    return true;
162  };
163
164  let generation = 0;
165  const init = async () => {
166    const epoch = ++generation;
167    const root = el("[data-dashboard-content]");
168    if (!root) return;
169    root.querySelectorAll("[data-report-hero], [data-report-cc]").forEach((node) => node.remove());
170    const context = window.AgentIdReportApi?.current();
171    if (!context) return;
172    mountSections(context.data, context.data.common_crawl || {error: "not_observed"});
173    if (context.data.common_crawl) return;
174    const cc = await window.AgentIdReportApi.fetchCommonCrawl(`https://${context.host}`);
175    if (epoch !== generation || window.AgentIdReportApi.current() !== context) return;
176    root.querySelectorAll("[data-report-hero], [data-report-cc]").forEach((node) => node.remove());
177    mountSections(context.data, cc.ok ? cc.data : {error: cc.error});
178  };
179
180  /* 觀察資料區塊解鎖 → 觸發 init(一次) */
181  window.addEventListener("dashboard-report-change", init);
182
183  window.AgentIdReportStage1 = Object.freeze({ init, renderHero, renderCommonCrawl });
184
185  /* 頁面已解鎖的直接觸發(script defer 可能晚於解鎖) */
186  init();
187})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.