Two changes: 1. Workspace updates correctly on profile switch switchToProfile() now applies data.default_workspace from the switch response to the current session via /api/session/update, updates S.session.workspace in-memory, and stores S._profileDefaultWorkspace so the next new session also inherits the profile's workspace. newSession() in sessions.js picks up S._profileDefaultWorkspace when creating a new session after a profile switch. 2. Workspace chip removed from topbar The workspace was shown in two places: the topbar chip (wsChip) AND the sidebar bottom display (sidebarWsDisplay with name + full path). The topbar chip was redundant, cluttered the topbar, and pushed other chips (profile, model, clear, settings) off screen. Removed wsChip from the topbar entirely. The sidebar display is now the sole workspace UI, consistent and unambiguous. Moved wsDropdown to live inside the sidebar position:relative wrapper so it opens downward from sidebarWsDisplay. Updated the click-outside listener to close on clicks outside sidebarWsDisplay/wsDropdown. Removed stale wsChip update code from syncTopbar() in ui.js. Tests: 426 passed, 0 failed.
365 lines
29 KiB
HTML
365 lines
29 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.25</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="profiles" data-label="Profiles" onclick="switchPanel('profiles')" title="Agent profiles"><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 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></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>
|
|
<!-- Profiles panel -->
|
|
<div class="panel-view" id="panelProfiles">
|
|
<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)">Agent profiles</div>
|
|
<button class="cron-btn run" style="padding:3px 8px;font-size:10px" onclick="toggleProfileForm()">+ New profile</button>
|
|
</div>
|
|
<!-- Profile create form (hidden by default) -->
|
|
<div id="profileCreateForm" style="display:none;padding:8px 12px;border-bottom:1px solid var(--border);flex-shrink:0">
|
|
<input id="profileFormName" placeholder="Profile name (lowercase, a-z 0-9 hyphens)" 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">
|
|
<label style="display:flex;align-items:center;gap:6px;font-size:11px;color:var(--muted);margin-bottom:8px;cursor:pointer">
|
|
<input type="checkbox" id="profileFormClone" style="accent-color:var(--accent)"> Clone config from active profile
|
|
</label>
|
|
<div style="display:flex;gap:6px">
|
|
<button class="cron-btn run" style="flex:1" onclick="submitProfileCreate()">Create</button>
|
|
<button class="cron-btn" style="flex:1" onclick="toggleProfileForm()">Cancel</button>
|
|
</div>
|
|
<div id="profileFormError" style="font-size:11px;color:var(--accent);margin-top:6px;display:none"></div>
|
|
</div>
|
|
<div style="flex:1;overflow-y:auto;padding:0 12px 12px" id="profilesPanel"><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 style="position:relative">
|
|
<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="ws-dropdown" id="wsDropdown"></div>
|
|
</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 id="profileChipWrap" style="position:relative">
|
|
<div class="chip profile-chip" id="profileChip" onclick="toggleProfileDropdown()" title="Switch profile" style="cursor:pointer"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:3px"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg><span id="profileChipLabel">default</span> ▾</div>
|
|
<div class="profile-dropdown" id="profileDropdown"></div>
|
|
</div>
|
|
<div class="chip model" id="modelChip">GPT-5.4 Mini</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> |