Initial commit

This commit is contained in:
2026-02-03 17:20:06 -05:00
commit 701e387a5b
16 changed files with 3453 additions and 0 deletions

206
src/index.html Normal file
View File

@@ -0,0 +1,206 @@
<!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="stylesheet" href="css/styles.css">
</head>
<body>
<header class="header">
<h1>🔀 Chat Switchboard</h1>
<div class="header-actions">
<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" 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">
<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 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>
<div class="toast-container" id="toastContainer"></div>
<script src="js/storage.js"></script>
<script src="js/state.js"></script>
<script src="js/api.js"></script>
<script src="js/ui.js"></script>
<script src="js/app.js"></script>
</body>
</html>