- CHANGELOG: add v0.23 Sprint 21 entry (mobile + Docker) - SPRINTS: Sprint 21 marked COMPLETED, footer updated - index.html: version label v0.22 -> v0.23 - docker-compose.yml: bind to 127.0.0.1 by default (SEC-1 fix) - README: add security note about Docker port binding Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
340 lines
26 KiB
HTML
340 lines
26 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Hermes</title>
|
|
<link rel="stylesheet" href="/static/style.css">
|
|
<!-- Prism.js syntax highlighting (loaded async, non-blocking) -->
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css" integrity="sha384-wFjoQjtV1y5jVHbt0p35Ui8aV8GVpEZkyF99OXWqP/eNJDU93D3Ugxkoyh6Y2I4A" crossorigin="anonymous">
|
|
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-core.min.js" integrity="sha384-MXybTpajaBV0AkcBaCPT4KIvo0FzoCiWXgcihYsw4FUkEz0Pv3JGV6tk2G8vJtDc" crossorigin="anonymous" defer></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js" integrity="sha384-Uq05+JLko69eOiPr39ta9bh7kld5PKZoU+fF7g0EXTAriEollhZ+DrN8Q/Oi8J2Q" crossorigin="anonymous" defer></script>
|
|
</head>
|
|
<body>
|
|
<div class="layout">
|
|
<aside class="sidebar">
|
|
<div class="sidebar-header"><div class="logo">H</div><div><h1 style="margin:0;font-size:15px;font-weight:700;letter-spacing:-.01em">Hermes</h1><div style="font-size:10px;color:var(--muted);opacity:.8;margin-top:1px">v0.23</div></div></div>
|
|
<div class="sidebar-nav">
|
|
<button class="nav-tab active" data-panel="chat" data-label="Chat" onclick="switchPanel('chat')" title="Chat">💬</button>
|
|
<button class="nav-tab" data-panel="tasks" data-label="Tasks" onclick="switchPanel('tasks')" title="Tasks">📅</button>
|
|
<button class="nav-tab" data-panel="skills" data-label="Skills" onclick="switchPanel('skills')" title="Skills">🧩</button>
|
|
<button class="nav-tab" data-panel="memory" data-label="Memory" onclick="switchPanel('memory')" title="Memory">🧠</button>
|
|
<button class="nav-tab" data-panel="workspaces" data-label="Spaces" onclick="switchPanel('workspaces')" title="Spaces">📁</button>
|
|
<button class="nav-tab" data-panel="todos" data-label="Todos" onclick="switchPanel('todos')" title="Current task list">✅</button>
|
|
</div>
|
|
<!-- Chat panel -->
|
|
<div class="panel-view active" id="panelChat">
|
|
<div class="sidebar-section">
|
|
<button class="new-chat-btn" id="btnNewChat">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
New conversation <span style="font-size:10px;opacity:.5;margin-left:4px">⌘K</span>
|
|
</button>
|
|
</div>
|
|
<div class="session-search"><input id="sessionSearch" placeholder="Filter conversations..." oninput="filterSessions()"></div>
|
|
<div class="session-list" id="sessionList"></div>
|
|
</div>
|
|
<!-- Tasks (cron) panel -->
|
|
<div class="panel-view" id="panelTasks">
|
|
<div class="sidebar-section" style="padding-bottom:4px;display:flex;align-items:center;justify-content:space-between">
|
|
<div style="font-size:11px;color:var(--muted)">Scheduled jobs</div>
|
|
<button class="cron-btn run" style="padding:3px 8px;font-size:10px" onclick="toggleCronForm()">+ New job</button>
|
|
</div>
|
|
<!-- Create job form (hidden by default) -->
|
|
<div id="cronCreateForm" style="display:none;padding:8px 12px;border-bottom:1px solid var(--border);flex-shrink:0">
|
|
<input id="cronFormName" placeholder="Job name (optional)" style="width:100%;background:rgba(255,255,255,.05);border:1px solid var(--border2);border-radius:6px;color:var(--text);padding:5px 8px;font-size:12px;outline:none;margin-bottom:6px">
|
|
<input id="cronFormSchedule" placeholder="Schedule: '0 9 * * *' or 'every 1h'" style="width:100%;background:rgba(255,255,255,.05);border:1px solid var(--border2);border-radius:6px;color:var(--text);padding:5px 8px;font-size:12px;outline:none;margin-bottom:6px">
|
|
<textarea id="cronFormPrompt" rows="3" placeholder="Prompt (must be self-contained)" style="width:100%;background:rgba(255,255,255,.05);border:1px solid var(--border2);border-radius:6px;color:var(--text);padding:5px 8px;font-size:12px;outline:none;resize:none;font-family:inherit;margin-bottom:6px"></textarea>
|
|
<select id="cronFormDeliver" style="width:100%;background:rgba(255,255,255,.05);border:1px solid var(--border2);border-radius:6px;color:var(--text);padding:5px 8px;font-size:12px;outline:none;margin-bottom:8px">
|
|
<option value="local">Local (save output only)</option>
|
|
<option value="discord">Discord</option>
|
|
<option value="telegram">Telegram</option>
|
|
</select>
|
|
<div style="display:flex;gap:6px">
|
|
<button class="cron-btn run" style="flex:1" onclick="submitCronCreate()">Create job</button>
|
|
<button class="cron-btn" style="flex:1" onclick="toggleCronForm()">Cancel</button>
|
|
</div>
|
|
<div id="cronFormError" style="font-size:11px;color:var(--accent);margin-top:6px;display:none"></div>
|
|
</div>
|
|
<div class="cron-list" id="cronList"><div style="padding:12px;color:var(--muted);font-size:12px">Loading...</div></div>
|
|
</div>
|
|
<!-- Skills panel -->
|
|
<div class="panel-view" id="panelSkills">
|
|
<div class="sidebar-section" style="padding-bottom:4px;display:flex;align-items:center;justify-content:space-between">
|
|
<div class="skills-search" style="flex:1;padding:0"><input id="skillsSearch" placeholder="Search skills..." oninput="filterSkills()"></div>
|
|
<button class="cron-btn run" style="padding:3px 8px;font-size:10px;flex-shrink:0;margin-left:6px" onclick="toggleSkillForm()">+ New skill</button>
|
|
</div>
|
|
<!-- Skill create/edit form (hidden by default) -->
|
|
<div id="skillCreateForm" style="display:none;padding:8px 12px;border-bottom:1px solid var(--border);flex-shrink:0">
|
|
<input id="skillFormName" placeholder="Skill name (e.g. my-skill)" style="width:100%;background:rgba(255,255,255,.05);border:1px solid var(--border2);border-radius:6px;color:var(--text);padding:5px 8px;font-size:12px;outline:none;margin-bottom:6px;box-sizing:border-box">
|
|
<input id="skillFormCategory" placeholder="Category (optional, e.g. devops)" style="width:100%;background:rgba(255,255,255,.05);border:1px solid var(--border2);border-radius:6px;color:var(--text);padding:5px 8px;font-size:12px;outline:none;margin-bottom:6px;box-sizing:border-box">
|
|
<textarea id="skillFormContent" rows="6" placeholder="SKILL.md content (YAML frontmatter + markdown body)" style="width:100%;background:rgba(255,255,255,.05);border:1px solid var(--border2);border-radius:6px;color:var(--text);padding:5px 8px;font-size:12px;outline:none;resize:vertical;font-family:'SF Mono',ui-monospace,monospace;margin-bottom:6px;box-sizing:border-box"></textarea>
|
|
<div style="display:flex;gap:6px">
|
|
<button class="cron-btn run" style="flex:1" onclick="submitSkillSave()">Save skill</button>
|
|
<button class="cron-btn" style="flex:1" onclick="toggleSkillForm()">Cancel</button>
|
|
</div>
|
|
<div id="skillFormError" style="font-size:11px;color:var(--accent);margin-top:6px;display:none"></div>
|
|
</div>
|
|
<div class="skills-list" id="skillsList"><div style="padding:12px;color:var(--muted);font-size:12px">Loading...</div></div>
|
|
</div>
|
|
<!-- Memory panel -->
|
|
<div class="panel-view" id="panelMemory">
|
|
<div style="padding:8px 12px 4px;display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
|
|
<span style="font-size:11px;color:var(--muted)">Personal memory</span>
|
|
<button class="cron-btn run" id="memEditBtn" style="padding:3px 8px;font-size:10px" onclick="toggleMemoryEdit()">✎ Edit</button>
|
|
</div>
|
|
<div class="memory-panel" id="memoryPanel"><div style="color:var(--muted);font-size:12px">Loading...</div></div>
|
|
<!-- Memory edit form (hidden by default) -->
|
|
<div id="memoryEditForm" style="display:none;padding:8px 12px;border-top:1px solid var(--border);flex-shrink:0">
|
|
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">Editing: <span id="memEditSection">memory</span></div>
|
|
<textarea id="memEditContent" rows="10" style="width:100%;background:rgba(255,255,255,.05);border:1px solid var(--border2);border-radius:6px;color:var(--text);padding:5px 8px;font-size:11px;outline:none;resize:vertical;font-family:'SF Mono',ui-monospace,monospace;box-sizing:border-box;margin-bottom:6px;line-height:1.5"></textarea>
|
|
<div style="display:flex;gap:6px">
|
|
<button class="cron-btn run" style="flex:1" onclick="submitMemorySave()">Save</button>
|
|
<button class="cron-btn" style="flex:1" onclick="closeMemoryEdit()">Cancel</button>
|
|
</div>
|
|
<div id="memEditError" style="font-size:11px;color:var(--accent);margin-top:6px;display:none"></div>
|
|
</div>
|
|
</div>
|
|
<!-- Todo panel -->
|
|
<div class="panel-view" id="panelTodos">
|
|
<div style="padding:10px 12px 4px;font-size:11px;color:var(--muted);flex-shrink:0">Current task list</div>
|
|
<div id="todoPanel" style="flex:1;overflow-y:auto;padding:8px 12px"></div>
|
|
</div>
|
|
<!-- Workspaces panel -->
|
|
<div class="panel-view" id="panelWorkspaces">
|
|
<div style="padding:10px 12px 4px;font-size:11px;color:var(--muted)">Add and switch workspaces for your sessions.</div>
|
|
<div style="flex:1;overflow-y:auto;padding:0 12px 12px" id="workspacesPanel"><div style="color:var(--muted);font-size:12px">Loading...</div></div>
|
|
</div>
|
|
<div class="sidebar-bottom">
|
|
<div class="field-label" style="font-size:10px;letter-spacing:.07em;margin-bottom:4px">MODEL</div>
|
|
<select id="modelSelect">
|
|
<optgroup label="OpenAI">
|
|
<option value="openai/gpt-5.4-mini">GPT-5.4 Mini</option>
|
|
<option value="openai/gpt-4o">GPT-4o</option>
|
|
<option value="openai/o3">o3</option>
|
|
<option value="openai/o4-mini">o4-mini</option>
|
|
</optgroup>
|
|
<optgroup label="Anthropic">
|
|
<option value="anthropic/claude-sonnet-4.6">Claude Sonnet 4.6</option>
|
|
<option value="anthropic/claude-sonnet-4-5">Claude Sonnet 4.5</option>
|
|
<option value="anthropic/claude-haiku-3-5">Claude Haiku 3.5</option>
|
|
</optgroup>
|
|
<optgroup label="Other">
|
|
<option value="google/gemini-2.5-pro">Gemini 2.5 Pro</option>
|
|
<option value="deepseek/deepseek-chat-v3-0324">DeepSeek V3</option>
|
|
<option value="meta-llama/llama-4-scout">Llama 4 Scout</option>
|
|
</optgroup>
|
|
</select>
|
|
<div id="sidebarWsDisplay" style="display:flex;align-items:center;gap:7px;padding:0 0 8px;cursor:pointer;border-radius:8px;transition:background .15s" onclick="toggleWsDropdown()" title="Switch workspace">
|
|
<span style="font-size:14px;opacity:.7">📁</span>
|
|
<div style="min-width:0;flex:1">
|
|
<div style="font-size:11px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap" id="sidebarWsName">Workspace</div>
|
|
<div style="font-size:10px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-top:1px" id="sidebarWsPath"></div>
|
|
</div>
|
|
<span style="font-size:10px;color:var(--muted);flex-shrink:0">▾</span>
|
|
</div>
|
|
<div class="sidebar-actions">
|
|
<button class="sm-btn" id="btnDownload" title="Download as Markdown">↓ Transcript</button>
|
|
<button class="sm-btn" id="btnExportJSON" title="Export full session as JSON">❬/❭ JSON</button>
|
|
<button class="sm-btn" id="btnImportJSON" title="Import session from JSON">↑ Import</button>
|
|
<input type="file" id="importFileInput" accept=".json" style="display:none">
|
|
</div>
|
|
</div>
|
|
<div class="resize-handle" id="sidebarResize"></div>
|
|
</aside>
|
|
<main class="main">
|
|
<div class="topbar">
|
|
<button class="mobile-hamburger" id="btnHamburger" onclick="toggleMobileSidebar()" title="Menu">
|
|
<svg width="20" height="20" 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 style="flex:1;min-width:0;overflow:hidden"><div class="topbar-title" id="topbarTitle">Hermes</div><div class="topbar-meta" id="topbarMeta">Start a new conversation</div></div>
|
|
<div class="topbar-chips">
|
|
<div class="chip model" id="modelChip">GPT-5.4 Mini</div>
|
|
<div id="wsChipWrap" style="position:relative">
|
|
<div class="chip ws-chip" id="wsChip" onclick="toggleWsDropdown()" title="Switch workspace" style="cursor:pointer">📁 test-workspace ▾</div>
|
|
<div class="ws-dropdown" id="wsDropdown"></div>
|
|
</div>
|
|
<button class="chip clear-btn" id="btnClearConv" onclick="clearConversation()" title="Clear all messages in this conversation" style="display:none">🗑 Clear</button>
|
|
<button class="chip gear-btn" id="btnSettings" onclick="toggleSettings()" title="Settings">⚙</button>
|
|
<button class="chip mobile-files-btn" id="btnMobileFiles" onclick="toggleMobileFiles()" title="Files">📁</button>
|
|
</div>
|
|
</div>
|
|
<div class="messages" id="messages">
|
|
<div class="empty-state" id="emptyState">
|
|
<div class="empty-logo">🦉</div>
|
|
<h2>What can I help with?</h2>
|
|
<p>Ask anything, run commands, explore files, or manage your scheduled tasks.</p>
|
|
<div class="suggestion-grid">
|
|
<button class="suggestion" data-msg="What files are in this workspace?">📁 What files are in this workspace?</button>
|
|
<button class="suggestion" data-msg="What's on my schedule today?">📋 What's on my schedule today?</button>
|
|
<button class="suggestion" data-msg="Help me plan a small project.">🗺 Help me plan a small project.</button>
|
|
</div>
|
|
</div>
|
|
<div class="messages-inner" id="msgInner"></div>
|
|
<div id="liveToolCards" style="display:none;max-width:800px;margin:0 auto;width:100%;padding:0 24px;"></div>
|
|
</div>
|
|
<div class="reconnect-banner" id="reconnectBanner">
|
|
<span id="reconnectMsg">⚠ A response may have been in progress when you last left. Reload messages?</span>
|
|
<div style="display:flex;gap:8px;flex-shrink:0">
|
|
<button class="reconnect-btn" onclick="dismissReconnect()">Dismiss</button>
|
|
<button class="reconnect-btn" onclick="refreshSession()">↻ Reload</button>
|
|
</div>
|
|
</div>
|
|
<div class="approval-card" id="approvalCard">
|
|
<div class="approval-inner">
|
|
<div class="approval-header">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
|
Dangerous command — approval required
|
|
</div>
|
|
<div class="approval-desc" id="approvalDesc"></div>
|
|
<div class="approval-cmd" id="approvalCmd"></div>
|
|
<div class="approval-btns">
|
|
<button class="approval-btn once" onclick="respondApproval('once')">✓ Allow once</button>
|
|
<button class="approval-btn session" onclick="respondApproval('session')">🔒 Allow this session</button>
|
|
<button class="approval-btn always" onclick="respondApproval('always')">☆ Always allow</button>
|
|
<button class="approval-btn deny" onclick="respondApproval('deny')">✕ Deny</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Activity bar: shows tool progress / status above composer (not inside input) -->
|
|
<div id="activityBar" style="display:none;max-width:800px;margin:0 auto;width:100%;padding:0 24px;">
|
|
<div id="activityBarInner" style="display:flex;align-items:center;gap:8px;padding:6px 12px;border-radius:8px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);font-size:12px;color:var(--muted);animation:fadeIn .15s ease;">
|
|
<span id="activityIcon" style="font-size:13px;opacity:.6">⚙</span>
|
|
<span id="activityText" style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"></span>
|
|
<button id="btnCancel" onclick="cancelStream()" style="display:none;background:rgba(233,69,96,.12);border:1px solid rgba(233,69,96,.35);color:#e94560;font-size:11px;font-weight:600;padding:3px 10px;border-radius:6px;cursor:pointer;flex-shrink:0;transition:background .15s" title="Cancel this task">■ Cancel</button>
|
|
<button id="btnDismissStatus" onclick="setStatus('')" style="display:none;background:none;border:none;color:var(--muted);font-size:14px;line-height:1;cursor:pointer;padding:0 2px;opacity:.5;flex-shrink:0" title="Dismiss">✕</button>
|
|
<span id="activityDots" style="display:flex;gap:3px;align-items:center">
|
|
<span style="width:4px;height:4px;border-radius:50%;background:var(--blue);opacity:.3;animation:pulse 1.4s ease-in-out infinite"></span>
|
|
<span style="width:4px;height:4px;border-radius:50%;background:var(--blue);opacity:.3;animation:pulse 1.4s ease-in-out .22s infinite"></span>
|
|
<span style="width:4px;height:4px;border-radius:50%;background:var(--blue);opacity:.3;animation:pulse 1.4s ease-in-out .44s infinite"></span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="composer-wrap" id="composerWrap">
|
|
<div class="cmd-dropdown" id="cmdDropdown"></div>
|
|
<div class="composer-box" id="composerBox">
|
|
<div class="drop-hint" id="dropHint">
|
|
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
|
Drop files to upload to workspace
|
|
</div>
|
|
<div class="attach-tray" id="attachTray"></div>
|
|
<div class="mic-status" id="micStatus" style="display:none"><span class="mic-dot"></span> Listening…</div>
|
|
<textarea id="msg" rows="1" placeholder="Message Hermes…"></textarea>
|
|
<div class="composer-footer">
|
|
<div class="composer-left">
|
|
<input type="file" id="fileInput" multiple accept="image/*,text/*,application/pdf,application/json,.md,.py,.js,.ts,.yaml,.yml,.toml,.csv,.sh,.txt,.log,.env" style="display:none">
|
|
<button class="icon-btn" id="btnAttach" title="Attach files">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
|
|
</button>
|
|
<button class="icon-btn mic-btn" id="btnMic" title="Voice input" style="display:none">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<rect x="9" y="1" width="6" height="12" rx="3"/>
|
|
<path d="M5 10a7 7 0 0 0 14 0"/>
|
|
<line x1="12" y1="19" x2="12" y2="23"/>
|
|
<line x1="8" y1="23" x2="16" y2="23"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="composer-right">
|
|
<button class="send-btn" id="btnSend" title="Send message" style="display:none">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="upload-bar-wrap" id="uploadBarWrap"><div class="upload-bar" id="uploadBar"></div></div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
<aside class="rightpanel">
|
|
<div class="resize-handle" id="rightpanelResize"></div>
|
|
<div class="panel-header">
|
|
<span>Workspace</span>
|
|
<div class="panel-actions">
|
|
<button class="panel-icon-btn" id="btnUpDir" title="Parent directory" onclick="navigateUp()" style="display:none">↑</button>
|
|
<button class="panel-icon-btn" id="btnNewFile" title="New file" onclick="promptNewFile()">+</button>
|
|
<button class="panel-icon-btn" id="btnNewFolder" title="New folder" onclick="promptNewFolder()">📁</button>
|
|
<button class="panel-icon-btn" id="btnRefreshPanel" title="Refresh" onclick="if(S.session)loadDir(S.currentDir)">↻</button>
|
|
<button class="panel-icon-btn close-preview" id="btnClearPreview" title="Close preview">✕</button>
|
|
</div>
|
|
</div>
|
|
<div class="breadcrumb-bar" id="breadcrumbBar" style="display:none"></div>
|
|
<div class="file-tree" id="fileTree"></div>
|
|
<div class="preview-area" id="previewArea">
|
|
<div class="preview-path" id="previewPath">
|
|
<span id="previewPathText"></span>
|
|
<span class="preview-badge" id="previewBadge"></span>
|
|
<button id="btnDownloadFile" class="panel-icon-btn" style="margin-left:auto;font-size:12px;width:auto;padding:2px 8px" onclick="downloadFile(_previewCurrentPath)" title="Download file to your computer">⇩ Download</button>
|
|
<button id="btnEditFile" class="panel-icon-btn" style="font-size:12px;width:auto;padding:2px 8px;display:none" onclick="toggleEditMode()">✎ Edit</button>
|
|
</div>
|
|
<pre class="preview-code" id="previewCode"></pre>
|
|
<div class="preview-img-wrap" id="previewImgWrap" style="display:none"><img class="preview-img" id="previewImg" src="" alt=""></div>
|
|
<div class="preview-md" id="previewMd" style="display:none"></div>
|
|
<textarea id="previewEditArea" style="display:none;flex:1;width:100%;background:var(--code-bg);color:#e2e8f0;border:1px solid var(--border2);border-radius:8px;padding:12px;font-family:'SF Mono',ui-monospace,monospace;font-size:12px;line-height:1.6;resize:none;outline:none" oninput="_previewDirty=true;updateEditBtn()"></textarea>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
<div class="settings-overlay" id="settingsOverlay" style="display:none">
|
|
<div class="settings-panel">
|
|
<div class="settings-header">
|
|
<h3 style="margin:0;font-size:16px">Settings</h3>
|
|
<button class="panel-icon-btn" onclick="toggleSettings()" title="Close">✕</button>
|
|
</div>
|
|
<div class="settings-body">
|
|
<div class="settings-field">
|
|
<label for="settingsModel">Default Model</label>
|
|
<select id="settingsModel" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px"></select>
|
|
</div>
|
|
<div class="settings-field">
|
|
<label for="settingsWorkspace">Default Workspace</label>
|
|
<select id="settingsWorkspace" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px"></select>
|
|
</div>
|
|
<div class="settings-field">
|
|
<label for="settingsSendKey">Send Key</label>
|
|
<select id="settingsSendKey" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px">
|
|
<option value="enter">Enter (Shift+Enter for newline)</option>
|
|
<option value="ctrl+enter">Ctrl+Enter (Enter for newline)</option>
|
|
</select>
|
|
</div>
|
|
<div class="settings-field" style="border-top:1px solid var(--border);padding-top:12px;margin-top:8px">
|
|
<label for="settingsPassword">Access Password</label>
|
|
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Enter a new password to set or change it. Leave blank to keep current setting.</div>
|
|
<input type="password" id="settingsPassword" placeholder="Enter new password…" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px;font-size:13px">
|
|
</div>
|
|
<button class="sm-btn" onclick="saveSettings()" style="margin-top:12px;width:100%;padding:8px;font-weight:600">Save Settings</button>
|
|
<button class="sm-btn" id="btnDisableAuth" onclick="disableAuth()" style="margin-top:6px;width:100%;padding:8px;font-weight:600;color:#e8a030;border-color:rgba(232,160,48,.3);display:none">Disable Auth</button>
|
|
<button class="sm-btn" id="btnSignOut" onclick="signOut()" style="margin-top:6px;width:100%;padding:8px;font-weight:600;color:var(--accent);border-color:rgba(233,69,96,.3);display:none">Sign Out</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mobile-overlay" id="mobileOverlay" onclick="closeMobileSidebar()"></div>
|
|
<nav class="mobile-bottom-nav" id="mobileBottomNav">
|
|
<button class="mobile-nav-btn active" data-panel="chat" onclick="mobileSwitchPanel('chat')">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><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>
|
|
<span>Chat</span>
|
|
</button>
|
|
<button class="mobile-nav-btn" data-panel="tasks" onclick="mobileSwitchPanel('tasks')">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
|
|
<span>Tasks</span>
|
|
</button>
|
|
<button class="mobile-nav-btn" data-panel="skills" onclick="mobileSwitchPanel('skills')">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
|
<span>Skills</span>
|
|
</button>
|
|
<button class="mobile-nav-btn" data-panel="memory" onclick="mobileSwitchPanel('memory')">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 2a7 7 0 0 1 7 7c0 2.5-1.3 4.7-3.2 6H8.2C6.3 13.7 5 11.5 5 9a7 7 0 0 1 7-7z"/><line x1="9" y1="17" x2="15" y2="17"/><line x1="10" y1="20" x2="14" y2="20"/></svg>
|
|
<span>Memory</span>
|
|
</button>
|
|
<button class="mobile-nav-btn" data-panel="workspaces" onclick="mobileSwitchPanel('workspaces')">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 4h8l2 2h10v14H2z"/></svg>
|
|
<span>Spaces</span>
|
|
</button>
|
|
</nav>
|
|
<div class="toast" id="toast"></div>
|
|
<script src="/static/ui.js"></script>
|
|
<script src="/static/workspace.js"></script>
|
|
<script src="/static/sessions.js"></script>
|
|
<script src="/static/commands.js"></script>
|
|
<script src="/static/messages.js"></script>
|
|
<script src="/static/panels.js"></script>
|
|
<script src="/static/boot.js"></script>
|
|
</body>
|
|
</html> |