"""
Memory Inspector Dashboard — HTML/CSS/JS for the Inspector Web UI.
OpenMemo Memory Inspector V3 — Aggregator-driven Impact Dashboard.

PRD V3 core principle: Inspector shows memory RESULTS, not raw memory data.
Data source: ONLY Aggregator API endpoints (/inspector/*).

4-module layout:
  [1] Impact Metrics   — GET /inspector/metrics
  [2] Task Trace       — GET /inspector/task-trace?task_id=xxx
  [3] Memory Timeline  — GET /inspector/timeline?task_id=xxx
  [4] Memory Details   — GET /inspector/memory-profile?memory_id=xxx
  [+] Impact Summary   — derived from task trace
  [+] Top Memories     — GET /inspector/top-memories
"""

INSPECTOR_HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenMemo Memory Inspector</title>
<style>
:root {
  --bg: #06090f;
  --bg-card: #0c1120;
  --bg-hover: #151d2e;
  --bg-input: #0a0f1a;
  --border: #1a2236;
  --border-hover: #2a3654;
  --text: #e8edf5;
  --text-s: #94a3b8;
  --text-m: #64748b;
  --accent: #6366f1;
  --accent-l: #818cf8;
  --green: #22c55e;
  --green-bg: rgba(34,197,94,0.10);
  --amber: #f59e0b;
  --amber-bg: rgba(245,158,11,0.10);
  --red: #ef4444;
  --red-bg: rgba(239,68,68,0.10);
  --blue: #3b82f6;
  --blue-bg: rgba(59,130,246,0.10);
  --purple: #a78bfa;
  --purple-bg: rgba(167,139,250,0.10);
  --cyan: #22d3ee;
  --cyan-bg: rgba(34,211,238,0.10);
  --r: 14px;
  --rs: 8px;
}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg);color:var(--text);line-height:1.5;-webkit-font-smoothing:antialiased}

/* ── Header ── */
.header{background:linear-gradient(135deg,#0c1120 0%,#0f172a 100%);border-bottom:1px solid var(--border);padding:16px 28px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:100;backdrop-filter:blur(16px)}
.header-l{display:flex;align-items:center;gap:12px}
.logo{width:32px;height:32px;border-radius:8px;background:linear-gradient(135deg,var(--accent),#8b5cf6);display:flex;align-items:center;justify-content:center;font-size:15px;color:#fff;font-weight:700}
.header h1{font-size:16px;font-weight:700;letter-spacing:-0.3px}
.header-r{display:flex;align-items:center;gap:14px}
.ver{font-size:11px;color:var(--text-m);font-weight:500}
.live{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--text-m);transition:color .3s}
.dot-live{width:6px;height:6px;border-radius:50%;background:var(--green);animation:pulse 2s infinite;transition:background .3s}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
.lang-btn{padding:4px 10px;border-radius:6px;font-size:11px;font-weight:600;background:rgba(255,255,255,.04);border:1px solid var(--border);color:var(--text-s);cursor:pointer;transition:all .15s;letter-spacing:.3px}
.lang-btn:hover{background:rgba(99,102,241,.12);border-color:var(--accent);color:var(--accent-l)}
.conn-lost .dot-live{background:var(--red)!important;animation:none!important}
.conn-lost .live{color:var(--red)!important}
.conn-banner{display:none;background:var(--red-bg);border:1px solid rgba(239,68,68,.25);color:var(--red);padding:10px 28px;font-size:12px;font-weight:600;text-align:center;letter-spacing:.3px}
.conn-lost .conn-banner{display:block}

/* ── Layout ── */
.layer-label{font-size:11px;font-weight:800;letter-spacing:2px;text-transform:uppercase;color:var(--text-s);padding:24px 28px 10px;background:linear-gradient(90deg,rgba(99,102,241,.08),transparent);border-left:3px solid var(--accent);margin:0 0 4px;border-radius:0 6px 6px 0}
.page{max-width:1360px;margin:0 auto;padding:0 28px 60px}
.g{display:grid;gap:12px;margin-bottom:4px}
.g2{grid-template-columns:1fr 1fr}
.g3{grid-template-columns:1fr 1fr 1fr}
.g-60-40{grid-template-columns:3fr 2fr}
.gf{grid-column:1/-1}
.c{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r);padding:20px;transition:border-color .2s}
.c:hover{border-color:var(--border-hover)}
.ct{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text-m);margin-bottom:14px;display:flex;align-items:center;gap:7px}
.ct .d{width:5px;height:5px;border-radius:50%}

/* ── Hero Metrics ── */
.hero{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:4px}
.hero-c{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r);padding:18px 20px;position:relative;overflow:hidden;transition:all .2s}
.hero-c:hover{border-color:var(--border-hover);transform:translateY(-1px)}
.hero-l{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:var(--text-m);margin-bottom:6px}
.hero-v{font-size:28px;font-weight:800;letter-spacing:-1px;line-height:1.1}
.hero-s{font-size:11px;color:var(--text-m);margin-top:3px}
.hero-i{position:absolute;top:14px;right:16px;width:34px;height:34px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:15px}

/* ── Task search bar ── */
.task-bar{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r);padding:16px 20px;margin-bottom:4px;display:flex;align-items:center;gap:10px}
.task-bar input{flex:1;background:var(--bg-input);border:1px solid var(--border);border-radius:var(--rs);padding:9px 14px;font-size:13px;color:var(--text);outline:none;transition:border-color .15s;font-family:inherit}
.task-bar input:focus{border-color:var(--accent)}
.task-bar input::placeholder{color:var(--text-m)}
.btn{padding:9px 18px;border-radius:var(--rs);font-size:12px;font-weight:700;border:none;cursor:pointer;transition:all .15s;letter-spacing:.3px}
.btn-primary{background:var(--accent);color:#fff}
.btn-primary:hover{background:var(--accent-l)}
.btn-secondary{background:rgba(255,255,255,.04);border:1px solid var(--border);color:var(--text-s)}
.btn-secondary:hover{border-color:var(--border-hover);color:var(--text)}
.task-bar-label{font-size:11px;color:var(--text-m);white-space:nowrap;font-weight:600}

/* ── Task Trace ── */
.trace-section{display:flex;flex-direction:column;gap:12px}
.trace-group{background:rgba(255,255,255,.015);border-radius:var(--rs);padding:12px 14px}
.trace-group-title{font-size:10px;font-weight:800;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:8px;display:flex;align-items:center;gap:6px}
.trace-group-title.recalled{color:var(--blue)}
.trace-group-title.injected{color:var(--cyan)}
.trace-group-title.adopted{color:var(--green)}
.trace-group-title.workflow{color:var(--amber)}
.trace-group-title.result{color:var(--purple)}
.trace-item{display:flex;align-items:flex-start;gap:8px;padding:6px 0;border-bottom:1px solid var(--border);cursor:pointer;transition:background .15s;border-radius:4px;padding:6px 8px;margin:0 -8px}
.trace-item:last-child{border-bottom:none}
.trace-item:hover{background:rgba(255,255,255,.04)}
.trace-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;margin-top:5px}
.trace-dot.recalled{background:var(--blue)}
.trace-dot.injected{background:var(--cyan)}
.trace-dot.adopted{background:var(--green)}
.trace-dot.workflow{background:var(--amber)}
.trace-text{font-size:12px;color:var(--text-s);flex:1;line-height:1.4}
.trace-type{font-size:10px;font-weight:600;padding:2px 7px;border-radius:10px;background:rgba(255,255,255,.06);color:var(--text-m);flex-shrink:0}
.trace-check{font-size:14px;flex-shrink:0;color:var(--green)}
.result-box{background:rgba(34,197,94,.06);border:1px solid rgba(34,197,94,.2);border-radius:var(--rs);padding:10px 14px}
.result-box.fail{background:var(--red-bg);border-color:rgba(239,68,68,.2)}
.result-label{font-size:11px;font-weight:700;display:flex;align-items:center;gap:6px;margin-bottom:4px}
.result-label.success{color:var(--green)}
.result-label.fail{color:var(--red)}
.result-summary{font-size:12px;color:var(--text-s)}
.badge{display:inline-flex;align-items:center;font-size:10px;font-weight:700;padding:2px 8px;border-radius:10px;letter-spacing:.3px}
.badge.success{background:var(--green-bg);color:var(--green)}
.badge.fail{background:var(--red-bg);color:var(--red)}
.badge.workflow{background:var(--amber-bg);color:var(--amber)}

/* ── Timeline ── */
.tl{display:flex;flex-direction:column;gap:0}
.tl-i{display:flex;align-items:flex-start;gap:10px;padding:9px 4px;border-bottom:1px solid var(--border);position:relative}
.tl-i:last-child{border-bottom:none}
.tl-icon{width:26px;height:26px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0}
.tl-icon.stored{background:var(--purple-bg);color:var(--purple)}
.tl-icon.recalled{background:var(--blue-bg);color:var(--blue)}
.tl-icon.injected{background:var(--cyan-bg);color:var(--cyan)}
.tl-icon.adopted{background:var(--green-bg);color:var(--green)}
.tl-icon.succeeded{background:var(--green-bg);color:var(--green)}
.tl-icon.workflow_recommended{background:var(--amber-bg);color:var(--amber)}
.tl-icon.workflow_adopted{background:var(--amber-bg);color:var(--amber)}
.tl-icon.workflow_completed{background:var(--amber-bg);color:var(--amber)}
.tl-body{flex:1;min-width:0}
.tl-ts{font-size:10px;color:var(--text-m);font-weight:600;white-space:nowrap}
.tl-summary{font-size:12px;color:var(--text-s);margin-top:2px;line-height:1.4}
.tl-event-tag{display:inline-block;font-size:9px;font-weight:700;padding:1px 6px;border-radius:8px;background:rgba(255,255,255,.06);color:var(--text-m);letter-spacing:.5px;text-transform:uppercase;margin-bottom:2px}

/* ── Memory Details ── */
.mem-details{display:none}
.mem-details.show{display:block}
.mem-profile{display:flex;flex-direction:column;gap:10px}
.mem-header{display:flex;align-items:center;gap:10px;margin-bottom:4px}
.mem-type-badge{padding:4px 12px;border-radius:12px;font-size:11px;font-weight:700;background:var(--blue-bg);color:var(--blue)}
.mem-status{padding:4px 12px;border-radius:12px;font-size:11px;font-weight:700}
.mem-status.promoted{background:var(--green-bg);color:var(--green)}
.mem-status.suppressed{background:var(--red-bg);color:var(--red)}
.mem-status.new{background:var(--blue-bg);color:var(--blue)}
.mem-status.normal{background:rgba(255,255,255,.06);color:var(--text-m)}
.mem-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.mem-stat{background:rgba(255,255,255,.02);border:1px solid var(--border);border-radius:var(--rs);padding:10px 12px}
.mem-stat-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text-m);margin-bottom:3px}
.mem-stat-value{font-size:18px;font-weight:800;letter-spacing:-0.5px}
.mem-stat-value.good{color:var(--green)}
.mem-stat-value.mid{color:var(--amber)}
.mem-stat-value.low{color:var(--red)}
.mem-quality{display:flex;align-items:center;gap:8px;margin-top:4px}
.quality-bar-wrap{flex:1;height:6px;background:rgba(255,255,255,.08);border-radius:3px;overflow:hidden}
.quality-bar{height:100%;border-radius:3px;transition:width .4s}
.quality-bar.good{background:var(--green)}
.quality-bar.mid{background:var(--amber)}
.quality-bar.low{background:var(--red)}
.mem-last-used{font-size:11px;color:var(--text-m);margin-top:6px}

/* ── Impact Summary ── */
.impact-sum{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
.impact-item{background:rgba(255,255,255,.015);border:1px solid var(--border);border-radius:var(--rs);padding:12px 14px;display:flex;align-items:flex-start;gap:10px}
.impact-icon{font-size:18px;flex-shrink:0}
.impact-label{font-size:12px;color:var(--text-s)}
.impact-val{font-size:15px;font-weight:700;color:var(--text);margin-top:1px}

/* ── Top Memories ── */
.top-mem-list{display:flex;flex-direction:column;gap:6px}
.top-mem-i{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--rs);background:rgba(255,255,255,.015);cursor:pointer;transition:background .15s}
.top-mem-i:hover{background:rgba(99,102,241,.08)}
.top-mem-rank{font-size:12px;font-weight:800;color:var(--text-m);width:20px;text-align:center}
.top-mem-type{font-size:10px;font-weight:700;padding:2px 8px;border-radius:10px;background:var(--blue-bg);color:var(--blue);white-space:nowrap}
.top-mem-summary{font-size:12px;color:var(--text-s);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.top-mem-score{font-size:11px;font-weight:700;color:var(--amber)}
.top-mem-rate{font-size:11px;color:var(--text-m)}

/* ── Utility ── */
.empty{padding:24px;text-align:center;color:var(--text-m);font-size:12px}
.loading{padding:20px;text-align:center;color:var(--text-m);font-size:12px;animation:fade .6s infinite alternate}
@keyframes fade{0%{opacity:.4}100%{opacity:1}}
.no-task{padding:40px 20px;text-align:center;color:var(--text-m);font-size:13px;display:flex;flex-direction:column;align-items:center;gap:10px}
.no-task-icon{font-size:32px;opacity:.4}
.section-empty{font-size:12px;color:var(--text-m);padding:12px 0;text-align:center}
.count-pill{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;font-size:9px;font-weight:800;background:rgba(255,255,255,.1);color:var(--text-m)}
.count-pill.has{background:rgba(99,102,241,.2);color:var(--accent-l)}
.divider{height:1px;background:var(--border);margin:4px 0}
</style>
</head>
<body>
<div class="header">
  <div class="header-l">
    <div class="logo">&#x1F9E0;</div>
    <h1>OpenMemo Memory Inspector <span id="header-version" style="color:var(--text-m);font-weight:500;font-size:13px"></span></h1>
  </div>
  <div class="header-r">
    <span class="ver">V3 · Aggregator-Driven</span>
    <div class="live"><div class="dot-live"></div><span id="conn-text">Live</span></div>
    <button class="lang-btn" id="lang-btn" onclick="toggleLang()">EN / \u4e2d</button>
  </div>
</div>
<div class="conn-banner" id="conn-banner">\u26a0 API Disconnected \u2014 Memory Bypass Active</div>

<div class="page">

<!-- MODULE 1: IMPACT METRICS -->
<div class="layer-label" id="lbl-metrics" data-i18n="lbl_metrics">IMPACT METRICS</div>
<div class="hero" id="hero-stats">
  <div class="hero-c"><div class="hero-l" data-i18n="m_stored">Memories Stored</div><div class="hero-v" id="v-stored">-</div><div class="hero-s" data-i18n="m_stored_s">total in system</div><div class="hero-i" style="background:var(--blue-bg)">&#x1F4E6;</div></div>
  <div class="hero-c"><div class="hero-l" data-i18n="m_recalled">Recalled</div><div class="hero-v" id="v-recalled" style="color:var(--cyan)">-</div><div class="hero-s" data-i18n="m_recalled_s">across all tasks</div><div class="hero-i" style="background:var(--cyan-bg)">&#x1F50D;</div></div>
  <div class="hero-c"><div class="hero-l" data-i18n="m_reused">Workflows Reused</div><div class="hero-v" id="v-reused" style="color:var(--amber)">-</div><div class="hero-s" data-i18n="m_reused_s">playbooks activated</div><div class="hero-i" style="background:var(--amber-bg)">&#x26A1;</div></div>
  <div class="hero-c"><div class="hero-l" data-i18n="m_avoided">Tasks Avoided</div><div class="hero-v" id="v-avoided" style="color:var(--green)">-</div><div class="hero-s" data-i18n="m_avoided_s">repeated work skipped</div><div class="hero-i" style="background:var(--green-bg)">&#x2705;</div></div>
  <div class="hero-c"><div class="hero-l" data-i18n="m_tokens">Tokens Saved</div><div class="hero-v" id="v-tokens" style="color:var(--purple)">-</div><div class="hero-s" id="v-tokens-s" data-i18n="m_tokens_s">~est. by reuse</div><div class="hero-i" style="background:var(--purple-bg)">&#x1F4B0;</div></div>
</div>

<!-- TASK SEARCH BAR -->
<div class="task-bar" style="margin-top:8px">
  <span class="task-bar-label" data-i18n="task_label">Task ID</span>
  <input type="text" id="task-input" placeholder="task_001" data-i18n-ph="task_ph" />
  <button class="btn btn-primary" onclick="doLoadTask()" data-i18n="load_btn">Load</button>
  <button class="btn btn-secondary" onclick="clearTask()" data-i18n="clear_btn">Clear</button>
</div>

<!-- MODULES 2 + 3: TASK TRACE + TIMELINE -->
<div class="layer-label" id="lbl-trace" data-i18n="lbl_trace">TASK INSPECTOR</div>
<div class="g g-60-40">
  <!-- Task Trace -->
  <div class="c">
    <div class="ct"><div class="d" style="background:var(--accent)"></div><span data-i18n="trace_title">Current Task Trace</span></div>
    <div id="trace-body">
      <div class="no-task">
        <div class="no-task-icon">&#x1F4CB;</div>
        <span data-i18n="no_task">Enter a Task ID above to inspect its memory chain</span>
      </div>
    </div>
  </div>

  <!-- Memory Timeline -->
  <div class="c">
    <div class="ct"><div class="d" style="background:var(--cyan)"></div><span data-i18n="timeline_title">Memory Timeline</span></div>
    <div id="timeline-body">
      <div class="no-task">
        <div class="no-task-icon">&#x23F1;</div>
        <span data-i18n="no_task_tl">Load a task to see its event stream</span>
      </div>
    </div>
  </div>
</div>

<!-- IMPACT SUMMARY (shown after task load) -->
<div id="impact-sum-wrap" style="display:none;margin-bottom:4px">
  <div class="layer-label" data-i18n="lbl_impact">IMPACT SUMMARY</div>
  <div class="c">
    <div class="ct"><div class="d" style="background:var(--green)"></div><span data-i18n="impact_title">Task Impact</span></div>
    <div class="impact-sum" id="impact-sum-body"></div>
  </div>
</div>

<!-- MODULE 4: MEMORY DETAILS (shown on click) -->
<div id="mem-details-wrap" class="mem-details" style="margin-bottom:4px">
  <div class="layer-label" data-i18n="lbl_mem_detail">MEMORY DETAILS</div>
  <div class="c">
    <div class="ct"><div class="d" style="background:var(--blue)"></div><span data-i18n="mem_detail_title">Memory Profile</span><span style="margin-left:auto;cursor:pointer;color:var(--text-m);font-size:14px" onclick="closeMemDetail()">&#x2715;</span></div>
    <div id="mem-detail-body" class="mem-profile"></div>
  </div>
</div>

<!-- TOP MEMORIES -->
<div class="layer-label" data-i18n="lbl_top">TOP MEMORIES</div>
<div class="c">
  <div class="ct"><div class="d" style="background:var(--amber)"></div><span data-i18n="top_title">Most Impactful Memories</span></div>
  <div id="top-mem-body"><div class="loading" data-i18n="loading">Loading...</div></div>
</div>

</div><!-- /page -->

<script>
var LANG=localStorage.getItem('inspector_lang')||'en';
var apiConnected=false;
var currentTaskId='';

var I18N={
  en:{
    lbl_metrics:'IMPACT METRICS',lbl_trace:'TASK INSPECTOR',lbl_impact:'IMPACT SUMMARY',
    lbl_mem_detail:'MEMORY DETAILS',lbl_top:'TOP MEMORIES',
    m_stored:'Memories Stored',m_stored_s:'total in system',
    m_recalled:'Recalled',m_recalled_s:'across all tasks',
    m_reused:'Workflows Reused',m_reused_s:'playbooks activated',
    m_avoided:'Tasks Avoided',m_avoided_s:'repeated work skipped',
    m_tokens:'Tokens Saved',m_tokens_s:'est. by reuse',
    task_label:'Task ID',task_ph:'task_001',load_btn:'Load',clear_btn:'Clear',
    trace_title:'Current Task Trace',timeline_title:'Memory Timeline',
    impact_title:'Task Impact',mem_detail_title:'Memory Profile',top_title:'Most Impactful Memories',
    no_task:'Enter a Task ID above to inspect its memory chain',
    no_task_tl:'Load a task to see its event stream',
    recalled:'Recalled',injected:'Injected',adopted:'Adopted',
    workflow:'Workflow Used',result:'Result',
    success_lbl:'\\u2713 Task Succeeded',fail_lbl:'\\u2717 Task Failed',
    no_workflow:'No workflow used',no_result:'No result recorded',
    impact_workflows:'Reused {n} workflow(s)',impact_adopted:'Adopted {n} memor{y}',
    impact_tokens:'Saved ~{n} tokens',impact_task_ok:'Task succeeded',impact_task_fail:'Task failed',
    used:'Used',adoption:'Adoption Rate',success:'Success Rate',quality:'Quality',last_used:'Last Used',
    status_promoted:'\\u2191 Promoted',status_suppressed:'\\u2193 Suppressed',
    status_new:'\\u2605 New',status_normal:'\\u25CB Normal',
    q_high:'High',q_mid:'Medium',q_low:'Low',
    top_used:'used',top_adoption:'adoption',
    loading:'Loading...',empty_top:'No top memories yet',
    conn_ok:'Live',conn_lost:'Disconnected',
    bypass:'\\u26a0 API Disconnected \\u2014 Memory Bypass Active',
  },
  zh:{
    lbl_metrics:'\\u5f71\\u54cd\\u6307\\u6807',lbl_trace:'\\u4efb\\u52a1\\u68c0\\u67e5',
    lbl_impact:'\\u5f71\\u54cd\\u6982\\u8981',lbl_mem_detail:'\\u8bb0\\u5fc6\\u8be6\\u60c5',
    lbl_top:'\\u9876\\u90e8\\u8bb0\\u5fc6',
    m_stored:'\\u5df2\\u5b58\\u50a8\\u8bb0\\u5fc6',m_stored_s:'\\u7cfb\\u7edf\\u603b\\u91cf',
    m_recalled:'\\u5df2\\u53ec\\u56de',m_recalled_s:'\\u6240\\u6709\\u4efb\\u52a1\\u7d2f\\u8ba1',
    m_reused:'\\u5de5\\u4f5c\\u6d41\\u590d\\u7528',m_reused_s:'\\u89e6\\u53d1\\u7684\\u64ad\\u653e\\u6e05\\u5355',
    m_avoided:'\\u907f\\u514d\\u91cd\\u590d',m_avoided_s:'\\u8df3\\u8fc7\\u7684\\u91cd\\u590d\\u5de5\\u4f5c',
    m_tokens:'\\u8282\\u7701 Token',m_tokens_s:'\\u5df2\\u590d\\u7528\\u4f30\\u7b97',
    task_label:'\\u4efb\\u52a1 ID',task_ph:'task_001',load_btn:'\\u52a0\\u8f7d',clear_btn:'\\u6e05\\u9664',
    trace_title:'\\u5f53\\u524d\\u4efb\\u52a1\\u94fe\\u8def',timeline_title:'\\u8bb0\\u5fc6\\u65f6\\u95f4\\u6d41',
    impact_title:'\\u4efb\\u52a1\\u5f71\\u54cd',mem_detail_title:'\\u8bb0\\u5fc6\\u753b\\u50cf',
    top_title:'\\u6700\\u6709\\u5f71\\u54cd\\u7684\\u8bb0\\u5fc6',
    no_task:'\\u8f93\\u5165\\u4efb\\u52a1 ID \\u4ee5\\u67e5\\u770b\\u8bb0\\u5fc6\\u94fe\\u8def',
    no_task_tl:'\\u52a0\\u8f7d\\u4efb\\u52a1\\u540e\\u67e5\\u770b\\u4e8b\\u4ef6\\u6d41',
    recalled:'\\u5df2\\u53ec\\u56de',injected:'\\u5df2\\u6ce8\\u5165',adopted:'\\u5df2\\u91c7\\u7528',
    workflow:'\\u4f7f\\u7528\\u7684\\u5de5\\u4f5c\\u6d41',result:'\\u6267\\u884c\\u7ed3\\u679c',
    success_lbl:'\\u2713 \\u4efb\\u52a1\\u6210\\u529f',fail_lbl:'\\u2717 \\u4efb\\u52a1\\u5931\\u8d25',
    no_workflow:'\\u672a\\u4f7f\\u7528\\u5de5\\u4f5c\\u6d41',no_result:'\\u672a\\u8bb0\\u5f55\\u7ed3\\u679c',
    impact_workflows:'\\u590d\\u7528\\u4e86 {n} \\u4e2a\\u5de5\\u4f5c\\u6d41',
    impact_adopted:'\\u91c7\\u7528\\u4e86 {n} \\u6761\\u8bb0\\u5fc6',
    impact_tokens:'\\u8282\\u7701\\u4e86\\u7ea6 {n} token',
    impact_task_ok:'\\u4efb\\u52a1\\u6210\\u529f',impact_task_fail:'\\u4efb\\u52a1\\u5931\\u8d25',
    used:'\\u4f7f\\u7528\\u6b21\\u6570',adoption:'\\u91c7\\u7528\\u7387',success:'\\u6210\\u529f\\u7387',
    quality:'\\u8d28\\u91cf',last_used:'\\u4e0a\\u6b21\\u4f7f\\u7528',
    status_promoted:'\\u2191 \\u5df2\\u6307\\u6570\\u5347',status_suppressed:'\\u2193 \\u5df2\\u62b9\\u6389',
    status_new:'\\u2605 \\u65b0',status_normal:'\\u25cb \\u6b63\\u5e38',
    q_high:'\\u4f18\\u79c0',q_mid:'\\u4e2d\\u7b49',q_low:'\\u5dee',
    top_used:'\\u6b21',top_adoption:'\\u91c7\\u7528',
    loading:'\\u52a0\\u8f7d\\u4e2d...',empty_top:'\\u6682\\u65e0\\u9876\\u90e8\\u8bb0\\u5fc6',
    conn_ok:'\\u5728\\u7ebf',conn_lost:'\\u5df2\\u65ad\\u5f00',
    bypass:'\\u26a0 API \\u5df2\\u65ad\\u5f00 \\u2014 \\u8bb0\\u5fc6\\u65c1\\u8def\\u6fc0\\u6d3b',
  }
};

function t(k,vars){
  var d=I18N[LANG]||I18N.en;
  var s=d[k]||I18N.en[k]||k;
  if(vars)Object.keys(vars).forEach(function(v){s=s.replace('{'+v+'}',vars[v])});
  return s;
}
function esc(s){var d=document.createElement('div');d.textContent=String(s||'');return d.innerHTML}
function relTime(iso){
  if(!iso)return '';
  var d=new Date(iso),now=new Date(),diff=Math.floor((now-d)/1000);
  if(diff<60)return diff+'s ago';
  if(diff<3600)return Math.floor(diff/60)+'m ago';
  if(diff<86400)return Math.floor(diff/3600)+'h ago';
  return Math.floor(diff/86400)+'d ago';
}
function fmtTime(iso){
  if(!iso)return '';
  try{
    var d=new Date(iso);
    return d.toLocaleTimeString([],{hour:'2-digit',minute:'2-digit',second:'2-digit'});
  }catch(e){return iso.slice(11,19)||iso}
}
function qualityLabel(q){
  var v=parseFloat(q)||0;
  if(v>=0.7)return{label:t('q_high'),cls:'good'};
  if(v>=0.4)return{label:t('q_mid'),cls:'mid'};
  return{label:t('q_low'),cls:'low'};
}
function statusDisplay(s){
  var map={promoted:t('status_promoted'),suppressed:t('status_suppressed'),new:t('status_new'),normal:t('status_normal')};
  return map[s]||t('status_normal');
}
function eventIcon(ev){
  var m={memory_stored:'&#x1F4E6;',memory_recalled:'&#x1F50D;',memory_injected:'&#x1F489;',
         memory_adopted:'&#x2705;',memory_succeeded:'&#x1F3AF;',
         workflow_recommended:'&#x1F4CB;',workflow_adopted:'&#x26A1;',workflow_completed:'&#x1F3C6;'};
  return m[ev]||'&#x25CF;';
}
function eventCls(ev){
  if(ev==='memory_stored')return 'stored';
  if(ev==='memory_recalled')return 'recalled';
  if(ev==='memory_injected')return 'injected';
  if(ev==='memory_adopted'||ev==='memory_succeeded')return 'adopted';
  return 'workflow_'+ev.split('_')[1]||ev;
}

async function fetchJSON(url){
  try{
    var r=await fetch(url);
    if(!r.ok){if(apiConnected){apiConnected=false;updateConnUI()}return null}
    if(!apiConnected){apiConnected=true;updateConnUI()}
    return await r.json();
  }catch(e){
    if(apiConnected){apiConnected=false;updateConnUI()}
    return null;
  }
}

function updateConnUI(){
  var banner=document.getElementById('conn-banner');
  var connText=document.getElementById('conn-text');
  if(!apiConnected){
    document.body.classList.add('conn-lost');
    banner.textContent=t('bypass');
    connText.textContent=t('conn_lost');
  }else{
    document.body.classList.remove('conn-lost');
    connText.textContent=t('conn_ok');
  }
}

function applyLang(){
  document.querySelectorAll('[data-i18n]').forEach(function(el){
    var k=el.getAttribute('data-i18n');
    el.textContent=t(k);
  });
  document.querySelectorAll('[data-i18n-ph]').forEach(function(el){
    el.placeholder=t(el.getAttribute('data-i18n-ph'));
  });
  document.getElementById('lang-btn').textContent=LANG==='en'?'EN / \u4e2d':'\u4e2d / EN';
  updateConnUI();
}
function toggleLang(){
  LANG=LANG==='en'?'zh':'en';
  localStorage.setItem('inspector_lang',LANG);
  applyLang();
  refreshMetrics();
  if(currentTaskId)doLoadTask();
  loadTopMemories();
}

/* ── MODULE 1: Impact Metrics ── */
async function refreshMetrics(){
  var d=await fetchJSON('/inspector/metrics');
  if(!d)return;
  document.getElementById('v-stored').textContent=d.total_memories||0;
  document.getElementById('v-recalled').textContent=d.memories_recalled||0;
  document.getElementById('v-reused').textContent=d.workflows_reused||0;
  document.getElementById('v-avoided').textContent=d.tasks_avoided||0;
  var tok=d.tokens_saved||0;
  document.getElementById('v-tokens').textContent=tok>=1000?'~'+(tok/1000).toFixed(1)+'k':tok;
}

/* ── MODULE 2: Task Trace ── */
function renderTrace(d){
  if(!d){
    document.getElementById('trace-body').innerHTML='<div class="empty">Failed to load task trace</div>';
    return;
  }
  var html='<div class="trace-section">';

  // Recalled
  var recalled=d.recalled_memories||[];
  html+='<div class="trace-group"><div class="trace-group-title recalled">&#x1F50D; '+t('recalled')+' <span class="count-pill'+(recalled.length?' has':'')+'">'+recalled.length+'</span></div>';
  if(recalled.length){
    html+=recalled.map(function(m){
      var mid=m.memory_id||m.id||'';
      return '<div class="trace-item" onclick="loadMemDetail(\''+esc(mid)+'\')">'+
        '<div class="trace-dot recalled"></div>'+
        '<div class="trace-text">'+esc(m.summary||mid)+'</div>'+
        '<span class="trace-type">'+esc(m.type||'memory')+'</span>'+
        '</div>';
    }).join('');
  }else{html+='<div class="section-empty">\u2014</div>'}
  html+='</div>';

  // Injected
  var injected=d.injected_memories||[];
  html+='<div class="trace-group"><div class="trace-group-title injected">&#x1F489; '+t('injected')+' <span class="count-pill'+(injected.length?' has':'')+'">'+injected.length+'</span></div>';
  if(injected.length){
    html+=injected.map(function(m){
      var mid=m.memory_id||m.id||'';
      return '<div class="trace-item" onclick="loadMemDetail(\''+esc(mid)+'\')">'+
        '<div class="trace-dot injected"></div>'+
        '<div class="trace-text">'+esc(m.summary||mid)+'</div>'+
        '<span class="trace-type">'+esc(m.type||'memory')+'</span>'+
        '</div>';
    }).join('');
  }else{html+='<div class="section-empty">\u2014</div>'}
  html+='</div>';

  // Adopted
  var adopted=d.adopted_memories||[];
  html+='<div class="trace-group"><div class="trace-group-title adopted">&#x2705; '+t('adopted')+' <span class="count-pill'+(adopted.length?' has':'')+'">'+adopted.length+'</span></div>';
  if(adopted.length){
    html+=adopted.map(function(m){
      var mid=m.memory_id||m.id||'';
      return '<div class="trace-item" onclick="loadMemDetail(\''+esc(mid)+'\')">'+
        '<div class="trace-dot adopted"></div>'+
        '<div class="trace-text">'+esc(m.summary||mid)+'</div>'+
        '<span class="trace-check">&#x2713;</span>'+
        '</div>';
    }).join('');
  }else{html+='<div class="section-empty">\u2014</div>'}
  html+='</div>';

  // Workflow
  var wf=d.workflow_used;
  html+='<div class="trace-group"><div class="trace-group-title workflow">&#x26A1; '+t('workflow')+'</div>';
  if(wf&&wf.workflow_id){
    html+='<div class="trace-item"><div class="trace-dot workflow"></div>'+
      '<div class="trace-text">'+esc(wf.summary||wf.workflow_id)+'</div>'+
      '<span class="badge workflow">WF</span></div>';
  }else{html+='<div class="section-empty">'+t('no_workflow')+'</div>'}
  html+='</div>';

  // Result
  var res=d.result;
  html+='<div class="trace-group"><div class="trace-group-title result">&#x1F3AF; '+t('result')+'</div>';
  if(res){
    var ok=res.success!==false;
    html+='<div class="result-box'+(ok?'':' fail')+'">'+
      '<div class="result-label '+(ok?'success':'fail')+'">'+(ok?t('success_lbl'):t('fail_lbl'))+'</div>'+
      '<div class="result-summary">'+esc(res.summary||'')+'</div></div>';
  }else{html+='<div class="section-empty">'+t('no_result')+'</div>'}
  html+='</div>';

  html+='</div>';
  document.getElementById('trace-body').innerHTML=html;

  // Impact Summary
  renderImpactSummary(d);
}

function renderImpactSummary(d){
  var adopted=(d.adopted_memories||[]).length;
  var wf=d.workflow_used&&d.workflow_used.workflow_id?1:0;
  var tokens=wf*350+adopted*50;
  var ok=(d.result&&d.result.success!==false);
  var html=
    '<div class="impact-item"><div class="impact-icon">&#x26A1;</div><div><div class="impact-label">'+t('impact_workflows',{n:wf})+'</div></div></div>'+
    '<div class="impact-item"><div class="impact-icon">&#x1F4AC;</div><div><div class="impact-label">'+t('impact_adopted',{n:adopted,y:adopted===1?'y':'ies'})+'</div></div></div>'+
    '<div class="impact-item"><div class="impact-icon">&#x1F4B0;</div><div><div class="impact-label">'+t('impact_tokens',{n:tokens})+'</div></div></div>'+
    '<div class="impact-item"><div class="impact-icon">'+(ok?'&#x1F3C6;':'&#x274C;')+'</div><div><div class="impact-label">'+(ok?t('impact_task_ok'):t('impact_task_fail'))+'</div></div></div>';
  document.getElementById('impact-sum-body').innerHTML=html;
  document.getElementById('impact-sum-wrap').style.display='block';
}

/* ── MODULE 3: Memory Timeline ── */
function renderTimeline(items){
  if(!items||!items.length){
    document.getElementById('timeline-body').innerHTML='<div class="empty">No timeline events for this task</div>';
    return;
  }
  var html='<div class="tl">';
  html+=items.map(function(ev){
    var cls=eventCls(ev.event||'');
    return '<div class="tl-i">'+
      '<div class="tl-icon '+cls+'">'+eventIcon(ev.event||'')+'</div>'+
      '<div class="tl-body">'+
        '<div><span class="tl-event-tag">'+esc(ev.event||'')+'</span></div>'+
        '<div class="tl-summary">'+esc(ev.summary||ev.description||'')+'</div>'+
        '<div class="tl-ts">'+fmtTime(ev.timestamp)+'</div>'+
      '</div>'+
    '</div>';
  }).join('');
  html+='</div>';
  document.getElementById('timeline-body').innerHTML=html;
}

/* ── MODULE 4: Memory Details ── */
async function loadMemDetail(memoryId){
  if(!memoryId)return;
  document.getElementById('mem-detail-body').innerHTML='<div class="loading">'+t('loading')+'</div>';
  document.getElementById('mem-details-wrap').classList.add('show');
  document.getElementById('mem-details-wrap').style.display='block';
  var d=await fetchJSON('/inspector/memory-profile?memory_id='+encodeURIComponent(memoryId));
  if(!d){
    document.getElementById('mem-detail-body').innerHTML='<div class="empty">Memory profile not found</div>';
    return;
  }
  var ql=qualityLabel(d.quality_score);
  var st=d.status||'normal';
  var stLabel=statusDisplay(st);
  var html=
    '<div class="mem-header">'+
      '<span class="mem-type-badge">'+esc(d.type||d.memory_layer||'memory')+'</span>'+
      '<span class="mem-status '+st+'">'+stLabel+'</span>'+
    '</div>'+
    '<div class="mem-grid">'+
      '<div class="mem-stat"><div class="mem-stat-label">'+t('used')+'</div><div class="mem-stat-value">'+esc(d.usage_count||0)+'&times;</div></div>'+
      '<div class="mem-stat"><div class="mem-stat-label">'+t('adoption')+'</div><div class="mem-stat-value '+ql.cls+'">'+Math.round((d.adoption_rate||0)*100)+'%</div></div>'+
      '<div class="mem-stat"><div class="mem-stat-label">'+t('success')+'</div><div class="mem-stat-value '+ql.cls+'">'+Math.round((d.success_rate||0)*100)+'%</div></div>'+
      '<div class="mem-stat"><div class="mem-stat-label">'+t('quality')+'</div>'+
        '<div class="mem-stat-value '+ql.cls+'">'+ql.label+' <span style="font-size:13px;font-weight:500">('+parseFloat(d.quality_score||0).toFixed(2)+')</span></div>'+
        '<div class="mem-quality"><div class="quality-bar-wrap"><div class="quality-bar '+ql.cls+'" style="width:'+Math.round((d.quality_score||0)*100)+'%"></div></div></div>'+
      '</div>'+
    '</div>'+
    '<div class="mem-last-used">&#x1F552; '+t('last_used')+': '+esc(d.last_used?relTime(d.last_used):'—')+'</div>'+
    '<div class="mem-last-used" style="font-size:10px;color:var(--text-m);margin-top:4px;font-family:monospace">'+esc(d.memory_id||memoryId)+'</div>';
  document.getElementById('mem-detail-body').innerHTML=html;
}
window.loadMemDetail=loadMemDetail;
function closeMemDetail(){
  document.getElementById('mem-details-wrap').style.display='none';
}
window.closeMemDetail=closeMemDetail;

/* ── Top Memories ── */
async function loadTopMemories(){
  var d=await fetchJSON('/inspector/top-memories?n=10');
  var el=document.getElementById('top-mem-body');
  if(!d||!d.length){el.innerHTML='<div class="empty">'+t('empty_top')+'</div>';return}
  var html='<div class="top-mem-list">';
  html+=d.map(function(m,i){
    var mid=m.memory_id||m.id||'';
    var ql=qualityLabel(m.quality_score);
    return '<div class="top-mem-i" onclick="loadMemDetail(\''+esc(mid)+'\')">'+
      '<span class="top-mem-rank">'+(i+1)+'</span>'+
      '<span class="top-mem-type">'+esc(m.type||m.memory_layer||'memory')+'</span>'+
      '<span class="top-mem-summary">'+esc(m.summary||mid)+'</span>'+
      '<span class="top-mem-score '+ql.cls+'">'+parseFloat(m.quality_score||0).toFixed(2)+'</span>'+
      '<span class="top-mem-rate">'+Math.round((m.adoption_rate||0)*100)+'% '+t('top_adoption')+'</span>'+
    '</div>';
  }).join('');
  html+='</div>';
  el.innerHTML=html;
}

/* ── Task loading ── */
async function doLoadTask(){
  var taskId=document.getElementById('task-input').value.trim();
  if(!taskId)return;
  currentTaskId=taskId;
  document.getElementById('trace-body').innerHTML='<div class="loading">'+t('loading')+'</div>';
  document.getElementById('timeline-body').innerHTML='<div class="loading">'+t('loading')+'</div>';
  document.getElementById('impact-sum-wrap').style.display='none';
  var [trace,timeline]=await Promise.all([
    fetchJSON('/inspector/task-trace?task_id='+encodeURIComponent(taskId)),
    fetchJSON('/inspector/timeline?task_id='+encodeURIComponent(taskId)),
  ]);
  renderTrace(trace);
  renderTimeline(Array.isArray(timeline)?timeline:(timeline&&timeline.events)||[]);
}
window.doLoadTask=doLoadTask;
document.getElementById('task-input').addEventListener('keydown',function(e){
  if(e.key==='Enter')doLoadTask();
});

function clearTask(){
  currentTaskId='';
  document.getElementById('task-input').value='';
  document.getElementById('impact-sum-wrap').style.display='none';
  document.getElementById('mem-details-wrap').style.display='none';
  document.getElementById('trace-body').innerHTML='<div class="no-task"><div class="no-task-icon">&#x1F4CB;</div><span>'+t('no_task')+'</span></div>';
  document.getElementById('timeline-body').innerHTML='<div class="no-task"><div class="no-task-icon">&#x23F1;</div><span>'+t('no_task_tl')+'</span></div>';
}
window.clearTask=clearTask;

/* ── Boot ── */
async function init(){
  applyLang();
  await Promise.all([refreshMetrics(),loadTopMemories()]);
  // Check version
  var v=await fetchJSON('/version');
  if(v)document.getElementById('header-version').textContent='v'+(v.latest_core||v.version||'');
}
init();
setInterval(refreshMetrics,30000);
setInterval(loadTopMemories,60000);
</script>
</body>
</html>"""
