diff --git a/ui/CHECKLIST.md b/ui/CHECKLIST.md index d2558b6bc..7f765fd58 100644 --- a/ui/CHECKLIST.md +++ b/ui/CHECKLIST.md @@ -22,7 +22,7 @@ Open the dashboard at `http://localhost:` (or the gateway's configured UI - [ ] Light - [ ] OpenKnot (Aurora) - [ ] Field Manual - - [ ] ClawDash (Chrome) + - [ ] OpenClaw (Chrome) - [ ] Glass components (cards, panels, inputs) render correctly per theme - [ ] Theme persists across page reload diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index 8c66a63c2..1f4cbe9c8 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -21,6 +21,7 @@ export const en: TranslationMap = { settings: "Settings", expand: "Expand sidebar", collapse: "Collapse sidebar", + resize: "Resize sidebar", }, tabs: { agents: "Agents", @@ -38,19 +39,19 @@ export const en: TranslationMap = { logs: "Logs", }, subtitles: { - agents: "Manage agent workspaces, tools, and identities.", - overview: "Gateway status, entry points, and a fast health read.", - channels: "Manage channels and settings.", - instances: "Presence beacons from connected clients and nodes.", - sessions: "Inspect active sessions and adjust per-session defaults.", - usage: "Monitor API usage and costs.", - cron: "Schedule wakeups and recurring agent runs.", - skills: "Manage skill availability and API key injection.", - nodes: "Paired devices, capabilities, and command exposure.", - chat: "Direct gateway chat session for quick interventions.", - config: "Edit ~/.openclaw/openclaw.json safely.", - debug: "Gateway snapshots, events, and manual RPC calls.", - logs: "Live tail of the gateway file logs.", + agents: "Workspaces, tools, identities.", + overview: "Status, entry points, health.", + channels: "Channels and settings.", + instances: "Connected clients and nodes.", + sessions: "Active sessions and defaults.", + usage: "API usage and costs.", + cron: "Wakeups and recurring runs.", + skills: "Skills and API keys.", + nodes: "Paired devices and commands.", + chat: "Gateway chat for quick interventions.", + config: "Edit openclaw.json.", + debug: "Snapshots, events, RPC.", + logs: "Live gateway logs.", }, overview: { access: { @@ -140,7 +141,7 @@ export const en: TranslationMap = { }, login: { subtitle: "Gateway Dashboard", - tokenPlaceholder: "paste gateway token", + passwordPlaceholder: "optional", }, chat: { disconnected: "Disconnected from gateway.", diff --git a/ui/src/i18n/locales/pt-BR.ts b/ui/src/i18n/locales/pt-BR.ts index b42234917..13b7ab922 100644 --- a/ui/src/i18n/locales/pt-BR.ts +++ b/ui/src/i18n/locales/pt-BR.ts @@ -21,6 +21,7 @@ export const pt_BR: TranslationMap = { settings: "Configurações", expand: "Expandir barra lateral", collapse: "Recolher barra lateral", + resize: "Redimensionar barra lateral", }, tabs: { agents: "Agentes", @@ -38,19 +39,19 @@ export const pt_BR: TranslationMap = { logs: "Logs", }, subtitles: { - agents: "Gerenciar espaços de trabalho, ferramentas e identidades de agentes.", - overview: "Status do gateway, pontos de entrada e leitura rápida de saúde.", - channels: "Gerenciar canais e configurações.", - instances: "Beacons de presença de clientes e nós conectados.", - sessions: "Inspecionar sessões ativas e ajustar padrões por sessão.", - usage: "Monitorar uso e custos da API.", - cron: "Agendar despertares e execuções recorrentes de agentes.", - skills: "Gerenciar disponibilidade de habilidades e injeção de chaves de API.", - nodes: "Dispositivos pareados, capacidades e exposição de comandos.", - chat: "Sessão de chat direta com o gateway para intervenções rápidas.", - config: "Editar ~/.openclaw/openclaw.json com segurança.", - debug: "Snapshots do gateway, eventos e chamadas RPC manuais.", - logs: "Acompanhamento ao vivo dos logs de arquivo do gateway.", + agents: "Espaços, ferramentas, identidades.", + overview: "Status, entrada, saúde.", + channels: "Canais e configurações.", + instances: "Clientes e nós conectados.", + sessions: "Sessões ativas e padrões.", + usage: "Uso e custos da API.", + cron: "Despertares e execuções.", + skills: "Habilidades e chaves API.", + nodes: "Dispositivos e comandos.", + chat: "Chat do gateway para intervenções rápidas.", + config: "Editar openclaw.json.", + debug: "Snapshots, eventos, RPC.", + logs: "Logs ao vivo do gateway.", }, overview: { access: { @@ -142,7 +143,7 @@ export const pt_BR: TranslationMap = { }, login: { subtitle: "Painel do Gateway", - tokenPlaceholder: "cole o token do gateway", + passwordPlaceholder: "opcional", }, chat: { disconnected: "Desconectado do gateway.", diff --git a/ui/src/i18n/locales/zh-CN.ts b/ui/src/i18n/locales/zh-CN.ts index 8fd4d86bd..f7e9a99d2 100644 --- a/ui/src/i18n/locales/zh-CN.ts +++ b/ui/src/i18n/locales/zh-CN.ts @@ -21,6 +21,7 @@ export const zh_CN: TranslationMap = { settings: "设置", expand: "展开侧边栏", collapse: "折叠侧边栏", + resize: "调整侧边栏大小", }, tabs: { agents: "代理", @@ -38,19 +39,19 @@ export const zh_CN: TranslationMap = { logs: "日志", }, subtitles: { - agents: "管理代理工作区、工具和身份。", - overview: "网关状态、入口点和快速健康读取。", - channels: "管理频道和设置。", - instances: "来自已连接客户端和节点的在线信号。", - sessions: "检查活动会话并调整每个会话的默认设置。", - usage: "监控 API 使用情况和成本。", - cron: "安排唤醒和重复的代理运行。", - skills: "管理技能可用性和 API 密钥注入。", - nodes: "配对设备、功能和命令公开。", - chat: "用于快速干预的直接网关聊天会话。", - config: "安全地编辑 ~/.openclaw/openclaw.json。", - debug: "网关快照、事件和手动 RPC 调用。", - logs: "网关文件日志的实时追踪。", + agents: "工作区、工具、身份。", + overview: "状态、入口点、健康。", + channels: "频道和设置。", + instances: "已连接客户端和节点。", + sessions: "活动会话和默认设置。", + usage: "API 使用情况和成本。", + cron: "唤醒和重复运行。", + skills: "技能和 API 密钥。", + nodes: "配对设备和命令。", + chat: "网关聊天,快速干预。", + config: "编辑 openclaw.json。", + debug: "快照、事件、RPC。", + logs: "实时网关日志。", }, overview: { access: { @@ -139,7 +140,7 @@ export const zh_CN: TranslationMap = { }, login: { subtitle: "网关仪表盘", - tokenPlaceholder: "粘贴网关令牌", + passwordPlaceholder: "可选", }, chat: { disconnected: "已断开与网关的连接。", diff --git a/ui/src/i18n/locales/zh-TW.ts b/ui/src/i18n/locales/zh-TW.ts index c480d32fb..a64c7bf09 100644 --- a/ui/src/i18n/locales/zh-TW.ts +++ b/ui/src/i18n/locales/zh-TW.ts @@ -21,6 +21,7 @@ export const zh_TW: TranslationMap = { settings: "設置", expand: "展開側邊欄", collapse: "折疊側邊欄", + resize: "調整側邊欄大小", }, tabs: { agents: "代理", @@ -38,19 +39,19 @@ export const zh_TW: TranslationMap = { logs: "日誌", }, subtitles: { - agents: "管理代理工作區、工具和身份。", - overview: "網關狀態、入口點和快速健康讀取。", - channels: "管理頻道和設置。", - instances: "來自已連接客戶端和節點的在線信號。", - sessions: "檢查活動會話並調整每個會話的默認設置。", - usage: "監控 API 使用情況和成本。", - cron: "安排喚醒和重複的代理運行。", - skills: "管理技能可用性和 API 密鑰注入。", - nodes: "配對設備、功能和命令公開。", - chat: "用於快速干預的直接網關聊天會話。", - config: "安全地編輯 ~/.openclaw/openclaw.json。", - debug: "網關快照、事件和手動 RPC 調用。", - logs: "網關文件日志的實時追蹤。", + agents: "工作區、工具、身份。", + overview: "狀態、入口點、健康。", + channels: "頻道和設置。", + instances: "已連接客戶端和節點。", + sessions: "活動會話和默認設置。", + usage: "API 使用情況和成本。", + cron: "喚醒和重複運行。", + skills: "技能和 API 密鑰。", + nodes: "配對設備和命令。", + chat: "網關聊天,快速干預。", + config: "編輯 openclaw.json。", + debug: "快照、事件、RPC。", + logs: "實時網關日誌。", }, overview: { access: { @@ -139,7 +140,7 @@ export const zh_TW: TranslationMap = { }, login: { subtitle: "閘道儀表板", - tokenPlaceholder: "貼上閘道令牌", + passwordPlaceholder: "可選", }, chat: { disconnected: "已斷開與網關的連接。", diff --git a/ui/src/styles/base.css b/ui/src/styles/base.css index de02aef78..165a2a314 100644 --- a/ui/src/styles/base.css +++ b/ui/src/styles/base.css @@ -96,55 +96,55 @@ --radius-full: 9999px; } -/* ─── Theme: light (Docs) — Warm Editorial Dark ─── */ +/* ─── Theme: light — Luxe Cream & Coral ─── */ :root[data-theme="light"] { - color-scheme: dark; + color-scheme: light; - --vscode-bg: #0e0c0e; - --vscode-sidebar: #131012; - --vscode-panel: #161214; - --vscode-panel-border: rgba(255, 255, 255, 0.06); - --vscode-surface: #1a1618; - --vscode-hover: #201c1e; - --vscode-contrast: #080608; - --vscode-text: #d5d0cf; - --vscode-muted: #7a7472; - --vscode-subtle: #4a4442; - --vscode-ghost: #1a1616; - --vscode-accent: #ca3a29; - --vscode-accent-alpha: rgba(202, 58, 41, 0.14); - --vscode-selection: #3d1418; - --vscode-success: #00d4aa; - --vscode-danger: #ca3a29; + --vscode-bg: #faf7f2; + --vscode-sidebar: #f5f0e8; + --vscode-panel: #fffef9; + --vscode-panel-border: rgba(26, 22, 20, 0.08); + --vscode-surface: #fffef9; + --vscode-hover: #f0ebe3; + --vscode-contrast: #f0ebe3; + --vscode-text: #1a1614; + --vscode-muted: #6b5d54; + --vscode-subtle: #9c8f84; + --vscode-ghost: #ebe6df; + --vscode-accent: #c73526; + --vscode-accent-alpha: rgba(199, 53, 38, 0.12); + --vscode-selection: rgba(199, 53, 38, 0.18); + --vscode-success: #0d9b7a; + --vscode-danger: #c73526; - --kn-claw: #ca3a29; - --kn-claw-bright: #fd8e2e; - --kn-claw-dim: rgba(202, 58, 41, 0.12); - --kn-claw-ember: #fb9231; - --kn-claw-deep: #9a2d1f; - --kn-ocean: #0e0c0e; - --kn-ocean-bright: #201c1e; - --kn-ocean-mid: #161214; - --kn-ocean-dim: rgba(14, 12, 14, 0.8); - --kn-ocean-deep: #0e0c0e; - --kn-silver: #8a7e72; - --kn-silver-bright: #c0b4a8; - --kn-silver-dim: rgba(138, 126, 114, 0.12); - --kn-bioluminescence: #00d4aa; - --kn-warm-dark: #1a1416; - --kn-void: #1a1416; + --kn-claw: #c73526; + --kn-claw-bright: #e85a4a; + --kn-claw-dim: rgba(199, 53, 38, 0.14); + --kn-claw-ember: #d94a3a; + --kn-claw-deep: #9a2a1e; + --kn-ocean: #faf7f2; + --kn-ocean-bright: #fffef9; + --kn-ocean-mid: #f5f0e8; + --kn-ocean-dim: rgba(250, 247, 242, 0.9); + --kn-ocean-deep: #f0ebe3; + --kn-silver: #6b5d54; + --kn-silver-bright: #1a1614; + --kn-silver-dim: rgba(107, 93, 84, 0.12); + --kn-bioluminescence: #0d9b7a; + --kn-warm-dark: #1a1614; + --kn-void: #ebe6df; - --glass-blur: 0px; - --glass-saturate: 100%; - --glass-bg: rgba(22, 18, 20, 0.95); - --glass-bg-elevated: rgba(26, 22, 24, 0.96); - --glass-border: rgba(255, 255, 255, 0.06); - --glass-border-hover: rgba(202, 58, 41, 0.25); - --glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.03); - --glass-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3); - --glass-shadow-md: 0 8px 24px rgba(0, 0, 0, 0.4); - --glass-shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.5); + --glass-blur: 12px; + --glass-saturate: 110%; + --glass-bg: rgba(255, 254, 249, 0.88); + --glass-bg-elevated: rgba(255, 255, 255, 0.95); + --glass-border: rgba(26, 22, 20, 0.1); + --glass-border-hover: rgba(199, 53, 38, 0.35); + --glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.9); + --glass-shadow-sm: 0 2px 12px rgba(26, 22, 20, 0.06), 0 1px 3px rgba(26, 22, 20, 0.04); + --glass-shadow-md: 0 8px 32px rgba(26, 22, 20, 0.08), 0 2px 8px rgba(26, 22, 20, 0.04); + --glass-shadow-lg: 0 20px 56px rgba(26, 22, 20, 0.12), 0 4px 16px rgba(26, 22, 20, 0.06); --radius-xs: 4px; --radius-sm: 8px; @@ -491,6 +491,16 @@ --agent-tab-hover-bg: var(--vscode-accent-alpha); } +/* Light theme semantic overrides (accent buttons need dark text) */ +:root[data-theme="light"] { + --card-highlight: rgba(255, 255, 255, 0.85); + --accent-foreground: #ffffff; + --primary-foreground: #ffffff; + --destructive-foreground: #ffffff; + --focus-offset-color: var(--bg); + --grid-line: rgba(26, 22, 20, 0.06); +} + /* ─── Accessibility: High Contrast ─── */ @media (prefers-contrast: more) { @@ -714,6 +724,20 @@ select { display: none; } +/* ─── light — Luxe Cream ambient gradient ─── */ + +:root[data-theme="light"] body { + background: + radial-gradient(ellipse 90% 60% at 50% -15%, rgba(199, 53, 38, 0.04) 0%, transparent 55%), + radial-gradient(ellipse 70% 50% at 85% 40%, rgba(13, 155, 122, 0.03) 0%, transparent 50%), + radial-gradient(ellipse 60% 40% at 15% 80%, rgba(199, 53, 38, 0.02) 0%, transparent 45%), + var(--bg); +} + +:root[data-theme="light"] body::after { + display: none; +} + /* ─── clawdash — Chrome Metallic Overrides ─── */ :root[data-theme="clawdash"] body { diff --git a/ui/src/styles/chat/agent-chat.css b/ui/src/styles/chat/agent-chat.css index 0b70ef31a..1642dd7d1 100644 --- a/ui/src/styles/chat/agent-chat.css +++ b/ui/src/styles/chat/agent-chat.css @@ -107,9 +107,12 @@ letter-spacing: 0.01em; } -.agent-chat__badge svg { +.agent-chat__badge svg, +.agent-chat__badge img { width: 14px; height: 14px; + object-fit: contain; + vertical-align: -0.15em; } /* ─── Starter Cards ─── */ @@ -239,6 +242,17 @@ flex-shrink: 0; } +.agent-chat__avatar--logo { + background: transparent; +} + +.agent-chat__avatar--logo svg, +.agent-chat__avatar--logo img { + width: 48px; + height: 48px; + object-fit: contain; +} + .agent-chat__avatar--sm { width: 24px; height: 24px; @@ -1124,10 +1138,11 @@ display: flex; align-items: center; justify-content: space-between; - padding: 4px 12px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); + padding: 2px 8px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); flex-shrink: 0; - gap: 6px; + gap: 4px; + min-height: 28px; } .chat-agent-bar__left { @@ -1145,143 +1160,29 @@ } .chat-agent-bar__name { - font-size: 12px; + font-size: 11px; font-weight: 600; color: var(--text); } .chat-agent-select { - background: color-mix(in srgb, var(--secondary) 70%, transparent); - border: 1px solid var(--border); - border-radius: var(--radius-md); + background: transparent; + border: none; color: var(--text); - font-size: 12px; - font-weight: 500; - padding: 2px 20px 2px 6px; + font-size: 11px; + font-weight: 600; + padding: 0 14px 0 0; cursor: pointer; appearance: none; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%237d8590' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 24 24' fill='none' stroke='%237d8590' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; - background-position: right 4px center; - transition: - border-color 150ms ease, - background 150ms ease; + background-position: right 0 center; } .chat-agent-select:hover { - border-color: var(--border-strong); - background: color-mix(in srgb, var(--secondary) 90%, transparent); + color: var(--accent); } .chat-agent-select:focus { outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 3px var(--accent-subtle); -} - -/* ─── Sessions Panel ─── */ - -.chat-sessions-panel { - position: relative; -} - -.chat-sessions-summary { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 2px 6px; - border-radius: var(--radius-sm); - font-size: 11px; - font-weight: 500; - color: var(--muted); - cursor: pointer; - user-select: none; - list-style: none; - transition: - color 150ms ease, - background 150ms ease; -} - -.chat-sessions-summary::-webkit-details-marker { - display: none; -} - -.chat-sessions-summary::before { - content: "▸"; - font-size: 9px; - transition: transform 150ms ease; -} - -.chat-sessions-panel[open] > .chat-sessions-summary::before { - transform: rotate(90deg); -} - -.chat-sessions-summary:hover { - color: var(--text); - background: color-mix(in srgb, var(--bg-hover) 60%, transparent); -} - -.chat-sessions-summary svg { - width: 12px; - height: 12px; -} - -.chat-sessions-list { - position: absolute; - top: 100%; - left: 0; - z-index: 50; - min-width: 240px; - max-width: 360px; - max-height: 280px; - overflow-y: auto; - margin-top: 4px; - padding: 4px; - background: var(--popover); - border: 1px solid var(--border); - border-radius: var(--radius-md); - box-shadow: var(--shadow-lg); - display: flex; - flex-direction: column; - gap: 2px; -} - -.chat-session-item { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; - padding: 4px 8px; - border: none; - border-radius: var(--radius-sm); - background: none; - color: var(--text); - font-size: 11px; - cursor: pointer; - text-align: left; - width: 100%; - transition: background 120ms ease; -} - -.chat-session-item:hover { - background: var(--bg-hover); -} - -.chat-session-item--active { - background: var(--accent-subtle); - color: var(--accent); - font-weight: 500; -} - -.chat-session-item__name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; -} - -.chat-session-item__meta { - font-size: 11px; - flex-shrink: 0; - white-space: nowrap; } diff --git a/ui/src/styles/chat/grouped.css b/ui/src/styles/chat/grouped.css index 46cd18f4e..d9267cc19 100644 --- a/ui/src/styles/chat/grouped.css +++ b/ui/src/styles/chat/grouped.css @@ -7,7 +7,7 @@ display: flex; gap: 12px; align-items: flex-start; - margin-bottom: 16px; + margin-bottom: 8px; margin-left: 4px; margin-right: 16px; } @@ -124,6 +124,13 @@ img.chat-avatar { object-position: center; } +/* Logo avatar (OpenClaw favicon) - contain to show full logo */ +img.chat-avatar.chat-avatar--logo { + object-fit: contain; + padding: 6px; + box-sizing: border-box; +} + /* Minimal Bubble Design - dynamic width based on content */ .chat-bubble { position: relative; diff --git a/ui/src/styles/chat/layout.css b/ui/src/styles/chat/layout.css index a94a9ce2f..f99ac1c56 100644 --- a/ui/src/styles/chat/layout.css +++ b/ui/src/styles/chat/layout.css @@ -9,7 +9,8 @@ flex-direction: column; flex: 1 1 0; height: 100%; - min-height: 0; /* Allow flex shrinking */ + min-height: 0; + /* Allow flex shrinking */ overflow: hidden; background: transparent !important; border: none !important; @@ -21,18 +22,18 @@ display: flex; justify-content: space-between; align-items: center; - gap: 12px; + gap: 8px; flex-wrap: nowrap; flex-shrink: 0; - padding-bottom: 12px; - margin-bottom: 12px; + padding-bottom: 4px; + margin-bottom: 4px; background: transparent; } .chat-header__left { display: flex; align-items: center; - gap: 12px; + gap: 8px; flex-wrap: wrap; min-width: 0; } @@ -40,21 +41,23 @@ .chat-header__right { display: flex; align-items: center; - gap: 8px; + gap: 6px; } .chat-session { - min-width: 180px; + min-width: 140px; } /* Chat thread - scrollable middle section, transparent */ .chat-thread { - flex: 1 1 0; /* Grow, shrink, and use 0 base for proper scrolling */ + flex: 1 1 0; + /* Grow, shrink, and use 0 base for proper scrolling */ overflow-y: auto; overflow-x: hidden; - padding: 10px 6px; + padding: 6px 6px 4px; margin: 0 -4px; - min-height: 0; /* Allow shrinking for flex scroll behavior */ + min-height: 0; + /* Allow shrinking for flex scroll behavior */ border-radius: var(--radius-lg); background: linear-gradient( 180deg, @@ -151,9 +154,10 @@ flex-shrink: 0; display: flex; flex-direction: column; - gap: 12px; - margin-top: auto; /* Push to bottom of flex container */ - padding: 14px 6px 6px; + gap: 8px; + margin-top: auto; + /* Push to bottom of flex container */ + padding: 6px 6px 6px; background: linear-gradient(to bottom, transparent, color-mix(in srgb, var(--bg) 94%, black) 22%); backdrop-filter: blur(4px); z-index: 10; @@ -170,7 +174,8 @@ border: 1px solid var(--border); width: fit-content; max-width: 100%; - align-self: flex-start; /* Don't stretch in flex column parent */ + align-self: flex-start; + /* Don't stretch in flex column parent */ } .chat-attachment { @@ -318,13 +323,13 @@ display: flex; align-items: center; justify-content: flex-start; - gap: 6px; + gap: 8px; flex-wrap: wrap; } .chat-controls__session { min-width: 120px; - max-width: 260px; + max-width: 220px; } .chat-controls__thinking { @@ -336,7 +341,7 @@ /* Icon button style */ .btn--icon { - padding: 6px !important; + padding: 0 !important; min-width: 32px; height: 32px; display: inline-flex; @@ -347,12 +352,17 @@ border-radius: var(--radius-md); } -/* Controls separator */ +/* Controls separator — renders as a thin vertical divider */ .chat-controls__separator { - color: var(--border); - font-size: 14px; - margin: 0 2px; - font-weight: 300; + width: 1px; + height: 32px; + background: var(--border); + font-size: 0; + color: transparent; + overflow: hidden; + align-self: center; + flex-shrink: 0; + margin: 0 4px; } .btn--icon:hover { @@ -371,6 +381,7 @@ display: block; width: 16px; height: 16px; + flex-shrink: 0; stroke: currentColor; fill: none; stroke-width: 1.5px; @@ -379,9 +390,9 @@ } .chat-controls__session select { - padding: 4px 8px; - font-size: 12px; - max-width: 260px; + padding: 0 28px 0 10px; + font-size: 13px; + max-width: 220px; overflow: hidden; text-overflow: ellipsis; } @@ -390,16 +401,17 @@ display: flex; align-items: center; gap: 4px; - font-size: 11px; - padding: 2px 8px; + font-size: 12px; + padding: 0 10px; + height: 32px; background: color-mix(in srgb, var(--secondary) 90%, transparent); - border-radius: var(--radius-sm); + border-radius: var(--radius-md); border: 1px solid color-mix(in srgb, var(--border) 88%, transparent); } @media (max-width: 640px) { .chat-session { - min-width: 100px; + min-width: 80px; } .chat-compose { @@ -408,11 +420,15 @@ .chat-controls { flex-wrap: wrap; - gap: 4px; + gap: 3px; } .chat-controls__session { - min-width: 100px; - max-width: 180px; + min-width: 80px; + max-width: 160px; + } + + .chat-controls__separator { + display: none; } } diff --git a/ui/src/styles/chat/sidebar.css b/ui/src/styles/chat/sidebar.css index bc2949309..22704cf84 100644 --- a/ui/src/styles/chat/sidebar.css +++ b/ui/src/styles/chat/sidebar.css @@ -18,7 +18,8 @@ .chat-sidebar { flex: 1; - min-width: 300px; + min-width: 200px; + container-type: inline-size; border-left: 1px solid color-mix(in srgb, var(--border) 90%, transparent); display: flex; flex-direction: column; @@ -77,11 +78,14 @@ flex: 1; overflow: auto; padding: 16px; + min-width: 0; } .sidebar-markdown { font-size: 14px; line-height: 1.6; + overflow-wrap: break-word; + word-break: break-word; } .sidebar-markdown pre { @@ -97,6 +101,38 @@ font-size: 13px; } +/* Minimal state when sidebar is narrow: hide content, show expand hint */ +@container (max-width: 260px) { + .chat-sidebar .sidebar-header { + padding: 6px 8px; + border-bottom: none; + justify-content: flex-end; + } + + .chat-sidebar .sidebar-title { + display: none; + } + + .chat-sidebar .sidebar-content { + display: flex; + align-items: center; + justify-content: center; + padding: 8px; + overflow: hidden; + } + + .chat-sidebar .sidebar-content > * { + display: none; + } + + .chat-sidebar .sidebar-content::before { + content: "← Drag to expand"; + font-size: 11px; + color: var(--muted); + white-space: nowrap; + } +} + /* Mobile: Full-screen modal */ @media (max-width: 768px) { .chat-split-container--open { diff --git a/ui/src/styles/components.css b/ui/src/styles/components.css index 8c323526b..ea5991edc 100644 --- a/ui/src/styles/components.css +++ b/ui/src/styles/components.css @@ -111,7 +111,7 @@ border: 1px solid var(--border); background: var(--card); border-radius: var(--radius-lg); - padding: 20px; + padding: 14px 16px; animation: rise 0.35s var(--ease-out) backwards; transition: border-color var(--duration-normal) var(--ease-out), @@ -130,7 +130,7 @@ } .card-title { - font-size: 16px; + font-size: 15px; font-weight: 600; letter-spacing: -0.02em; color: var(--text-strong); @@ -138,9 +138,9 @@ .card-sub { color: var(--muted); - font-size: 14px; - margin-top: 6px; - line-height: 1.5; + font-size: 12px; + margin-top: 4px; + line-height: 1.45; } /* =========================================== @@ -150,12 +150,13 @@ .stat { background: color-mix(in srgb, var(--card) 96%, transparent); border-radius: var(--radius-md); - padding: 14px 16px; + padding: 10px 12px; border: 1px solid color-mix(in srgb, var(--border) 92%, transparent); transition: border-color var(--duration-normal) var(--ease-out), box-shadow var(--duration-normal) var(--ease-out); box-shadow: inset 0 1px 0 var(--card-highlight); + text-align: center; } .stat:hover { @@ -314,109 +315,37 @@ } /* =========================================== - Theme Toggle + Theme Select =========================================== */ -.theme-toggle { - display: inline-flex; - align-items: center; - justify-content: center; +.theme-select { + appearance: none; border: 1px solid var(--clay-border-color); - border-radius: 999px; - padding: 5px; - height: 36px; + border-radius: var(--radius-md); + padding: 4px 28px 4px 10px; + height: 28px; + min-width: 90px; + font-size: 12px; + font-weight: 500; + color: var(--text); background: var(--clay-bg); - overflow: hidden; - max-width: 36px; - transition: - max-width var(--clay-duration-normal) var(--clay-easing), - padding var(--clay-duration-normal) var(--clay-easing); -} - -@media (hover: hover) { - .theme-toggle:hover { - max-width: 400px; - padding: 4px 6px; - } -} - -.theme-toggle:focus-within { - max-width: 400px; - padding: 4px 6px; -} - -.theme-toggle.theme-toggle--open { - max-width: 400px; - padding: 4px 6px; -} - -.theme-btn { - border: 0; - background: transparent; - padding: 6px 10px; - border-radius: 999px; - font-size: 0.84rem; - color: var(--muted); - display: inline-flex; - align-items: center; - gap: 0.35rem; - white-space: nowrap; - flex-shrink: 0; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236e7a8a' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; cursor: pointer; transition: - color var(--clay-duration-fast) var(--clay-easing), - background var(--clay-duration-fast) var(--clay-easing), - transform var(--clay-duration-fast) var(--clay-easing); + border-color var(--clay-duration-fast) var(--clay-easing), + box-shadow var(--clay-duration-fast) var(--clay-easing); } -.theme-btn.active { - padding: 6px 8px; - background: var(--clay-bg-button); - color: var(--text); - box-shadow: var(--clay-shadow-pressed); +.theme-select:hover { + border-color: var(--border-strong); } -.theme-btn:not(.active) { - opacity: 0; - pointer-events: none; - width: 0; - padding: 6px 0; - overflow: hidden; - transition: - opacity var(--clay-duration-fast) var(--clay-easing), - width var(--clay-duration-fast) var(--clay-easing), - padding var(--clay-duration-fast) var(--clay-easing), - color var(--clay-duration-fast) var(--clay-easing), - background var(--clay-duration-fast) var(--clay-easing), - transform var(--clay-duration-fast) var(--clay-easing); -} - -.theme-toggle:hover .theme-btn, -.theme-toggle:focus-within .theme-btn, -.theme-toggle--open .theme-btn { - opacity: 1; - pointer-events: auto; - width: auto; - padding: 6px 10px; -} - -.theme-btn:hover { - border: 0; - color: var(--text); -} - -.theme-btn:active { - transform: scale(0.93); -} - -.theme-btn svg { - width: 16px; - height: 16px; - stroke: currentColor; - fill: none; - stroke-width: 1.5px; - stroke-linecap: round; - stroke-linejoin: round; +.theme-select:focus-visible { + outline: none; + border-color: var(--ring); + box-shadow: var(--focus-ring); } /* =========================================== @@ -477,14 +406,15 @@ } .btn svg { + display: block; width: 16px; height: 16px; + flex-shrink: 0; stroke: currentColor; fill: none; stroke-width: 1.5px; stroke-linecap: round; stroke-linejoin: round; - flex-shrink: 0; } .btn.primary { @@ -626,6 +556,47 @@ align-items: center; } +.field-inline { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 13px; +} + +.field-inline span { + color: var(--muted); + font-weight: 500; + white-space: nowrap; +} + +.field-inline input:not([type="checkbox"]) { + border: 1px solid color-mix(in srgb, var(--input) 92%, transparent); + background: color-mix(in srgb, var(--card) 96%, var(--bg)); + border-radius: var(--radius-md); + padding: 6px 10px; + font-size: 13px; + outline: none; + transition: + border-color var(--duration-fast) ease, + box-shadow var(--duration-fast) ease; +} + +.field-inline input:not([type="checkbox"]):focus-visible { + border-color: var(--ring); + box-shadow: var(--focus-ring); + background: var(--card); +} + +.field-inline.checkbox { + cursor: pointer; + user-select: none; +} + +.field-inline.checkbox input[type="checkbox"] { + margin: 0; + accent-color: var(--accent); +} + .config-form .field.checkbox { grid-template-columns: 18px minmax(0, 1fr); column-gap: 10px; @@ -1147,6 +1118,12 @@ min-width: 0; } +/* Sessions table: wider key column */ +.data-table th:first-child, +.data-table td:first-child { + min-width: 280px; +} + .session-key-cell .session-link, .session-key-display-name { overflow-wrap: anywhere; @@ -1157,6 +1134,273 @@ font-size: 11px; } +/* =========================================== + Data Table (shadcn-inspired) + =========================================== */ + +.data-table-wrapper { + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background: var(--card); + overflow: hidden; +} + +.data-table-toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 12px; + padding: 16px; + border-bottom: 1px solid var(--border); +} + +.data-table-search { + display: flex; + align-items: center; + gap: 8px; + flex: 1; + min-width: 200px; + max-width: 320px; +} + +.data-table-search input { + flex: 1; + height: 36px; + padding: 0 12px; + font-size: 14px; + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--bg); + color: var(--text); + transition: border-color var(--duration-fast) ease; +} + +.data-table-search input::placeholder { + color: var(--muted); +} + +.data-table-search input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 2px var(--accent-subtle); +} + +.data-table-search .data-table-search__icon { + width: 16px; + height: 16px; + color: var(--muted); + flex-shrink: 0; +} + +.data-table-container { + overflow-x: auto; +} + +.data-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +.data-table th, +.data-table td { + padding: 12px 16px; + text-align: left; + border-bottom: 1px solid var(--border); + vertical-align: middle; +} + +.data-table th { + font-weight: 600; + color: var(--muted); + background: color-mix(in srgb, var(--secondary) 50%, transparent); + white-space: nowrap; +} + +.data-table th[data-sortable] { + cursor: pointer; + user-select: none; +} + +.data-table th[data-sortable]:hover { + color: var(--text); +} + +.data-table th .data-table-sort-icon { + display: inline-flex; + margin-left: 4px; + opacity: 0.5; + vertical-align: middle; +} + +.data-table th[data-sort-dir] .data-table-sort-icon { + opacity: 1; +} + +.data-table tbody tr { + transition: background var(--duration-fast) ease; +} + +.data-table tbody tr:hover { + background: var(--bg-hover); +} + +.data-table tbody tr:last-child td { + border-bottom: none; +} + +.data-table-badge { + display: inline-flex; + align-items: center; + padding: 2px 8px; + font-size: 11px; + font-weight: 500; + border-radius: var(--radius-full); + text-transform: capitalize; +} + +.data-table-badge--direct { + background: color-mix(in srgb, var(--accent) 15%, transparent); + color: var(--accent); +} + +.data-table-badge--group { + background: color-mix(in srgb, var(--ok) 15%, transparent); + color: var(--ok); +} + +.data-table-badge--global { + background: color-mix(in srgb, var(--muted) 30%, transparent); + color: var(--muted); +} + +.data-table-badge--unknown { + background: color-mix(in srgb, var(--warn) 15%, transparent); + color: var(--warn); +} + +.data-table-row-actions { + position: relative; + display: inline-flex; +} + +.data-table-row-actions__trigger { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 0; + border: none; + border-radius: var(--radius-md); + background: transparent; + color: var(--muted); + cursor: pointer; + transition: + color var(--duration-fast) ease, + background var(--duration-fast) ease; +} + +.data-table-row-actions__trigger:hover { + color: var(--text); + background: var(--bg-hover); +} + +.data-table-row-actions__trigger svg { + width: 16px; + height: 16px; +} + +.data-table-row-actions__menu { + position: absolute; + top: calc(100% + 4px); + right: 0; + z-index: 50; + min-width: 160px; + padding: 4px; + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--card); + box-shadow: var(--shadow-lg); +} + +.data-table-row-actions__menu button { + display: block; + width: 100%; + padding: 8px 12px; + font-size: 13px; + text-align: left; + border: none; + border-radius: var(--radius-sm); + background: none; + color: var(--text); + cursor: pointer; + transition: background var(--duration-fast) ease; +} + +.data-table-row-actions__menu button:hover { + background: var(--bg-hover); +} + +.data-table-row-actions__menu button.danger { + color: var(--danger); +} + +.data-table-row-actions__menu button.danger:hover { + background: var(--danger-subtle); +} + +.data-table-pagination { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px 16px; + border-top: 1px solid var(--border); + font-size: 13px; + color: var(--muted); +} + +.data-table-pagination__info { + flex: 1; +} + +.data-table-pagination__controls { + display: flex; + align-items: center; + gap: 8px; +} + +.data-table-pagination__controls button { + padding: 6px 12px; + font-size: 13px; + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--card); + color: var(--text); + cursor: pointer; + transition: + border-color var(--duration-fast) ease, + background var(--duration-fast) ease; +} + +.data-table-pagination__controls button:hover:not(:disabled) { + border-color: var(--border-strong); + background: var(--bg-hover); +} + +.data-table-pagination__controls button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.data-table-overlay { + position: fixed; + inset: 0; + z-index: 40; +} + /* =========================================== Log Stream =========================================== */ @@ -1434,6 +1678,7 @@ 100% { border-color: var(--border); } + 50% { border-color: var(--accent); } @@ -1490,6 +1735,7 @@ opacity: 0.4; transform: translateY(0); } + 40% { opacity: 1; transform: translateY(-3px); @@ -1678,9 +1924,9 @@ .shell--chat .chat-compose { position: sticky; bottom: 0; - z-index: 5; + /* z-index: 5; */ margin-top: 0; - padding-top: 12px; + padding-top: 6px; background: linear-gradient(180deg, transparent 0%, var(--bg) 40%); } @@ -1858,21 +2104,129 @@ .agents-layout { display: grid; - grid-template-columns: minmax(220px, 280px) minmax(0, 1fr); - gap: 16px; + grid-template-columns: 1fr; + gap: 10px; } -.agents-sidebar { +.agents-toolbar { display: grid; - gap: 12px; - align-self: start; - position: sticky; - top: 16px; + gap: 4px; +} + +.agents-toolbar-row { + display: grid; + gap: 3px; +} + +.agents-toolbar-label { + color: var(--muted); + font-size: 12px; + font-weight: 500; +} + +.agents-control-row { + display: grid; + grid-template-columns: 1fr 2fr; + gap: 4px; + align-items: stretch; +} + +.agents-control-select { + min-width: 0; +} + +.agents-control-select .agents-select { + flex: 1; + min-width: 0; + height: 36px; + font-size: 14px; + box-sizing: border-box; + border: 1px solid color-mix(in srgb, var(--input) 60%, transparent); + background: color-mix(in srgb, var(--card) 55%, transparent); + border-radius: var(--radius-md); + padding: 6px 36px 6px 12px; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23a1a1aa' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + background-size: 16px; + cursor: pointer; + outline: none; + box-shadow: inset 0 1px 0 var(--card-highlight); + transition: + border-color var(--duration-fast) ease, + box-shadow var(--duration-fast) ease, + background var(--duration-fast) ease; +} + +.agents-control-select .agents-select:focus-visible { + border-color: var(--ring); + box-shadow: var(--focus-ring); + background: color-mix(in srgb, var(--card) 75%, transparent); +} + +.agents-control-select .agents-select:disabled { + opacity: 0.6; + cursor: not-allowed; + background: color-mix(in srgb, var(--secondary) 80%, transparent); +} + +.agents-control-actions { + display: flex; + gap: 4px; + align-items: stretch; + min-width: 0; +} + +.agents-control-actions .agent-actions-toggle { + width: 36px; + height: 36px; + flex-shrink: 0; + padding: 0; + font-size: 18px; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + background: color-mix(in srgb, var(--secondary) 55%, transparent); + border-color: color-mix(in srgb, var(--border) 60%, transparent); +} + +.agents-control-actions .agent-actions-toggle:hover { + background: color-mix(in srgb, var(--secondary) 75%, transparent); +} + +.agents-control-actions .agents-refresh-btn { + flex: 1; + min-width: 0; + height: 36px; + font-size: 13px; + padding: 0 12px; + box-sizing: border-box; + display: inline-flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--bg-elevated) 55%, transparent); + border-color: color-mix(in srgb, var(--border) 60%, transparent); +} + +.agents-refresh-btn:hover:not(:disabled) { + background: color-mix(in srgb, var(--bg-hover) 75%, transparent); +} + +.agents-toolbar-field { + min-width: 160px; + max-width: 280px; + gap: 4px; +} + +.agents-select { + width: 100%; } .agents-main { display: grid; - gap: 16px; + gap: 10px; } .agent-list { @@ -1915,9 +2269,19 @@ } .agent-avatar--lg { - width: 48px; - height: 48px; - font-size: 20px; + width: 40px; + height: 40px; + font-size: 18px; +} + +.agent-avatar--logo { + background: transparent; +} + +.agent-avatar--logo .agent-avatar__img { + width: 100%; + height: 100%; + object-fit: contain; } .agent-info { @@ -1938,7 +2302,7 @@ .agent-pill { border: 1px solid var(--border); border-radius: var(--radius-full); - padding: 4px 10px; + padding: 3px 8px; font-size: 11px; color: var(--muted); background: var(--secondary); @@ -1954,23 +2318,28 @@ .agent-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; - gap: 16px; + gap: 12px; align-items: center; + padding: 10px 14px; } .agent-header-main { display: flex; - gap: 16px; + gap: 10px; align-items: center; } .agent-header-meta { display: grid; justify-items: end; - gap: 6px; + gap: 4px; color: var(--muted); } +.agent-header .card-sub { + margin-top: 2px; +} + .agent-tabs { display: flex; gap: 8px; @@ -1980,7 +2349,7 @@ .agent-tab { border: 1px solid var(--border); border-radius: var(--radius-full); - padding: 6px 14px; + padding: 5px 12px; font-size: 12px; font-weight: 600; background: var(--secondary); @@ -2005,8 +2374,8 @@ .agents-overview-grid { display: grid; - gap: 10px; - grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 14px; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } .agent-kv { @@ -2032,15 +2401,41 @@ .agent-model-select { display: grid; gap: 12px; - margin-top: 12px; +} + +.agent-model-fields { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 16px; + align-items: start; +} + +.agent-model-fields .field { + min-width: 0; + overflow: hidden; +} + +.agent-model-fields .field select { + width: 100%; + min-width: 0; + box-sizing: border-box; +} + +.agent-model-fields .agent-chip-input { + min-width: 0; + overflow: hidden; +} + +@media (max-width: 640px) { + .agent-model-fields { + grid-template-columns: 1fr; + } } .agent-model-actions { display: flex; + justify-content: flex-end; gap: 8px; - align-items: flex-end; - flex-shrink: 0; - padding-bottom: 2px; } .agent-model-meta { @@ -2278,9 +2673,9 @@ .agent-identity-card { display: flex; - gap: 12px; + gap: 16px; align-items: center; - padding: 12px; + padding: 16px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg-elevated); @@ -2444,6 +2839,17 @@ text-decoration-style: solid; } +/* =========================================== + Overview Section Dividers + =========================================== */ + +.ov-section-divider { + height: 1px; + background: var(--border); + margin: 22px 0; + opacity: 0.5; +} + /* =========================================== Overview Dashboard Cards =========================================== */ @@ -2455,91 +2861,77 @@ margin-top: 18px; } -.ov-stat-card { +.ov-card { --ov-accent: var(--muted); + all: unset; display: grid; - gap: 0; - padding: 0; - overflow: hidden; - border-top: 2px solid var(--ov-accent); - position: relative; -} - -.ov-stat-card.clickable { + gap: 4px; + padding: 16px; + border: 1px solid var(--border); + border-radius: var(--radius-lg); + border-left: 3px solid var(--ov-accent); + background: var(--card); cursor: pointer; + box-shadow: + var(--shadow-sm), + inset 0 1px 0 var(--card-highlight); transition: - border-color 0.15s ease, - transform 0.15s ease, - box-shadow 0.15s ease; + border-color var(--duration-normal) var(--ease-out), + box-shadow var(--duration-normal) var(--ease-out), + transform var(--duration-normal) var(--ease-out); + animation: rise 0.35s var(--ease-out) backwards; } -.ov-stat-card.clickable:hover { - border-color: var(--accent); +.ov-card:hover { + border-color: var(--border-strong); + border-left-color: var(--ov-accent); + box-shadow: + var(--shadow-md), + inset 0 1px 0 var(--card-highlight); transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25); } -.ov-stat-card[data-kind="cost"] { +.ov-card:focus-visible { + outline: none; + border-color: var(--ring); + border-left-color: var(--ov-accent); + box-shadow: var(--focus-ring); +} + +.ov-card[data-kind="cost"] { --ov-accent: var(--kn-bioluminescence); } -.ov-stat-card[data-kind="sessions"] { +.ov-card[data-kind="sessions"] { --ov-accent: var(--kn-silver); } -.ov-stat-card[data-kind="skills"] { +.ov-card[data-kind="skills"] { --ov-accent: var(--kn-claw-ember); } -.ov-stat-card[data-kind="cron"] { +.ov-card[data-kind="cron"] { --ov-accent: var(--vscode-accent); } -.ov-stat-card__inner { - display: flex; - gap: 12px; - align-items: flex-start; - padding: 14px 16px; -} - -.ov-stat-card__icon { - flex-shrink: 0; - width: 20px; - height: 20px; - color: var(--ov-accent); - opacity: 0.8; - margin-top: 1px; -} - -.ov-stat-card__icon svg { - width: 100%; - height: 100%; -} - -.ov-stat-card__body { - min-width: 0; - flex: 1; -} - -.ov-stat-card__body .stat-label { +.ov-card__label { font-size: 11px; + font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); - margin-bottom: 6px; - font-weight: 600; } -.ov-stat-card__body .stat-value { - font-size: 22px; +.ov-card__value { + font-size: 24px; font-weight: 700; - letter-spacing: -0.02em; - line-height: 1.1; + letter-spacing: -0.025em; + line-height: 1.15; } -.ov-stat-card__body .muted { +.ov-card__hint { font-size: 12px; - margin-top: 6px; + color: var(--muted); line-height: 1.4; } @@ -2552,35 +2944,52 @@ /* Recent sessions */ -.ov-recent-sessions { +.ov-recent { margin-top: 14px; + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background: var(--card); + padding: 14px 16px; + box-shadow: + var(--shadow-sm), + inset 0 1px 0 var(--card-highlight); + animation: rise 0.35s var(--ease-out) backwards; } -.ov-session-list { - margin-top: 10px; +.ov-recent__title { + font-size: 14px; + font-weight: 600; + letter-spacing: -0.02em; + color: var(--text-strong); + margin: 0 0 8px; } -.ov-session-row { - display: flex; - align-items: center; +.ov-recent__list { + list-style: none; + margin: 0; + padding: 0; +} + +.ov-recent__row { + display: grid; + grid-template-columns: minmax(0, 2fr) minmax(0, auto) auto; gap: 12px; - padding: 8px 0; - border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); + align-items: center; + padding: 7px 0; + border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent); font-size: 13px; - transition: opacity 0.1s ease; } -.ov-session-row:last-child { +.ov-recent__row:last-child { border-bottom: none; padding-bottom: 0; } -.ov-session-row:first-child { +.ov-recent__row:first-child { padding-top: 0; } -.ov-session-key { - flex: 1; +.ov-recent__key { min-width: 0; overflow: hidden; text-overflow: ellipsis; @@ -2588,16 +2997,31 @@ font-weight: 500; } -.ov-session-key .blur-digits { +.ov-recent__key .blur-digits { filter: blur(5px); transition: filter 200ms ease-out; user-select: none; } -.ov-session-row:hover .blur-digits { +.ov-recent__row:hover .blur-digits { filter: none; } +.ov-recent__model { + color: var(--muted); + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 120px; +} + +.ov-recent__time { + color: var(--muted); + font-size: 12px; + white-space: nowrap; +} + /* =========================================== Attention Center =========================================== */ @@ -2667,67 +3091,6 @@ text-decoration: underline; } -/* =========================================== - Debug Event Log - =========================================== */ - -.debug-event-log-scroll { - margin-top: 12px; - max-height: 480px; - overflow-y: auto; -} - -.debug-event-entry { - border-bottom: 1px solid var(--border); -} - -.debug-event-entry:last-child { - border-bottom: none; -} - -.debug-event-summary { - display: flex; - align-items: center; - gap: 10px; - padding: 8px 0; - cursor: pointer; - font-family: var(--mono); - font-size: 13px; - list-style: none; -} - -.debug-event-summary::-webkit-details-marker { - display: none; -} - -.debug-event-summary::before { - content: "▸"; - flex-shrink: 0; - width: 12px; - color: var(--muted); - transition: transform 0.15s ease; -} - -.debug-event-entry[open] > .debug-event-summary::before { - transform: rotate(90deg); -} - -.debug-event-name { - font-weight: 600; -} - -.debug-event-ts { - margin-left: auto; - flex-shrink: 0; - font-size: 12px; -} - -.debug-event-payload { - margin: 0 0 8px 22px; - max-height: 300px; - overflow-y: auto; -} - /* =========================================== Overview Event Log =========================================== */ @@ -2739,9 +3102,9 @@ .ov-expandable-toggle { display: flex; align-items: center; - gap: 8px; + gap: 6px; cursor: pointer; - font-size: 14px; + font-size: 13px; font-weight: 600; list-style: none; padding: 0; @@ -2840,16 +3203,16 @@ } .ov-log-tail-content { - margin-top: 10px; - max-height: 220px; + margin-top: 8px; + max-height: 180px; overflow: auto; font-family: var(--mono); - font-size: 11px; - line-height: 1.5; + font-size: 10px; + line-height: 1.45; white-space: pre-wrap; word-break: break-word; background: var(--bg-inset, var(--bg)); - padding: 10px; + padding: 8px; border-radius: var(--radius); border: 1px solid var(--border); } @@ -2908,10 +3271,8 @@ .ov-bottom-grid { display: grid; - grid-template-columns: 1fr; + grid-template-columns: 1fr 1fr; gap: 14px; - max-height: 420px; - overflow-y: auto; } @media (max-width: 768px) { @@ -2922,6 +3283,14 @@ .ov-cards { grid-template-columns: repeat(2, 1fr); } + + .ov-recent__row { + grid-template-columns: minmax(0, 1fr) auto; + } + + .ov-recent__model { + display: none; + } } @media (max-width: 480px) { diff --git a/ui/src/styles/config.css b/ui/src/styles/config.css index c68908a42..e5ef45bc5 100644 --- a/ui/src/styles/config.css +++ b/ui/src/styles/config.css @@ -8,7 +8,7 @@ grid-template-columns: 260px minmax(0, 1fr); gap: 0; height: calc(100vh - 160px); - margin: 0 -16px -16px; + margin: -16px; border-radius: var(--radius-xl); border: 1px solid var(--border); background: var(--panel); diff --git a/ui/src/styles/layout.css b/ui/src/styles/layout.css index 340e33850..4b1cd7631 100644 --- a/ui/src/styles/layout.css +++ b/ui/src/styles/layout.css @@ -3,10 +3,10 @@ =========================================== */ .shell { - --shell-pad: 16px; - --shell-gap: 16px; - --shell-nav-width: 240px; - --shell-topbar-height: 62px; + --shell-pad: 12px; + --shell-gap: 12px; + --shell-nav-width: 220px; + --shell-topbar-height: 52px; --shell-focus-duration: 200ms; --shell-focus-ease: var(--ease-out); height: 100vh; @@ -80,8 +80,8 @@ display: flex; justify-content: space-between; align-items: center; - gap: 12px; - padding: 0 20px; + gap: 10px; + padding: 0 16px; height: var(--shell-topbar-height); background: var(--topbar-bg); backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur)); @@ -102,7 +102,7 @@ display: flex; align-items: center; gap: 6px; - font-size: 0.82rem; + font-size: 0.8rem; min-width: 0; } @@ -142,17 +142,17 @@ .topbar-search { display: flex; align-items: center; - gap: 8px; - padding: 6px 12px; - min-width: 200px; - max-width: 340px; + gap: 6px; + padding: 5px 10px; + min-width: 160px; + max-width: 280px; flex: 1; - height: 34px; + height: 30px; border: 1px solid var(--border); border-radius: var(--radius-full); background: color-mix(in srgb, var(--secondary) 60%, transparent); color: var(--muted); - font-size: 13px; + font-size: 12px; font-family: var(--font-body); cursor: pointer; transition: @@ -220,10 +220,10 @@ .topbar-connection { display: inline-flex; align-items: center; - gap: 6px; - padding: 4px 10px; + gap: 5px; + padding: 3px 8px; border-radius: var(--radius-full); - font-size: 12px; + font-size: 11px; font-weight: 500; color: var(--danger); background: var(--danger-subtle); @@ -262,10 +262,9 @@ display: inline-flex; align-items: center; justify-content: center; - gap: 6px; - min-width: 28px; - height: 28px; - padding: 0 4px; + width: 26px; + height: 26px; + padding: 0; border: 1px solid transparent; border-radius: var(--radius); background: none; @@ -279,8 +278,8 @@ } .topbar-redact svg { - width: 14px; - height: 14px; + width: 12px; + height: 12px; } .topbar-redact:hover { @@ -290,45 +289,40 @@ } .topbar-redact--active { - border-radius: var(--radius-full); - padding: 4px 10px; color: var(--warn); - background: var(--warn-subtle); } .topbar-redact--active:hover { color: var(--warn); - background: color-mix(in srgb, var(--warn-subtle) 80%, var(--warn) 10%); + background: var(--warn-subtle); + border-color: color-mix(in srgb, var(--warn) 30%, transparent); } -.topbar-redact__label { - font-size: 12px; - font-weight: 500; - letter-spacing: 0.04em; - text-transform: uppercase; - line-height: 1; - white-space: nowrap; -} +/* Topbar theme select sizing */ -/* Topbar theme toggle sizing */ - -.topbar-status .theme-toggle { - height: 30px; -} - -.topbar-status .theme-btn svg { - width: 13px; - height: 13px; +.topbar-status .theme-select { + height: 26px; + min-width: 82px; + font-size: 11px; } /* =========================================== Navigation Sidebar =========================================== */ -.sidebar { +.shell-nav { grid-area: nav; display: flex; flex-direction: column; + min-height: 0; + position: relative; +} + +.sidebar { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; overflow-y: auto; overflow-x: hidden; scrollbar-width: none; @@ -347,13 +341,9 @@ display: none; } -.shell--chat-focus .sidebar { - width: 0; - padding: 0; - border-width: 0; - overflow: hidden; - pointer-events: none; - opacity: 0; +.shell--chat-focus .sidebar, +.shell--chat-focus .sidebar-resizer { + display: none; } .sidebar--collapsed { @@ -395,6 +385,21 @@ border-left: 0; } +.sidebar--collapsed .nav-item--active::before { + background: + radial-gradient( + ellipse 120% 28px at 50% -2px, + color-mix(in srgb, var(--accent) 38%, transparent) 0%, + color-mix(in srgb, var(--accent) 14%, transparent) 40%, + transparent 100% + ), + radial-gradient( + ellipse 60% 100% at -4px 50%, + color-mix(in srgb, var(--accent) 28%, transparent) 0%, + transparent 70% + ); +} + .sidebar--collapsed .sidebar-footer { display: flex; flex-direction: column; @@ -409,24 +414,54 @@ height: 44px; } +/* Sidebar resizer handle */ +.sidebar-resizer { + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 6px; + cursor: col-resize; + z-index: 10; + flex-shrink: 0; + /* Hit area extends beyond visible handle for easier grabbing */ + margin-right: -3px; +} + +.sidebar-resizer::before { + content: ""; + position: absolute; + left: 2px; + top: 20%; + bottom: 20%; + width: 2px; + border-radius: 1px; + background: var(--glass-border); + transition: background 0.15s ease; +} + +.sidebar-resizer:hover::before, +.sidebar-resizer:active::before { + background: var(--glass-border-hover); +} + /* Sidebar header (brand + collapse) */ .sidebar-header { display: flex; flex-direction: row; align-items: center; padding: 10px 8px; - gap: 0; + gap: 8px; flex-shrink: 0; min-height: 54px; } .sidebar-brand { - flex: 2; + flex: 1; + min-width: 0; display: flex; align-items: center; gap: 10px; - min-width: 0; - max-height: 28px; padding-left: 10px; @@ -452,13 +487,19 @@ line-height: 1.1; color: var(--text-strong); white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; } .sidebar-collapse-btn { - flex: 1; + flex: 0 0 32px; + width: 32px; height: 32px; @media (max-width: 1100px) { + flex: 0 0 28px; + width: 28px; height: 28px; } @@ -595,6 +636,7 @@ border-radius: var(--radius-md); border: 1px solid transparent; background: transparent; + overflow: hidden; color: var(--muted); cursor: pointer; text-decoration: none; @@ -667,6 +709,33 @@ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 20%, transparent); } +.nav-item--active::before { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + background: radial-gradient( + ellipse 28px 120% at -2px 50%, + color-mix(in srgb, var(--accent) 38%, transparent) 0%, + color-mix(in srgb, var(--accent) 14%, transparent) 40%, + transparent 100% + ); + opacity: 0; + animation: nav-glow-in 0.4s ease-out 0.05s forwards; + pointer-events: none; +} + +@keyframes nav-glow-in { + from { + opacity: 0; + transform: translateX(-6px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + .nav-item--active .nav-item__icon { opacity: 1; color: var(--accent); @@ -680,11 +749,17 @@ margin-top: auto; } +.sidebar-footer__docs-block { + display: flex; + flex-direction: column; + gap: 4px; +} + .sidebar-version { display: flex; align-items: center; - justify-content: center; - padding: 6px 10px; + justify-content: flex-start; + padding: 2px 12px 6px; } .sidebar-version__text { @@ -708,7 +783,7 @@ .content { grid-area: content; - padding: 14px 18px 36px; + padding: 12px 14px 24px; display: block; min-height: 0; overflow-y: auto; @@ -716,13 +791,13 @@ } .content > * + * { - margin-top: 24px; + margin-top: 18px; } .content--chat { display: flex; flex-direction: column; - gap: 16px; + gap: 2px; overflow: hidden; padding-bottom: 0; } @@ -734,10 +809,12 @@ /* Content header */ .content-header { display: flex; - align-items: flex-end; + align-items: center; justify-content: space-between; - gap: 12px; - padding: 2px 0; + gap: 10px; + height: 36px; + min-height: 36px; + padding: 0; overflow: hidden; transform-origin: top center; transition: @@ -745,30 +822,30 @@ transform var(--shell-focus-duration) var(--shell-focus-ease), max-height var(--shell-focus-duration) var(--shell-focus-ease), padding var(--shell-focus-duration) var(--shell-focus-ease); - max-height: 64px; + max-height: 36px; } .shell--chat-focus .content-header { opacity: 0; transform: translateY(-8px); - max-height: 0px; + max-height: 0; padding: 0; pointer-events: none; } .page-title { - font-size: 22px; + font-size: 18px; font-weight: 600; letter-spacing: -0.03em; - line-height: 1.2; + line-height: 1.25; color: var(--text-strong); } .page-sub { color: var(--muted); - font-size: 13px; + font-size: 12px; font-weight: 400; - margin-top: 2px; + margin-top: 1px; letter-spacing: -0.01em; } @@ -783,10 +860,13 @@ flex-direction: row; align-items: center; justify-content: space-between; - gap: 12px; + gap: 10px; } .content--chat .content-header > div:first-child { + display: flex; + flex-direction: column; + justify-content: center; text-align: left; } @@ -796,6 +876,66 @@ .content--chat .chat-controls { flex-shrink: 0; + align-items: center; + align-content: center; +} + +/* Chat controls in header — uniform 32px height across all controls */ +.content-header .btn--icon { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 32px; + height: 32px; + padding: 0 !important; +} + +.content-header .btn--icon svg { + display: block; + width: 16px; + height: 16px; + flex-shrink: 0; +} + +.content-header .chat-controls__session { + display: flex; + align-items: center; + gap: 0; + min-width: 0; +} + +.content-header .chat-controls__session select { + height: 32px; + line-height: 1; + font-size: 13px; + font-weight: 600; + letter-spacing: -0.02em; + padding: 0 28px 0 10px; + background-position: right 8px center; + border-radius: var(--radius-md); + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; +} + +.content-header .chat-controls__separator { + width: 1px; + height: 32px; + background: var(--border); + font-size: 0; + color: transparent; + overflow: hidden; + align-self: center; + flex-shrink: 0; + margin: 0 4px; +} + +.content-header .chat-controls__thinking { + height: 32px; + min-height: 32px; + align-items: center; + padding: 0 10px; + font-size: 12px; } /* =========================================== @@ -804,7 +944,7 @@ .grid { display: grid; - gap: 20px; + gap: 14px; } .grid-cols-2 { @@ -817,32 +957,32 @@ .stat-grid { display: grid; - gap: 14px; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 10px; + grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); } .note-grid { display: grid; - gap: 16px; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 12px; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } .row { display: flex; - gap: 12px; + gap: 10px; align-items: center; } .stack { display: grid; - gap: 12px; + gap: 10px; grid-template-columns: minmax(0, 1fr); } .filters { display: flex; flex-wrap: wrap; - gap: 8px; + gap: 12px; align-items: center; } @@ -852,58 +992,14 @@ @media (max-width: 1100px) { .shell { - --shell-pad: 12px; - --shell-gap: 12px; - grid-template-columns: 1fr; - grid-template-rows: auto auto 1fr; - grid-template-areas: - "topbar" - "nav" - "content"; - } - - .sidebar { - position: static; - max-height: none; - display: flex; - flex-direction: row; - gap: 6px; - overflow-x: auto; - border-right: none; - border-bottom: 1px solid var(--border); - } - - .sidebar-header { - display: none; - } - - .sidebar-footer { - display: none; - } - - .sidebar-nav { - display: flex; - flex-direction: row; - gap: 6px; - padding: 10px 14px; - overflow-x: auto; - } - - .nav-group { - grid-auto-flow: column; - grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); - margin-bottom: 0; - } - - .grid-cols-2, - .grid-cols-3 { - grid-template-columns: 1fr; + --shell-pad: 10px; + --shell-gap: 10px; + --shell-nav-width: 200px; } .topbar { - position: static; - padding: 12px 14px; - gap: 10px; + padding: 10px 12px; + gap: 8px; } .topbar-search__kbd { @@ -914,6 +1010,30 @@ flex-wrap: nowrap; } + .content-header { + height: 36px; + min-height: 36px; + max-height: 36px; + padding: 0; + } + + .page-sub { + display: none; + } + + .content { + padding: 10px 12px 20px; + } + + .content > * + * { + margin-top: 14px; + } + + .grid-cols-2, + .grid-cols-3 { + grid-template-columns: 1fr; + } + .table-head, .table-row { grid-template-columns: 1fr; diff --git a/ui/src/styles/layout.mobile.css b/ui/src/styles/layout.mobile.css index 19c5c6474..74815420a 100644 --- a/ui/src/styles/layout.mobile.css +++ b/ui/src/styles/layout.mobile.css @@ -2,60 +2,10 @@ Mobile Layout =========================================== */ -/* Tablet: Horizontal nav */ +/* Tablet: keep side nav vertical, narrow sidebar */ @media (max-width: 1100px) { - .sidebar { - flex-direction: row; - flex-wrap: nowrap; - border-right: none; - border-bottom: 1px solid var(--border); - } - - .sidebar-header { - display: none; - } - - .sidebar-footer { - display: none; - } - - .sidebar-nav { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - gap: 4px; - padding: 10px 14px; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - - .sidebar-nav::-webkit-scrollbar { - display: none; - } - - .nav-group { - display: contents; - } - - .nav-group__items { - display: contents; - } - - .nav-group__label { - display: none; - } - - .nav-group--collapsed .nav-group__items { - display: contents; - } - - .nav-item { - padding: 8px 14px; - font-size: 13px; - border-radius: var(--radius-md); - white-space: nowrap; - flex-shrink: 0; + .shell { + --shell-nav-width: 200px; } } @@ -64,6 +14,7 @@ .shell { --shell-pad: 8px; --shell-gap: 8px; + --shell-nav-width: 180px; } /* Topbar */ @@ -142,8 +93,12 @@ /* Content — compact header on chat, hide on other tabs */ .content-header { - padding: 0; - max-height: 48px; + height: 64px; + min-height: 64px; + padding: 12px 0; + /* This controls the height of the content header on mobile */ + max-height: 64px; + margin-top: 24px; } .content:not(.content--chat) .content-header { @@ -151,7 +106,7 @@ } .content--chat .page-title { - font-size: 18px; + font-size: 16px; } .content--chat .page-sub { @@ -159,8 +114,8 @@ } .content { - padding: 4px 4px 16px; - gap: 12px; + padding: 4px 6px 12px; + gap: 10px; } /* Cards */ @@ -226,22 +181,7 @@ /* Chat */ .chat-agent-bar { - padding: 4px 8px; - gap: 4px; - } - - .chat-agent-bar__name { - font-size: 11px; - } - - .chat-agent-select { - font-size: 11px; - padding: 2px 16px 2px 4px; - } - - .chat-sessions-summary { - padding: 2px 4px; - font-size: 10px; + padding: 2px 6px; } .chat-header { @@ -366,18 +306,10 @@ font-size: 11px; } - .theme-toggle { - height: 28px; - } - - .theme-btn svg { - width: 12px; - height: 12px; - } - - .theme-icon { - width: 12px; - height: 12px; + .theme-select { + height: 26px; + min-width: 78px; + font-size: 11px; } } @@ -440,12 +372,9 @@ padding: 3px 6px; } - .theme-toggle { - height: 26px; - } - - .theme-icon { - width: 11px; - height: 11px; + .theme-select { + height: 24px; + min-width: 72px; + font-size: 10px; } } diff --git a/ui/src/ui/app-gateway.ts b/ui/src/ui/app-gateway.ts index 340922baf..4aacd29c5 100644 --- a/ui/src/ui/app-gateway.ts +++ b/ui/src/ui/app-gateway.ts @@ -170,11 +170,6 @@ export function connectGateway(host: GatewayHost) { return; } host.connected = false; - // Code 1008 = Policy Violation (auth failure) — show the gateway's reason directly - if (code === 1008) { - host.lastError = reason || "Authentication failed. Check your gateway token."; - return; - } // Code 1012 = Service Restart (expected during config saves, don't show as error) if (code !== 1012) { host.lastError = `disconnected (${code}): ${reason || "no reason"}`; diff --git a/ui/src/ui/app-render.helpers.ts b/ui/src/ui/app-render.helpers.ts index 316c7968e..890611483 100644 --- a/ui/src/ui/app-render.helpers.ts +++ b/ui/src/ui/app-render.helpers.ts @@ -84,18 +84,59 @@ export function renderTab(state: AppViewState, tab: Tab) { `; } -export function renderChatControls(state: AppViewState) { +export function renderChatSessionSelect(state: AppViewState) { const mainSessionKey = resolveMainSessionKey(state.hello, state.sessionsResult); const sessionOptions = resolveSessionOptions( state.sessionKey, state.sessionsResult, mainSessionKey, ); + return html` + + `; +} + +export function renderChatControls(state: AppViewState) { const disableThinkingToggle = state.onboarding; const disableFocusToggle = state.onboarding; const showThinking = state.onboarding ? false : state.settings.chatShowThinking; const focusActive = state.onboarding ? true : state.settings.chatFocusMode; - // Refresh icon const refreshIcon = html` -
{ - const toggle = e.currentTarget as HTMLElement; - requestAnimationFrame(() => { - if (!toggle.contains(document.activeElement)) { - handleCollapse(); - } - }); + `; } diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index 0c61a7c39..78a75719b 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -6,7 +6,12 @@ import { import { t } from "../i18n/index.ts"; import { refreshChatAvatar } from "./app-chat.ts"; import { renderUsageTab } from "./app-render-usage-tab.ts"; -import { renderChatControls, renderTab, renderThemeToggle } from "./app-render.helpers.ts"; +import { + renderChatControls, + renderChatSessionSelect, + renderTab, + renderThemeToggle, +} from "./app-render.helpers.ts"; import type { AppViewState } from "./app-view-state.ts"; import { loadAgentFileContent, loadAgentFiles, saveAgentFile } from "./controllers/agent-files.ts"; import { loadAgentIdentities, loadAgentIdentity } from "./controllers/agent-identity.ts"; @@ -59,7 +64,6 @@ import "./components/dashboard-header.ts"; import { icons } from "./icons.ts"; import { normalizeBasePath, TAB_GROUPS, subtitleForTab, titleForTab } from "./navigation.ts"; import { renderAgents } from "./views/agents.ts"; -import { renderBottomTabs } from "./views/bottom-tabs.ts"; import { renderChannels } from "./views/channels.ts"; import { renderChat } from "./views/chat.ts"; import { renderCommandPalette } from "./views/command-palette.ts"; @@ -79,6 +83,33 @@ import { renderSkills } from "./views/skills.ts"; const AVATAR_DATA_RE = /^data:/i; const AVATAR_HTTP_RE = /^https?:\/\//i; +const NAV_WIDTH_MIN = 180; +const NAV_WIDTH_MAX = 400; + +function handleNavResizeStart(e: MouseEvent, state: AppViewState) { + e.preventDefault(); + const startX = e.clientX; + const startWidth = state.settings.navWidth; + + const onMove = (ev: MouseEvent) => { + const delta = ev.clientX - startX; + const next = Math.round(Math.min(NAV_WIDTH_MAX, Math.max(NAV_WIDTH_MIN, startWidth + delta))); + state.applySettings({ ...state.settings, navWidth: next }); + }; + + const onUp = () => { + document.removeEventListener("mousemove", onMove); + document.removeEventListener("mouseup", onUp); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + }; + + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + document.addEventListener("mousemove", onMove); + document.addEventListener("mouseup", onUp); +} + function resolveAssistantAvatarUrl(state: AppViewState): string | undefined { const list = state.agentsList?.agents ?? []; const parsed = parseAgentSessionKey(state.sessionKey); @@ -140,11 +171,15 @@ export function renderApp(state: AppViewState) { onNavigate: (tab) => { state.setTab(tab as import("./navigation.ts").Tab); }, - onSlashCommand: (_cmd) => { + onSlashCommand: (cmd) => { state.setTab("chat" as import("./navigation.ts").Tab); + state.chatMessage = cmd.endsWith(" ") ? cmd : `${cmd} `; }, })} -
+
- -
${state.connected ? t("common.ok") : t("common.offline")} @@ -191,6 +202,7 @@ export function renderApp(state: AppViewState) { ${renderThemeToggle(state)}
+
+ ${ + !state.settings.navCollapsed && !chatFocus + ? html` + + ` + : nothing + } +
${ state.updateAvailable @@ -308,8 +339,14 @@ export function renderApp(state: AppViewState) { }
- ${state.tab === "usage" ? nothing : html`
${titleForTab(state.tab)}
`} - ${state.tab === "usage" ? nothing : html`
${subtitleForTab(state.tab)}
`} + ${ + isChat + ? renderChatSessionSelect(state) + : state.tab === "skills" + ? nothing + : html`
${titleForTab(state.tab)}
` + } + ${isChat || state.tab === "skills" ? nothing : html`
${subtitleForTab(state.tab)}
`}
${state.lastError ? html`
${state.lastError}
` : nothing} @@ -431,12 +468,37 @@ export function renderApp(state: AppViewState) { includeGlobal: state.sessionsIncludeGlobal, includeUnknown: state.sessionsIncludeUnknown, basePath: state.basePath, + searchQuery: state.sessionsSearchQuery, + sortColumn: state.sessionsSortColumn, + sortDir: state.sessionsSortDir, + page: state.sessionsPage, + pageSize: state.sessionsPageSize, + actionsOpenKey: state.sessionsActionsOpenKey, onFiltersChange: (next) => { state.sessionsFilterActive = next.activeMinutes; state.sessionsFilterLimit = next.limit; state.sessionsIncludeGlobal = next.includeGlobal; state.sessionsIncludeUnknown = next.includeUnknown; }, + onSearchChange: (q) => { + state.sessionsSearchQuery = q; + state.sessionsPage = 0; + }, + onSortChange: (col, dir) => { + state.sessionsSortColumn = col; + state.sessionsSortDir = dir; + state.sessionsPage = 0; + }, + onPageChange: (p) => { + state.sessionsPage = p; + }, + onPageSizeChange: (s) => { + state.sessionsPageSize = s; + state.sessionsPage = 0; + }, + onActionsOpenChange: (key) => { + state.sessionsActionsOpenKey = key; + }, onRefresh: () => loadSessions(state), onPatch: (key, patch) => patchSession(state, key, patch), onDelete: (key) => deleteSessionAndRefresh(state, key), @@ -478,7 +540,7 @@ export function renderApp(state: AppViewState) { ${ state.tab === "agents" ? renderAgents({ - basePath: state.basePath, + basePath: state.basePath ?? "", loading: state.agentsLoading, error: state.agentsError, agentsList: state.agentsList, @@ -521,10 +583,6 @@ export function renderApp(state: AppViewState) { agentId: state.agentSkillsAgentId, filter: state.skillsFilter, }, - sidebarFilter: state.agentsSidebarFilter, - onSidebarFilterChange: (value) => { - state.agentsSidebarFilter = value; - }, onRefresh: async () => { await loadAgents(state); const agentIds = state.agentsList?.agents?.map((entry) => entry.id) ?? []; @@ -1060,6 +1118,7 @@ export function renderApp(state: AppViewState) { onSplitRatioChange: (ratio: number) => state.handleSplitRatioChange(ratio), assistantName: state.assistantName, assistantAvatar: state.assistantAvatar, + basePath: state.basePath ?? "", }) : nothing } @@ -1151,10 +1210,7 @@ export function renderApp(state: AppViewState) {
${renderExecApprovalPrompt(state)} ${renderGatewayUrlConfirmation(state)} - ${renderBottomTabs({ - activeTab: state.tab, - onTabChange: (tab) => state.setTab(tab), - })} + ${nothing}
`; } diff --git a/ui/src/ui/app-settings.test.ts b/ui/src/ui/app-settings.test.ts index e1b057913..051eac27d 100644 --- a/ui/src/ui/app-settings.test.ts +++ b/ui/src/ui/app-settings.test.ts @@ -19,6 +19,7 @@ const createHost = (tab: Tab): SettingsHost => ({ splitRatio: 0.6, navCollapsed: false, navGroupsCollapsed: {}, + navWidth: 220, }, theme: "dark", themeResolved: "dark", diff --git a/ui/src/ui/app-settings.ts b/ui/src/ui/app-settings.ts index 1d50cd985..1e7a8a6ad 100644 --- a/ui/src/ui/app-settings.ts +++ b/ui/src/ui/app-settings.ts @@ -269,7 +269,7 @@ export function applyResolvedTheme(host: SettingsHost, resolved: ResolvedTheme) } const root = document.documentElement; root.dataset.theme = resolved; - root.style.colorScheme = "dark"; + root.style.colorScheme = resolved === "light" ? "light" : "dark"; } export function syncTabWithLocation(host: SettingsHost, replace: boolean) { diff --git a/ui/src/ui/app-view-state.ts b/ui/src/ui/app-view-state.ts index 5ee23477b..34c404a4e 100644 --- a/ui/src/ui/app-view-state.ts +++ b/ui/src/ui/app-view-state.ts @@ -146,7 +146,6 @@ export type AppViewState = { agentSkillsError: string | null; agentSkillsReport: SkillStatusReport | null; agentSkillsAgentId: string | null; - agentsSidebarFilter: string; sessionsLoading: boolean; sessionsResult: SessionsListResult | null; sessionsError: string | null; @@ -154,6 +153,12 @@ export type AppViewState = { sessionsFilterLimit: string; sessionsIncludeGlobal: boolean; sessionsIncludeUnknown: boolean; + sessionsSearchQuery: string; + sessionsSortColumn: "key" | "kind" | "updated" | "tokens"; + sessionsSortDir: "asc" | "desc"; + sessionsPage: number; + sessionsPageSize: number; + sessionsActionsOpenKey: string | null; usageLoading: boolean; usageResult: SessionsUsageResult | null; usageCostSummary: CostUsageSummary | null; diff --git a/ui/src/ui/app.ts b/ui/src/ui/app.ts index 1c284079c..275ff9794 100644 --- a/ui/src/ui/app.ts +++ b/ui/src/ui/app.ts @@ -231,7 +231,6 @@ export class OpenClawApp extends LitElement { @state() agentSkillsError: string | null = null; @state() agentSkillsReport: SkillStatusReport | null = null; @state() agentSkillsAgentId: string | null = null; - @state() agentsSidebarFilter = ""; @state() sessionsLoading = false; @state() sessionsResult: SessionsListResult | null = null; @@ -240,6 +239,12 @@ export class OpenClawApp extends LitElement { @state() sessionsFilterLimit = "120"; @state() sessionsIncludeGlobal = true; @state() sessionsIncludeUnknown = false; + @state() sessionsSearchQuery = ""; + @state() sessionsSortColumn: "key" | "kind" | "updated" | "tokens" = "updated"; + @state() sessionsSortDir: "asc" | "desc" = "desc"; + @state() sessionsPage = 0; + @state() sessionsPageSize = 10; + @state() sessionsActionsOpenKey: string | null = null; @state() usageLoading = false; @state() usageResult: import("./types.js").SessionsUsageResult | null = null; @@ -464,12 +469,6 @@ export class OpenClawApp extends LitElement { return [active, ...rest]; } - handleThemeToggleCollapse() { - setTimeout(() => { - this.themeOrder = this.buildThemeOrder(this.theme); - }, 80); - } - async loadOverview() { await loadOverviewInternal(this as unknown as Parameters[0]); } diff --git a/ui/src/ui/chat/grouped-render.ts b/ui/src/ui/chat/grouped-render.ts index 0eb3f2251..160e36a5e 100644 --- a/ui/src/ui/chat/grouped-render.ts +++ b/ui/src/ui/chat/grouped-render.ts @@ -5,6 +5,7 @@ import { icons } from "../icons.ts"; import { toSanitizedMarkdownHtml } from "../markdown.ts"; import { detectTextDirection } from "../text-direction.ts"; import type { MessageGroup, ToolCard } from "../types/chat-types.ts"; +import { agentLogoUrl } from "../views/agents-utils.ts"; import { renderCopyAsMarkdownButton } from "./copy-as-markdown.ts"; import { extractTextCached, @@ -56,10 +57,10 @@ function extractImages(message: unknown): ImageBlock[] { return images; } -export function renderReadingIndicatorGroup(assistant?: AssistantIdentity) { +export function renderReadingIndicatorGroup(assistant?: AssistantIdentity, basePath?: string) { return html`
- ${renderAvatar("assistant", assistant)} + ${renderAvatar("assistant", assistant, basePath)}