"""
Desktop App Web UI — HTML/CSS/JS for the TaskingBot AI desktop application.

This is a standalone adaptation of the tt-fab sidepanel that works without
Chrome extension APIs. It uses localStorage, direct fetch to tasking.tech,
and pywebview bridges instead.
"""

DESKTOP_HTML = r'''<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TaskingBot AI</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }

    :root {
      --bg: #0d0d0d;
      --surface: #1a1a1a;
      --surface-2: #2a2a2a;
      --border: #2a2a2a;
      --text: #ffffff;
      --text-muted: #e0e0e0;
      --text-subtle: rgba(255, 255, 255, 0.6);
      --overlay: rgba(0, 0, 0, 0.2);
      --overlay-border: rgba(255, 255, 255, 0.1);
      --accent: #FF6B00;
      --accent-2: #FF8C00;
      --shadow: rgba(0, 0, 0, 0.08);
      --typing-bg: rgba(255, 255, 255, 0.1);
      --typing-dot: rgba(255, 255, 255, 0.5);
    }

    :root[data-theme="light"] {
      --bg: #f7f7f7;
      --surface: #ffffff;
      --surface-2: #f1f1f1;
      --border: #e3e3e3;
      --text: #111111;
      --text-muted: #333333;
      --text-subtle: rgba(17, 17, 17, 0.6);
      --overlay: rgba(255, 255, 255, 0.75);
      --overlay-border: rgba(17, 17, 17, 0.1);
      --accent: #FF6B00;
      --accent-2: #FF8C00;
      --shadow: rgba(0, 0, 0, 0.06);
      --typing-bg: rgba(0, 0, 0, 0.06);
      --typing-dot: rgba(0, 0, 0, 0.35);
    }

    html, body { height: 100%; overflow: hidden; }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      background: var(--bg);
      color: var(--text);
      display: flex;
      flex-direction: column;
      font-size: 12px;
      align-items: stretch;
    }

    .panel-shell {
      width: 100%; max-width: none; height: 100vh; max-height: 100vh;
      margin: 0; display: flex; flex-direction: column;
      background: var(--bg); overflow: hidden;
    }

    .header {
      position: relative; padding: 3px 0 3px 4px;
      background: var(--surface); border-bottom: 1px solid var(--border);
      display: flex; align-items: center; gap: 2px;
      flex-wrap: nowrap; flex-shrink: 0; min-height: 30px;
    }

    .logo {
      width: 26px; height: 26px; border-radius: 6px;
      display: flex; align-items: center; justify-content: center;
      background: var(--bg); box-shadow: 0 1px 4px var(--shadow);
    }

    .logo svg { width: 18px; height: 18px; }

    .title { font-size: 12px; font-weight: 600; flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

    .header-controls {
      margin-left: auto; display: flex; align-items: center;
      gap: 2px; justify-content: flex-end; flex-shrink: 0;
      white-space: nowrap; flex-wrap: nowrap;
    }

    .theme-btn, .logout-btn {
      width: 24px; height: 24px; border-radius: 6px;
      border: 1px solid var(--border); background: var(--bg);
      display: flex; align-items: center; justify-content: center;
      cursor: pointer; color: var(--text-muted);
      transition: border-color 0.2s, color 0.2s, transform 0.2s;
    }
    .theme-btn:hover, .logout-btn:hover { transform: translateY(-1px); border-color: var(--accent); }
    .logout-btn:hover { border-color: #ff6b6b; color: #ff6b6b; }
    .logout-btn { display: none; padding: 0; }
    .logout-btn svg, .theme-btn svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 1.8; }

    .login-btn {
      height: 24px; border-radius: 6px; border: 1px solid var(--border);
      background: var(--bg); color: var(--text-muted); font-size: 11px;
      font-weight: 600; padding: 0 6px; cursor: pointer;
    }
    .login-btn:hover { border-color: var(--accent); color: var(--accent); }

    .header-avatar {
      width: 24px; height: 24px; border-radius: 50%; object-fit: cover;
      border: 1.5px solid var(--accent); background: var(--bg); flex-shrink: 0;
    }
    .header-avatar-wrap { position: relative; width: 24px; height: 24px; flex-shrink: 0; }
    .avatar-presence-dot {
      position: absolute; right: -1px; bottom: -1px; width: 8px; height: 8px;
      border-radius: 50%; border: 2px solid var(--surface); background: #8a8a8a;
      box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);
    }
    .header-avatar-wrap.is-online .avatar-presence-dot { background: #23c552; }
    .header-avatar-wrap.is-offline .avatar-presence-dot { background: #8a8a8a; }

    .theme-icon { display: none; }
    :root[data-theme="light"] .theme-icon-light { display: block; }
    :root[data-theme="dark"] .theme-icon-dark { display: block; }

    .chat-container {
      flex: 1; overflow-y: auto; overflow-x: hidden;
      padding: 6px 3px; display: flex; flex-direction: column;
      gap: 8px; width: 100%; min-height: 0;
    }

    .message {
      padding: 0; border-radius: 0; background: transparent; border: 0;
      display: flex; max-width: 100%; min-width: 0;
      overflow-wrap: break-word; word-break: normal;
    }
    .message-content {
      min-width: 0; flex: 1 1 0%; max-width: calc(100% - 28px);
      padding: 5px 6px; border-radius: 10px; white-space: pre-wrap;
      overflow-wrap: break-word; word-break: normal; box-sizing: border-box;
    }
    .message.user {
      color: #fff; align-self: flex-end; display: flex;
      flex-direction: row-reverse; align-items: flex-end; gap: 4px;
    }
    .message.user .message-content {
      background: linear-gradient(90deg, var(--accent) 0%, var(--accent-2) 100%);
      color: #fff; border-bottom-right-radius: 3px;
    }
    .message.assistant {
      color: var(--text-muted); align-self: flex-start; margin-left: 0;
      display: flex; flex-direction: row; align-items: flex-start;
      gap: 4px; max-width: 100%;
    }
    .message.assistant .message-content {
      background: var(--surface); color: var(--text-muted);
      border-bottom-left-radius: 12px; border-bottom-right-radius: 3px;
      border: 1px solid var(--border);
    }
    .avatar {
      width: 20px; height: 20px; flex: 0 0 20px; border-radius: 50%;
      object-fit: cover; border: 1.5px solid var(--accent);
      background: var(--bg); margin-top: 2px;
    }

    .input-container {
      position: relative; padding: 4px 0 0; background: var(--overlay);
      border-top: 1px solid var(--overlay-border); width: 100%; flex-shrink: 0;
    }
    .input-wrapper { display: flex; flex-direction: column; gap: 4px; align-items: stretch; padding: 0 4px 4px 4px; }
    .input-actions { display: flex; align-items: center; gap: 3px; flex-wrap: wrap; justify-content: flex-end; }

    .sidebar-action-btn {
      width: 26px; height: 26px; display: inline-flex; align-items: center;
      justify-content: center; border-radius: 8px; background: none;
      border: none; cursor: pointer; padding: 0;
    }
    .sidebar-action-btn svg { width: 16px; height: 16px; }
    .sidebar-action-btn.is-listening { border: 1px solid var(--accent); background: rgba(255, 107, 0, 0.14); box-shadow: 0 0 0 2px rgba(255, 107, 0, 0.2); }

    .message-input {
      width: 100%; min-width: 100%; background: var(--bg);
      border: 1px solid var(--border); border-radius: 12px;
      padding: 6px 4px 6px 8px; color: var(--text); font-size: 12px;
      outline: none; resize: vertical; min-height: 38px; max-height: 120px; line-height: 1.3;
    }
    .message-input::placeholder { color: var(--text-subtle); }
    .message-input:focus { border-color: var(--accent); background: var(--surface-2); }

    .send-button {
      width: 28px; height: 28px; border-radius: 50%;
      background: linear-gradient(90deg, var(--accent) 0%, var(--accent-2) 100%);
      border: none; cursor: pointer; display: flex; align-items: center;
      justify-content: center; transition: transform 0.2s, box-shadow 0.2s;
      min-width: 28px; min-height: 28px; padding: 0;
    }
    .send-button:hover { transform: scale(1.05); box-shadow: 0 2px 8px rgba(255, 107, 0, 0.2); }
    .send-button svg { width: 16px; height: 16px; fill: #fff; }

    .typing-indicator {
      display: flex; gap: 3px; padding: 6px 10px;
      background: var(--typing-bg); border-radius: 16px; align-self: flex-start;
    }
    .typing-dot {
      width: 7px; height: 7px; background: var(--typing-dot);
      border-radius: 50%; animation: typing 1.4s infinite ease-in-out;
    }
    .typing-dot:nth-child(2) { animation-delay: 0.2s; }
    .typing-dot:nth-child(3) { animation-delay: 0.4s; }
    @keyframes typing {
      0%, 60%, 100% { transform: translateY(0); }
      30% { transform: translateY(-8px); }
    }

    .message-actions { display: flex; align-items: center; justify-content: flex-end; gap: 2px; width: 100%; margin-top: 6px; }
    .message-action-btn {
      border: 1px solid var(--border); background: transparent; color: var(--accent-2);
      border-radius: 6px; min-width: 20px; width: 20px; height: 20px;
      display: inline-flex; align-items: center; justify-content: center;
      font-size: 11px; cursor: pointer; padding: 0; line-height: 1; flex-shrink: 0;
    }

    .media-gallery { margin-top: 8px; display: flex; flex-direction: column; gap: 8px; }
    .media-card { border: 1px solid var(--border); border-radius: 8px; padding: 4px; background: var(--surface-2); }
    .media-preview-wrap {
      width: 100%; max-height: 220px; overflow: auto; border-radius: 6px;
      background: var(--bg); display: flex; justify-content: center;
      align-items: center; padding: 2px;
    }
    .media-preview { max-width: 100%; max-height: 220px; object-fit: contain; cursor: zoom-in; }

    .tab-btn {
      flex: 1; padding: 4px 0; background: none; border: none;
      font-weight: 600; cursor: pointer; border-bottom: 2px solid transparent;
      transition: color 0.2s, border-color 0.2s; font-size: 11px;
      color: var(--text-muted);
    }
    .tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }

    /* Login overlay */
    .login-overlay {
      display: none; position: fixed; inset: 0; z-index: 9999;
      background: var(--bg); flex-direction: column; align-items: center;
      justify-content: center; padding: 32px 24px;
    }
    .login-overlay.is-open { display: flex; }
    .login-overlay h2 { font-size: 18px; margin-bottom: 8px; color: var(--text); }
    .login-overlay p { font-size: 12px; color: var(--text-subtle); margin-bottom: 24px; text-align: center; }
    .login-form { width: 100%; max-width: 300px; display: flex; flex-direction: column; gap: 10px; }
    .login-form input {
      padding: 10px 12px; border-radius: 8px; border: 1px solid var(--border);
      background: var(--surface); color: var(--text); font-size: 13px; outline: none;
    }
    .login-form input:focus { border-color: var(--accent); }
    .login-form button {
      padding: 10px; border-radius: 8px; border: none;
      background: linear-gradient(90deg, var(--accent), var(--accent-2));
      color: #fff; font-size: 13px; font-weight: 600; cursor: pointer;
    }
    .login-form button:hover { opacity: 0.9; }
    .login-status { font-size: 11px; color: #ff6b6b; margin-top: 8px; text-align: center; min-height: 16px; }
    .login-link { color: var(--accent); text-decoration: none; font-size: 12px; margin-top: 12px; cursor: pointer; }
    .oauth-btn {
      width: 100%; height: 34px; border-radius: 8px; border: 1px solid var(--border);
      background: var(--bg); color: var(--text-muted); font-size: 12px; font-weight: 600;
      cursor: pointer; transition: border-color 0.2s, color 0.2s;
      display: flex; align-items: center; justify-content: center; gap: 6px;
    }
    .oauth-btn svg { flex-shrink: 0; }
    .oauth-btn:hover { border-color: var(--accent); color: var(--accent); }

    /* Action log */
    .action-log {
      margin-top: 6px; padding: 4px 6px; border-radius: 6px;
      background: var(--surface-2); border: 1px solid var(--border);
      font-size: 11px; color: var(--text-subtle);
    }
    .action-log-item { padding: 2px 0; border-bottom: 1px solid var(--border); }
    .action-log-item:last-child { border-bottom: none; }
    .action-log-label { color: var(--accent); font-weight: 600; }

    /* Desktop panel */
    .desktop-panel {
      display: none; padding: 24px 16px; min-height: 120px; color: var(--text);
      background: var(--surface); overflow-y: auto; flex: 1;
    }

    /* Slash command autocomplete */
    .slash-dropdown {
      position: absolute; bottom: 100%; left: 4px; right: 4px;
      max-height: 220px; overflow-y: auto; z-index: 100;
      background: var(--surface-2); border: 1px solid var(--border);
      border-radius: 10px; padding: 4px 0; margin-bottom: 4px;
      box-shadow: 0 -4px 16px rgba(0,0,0,0.4); display: none;
    }
    .slash-dropdown.visible { display: block; }
    .slash-item {
      padding: 6px 10px; cursor: pointer; display: flex; gap: 8px;
      align-items: baseline; font-size: 12px; border-radius: 6px; margin: 1px 4px;
    }
    .slash-item:hover, .slash-item.active { background: rgba(255, 107, 0, 0.15); }
    .slash-item .cmd { color: var(--accent); font-weight: 600; white-space: nowrap; }
    .slash-item .args { color: var(--text-subtle); font-size: 11px; white-space: nowrap; }
    .slash-item .desc { color: var(--text-secondary); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }

    /* Cmdlist modal */
    .cmdlist-overlay {
      position: fixed; inset: 0; z-index: 200; background: rgba(0,0,0,0.6);
      display: none; align-items: center; justify-content: center;
    }
    .cmdlist-overlay.visible { display: flex; }
    .cmdlist-modal {
      background: var(--surface-2); border: 1px solid var(--border);
      border-radius: 14px; width: 90%; max-width: 500px; max-height: 80vh;
      overflow-y: auto; padding: 16px;
    }
    .cmdlist-modal h3 { margin: 0 0 12px; color: var(--accent); font-size: 14px; }
    .cmdlist-modal .cmd-row { display: flex; gap: 6px; padding: 4px 0; font-size: 11px; border-bottom: 1px solid var(--overlay-border); }
    .cmdlist-modal .cmd-row .cmd-name { color: var(--accent); font-weight: 600; min-width: 120px; }
    .cmdlist-modal .cmd-row .cmd-desc { color: var(--text-secondary); flex: 1; }

    /* Scrollbar styling */
    .chat-container, .message-input { scrollbar-width: thin; scrollbar-color: #FF6B00 transparent; }
    .chat-container::-webkit-scrollbar, .message-input::-webkit-scrollbar { width: 2px; height: 2px; }
    .chat-container::-webkit-scrollbar-track, .message-input::-webkit-scrollbar-track { background: transparent; }
    .chat-container::-webkit-scrollbar-thumb, .message-input::-webkit-scrollbar-thumb { background: #FF6B00; border-radius: 999px; }
  </style>
</head>
<body>
  <div class="panel-shell">
    <!-- Header -->
    <div class="header">
      <div class="logo">
        <img src="data:image/png;base64,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" alt="TaskingBot Logo" style="width:22px;height:22px;border-radius:5px;" />
      </div>
      <div class="title">
        <div style="font-size:13px;font-weight:600;line-height:1.1;">TaskingBot AI<sup style="font-size:7px;vertical-align:super;opacity:0.7;">&reg;</sup></div>
        <div style="font-size:9px;opacity:0.5;line-height:1;">tasking.tech<sup style="font-size:5px;vertical-align:super;">&reg;</sup> Desktop</div>
      </div>
      <div class="header-controls">
        <button class="login-btn" id="headerLoginBtn" type="button" title="Login to Tasking">Login</button>
        <button class="logout-btn" id="headerLogoutBtn" type="button" title="Logout" aria-label="Logout">
          <svg viewBox="0 0 24 24"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
        </button>
        <button class="theme-btn" id="themeToggle" aria-label="Toggle theme" title="Toggle theme" type="button">
          <svg class="theme-icon theme-icon-light" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><line x1="12" y1="2" x2="12" y2="5"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="2" y1="12" x2="5" y2="12"/><line x1="19" y1="12" x2="22" y2="12"/><line x1="4.2" y1="4.2" x2="6.4" y2="6.4"/><line x1="17.6" y1="17.6" x2="19.8" y2="19.8"/><line x1="4.2" y1="19.8" x2="6.4" y2="17.6"/><line x1="17.6" y1="6.4" x2="19.8" y2="4.2"/></svg>
          <svg class="theme-icon theme-icon-dark" viewBox="0 0 24 24"><path d="M21 14.5A8.5 8.5 0 1 1 9.5 3a7 7 0 0 0 11.5 11.5Z"/></svg>
        </button>
        <div class="header-avatar-wrap is-offline" id="headerAvatarWrap">
          <div class="header-avatar" id="headerAvatar" style="width:24px;height:24px;border-radius:50%;background:var(--surface-2);border:1.5px solid var(--accent);display:flex;align-items:center;justify-content:center;font-size:11px;color:var(--accent);">?</div>
          <span class="avatar-presence-dot" id="avatarPresenceDot"></span>
        </div>
      </div>
    </div>

    <!-- Tab bar -->
    <div id="tabBar" style="display:flex;align-items:center;background:var(--surface);border-bottom:1px solid var(--border);">
      <button class="tab-btn active" id="tabChat" data-tab="chat">Chat</button>
      <button class="tab-btn" id="tabAssets" data-tab="assets">Assets</button>
      <button class="tab-btn" id="tabDesktop" data-tab="desktop">Desktop</button>
    </div>

    <!-- Chat panel -->
    <div class="chat-container" id="chatPanel">
      <div class="message assistant">
        <div class="message-content">Hello! I'm TaskingBot, your AI assistant. How can I help you today?</div>
      </div>
    </div>

    <!-- Cmdlist modal -->
    <div class="cmdlist-overlay" id="cmdlistOverlay">
      <div class="cmdlist-modal" id="cmdlistModal"></div>
    </div>

    <!-- Input area -->
    <div class="input-container" id="inputContainer">
      <div class="slash-dropdown" id="slashDropdown"></div>
      <div class="input-wrapper">
        <textarea class="message-input" id="messageInput" placeholder="Type a message or / for commands..." rows="1"></textarea>
        <div class="input-actions">
          <button class="sidebar-action-btn" id="sidebar-mic" title="Voice input">
            <svg viewBox="0 0 24 24" fill="none" stroke="#FF6B00" stroke-width="2"><path d="M12 15a4 4 0 0 0 4-4V7a4 4 0 0 0-8 0v4a4 4 0 0 0 4 4z"/><path d="M19 11a7 7 0 0 1-14 0"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="8" y1="22" x2="16" y2="22"/></svg>
          </button>
          <button class="sidebar-action-btn" id="sidebar-new-chat" title="New chat">
            <svg viewBox="0 0 24 24" fill="none" stroke="#FF6B00" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/><line x1="9" y1="10" x2="15" y2="10"/><line x1="12" y1="7" x2="12" y2="13"/></svg>
          </button>
          <button class="sidebar-action-btn" id="sidebar-stop" title="Stop">
            <svg viewBox="0 0 24 24" fill="none" stroke="#FF6B00" stroke-width="2"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>
          </button>
          <button class="send-button" id="sendButton" aria-label="Send">
            <svg viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
          </button>
          <button class="sidebar-action-btn" id="sidebar-screenshot" title="Take Screenshot">
            <svg viewBox="0 0 24 24" fill="none" stroke="#FF6B00" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
          </button>
          <button class="sidebar-action-btn" id="sidebar-attach" title="Attach File">
            <svg viewBox="0 0 24 24" fill="none" stroke="#FF6B00" stroke-width="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
          </button>
        </div>
      </div>
    </div>

    <!-- Assets panel -->
    <div id="assetsPanel" style="display:none;padding:24px 12px;min-height:120px;height:100%;overflow-y:auto;color:var(--text);background:var(--surface);">
      <div style="font-size:1.1em;font-weight:600;margin-bottom:12px;">Your Assets</div>
      <div style="font-size:12px;color:var(--text-muted);margin-bottom:12px;">Generated images, files, and other media from this chat appear here.</div>
      <div id="assetsList" style="display:grid;grid-template-columns:repeat(2,1fr);gap:8px;"></div>
    </div>

    <!-- Desktop Agent panel -->
    <div class="desktop-panel" id="desktopPanel">
      <div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;">
        <svg viewBox="0 0 24 24" fill="none" stroke="#FF6B00" stroke-width="2" style="width:24px;height:24px;flex-shrink:0;">
          <rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8"/><path d="M12 17v4"/>
        </svg>
        <div style="font-size:1.1em;font-weight:600;">Desktop Agent</div>
        <div id="desktopStatusDot" style="width:8px;height:8px;border-radius:50%;background:#23c552;margin-left:auto;flex-shrink:0;" title="Running"></div>
      </div>
      <div style="font-size:12px;color:var(--text-subtle);margin-bottom:16px;line-height:1.5;">
        Quantum desktop agent active. Full computer control — mouse, keyboard, apps, files, screenshots. Already mastered every OS and every workflow.
      </div>
      <div id="desktopConnected" style="padding:12px;border-radius:10px;background:rgba(35,197,82,0.08);border:1px solid rgba(35,197,82,0.2);margin-bottom:16px;">
        <div style="display:flex;align-items:center;gap:8px;font-size:13px;font-weight:600;color:#23c552;">
          <span>Agent Running</span>
        </div>
        <div id="desktopOsInfo" style="font-size:11px;color:var(--text-subtle);margin-top:4px;"></div>
      </div>
      <div id="desktopLog" style="font-size:11px;color:var(--text-subtle);max-height:300px;overflow-y:auto;"></div>
    </div>

    <!-- Login overlay -->
    <div class="login-overlay" id="loginOverlay">
      <img src="data:image/png;base64,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" alt="TaskingBot Logo" style="width:56px;height:56px;border-radius:10px;margin-bottom:16px;" />
      <h2>TaskingBot AI</h2>
      <p>Sign in with your tasking.tech account</p>
      <div class="login-form" id="loginForm">
        <input type="email" id="loginEmail" placeholder="Email" autocomplete="email" />
        <input type="password" id="loginPassword" placeholder="Password" autocomplete="current-password" />
        <button type="button" id="loginSubmitBtn">Sign In</button>
        <div class="login-status" id="loginStatus"></div>
        <div style="display:flex;align-items:center;gap:8px;margin:8px 0 4px;">
          <div style="flex:1;height:1px;background:var(--border);"></div>
          <span style="font-size:10px;color:var(--text-subtle);text-transform:uppercase;">or continue with</span>
          <div style="flex:1;height:1px;background:var(--border);"></div>
        </div>
        <button type="button" class="oauth-btn" id="loginGoogleBtn"><svg viewBox="0 0 24 24" width="16" height="16"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg> Google</button>
        <button type="button" class="oauth-btn" id="loginGithubBtn"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg> GitHub</button>
        <button type="button" class="oauth-btn" id="loginMicrosoftBtn"><svg viewBox="0 0 24 24" width="16" height="16"><path fill="#F25022" d="M1 1h10v10H1z"/><path fill="#00A4EF" d="M1 13h10v10H1z"/><path fill="#7FBA00" d="M13 1h10v10H13z"/><path fill="#FFB900" d="M13 13h10v10H13z"/></svg> Microsoft</button>
        <button type="button" class="oauth-btn" id="loginAuth0Btn"><svg viewBox="0 0 256 292" width="16" height="16"><path fill="#EB5424" d="M175.7 2.6H80.3L0 248.2l80.3 43.4 47.7-145.3 47.7 145.3 80.3-43.4L175.7 2.6zM128 146.3L80.3 2.6h95.4L128 146.3z"/></svg> Auth0</button>
        <button type="button" class="oauth-btn" id="loginOktaBtn"><svg viewBox="0 0 431 431" width="16" height="16"><path fill="#007DC1" d="M215.5 0C96.5 0 0 96.5 0 215.5S96.5 431 215.5 431 431 334.5 431 215.5 334.5 0 215.5 0zm0 312.6c-53.7 0-97.1-43.5-97.1-97.1 0-53.7 43.5-97.1 97.1-97.1 53.7 0 97.1 43.5 97.1 97.1 0 53.6-43.4 97.1-97.1 97.1z"/></svg> Okta</button>
        <a class="login-link" id="loginSignupLink">Don't have an account? Sign up at tasking.tech</a>
      </div>
    </div>

    <!-- Hidden file input for attachments -->
    <input type="file" id="fileInput" style="display:none;" accept="image/*,.pdf,.txt,.csv,.json,.doc,.docx" />
  </div>

<script>
'use strict';

// ── Error overlay for debugging ────────────────────────────────────────
window.onerror = function(msg, url, line, col, err) {
  var d = document.createElement('div');
  d.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:99999;background:red;color:white;padding:8px;font-size:11px;white-space:pre-wrap;';
  d.textContent = 'JS ERROR: ' + msg + '\nLine: ' + line + ' Col: ' + col + '\n' + (err && err.stack || '');
  document.body.appendChild(d);
};

// ── LocalStorage polyfill (WebKit may block it) ───────────────────────
var storage = (function() {
  try { storage.setItem('__test', '1'); storage.removeItem('__test'); return localStorage; }
  catch(e) {
    var mem = {};
    return {
      getItem: function(k) { return mem[k] || null; },
      setItem: function(k, v) { mem[k] = String(v); },
      removeItem: function(k) { delete mem[k]; }
    };
  }
})();

// ── Constants ──────────────────────────────────────────────────────────
const TASKING_ORIGIN = 'https://tasking.tech';
const CHAT_API = TASKING_ORIGIN + '/_api/taskingbot/chat';
const AUTH_SESSION_API = TASKING_ORIGIN + '/_api/auth/session';
const AUTH_LOGIN_API = TASKING_ORIGIN + '/_api/auth/login_with_password';
const USER_PROFILE_API = TASKING_ORIGIN + '/_api/user/profile';
const CHAT_HISTORY_API = TASKING_ORIGIN + '/_api/taskingbot/chat-history';
const CHAT_HISTORY_LIMIT = 30;
const MAX_HISTORY_ENTRY_LENGTH = 2000;

// ── State ──────────────────────────────────────────────────────────────
let conversationHistory = [];
let isSending = false;
let stopRequested = false;
let currentUserId = storage.getItem('tt_userId') || null;
let authToken = storage.getItem('tt_authToken') || null;
let currentUserAvatar = null;
let currentTaskId = null;
let currentConversationId = null;
let lastUserAttachment = null;
let assets = [];

// ── DOM refs ───────────────────────────────────────────────────────────
const chatPanel = document.getElementById('chatPanel');
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendButton');
const micBtn = document.getElementById('sidebar-mic');
const stopBtn = document.getElementById('sidebar-stop');
const screenshotBtn = document.getElementById('sidebar-screenshot');
const attachBtn = document.getElementById('sidebar-attach');
const newChatBtn = document.getElementById('sidebar-new-chat');
const headerLoginBtn = document.getElementById('headerLoginBtn');
const headerLogoutBtn = document.getElementById('headerLogoutBtn');
const themeToggleBtn = document.getElementById('themeToggle');
const headerAvatar = document.getElementById('headerAvatar');
const headerAvatarWrap = document.getElementById('headerAvatarWrap');
const loginOverlay = document.getElementById('loginOverlay');
const loginEmail = document.getElementById('loginEmail');
const loginPassword = document.getElementById('loginPassword');
const loginSubmitBtn = document.getElementById('loginSubmitBtn');
const loginStatus = document.getElementById('loginStatus');
const loginSignupLink = document.getElementById('loginSignupLink');
const fileInput = document.getElementById('fileInput');
const assetsList = document.getElementById('assetsList');
const desktopOsInfo = document.getElementById('desktopOsInfo');
const desktopLog = document.getElementById('desktopLog');

const slashDropdown = document.getElementById('slashDropdown');
const cmdlistOverlay = document.getElementById('cmdlistOverlay');
const cmdlistModal = document.getElementById('cmdlistModal');

// ── Slash Commands ────────────────────────────────────────────────────
const SLASH_COMMANDS = [
  { cmd: '/image', args: '<prompt>', desc: 'Generate an AI image' },
  { cmd: '/video', args: '<prompt>', desc: 'Generate an AI video clip' },
  { cmd: '/video-anime', args: '<prompt>', desc: 'Generate anime-style video' },
  { cmd: '/video-cartoon', args: '<prompt>', desc: 'Generate cartoon-style video' },
  { cmd: '/video-cinematic', args: '<prompt>', desc: 'Generate cinematic video' },
  { cmd: '/video-promo', args: '<prompt>', desc: 'Generate promo video' },
  { cmd: '/video-commercial', args: '<prompt>', desc: 'Generate commercial video' },
  { cmd: '/sound', args: '<prompt>', desc: 'Generate sound/music clip' },
  { cmd: '/voice', args: '<text>', desc: 'Generate voice audio' },
  { cmd: '/musicvideo', args: '<concept>', desc: 'Generate music video concept' },
  { cmd: '/task', args: '<description>', desc: 'Create a task' },
  { cmd: '/plan', args: '<goal>', desc: 'Create an interactive plan' },
  { cmd: '/brainstorm', args: '<idea>', desc: 'Research and brainstorm' },
  { cmd: '/review', args: '[file]', desc: 'Review code' },
  { cmd: '/debug', args: '<issue>', desc: 'Debug a problem' },
  { cmd: '/fix', args: '<error>', desc: 'Fix errors' },
  { cmd: '/refactor', args: '<file>', desc: 'Refactor code' },
  { cmd: '/test', args: '<scope>', desc: 'Generate/run tests' },
  { cmd: '/website', args: '<desc>', desc: 'Generate a website' },
  { cmd: '/webapp', args: '<desc>', desc: 'Generate a web app' },
  { cmd: '/sow', args: '<project>', desc: 'Generate Statement of Work PDF' },
  { cmd: '/proposal', args: '<project>', desc: 'Generate proposal PDF' },
  { cmd: '/report', args: '<topic>', desc: 'Generate report PDF' },
  { cmd: '/resume', args: '<info>', desc: 'Generate resume PDF' },
  { cmd: '/logo', args: '<brand>', desc: 'Generate logo designs' },
  { cmd: '/banner', args: '<text>', desc: 'Generate banner designs' },
  { cmd: '/3d', args: '<prompt>', desc: 'Generate 3D render' },
  { cmd: '/cartoon', args: '<concept>', desc: 'Generate cartoon concept art' },
  { cmd: '/comic', args: '<story>', desc: 'Generate comic pages' },
  { cmd: '/game', args: '<concept>', desc: 'Generate game design doc' },
  { cmd: '/memory', args: '<fact>', desc: 'Save to memory' },
  { cmd: '/get', args: '<url>', desc: 'Fetch URL content' },
  { cmd: '/http', args: '<url> <json>', desc: 'HTTP POST request' },
  { cmd: '/clear', args: '', desc: 'Clear chat' },
  { cmd: '/reset', args: '', desc: 'Reset session' },
  { cmd: '/cmdlist', args: '', desc: 'Show all commands' },
];

let slashSelectedIdx = 0;
let slashFiltered = [];

function updateSlashDropdown(val) {
  if (val.startsWith('/') && !val.includes(' ')) {
    slashFiltered = SLASH_COMMANDS.filter(c => c.cmd.startsWith(val));
    if (slashFiltered.length === 0) { slashDropdown.classList.remove('visible'); return; }
    slashSelectedIdx = 0;
    slashDropdown.innerHTML = slashFiltered.map((c, i) =>
      `<div class="slash-item ${i === 0 ? 'active' : ''}" data-idx="${i}">` +
      `<span class="cmd">${c.cmd}</span>` +
      (c.args ? `<span class="args">${c.args}</span>` : '') +
      `<span class="desc">${c.desc}</span></div>`
    ).join('');
    slashDropdown.classList.add('visible');
    slashDropdown.querySelectorAll('.slash-item').forEach(el => {
      el.addEventListener('click', () => {
        const idx = parseInt(el.dataset.idx);
        const item = slashFiltered[idx];
        messageInput.value = item.cmd + ' ';
        slashDropdown.classList.remove('visible');
        messageInput.focus();
      });
    });
  } else {
    slashDropdown.classList.remove('visible');
  }
}

function showCmdlist() {
  let html = '<h3>Available Commands</h3>';
  html += SLASH_COMMANDS.map(c =>
    `<div class="cmd-row"><span class="cmd-name">${c.cmd} ${c.args}</span><span class="cmd-desc">${c.desc}</span></div>`
  ).join('');
  cmdlistModal.innerHTML = html;
  cmdlistOverlay.classList.add('visible');
}

cmdlistOverlay.addEventListener('click', (e) => {
  if (e.target === cmdlistOverlay) cmdlistOverlay.classList.remove('visible');
});

// Tab elements
const tabBtns = document.querySelectorAll('.tab-btn');
const panels = {
  chat: { panel: chatPanel, input: document.getElementById('inputContainer') },
  assets: { panel: document.getElementById('assetsPanel') },
  desktop: { panel: document.getElementById('desktopPanel') },
};

// ── Theme ──────────────────────────────────────────────────────────────
function initTheme() {
  const saved = storage.getItem('tt_theme') || 'dark';
  document.documentElement.setAttribute('data-theme', saved);
}
initTheme();

themeToggleBtn.addEventListener('click', () => {
  const current = document.documentElement.getAttribute('data-theme');
  const next = current === 'dark' ? 'light' : 'dark';
  document.documentElement.setAttribute('data-theme', next);
  storage.setItem('tt_theme', next);
});

// ── Tabs ───────────────────────────────────────────────────────────────
function switchTab(tab) {
  tabBtns.forEach(btn => {
    btn.classList.toggle('active', btn.dataset.tab === tab);
  });
  Object.entries(panels).forEach(([key, val]) => {
    if (val.panel) val.panel.style.display = key === tab ? (key === 'chat' ? 'flex' : 'block') : 'none';
    if (val.input) val.input.style.display = key === tab ? 'block' : 'none';
  });
  // Show input only for chat
  const inputC = document.getElementById('inputContainer');
  if (inputC) inputC.style.display = tab === 'chat' ? 'block' : 'none';
}

tabBtns.forEach(btn => {
  btn.addEventListener('click', () => switchTab(btn.dataset.tab));
});

// ── Auth helpers ──────────────────────────────────────────────────────
function authHeaders(extra = {}) {
  const h = { ...extra };
  if (authToken) h['Authorization'] = 'Bearer ' + authToken;
  return h;
}

// Use Python bridge for non-streaming requests (bypasses CORS)
async function authFetch(url, opts = {}) {
  const api = window.pywebview && window.pywebview.api;
  if (api && api.api_fetch) {
    const method = (opts.method || 'GET').toUpperCase();
    const body = opts.body ? JSON.parse(opts.body) : null;
    const result = await api.api_fetch(url, method, body, authToken);
    // Return a fetch-like Response
    return {
      ok: result.ok,
      status: result.status || (result.ok ? 200 : 500),
      json: async () => JSON.parse(result.body || '{}'),
      text: async () => result.body || '',
    };
  }
  // Fallback to regular fetch
  opts.headers = authHeaders(opts.headers || {});
  return fetch(url, opts);
}

async function checkAuth() {
  if (!authToken) return false;
  try {
    const resp = await authFetch(AUTH_SESSION_API);
    if (!resp.ok) return false;
    const data = await resp.json();
    const payload = data?.json || data;
    if (payload?.user || payload?.userId || payload?.id) {
      const user = payload.user || payload;
      currentUserId = user.id || user.userId || user._id || null;
      if (currentUserId) storage.setItem('tt_userId', currentUserId);
      updateAuthUI(true, user);
      return true;
    }
    return false;
  } catch {
    return false;
  }
}

function updateAuthUI(loggedIn, user) {
  if (loggedIn) {
    headerLoginBtn.style.display = 'none';
    headerLogoutBtn.style.display = 'flex';
    headerAvatarWrap.className = 'header-avatar-wrap is-online';
    loginOverlay.classList.remove('is-open');
    if (user?.avatarUrl || user?.avatar || user?.image || user?.picture) {
      const url = user.avatarUrl || user.avatar || user.image || user.picture;
      headerAvatar.outerHTML = `<img class="header-avatar" id="headerAvatar" src="${url}" alt="Avatar" />`;
    }
  } else {
    headerLoginBtn.style.display = 'block';
    headerLogoutBtn.style.display = 'none';
    headerAvatarWrap.className = 'header-avatar-wrap is-offline';
  }
}

function showLogin() {
  loginOverlay.classList.add('is-open');
  loginEmail.focus();
}

function hideLogin() {
  loginOverlay.classList.remove('is-open');
  loginStatus.textContent = '';
}

headerLoginBtn.addEventListener('click', showLogin);

headerLogoutBtn.addEventListener('click', async () => {
  try {
    await authFetch(TASKING_ORIGIN + '/_api/auth/logout', { method: 'POST' });
  } catch {}
  currentUserId = null;
  authToken = null;
  storage.removeItem('tt_userId');
  storage.removeItem('tt_authToken');
  updateAuthUI(false);
  showLogin();
});

loginSubmitBtn.addEventListener('click', async () => {
  const email = loginEmail.value.trim();
  const password = loginPassword.value;
  if (!email || !password) { loginStatus.textContent = 'Please enter email and password.'; return; }

  loginStatus.textContent = 'Signing in...';
  loginSubmitBtn.disabled = true;

  try {
    const api = window.pywebview && window.pywebview.api;
    let data;
    if (api && api.login) {
      data = await api.login(email, password);
    } else {
      const resp = await fetch(AUTH_LOGIN_API, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password }),
      });
      data = await resp.json().catch(() => ({}));
      data.ok = resp.ok;
    }
    if (data.ok) {
      if (data.token) {
        authToken = data.token;
        storage.setItem('tt_authToken', authToken);
      }
      if (data.user) {
        currentUserId = data.user.id || data.user.userId || null;
        if (currentUserId) storage.setItem('tt_userId', currentUserId);
        updateAuthUI(true, data.user);
      }
      loginStatus.textContent = '';
      hideLogin();
      loadChatHistory();
    } else {
      loginStatus.textContent = data?.error || data?.message || 'Login failed. Check credentials.';
    }
  } catch (err) {
    loginStatus.textContent = 'Connection error. Please try again.';
  } finally {
    loginSubmitBtn.disabled = false;
  }
});

loginPassword.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') loginSubmitBtn.click();
});

loginSignupLink.addEventListener('click', () => {
  if (window.pywebview && window.pywebview.api) {
    window.pywebview.api.open_external('https://tasking.tech/register');
  } else {
    window.open('https://tasking.tech/register', '_blank');
  }
});

// OAuth buttons — open tasking.tech/login in system browser
function openOAuth(provider) {
  loginStatus.textContent = 'Opening ' + provider + ' login in browser...';
  const api = window.pywebview && window.pywebview.api;
  if (api) {
    api.open_external('https://tasking.tech/login');
  } else {
    window.open('https://tasking.tech/login', '_blank');
  }
  loginStatus.textContent = 'Complete login in your browser, then paste your session token here.';
}
document.getElementById('loginGoogleBtn').addEventListener('click', () => openOAuth('Google'));
document.getElementById('loginGithubBtn').addEventListener('click', () => openOAuth('GitHub'));
document.getElementById('loginMicrosoftBtn').addEventListener('click', () => openOAuth('Microsoft'));
document.getElementById('loginAuth0Btn').addEventListener('click', () => openOAuth('Auth0'));
document.getElementById('loginOktaBtn').addEventListener('click', () => openOAuth('Okta'));

// ── Chat History ───────────────────────────────────────────────────────
async function loadChatHistory() {
  if (!currentUserId) return;
  try {
    const resp = await authFetch(CHAT_HISTORY_API + '?limit=' + CHAT_HISTORY_LIMIT, {
      headers: { 'Accept': 'application/json' },
    });
    if (!resp.ok) return;
    const raw = await resp.json();
    const payload = raw?.json || raw;
    const messages = Array.isArray(payload?.messages) ? payload.messages : [];
    if (messages.length > 0) {
      conversationHistory = messages.map(m => ({ role: m.role || 'assistant', content: m.content || '' }));
      // Show last few in UI
      chatPanel.innerHTML = '';
      const recent = messages.slice(-20);
      for (const msg of recent) {
        addMessageToUI(msg.content || '', msg.role === 'user');
      }
      scrollToBottom();
    }
  } catch {}
}

// ── Message Rendering ──────────────────────────────────────────────────
function escapeHtml(text) {
  const d = document.createElement('div');
  d.textContent = text;
  return d.innerHTML;
}

function renderMarkdown(text) {
  if (!text) return '';
  let html = escapeHtml(text);

  // Code blocks
  html = html.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) => {
    return `<pre style="background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;overflow-x:auto;margin:4px 0;"><code>${code.trim()}</code></pre>`;
  });

  // Inline code
  html = html.replace(/`([^`]+)`/g, '<code style="background:var(--bg);padding:1px 4px;border-radius:3px;font-size:11px;">$1</code>');

  // Bold
  html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');

  // Italic
  html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');

  // Links
  html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" style="color:var(--accent);text-decoration:none;">$1</a>');

  // [media:image] tokens
  html = html.replace(/\[media:image\]\s*(https?:\/\/[^\s<]+)/g, (_, url) => {
    return `<div class="media-gallery"><div class="media-card"><div class="media-preview-wrap"><img class="media-preview" src="${url}" alt="Generated image" onerror="this.style.display='none'" /></div></div></div>`;
  });

  // Resolve /_api/ relative image URLs
  html = html.replace(/\[media:image\]\s*(\/[^\s<]+)/g, (_, path) => {
    const url = TASKING_ORIGIN + path;
    return `<div class="media-gallery"><div class="media-card"><div class="media-preview-wrap"><img class="media-preview" src="${url}" alt="Generated image" onerror="this.style.display='none'" /></div></div></div>`;
  });

  return html;
}

function renderActionLog(text) {
  // Parse [ACTIONS]...[/ACTIONS] and [ACTION_REPORT]...[/ACTION_REPORT] blocks
  let html = text;
  html = html.replace(/\[ACTIONS\]([\s\S]*?)\[\/ACTIONS\]/g, (_, content) => {
    try {
      const data = JSON.parse(content);
      const actions = data.actions || [];
      if (!actions.length) return '';
      const items = actions.map(a =>
        `<div class="action-log-item"><span class="action-log-label">${escapeHtml(a.type || a.action || '?')}</span> ${escapeHtml(JSON.stringify(a.params || a.args || {}).slice(0, 80))}</div>`
      ).join('');
      return `<div class="action-log"><div style="font-weight:600;margin-bottom:2px;color:var(--accent);">Actions</div>${items}</div>`;
    } catch { return ''; }
  });
  html = html.replace(/\[ACTION_REPORT\]([\s\S]*?)\[\/ACTION_REPORT\]/g, (_, content) => {
    return `<div class="action-log"><div style="font-weight:600;margin-bottom:2px;color:#23c552;">Result</div><div class="action-log-item">${escapeHtml(content.trim().slice(0, 200))}</div></div>`;
  });
  return html;
}

function addMessageToUI(text, isUser, extraHtml) {
  const msg = document.createElement('div');
  msg.className = `message ${isUser ? 'user' : 'assistant'}`;

  const content = document.createElement('div');
  content.className = 'message-content';

  if (isUser) {
    content.textContent = text;
  } else {
    // Render markdown + media + action logs
    let rendered = renderMarkdown(text);
    rendered = renderActionLog(rendered);
    content.innerHTML = rendered;
    if (extraHtml) content.innerHTML += extraHtml;

    // Collect media assets
    collectAssets(text);
  }

  msg.appendChild(content);

  // Copy button for assistant messages
  if (!isUser) {
    const actions = document.createElement('div');
    actions.className = 'message-actions';
    const copyBtn = document.createElement('button');
    copyBtn.className = 'message-action-btn';
    copyBtn.title = 'Copy';
    copyBtn.innerHTML = '&#x2398;';
    copyBtn.addEventListener('click', () => {
      navigator.clipboard.writeText(text).then(() => {
        copyBtn.innerHTML = '&#x2713;';
        setTimeout(() => { copyBtn.innerHTML = '&#x2398;'; }, 1500);
      });
    });
    actions.appendChild(copyBtn);
    content.appendChild(actions);
  }

  chatPanel.appendChild(msg);
  scrollToBottom();
  return content;
}

function scrollToBottom() {
  requestAnimationFrame(() => {
    chatPanel.scrollTop = chatPanel.scrollHeight;
  });
}

function showTyping() {
  let indicator = document.getElementById('typingIndicator');
  if (indicator) return;
  indicator = document.createElement('div');
  indicator.id = 'typingIndicator';
  indicator.className = 'typing-indicator';
  indicator.innerHTML = '<div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div>';
  chatPanel.appendChild(indicator);
  scrollToBottom();
}

function hideTyping() {
  const indicator = document.getElementById('typingIndicator');
  if (indicator) indicator.remove();
}

// ── Asset Collection ───────────────────────────────────────────────────
function collectAssets(text) {
  const imgRegex = /\[media:image\]\s*((?:https?:\/\/|\/)[^\s<]+)/g;
  let match;
  while ((match = imgRegex.exec(text)) !== null) {
    let url = match[1];
    if (url.startsWith('/')) url = TASKING_ORIGIN + url;
    if (!assets.find(a => a.url === url)) {
      assets.push({ type: 'image', url, timestamp: Date.now() });
      renderAssets();
    }
  }
}

function renderAssets() {
  if (!assetsList) return;
  assetsList.innerHTML = '';
  if (assets.length === 0) {
    assetsList.innerHTML = '<div style="grid-column:1/-1;font-size:12px;color:var(--text-subtle);">No assets yet.</div>';
    return;
  }
  for (const asset of assets) {
    const card = document.createElement('div');
    card.style.cssText = 'border:1px solid var(--border);border-radius:8px;padding:4px;background:var(--surface-2);';
    if (asset.type === 'image') {
      card.innerHTML = `<img src="${escapeHtml(asset.url)}" style="width:100%;border-radius:6px;object-fit:cover;max-height:120px;" onerror="this.style.display='none'" />`;
    }
    assetsList.appendChild(card);
  }
}

// ── Chat Send ──────────────────────────────────────────────────────────
async function sendMessage(text, attachment) {
  if (isSending || !text.trim()) return;

  // Handle commands
  const trimmed = text.trim();
  if (trimmed === '/clear') {
    chatPanel.innerHTML = '';
    conversationHistory = [];
    addMessageToUI('Chat cleared.', false);
    return;
  }
  if (trimmed === '/reset') {
    conversationHistory = [];
    currentTaskId = null;
    currentConversationId = null;
    addMessageToUI('Session reset.', false);
    return;
  }
  if (trimmed === '/cmdlist' || trimmed === '/commands' || trimmed === '/help') {
    messageInput.value = '';
    showCmdlist();
    return;
  }

  isSending = true;
  stopRequested = false;
  addMessageToUI(text, true);
  messageInput.value = '';
  showTyping();

  const history = conversationHistory.slice(-CHAT_HISTORY_LIMIT * 2).map(h => ({
    role: h.role,
    content: h.content.length > MAX_HISTORY_ENTRY_LENGTH ? h.content.slice(0, MAX_HISTORY_ENTRY_LENGTH) + '...' : h.content,
  }));

  const payload = {
    message: text,
    history,
    source: 'taskingbot_desktop',
    channel: 'desktop',
    bridge: false,
    taskingbot: true,
    request_type: 'task',
    desktop_agent: true,
    force_desktop: true,
    save_history: true,
    persist: true,
    persist_task: true,
    create_task: true,
    max_rounds: 32,
    ...(currentTaskId ? { task_id: currentTaskId } : {}),
    ...(currentConversationId ? { conversation_id: currentConversationId } : {}),
    ...(currentTaskId || currentConversationId ? { continue_task: true } : {}),
    ...(attachment ? { attachment } : {}),
  };

  try {
    const api = window.pywebview && window.pywebview.api;
    let responseText = '';

    if (api && api.chat_send) {
      // Use Python bridge to bypass CORS (no streaming, full response)
      const result = await api.chat_send(CHAT_API, payload, authToken);
      hideTyping();

      if (!result.ok) {
        if (result.status === 401) {
          addMessageToUI('Please log in to continue.', false);
          showLogin();
          isSending = false;
          return;
        }
        const errData = JSON.parse(result.body || '{}');
        addMessageToUI('Error: ' + (errData.error || errData.message || 'Server error'), false);
        isSending = false;
        return;
      }

      responseText = result.body || '';
    } else {
      // Fallback: direct fetch
      const chatHeaders = { 'Content-Type': 'application/json', 'Accept': 'application/json' };
      if (authToken) chatHeaders['Authorization'] = 'Bearer ' + authToken;
      const resp = await fetch(CHAT_API, {
        method: 'POST',
        headers: chatHeaders,
        body: JSON.stringify(payload),
      });
      hideTyping();

      if (resp.status === 401) {
        addMessageToUI('Please log in to continue.', false);
        showLogin();
        isSending = false;
        return;
      }
      if (!resp.ok) {
        const errData = await resp.json().catch(() => ({}));
        addMessageToUI('Error: ' + (errData.error || errData.message || 'Server error'), false);
        isSending = false;
        return;
      }
      responseText = await resp.text();
    }

    // Parse response — may be ndjson (streaming) or plain JSON
    let reply = '';
    let taskId = null;
    let convId = null;

    // Try parsing as ndjson (line-delimited JSON chunks)
    const lines = responseText.split('\n').filter(l => l.trim());
    for (const line of lines) {
      try {
        const chunk = JSON.parse(line);
        if (chunk.type === 'text' || chunk.type === 'content') {
          reply += chunk.content || chunk.text || '';
        } else if (chunk.type === 'done' || chunk.type === 'complete') {
          if (chunk.taskId || chunk.task_id) taskId = chunk.taskId || chunk.task_id;
          if (chunk.conversationId || chunk.conversation_id) convId = chunk.conversationId || chunk.conversation_id;
        } else if (chunk.response || chunk.message || chunk.content) {
          reply = chunk.response || chunk.message || chunk.content || reply;
          if (chunk.taskId || chunk.task_id) taskId = chunk.taskId || chunk.task_id;
          if (chunk.conversationId || chunk.conversation_id) convId = chunk.conversationId || chunk.conversation_id;
        }
      } catch {
        // Not JSON, treat as plain text
        if (!reply) reply += line;
      }
    }

    if (!reply) reply = responseText.slice(0, 5000) || 'No response received.';

    if (taskId) currentTaskId = taskId;
    if (convId) currentConversationId = convId;

    addMessageToUI(reply, false);
    conversationHistory.push({ role: 'user', content: text });
    conversationHistory.push({ role: 'assistant', content: reply });

  } catch (err) {
    hideTyping();
    addMessageToUI('Connection error: ' + err.message, false);
  } finally {
    isSending = false;
    lastUserAttachment = null;
  }
}

async function handleStreamResponse(resp, userText) {
  const reader = resp.body.getReader();
  const decoder = new TextDecoder();
  let fullText = '';
  let contentEl = null;
  let buffer = '';

  try {
    while (true) {
      if (stopRequested) break;
      const { done, value } = await reader.read();
      if (done) break;

      buffer += decoder.decode(value, { stream: true });
      const lines = buffer.split('\n');
      buffer = lines.pop() || '';

      for (const line of lines) {
        if (stopRequested) break;
        const trimmed = line.trim();
        if (!trimmed) continue;

        // SSE format: data: ...
        let jsonStr = trimmed;
        if (trimmed.startsWith('data: ')) {
          jsonStr = trimmed.slice(6);
        }
        if (jsonStr === '[DONE]') continue;

        try {
          const chunk = JSON.parse(jsonStr);
          const token = chunk.token || chunk.content || chunk.text || chunk.delta?.content || '';
          const taskId = chunk.taskId || chunk.task_id;
          const convId = chunk.conversationId || chunk.conversation_id;
          if (taskId) currentTaskId = taskId;
          if (convId) currentConversationId = convId;

          if (token) {
            fullText += token;
            if (!contentEl) {
              contentEl = addMessageToUI(fullText, false);
            } else {
              let rendered = renderMarkdown(fullText);
              rendered = renderActionLog(rendered);
              contentEl.innerHTML = rendered;
            }
            scrollToBottom();
          }

          // Handle complete response in chunk
          if (chunk.response && !token) {
            fullText = chunk.response;
            if (!contentEl) {
              contentEl = addMessageToUI(fullText, false);
            } else {
              let rendered = renderMarkdown(fullText);
              rendered = renderActionLog(rendered);
              contentEl.innerHTML = rendered;
            }
          }
        } catch {
          // Non-JSON line, could be raw text
          if (trimmed && !trimmed.startsWith(':')) {
            fullText += trimmed + '\n';
            if (!contentEl) {
              contentEl = addMessageToUI(fullText, false);
            } else {
              let rendered = renderMarkdown(fullText);
              rendered = renderActionLog(rendered);
              contentEl.innerHTML = rendered;
            }
          }
        }
      }
    }
  } catch (err) {
    if (!stopRequested) {
      fullText += '\n[Stream error: ' + err.message + ']';
    }
  }

  if (fullText) {
    conversationHistory.push({ role: 'user', content: userText });
    conversationHistory.push({ role: 'assistant', content: fullText });
    collectAssets(fullText);
  }

  if (!contentEl && fullText) {
    addMessageToUI(fullText, false);
  }
}

// ── Event Listeners ────────────────────────────────────────────────────
sendBtn.addEventListener('click', () => {
  const text = messageInput.value.trim();
  if (text) sendMessage(text, lastUserAttachment);
});

// ── Input history (up/down arrows) ─────────────────────────────────────
let inputHistory = [];
let historyIndex = -1;
let savedInput = '';

messageInput.addEventListener('keydown', (e) => {
  // Slash dropdown keyboard nav
  if (slashDropdown.classList.contains('visible') && slashFiltered.length > 0) {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      slashSelectedIdx = Math.min(slashSelectedIdx + 1, slashFiltered.length - 1);
      slashDropdown.querySelectorAll('.slash-item').forEach((el, i) => el.classList.toggle('active', i === slashSelectedIdx));
      slashDropdown.querySelector('.slash-item.active')?.scrollIntoView({ block: 'nearest' });
      return;
    }
    if (e.key === 'ArrowUp') {
      e.preventDefault();
      slashSelectedIdx = Math.max(slashSelectedIdx - 1, 0);
      slashDropdown.querySelectorAll('.slash-item').forEach((el, i) => el.classList.toggle('active', i === slashSelectedIdx));
      slashDropdown.querySelector('.slash-item.active')?.scrollIntoView({ block: 'nearest' });
      return;
    }
    if (e.key === 'Tab' || (e.key === 'Enter' && !e.shiftKey)) {
      e.preventDefault();
      const item = slashFiltered[slashSelectedIdx];
      if (item) {
        messageInput.value = item.cmd + (item.args ? ' ' : '');
        slashDropdown.classList.remove('visible');
      }
      return;
    }
    if (e.key === 'Escape') {
      slashDropdown.classList.remove('visible');
      return;
    }
  }

  if (e.key === 'Enter' && !e.shiftKey) {
    e.preventDefault();
    const text = messageInput.value.trim();
    if (text) {
      inputHistory.push(text);
      historyIndex = inputHistory.length;
      sendMessage(text, lastUserAttachment);
    }
  } else if (e.key === 'ArrowUp' && messageInput.value === '') {
    e.preventDefault();
    if (inputHistory.length === 0) return;
    if (historyIndex === inputHistory.length) savedInput = messageInput.value;
    if (historyIndex > 0) {
      historyIndex--;
      messageInput.value = inputHistory[historyIndex];
    }
  } else if (e.key === 'ArrowDown' && historyIndex < inputHistory.length) {
    e.preventDefault();
    historyIndex++;
    if (historyIndex === inputHistory.length) {
      messageInput.value = savedInput;
    } else {
      messageInput.value = inputHistory[historyIndex];
    }
  }
});

// Auto-resize textarea + slash autocomplete
messageInput.addEventListener('input', () => {
  messageInput.style.height = 'auto';
  messageInput.style.height = Math.min(messageInput.scrollHeight, 140) + 'px';
  updateSlashDropdown(messageInput.value.trim());
});

// Stop button
stopBtn.addEventListener('click', () => {
  stopRequested = true;
  hideTyping();
});

// New chat
newChatBtn.addEventListener('click', () => {
  chatPanel.innerHTML = '';
  conversationHistory = [];
  currentTaskId = null;
  currentConversationId = null;
  addMessageToUI("Hello! I'm TaskingBot, your AI assistant. How can I help you today?", false);
});

// Screenshot — calls the desktop agent's screenshot function
screenshotBtn.addEventListener('click', async () => {
  if (window.pywebview && window.pywebview.api) {
    try {
      const result = await window.pywebview.api.take_screenshot();
      if (result && result.ok) {
        lastUserAttachment = {
          type: 'image/png',
          name: 'screenshot.png',
          data: result.data, // base64
        };
        addMessageToUI('[Screenshot captured — will be sent with your next message]', false);
      } else {
        addMessageToUI('Screenshot failed: ' + (result?.error || 'unknown error'), false);
      }
    } catch (err) {
      addMessageToUI('Screenshot error: ' + err.message, false);
    }
  } else {
    addMessageToUI('Screenshot requires the desktop agent (pywebview bridge not available).', false);
  }
});

// Attach file
attachBtn.addEventListener('click', () => fileInput.click());

fileInput.addEventListener('change', (e) => {
  const file = e.target.files[0];
  if (!file) return;
  const reader = new FileReader();
  reader.onload = () => {
    const base64 = reader.result.split(',')[1];
    lastUserAttachment = {
      type: file.type,
      name: file.name,
      data: base64,
    };
    addMessageToUI(`[Attached: ${file.name}]`, false);
  };
  reader.readAsDataURL(file);
  fileInput.value = '';
});

// Voice input (Web Speech API)
let recognition = null;
micBtn.addEventListener('click', () => {
  if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
    addMessageToUI('Voice input is not supported in this environment.', false);
    return;
  }
  if (recognition) {
    recognition.stop();
    recognition = null;
    micBtn.classList.remove('is-listening');
    return;
  }
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
  recognition = new SpeechRecognition();
  recognition.continuous = false;
  recognition.interimResults = false;
  recognition.lang = 'en-US';
  micBtn.classList.add('is-listening');
  recognition.onresult = (event) => {
    const transcript = event.results[0][0].transcript;
    messageInput.value += transcript;
    micBtn.classList.remove('is-listening');
    recognition = null;
  };
  recognition.onerror = () => {
    micBtn.classList.remove('is-listening');
    recognition = null;
  };
  recognition.onend = () => {
    micBtn.classList.remove('is-listening');
    recognition = null;
  };
  recognition.start();
});

// ── Desktop Agent Status ───────────────────────────────────────────────
function updateDesktopStatus(info) {
  if (desktopOsInfo && info) {
    desktopOsInfo.textContent = `${info.platform || ''} ${info.release || ''} (${info.machine || ''})`;
  }
}

function addDesktopLog(msg) {
  if (!desktopLog) return;
  const entry = document.createElement('div');
  entry.style.cssText = 'padding:2px 0;border-bottom:1px solid var(--border);';
  const time = new Date().toLocaleTimeString();
  entry.textContent = `[${time}] ${msg}`;
  desktopLog.appendChild(entry);
  desktopLog.scrollTop = desktopLog.scrollHeight;
  // Keep max 100 entries
  while (desktopLog.children.length > 100) desktopLog.removeChild(desktopLog.firstChild);
}

// ── Pywebview Bridge ───────────────────────────────────────────────────
// These are called from Python via window.evaluate_js or exposed via the API
window.desktopAgentStatus = function(msg) {
  addDesktopLog(msg);
};

window.desktopAgentOsInfo = function(info) {
  updateDesktopStatus(info);
};

// ── Init ───────────────────────────────────────────────────────────────
async function init() {
  const api = window.pywebview && window.pywebview.api;

  // Try auto-login using desktop API key first
  if (api && api.auto_login && !authToken) {
    try {
      const result = await api.auto_login();
      if (result && result.ok && result.token) {
        authToken = result.token;
        storage.setItem('tt_authToken', authToken);
        if (result.user) {
          currentUserId = result.user.id || null;
          if (currentUserId) storage.setItem('tt_userId', currentUserId);
          updateAuthUI(true, result.user);
        }
      }
    } catch (e) {
      console.log('[tt-desktop] Auto-login failed:', e);
    }
  }

  if (!authToken) {
    const loggedIn = await checkAuth();
    if (!loggedIn) {
      showLogin();
    } else {
      loadChatHistory();
    }
  } else {
    loadChatHistory();
  }

  // Get OS info from pywebview bridge
  if (api) {
    try {
      const info = await api.get_os_info();
      updateDesktopStatus(info);
    } catch {}
  }

  renderAssets();
}

// Wait for pywebview to be ready, or start immediately for browser testing
if (window.pywebview) {
  init();
} else {
  window.addEventListener('pywebviewready', init);
  // Fallback: if pywebview never fires, init after a short delay
  setTimeout(() => {
    if (!currentUserId) init();
  }, 1000);
}

// Debug: confirm JS ran
console.log('[tt-desktop] JS loaded, event listeners attached');
document.title = 'TaskingBot AI - JS OK';
</script>
</body>
</html>
'''
