525 lines
28 KiB
HTML
525 lines
28 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<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=0.4.1">
|
|
</head>
|
|
<body>
|
|
<div id="appContainer" class="app-hidden" style="display:none">
|
|
<header class="header">
|
|
<h1>🔀 Chat Switchboard</h1>
|
|
<div class="header-actions">
|
|
<div class="connection-status offline" id="connectionStatus" title="Unmanaged mode">
|
|
<span class="status-dot"></span> Offline
|
|
</div>
|
|
<button class="btn btn-secondary" id="toggleSidebarBtn" title="Toggle Sidebar (Ctrl+B)">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
<line x1="9" y1="3" x2="9" y2="21"></line>
|
|
</svg>
|
|
</button>
|
|
<div class="dropdown">
|
|
<button class="btn btn-secondary" id="exportBtn" title="Export Chat">
|
|
📥 Export
|
|
</button>
|
|
<div class="dropdown-content" id="exportDropdown">
|
|
<button class="dropdown-item" onclick="exportChat('markdown')">📄 Markdown</button>
|
|
<button class="dropdown-item" onclick="exportChat('json')">📋 JSON</button>
|
|
<button class="dropdown-item" onclick="exportChat('text')">📝 Plain Text</button>
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-secondary admin-only" id="adminBtn" title="Admin Panel" style="display:none">
|
|
🛡️ Admin
|
|
</button>
|
|
<button class="btn btn-secondary" id="settingsBtn" title="Settings (Ctrl+,)">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<circle cx="12" cy="12" r="3"></circle>
|
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
|
</svg>
|
|
Settings
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="main-container">
|
|
<aside class="sidebar" id="sidebar">
|
|
<div class="sidebar-header">
|
|
<span class="sidebar-title">Chat History</span>
|
|
</div>
|
|
<button class="btn btn-primary new-chat-btn" id="newChatBtn">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
</svg>
|
|
New Chat
|
|
</button>
|
|
<div class="chat-history" id="chatHistory"></div>
|
|
</aside>
|
|
|
|
<main class="chat-area">
|
|
<div class="chat-messages" id="chatMessages">
|
|
<div class="empty-state" id="emptyState">
|
|
<div class="empty-state-icon">
|
|
<svg 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"></path>
|
|
</svg>
|
|
</div>
|
|
<h2>Start a Conversation</h2>
|
|
<p>Configure your API settings and start chatting</p>
|
|
<button class="btn btn-primary" style="margin-top: 1.5rem;" id="configureBtn">⚙️ Configure API</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="input-area">
|
|
<div class="input-container">
|
|
<div class="input-top-bar">
|
|
<div class="model-selector">
|
|
<label for="quickModel">Model:</label>
|
|
<select id="quickModel">
|
|
<option value="">-- Configure in Settings --</option>
|
|
</select>
|
|
<button class="btn btn-small btn-secondary" id="quickFetchBtn" title="Refresh models">🔄</button>
|
|
</div>
|
|
<div class="input-shortcuts">
|
|
<kbd>Enter</kbd> send · <kbd>Shift+Enter</kbd> newline · <kbd>Esc</kbd> stop
|
|
</div>
|
|
</div>
|
|
<div class="input-wrapper">
|
|
<textarea id="messageInput" placeholder="Type your message..." rows="1"></textarea>
|
|
<div class="input-actions">
|
|
<div class="input-left">
|
|
<button class="btn btn-small btn-secondary" id="regenerateBtn" title="Regenerate last response" style="display: none;">🔄 Regenerate</button>
|
|
</div>
|
|
<div style="display: flex; gap: 0.5rem;">
|
|
<button class="stop-btn" id="stopBtn">⏹ Stop</button>
|
|
<button class="send-btn" id="sendBtn">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<line x1="22" y1="2" x2="11" y2="13"></line>
|
|
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
|
</svg>
|
|
Send
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Settings Modal -->
|
|
<div class="modal-overlay" id="settingsModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2>⚙️ Settings</h2>
|
|
<button class="modal-close" id="closeModalBtn">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- Mode indicator -->
|
|
<div class="settings-mode-badge" id="settingsModeBadge"></div>
|
|
|
|
<!-- Profile (managed mode only) -->
|
|
<div class="settings-section" id="profileSection" style="display:none">
|
|
<h3 class="settings-section-title">Profile</h3>
|
|
<div class="form-group">
|
|
<label for="profileDisplayName">Display Name</label>
|
|
<input type="text" id="profileDisplayName" placeholder="How you want to be called">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="profileEmail">Email</label>
|
|
<input type="email" id="profileEmail" placeholder="you@example.com">
|
|
</div>
|
|
<div class="profile-password-row">
|
|
<button class="btn btn-secondary btn-small" id="profileChangePwBtn">🔑 Change Password</button>
|
|
</div>
|
|
<div id="profileChangePwForm" style="display:none">
|
|
<div class="form-group">
|
|
<label for="profileCurrentPw">Current Password</label>
|
|
<input type="password" id="profileCurrentPw" placeholder="••••••••">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="profileNewPw">New Password</label>
|
|
<input type="password" id="profileNewPw" placeholder="At least 8 characters">
|
|
</div>
|
|
<button class="btn btn-primary btn-small" id="profileSavePwBtn">Update Password</button>
|
|
</div>
|
|
<hr class="settings-divider">
|
|
</div>
|
|
|
|
<!-- API Settings (unmanaged / offline only) -->
|
|
<div id="unmanagedSettings">
|
|
<div class="form-group">
|
|
<label for="apiEndpoint">API Endpoint</label>
|
|
<input type="text" id="apiEndpoint" placeholder="https://api.openai.com/v1">
|
|
<small>Base URL for the OpenAI-compatible API</small>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="apiKey">API Key</label>
|
|
<input type="password" id="apiKey" placeholder="sk-...">
|
|
<small>Your API key for authentication</small>
|
|
</div>
|
|
</div> <!-- /unmanagedSettings -->
|
|
|
|
<!-- API Providers (managed mode only) -->
|
|
<div id="managedProviders" style="display:none">
|
|
<h3 class="settings-section-title">API Providers</h3>
|
|
<div id="providerList" class="provider-list"></div>
|
|
<div class="provider-add-toggle">
|
|
<button class="btn btn-secondary btn-small" id="providerShowAddBtn">+ Add Provider</button>
|
|
</div>
|
|
<div class="provider-add-form" id="providerAddForm" style="display:none">
|
|
<div class="form-group">
|
|
<label for="providerName">Name</label>
|
|
<input type="text" id="providerName" placeholder="e.g. My OpenAI Key">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="providerType">Provider</label>
|
|
<select id="providerType">
|
|
<option value="openai">OpenAI-compatible</option>
|
|
<option value="anthropic">Anthropic</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="providerEndpoint">Endpoint</label>
|
|
<input type="text" id="providerEndpoint" placeholder="https://api.openai.com/v1">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="providerApiKey">API Key</label>
|
|
<input type="password" id="providerApiKey" placeholder="sk-...">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="providerDefaultModel">Default Model (optional)</label>
|
|
<input type="text" id="providerDefaultModel" placeholder="gpt-4o">
|
|
</div>
|
|
<div class="admin-form-row">
|
|
<button class="btn btn-primary btn-small" id="providerCreateBtn">Save</button>
|
|
<button class="btn btn-secondary btn-small" id="providerCancelBtn">Cancel</button>
|
|
</div>
|
|
</div>
|
|
<hr class="settings-divider">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="model">Default Model</label>
|
|
<div style="display: flex; gap: 0.5rem;">
|
|
<select id="model" style="flex: 1;">
|
|
<option value="">-- Select or type below --</option>
|
|
</select>
|
|
<button type="button" class="btn btn-secondary" id="fetchModelsBtn" title="Fetch available models">
|
|
🔄 Fetch
|
|
</button>
|
|
</div>
|
|
<input type="text" id="modelCustom" placeholder="Or type custom model name..." style="margin-top: 0.5rem;">
|
|
<small>Select from list or enter custom model name</small>
|
|
</div>
|
|
|
|
<div class="toggle-group">
|
|
<span class="toggle-label">Stream Responses</span>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox" id="streamResponse" checked>
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="toggle-group">
|
|
<span class="toggle-label">Save Chat History</span>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox" id="saveHistory" checked>
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="toggle-group">
|
|
<span class="toggle-label">Show Thinking Blocks</span>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox" id="showThinking" checked>
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="systemPrompt">System Prompt (Optional)</label>
|
|
<textarea id="systemPrompt" rows="3" placeholder="You are a helpful AI assistant."></textarea>
|
|
<small>Instructions that define the AI's behavior</small>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="maxTokens">Max Tokens</label>
|
|
<input type="number" id="maxTokens" value="4096" min="1" max="32768">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="temperature">Temperature</label>
|
|
<input type="number" id="temperature" value="0.7" min="0" max="2" step="0.1">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="topP">Top P</label>
|
|
<input type="number" id="topP" value="1" min="0" max="1" step="0.1">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="presencePenalty">Presence Penalty</label>
|
|
<input type="number" id="presencePenalty" value="0" min="-2" max="2" step="0.1">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" id="cancelBtn">Cancel</button>
|
|
<button class="btn btn-primary" id="saveBtn">💾 Save Settings</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Admin Panel Modal -->
|
|
<div class="modal-overlay" id="adminModal">
|
|
<div class="modal admin-modal">
|
|
<div class="modal-header">
|
|
<h2>🛡️ Admin Panel</h2>
|
|
<button class="modal-close" id="adminCloseBtn">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="admin-tabs">
|
|
<button class="admin-tab active" data-tab="users">Users</button>
|
|
<button class="admin-tab" data-tab="models">Models</button>
|
|
<button class="admin-tab" data-tab="providers">Providers</button>
|
|
<button class="admin-tab" data-tab="settings">Settings</button>
|
|
<button class="admin-tab" data-tab="stats">Stats</button>
|
|
</div>
|
|
|
|
<!-- Users Tab -->
|
|
<div class="admin-tab-content active" id="adminUsersTab">
|
|
<div class="admin-add-user-toggle">
|
|
<button class="btn btn-primary btn-small" id="adminShowAddUser">+ Add User</button>
|
|
</div>
|
|
<div class="admin-add-user-form" id="adminAddUserForm" style="display:none">
|
|
<div class="admin-form-row">
|
|
<input type="text" id="adminNewUsername" placeholder="Username" class="admin-input">
|
|
<input type="email" id="adminNewEmail" placeholder="Email" class="admin-input">
|
|
</div>
|
|
<div class="admin-form-row">
|
|
<input type="password" id="adminNewPassword" placeholder="Password (min 8)" class="admin-input">
|
|
<select id="adminNewRole" class="admin-role-select">
|
|
<option value="user">user</option>
|
|
<option value="admin">admin</option>
|
|
<option value="moderator">moderator</option>
|
|
</select>
|
|
</div>
|
|
<div class="admin-form-row">
|
|
<button class="btn btn-primary btn-small" id="adminCreateUserBtn">Create</button>
|
|
<button class="btn btn-secondary btn-small" id="adminCancelAddUser">Cancel</button>
|
|
</div>
|
|
</div>
|
|
<div id="adminUserList" class="admin-user-list">Loading...</div>
|
|
</div>
|
|
|
|
<!-- Reset Password Dialog (inline overlay) -->
|
|
<div class="admin-reset-dialog" id="adminResetDialog" style="display:none">
|
|
<div class="admin-reset-card">
|
|
<h3>Reset Password</h3>
|
|
<p id="adminResetTarget"></p>
|
|
<input type="password" id="adminResetNewPw" placeholder="New password (min 8)" class="admin-input">
|
|
<div class="admin-form-row">
|
|
<button class="btn btn-primary btn-small" id="adminResetConfirmBtn">Reset</button>
|
|
<button class="btn btn-secondary btn-small" id="adminResetCancelBtn">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Models Tab -->
|
|
<div class="admin-tab-content" id="adminModelsTab" style="display:none">
|
|
<div class="admin-models-header">
|
|
<p class="admin-hint">Manage which models are available to users. Fetch from configured providers, then enable/disable and tag capabilities.</p>
|
|
<button class="btn btn-primary btn-small" id="adminFetchModelsBtn">🔄 Fetch Models</button>
|
|
</div>
|
|
<div id="adminModelList" class="admin-model-list">
|
|
<div class="admin-empty">No models configured. Add a provider first, then fetch models.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Providers Tab -->
|
|
<div class="admin-tab-content" id="adminProvidersTab" style="display:none">
|
|
<p class="admin-hint">Global providers are available to all users. Users can also add their own.</p>
|
|
<div id="adminProviderList" class="provider-list"></div>
|
|
<div class="provider-add-toggle">
|
|
<button class="btn btn-secondary btn-small" id="adminProviderShowAddBtn">+ Add Global Provider</button>
|
|
</div>
|
|
<div class="provider-add-form" id="adminProviderAddForm" style="display:none">
|
|
<div class="form-group">
|
|
<label for="adminProviderName">Name</label>
|
|
<input type="text" id="adminProviderName" placeholder="e.g. Shared OpenAI">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="adminProviderType">Provider</label>
|
|
<select id="adminProviderType">
|
|
<option value="openai">OpenAI-compatible</option>
|
|
<option value="anthropic">Anthropic</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="adminProviderEndpoint">Endpoint</label>
|
|
<input type="text" id="adminProviderEndpoint" placeholder="https://api.openai.com/v1">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="adminProviderApiKey">API Key</label>
|
|
<input type="password" id="adminProviderApiKey" placeholder="sk-...">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="adminProviderDefaultModel">Default Model (optional)</label>
|
|
<input type="text" id="adminProviderDefaultModel" placeholder="gpt-4o">
|
|
</div>
|
|
<div class="admin-form-row">
|
|
<button class="btn btn-primary btn-small" id="adminProviderCreateBtn">Save</button>
|
|
<button class="btn btn-secondary btn-small" id="adminProviderCancelBtn">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Tab -->
|
|
<div class="admin-tab-content" id="adminSettingsTab" style="display:none">
|
|
<div class="form-group">
|
|
<label class="toggle-label">
|
|
<input type="checkbox" id="adminRegToggle" checked>
|
|
<span>Allow new user registration</span>
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="adminSiteName">Site Name</label>
|
|
<input type="text" id="adminSiteName" value="Chat Switchboard">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="adminTagline">Tagline</label>
|
|
<input type="text" id="adminTagline" value="Multi-Model AI Chat">
|
|
</div>
|
|
<button class="btn btn-primary" id="adminSaveSettings">💾 Save</button>
|
|
</div>
|
|
|
|
<!-- Stats Tab -->
|
|
<div class="admin-tab-content" id="adminStatsTab" style="display:none">
|
|
<div id="adminStats" class="admin-stats">Loading...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div> <!-- /appContainer -->
|
|
|
|
<!-- ── Auth Splash Gate ────────────────── -->
|
|
<div class="splash-gate" id="splashGate" style="display:none">
|
|
<div class="splash-card">
|
|
<div class="splash-brand">
|
|
<div class="splash-logo">🔀</div>
|
|
<h1>Chat Switchboard</h1>
|
|
<p class="splash-tagline">Multi-Model AI Chat</p>
|
|
</div>
|
|
<div class="splash-form">
|
|
<div class="auth-tabs">
|
|
<button class="auth-tab active" id="authTabLogin">Sign In</button>
|
|
<button class="auth-tab" id="authTabRegister">Register</button>
|
|
</div>
|
|
<div id="authLoginForm">
|
|
<div class="form-group">
|
|
<label for="authLogin">Username or Email</label>
|
|
<input type="text" id="authLogin" placeholder="jeff or jeff@example.com" autocomplete="username">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="authPassword">Password</label>
|
|
<input type="password" id="authPassword" placeholder="••••••••" autocomplete="current-password">
|
|
</div>
|
|
</div>
|
|
<div id="authRegisterForm" style="display:none;">
|
|
<div class="form-group">
|
|
<label for="authUsername">Username</label>
|
|
<input type="text" id="authUsername" placeholder="Choose a username" autocomplete="username">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="authEmail">Email</label>
|
|
<input type="email" id="authEmail" placeholder="you@example.com" autocomplete="email">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="authRegPassword">Password</label>
|
|
<input type="password" id="authRegPassword" placeholder="At least 8 characters" autocomplete="new-password">
|
|
</div>
|
|
</div>
|
|
<div class="auth-error" id="authError"></div>
|
|
<div class="splash-actions">
|
|
<button class="btn btn-primary btn-full" id="authLoginBtn">Sign In</button>
|
|
<button class="btn btn-primary btn-full" id="authRegisterBtn" style="display:none;">Create Account</button>
|
|
<div class="splash-divider"><span>or</span></div>
|
|
<button class="btn btn-secondary btn-full" id="authSkipBtn">Use Offline</button>
|
|
</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="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">
|
|
<!-- Console Tab -->
|
|
<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>
|
|
<!-- Network Tab -->
|
|
<div class="debug-tab-content" id="debugNetworkTab" style="display:none">
|
|
<div id="debugNetworkContent" class="debug-content"></div>
|
|
</div>
|
|
<!-- State Tab -->
|
|
<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 btn-primary btn-small" onclick="runDebugDiagnostics()">🔍 Run Diagnostics</button>
|
|
<div style="margin-left: auto; display: flex; gap: 0.5rem;">
|
|
<button class="btn btn-secondary btn-small" onclick="clearDebugLog()">🗑️ Clear</button>
|
|
<button class="btn btn-secondary btn-small" onclick="copyDebugLog()">📋 Copy</button>
|
|
<button class="btn btn-secondary btn-small" onclick="exportDebugLog()">📥 Export</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="toast-container" id="toastContainer"></div>
|
|
|
|
<script src="js/debug.js?v=0.4.1"></script>
|
|
<script src="js/storage.js?v=0.4.1"></script>
|
|
<script src="js/backend.js?v=0.4.1"></script>
|
|
<script src="js/state.js?v=0.4.1"></script>
|
|
<script src="js/api.js?v=0.4.1"></script>
|
|
<script src="js/ui.js?v=0.4.1"></script>
|
|
<script src="js/admin.js?v=0.4.1"></script>
|
|
<script src="js/app.js?v=0.4.1"></script>
|
|
</body>
|
|
</html>
|