This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/src/index.html
2026-02-21 21:59:38 +00:00

562 lines
39 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content">
<base href="%%BASE_HREF%%">
<script>window.__BASE__ = '%%BASE_PATH%%';</script>
<script>window.__VERSION__ = '%%APP_VERSION%%'; if (window.__VERSION__.includes('%%')) window.__VERSION__ = 'dev';</script>
<script>try { window.__BRANDING__ = %%BRANDING_JSON%%; } catch(e) { window.__BRANDING__ = {}; }</script>
<title>Chat Switchboard</title>
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png">
<link rel="apple-touch-icon" sizes="192x192" href="favicon-192.png">
<link rel="stylesheet" href="css/styles.css?v=%%APP_VERSION%%">
<link rel="stylesheet" href="branding/custom.css" onerror="this.remove()">
</head>
<body>
<!-- ── App Shell ───────────────────────────── -->
<div id="appContainer" class="app" style="display:none">
<!-- Environment Banner (top) -->
<div class="banner banner-top" id="bannerTop"></div>
<div class="app-body">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-top">
<button class="sb-brand" id="sidebarToggle" title="Toggle sidebar">
<span class="brand-logo" id="brandSidebarLogo">🔀</span>
<svg class="brand-collapse" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg>
<span class="sb-label brand-text" id="brandSidebarText">Chat Switchboard</span>
</button>
<div class="split-btn" style="position:relative">
<button class="split-btn-main" id="newChatBtn" title="New Chat">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>
<span class="sb-label">New Chat</span>
</button>
<button class="split-btn-drop" id="newChatDropBtn" title="More options">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="split-dropdown" id="newChatDropdown">
<button class="split-dropdown-item" onclick="newChat()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>
New Chat
</button>
<button class="split-dropdown-item disabled" title="Coming soon">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
Group Chat <span class="dd-hint">soon</span>
</button>
<button class="split-dropdown-item disabled" title="Coming soon">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" 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"/></svg>
New Channel <span class="dd-hint">soon</span>
</button>
</div>
</div>
</div>
<div class="sidebar-chats" id="chatHistory"></div>
<!-- Notes shortcut -->
<div class="sidebar-notes-btn">
<button class="sb-notes" id="notesBtn" title="Notes">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
<span class="sb-label">Notes</span>
</button>
</div>
<!-- User area -->
<div class="sidebar-bottom">
<button class="user-btn" id="userMenuBtn">
<div class="user-avatar" id="userAvatar"><span id="avatarLetter">?</span>
<span class="avatar-bug" title="Debug available">🐛</span>
</div>
<span class="sb-label user-name" id="userName">User</span>
</button>
<div class="user-flyout" id="userFlyout">
<button class="flyout-item" id="menuSettings">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
<span>Settings</span>
</button>
<button class="flyout-item" id="menuAdmin" style="display:none">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
<span>Admin Panel</span>
</button>
<button class="flyout-item" id="menuDebug">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 11V6a2 2 0 0 0-2-2h-1a2 2 0 0 0-2 2"/><path d="M9 6a2 2 0 0 0-2 2v3"/><path d="M4 14h4"/><path d="M16 14h4"/><path d="M12 18v-6"/><circle cx="12" cy="14" r="6"/></svg>
<span>Debug Log</span>
</button>
<div class="flyout-divider"></div>
<button class="flyout-item flyout-danger" id="menuSignout">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>
<span>Sign Out</span>
</button>
</div>
</div>
</aside>
<div class="sidebar-overlay" id="sidebarOverlay"></div>
<!-- Main Chat Area -->
<main class="chat-area">
<div class="model-bar">
<button class="mobile-menu-btn" id="mobileMenuBtn" title="Menu">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
</button>
<div class="model-dropdown" id="modelDropdown">
<button class="model-dropdown-btn" id="modelDropdownBtn" title="Select model">
<span class="model-dropdown-label" id="modelDropdownLabel">Select a model</span>
<svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><path d="M2 3.5L5 6.5L8 3.5"/></svg>
</button>
<div class="model-dropdown-menu" id="modelDropdownMenu"></div>
</div>
<button class="icon-btn" id="fetchModelsBtn" title="Refresh models">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
</button>
<div class="model-caps" id="modelCaps"></div>
</div>
<div class="messages" id="chatMessages">
<div class="empty-state">
<div class="empty-logo">🔀</div>
<h2>Chat Switchboard</h2>
<p>Select a model and start chatting</p>
</div>
</div>
<div class="input-area">
<div class="input-wrap">
<textarea id="messageInput" placeholder="Send a message..." rows="1"></textarea>
<div class="input-actions">
<button class="action-btn stop-btn" id="stopBtn" title="Stop">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>
</button>
<button class="action-btn send-btn" id="sendBtn" title="Send">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
</button>
</div>
</div>
</div>
</main>
</div><!-- .app-body -->
<!-- Environment Banner (bottom) -->
<div class="banner banner-bottom" id="bannerBottom"></div>
</div>
<!-- ── Auth Splash ─────────────────────────── -->
<div class="splash" id="splashGate">
<div class="splash-hero">
<div class="hero-content">
<div class="hero-logo-row">
<div class="hero-logo-mark" id="brandLogo">
<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="64" rx="12" fill="#1a1a2e"/>
<path d="M12 48 L36 16" stroke="#6c9fff" stroke-width="5.5" stroke-linecap="round" fill="none"/>
<polygon points="42,12 34,14 38,22" fill="#6c9fff"/>
<path d="M12 16 L24 30" stroke="#a78bfa" stroke-width="5.5" stroke-linecap="round" fill="none"/>
<path d="M30 38 L36 48" stroke="#a78bfa" stroke-width="5.5" stroke-linecap="round" fill="none"/>
<polygon points="42,52 34,50 38,42" fill="#a78bfa"/>
</svg>
</div>
<div class="hero-wordmark" id="brandWordmark">Chat <span>Switchboard</span></div>
</div>
<h1 class="hero-headline" id="brandHeadline">One interface.<br>Every AI model.</h1>
<p class="hero-sub" id="brandTagline">Route conversations to OpenAI, Anthropic, and any OpenAI-compatible provider from a single, self-hosted chat interface. Bring your own keys. Keep your data.</p>
<div class="hero-features" id="brandPills">
<div class="hero-pill accent"><span class="pill-icon"></span> Streaming responses</div>
<div class="hero-pill purple"><span class="pill-icon">🔀</span> Multi-provider routing</div>
<div class="hero-pill"><span class="pill-icon">🔑</span> Bring your own API keys</div>
<div class="hero-pill"><span class="pill-icon">🏠</span> Self-hosted</div>
<div class="hero-pill"><span class="pill-icon">✈️</span> Air-gap ready</div>
<div class="hero-pill"><span class="pill-icon">🧠</span> Thinking blocks</div>
</div>
<div class="hero-version">v%%APP_VERSION%%</div>
</div>
</div>
<div class="splash-auth">
<div class="auth-card">
<div class="auth-card-header">
<h2>Welcome back</h2>
<p>Sign in to continue to your workspace</p>
</div>
<div class="auth-tabs">
<button class="auth-tab active" id="authTabLogin" onclick="switchAuthTab('login')">Sign In</button>
<button class="auth-tab" id="authTabRegister" onclick="switchAuthTab('register')">Register</button>
</div>
<div id="authLoginForm">
<div class="form-group"><label>Username or Email</label><input type="text" id="authLogin" autocomplete="username" placeholder="you@example.com"></div>
<div class="form-group"><label>Password</label><input type="password" id="authPassword" autocomplete="current-password" placeholder="••••••••" onkeydown="if(event.key==='Enter')handleLogin()"></div>
</div>
<div id="authRegisterForm" style="display:none">
<div class="form-group"><label>Username</label><input type="text" id="authUsername" autocomplete="username" placeholder="Choose a username"></div>
<div class="form-group"><label>Email</label><input type="email" id="authEmail" autocomplete="email" placeholder="you@example.com"></div>
<div class="form-group"><label>Password</label><input type="password" id="authRegPassword" autocomplete="new-password" placeholder="Min 8 characters" onkeydown="if(event.key==='Enter')handleRegister()"></div>
</div>
<div class="auth-error" id="authError"></div>
<div class="splash-error" id="splashError"></div>
<div class="auth-actions">
<button class="btn-primary btn-full" id="authLoginBtn" data-label="Sign In" onclick="handleLogin()">Sign In</button>
<button class="btn-primary btn-full" id="authRegisterBtn" data-label="Create Account" onclick="handleRegister()" style="display:none">Create Account</button>
</div>
<div class="auth-footer">
<p id="brandAuthFooter">Self-hosted AI chat &middot; Your keys, your data</p>
</div>
</div>
</div>
</div>
<!-- ── Settings Modal ──────────────────────── -->
<div class="modal-overlay" id="settingsModal">
<div class="modal">
<div class="modal-header"><h2>Settings</h2><button class="modal-close" id="settingsCloseBtn"></button></div>
<div class="modal-tabs settings-tabs">
<button class="settings-tab active" data-stab="general" onclick="UI.switchSettingsTab('general')">General</button>
<button class="settings-tab" data-stab="appearance" onclick="UI.switchSettingsTab('appearance')">Appearance</button>
<button class="settings-tab" data-stab="providers" onclick="UI.switchSettingsTab('providers')">Providers</button>
<button class="settings-tab" data-stab="models" onclick="UI.switchSettingsTab('models')">Models</button>
</div>
<div class="modal-body">
<!-- General Tab -->
<div class="settings-tab-content" id="settingsGeneralTab">
<section class="settings-section">
<h3>Profile</h3>
<div class="avatar-upload-row">
<div class="avatar-preview" id="avatarPreview">
<span id="avatarPreviewLetter">?</span>
</div>
<div class="avatar-actions">
<button class="btn-small" id="avatarUploadBtn">Upload Photo</button>
<button class="btn-small btn-danger" id="avatarRemoveBtn" style="display:none">Remove</button>
<input type="file" id="avatarFileInput" accept="image/png,image/jpeg,image/gif" style="display:none">
<div class="form-hint">Auto-resized to 128×128</div>
</div>
</div>
<div class="form-group"><label>Display Name</label><input type="text" id="profileDisplayName"></div>
<div class="form-group"><label>Email</label><input type="email" id="profileEmail"></div>
<button class="btn-small" id="profileChangePwBtn">Change Password</button>
<div id="profileChangePwForm" style="display:none">
<div class="form-group"><label>Current Password</label><input type="password" id="profileCurrentPw"></div>
<div class="form-group"><label>New Password</label><input type="password" id="profileNewPw"></div>
<button class="btn-small btn-primary" id="profileSavePwBtn">Save Password</button>
</div>
</section>
<section class="settings-section">
<h3>Chat</h3>
<div class="form-group"><label>Default Model</label><select id="settingsModel"></select></div>
<div class="form-group"><label>System Prompt</label><textarea id="settingsSystemPrompt" rows="3"></textarea></div>
<div class="form-row">
<div class="form-group">
<label>Max Tokens <span class="form-hint" id="settingsMaxHint"></span></label>
<input type="number" id="settingsMaxTokens" placeholder="Auto (from model)">
</div>
<div class="form-group"><label>Temperature</label><input type="number" id="settingsTemp" value="0.7" step="0.1" min="0" max="2"></div>
</div>
<label class="checkbox-label"><input type="checkbox" id="settingsThinking" checked> Show thinking blocks</label>
</section>
</div>
<!-- Appearance Tab -->
<div class="settings-tab-content" id="settingsAppearanceTab" style="display:none">
<section class="settings-section">
<h3>Display</h3>
<div class="form-group">
<label>UI Scale <span class="form-hint" id="scaleValue">100%</span></label>
<input type="range" id="settingsScale" min="80" max="150" step="5" value="100" class="range-input">
<div class="range-labels"><span>80%</span><span>150%</span></div>
</div>
<div class="form-group">
<label>Message Font Size <span class="form-hint" id="msgFontValue">14px</span></label>
<input type="range" id="settingsMsgFont" min="12" max="20" step="1" value="14" class="range-input">
<div class="range-labels"><span>12px</span><span>20px</span></div>
</div>
</section>
</div>
<!-- Providers Tab -->
<div class="settings-tab-content" id="settingsProvidersTab" style="display:none">
<section class="settings-section">
<div id="providerList"></div>
<button class="btn-small" id="providerShowAddBtn">+ Add Provider</button>
<div id="providerAddForm" style="display:none">
<div class="form-group"><label>Name</label><input type="text" id="providerName" placeholder="My OpenAI Key"></div>
<div class="form-group"><label>Provider</label><select id="providerType"><option value="openai">OpenAI-compatible</option><option value="anthropic">Anthropic</option><option value="venice">Venice.ai</option><option value="openrouter">OpenRouter</option></select></div>
<div class="form-group"><label>Endpoint</label><input type="text" id="providerEndpoint" placeholder="https://api.openai.com/v1"></div>
<div class="form-group"><label>API Key</label><input type="password" id="providerApiKey" placeholder="sk-..."></div>
<div class="form-group"><label>Default Model</label><input type="text" id="providerDefaultModel" placeholder="gpt-4o"></div>
<div class="form-row"><button class="btn-small btn-primary" id="providerCreateBtn">Save</button><button class="btn-small" id="providerCancelBtn">Cancel</button></div>
</div>
</section>
<div id="userProvidersDisabled" class="settings-notice" style="display:none">
<span>⚠️</span> User-configured providers have been disabled by the administrator.
</div>
</div>
<!-- Models Tab -->
<div class="settings-tab-content" id="settingsModelsTab" style="display:none">
<p class="section-hint">Models available from your providers and global configs.</p>
<div id="userModelList" class="model-list-grid"><div class="empty-hint">Loading models...</div></div>
</div>
</div>
<div class="modal-footer"><button class="btn-primary" id="settingsSaveBtn">Save Settings</button></div>
</div>
</div>
<!-- ── Admin Modal ─────────────────────────── -->
<div class="modal-overlay" id="adminModal">
<div class="modal modal-wide">
<div class="modal-header"><h2>Admin Panel</h2><button class="modal-close" id="adminCloseBtn"></button></div>
<div class="modal-tabs admin-tabs">
<button class="admin-tab active" data-tab="users">Users</button>
<button class="admin-tab" data-tab="providers">Providers</button>
<button class="admin-tab" data-tab="models">Models</button>
<button class="admin-tab" data-tab="presets">Presets</button>
<button class="admin-tab" data-tab="settings">Settings</button>
<button class="admin-tab" data-tab="stats">Stats</button>
</div>
<div class="modal-body">
<div class="admin-tab-content" id="adminUsersTab">
<div class="admin-toolbar">
<button class="btn-small btn-primary" id="adminAddUserBtn">+ Add User</button>
</div>
<div id="adminAddUserForm" style="display:none" class="admin-inline-form">
<div class="form-row">
<div class="form-group"><label>Username</label><input type="text" id="adminNewUsername" placeholder="username"></div>
<div class="form-group"><label>Email</label><input type="email" id="adminNewEmail" placeholder="email@example.com"></div>
</div>
<div class="form-row">
<div class="form-group"><label>Password</label><input type="password" id="adminNewPassword" placeholder="min 8 chars"></div>
<div class="form-group"><label>Role</label><select id="adminNewRole"><option value="user">User</option><option value="admin">Admin</option></select></div>
</div>
<div class="form-row"><button class="btn-small btn-primary" id="adminCreateUserBtn">Create</button><button class="btn-small" id="adminCancelUserBtn">Cancel</button></div>
</div>
<div id="adminUserList"></div>
</div>
<div class="admin-tab-content" id="adminProvidersTab" style="display:none">
<div class="admin-toolbar">
<button class="btn-small btn-primary" id="adminAddProviderBtn">+ Add Global Provider</button>
</div>
<div id="adminAddProviderForm" style="display:none" class="admin-inline-form">
<div class="form-row">
<div class="form-group"><label>Name</label><input type="text" id="adminProvName" placeholder="OpenAI Production"></div>
<div class="form-group"><label>Provider</label><select id="adminProvType"><option value="openai">OpenAI-compatible</option><option value="anthropic">Anthropic</option><option value="venice">Venice.ai</option><option value="openrouter">OpenRouter</option></select></div>
</div>
<div class="form-group"><label>Endpoint</label><input type="text" id="adminProvEndpoint" placeholder="https://api.openai.com/v1"></div>
<div class="form-row">
<div class="form-group"><label>API Key</label><input type="password" id="adminProvKey" placeholder="sk-..."></div>
<div class="form-group"><label>Default Model</label><input type="text" id="adminProvModel" placeholder="gpt-4o"></div>
</div>
<div class="form-row"><button class="btn-small btn-primary" id="adminCreateProvBtn">Save</button><button class="btn-small" id="adminCancelProvBtn">Cancel</button></div>
</div>
<div id="adminProviderList"></div>
</div>
<div class="admin-tab-content" id="adminModelsTab" style="display:none">
<div class="admin-toolbar">
<button class="btn-small btn-primary" id="adminFetchModelsBtn">⟳ Fetch Models</button>
<button class="btn-small" onclick="bulkToggleModels(true)">Enable All</button>
<button class="btn-small" onclick="bulkToggleModels(false)">Disable All</button>
<span class="admin-hint" id="adminModelsHint"></span>
</div>
<div id="adminModelList"></div>
</div>
<div class="admin-tab-content" id="adminPresetsTab" style="display:none">
<div class="admin-toolbar">
<button class="btn-small btn-primary" id="adminAddPresetBtn">+ New Global Preset</button>
</div>
<div id="adminAddPresetForm" style="display:none" class="admin-inline-form">
<div class="form-row">
<div class="form-group"><label>Name</label><input type="text" id="adminPresetName" placeholder="Code Reviewer"></div>
<div class="form-group"><label>Icon</label><input type="text" id="adminPresetIcon" placeholder="🔍" maxlength="4" style="width:60px"></div>
</div>
<div class="avatar-upload-row" style="margin-bottom:0.75rem">
<div class="avatar-preview avatar-preview-sm" id="presetAvatarPreview">
<span id="presetAvatarPlaceholder">🤖</span>
</div>
<div class="avatar-actions">
<button class="btn-small" type="button" id="presetAvatarUploadBtn">Upload Avatar</button>
<button class="btn-small btn-danger" type="button" id="presetAvatarRemoveBtn" style="display:none">Remove</button>
<input type="file" id="presetAvatarFileInput" accept="image/png,image/jpeg,image/gif" style="display:none">
<div class="form-hint">Optional · 128×128 · shown in chat</div>
</div>
</div>
<div class="form-group"><label>Description</label><input type="text" id="adminPresetDesc" placeholder="Reviews code for best practices"></div>
<div class="form-row">
<div class="form-group"><label>Base Model</label><select id="adminPresetModel"></select></div>
<div class="form-group"><label>Provider Config</label><select id="adminPresetConfig"><option value="">Auto-resolve</option></select></div>
</div>
<div class="form-group"><label>System Prompt</label><textarea id="adminPresetPrompt" rows="3" placeholder="You are a code reviewer..."></textarea></div>
<div class="form-row">
<div class="form-group"><label>Temperature</label><input type="number" id="adminPresetTemp" step="0.1" min="0" max="2" placeholder="default"></div>
<div class="form-group"><label>Max Tokens</label><input type="number" id="adminPresetMaxTokens" placeholder="default"></div>
</div>
<div class="form-row"><button class="btn-small btn-primary" id="adminCreatePresetBtn">Create</button><button class="btn-small" id="adminCancelPresetBtn">Cancel</button></div>
</div>
<div id="adminPresetList"></div>
</div>
<div class="admin-tab-content" id="adminSettingsTab" style="display:none">
<section class="settings-section">
<h3>Registration</h3>
<label class="checkbox-label"><input type="checkbox" id="adminRegToggle" checked> Allow new user registration</label>
<div class="form-group" style="margin-top: 8px;">
<label>Default state for new accounts</label>
<select id="adminRegDefaultState">
<option value="active">Active — immediate access</option>
<option value="pending">Pending — require admin approval</option>
</select>
</div>
</section>
<section class="settings-section">
<h3>User Providers</h3>
<label class="checkbox-label"><input type="checkbox" id="adminUserProvidersToggle" checked> Allow users to configure their own API providers</label>
<p class="section-hint">When disabled, users can only use admin-configured global providers.</p>
</section>
<section class="settings-section">
<h3>Environment Banner</h3>
<label class="checkbox-label"><input type="checkbox" id="adminBannerEnabled"> Show environment banner</label>
<div id="bannerConfigFields" style="display:none">
<div class="form-group">
<label>Preset</label>
<select id="adminBannerPreset"><option value="">Custom</option></select>
</div>
<div class="form-group"><label>Banner Text</label><input type="text" id="adminBannerText" placeholder="DEVELOPMENT"></div>
<div class="form-row">
<div class="form-group"><label>Position</label>
<select id="adminBannerPosition"><option value="both">Top &amp; Bottom</option><option value="top">Top only</option><option value="bottom">Bottom only</option></select>
</div>
</div>
<div class="form-row">
<div class="form-group"><label>Background</label><div class="color-input-wrap"><input type="color" id="adminBannerBg" value="#007a33"><input type="text" id="adminBannerBgHex" class="color-hex" value="#007a33" maxlength="7"></div></div>
<div class="form-group"><label>Text Color</label><div class="color-input-wrap"><input type="color" id="adminBannerFg" value="#ffffff"><input type="text" id="adminBannerFgHex" class="color-hex" value="#ffffff" maxlength="7"></div></div>
</div>
<div class="banner-preview" id="bannerPreview">PREVIEW</div>
</div>
</section>
<button class="btn-primary btn-small" id="adminSaveSettings">Save Settings</button>
</div>
<div class="admin-tab-content" id="adminStatsTab" style="display:none"><div id="adminStats"></div></div>
</div>
</div>
</div>
<!-- ── Notes Modal ────────────────────────── -->
<div class="modal-overlay" id="notesModal">
<div class="modal modal-wide">
<div class="modal-header">
<h2>Notes</h2>
<div class="notes-header-actions">
<button class="btn-small" id="notesSelectModeBtn">Select</button>
<button class="btn-small btn-primary" id="notesNewBtn">+ New Note</button>
<button class="modal-close" onclick="closeModal('notesModal')"></button>
</div>
</div>
<div class="notes-toolbar">
<div class="notes-search-wrap">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input type="text" id="notesSearchInput" placeholder="Search notes..." autocomplete="off">
</div>
<select id="notesFolderFilter" class="notes-filter-select">
<option value="">All folders</option>
</select>
<select id="notesSortSelect" class="notes-filter-select" title="Sort by">
<option value="updated_desc">Updated ↓</option>
<option value="updated_asc">Updated ↑</option>
<option value="created_desc">Created ↓</option>
<option value="created_asc">Created ↑</option>
<option value="title_asc">Title AZ</option>
<option value="title_desc">Title ZA</option>
</select>
</div>
<div class="notes-selection-bar" id="notesSelectionBar" style="display:none">
<label class="notes-select-all"><input type="checkbox" id="notesSelectAll"> <span id="notesSelectedCount">0</span> selected</label>
<button class="btn-small btn-danger" id="notesDeleteSelectedBtn">Delete selected</button>
<button class="btn-small" id="notesCancelSelectBtn">Cancel</button>
</div>
<div class="modal-body notes-body">
<!-- List view (default) -->
<div id="notesListView">
<div id="notesList" class="notes-list">
<div class="notes-empty">No notes yet. Create one or ask the AI to save a note.</div>
</div>
</div>
<!-- Editor view (hidden by default) -->
<div id="notesEditorView" style="display:none">
<div class="notes-editor">
<div class="notes-editor-toolbar">
<button class="notes-back-btn" id="notesBackBtn">← Back to list</button>
<div class="notes-mode-toggle">
<button class="btn-small" id="noteEditBtn" style="display:none">Edit</button>
<button class="btn-small" id="notePreviewBtn" style="display:none">Preview</button>
</div>
</div>
<!-- Read mode: rendered markdown -->
<div id="noteReadMode" style="display:none">
<h3 class="note-read-title" id="noteReadTitle"></h3>
<div class="note-read-meta" id="noteReadMeta"></div>
<div class="note-read-content msg-text" id="noteReadContent"></div>
<div class="notes-editor-actions">
<button class="btn-small btn-primary" id="noteEditBtn2">Edit</button>
<button class="btn-small btn-danger" id="noteDeleteBtn2" style="display:none">Delete</button>
</div>
</div>
<!-- Edit mode: form fields -->
<div id="noteEditMode">
<div class="form-group"><input type="text" id="noteEditorTitle" placeholder="Note title" class="notes-title-input"></div>
<div class="form-row">
<div class="form-group"><input type="text" id="noteEditorFolder" placeholder="Folder (e.g. /work/meetings)" class="notes-folder-input"></div>
<div class="form-group"><input type="text" id="noteEditorTags" placeholder="Tags (comma-separated)" class="notes-tags-input"></div>
</div>
<div class="form-group"><textarea id="noteEditorContent" rows="12" placeholder="Note content (Markdown supported)..." class="notes-content-input"></textarea></div>
<div class="notes-editor-actions">
<button class="btn-small btn-primary" id="noteSaveBtn">Save</button>
<button class="btn-small" id="noteCancelEditBtn" style="display:none">Cancel</button>
<button class="btn-small btn-danger" id="noteDeleteBtn" style="display:none">Delete</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Debug Modal (Ctrl+Shift+L) -->
<div class="modal-overlay" id="debugModal">
<div class="modal debug-modal">
<div class="modal-header"><h2>Debug Log</h2><button class="modal-close" onclick="closeDebugModal()"></button></div>
<div class="modal-tabs debug-tabs">
<button class="debug-tab active" data-tab="console" onclick="switchDebugTab('console')">Console (<span id="debugConsoleCount">0</span>)</button>
<button class="debug-tab" data-tab="network" onclick="switchDebugTab('network')">Network (<span id="debugNetworkCount">0</span>)</button>
<button class="debug-tab" data-tab="state" onclick="switchDebugTab('state')">State</button>
</div>
<div class="modal-body debug-modal-body">
<div class="debug-tab-content" id="debugConsoleTab">
<div class="debug-toolbar">
<label class="debug-check"><input type="checkbox" id="debugFilterErrors"> Errors only</label>
<label class="debug-check"><input type="checkbox" id="debugAutoScroll" checked> Auto-scroll</label>
</div>
<div id="debugConsoleContent" class="debug-content"></div>
</div>
<div class="debug-tab-content" id="debugNetworkTab" style="display:none"><div id="debugNetworkContent" class="debug-content"></div></div>
<div class="debug-tab-content" id="debugStateTab" style="display:none"><div id="debugStateContent" class="debug-content"></div></div>
</div>
<div class="modal-footer debug-footer">
<button class="btn-primary btn-small" onclick="runDebugDiagnostics()">Diagnostics</button>
<div style="margin-left:auto;display:flex;gap:0.5rem">
<button class="btn-small" onclick="clearDebugLog()">Clear</button>
<button class="btn-small" onclick="copyDebugLog()">Copy</button>
<button class="btn-small" onclick="exportDebugLog()">Export</button>
</div>
</div>
</div>
</div>
<div class="toast-container" id="toastContainer"></div>
<!-- Vendor libs -->
<script src="vendor/marked.min.js" onerror="this.onerror=null;this.src='https://cdnjs.cloudflare.com/ajax/libs/marked/16.3.0/lib/marked.umd.min.js'"></script>
<script src="vendor/purify.min.js" onerror="this.onerror=null;this.src='https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.4/purify.min.js'"></script>
<script src="js/debug.js?v=%%APP_VERSION%%"></script>
<script src="js/events.js?v=%%APP_VERSION%%"></script>
<script src="js/api.js?v=%%APP_VERSION%%"></script>
<script src="js/ui.js?v=%%APP_VERSION%%"></script>
<script src="js/app.js?v=%%APP_VERSION%%"></script>
</body>
</html>