Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Dashboard - autonomy-labs</title> | |
| <script src="https://cdn.tailwindcss.com?plugins=typography"></script> | |
| <script src="/static/vendor/supabase-loader.js" defer></script> | |
| <link href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" rel="stylesheet"> | |
| <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.6/dist/purify.min.js"></script> | |
| <!-- KaTeX --> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css"> | |
| <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script> | |
| <!-- Highlight.js --> | |
| <link rel="stylesheet" | |
| href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/tokyo-night-dark.min.css"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> | |
| <link rel="stylesheet" href="/static/theme.css"> | |
| <link rel="stylesheet" href="/static/dashboard.css"> | |
| </head> | |
| <body class="h-screen flex flex-col overflow-hidden"> | |
| <!-- Navbar --> | |
| <nav class="bg-gray-800 border-b border-gray-700 shrink-0"> | |
| <div class="px-4 py-3 flex items-center gap-3"> | |
| <!-- Left: brand + text nav --> | |
| <div class="text-xl font-bold text-blue-500 flex items-center gap-2 shrink-0"> | |
| <span class="md:hidden cursor-pointer text-gray-400" | |
| onclick="document.getElementById('mobile-menu').classList.toggle('hidden')">☰</span> | |
| autonomy-labs | |
| </div> | |
| <div class="hidden md:flex space-x-1 shrink-0"> | |
| <button onclick="switchMode('dashboard')" | |
| class="px-3 py-1 rounded hover:bg-gray-700 text-gray-300 transition" | |
| id="nav-dashboard">Dashboard</button> | |
| <button onclick="switchMode('chat')" | |
| class="px-3 py-1 rounded hover:bg-gray-700 text-gray-300 transition" id="nav-chat">Chat</button> | |
| <button onclick="switchMode('rooms')" | |
| class="px-3 py-1 rounded hover:bg-gray-700 text-gray-300 transition" id="nav-rooms">Rooms</button> | |
| <button onclick="switchMode('notes')" | |
| class="px-3 py-1 rounded hover:bg-gray-700 text-gray-300 transition" id="nav-notes">Notes</button> | |
| </div> | |
| <!-- Center: welcome --> | |
| <div class="hidden md:flex flex-1 justify-center min-w-0"> | |
| <div class="text-sm text-gray-400 truncate">Welcome to autonomy-labs</div> | |
| </div> | |
| <!-- Right: icons + user --> | |
| <div class="flex items-center gap-2 min-w-0 ml-auto"> | |
| <button onclick="switchMode('terminal')" | |
| class="p-2 rounded hover:bg-gray-700 text-gray-300 transition inline-flex items-center justify-center" | |
| id="nav-terminal" title="Terminal" aria-label="Terminal"> | |
| <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5h16v14H4V5z" /> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 9l3 3-3 3" /> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 15h6" /> | |
| </svg> | |
| </button> | |
| <button onclick="toggleTheme()" id="nav-theme" title="Toggle theme" aria-label="Toggle theme" | |
| class="p-2 rounded hover:bg-gray-700 text-gray-300 transition inline-flex items-center justify-center"> | |
| <span id="theme-icon">☾</span> | |
| </button> | |
| <button onclick="openAdmin()" id="nav-admin" title="Admin" aria-label="Admin" | |
| class="hidden p-2 rounded hover:bg-gray-700 text-gray-300 transition inline-flex items-center justify-center"> | |
| <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | |
| d="M12 3l8 4v6c0 5-3.5 9-8 9s-8-4-8-9V7l8-4z" /> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | |
| d="M9 12l2 2 4-5" /> | |
| </svg> | |
| </button> | |
| <button onclick="openSettings()" id="nav-settings" title="Settings" aria-label="Settings" | |
| class="p-2 rounded hover:bg-gray-700 text-gray-300 transition inline-flex items-center justify-center"> | |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | |
| d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37.608-.996.07-2.296-1.065-2.572-1.756-.426-1.756-2.924 0-3.35.996-.242 1.53-1.53 1.066-2.573-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | |
| d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> | |
| </svg> | |
| </button> | |
| <div id="user-badge" class="hidden md:flex flex-col items-end leading-tight min-w-0"> | |
| <div id="user-name" class="text-sm text-gray-200 font-medium truncate max-w-[20rem]"></div> | |
| <div id="user-email" class="text-xs text-gray-400 truncate max-w-[20rem]"></div> | |
| </div> | |
| <button id="logout-btn" | |
| class="px-3 py-1 bg-red-600 rounded hover:bg-red-700 text-sm font-medium transition">Logout</button> | |
| </div> | |
| </div> | |
| <!-- Mobile Menu --> | |
| <div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700 p-2 space-y-1"> | |
| <button onclick="switchMode('dashboard')" | |
| class="block w-full text-left px-3 py-2 rounded hover:bg-gray-700 text-gray-300">Dashboard</button> | |
| <button onclick="switchMode('chat')" | |
| class="block w-full text-left px-3 py-2 rounded hover:bg-gray-700 text-gray-300">Chat</button> | |
| <button onclick="switchMode('rooms')" id="mobile-rooms" | |
| class="block w-full text-left px-3 py-2 rounded hover:bg-gray-700 text-gray-300">Rooms</button> | |
| <button onclick="switchMode('notes')" | |
| class="block w-full text-left px-3 py-2 rounded hover:bg-gray-700 text-gray-300">Notes</button> | |
| <button onclick="switchMode('terminal')" | |
| class="block w-full text-left px-3 py-2 rounded hover:bg-gray-700 text-gray-300">Terminal</button> | |
| <button onclick="openAdmin()" id="mobile-admin" | |
| class="hidden block w-full text-left px-3 py-2 rounded hover:bg-gray-700 text-gray-300">Admin</button> | |
| </div> | |
| </nav> | |
| <!-- Main Content Area (Full Screen) --> | |
| <div class="flex-grow relative overflow-hidden"> | |
| <!-- Dashboard View --> | |
| <div id="dashboard-view" | |
| class="view-section w-full h-full flex flex-col items-center justify-start p-8 text-center overflow-y-auto"> | |
| <h1 | |
| class="text-4xl font-bold mb-4 bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent"> | |
| Welcome to autonomy-labs</h1> | |
| <p class="text-gray-400 max-w-md mx-auto mb-2">Your AI-powered development environment. Access the Chat for | |
| coding assistance or the Terminal for execution.</p> | |
| <p class="text-gray-500 text-sm max-w-md mx-auto mb-8">Build faster with chat + runnable terminals.</p> | |
| <div class="flex gap-4 mb-8"> | |
| <button onclick="switchMode('chat')" | |
| class="px-6 py-3 bg-blue-600 rounded-lg hover:bg-blue-700 font-semibold transition">Open | |
| Chat</button> | |
| <button onclick="switchMode('terminal')" | |
| class="px-6 py-3 bg-gray-700 rounded-lg hover:bg-gray-600 font-semibold transition">Open | |
| Terminal</button> | |
| </div> | |
| <div class="w-full max-w-3xl text-left bg-gray-800/60 border border-gray-700 rounded-2xl p-5 shadow-xl"> | |
| <div class="flex items-start justify-between gap-4"> | |
| <div> | |
| <h2 class="text-lg font-semibold text-gray-100">Getting started</h2> | |
| <p class="text-sm text-gray-400 mt-1">A quick checklist to get productive in under a minute.</p> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-3 mt-4"> | |
| <div class="p-3 rounded-xl bg-gray-900/40 border border-gray-700"> | |
| <div class="text-sm font-semibold text-gray-200">1) Configure provider</div> | |
| <div class="text-sm text-gray-400 mt-1">Set <code>DEFAULT_BASE_URL</code> + <code>DEFAULT_API_KEY</code> in deployment secrets. Use the Model box in chat to override.</div> | |
| </div> | |
| <div class="p-3 rounded-xl bg-gray-900/40 border border-gray-700"> | |
| <div class="text-sm font-semibold text-gray-200">2) Start chatting</div> | |
| <div class="text-sm text-gray-400 mt-1">Use Stop to cancel a response. Attach images or take a screenshot for multimodal prompts.</div> | |
| </div> | |
| <div class="p-3 rounded-xl bg-gray-900/40 border border-gray-700"> | |
| <div class="text-sm font-semibold text-gray-200">3) Run commands</div> | |
| <div class="text-sm text-gray-400 mt-1">Open Terminal and run shell commands; resize panes as needed.</div> | |
| </div> | |
| <div class="p-3 rounded-xl bg-gray-900/40 border border-gray-700"> | |
| <div class="text-sm font-semibold text-gray-200">4) Autonomous mode</div> | |
| <div class="text-sm text-gray-400 mt-1">Use “Run in Terminal” buttons on code blocks and resize chat/terminal split.</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Chat View --> | |
| <div id="chat-view" class="view-section hidden w-full h-full flex overflow-hidden"> | |
| <!-- Sidebar for History (Desktop) --> | |
| <div id="chat-sidebar" class="w-64 bg-gray-800 border-r border-gray-700 flex flex-col hidden md:flex shrink-0"> | |
| <div class="p-3 border-b border-gray-700 flex items-center gap-2"> | |
| <button onclick="startNewChat()" | |
| class="flex-grow bg-blue-600 hover:bg-blue-700 text-white py-2 rounded flex items-center justify-center gap-2 text-sm font-medium transition" | |
| title="New chat" aria-label="New chat"> | |
| <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v14M5 12h14" /> | |
| </svg> | |
| </button> | |
| <button onclick="toggleChatSidebar()" | |
| class="hidden md:inline-flex p-2 rounded hover:bg-gray-700 text-gray-300 transition" | |
| title="Collapse history" aria-label="Collapse history"> | |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | |
| d="M4 6h16M4 12h16M4 18h16"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| <div id="history-list" class="flex-grow overflow-y-auto p-2 space-y-1"></div> | |
| </div> | |
| <!-- Chat Area --> | |
| <div class="flex-grow flex flex-col h-full bg-gray-900 relative min-w-0"> | |
| <!-- Header --> | |
| <div | |
| class="bg-gray-800 p-3 border-b border-gray-700 flex justify-between items-center shadow-sm shrink-0"> | |
| <div class="flex items-center gap-3 overflow-hidden"> | |
| <button onclick="toggleChatSidebar()" | |
| class="text-gray-400 hover:text-white p-1 rounded hover:bg-gray-700 transition" | |
| title="Toggle history" aria-label="Toggle history"> | |
| <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | |
| d="M4 6h16M4 12h16M4 18h16"></path> | |
| </svg> | |
| </button> | |
| <h2 class="text-lg font-semibold truncate" id="current-chat-title">New Chat</h2> | |
| </div> | |
| <div class="flex items-center gap-2"> | |
| <div class="hidden md:flex bg-gray-700/60 rounded-lg p-1"> | |
| <button id="chat-mode-chat" onclick="setChatMode('chat')" | |
| class="px-2 py-1 text-xs rounded-md text-gray-200 hover:bg-white/10 transition">Chat</button> | |
| <button id="chat-mode-auto" onclick="setChatMode('autonomous')" | |
| class="px-2 py-1 text-xs rounded-md text-gray-200 hover:bg-white/10 transition">Autonomous</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Settings Drawer (Unified) --> | |
| <div id="settings-overlay" class="settings-overlay settings-hidden" onclick="closeSettings()"></div> | |
| <div id="settings-drawer" class="settings-drawer settings-hidden" role="dialog" aria-label="Settings"> | |
| <div class="p-4 border-b border-gray-700 flex items-center justify-between"> | |
| <div class="font-semibold text-gray-100">Settings</div> | |
| <button onclick="closeSettings()" class="text-gray-400 hover:text-white px-2 py-1 rounded hover:bg-white/10">×</button> | |
| </div> | |
| <div id="settings-content" class="p-4 space-y-4 overflow-y-auto" style="max-height: calc(100% - 56px);"> | |
| <div class="bg-gray-800/40 border border-gray-700 rounded-lg px-3 py-2"> | |
| <div class="text-sm text-gray-100">Chat provider</div> | |
| <div class="text-xs text-gray-400 mt-0.5">Configured via deployment secrets (<code>DEFAULT_BASE_URL</code>, <code>DEFAULT_API_KEY</code>, <code>DEFAULT_MODEL</code>).</div> | |
| <div class="text-xs text-gray-500 mt-1">Use the Model input in the chat bar to override per-message.</div> | |
| </div> | |
| <div class="pt-4 border-t border-gray-700"> | |
| <div class="flex items-center justify-between gap-2 mb-2"> | |
| <div class="font-semibold text-gray-200">Access</div> | |
| </div> | |
| <label class="flex items-center justify-between gap-3 bg-gray-800/40 border border-gray-700 rounded-lg px-3 py-2"> | |
| <div> | |
| <div class="text-sm text-gray-100">Enable Sign Up / Register</div> | |
| <div class="text-xs text-gray-400 mt-0.5">Default disabled. Affects the login page “Register” button.</div> | |
| </div> | |
| <input id="auth-allow-signup" type="checkbox" onchange="setAllowSignup(this.checked)" | |
| class="h-4 w-4 accent-blue-600"> | |
| </label> | |
| </div> | |
| <div class="pt-4 border-t border-gray-700"> | |
| <div class="flex items-center justify-between gap-2 mb-2"> | |
| <div class="font-semibold text-gray-200">Account</div> | |
| </div> | |
| <div class="bg-gray-900/30 border border-gray-700 rounded-lg p-3 space-y-3"> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-3"> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Display name</label> | |
| <input id="account-display-name" type="text" placeholder="Your name" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| <div class="text-xs text-gray-500 mt-1">Saved to Supabase user metadata.</div> | |
| </div> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Device name (Rooms)</label> | |
| <input id="account-device-name" type="text" placeholder="Laptop / Phone / ..." | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| <div class="text-xs text-gray-500 mt-1">Stored in this browser only.</div> | |
| </div> | |
| </div> | |
| <div class="flex flex-wrap items-center justify-between gap-2"> | |
| <div class="text-xs text-gray-400"> | |
| <span class="font-semibold text-gray-200">Device ID:</span> | |
| <span id="account-device-id" class="font-mono"></span> | |
| </div> | |
| <div class="flex gap-2"> | |
| <button onclick="saveAccountSettings()" | |
| class="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded text-xs">Save</button> | |
| <button onclick="regenerateDeviceId()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">New device</button> | |
| </div> | |
| </div> | |
| <div id="account-status" class="text-xs text-gray-500"></div> | |
| </div> | |
| </div> | |
| <div class="pt-4 border-t border-gray-700"> | |
| <div class="flex items-center justify-between gap-2 mb-2"> | |
| <div class="font-semibold text-gray-200">Codex SDK</div> | |
| <div class="flex gap-2"> | |
| <button onclick="resetCodexThread()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Reset Thread</button> | |
| <button onclick="refreshCodexMcpServers()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">List MCP</button> | |
| </div> | |
| </div> | |
| <div class="text-xs text-gray-400 mb-2"> | |
| Uses OpenAI Codex SDK locally (separate from chat). Device auth is available in the terminal: <code>codex login --device-auth</code>. | |
| </div> | |
| <div class="grid grid-cols-2 gap-3"> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Codex Base URL</label> | |
| <input type="text" id="codex-base-url" placeholder="https://api.openai.com/v1" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500" | |
| onchange="saveCodexSdkSettings()"> | |
| </div> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Codex API Key</label> | |
| <input type="password" id="codex-api-key" placeholder="sk-..." | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500" | |
| onchange="saveCodexSdkSettings()"> | |
| </div> | |
| </div> | |
| <div class="mt-3"> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Codex Model (optional)</label> | |
| <input type="text" id="codex-model" placeholder="(leave blank for default)" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500" | |
| onchange="saveCodexSdkSettings()"> | |
| <div class="text-xs text-gray-500 mt-1">Prevents chat-provider models (e.g. DeepSeek) from being sent to Codex.</div> | |
| </div> | |
| <div class="mt-3"> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Codex Sandbox</label> | |
| <select id="codex-sandbox-mode" onchange="saveCodexSandboxMode()" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| <option value="read-only">read-only</option> | |
| <option value="workspace-write">workspace-write</option> | |
| <option value="danger-full-access">danger-full-access</option> | |
| </select> | |
| <div class="text-xs text-gray-500 mt-1">Use <code>danger-full-access</code> only if you fully trust the model and this environment.</div> | |
| </div> | |
| <div class="mt-3"> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Codex Workspace Directory (optional)</label> | |
| <input type="text" id="codex-workdir" placeholder="/data/codex/workspace" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500" | |
| onchange="saveCodexWorkdir()"> | |
| <div class="text-xs text-gray-500 mt-1">Used as <code>--cd</code> for Codex runs. Restricted to <code>/data/codex/workspace</code> when available.</div> | |
| </div> | |
| <div class="mt-3"> | |
| <label class="flex items-center justify-between gap-3 bg-gray-800/40 border border-gray-700 rounded-lg px-3 py-2"> | |
| <div> | |
| <div class="text-sm text-gray-100">Show raw Codex JSON events</div> | |
| <div class="text-xs text-gray-400 mt-0.5">Appends raw JSON events to the progress view while Codex runs.</div> | |
| </div> | |
| <input id="codex-show-jsonl" type="checkbox" onchange="setCodexShowJsonl(this.checked)" | |
| class="h-4 w-4 accent-blue-600"> | |
| </label> | |
| </div> | |
| <div id="codex-mcp-list" class="mt-3 text-xs text-gray-400"></div> | |
| </div> | |
| <div class="pt-4 border-t border-gray-700"> | |
| <div class="flex items-center justify-between gap-2 mb-2"> | |
| <div class="font-semibold text-gray-200">Autonomous Tools</div> | |
| </div> | |
| <label class="flex items-center justify-between gap-3 bg-gray-800/40 border border-gray-700 rounded-lg px-3 py-2"> | |
| <div> | |
| <div class="text-sm text-gray-100">Use Codex CLI in Autonomous mode</div> | |
| <div class="text-xs text-gray-400 mt-0.5">Lets the agent access configured MCP tools (when available).</div> | |
| </div> | |
| <input id="agent-use-codex-cli" type="checkbox" onchange="setAgentUseCodexCli(this.checked)" | |
| class="h-4 w-4 accent-blue-600"> | |
| </label> | |
| </div> | |
| <div class="pt-4 border-t border-gray-700"> | |
| <div class="flex items-center justify-between gap-2 mb-2"> | |
| <div class="font-semibold text-gray-200">MCP</div> | |
| <button onclick="addMcpServerPrompt()" | |
| class="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded text-xs">+ Add</button> | |
| </div> | |
| <div class="text-xs text-gray-400 mb-2"> | |
| Configure MCP servers (URLs + auth). Stored in <code>localStorage</code>. Import/export via <code>mcp.json</code>. | |
| </div> | |
| <div class="flex gap-2 mb-3"> | |
| <button onclick="exportMcpConfig()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Export</button> | |
| <input id="mcp-import-input" type="file" accept="application/json" class="hidden" /> | |
| <button onclick="document.getElementById('mcp-import-input').click()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Import</button> | |
| <button onclick="loadMcpRegistryFromServer()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Load (server)</button> | |
| <button onclick="saveMcpRegistryToServer()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Save (server)</button> | |
| </div> | |
| <div class="bg-gray-900/30 border border-gray-700 rounded-lg p-3 mb-3 space-y-2"> | |
| <div class="flex items-center justify-between gap-2"> | |
| <div class="text-xs font-semibold text-gray-300 uppercase">Tool Policy</div> | |
| <div class="flex gap-2"> | |
| <button onclick="loadMcpPolicyFromServer()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Load</button> | |
| <button onclick="saveMcpPolicyToServer()" | |
| class="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded text-xs">Save</button> | |
| </div> | |
| </div> | |
| <div class="text-xs text-gray-400">Optional allow/deny list enforced server-side for <code>/api/mcp/call</code> and tool listing.</div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-3"> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Allow tools (one per line)</label> | |
| <textarea id="mcp-allow-tools" rows="3" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500 font-mono" | |
| placeholder="filesystem.read\nfilesystem.write"></textarea> | |
| <div class="text-xs text-gray-500 mt-1">If non-empty, only these tools are allowed.</div> | |
| </div> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Deny tools (one per line)</label> | |
| <textarea id="mcp-deny-tools" rows="3" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500 font-mono" | |
| placeholder="filesystem.write"></textarea> | |
| <div class="text-xs text-gray-500 mt-1">Always blocked (overrides allow).</div> | |
| </div> | |
| </div> | |
| <div id="mcp-policy-status" class="text-xs text-gray-500"></div> | |
| </div> | |
| <div class="flex gap-2 mb-3"> | |
| <input id="mcp-direct-url" type="text" placeholder="Direct MCP URL (https://...)" | |
| class="flex-grow bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| <button onclick="addMcpServerByUrl()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 rounded text-xs">Add</button> | |
| </div> | |
| <div id="settings-mcp-server-list" class="space-y-3"></div> | |
| </div> | |
| <div id="admin-panel" class="hidden pt-4 border-t border-gray-700"> | |
| <div class="flex items-center justify-between gap-2 mb-2"> | |
| <div class="font-semibold text-gray-200">Admin</div> | |
| </div> | |
| <div class="text-xs text-gray-400 mb-2">Read-only admin status and server feature flags.</div> | |
| <div id="admin-status" class="text-sm text-gray-200">Loading...</div> | |
| <div id="admin-features" class="mt-2 grid grid-cols-2 gap-2 text-xs text-gray-300"></div> | |
| <div class="mt-3 bg-gray-900/30 border border-gray-700 rounded-lg p-3 space-y-2"> | |
| <div class="flex items-center justify-between gap-2"> | |
| <div class="text-xs font-semibold text-gray-300 uppercase">Feature Overrides</div> | |
| <div class="flex gap-2"> | |
| <button onclick="loadAdminFeatureOverrides()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Load</button> | |
| <button onclick="saveAdminFeatureOverrides()" | |
| class="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded text-xs">Save</button> | |
| </div> | |
| </div> | |
| <div class="text-xs text-gray-400"> | |
| Overrides are persisted server-side (requires admin). Useful for temporarily disabling high-risk features without a redeploy. | |
| </div> | |
| <div class="grid grid-cols-2 gap-2 text-xs text-gray-200"> | |
| <label class="flex items-center gap-2 bg-gray-800/40 border border-gray-700 rounded-lg px-3 py-2"> | |
| <input id="admin-override-terminal" type="checkbox" class="h-4 w-4 accent-blue-600"> | |
| Terminal | |
| </label> | |
| <label class="flex items-center gap-2 bg-gray-800/40 border border-gray-700 rounded-lg px-3 py-2"> | |
| <input id="admin-override-codex" type="checkbox" class="h-4 w-4 accent-blue-600"> | |
| Codex | |
| </label> | |
| <label class="flex items-center gap-2 bg-gray-800/40 border border-gray-700 rounded-lg px-3 py-2"> | |
| <input id="admin-override-mcp" type="checkbox" class="h-4 w-4 accent-blue-600"> | |
| MCP | |
| </label> | |
| <label class="flex items-center gap-2 bg-gray-800/40 border border-gray-700 rounded-lg px-3 py-2"> | |
| <input id="admin-override-indexing" type="checkbox" class="h-4 w-4 accent-blue-600"> | |
| Indexing | |
| </label> | |
| <label class="flex items-center gap-2 bg-gray-800/40 border border-gray-700 rounded-lg px-3 py-2"> | |
| <input id="admin-override-rooms" type="checkbox" class="h-4 w-4 accent-blue-600"> | |
| Rooms | |
| </label> | |
| <label class="flex items-center gap-2 bg-gray-800/40 border border-gray-700 rounded-lg px-3 py-2"> | |
| <input id="admin-override-vault" type="checkbox" class="h-4 w-4 accent-blue-600"> | |
| Vault | |
| </label> | |
| </div> | |
| <div id="admin-overrides-status" class="text-xs text-gray-500"></div> | |
| </div> | |
| <div class="mt-3 bg-gray-900/30 border border-gray-700 rounded-lg p-3 space-y-2"> | |
| <div class="flex items-center justify-between gap-2"> | |
| <div class="text-xs font-semibold text-gray-300 uppercase">Users</div> | |
| <div class="flex gap-2"> | |
| <button onclick="loadAdminUsers()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Load</button> | |
| </div> | |
| </div> | |
| <div class="text-xs text-gray-400"> | |
| Manage Supabase users. Requires <code>SUPABASE_SERVICE_ROLE_KEY</code> on the server. | |
| </div> | |
| <div class="flex flex-wrap items-center gap-2 text-xs text-gray-200"> | |
| <input id="admin-users-prune-days" type="number" min="1" value="90" | |
| class="w-24 bg-gray-800 text-xs rounded border border-gray-700 p-1 text-white outline-none focus:border-blue-500" | |
| title="Older than (days)"> | |
| <input id="admin-users-prune-max" type="number" min="1" value="50" | |
| class="w-20 bg-gray-800 text-xs rounded border border-gray-700 p-1 text-white outline-none focus:border-blue-500" | |
| title="Max delete"> | |
| <label class="flex items-center gap-1"> | |
| <input id="admin-users-prune-inactive" type="checkbox" class="accent-blue-600" checked> | |
| Inactive only | |
| </label> | |
| <button onclick="pruneAdminUsers()" | |
| class="bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded text-xs">Prune old users</button> | |
| </div> | |
| <div id="admin-users-status" class="text-xs text-gray-500"></div> | |
| <div id="admin-users-list" class="space-y-2"></div> | |
| </div> | |
| <div class="mt-3"> | |
| <div class="text-xs font-semibold text-gray-400 mb-1 uppercase">MCP Templates (server)</div> | |
| <textarea id="admin-mcp-templates" rows="6" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500 font-mono" | |
| placeholder='{\"version\":1,\"templates\":[]}'></textarea> | |
| <div class="mt-2 flex gap-2"> | |
| <button onclick="loadAdminMcpTemplates()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Load</button> | |
| <button onclick="saveAdminMcpTemplates()" | |
| class="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded text-xs">Save</button> | |
| </div> | |
| <div class="text-xs text-gray-500 mt-1">Templates are admin-managed and persisted server-side; use to share common MCP server presets.</div> | |
| </div> | |
| </div> | |
| <div class="pt-4 border-t border-gray-700"> | |
| <div class="flex items-center justify-between gap-2 mb-2"> | |
| <div class="font-semibold text-gray-200">Notes</div> | |
| <button onclick="switchMode('notes'); closeSettings();" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Open Notes</button> | |
| </div> | |
| <div class="text-xs text-gray-400">Notes editor is available in the Notes tab.</div> | |
| </div> | |
| <div id="rag-panel" class="pt-4 border-t border-gray-700"> | |
| <div class="flex items-center justify-between gap-2 mb-2"> | |
| <div class="font-semibold text-gray-200">RAG (Indexing)</div> | |
| <button onclick="loadRagDocuments()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Refresh</button> | |
| </div> | |
| <div class="text-xs text-gray-400 mb-2"> | |
| Upload plain text documents and run basic keyword search. Stored per user. Enable with <code>ENABLE_INDEXING=1</code>. | |
| </div> | |
| <div id="rag-disabled" class="hidden text-xs bg-yellow-900/30 border border-yellow-800 text-yellow-200 rounded-lg px-3 py-2"> | |
| Indexing is disabled on this deployment. | |
| </div> | |
| <div id="rag-status" class="text-xs text-gray-500 mt-2"></div> | |
| <div id="rag-controls" class="mt-3 space-y-3"> | |
| <div class="flex items-center gap-2"> | |
| <input id="rag-file-input" type="file" accept=".txt,.md,.markdown,text/plain" | |
| class="flex-grow bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| <button onclick="uploadRagDocument()" | |
| class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded text-xs">Upload</button> | |
| </div> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Search</label> | |
| <div class="flex gap-2"> | |
| <input id="rag-query" type="text" placeholder="Search query..." | |
| class="flex-grow bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| <button onclick="searchRag()" | |
| class="bg-green-600 hover:bg-green-700 text-white px-3 py-2 rounded text-xs">Go</button> | |
| </div> | |
| <div id="rag-search-results" class="mt-2 space-y-2"></div> | |
| </div> | |
| <div class="bg-gray-900/30 border border-gray-700 rounded-lg p-3 space-y-2"> | |
| <div class="flex items-center justify-between gap-2"> | |
| <div class="text-xs font-semibold text-gray-300 uppercase">Website Indexing</div> | |
| <button onclick="loadIndexingJobs()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Refresh Jobs</button> | |
| </div> | |
| <div class="text-xs text-gray-400">Indexes same-origin HTML pages into RAG with depth/page limits. Blocks private/localhost targets.</div> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-2"> | |
| <input id="crawl-url" type="text" placeholder="https://example.com" | |
| class="md:col-span-2 bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| <button onclick="startWebCrawlJob()" | |
| class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded text-xs">Start</button> | |
| </div> | |
| <div class="grid grid-cols-3 gap-2"> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Max Pages</label> | |
| <input id="crawl-max-pages" type="number" min="1" max="150" value="25" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| </div> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Max Depth</label> | |
| <input id="crawl-max-depth" type="number" min="0" max="6" value="2" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| </div> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Rate (sec)</label> | |
| <input id="crawl-rate" type="number" min="0" max="5" step="0.05" value="0.25" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| </div> | |
| </div> | |
| <div class="flex items-center gap-2"> | |
| <input id="crawl-respect-robots" type="checkbox" checked class="h-4 w-4 accent-blue-600"> | |
| <label for="crawl-respect-robots" class="text-xs text-gray-300">Respect robots.txt (basic)</label> | |
| </div> | |
| <div id="indexing-status" class="text-xs text-gray-500"></div> | |
| <div id="indexing-jobs" class="space-y-2"></div> | |
| </div> | |
| <div class="bg-gray-900/30 border border-gray-700 rounded-lg p-3 space-y-2"> | |
| <div class="text-xs font-semibold text-gray-300 uppercase">GitHub Repo Indexing</div> | |
| <div class="text-xs text-gray-400">Indexes repository text files into RAG (uses `GITHUB_TOKEN`/`GITHUB_PAT` for private repos).</div> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-2"> | |
| <input id="gh-repo" type="text" placeholder="owner/repo or https://github.com/owner/repo" | |
| class="md:col-span-2 bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| <button onclick="startGithubRepoJob()" | |
| class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded text-xs">Start</button> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-2"> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Ref</label> | |
| <input id="gh-ref" type="text" placeholder="main" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| </div> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Path Prefix</label> | |
| <input id="gh-path" type="text" placeholder="(optional) src/" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| </div> | |
| <div> | |
| <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Max Files</label> | |
| <input id="gh-max-files" type="number" min="1" max="400" value="60" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| </div> | |
| </div> | |
| <div id="github-indexing-status" class="text-xs text-gray-500"></div> | |
| </div> | |
| <div> | |
| <div class="text-xs font-semibold text-gray-400 mb-1 uppercase">Documents</div> | |
| <div id="rag-documents" class="space-y-2"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="vault-panel" class="pt-4 border-t border-gray-700"> | |
| <div class="flex items-center justify-between gap-2 mb-2"> | |
| <div class="font-semibold text-gray-200">Vault (Encrypted)</div> | |
| <div class="flex gap-2"> | |
| <button onclick="loadVaultFromServer()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Load</button> | |
| <button onclick="lockVault()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Lock</button> | |
| </div> | |
| </div> | |
| <div class="text-xs text-gray-400"> | |
| Client-side encrypted vault stored per user. Enable with <code>ENABLE_VAULT=1</code>. Do not rely on this for production security. | |
| </div> | |
| <div id="vault-disabled" class="hidden text-xs bg-yellow-900/30 border border-yellow-800 text-yellow-200 rounded-lg px-3 py-2 mt-2"> | |
| Vault is disabled on this deployment. | |
| </div> | |
| <div id="vault-status" class="text-xs text-gray-500 mt-2"></div> | |
| <div class="mt-3 space-y-3"> | |
| <div class="bg-gray-900/30 border border-gray-700 rounded-lg p-3 space-y-2"> | |
| <div class="text-xs font-semibold text-gray-300 uppercase">Unlock</div> | |
| <div class="flex gap-2"> | |
| <input id="vault-password" type="password" placeholder="Master password" | |
| class="flex-grow bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| <button onclick="unlockVault()" | |
| class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded text-xs">Unlock</button> | |
| <button onclick="createNewVault()" | |
| class="bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-2 rounded text-xs">New</button> | |
| </div> | |
| <div class="text-xs text-gray-500">Your password is never sent to the server.</div> | |
| </div> | |
| <div class="bg-gray-900/30 border border-gray-700 rounded-lg p-3 space-y-2"> | |
| <div class="flex items-center justify-between gap-2"> | |
| <div class="text-xs font-semibold text-gray-300 uppercase">Entries</div> | |
| <button onclick="openVaultNewEntry()" | |
| class="bg-green-600 hover:bg-green-700 text-white px-2 py-1 rounded text-xs">+ Add</button> | |
| </div> | |
| <input id="vault-search" type="text" placeholder="Search..." | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| <div id="vault-entries" class="space-y-2"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="agent-split-root" class="split-root flex-grow min-h-0"> | |
| <div id="agent-pane-chat" class="split-pane min-h-0 flex flex-col border-r border-gray-700 agent-pane-surface"> | |
| <div id="agent-terminal-collapsed-bar" class="hidden px-3 py-2 border-b border-gray-700/70 flex items-center justify-end bg-gray-900/30"> | |
| <button onclick="toggleAgentTerminalCollapsed()" | |
| class="px-2 py-1 text-xs rounded-md text-gray-200 hover:bg-white/10 transition" | |
| title="Show terminal">Show terminal</button> | |
| </div> | |
| <div id="chat-standard" class="flex-grow flex flex-col min-h-0"> | |
| <!-- Messages --> | |
| <div id="chat-history" class="flex-grow p-4 overflow-y-auto space-y-4 scroll-smooth"> | |
| <div id="chat-empty-state" class="text-center text-gray-500 mt-10"> | |
| <div class="text-lg font-semibold text-gray-300">Start a conversation</div> | |
| <div class="text-sm text-gray-500 mt-1" id="chat-empty-hint">Configure <code>DEFAULT_BASE_URL</code>, then send a message.</div> | |
| </div> | |
| </div> | |
| <!-- Input --> | |
| <div class="p-3 bg-gray-800 border-t border-gray-700 shrink-0"> | |
| <div id="chat-attachments" class="max-w-5xl mx-auto mb-2 hidden"></div> | |
| <div class="flex gap-2 max-w-5xl mx-auto items-center flex-nowrap overflow-x-auto"> | |
| <select id="chat-agent-target" onchange="setChatAgentTarget(this.value)" | |
| class="bg-gray-700 h-10 rounded-lg border border-gray-600 px-2 text-sm text-white outline-none focus:border-blue-500" | |
| title="Target"> | |
| <option value="chat">Chat</option> | |
| <option value="codex">@codex</option> | |
| </select> | |
| <input id="chat-input" type="text" autocomplete="off" | |
| class="flex-grow bg-gray-700 h-10 px-3 rounded-lg border border-gray-600 focus:ring-2 focus:ring-blue-500 focus:outline-none text-white" | |
| placeholder="Message..." | |
| onkeydown="handleEnter(event)" /> | |
| <div class="hidden sm:flex items-center gap-2"> | |
| <input id="chat-model-quick" type="text" list="model-list" placeholder="Model" | |
| class="bg-gray-700 h-10 w-44 px-2 rounded-lg border border-gray-600 text-sm text-white outline-none focus:border-blue-500" | |
| oninput="setQuickModel(this.value)" title="Model" /> | |
| <button onclick="fetchModels({ quiet: false }).catch(() => {})" | |
| class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200" | |
| title="Refresh models" aria-label="Refresh models"> | |
| <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | |
| d="M20 12a8 8 0 1 1-2.343-5.657M20 4v6h-6" /> | |
| </svg> | |
| </button> | |
| </div> | |
| <datalist id="model-list"></datalist> | |
| <input id="chat-file-input" type="file" accept="image/*" multiple class="hidden" /> | |
| <button onclick="document.getElementById('chat-file-input').click()" | |
| class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200" | |
| title="Attach images" aria-label="Attach images"> | |
| <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21.44 11.05l-8.49 8.49a5 5 0 0 1-7.07-7.07l9.2-9.2a3.5 3.5 0 0 1 4.95 4.95l-9.2 9.2a2 2 0 0 1-2.83-2.83l8.49-8.49" /> | |
| </svg> | |
| </button> | |
| <button onclick="captureScreenshotToChat()" | |
| class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200" | |
| title="Take screenshot" aria-label="Take screenshot"> | |
| <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7h4l2-2h4l2 2h4v12H4V7z" /> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 17a4 4 0 1 0 0-8 4 4 0 0 0 0 8z" /> | |
| </svg> | |
| </button> | |
| <button id="chat-mic-btn" onclick="toggleChatDictation()" | |
| class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200" | |
| title="Voice input" aria-label="Voice input"> | |
| <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 14a3 3 0 0 0 3-3V6a3 3 0 0 0-6 0v5a3 3 0 0 0 3 3z" /> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 0 1-14 0" /> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19v3" /> | |
| </svg> | |
| </button> | |
| <button id="chat-stop-btn" onclick="stopChatGeneration()" disabled | |
| class="bg-gray-600 h-10 w-10 rounded-lg hover:bg-gray-700 font-bold transition inline-flex items-center justify-center disabled:opacity-40 disabled:cursor-not-allowed" | |
| title="Stop generating"> | |
| <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | |
| <circle cx="12" cy="12" r="9" stroke-width="2" /> | |
| <rect x="9" y="9" width="6" height="6" rx="1" fill="currentColor" stroke="none" /> | |
| </svg> | |
| </button> | |
| <button onclick="sendChatBarMessage()" | |
| class="bg-blue-600 h-10 w-10 rounded-lg hover:bg-blue-700 font-bold transition inline-flex items-center justify-center"> | |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | |
| d="M10 14l11-11M21 3l-7 20-4-9-9-4 20-7z" /> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="agent-split-divider" class="split-divider hidden" title="Drag to resize panes"></div> | |
| <div id="agent-pane-terminal" class="split-pane hidden min-h-0 agent-terminal-chrome flex flex-col"> | |
| <div class="shrink-0 flex items-center bg-gray-900/60 border-b border-gray-700 overflow-x-auto"> | |
| <div id="agent-terminal-tabs" class="flex"></div> | |
| <button onclick="toggleAgentTerminalCollapsed()" | |
| class="ml-auto px-2 py-2 text-gray-300 hover:text-white hover:bg-white/10 transition font-bold" | |
| title="Collapse terminal" aria-label="Collapse terminal">⟷</button> | |
| <button onclick="createTerminalTab({ scope: 'agent' })" | |
| class="px-3 py-2 text-gray-300 hover:text-white hover:bg-white/10 transition font-bold" | |
| title="New Terminal">+</button> | |
| </div> | |
| <div id="agent-terminals-container" class="flex-grow relative w-full h-full"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Terminal View (Multi-Tab) --> | |
| <div id="terminal-view" class="view-section hidden w-full h-full flex flex-col overflow-hidden bg-black"> | |
| <!-- Tabs Bar --> | |
| <div class="flex items-center bg-gray-800 border-b border-gray-700 shrink-0 overflow-x-auto"> | |
| <div id="terminal-tabs" class="flex"> | |
| <!-- Tab Items will be injected here --> | |
| </div> | |
| <div class="ml-auto flex items-center"> | |
| <button onclick="copyActiveTerminalOutput()" | |
| class="px-2 py-2 text-gray-400 hover:text-white hover:bg-gray-700 transition" | |
| title="Copy terminal output">⧉</button> | |
| <button onclick="setTerminalLayout('single')" id="layout-single" | |
| class="px-2 py-2 text-gray-400 hover:text-white hover:bg-gray-700 transition" | |
| title="Single pane">▢</button> | |
| <button onclick="setTerminalLayout('vsplit')" id="layout-vsplit" | |
| class="px-2 py-2 text-gray-400 hover:text-white hover:bg-gray-700 transition" | |
| title="Vertical split">▥</button> | |
| <button onclick="setTerminalLayout('hsplit')" id="layout-hsplit" | |
| class="px-2 py-2 text-gray-400 hover:text-white hover:bg-gray-700 transition" | |
| title="Horizontal split">▤</button> | |
| <button onclick="setTerminalLayout('quad')" id="layout-quad" | |
| class="px-2 py-2 text-gray-400 hover:text-white hover:bg-gray-700 transition" | |
| title="2x2 grid">▦</button> | |
| <button onclick="createTerminalTab()" | |
| class="px-3 py-2 text-gray-400 hover:text-white hover:bg-gray-700 transition font-bold" | |
| title="New Terminal"> | |
| + | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Terminal Containers Area --> | |
| <div id="terminals-container" class="flex-grow w-full h-full terminal-grid single"> | |
| <div id="terminal-pane-1" class="terminal-pane"></div> | |
| <div id="terminal-pane-2" class="terminal-pane hidden"></div> | |
| <div id="terminal-pane-3" class="terminal-pane hidden"></div> | |
| <div id="terminal-pane-4" class="terminal-pane hidden"></div> | |
| </div> | |
| </div> | |
| <!-- Notes View --> | |
| <div id="notes-view" class="view-section hidden w-full h-full flex overflow-hidden bg-gray-900"> | |
| <div id="notes-sidebar" class="w-72 bg-gray-800 border-r border-gray-700 flex flex-col shrink-0"> | |
| <div class="p-3 border-b border-gray-700 flex items-center justify-between gap-2"> | |
| <div class="font-semibold text-gray-200">Notes</div> | |
| <div class="flex gap-2"> | |
| <button onclick="createNotesFolder()" class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">+ Folder</button> | |
| <button onclick="createNotesDoc()" class="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded text-xs">+ Note</button> | |
| </div> | |
| </div> | |
| <div class="p-2 border-b border-gray-700"> | |
| <button onclick="refreshNotesTree()" class="text-xs text-gray-300 hover:text-white">↻ Refresh</button> | |
| </div> | |
| <div id="notes-tree" class="flex-grow overflow-y-auto p-2 text-sm space-y-1"></div> | |
| </div> | |
| <div class="flex-grow min-w-0 flex flex-col"> | |
| <div class="p-3 border-b border-gray-700 bg-gray-800 flex items-center justify-between gap-2"> | |
| <div class="flex items-center gap-2 min-w-0 flex-1"> | |
| <button onclick="toggleNotesSidebar()" class="p-2 rounded hover:bg-gray-700 text-gray-300 transition inline-flex items-center justify-center" | |
| title="Toggle folders" aria-label="Toggle folders"> | |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path> | |
| </svg> | |
| </button> | |
| <input id="notes-title" type="text" placeholder="Title" | |
| class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500" /> | |
| </div> | |
| <div class="flex items-center gap-2 shrink-0"> | |
| <div class="hidden md:flex bg-gray-700/60 rounded-lg p-1 mr-2"> | |
| <button id="notes-mode-split" onclick="setNotesMode('split')" | |
| class="px-2 py-1 text-xs rounded-md text-gray-200 hover:bg-white/10 transition">Split</button> | |
| <button id="notes-mode-edit" onclick="setNotesMode('edit')" | |
| class="px-2 py-1 text-xs rounded-md text-gray-200 hover:bg-white/10 transition">Edit</button> | |
| <button id="notes-mode-preview" onclick="setNotesMode('preview')" | |
| class="px-2 py-1 text-xs rounded-md text-gray-200 hover:bg-white/10 transition">Preview</button> | |
| </div> | |
| <button onclick="aiWriteNotes()" class="bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-2 rounded text-xs" title="Use AI to write in this note">@AI</button> | |
| <button onclick="saveActiveNote()" class="bg-green-600 hover:bg-green-700 text-white px-3 py-2 rounded text-xs">Save</button> | |
| <button onclick="renameActiveNote()" class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-2 rounded text-xs">Rename</button> | |
| <button onclick="deleteActiveNote()" class="bg-red-600 hover:bg-red-700 text-white px-3 py-2 rounded text-xs">Delete</button> | |
| </div> | |
| </div> | |
| <div id="notes-main" class="flex-grow min-h-0 grid grid-cols-1 lg:grid-cols-2 gap-0"> | |
| <div id="notes-editor-pane" class="min-h-0 flex flex-col border-r border-gray-700"> | |
| <div class="px-3 py-2 text-xs text-gray-400 border-b border-gray-700 bg-gray-900/30 flex items-center justify-between"> | |
| <span>Editor</span> | |
| <button id="notes-toggle-editor" onclick="toggleNotesPane('editor')" class="text-gray-300 hover:text-white">Hide</button> | |
| </div> | |
| <textarea id="notes-editor" | |
| class="flex-grow min-h-0 w-full bg-gray-900 text-sm p-3 text-white outline-none font-mono" | |
| placeholder="# Notes..."></textarea> | |
| </div> | |
| <div id="notes-preview-pane" class="min-h-0 flex flex-col"> | |
| <div class="px-3 py-2 text-xs text-gray-400 border-b border-gray-700 bg-gray-900/30 flex items-center justify-between"> | |
| <span>Preview</span> | |
| <button id="notes-toggle-preview" onclick="toggleNotesPane('preview')" class="text-gray-300 hover:text-white">Hide</button> | |
| </div> | |
| <div id="notes-preview" class="flex-grow min-h-0 overflow-y-auto prose prose-invert max-w-none p-4"></div> | |
| </div> | |
| </div> | |
| <div class="px-3 py-2 text-xs text-gray-400 border-t border-gray-700 bg-gray-800" id="notes-status"></div> | |
| </div> | |
| </div> | |
| <!-- Rooms View (P2P PubSub MVP) --> | |
| <div id="rooms-view" class="view-section hidden w-full h-full flex overflow-hidden bg-gray-900"> | |
| <div class="w-80 bg-gray-800 border-r border-gray-700 flex flex-col shrink-0"> | |
| <div class="p-3 border-b border-gray-700 flex items-center justify-between gap-2"> | |
| <div class="font-semibold text-gray-200">Rooms</div> | |
| <button onclick="createRoom()" | |
| class="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded text-xs">+ New</button> | |
| </div> | |
| <div class="p-3 border-b border-gray-700 space-y-2"> | |
| <div class="text-xs font-semibold text-gray-400 uppercase">Join by ID</div> | |
| <div class="flex gap-2"> | |
| <input id="rooms-join-id" type="text" placeholder="room UUID" | |
| class="flex-grow bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"> | |
| <button onclick="joinRoomFromInput()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 rounded text-xs">Join</button> | |
| </div> | |
| <div class="text-xs text-gray-500">Tip: share a room ID to invite others.</div> | |
| </div> | |
| <div class="p-2 border-b border-gray-700 flex items-center justify-between"> | |
| <button onclick="loadRoomsList()" class="text-xs text-gray-300 hover:text-white">↻ Refresh</button> | |
| <div id="rooms-status" class="text-xs text-gray-500"></div> | |
| </div> | |
| <div id="rooms-list" class="flex-grow overflow-y-auto p-2 text-sm space-y-1"></div> | |
| </div> | |
| <div class="flex-grow min-w-0 flex flex-col"> | |
| <div class="p-3 border-b border-gray-700 bg-gray-800 flex items-center justify-between gap-2"> | |
| <div class="min-w-0"> | |
| <div class="text-sm font-semibold text-gray-200 truncate" id="rooms-active-title">Select a room</div> | |
| <div class="text-xs text-gray-400 truncate" id="rooms-active-subtitle">Create or join a room to start chatting.</div> | |
| </div> | |
| <div class="flex items-center gap-2 shrink-0"> | |
| <label class="flex items-center gap-2 text-xs text-gray-300 bg-gray-700/60 px-2 py-1 rounded-lg"> | |
| <input id="rooms-prefer-p2p" type="checkbox" class="h-4 w-4 accent-blue-600"> | |
| Prefer P2P | |
| </label> | |
| <button onclick="copyActiveRoomId()" | |
| class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Copy ID</button> | |
| <button onclick="leaveActiveRoom()" | |
| class="bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded text-xs">Leave</button> | |
| </div> | |
| </div> | |
| <div class="px-3 py-2 border-b border-gray-700 bg-gray-900/40 flex items-center justify-between gap-3"> | |
| <div class="text-xs text-gray-400"> | |
| <span class="font-semibold text-gray-200" id="rooms-my-role">role: —</span> | |
| <span class="mx-2">•</span> | |
| <button onclick="toggleRoomsMembers()" class="text-gray-300 hover:text-white underline underline-offset-2">members</button> | |
| </div> | |
| <div class="flex items-center gap-2"> | |
| <button onclick="loadRoomMembers()" class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Refresh</button> | |
| </div> | |
| </div> | |
| <div id="rooms-members-panel" class="hidden border-b border-gray-700 bg-gray-900/30 p-3"> | |
| <div id="rooms-members" class="grid grid-cols-1 md:grid-cols-2 gap-2 text-xs"></div> | |
| <div id="rooms-members-status" class="text-xs text-gray-500 mt-2"></div> | |
| </div> | |
| <div id="rooms-peers-panel" class="hidden border-b border-gray-700 bg-gray-900/30 p-3"> | |
| <div class="text-xs font-semibold text-gray-400 uppercase mb-2">Peers</div> | |
| <div id="rooms-peers-list" class="grid grid-cols-1 md:grid-cols-2 gap-2 text-xs"></div> | |
| </div> | |
| <div class="flex-grow min-h-0 overflow-y-auto p-4 space-y-3" id="rooms-messages"> | |
| <div class="text-sm text-gray-500">No room selected.</div> | |
| </div> | |
| <div class="border-t border-gray-700 bg-gray-800 p-3"> | |
| <div class="flex items-end gap-2"> | |
| <textarea id="rooms-input" rows="2" placeholder="Message…" | |
| class="flex-grow bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500"></textarea> | |
| <button onclick="sendRoomMessage()" | |
| class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded text-sm font-semibold">Send</button> | |
| </div> | |
| <div class="mt-2 text-xs text-gray-500"> | |
| <span id="rooms-conn-status">Disconnected</span> | |
| <span class="mx-2">•</span> | |
| <span id="rooms-peers">Peers: 0</span> | |
| <span class="mx-2">•</span> | |
| <button onclick="toggleRoomsPeers()" class="text-gray-300 hover:text-white underline underline-offset-2">peers</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Autonomous Mode View removed (now embedded in Chat) --> | |
| </div> | |
| <!-- Footer --> | |
| <footer class="shrink-0 border-t border-gray-700 bg-gray-800 px-4 py-1 text-xs text-gray-400 flex items-center justify-between"> | |
| <div>autonomy-labs — ship ideas faster.</div> | |
| <div class="hidden md:block">Chat • Autonomous • Terminal</div> | |
| </footer> | |
| <script src="/static/dashboard.js" defer></script> | |
| </body> | |
| </html> | |