mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-02 17:49:20 +00:00
* feat(ux): complete UX overhaul — design system, boot screen, onboarding, web polish Shared design system: CSS custom properties for spacing, typography, transitions, and color tokens used across web UI and boot screen. Boot screen: compact feature-tags line showing enabled subsystems (db, tools, routines, heartbeat, skills, sandbox, embeddings) at a glance. Downgrade startup info logs (libSQL, webhook, workspace seed) to debug level since the boot screen now covers this. Onboarding wizard: model picker with live API fetch, provider-aware auth flow, improved error recovery and progress display. Web UI: ARIA attributes, welcome card, streaming debounce, connection status banner, skeleton loaders, send cooldown. CLI: doctor command enhancements, status command cleanup, REPL banner consolidation, shared fmt module. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * feat(ux): Apple-level design refinements — spring physics, glass morphism, chat polish Merge staging theme support (dark/light/system toggle) and layer UX polish on top: spring-physics motion, glass morphism depth, chat experience improvements, and responsive mobile refinements. Design system: - Restore and extend design token system (spacing, typography, timing, easing) with legacy aliases for theme compatibility - Add shadow tiers, accent glow, glass morphism, spring easing tokens - Tokens defined in both dark (:root) and light ([data-theme="light"]) Micro-interactions (Phase 2): - Spring-overshoot message entry animation (slideUp) - Spring-scale button press on all interactive buttons - Tab crossfade animation, tool card smooth accordion (max-height) - Modal scale(0.95) + blur(8px) entry, toast spring slide - Sidebar width crossfade, card hover lift Visual depth (Phase 3): - Tab bar glass morphism + surface highlight + sliding indicator - Active tab accent background pill - Assistant message accent left border, user message bubble tail - Floating input area (rounded + shadow + margin) Chat polish (Phase 4): - Smooth streaming cursor (cursorPulse), message hover timestamps - Time separators (Today/Yesterday/date) - Textarea smooth auto-expand, send button glow Settings & forms (Phase 5): - iOS-style toggle switches for boolean settings - Input focus glow, save feedback spring animation - Welcome card with gradient background + proper spacing - Sticky settings group headers with glass backdrop Accessibility & mobile (Phase 6): - Animated focus ring, prefers-reduced-motion global kill-switch - Touch target audit (44px min), mobile bottom-sheet modals - Mobile bottom tab bar, toast redesign (icon + border + countdown) - Thread hover translateX, badge in_progress pulse Bug fixes: - Gateway/TEE popover z-index (tab-bar z-index: 200, popovers 500) - Connection lost banner as fixed top bar instead of flex child - Sidebar collapse keeps toggle + new thread buttons visible - Downgrade noisy startup logs (db, webhook, vector) to debug - Remove green dot pulse animation on connected status - Deduplicate confirm-modal in HTML, add tab-indicator div Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * feat(web): mobile layout improvements — sidebar toggle, settings drill-down, tab bar polish - Fix mobile sidebar toggle: use expanded-mobile class instead of collapsed, add backdrop overlay, auto-close on thread select, outside-click dismiss - Settings: replace cramped horizontal tabs with drill-down navigation (category list → detail view → back button) - Bottom tab bar: add glass morphism, hide theme toggle, flip tab indicator to top edge - Keep thread toggle button visible in collapsed 36px sidebar strip Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * feat(repl): interactive approval selector and transient status lines - Replace ASCII-art approval box with clean horizontal rule card - Add inquire-based interactive selector for tool approvals (↑↓ + Enter) - Selector runs directly from send_status via spawn_blocking, with stdin_locked flag to prevent readline from competing for stdin - Transient thinking/tool-started lines: each replaces the previous, all erased before final output (no clutter left in scrollback) - Esc in selector sends denial so agent never gets stuck Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: widen TurnCost token fields to u64 and remove unused variable - Change input_tokens/output_tokens from u32 to u64 in StatusUpdate::TurnCost, SseEvent::TurnCost, and the thread_ops emit site to avoid truncation on large conversations - Remove unused _routine_engine_for_loop binding in agent_loop.rs Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * chore: reduce startup log noise — demote info to debug Demote routine startup messages (builder, WASM tools, tunnel, WASM channels) from info to debug so the default log output stays clean. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(web): allow CDN scripts in CSP connect-src directive Add cdn.jsdelivr.net and cdnjs.cloudflare.com to connect-src so the browser can fetch marked.js and DOMPurify without CSP violations. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * style: fix cargo fmt in repl.rs Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(web): gate turn_cost SSE handler on current thread Prevents cost badge from attaching to the wrong message when switching threads or receiving events from background threads. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * ci: retrigger CI * fix: add missing extension_manager to webhook EngineContext The webhook trigger path added in #736 was missing the extension_manager field introduced by #1453. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * chore: ignore RUSTSEC-2026-0049 rustls-webpki CRL advisory Low impact — requires compromised CA to exploit. Tracked for upstream rustls-webpki upgrade. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(routines): use fields.join for cron normalization Use split_whitespace fields instead of re-trimming the original string to avoid preserving extra internal whitespace in cron expressions. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * feat(repl): Apple-style approval card — clean vertical flow - Drop verbose tool description (the command IS the decision surface) - Unified vertical pipe layout: ◆ header → │ params → │ selector - Selector options show keyboard shortcuts inline: Approve (y) - Compact help message, answered state uses └ to close the flow - No horizontal rules, no blank-line padding — just breathing room Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * refactor(repl): replace inquire with crossterm for approval selector Drop the inquire dependency (which pulled in crossterm 0.25, duplicating the existing 0.28). The 3-option approval selector is now built directly with crossterm raw mode — same UX, zero new dependencies. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * chore(deps): upgrade crossterm 0.28 → 0.29, eliminate duplication termimad (via crokey) uses crossterm 0.29. Upgrading our direct dependency from 0.28 to 0.29 collapses to a single crossterm version in the dependency tree. Also migrated termimad::crossterm:: references to the direct crossterm import. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: address review comments — box_top off-by-one, smart_truncate overflow, mobile theme toggle - Fix box_top() fill calculation: was off-by-one, producing boxes 1 char too wide (fmt.rs) - Fix smart_truncate(): account for "..." in the budget so output never exceeds max_chars (repl.rs) - Move theme toggle to settings sidebar on mobile instead of display:none, so mobile users can still switch themes (style.css, index.html, app.js) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * style: cargo fmt repl.rs Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: address review — retry duplication, CSP connect-src, deny color - Remove failed message before retry to prevent duplicate user messages - Revert connect-src to 'self' — CDN hosts only need script-src - Use red for Deny confirmation in REPL approval selector Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
415 lines
22 KiB
HTML
415 lines
22 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||
<title>IronClaw</title>
|
||
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="/style.css">
|
||
|
||
<!-- i18n Modules -->
|
||
<script src="/i18n/index.js"></script>
|
||
<script src="/i18n/en.js"></script>
|
||
<script src="/i18n/zh-CN.js"></script>
|
||
|
||
<script
|
||
src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.3/purify.min.js"
|
||
integrity="sha384-osZDKVu4ipZP703HmPOhWdyBajcFyjX2Psjk//TG1Rc0AdwEtuToaylrmcK3LdAl"
|
||
crossorigin="anonymous"
|
||
></script>
|
||
<script
|
||
src="https://cdn.jsdelivr.net/npm/[email protected]/lib/marked.umd.min.js"
|
||
integrity="sha384-pN9zSKOnTZwXRtYZAu0PBPEgR2B7DOC1aeLxQ33oJ0oy5iN1we6gm57xldM2irDG"
|
||
crossorigin="anonymous"
|
||
></script>
|
||
<script src="/theme-init.js"></script>
|
||
</head>
|
||
<body>
|
||
<!-- Auth Screen -->
|
||
<div id="auth-screen">
|
||
<div class="auth-card-login">
|
||
<div class="auth-brand">
|
||
<h1 data-i18n="auth.title">IronClaw</h1>
|
||
<p class="auth-tagline" data-i18n="auth.tagline">Secure AI Assistant</p>
|
||
</div>
|
||
<div class="auth-form">
|
||
<label for="token-input" data-i18n="auth.tokenLabel">Gateway Token</label>
|
||
<input type="password" id="token-input" data-i18n="auth.tokenPlaceholder" data-i18n-attr="placeholder" placeholder="Paste your auth token" autofocus>
|
||
<button id="auth-connect-btn" data-i18n="auth.connect">Connect</button>
|
||
</div>
|
||
<div id="auth-error"></div>
|
||
<p class="auth-hint" data-i18n="auth.hint">Enter the GATEWAY_AUTH_TOKEN from your .env configuration.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Restart Confirmation Modal -->
|
||
<div id="restart-confirm-modal" class="restart-modal" style="display: none;">
|
||
<div class="restart-modal-overlay" id="restart-overlay"></div>
|
||
<div class="restart-modal-content">
|
||
<div class="restart-modal-header">
|
||
<h2 data-i18n="restart.title">Restart IronClaw Instance</h2>
|
||
<button class="restart-modal-close" id="restart-close-btn" data-i18n="restart.closeTooltip" data-i18n-attr="title"
|
||
title="Close">×</button>
|
||
</div>
|
||
<div class="restart-modal-body">
|
||
<p class="restart-modal-description" data-i18n="restart.description">
|
||
Are you sure you want to restart the IronClaw instance? This will gracefully restart the process.
|
||
</p>
|
||
<div class="restart-modal-warning">
|
||
<span class="restart-modal-warning-icon">⚠️</span>
|
||
<p data-i18n="restart.warning">Any in-progress jobs may be interrupted. The restart will complete within a few seconds.</p>
|
||
</div>
|
||
</div>
|
||
<div class="restart-modal-footer">
|
||
<button class="restart-modal-btn cancel" id="restart-cancel-btn" data-i18n="restart.cancel">Cancel</button>
|
||
<button class="restart-modal-btn confirm" id="restart-confirm-btn" data-i18n="restart.confirm">Confirm Restart</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Restart Progress Modal -->
|
||
<div id="restart-loader" class="restart-loader" style="display: none;">
|
||
<div class="restart-loader-overlay"></div>
|
||
<div class="restart-loader-content">
|
||
<div class="restart-spinner"></div>
|
||
<div class="restart-loader-text">
|
||
<p class="restart-title" data-i18n="restart.progressTitle">Restarting IronClaw</p>
|
||
<p class="restart-subtitle" data-i18n="restart.progressSubtitle">Please wait while the process restarts...</p>
|
||
</div>
|
||
<div class="restart-progress-bar">
|
||
<div class="restart-progress-fill"></div>
|
||
</div>
|
||
<p class="restart-modal-info" data-i18n="restart.checkLogs">
|
||
Check the Logs tab for details after the restart completes.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main App (hidden until authenticated) -->
|
||
<div id="app">
|
||
<!-- Tab Bar -->
|
||
<div class="tab-bar">
|
||
<div class="tab-indicator" id="tab-indicator"></div>
|
||
<button class="active" data-tab="chat" data-i18n="tab.chat">Chat</button>
|
||
<button data-tab="memory" data-i18n="tab.memory">Memory</button>
|
||
<button data-tab="jobs" data-i18n="tab.jobs">Jobs</button>
|
||
<button data-tab="routines" data-i18n="tab.routines">Routines</button>
|
||
<button data-tab="settings" data-i18n="tab.settings">Settings</button>
|
||
<div class="spacer"></div>
|
||
|
||
<!-- Language Switcher -->
|
||
<div class="language-switcher">
|
||
<button class="language-btn" id="language-btn" type="button" title="Switch Language"
|
||
aria-label="Switch language" aria-haspopup="true" aria-expanded="false" aria-controls="language-menu">🌐</button>
|
||
<div class="language-menu" id="language-menu" style="display: none;">
|
||
<button type="button" class="language-option" data-action="switch-language" data-lang="en">English</button>
|
||
<button type="button" class="language-option" data-action="switch-language" data-lang="zh-CN">简体中文</button>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="status-logs-btn" data-tab="logs" data-i18n="tab.logs" title="Logs">Logs</button>
|
||
<button class="theme-toggle-btn" id="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
|
||
<svg class="theme-icon icon-dark" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
||
</svg>
|
||
<svg class="theme-icon icon-light" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
||
</svg>
|
||
<svg class="theme-icon icon-system" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>
|
||
</svg>
|
||
</button>
|
||
<span id="theme-announce" class="sr-only" aria-live="polite"></span>
|
||
<div class="tee-shield" id="tee-shield" style="display:none" title="Running in a Trusted Execution Environment">
|
||
<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="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||
</svg>
|
||
<span id="tee-shield-label" data-i18n="status.teeVerified">TEE Verified</span>
|
||
<div class="tee-popover" id="tee-popover"></div>
|
||
</div>
|
||
<div class="status" id="gateway-status-trigger">
|
||
<div class="dot" id="sse-dot"></div>
|
||
<span id="sse-status" data-i18n="status.connected">Connected</span>
|
||
<div class="gateway-popover" id="gateway-popover"></div>
|
||
</div>
|
||
<button class="restart-btn" id="restart-btn" data-i18n="status.restartTooltip"
|
||
data-i18n-attr="title" title="Gracefully restart the process" style="display: none;">
|
||
<svg id="restart-icon" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M23 4v6h-6"></path>
|
||
<path d="M1 20v-6h6"></path>
|
||
<path d="M3.51 9a9 9 0 0114.85-3.36M20.49 15a9 9 0 01-14.85 3.36"></path>
|
||
</svg>
|
||
<span data-i18n="status.restart">Restart</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Chat Tab -->
|
||
<div class="tab-panel active" id="tab-chat">
|
||
<div class="thread-sidebar" id="thread-sidebar">
|
||
<div class="assistant-item" id="assistant-thread">
|
||
<span class="assistant-label" id="assistant-label" data-i18n="chat.assistant">Assistant</span>
|
||
<span class="assistant-meta" id="assistant-meta"></span>
|
||
</div>
|
||
<div class="threads-section-header">
|
||
<span data-i18n="chat.conversations">Conversations</span>
|
||
<div class="spacer"></div>
|
||
<button class="thread-new-btn" id="thread-new-btn" data-i18n="chat.newThread" data-i18n-attr="title"
|
||
title="New thread (Ctrl/Cmd+N)">+</button>
|
||
<button class="thread-toggle-btn" id="thread-toggle-btn" data-i18n="chat.toggleSidebar"
|
||
data-i18n-attr="title" title="Toggle sidebar">«</button>
|
||
</div>
|
||
<div class="thread-list" id="thread-list"></div>
|
||
</div>
|
||
<div class="chat-container">
|
||
<div class="chat-messages" id="chat-messages"></div>
|
||
<div id="slash-autocomplete" class="slash-autocomplete" style="display:none"></div>
|
||
<div id="suggestion-chips" class="suggestion-chips" style="display:none"></div>
|
||
<div class="chat-input">
|
||
<div id="image-preview-strip" class="image-preview-strip"></div>
|
||
<div class="chat-input-wrapper">
|
||
<textarea id="chat-input" data-i18n="chat.inputPlaceholder" data-i18n-attr="placeholder" placeholder="Message or / for commands..." rows="1"></textarea>
|
||
<div id="ghost-text" class="ghost-text"></div>
|
||
</div>
|
||
<input type="file" id="image-file-input" accept="image/*" multiple style="display:none">
|
||
<button id="attach-btn" class="attach-btn" data-i18n="chat.attachImages" data-i18n-attr="title" title="Attach images"
|
||
aria-label="Attach images">📎</button>
|
||
<button id="send-btn" data-i18n="chat.send">Send</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Memory Tab -->
|
||
<div class="tab-panel" id="tab-memory">
|
||
<div class="memory-container">
|
||
<div class="memory-sidebar">
|
||
<div class="search-box">
|
||
<input type="text" id="memory-search" data-i18n="memory.searchPlaceholder" data-i18n-attr="placeholder" placeholder="Search memory...">
|
||
</div>
|
||
<div class="memory-tree" id="memory-tree"></div>
|
||
</div>
|
||
<div class="memory-content">
|
||
<div class="memory-breadcrumb" id="memory-breadcrumb">
|
||
<span id="memory-breadcrumb-path">workspace /</span>
|
||
<button class="memory-edit-btn" id="memory-edit-btn" style="display:none" data-i18n="memory.edit">Edit</button>
|
||
</div>
|
||
<div class="memory-viewer" id="memory-viewer">
|
||
<div class="empty" data-i18n="memory.selectFile">Select a file to view its contents</div>
|
||
</div>
|
||
<div class="memory-editor" id="memory-editor" style="display:none">
|
||
<textarea id="memory-edit-textarea"></textarea>
|
||
<div class="memory-editor-actions">
|
||
<button class="btn-save" id="memory-save-btn" data-i18n="memory.save">Save</button>
|
||
<button class="btn-cancel-edit" id="memory-cancel-btn" data-i18n="memory.cancel">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Jobs Tab -->
|
||
<div class="tab-panel" id="tab-jobs">
|
||
<div class="jobs-container">
|
||
<div class="jobs-summary" id="jobs-summary"></div>
|
||
<table class="jobs-table" id="jobs-table">
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="jobs.id">ID</th>
|
||
<th data-i18n="jobs.title">Title</th>
|
||
<th data-i18n="jobs.source">Source</th>
|
||
<th data-i18n="jobs.status">Status</th>
|
||
<th data-i18n="jobs.created">Created</th>
|
||
<th data-i18n="jobs.actions">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="jobs-tbody"></tbody>
|
||
</table>
|
||
<div class="empty-state" id="jobs-empty" style="display:none" data-i18n="jobs.empty">No jobs found</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Logs Tab -->
|
||
<div class="tab-panel" id="tab-logs">
|
||
<div class="logs-container">
|
||
<div class="logs-toolbar">
|
||
<select id="logs-server-level" title="Server-side log level (changes what the server emits)">
|
||
<option value="error">Server: ERROR</option>
|
||
<option value="warn">Server: WARN</option>
|
||
<option value="info" selected>Server: INFO</option>
|
||
<option value="debug">Server: DEBUG</option>
|
||
</select>
|
||
<select id="logs-level-filter">
|
||
<option value="all" data-i18n="logs.allLevels">All Levels</option>
|
||
<option value="ERROR" data-i18n="logs.error">Error</option>
|
||
<option value="WARN" data-i18n="logs.warn">Warn</option>
|
||
<option value="INFO" data-i18n="logs.info">Info</option>
|
||
<option value="DEBUG" data-i18n="logs.debug">Debug</option>
|
||
</select>
|
||
<input type="text" id="logs-target-filter" placeholder="Filter by target...">
|
||
<label class="logs-checkbox"><input type="checkbox" id="logs-autoscroll" checked> <span data-i18n="logs.autoScroll">Auto-scroll</span></label>
|
||
<button id="logs-pause-btn" data-i18n="logs.pause">Pause</button>
|
||
<button id="logs-clear-btn" data-i18n="logs.clear">Clear</button>
|
||
</div>
|
||
<div class="logs-output" id="logs-output"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Routines Tab -->
|
||
<div class="tab-panel" id="tab-routines">
|
||
<div class="routines-container">
|
||
<div class="routines-summary" id="routines-summary"></div>
|
||
<table class="routines-table" id="routines-table">
|
||
<thead>
|
||
<tr>
|
||
<th data-i18n="routines.name">Name</th>
|
||
<th data-i18n="routines.trigger">Trigger</th>
|
||
<th data-i18n="routines.action">Action</th>
|
||
<th data-i18n="routines.lastRun">Last Run</th>
|
||
<th data-i18n="routines.nextRun">Next Run</th>
|
||
<th data-i18n="routines.runs">Runs</th>
|
||
<th data-i18n="routines.status">Status</th>
|
||
<th data-i18n="routines.actions">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="routines-tbody"></tbody>
|
||
</table>
|
||
<div class="empty-state" id="routines-empty" style="display:none">
|
||
<span data-i18n="routines.noConfigured">No routines configured. Ask the assistant to create one.</span>
|
||
</div>
|
||
<div class="routine-detail" id="routine-detail" style="display:none"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Settings Tab -->
|
||
<div class="tab-panel" id="tab-settings">
|
||
<div class="settings-layout">
|
||
<div class="settings-sidebar">
|
||
<button class="settings-subtab active" data-settings-subtab="inference" data-i18n="settings.inference">Inference</button>
|
||
<button class="settings-subtab" data-settings-subtab="agent" data-i18n="settings.agent">Agent</button>
|
||
<button class="settings-subtab" data-settings-subtab="channels" data-i18n="settings.channels">Channels</button>
|
||
<button class="settings-subtab" data-settings-subtab="networking" data-i18n="settings.networking">Networking</button>
|
||
<button class="settings-subtab" data-settings-subtab="extensions" data-i18n="tab.extensions">Extensions</button>
|
||
<button class="settings-subtab" data-settings-subtab="mcp" data-i18n="settings.mcp">MCP</button>
|
||
<button class="settings-subtab" data-settings-subtab="skills" data-i18n="tab.skills">Skills</button>
|
||
<button class="settings-theme-toggle" id="settings-theme-toggle" data-i18n="theme.tooltipSystem" title="Toggle theme">Theme</button>
|
||
</div>
|
||
<div class="settings-content">
|
||
<div class="settings-toolbar">
|
||
<button id="settings-back-btn" class="settings-back-btn">← Back</button>
|
||
<div class="settings-search">
|
||
<input type="text" id="settings-search-input" data-i18n-placeholder="settings.searchPlaceholder" placeholder="Search settings..." data-i18n-attr="aria-label" data-i18n="settings.searchPlaceholder" aria-label="Search settings...">
|
||
</div>
|
||
<button id="settings-export-btn" class="settings-toolbar-btn" data-i18n="settings.export">Export</button>
|
||
<button id="settings-import-btn" class="settings-toolbar-btn" data-i18n="settings.import">Import</button>
|
||
</div>
|
||
<div class="settings-subpanel active" id="settings-inference">
|
||
<div class="extensions-container" id="settings-inference-content">
|
||
<div class="empty-state" data-i18n="common.loading">Loading settings...</div>
|
||
</div>
|
||
</div>
|
||
<div class="settings-subpanel" id="settings-agent">
|
||
<div class="extensions-container" id="settings-agent-content">
|
||
<div class="empty-state" data-i18n="common.loading">Loading settings...</div>
|
||
</div>
|
||
</div>
|
||
<div class="settings-subpanel" id="settings-channels">
|
||
<div class="extensions-container" id="settings-channels-content">
|
||
<div class="empty-state" data-i18n="common.loading">Loading channels...</div>
|
||
</div>
|
||
</div>
|
||
<div class="settings-subpanel" id="settings-networking">
|
||
<div class="extensions-container" id="settings-networking-content">
|
||
<div class="empty-state" data-i18n="common.loading">Loading...</div>
|
||
</div>
|
||
</div>
|
||
<div class="settings-subpanel" id="settings-extensions">
|
||
<div class="extensions-container">
|
||
<div class="extensions-section">
|
||
<h3 data-i18n="extensions.installed">Installed Extensions</h3>
|
||
<div class="extensions-list" id="extensions-list">
|
||
<div class="empty-state" data-i18n="common.loading">Loading...</div>
|
||
</div>
|
||
</div>
|
||
<div class="extensions-section" id="available-wasm-section">
|
||
<h3 data-i18n="extensions.available">Available Extensions</h3>
|
||
<div class="extensions-list" id="available-wasm-list">
|
||
<div class="empty-state" data-i18n="common.loading">Loading...</div>
|
||
</div>
|
||
</div>
|
||
<div class="extensions-section">
|
||
<h3 data-i18n="extensions.installWasm">Install Extension</h3>
|
||
<div class="ext-install-form">
|
||
<input type="text" id="wasm-install-name" data-i18n-placeholder="common.name" placeholder="Extension name">
|
||
<input type="text" id="wasm-install-url" placeholder="URL to .tar.gz bundle">
|
||
<button id="wasm-install-btn" data-i18n="extensions.install">Install</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="settings-subpanel" id="settings-mcp">
|
||
<div class="extensions-container">
|
||
<div class="extensions-section">
|
||
<h3 data-i18n="mcp.servers">MCP Servers</h3>
|
||
<div class="extensions-list" id="mcp-servers-list">
|
||
<div class="empty-state" data-i18n="common.loading">Loading...</div>
|
||
</div>
|
||
<h4 data-i18n="mcp.addCustom">Add Custom MCP Server</h4>
|
||
<div class="ext-install-form">
|
||
<input type="text" id="mcp-install-name" data-i18n-placeholder="common.name" placeholder="Server name">
|
||
<input type="text" id="mcp-install-url" placeholder="MCP server URL (https://...)">
|
||
<button id="mcp-add-btn" data-i18n="mcp.add">Add</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="settings-subpanel" id="settings-skills">
|
||
<div class="extensions-container">
|
||
<div class="extensions-section">
|
||
<h3 data-i18n="skills.searchClawHub">Search ClawHub</h3>
|
||
<div class="skill-search-box">
|
||
<input type="text" id="skill-search-input" data-i18n-placeholder="skills.searchPlaceholder" placeholder="Search for skills...">
|
||
<button id="skill-search-btn" data-i18n="skills.search">Search</button>
|
||
</div>
|
||
<div class="extensions-list" id="skill-search-results"></div>
|
||
</div>
|
||
<div class="extensions-section">
|
||
<h3 data-i18n="skills.installed">Installed Skills</h3>
|
||
<div class="extensions-list" id="skills-list">
|
||
<div class="empty-state" data-i18n="skills.loading">Loading skills...</div>
|
||
</div>
|
||
</div>
|
||
<div class="extensions-section">
|
||
<h3 data-i18n="skills.installByUrl">Install Skill by URL</h3>
|
||
<div class="ext-install-form">
|
||
<input type="text" id="skill-install-name" data-i18n-placeholder="skills.namePlaceholder" placeholder="Skill name or slug">
|
||
<input type="text" id="skill-install-url" data-i18n-placeholder="skills.urlPlaceholder" placeholder="HTTPS URL to SKILL.md (optional)">
|
||
<button id="skill-install-btn" data-i18n="extensions.install">Install</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Confirmation Modal -->
|
||
<div id="confirm-modal" class="modal-overlay" style="display:none" role="dialog" aria-modal="true" aria-labelledby="confirm-modal-title">
|
||
<div class="modal">
|
||
<h3 id="confirm-modal-title"></h3>
|
||
<p id="confirm-modal-message"></p>
|
||
<div class="modal-actions">
|
||
<button id="confirm-modal-cancel-btn" class="btn-secondary" data-i18n="btn.cancel">Cancel</button>
|
||
<button id="confirm-modal-btn" class="btn-danger">Confirm</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="toasts"></div>
|
||
<script src="/app.js"></script>
|
||
<script src="/i18n-app.js"></script>
|
||
</body>
|
||
</html>
|