// User-facing console page — shows live agent activity in a friendly,
// non-technical format with inline screenshot rendering.
export function renderConsoleHtml() {
    return `<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>VisionClaw Console</title>
  <link rel="icon" type="image/png" href="https://files.catbox.moe/au9xqh.png" />
  <style>
    :root {
      --bg: #0b1020;
      --border: rgba(255,255,255,0.10);
      --text: rgba(255,255,255,0.92);
      --muted: rgba(255,255,255,0.70);
      --dim: rgba(255,255,255,0.55);
      --good: #31d0aa;
      --warn: #ffcc66;
      --bad: #ff5c7a;
      --accent: #7aa2ff;
      --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
      --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
    }
    *, *::before, *::after { box-sizing: border-box; }
    body {
      margin: 0;
      background: radial-gradient(1200px 800px at 15% 10%, rgba(122,162,255,0.18), transparent 60%), var(--bg);
      color: var(--text);
      font-family: var(--sans);
      min-height: 100vh;
    }

    /* ── Header ─────────────────────────────────── */
    header {
      position: sticky; top: 0; z-index: 10;
      backdrop-filter: blur(10px);
      background: rgba(22,32,55,0.55);
      border-bottom: 1px solid var(--border);
    }
    .wrap { max-width: 720px; margin: 0 auto; padding: 0 20px; }
    .header-inner { display: flex; align-items: center; gap: 12px; padding: 10px 0; flex-wrap: wrap; }
    h1 { margin: 0; font-size: 15px; font-weight: 650; display: flex; align-items: center; gap: 8px; }
    .logo { width: 36px; height: 36px; border-radius: 8px; }
    .spacer { flex: 1; }

    /* ── Status (in header) ─────────────────────── */
    .status-indicator { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600; }
    .dot { width: 9px; height: 9px; border-radius: 999px; background: var(--dim); flex-shrink: 0; }
    .dot.idle { background: var(--good); box-shadow: 0 0 0 3px rgba(49,208,170,0.20); }
    .dot.busy { background: var(--accent); box-shadow: 0 0 0 3px rgba(122,162,255,0.22); animation: pulse 1.4s ease-in-out infinite; }
    .dot.stop { background: var(--warn); box-shadow: 0 0 0 3px rgba(255,204,102,0.22); }
    .dot.disconnected { background: var(--bad); box-shadow: 0 0 0 3px rgba(255,92,122,0.20); }
    @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.45} }
    .status-label { color: var(--text); }
    .stop-btn {
      cursor: pointer;
      border: 1px solid rgba(255,92,122,0.45); background: rgba(255,92,122,0.10); color: #ff5c7a;
      padding: 6px 14px; border-radius: 8px; font-size: 12px; font-weight: 600; font-family: var(--sans);
      transition: background 0.15s, transform 0.1s;
    }
    .stop-btn:hover:not(:disabled) { background: rgba(255,92,122,0.20); }
    .stop-btn:active:not(:disabled) { transform: translateY(1px); }
    .stop-btn:disabled { opacity: 0.38; cursor: not-allowed; }
    .stop-btn.stopping { border-color: rgba(255,204,102,0.5); background: rgba(255,204,102,0.12); color: var(--warn); }

    /* ── Feed ───────────────────────────────────── */
    .feed { padding: 16px 0 24px; }
    .feed-list { display: flex; flex-direction: column; gap: 6px; max-height: calc(100vh - 100px); overflow-y: auto; padding-right: 4px; }
    .feed-empty { padding: 48px 24px; text-align: center; color: var(--dim); font-size: 14px; }

    /* ── Bubble ──────────────────────────────────── */
    .bubble {
      padding: 10px 14px; border-radius: 12px;
      font-size: 13px; line-height: 1.5;
      max-width: 100%; animation: fadeIn 0.15s ease;
    }
    @keyframes fadeIn { from{opacity:0;transform:translateY(4px)} to{opacity:1;transform:none} }

    .bubble .time { font-size: 10px; color: var(--dim); margin-top: 4px; }

    /* Type variants */
    .bubble.wake {
      background: rgba(49,208,170,0.08); border: 1px solid rgba(49,208,170,0.18);
      color: var(--good); font-weight: 600; font-size: 12px; text-align: center;
      align-self: center; padding: 6px 16px;
    }
    .bubble.thinking {
      background: rgba(208,156,240,0.08); border: 1px solid rgba(208,156,240,0.14); color: var(--text);
    }
    .bubble.tool {
      background: rgba(92,201,245,0.06); border: 1px solid rgba(92,201,245,0.14);
      font-family: var(--mono); font-size: 12px; color: var(--muted);
    }
    .bubble.tool .tool-name { color: #5cc9f5; font-weight: 600; }
    .bubble.tool .tool-input { color: var(--dim); margin-top: 2px; word-break: break-word; }
    .bubble.message {
      background: rgba(102,224,184,0.08); border: 1px solid rgba(102,224,184,0.16); color: var(--text);
    }
    .bubble.message .sender { color: #66e0b8; font-weight: 600; font-size: 12px; }
    .bubble.reply {
      background: rgba(122,200,255,0.08); border: 1px solid rgba(122,200,255,0.14); color: var(--text);
    }
    .bubble.error {
      background: rgba(255,92,122,0.08); border: 1px solid rgba(255,92,122,0.18); color: var(--bad);
    }
    .bubble.done {
      background: rgba(255,255,255,0.04); border: 1px solid var(--border);
      color: var(--dim); font-size: 12px; text-align: center;
      align-self: center; padding: 6px 16px;
    }
    .bubble.done .cost { color: var(--muted); font-weight: 600; }
    .bubble.screenshot {
      background: rgba(255,255,255,0.03); border: 1px solid var(--border); padding: 8px;
    }
    .bubble.screenshot .screenshot-label { font-size: 11px; color: var(--dim); margin-bottom: 6px; }
    .bubble.screenshot img {
      display: block; max-width: 100%; height: auto; border-radius: 8px;
      border: 1px solid rgba(255,255,255,0.08); cursor: pointer;
    }
    .bubble.info {
      background: rgba(255,255,255,0.03); border: 1px solid var(--border);
      color: var(--muted); font-size: 12px;
    }

    /* ── Lightbox ────────────────────────────────── */
    .lightbox {
      display: none; position: fixed; inset: 0; z-index: 200;
      background: rgba(0,0,0,0.85); align-items: center; justify-content: center; cursor: zoom-out;
    }
    .lightbox.open { display: flex; }
    .lightbox img { max-width: 95vw; max-height: 95vh; border-radius: 8px; }

    /* ── Toast ───────────────────────────────────── */
    .toast {
      position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
      background: rgba(30,40,60,0.96); border: 1px solid var(--border);
      color: var(--text); padding: 10px 20px; border-radius: 10px;
      font-size: 13px; z-index: 100; pointer-events: none;
      opacity: 0; transition: opacity 0.2s;
    }
    .toast.show { opacity: 1; }

    @media (max-width: 600px) {
      .bubble { font-size: 13px; }
    }
  </style>
</head>
<body>
<header>
  <div class="wrap">
    <div class="header-inner">
      <h1><img class="logo" src="https://files.catbox.moe/au9xqh.png" alt="" />VisionClaw Console</h1>
      <span class="spacer"></span>
      <div class="status-indicator">
        <div class="dot" id="status-dot"></div>
        <span class="status-label" id="status-label">Unknown</span>
      </div>
      <button class="stop-btn" id="stop-btn" disabled>Stop Agent</button>
    </div>
  </div>
</header>

<main>
  <div class="wrap">
    <div class="feed">
      <div class="feed-list" id="feed-list">
        <div class="feed-empty" id="feed-empty">Waiting for activity…</div>
      </div>
    </div>
  </div>
</main>

<div class="lightbox" id="lightbox"><img id="lightbox-img" /></div>
<div class="toast" id="toast"></div>

<script>
  const MAX = 80;
  let agentBusy = false, stopRequested = false, sseConnected = false, stopInFlight = false, count = 0;

  const $ = (id) => document.getElementById(id);
  const $dot = $('status-dot'), $label = $('status-label');
  const $btn = $('stop-btn'), $list = $('feed-list'), $empty = $('feed-empty');
  const $toast = $('toast');
  const $lightbox = $('lightbox'), $lbImg = $('lightbox-img');

  // ── Lightbox ────────────────────────────────────────────────────────────
  $lightbox.addEventListener('click', () => $lightbox.classList.remove('open'));

  function openLightbox(src) {
    $lbImg.src = src;
    $lightbox.classList.add('open');
  }

  // ── Toast ───────────────────────────────────────────────────────────────
  let toastT = null;
  function showToast(msg) {
    $toast.textContent = msg;
    $toast.classList.add('show');
    if (toastT) clearTimeout(toastT);
    toastT = setTimeout(() => $toast.classList.remove('show'), 3000);
  }

  // ── Status ──────────────────────────────────────────────────────────────
  function renderStatus() {
    if (!sseConnected) {
      $dot.className = 'dot disconnected'; $label.textContent = 'Disconnected';
      $btn.disabled = true;
    } else if (stopRequested || stopInFlight) {
      $dot.className = 'dot stop'; $label.textContent = 'Stopping…';
      $btn.disabled = true;
    } else if (agentBusy) {
      $dot.className = 'dot busy'; $label.textContent = 'Working';
      $btn.disabled = false;
    } else {
      $dot.className = 'dot idle'; $label.textContent = 'Idle';
      $btn.disabled = true;
    }
    $btn.textContent = (stopRequested || stopInFlight) ? 'Stopping…' : 'Stop Agent';
    $btn.className = (stopRequested || stopInFlight) ? 'stop-btn stopping' : 'stop-btn';
  }

  // ── Stop ─────────────────────────────────────────────────────────────────
  $btn.addEventListener('click', async () => {
    if ($btn.disabled || stopInFlight) return;
    stopInFlight = true;
    $btn.disabled = true;
    $btn.textContent = 'Stopping…';
    $btn.className = 'stop-btn stopping';
    try {
      const res = await fetch('/console/stop', { method: 'POST' });
      if (res.ok) { stopRequested = true; renderStatus(); showToast('Stop signal sent'); }
      else { showToast('Stop failed (' + res.status + ')'); }
    } catch { showToast('Stop failed (network)'); }
    stopInFlight = false;
  });

  // ── Helpers ──────────────────────────────────────────────────────────────
  function fmtTime(iso) {
    if (!iso) return '';
    const d = new Date(iso);
    const h = String(d.getHours()).padStart(2,'0');
    const m = String(d.getMinutes()).padStart(2,'0');
    return h + ':' + m;
  }

  function esc(s) {
    const el = document.createElement('span');
    el.textContent = s;
    return el.innerHTML;
  }

  function safeStr(v) {
    if (v == null) return '';
    if (typeof v === 'string') return v;
    try { return JSON.stringify(v); } catch { return String(v); }
  }

  function addBubble(html, cls) {
    if ($empty && $empty.parentNode) $empty.remove();
    const el = document.createElement('div');
    el.className = 'bubble ' + cls;
    el.innerHTML = html;
    $list.appendChild(el);
    count++;
    while ($list.childElementCount > MAX) { $list.removeChild($list.firstChild); count--; }
    $list.scrollTop = $list.scrollHeight;

    // Wire up any screenshot click-to-zoom
    el.querySelectorAll('img.ss-img').forEach(img => {
      img.addEventListener('click', () => openLightbox(img.src));
    });
  }

  // ── Entry processing ────────────────────────────────────────────────────
  function processEntry(entry) {
    const cat = entry.category || '';
    const lvl = entry.level || 'info';
    const msg = entry.message || '';
    const d   = entry.data || {};
    const t   = fmtTime(entry.timestamp);

    // Skip debug-level entries entirely
    if (lvl === 'debug') return;

    switch (cat) {
      case 'wake': {
        const trigger = d.trigger || '';
        const label = trigger === 'heartbeat' ? 'Heartbeat check' : 'New message received';
        addBubble(esc(label), 'wake');
        return;
      }

      case 'assistant': {
        if (!msg || msg.startsWith('[thinking]') || msg.startsWith('[subagent')) return;
        addBubble(esc(msg) + '<div class="time">' + esc(t) + '</div>', 'thinking');
        return;
      }

      case 'tool_call': {
        const name = safeStr(d.name || '');
        if (!name) return;
        // Skip internal/noisy tools
        if (['finish', 'mcp__visionclaw__finish'].includes(name)) return;

        const friendly = friendlyToolName(name);
        const input = d.input ? friendlyToolInput(name, d.input) : '';
        let html = '<span class="tool-name">' + esc(friendly) + '</span>';
        if (input) html += '<div class="tool-input">' + esc(input) + '</div>';
        addBubble(html, 'tool');
        return;
      }

      case 'screenshot': {
        const b64 = d.base64;
        if (!b64) return;
        addBubble(
          '<div class="screenshot-label">Desktop screenshot</div>' +
          '<img class="ss-img" src="data:image/png;base64,' + b64 + '" alt="Desktop screenshot" loading="lazy" />',
          'screenshot'
        );
        return;
      }

      case 'incoming': {
        const sender = safeStr(d.sender || '');
        const channel = safeStr(d.channel || '');
        const text = safeStr(d.text || msg);
        addBubble(
          '<div class="sender">' + esc(sender) + ' via ' + esc(channel) + '</div>' +
          esc(text) +
          '<div class="time">' + esc(t) + '</div>',
          'message'
        );
        return;
      }

      case 'fast_response': {
        if (!msg) return;
        addBubble(esc(msg) + '<div class="time">' + esc(t) + '</div>', 'reply');
        return;
      }

      case 'result': {
        const cost = d.cost != null ? '$' + d.cost : '';
        const turns = d.turns != null ? d.turns + ' steps' : '';
        const parts = [turns, cost].filter(Boolean).join(' · ');
        addBubble('Done' + (parts ? ' <span class="cost">(' + esc(parts) + ')</span>' : ''), 'done');
        return;
      }

      case 'error': {
        addBubble(esc(msg) + '<div class="time">' + esc(t) + '</div>', 'error');
        return;
      }

      case 'channel': {
        if (lvl === 'warn' || lvl === 'error') {
          addBubble(esc(msg), 'error');
        }
        return;
      }

      case 'system': {
        if (lvl === 'warn' || lvl === 'error') {
          addBubble(esc(msg), 'error');
        }
        // Silently skip info-level system messages (startup noise, compaction, etc.)
        return;
      }

      // Everything else (send, tool_result, etc.) is hidden
      default:
        return;
    }
  }

  // ── Friendly tool names ─────────────────────────────────────────────────
  function friendlyToolName(name) {
    const map = {
      'mcp__visionclaw__notify_user': 'Sending notification',
      'mcp__visionclaw__manage_skills': 'Managing skills',
      'mcp__visionclaw__upgrade': 'Checking for updates',
      'mcp__visionclaw__manage_calendar': 'Managing calendar',
      'mcp__visionclaw__memory': 'Accessing memory',
      'mcp__visionclaw__computer_use_click': 'Clicking on screen',
      'mcp__visionclaw__computer_use_scroll': 'Scrolling',
      'mcp__visionclaw__computer_use_drag': 'Dragging',
      'mcp__visionclaw__computer_use_type': 'Typing text',
      'mcp__visionclaw__computer_use_key': 'Pressing keys',
      'mcp__visionclaw__computer_use_screenshot': 'Taking screenshot',
      'mcp__playwright__browser_navigate': 'Opening webpage',
      'mcp__playwright__browser_click': 'Clicking in browser',
      'mcp__playwright__browser_fill_form': 'Filling form',
      'mcp__playwright__browser_snapshot': 'Reading page',
      'mcp__playwright__browser_take_screenshot': 'Browser screenshot',
      'mcp__playwright__browser_hover': 'Hovering in browser',
      'mcp__playwright__browser_press_key': 'Pressing key in browser',
      'mcp__playwright__browser_evaluate': 'Running browser script',
      'mcp__playwright__browser_tabs': 'Managing browser tabs',
      'mcp__playwright__browser_close': 'Closing browser',
      'mcp__playwright__browser_wait': 'Waiting for page',
      'Bash': 'Running command',
      'Read': 'Reading file',
      'Write': 'Writing file',
      'Edit': 'Editing file',
      'Glob': 'Searching files',
      'Grep': 'Searching in files',
      'WebSearch': 'Searching the web',
      'web_search': 'Searching the web',
      'WebFetch': 'Fetching webpage',
      'Task': 'Running sub-task',
    };
    return map[name] || name.replace(/^mcp__\\w+__/, '').replace(/_/g, ' ');
  }

  function friendlyToolInput(name, input) {
    if (!input || typeof input !== 'object') return '';
    if (input.url) return input.url;
    if (input.path) return input.path;
    if (input.command) return input.command.length > 80 ? input.command.substring(0, 80) + '…' : input.command;
    if (input.query) return input.query;
    if (input.text && typeof input.text === 'string') return input.text.length > 80 ? input.text.substring(0, 80) + '…' : input.text;
    if (input.action) return input.action;
    return '';
  }

  // ── Status polling ──────────────────────────────────────────────────────
  function pollStatus() {
    fetch('/console/status').then(r => r.json()).then(d => {
      agentBusy = !!d.busy; stopRequested = !!d.stopRequested; renderStatus();
    }).catch(() => {});
  }
  pollStatus();
  setInterval(pollStatus, 3000);

  // ── SSE + snapshot ──────────────────────────────────────────────────────
  fetch('/obs/snapshot').then(r => r.json()).then(items => {
    for (const e of items) processEntry(e);
  }).catch(() => {});

  const es = new EventSource('/obs/events');
  es.onopen  = () => { sseConnected = true; renderStatus(); };
  es.onerror = () => { sseConnected = false; renderStatus(); };
  es.onmessage = (ev) => {
    try {
      const entry = JSON.parse(ev.data);
      processEntry(entry);
      if (entry.category === 'wake')   { agentBusy = true;  stopRequested = false; renderStatus(); }
      if (entry.category === 'result') { agentBusy = false; stopRequested = false; renderStatus(); }
    } catch {}
  };

  renderStatus();
</script>
</body>
</html>`;
}
//# sourceMappingURL=ui.js.map