Loading...
diff --git a/cmd/web-ui/static/style.css b/cmd/web-ui/static/style.css index 13b82d9..3a1d256 100644 --- a/cmd/web-ui/static/style.css +++ b/cmd/web-ui/static/style.css @@ -832,6 +832,19 @@ tr.expandable:hover .expand-icon::before { border-color: rgba(34, 211, 238, 0.15); } +.timeline-event-link { + cursor: pointer; +} + +.timeline-event-link:hover { + border-color: rgba(34, 211, 238, 0.3); +} + +.timeline-event-link:focus-visible { + outline: 2px solid rgba(34, 211, 238, 0.45); + outline-offset: 2px; +} + .timeline-event-header { display: flex; align-items: center; @@ -1192,6 +1205,8 @@ tr.expandable:hover .expand-icon::before { align-items: center; justify-content: space-between; margin-bottom: 1rem; + gap: 1rem; + flex-wrap: wrap; } .chart-title { @@ -1202,6 +1217,27 @@ tr.expandable:hover .expand-icon::before { letter-spacing: 0.01em; } +.chart-title-group { + display: flex; + flex-direction: column; + gap: 0.2rem; +} + +.chart-subtitle { + font-family: var(--font-mono); + font-size: 0.7rem; + color: var(--text-dim); + letter-spacing: 0.02em; +} + +.chart-header-controls { + display: flex; + align-items: center; + gap: 0.85rem; + flex-wrap: wrap; + justify-content: flex-end; +} + .window-selector { display: flex; gap: 0; @@ -1238,11 +1274,166 @@ tr.expandable:hover .expand-icon::before { background: var(--accent-dim); } +.mode-selector { + display: flex; + gap: 0; + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} + +.mode-btn { + background: transparent; + border: none; + color: var(--text-dim); + font-family: var(--font-mono); + font-size: 0.68rem; + font-weight: 700; + padding: 0.3rem 0.7rem; + cursor: pointer; + letter-spacing: 0.05em; + text-transform: uppercase; + border-right: 1px solid var(--border); + transition: background 0.15s, color 0.15s; +} + +.mode-btn:last-child { + border-right: none; +} + +.mode-btn:hover { + color: var(--text-bright); + background: var(--surface-2); +} + +.mode-btn.active { + color: var(--success); + background: rgba(52, 211, 153, 0.12); +} + +.chart-insights { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.75rem; + margin-bottom: 0.9rem; +} + +@media (max-width: 900px) { + .chart-insights { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 560px) { + .chart-insights { + grid-template-columns: 1fr; + } +} + +.chart-insight-pill { + min-height: 62px; + border: 1px solid var(--border-soft); + border-radius: var(--radius); + background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0)); + padding: 0.75rem 0.85rem; + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.chart-insight-label, +.chart-insight-meta { + font-family: var(--font-mono); + font-size: 0.68rem; + color: var(--text-dim); + letter-spacing: 0.03em; + text-transform: uppercase; +} + +.chart-insight-pill strong { + font-size: 0.95rem; + color: var(--text-bright); +} + .chart-container { width: 100%; min-height: 200px; } +.chart-hover-panel { + margin-top: 0.9rem; + border: 1px solid var(--border-soft); + border-radius: var(--radius); + background: var(--surface-2); + padding: 0.9rem 1rem; +} + +.chart-hover-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + margin-bottom: 0.8rem; +} + +.chart-hover-label, +.chart-hover-total span, +.chart-hover-metric span { + font-family: var(--font-mono); + font-size: 0.68rem; + color: var(--text-dim); + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.chart-hover-time { + color: var(--text-bright); + font-size: 0.85rem; + margin-top: 0.25rem; +} + +.chart-hover-total { + text-align: right; +} + +.chart-hover-total strong { + display: block; + color: var(--text-bright); + font-size: 1.15rem; + margin-top: 0.15rem; +} + +.chart-hover-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.75rem; +} + +@media (max-width: 720px) { + .chart-hover-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +.chart-hover-metric { + border-radius: var(--radius); + padding: 0.7rem 0.8rem; + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.04); +} + +.chart-hover-metric strong { + display: block; + margin-top: 0.2rem; + color: var(--text-bright); + font-size: 1rem; +} + +.chart-hover-metric.runs strong { color: #34d399; } +.chart-hover-metric.tools strong { color: #22d3ee; } +.chart-hover-metric.errors strong { color: #f87171; } +.chart-hover-metric.delta strong { color: var(--accent); } + .fw-bars { display: flex; flex-direction: column; @@ -1341,6 +1532,22 @@ tr.expandable:hover .expand-icon::before { padding: 1.25rem; } +.usage-rank-group + .usage-rank-group { + margin-top: 1.15rem; + padding-top: 1rem; + border-top: 1px solid var(--border-soft); +} + +.usage-rank-header { + font-family: var(--font-mono); + font-size: 0.72rem; + font-weight: 700; + color: var(--text-dim); + letter-spacing: 0.06em; + text-transform: uppercase; + margin-bottom: 0.45rem; +} + .uplot .u-legend { display: none; } /* ── Chart legend ─────────────────────────────────────────── */ @@ -1366,6 +1573,15 @@ tr.expandable:hover .expand-icon::before { flex-shrink: 0; } +.chart-legend-dot.total { + background: #f8fafc; + box-shadow: 0 0 0 1px rgba(248, 250, 252, 0.2); +} + +.stat-list-bar-fill.model { + background: var(--success); +} + /* ── Framework dots ───────────────────────────────────────── */ .fw-dot { display: inline-block; @@ -2216,3 +2432,757 @@ tr.expandable:hover .expand-icon::before { text-transform: uppercase; letter-spacing: 0.08em; } + +/* ── Header right cluster ─────────────────────────────────── */ +.header-right { + display: flex; + align-items: center; + gap: 0; + flex-shrink: 0; +} + +/* ── WebSocket status dot ─────────────────────────────────── */ +.ws-dot { + display: inline-block; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--text-dim); + opacity: 0.3; + flex-shrink: 0; + margin-right: 0.6rem; + cursor: default; + transition: background 0.4s, opacity 0.4s, box-shadow 0.4s; +} + +.ws-dot.connected { + background: var(--success); + opacity: 1; + box-shadow: 0 0 5px rgba(52, 211, 153, 0.5); + animation: livePulse 2.5s ease-in-out infinite; +} + +.ws-dot.reconnecting { + background: var(--warning); + opacity: 1; + animation: livePulse 0.9s ease-in-out infinite; +} + +/* ── Session date group header row ────────────────────────── */ +tr.session-date-group > td { + padding: 0.85rem 1.25rem 0.35rem; + font-size: 0.62rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--text-dim); + background: var(--surface-2); + border-bottom: 1px solid var(--border-soft); + pointer-events: none; + user-select: none; +} + +tr.session-date-group:first-child > td { + padding-top: 0.5rem; +} + +/* ── Span kind badge ──────────────────────────────────────── */ +.span-kind-badge { + display: inline-flex; + align-items: center; + padding: 0.08rem 0.4rem; + border-radius: 3px; + font-size: 0.6rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-right: 0.4rem; + vertical-align: middle; + font-family: var(--font-mono); +} + +.span-kind-badge.tool { + background: rgba(34, 211, 238, 0.1); + color: var(--accent); + border: 1px solid rgba(34, 211, 238, 0.18); +} + +.span-kind-badge.agent { + background: rgba(167, 139, 250, 0.1); + color: var(--purple); + border: 1px solid rgba(167, 139, 250, 0.18); +} + +.span-kind-badge.unknown, +.span-kind-badge.other { + background: var(--surface-2); + color: var(--text-dim); + border: 1px solid var(--border); +} + +/* ── Structured span detail panel ─────────────────────────── */ +.span-details-structured { + padding: 1rem 1.25rem; + border-top: 1px solid var(--border); + background: var(--bg); + display: flex; + flex-direction: column; + gap: 0.6rem; +} + +.span-kv { + display: grid; + grid-template-columns: 72px minmax(0, 1fr); + gap: 0.8rem; + align-items: start; + font-size: 0.8rem; +} + +.span-kv.ids { + margin-top: 0.5rem; + padding-top: 0.5rem; + border-top: 1px solid var(--border-soft); +} + +.span-kv-key { + font-family: var(--font-mono); + font-size: 0.65rem; + color: var(--text-dim); + text-transform: uppercase; + letter-spacing: 0.07em; + padding-top: 0.15rem; +} + +.span-kv-val { + color: var(--text); + word-break: break-word; + line-height: 1.55; + margin: 0; +} + +.span-kv-val.span-kv-raw, +.span-kv-raw { + font-family: var(--font-mono); + font-size: 0.73rem; + color: var(--code-text); + white-space: pre-wrap; + word-break: break-all; + line-height: 1.65; + margin: 0; +} + +/* ── Metrics strip ────────────────────────────────────────── */ +.metrics-strip { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-bottom: 1.25rem; +} + +.metric-pill { + display: flex; + flex-direction: column; + gap: 0.15rem; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; + padding: 0.5rem 0.85rem; + min-width: 130px; + flex: 1; +} + +.metric-pill-label { + font-size: 0.68rem; + text-transform: uppercase; + letter-spacing: 0.07em; + color: var(--text-dim); +} + +.metric-pill-value { + font-size: 1.05rem; + font-weight: 600; + font-family: 'Fira Code', monospace; + color: var(--text); +} + +.metric-pill-alert.alert { + color: var(--error); +} + +/* ── Right panel tabs ─────────────────────────────────────── */ +.right-panel-tabs { + display: flex; + gap: 0.25rem; +} + +.right-panel-tab { + background: none; + border: 1px solid transparent; + border-radius: 6px; + color: var(--text-dim); + cursor: pointer; + font-size: 0.78rem; + font-family: inherit; + padding: 0.25rem 0.6rem; + transition: color 0.15s, border-color 0.15s, background 0.15s; +} + +.right-panel-tab:hover { + color: var(--text); + border-color: var(--border); +} + +.right-panel-tab.active { + color: var(--accent); + border-color: var(--accent); + background: rgba(var(--accent-rgb, 99, 102, 241), 0.08); +} + +.right-panel-body { + flex: 1; + overflow: auto; +} + +/* ── Token panel ──────────────────────────────────────────── */ +.token-panel { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 0.75rem 1rem; +} + +.token-stat-big { + display: flex; + flex-direction: column; + gap: 0.15rem; +} + +.token-stat-label { + font-size: 0.68rem; + text-transform: uppercase; + letter-spacing: 0.07em; + color: var(--text-dim); +} + +.token-stat-value { + font-size: 2rem; + font-weight: 700; + font-family: 'Fira Code', monospace; + color: var(--text); + line-height: 1.1; +} + +.token-io-bars { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.token-bar-row { + display: grid; + grid-template-columns: 3.5rem 1fr 3rem; + align-items: center; + gap: 0.5rem; +} + +.token-bar-label { + font-size: 0.72rem; + color: var(--text-dim); + text-align: right; +} + +.token-bar-track { + height: 6px; + background: var(--border); + border-radius: 3px; + overflow: hidden; +} + +.token-bar-fill { + height: 100%; + border-radius: 3px; + transition: width 0.3s ease; +} + +.token-bar-fill.input { + background: var(--accent); +} + +.token-bar-fill.output { + background: var(--purple, #a78bfa); +} + +.token-bar-count { + font-size: 0.72rem; + font-family: 'Fira Code', monospace; + color: var(--text-muted, var(--text-dim)); + text-align: right; +} + +.token-cost-display { + display: flex; + justify-content: space-between; + align-items: baseline; + padding: 0.5rem 0; + border-top: 1px solid var(--border); + font-size: 0.82rem; +} + +.token-cost-display strong { + font-family: 'Fira Code', monospace; + font-size: 1rem; + color: var(--success, #34d399); +} + +/* ── Latency panel ────────────────────────────────────────── */ +.latency-panel { + display: flex; + flex-direction: column; + gap: 0.75rem; + padding: 0.75rem 1rem; +} + +.latency-range { + display: flex; + justify-content: space-between; +} + +.latency-range-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.1rem; +} + +.latency-range-label { + font-size: 0.68rem; + text-transform: uppercase; + letter-spacing: 0.07em; + color: var(--text-dim); +} + +.latency-range-val { + font-size: 1rem; + font-weight: 600; + font-family: 'Fira Code', monospace; + color: var(--text); +} + +.latency-mini-bars { + display: flex; + align-items: flex-end; + gap: 2px; + height: 80px; + padding: 4px 0; +} + +.latency-mini-bar { + flex: 1; + min-height: 2px; + background: var(--accent); + border-radius: 2px 2px 0 0; + opacity: 0.7; + transition: opacity 0.15s; + cursor: pointer; +} + +.latency-mini-bar:hover { + opacity: 1; +} + +/* ── Thinking stream (agents live view) ───────────────────── */ +.thinking-stream-card { + min-width: 0; +} + +.thinking-stream { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.25rem 0; + min-height: 3rem; +} + +.thinking-stream-empty { + color: var(--text-dim); + font-size: 0.8rem; + padding: 0.5rem 0; +} + +.thinking-op { + border-radius: 8px; + padding: 0.55rem 0.75rem; + border-left: 3px solid var(--border); + background: var(--bg); + transition: border-color 0.2s, opacity 0.3s; +} + +.thinking-op.active { + border-left-color: var(--accent); +} + +.thinking-op.ended { + border-left-color: var(--success, #34d399); + opacity: 0.6; +} + +.thinking-op-run.active { + border-left-color: var(--accent); + background: rgba(99, 102, 241, 0.06); +} + +.thinking-op-subagent.active { + border-left-color: var(--purple, #a78bfa); + background: rgba(167, 139, 250, 0.06); +} + +.thinking-op-tool.active { + border-left-color: #22d3ee; + background: rgba(34, 211, 238, 0.04); +} + +.thinking-op-link { + cursor: pointer; +} + +.thinking-op-link:hover { + border-left-width: 3px; + filter: brightness(1.1); +} + +.thinking-op-link:hover .thinking-op-arrow { + opacity: 1; + transform: translateX(2px); +} + +.thinking-op-arrow { + margin-left: auto; + font-size: 0.8rem; + color: var(--text-dim); + opacity: 0.4; + transition: opacity 0.15s, transform 0.15s; + flex-shrink: 0; +} + +.thinking-op-header { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.8rem; +} + +.thinking-op-icon { + font-size: 0.9rem; + line-height: 1; + flex-shrink: 0; + color: var(--text-dim); + width: 1rem; + text-align: center; +} + +.thinking-op-icon.spin { + animation: thinkSpin 1.4s linear infinite; +} + +@keyframes thinkSpin { + 0% { content: '◌'; opacity: 1; } + 25% { opacity: 0.4; } + 50% { opacity: 1; } + 75% { opacity: 0.4; } + 100% { opacity: 1; } +} + +/* Pulsing opacity trick since content can't be animated */ +.thinking-op-icon.spin { + animation: thinkPulse 1s ease-in-out infinite; +} + +@keyframes thinkPulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} + +.thinking-op-kind { + font-size: 0.65rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-dim); + min-width: 4.5rem; +} + +.thinking-op-name { + font-weight: 600; + font-size: 0.82rem; + color: var(--text); + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.thinking-op-elapsed { + font-family: 'Fira Code', monospace; + font-size: 0.72rem; + color: var(--text-dim); + flex-shrink: 0; +} + +.thinking-op-elapsed.live { + color: var(--accent); +} + +.thinking-op-preview { + margin-top: 0.3rem; + font-size: 0.75rem; + color: var(--text-muted, var(--text-dim)); + line-height: 1.4; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + padding-left: 1.4rem; +} + +.thinking-op-result { + margin-top: 0.25rem; + font-size: 0.75rem; + color: var(--success, #34d399); + font-family: 'Fira Code', monospace; + line-height: 1.4; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + padding-left: 1.4rem; +} + +.thinking-op-tokens { + display: flex; + gap: 0.4rem; + margin-top: 0.3rem; + padding-left: 1.4rem; +} + +.thinking-tok-badge { + font-size: 0.68rem; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 4px; + padding: 0.1rem 0.35rem; + color: var(--text-dim); +} + +.thinking-toks { + color: #a78bfa; +} + +.error-kv strong { + color: var(--error); +} + +/* ── Context bar ──────────────────────────────────────────── */ +.context-bar { + margin-top: 0.5rem; + height: 4px; + background: var(--border); + border-radius: 2px; + overflow: hidden; +} + +.context-bar-fill { + height: 100%; + background: var(--accent); + border-radius: 2px; + transition: width 0.4s ease; +} + +/* ── Run detail live ops banner ────────────────────────────── */ +.run-live-ops { + margin-bottom: 1rem; +} + +.run-live-ops-inner { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + align-items: center; +} + +.run-live-op-pill { + display: inline-flex; + align-items: center; + gap: 0.35rem; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 20px; + padding: 0.25rem 0.65rem; + font-size: 0.78rem; + max-width: 320px; +} + +.run-live-op-pill.thinking { + border-color: var(--accent); + background: rgba(99, 102, 241, 0.08); +} + +.run-live-op-pill.subagent { + border-color: var(--purple, #a78bfa); + background: rgba(167, 139, 250, 0.08); +} + +.run-live-op-pill.tool { + border-color: #22d3ee; + background: rgba(34, 211, 238, 0.06); +} + +.run-live-op-spin { + animation: thinkPulse 1s ease-in-out infinite; + font-size: 0.75rem; + flex-shrink: 0; +} + +.run-live-op-name { + font-weight: 600; + color: var(--text); +} + +.run-live-op-preview { + color: var(--text-dim); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 140px; +} + +.run-live-op-time { + font-family: 'Fira Code', monospace; + font-size: 0.7rem; + color: var(--accent); + flex-shrink: 0; +} + +/* ── Mobile: agents live — sidebar after content ──────────── */ +@media (max-width: 640px) { + .agents-live-sidebar { + order: 2; + } +} + +/* ── Mobile: compact filters ──────────────────────────────── */ +@media (max-width: 480px) { + .filters { + padding: 0.75rem; + gap: 0.5rem; + } + + .filters label { + width: 100%; + } + + .filters input, + .filters select { + width: 100%; + min-width: unset; + } + + .chart-legend { + display: none; + } + + .chart-header-controls { + justify-content: flex-start; + gap: 0.5rem; + } + + .window-selector, + .mode-selector { + flex-shrink: 0; + } +} + +/* ── Toast notifications ──────────────────────────────── */ +.toast { + position: fixed; + bottom: 1.5rem; + left: 50%; + transform: translateX(-50%) translateY(1rem); + opacity: 0; + z-index: 9999; + padding: 0.65rem 1.25rem; + border-radius: 8px; + font-family: var(--font-body); + font-size: 0.82rem; + font-weight: 500; + color: var(--text-bright); + background: var(--surface); + border: 1px solid var(--border); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); + transition: opacity 0.3s ease, transform 0.3s ease; + pointer-events: none; + max-width: 480px; + text-align: center; +} +.toast.visible { + opacity: 1; + transform: translateX(-50%) translateY(0); +} +.toast-error { + border-color: var(--error); + background: rgba(248, 113, 113, 0.12); +} +.toast-info { + border-color: var(--accent); + background: rgba(34, 211, 238, 0.08); +} + +/* ── 404 page ─────────────────────────────────────────── */ +.not-found { + text-align: center; + padding: 6rem 2rem; +} +.not-found h2 { + font-family: var(--font-display); + font-size: 1.6rem; + color: var(--text-bright); + margin-bottom: 0.5rem; +} +.not-found p { + color: var(--text-dim); + margin-bottom: 1.5rem; +} + +/* ── Skeleton loading ─────────────────────────────────── */ +.skeleton-line { + height: 0.85rem; + width: 80%; + border-radius: 4px; + background: linear-gradient( + 90deg, + var(--border) 25%, + rgba(255, 255, 255, 0.04) 50%, + var(--border) 75% + ); + background-size: 200% 100%; + animation: skeleton-pulse 1.5s ease-in-out infinite; +} + +@keyframes skeleton-pulse { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +/* Vary widths in table cells for a natural look */ +tbody .skeleton-line { margin: 0.15rem 0; } +tbody td:nth-child(1) .skeleton-line { width: 70%; } +tbody td:nth-child(2) .skeleton-line { width: 55%; } +tbody td:nth-child(3) .skeleton-line { width: 45%; } +tbody td:nth-child(4) .skeleton-line { width: 30%; } +tbody td:nth-child(5) .skeleton-line { width: 40%; } + +/* ── Keyboard accessibility ───────────────────────────── */ +tr.expandable[tabindex="0"]:focus-visible, +tr.expandable-run[tabindex="0"]:focus-visible, +tr.run-span-row[tabindex="0"]:focus-visible { + outline: 2px solid var(--accent); + outline-offset: -2px; +}