Files
DemoCoder/web/index.html
2026-04-21 13:19:17 +03:00

407 lines
25 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">
<title>GoCoder</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<link rel="stylesheet" href="style.css">
<style>
.conversation-item {
border-left: 3px solid transparent;
}
.conversation-item:hover {
border-left-color: #cbd5e1;
}
.conversation-item.active {
border-left-color: #3b82f6;
background-color: #eff6ff;
color: #1d4ed8;
}
.conversation-item.active .text-gray-400 {
color: #3b82f6;
}
.loading-dots {
display: inline-flex;
align-items: center;
}
.loading-dots .dot {
animation: blink 1.4s infinite both;
animation-duration: 1.4s;
}
.loading-dots .dot:nth-child(2) {
animation-delay: 0.2s;
}
.loading-dots .dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes blink {
0%, 80%, 100% { opacity: 0; }
40% { opacity: 1; }
}
.sidebar {
transition: transform 0.2s ease;
}
@media (max-width: 768px) {
.sidebar {
position: fixed;
z-index: 50;
height: 100vh;
transform: translateX(-100%);
}
.sidebar.open {
transform: translateX(0);
}
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.3);
z-index: 40;
}
.sidebar-overlay.open {
display: block;
}
}
</style>
</head>
<body class="min-h-screen bg-gray-50">
<div class="flex h-screen">
<!-- Sidebar -->
<div class="sidebar w-72 bg-white border-r border-gray-200 flex flex-col" id="sidebar">
<div class="p-4 border-b border-gray-200">
<div class="flex items-center justify-between mb-3">
<h2 class="text-lg font-semibold text-gray-800">Workspaces</h2>
<div class="flex gap-1">
<button id="newGroupBtn" class="p-1.5 rounded-lg hover:bg-gray-100 text-gray-600 transition-colors" title="New workspace">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
</button>
<button id="newConversationBtn" class="p-1.5 rounded-lg hover:bg-gray-100 text-gray-600 transition-colors" title="New conversation">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
</div>
</div>
</div>
<div id="conversationList" class="flex-1 overflow-y-auto p-2 space-y-3">
</div>
<div class="p-3 border-t border-gray-200">
<button id="mobileMenuBtn" class="md:hidden w-full text-center text-sm text-gray-500 hover:text-gray-700 py-2">
Close sidebar
</button>
</div>
</div>
<!-- Sidebar overlay for mobile -->
<div class="sidebar-overlay" id="sidebarOverlay"></div>
<!-- Group settings modal -->
<div id="groupSettingsModal" class="fixed inset-0 z-50 hidden items-center justify-center" style="background: rgba(0,0,0,0.4);">
<div class="bg-white rounded-2xl shadow-xl w-full max-w-md mx-4 overflow-hidden">
<div class="p-6">
<div class="flex items-center justify-between mb-5">
<h3 class="text-lg font-semibold text-gray-800">Workspace Settings</h3>
<button id="closeGroupSettingsModal" class="p-1.5 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
</div>
<form id="groupSettingsForm">
<label class="block text-sm font-medium text-gray-700 mb-1.5">Workspace name</label>
<input
type="text"
id="groupSettingsNameInput"
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800"
required
>
<label class="block text-sm font-medium text-gray-700 mb-1.5 mt-4">Workspace folder</label>
<div id="groupSettingsLocation" class="hidden mb-3 px-3 py-2 bg-gray-50 border border-gray-200 rounded-xl">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-gray-400 flex-shrink-0"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg>
<span id="groupSettingsLocationText" class="text-sm text-gray-600 truncate"></span>
</div>
</div>
<button type="button" id="groupSettingsPickBtn" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-gray-700 font-medium hover:bg-gray-50 transition-colors flex items-center justify-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
Pick Folder
</button>
<div class="flex gap-3 mt-5">
<button type="button" id="cancelGroupSettingsBtn" class="flex-1 px-4 py-2.5 border border-gray-300 rounded-xl text-gray-700 font-medium hover:bg-gray-50 transition-colors">
Cancel
</button>
<button type="submit" class="flex-1 px-4 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-xl transition-colors">
Save
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Runtime settings modal -->
<div id="settingsModal" class="fixed inset-0 z-50 hidden items-center justify-center p-4" style="background: rgba(0,0,0,0.4);">
<div class="bg-white rounded-2xl shadow-xl w-full max-w-2xl max-h-[90vh] overflow-hidden">
<div class="max-h-[90vh] overflow-y-auto">
<div class="p-6">
<div class="flex items-center justify-between mb-4">
<div>
<h3 class="text-lg font-semibold text-gray-800">Runtime Settings</h3>
<p class="mt-1 text-sm text-gray-500">Edit the model endpoint, generation params, server settings, and compaction budget used by the chat runtime.</p>
</div>
<button id="closeSettingsModal" class="p-1.5 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600 transition-colors flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
</div>
<form id="settingsForm">
<div class="space-y-4">
<div class="rounded-2xl border border-gray-200 bg-gray-50/70 p-4">
<div class="mb-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500">
<span>Connection</span>
</div>
<div class="grid gap-4 md:grid-cols-2">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsProvider">Provider</label>
<input type="text" id="settingsProvider" required class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsModel">Model</label>
<input type="text" id="settingsModel" required class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsBaseURL">Base URL</label>
<input type="url" id="settingsBaseURL" required class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsAPIKey">API key</label>
<input type="password" id="settingsAPIKey" autocomplete="off" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
<p class="mt-1 text-xs text-gray-500">Leave as-is to keep the current key or replace it with a new one.</p>
</div>
</div>
</div>
<div class="rounded-2xl border border-gray-200 bg-gray-50/70 p-4">
<div class="mb-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500">
<span>Server</span>
</div>
<div class="grid gap-4 md:grid-cols-2">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsServerHost">Host</label>
<input type="text" id="settingsServerHost" required placeholder="localhost" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsServerAddr">Address</label>
<input type="text" id="settingsServerAddr" required placeholder=":8080" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
</div>
<div class="md:col-span-2">
<p class="text-xs text-gray-500">Server settings are saved immediately but only take effect after you restart the app.</p>
</div>
</div>
</div>
<div class="rounded-2xl border border-gray-200 bg-gray-50/70 p-4">
<div class="mb-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500">
<span>Generation</span>
</div>
<div class="grid gap-4 md:grid-cols-2">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsTemperature">Temperature</label>
<input type="number" id="settingsTemperature" min="0" step="0.01" required class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsMaxTokens">Max tokens</label>
<input type="number" id="settingsMaxTokens" min="1" step="1" required class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsTopP">Top P</label>
<input type="number" id="settingsTopP" min="0.01" max="1" step="0.01" required class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsFrequencyPenalty">Frequency penalty</label>
<input type="number" id="settingsFrequencyPenalty" step="0.1" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsPresencePenalty">Presence penalty</label>
<input type="number" id="settingsPresencePenalty" step="0.1" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsReasoningEffort">Reasoning effort</label>
<select id="settingsReasoningEffort" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
<option value="">Default</option>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
<p class="mt-1 text-xs text-gray-500">Forwarded to LM Studio when the backend supports reasoning controls.</p>
</div>
<div class="md:col-span-2 flex items-start gap-3 rounded-xl border border-gray-200 bg-white px-4 py-3">
<input type="checkbox" id="settingsAutoOpenReasoning" class="mt-1 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<div class="min-w-0">
<label class="block text-sm font-medium text-gray-700" for="settingsAutoOpenReasoning">Auto-open reasoning</label>
<p class="mt-1 text-xs text-gray-500">When enabled, live reasoning starts expanded. You can still collapse it while the model is thinking.</p>
</div>
</div>
</div>
</div>
<div class="rounded-2xl border border-gray-200 bg-gray-50/70 p-4">
<div class="mb-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500">
<span>Context compaction</span>
</div>
<div class="grid gap-4 md:grid-cols-2">
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsContextTokens">Prompt budget (tokens)</label>
<input type="number" id="settingsContextTokens" min="1" step="1" required class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
<p class="mt-1 text-xs text-gray-500">This is the estimated prompt size budget used before compaction starts.</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsTriggerRatio">Compaction trigger</label>
<input type="number" id="settingsTriggerRatio" min="0.01" max="0.99" step="0.01" required class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
<p class="mt-1 text-xs text-gray-500">Compaction starts above this fraction of the budget.</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5" for="settingsTargetRatio">Compaction target</label>
<input type="number" id="settingsTargetRatio" min="0.01" max="0.99" step="0.01" required class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800">
<p class="mt-1 text-xs text-gray-500">The summarizer tries to shrink history below this fraction.</p>
</div>
</div>
</div>
</div>
<p id="settingsFormError" class="mt-4 hidden text-sm text-red-600"></p>
<div class="flex gap-3 mt-5">
<button type="button" id="cancelSettingsBtn" class="flex-1 px-4 py-2.5 border border-gray-300 rounded-xl text-gray-700 font-medium hover:bg-gray-50 transition-colors">
Cancel
</button>
<button type="submit" class="flex-1 px-4 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-xl transition-colors">
Save
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Group creation modal -->
<div id="groupModal" class="fixed inset-0 z-50 hidden items-center justify-center" style="background: rgba(0,0,0,0.4);">
<div class="bg-white rounded-2xl shadow-xl w-full max-w-md mx-4 overflow-hidden">
<div class="p-6">
<div class="flex items-center justify-between mb-5">
<h3 class="text-lg font-semibold text-gray-800">New Workspace</h3>
<button id="closeGroupModal" class="p-1.5 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
</div>
<form id="groupForm">
<label class="block text-sm font-medium text-gray-700 mb-1.5">Workspace name</label>
<input
type="text"
id="groupNameInput"
placeholder="e.g. My Project"
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800 placeholder:text-gray-400"
required
autofocus
>
<div class="flex gap-3 mt-5">
<button type="button" id="cancelGroupBtn" class="flex-1 px-4 py-2.5 border border-gray-300 rounded-xl text-gray-700 font-medium hover:bg-gray-50 transition-colors">
Cancel
</button>
<button type="submit" class="flex-1 px-4 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-xl transition-colors">
Create
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Main content -->
<div class="flex-1 flex flex-col overflow-hidden">
<!-- Header -->
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center gap-4">
<button id="mobileMenuBtnDesktop" class="md:hidden p-2 rounded-lg hover:bg-gray-100 text-gray-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
</button>
<div class="flex items-center gap-2 flex-1 min-w-0">
<h1 class="text-xl font-bold text-gray-800">GoCoder</h1>
<span class="text-gray-300 text-lg leading-none mx-1 flex-shrink-0"></span>
<div id="headerLocation" class="hidden items-center gap-1.5">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-gray-400"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg>
<span id="headerLocationText" class="text-sm text-gray-500 truncate max-w-xs"></span>
<button id="changeLocationBtn" class="hidden p-1 rounded hover:bg-gray-100 text-gray-400 hover:text-gray-600 transition-colors" title="Change location">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
</button>
</div>
<button id="pickLocationBtn" class="hidden items-center gap-1 px-2.5 py-1 text-xs bg-gray-100 hover:bg-gray-200 text-gray-600 rounded-lg transition-colors border border-gray-200">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
Pick Location
</button>
</div>
<button id="openSettingsBtn" class="p-2 rounded-lg hover:bg-gray-100 text-gray-500 hover:text-gray-700 transition-colors" title="Runtime settings">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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>
</button>
</div>
<!-- Chat area -->
<div id="chatScrollArea" class="flex-1 overflow-y-auto px-4 md:px-8 lg:px-16 py-6">
<div id="result" class="hidden">
<div id="resultText" class="space-y-4"></div>
</div>
<div id="emptyState" class="flex flex-col items-center justify-center h-full text-center">
<div class="w-20 h-20 bg-gray-100 rounded-2xl flex items-center justify-center mb-6">
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><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>
</div>
<h2 class="text-2xl font-semibold text-gray-700 mb-2">Start a conversation</h2>
<p class="text-gray-500 mb-6">Type a message to begin, or select an existing conversation</p>
<button id="newConversationBtnDesktop" class="bg-blue-600 hover:bg-blue-700 text-white font-medium px-6 py-3 rounded-lg transition-colors">
New Conversation
</button>
</div>
</div>
<!-- Input area -->
<div class="bg-white border-t border-gray-200 px-4 md:px-8 lg:px-16 py-4">
<div class="flex items-center gap-2 mb-2">
<span class="text-xs text-gray-500 font-medium">Agent:</span>
<div id="agentSelector" class="flex gap-1.5 flex-wrap">
<button type="button" data-agent="none" class="agent-btn px-3 py-1 text-xs font-medium rounded-lg border border-gray-300 text-gray-600 hover:bg-gray-100 transition-colors bg-blue-50 border-blue-300 text-blue-700">None</button>
<button type="button" data-agent="dadjokes" class="agent-btn px-3 py-1 text-xs font-medium rounded-lg border border-gray-300 text-gray-600 hover:bg-gray-100 transition-colors">Dadjokes</button>
<button type="button" data-agent="poet" class="agent-btn px-3 py-1 text-xs font-medium rounded-lg border border-gray-300 text-gray-600 hover:bg-gray-100 transition-colors">Poet</button>
<button type="button" data-agent="robot" class="agent-btn px-3 py-1 text-xs font-medium rounded-lg border border-gray-300 text-gray-600 hover:bg-gray-100 transition-colors">Robot</button>
</div>
</div>
<form id="messageForm" class="flex items-end gap-3">
<textarea
id="messageInput"
name="message"
rows="1"
placeholder="Type a message... Enter sends, Shift+Enter adds a new line"
aria-describedby="messageInputHint"
class="message-input flex-1 px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800"
required
></textarea>
<button
type="submit"
id="sendBtn"
class="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed text-white font-medium px-6 py-3 rounded-xl transition-colors"
>
Send
</button>
<button
type="button"
id="cancelBtn"
class="hidden border border-red-200 bg-white px-5 py-3 font-medium text-red-600 rounded-xl transition-colors hover:bg-red-50 disabled:cursor-not-allowed disabled:opacity-60"
>
Stop
</button>
</form>
<div id="messageInputHint" class="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-gray-500">
<span><kbd class="rounded border border-gray-200 bg-gray-100 px-1.5 py-0.5 font-mono text-[10px] text-gray-700">Enter</kbd> sends</span>
<span><kbd class="rounded border border-gray-200 bg-gray-100 px-1.5 py-0.5 font-mono text-[10px] text-gray-700">Shift</kbd>+<kbd class="rounded border border-gray-200 bg-gray-100 px-1.5 py-0.5 font-mono text-[10px] text-gray-700">Enter</kbd> adds a new line</span>
<span><kbd class="rounded border border-gray-200 bg-gray-100 px-1.5 py-0.5 font-mono text-[10px] text-gray-700">Alt</kbd>, <kbd class="rounded border border-gray-200 bg-gray-100 px-1.5 py-0.5 font-mono text-[10px] text-gray-700">Ctrl</kbd>, or <kbd class="rounded border border-gray-200 bg-gray-100 px-1.5 py-0.5 font-mono text-[10px] text-gray-700">Cmd</kbd>+<kbd class="rounded border border-gray-200 bg-gray-100 px-1.5 py-0.5 font-mono text-[10px] text-gray-700">Enter</kbd> also add a new line</span>
</div>
</div>
</div>
</div>
<script src="app.js"></script>
</body>
</html>