Files
OpenJarvis/frontend/src/components/SystemPulse.tsx
T
e240fde74a feat(ui): Raycast-inspired visual refresh with System Pulse and X-Ray footers (#57)
* feat(ui): update design tokens — system-ui font, warmer palette, accent triad

- Remove Merriweather serif font, switch to system-ui font stack
- Warmer dark mode neutrals (#161618 bg instead of #09090b)
- Add amber/purple accent tokens for signature color triad
- Update borders to rgba for softer feel
- Tighter line-height (1.5) and letter-spacing (-0.01em)
- Update PWA theme_color to match new palette

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(ui): frosted glass sidebar with backdrop-blur

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(ui): initialize shadcn/ui with button, dialog, input, select, tooltip, sonner

Set up shadcn/ui (v4) for Tailwind CSS v4 with CSS-first config. Added
path aliases (@/) to tsconfig.json and vite.config.ts. Installed six
components (button, dialog, input, select, tooltip, sonner) and wired
Sonner's <Toaster> into App.tsx for toast notifications. Removed the
next-themes dependency since this is a Vite/React project, not Next.js.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ui): disable create agent when unavailable, replace errors with toasts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(ui): add System Pulse bar and health check banner

- 3px gradient bar at top reflecting system state (idle/inferencing/agent)
- Health check banner when backend is unreachable
- Pulse colors: blue (inference), purple (agents), dim blue (idle)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(ui): add X-Ray message footers with collapsible telemetry

- MessageTelemetry type on ChatMessage (engine, model, speed, TTFT, timing)
- Capture timing data during SSE streaming in InputArea
- XRayFooter component: collapsed one-liner + expandable trace grid
- Integrated into MessageBubble, replacing old token count display

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ui): resolve shadcn/ui CSS token conflicts with design system

- Namespace shadcn @theme inline variables (--color-cn-*) to avoid
  overriding project's --color-border, --color-accent, --radius-* tokens
- Remove @apply border-border, bg-background, text-foreground, font-sans
  that conflicted with our explicit CSS custom properties
- Remove dead Geist font import (we use system-ui)
- Remove ---break--- comment artifacts from shadcn CLI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 13:38:07 -07:00

61 lines
1.9 KiB
TypeScript

import { useEffect, useState } from 'react';
import { useAppStore } from '../lib/store';
import { fetchManagedAgents } from '../lib/api';
type PulseState = 'idle' | 'inferencing' | 'agent-active' | 'hidden';
const PULSE_CONFIG: Record<Exclude<PulseState, 'hidden'>, { color: string; animation: string }> = {
idle: {
color: 'rgba(59, 130, 246, 0.3)',
animation: 'none',
},
inferencing: {
color: '#3b82f6',
animation: 'pulse-glow 2s ease-in-out infinite',
},
'agent-active': {
color: '#8b5cf6',
animation: 'pulse-travel 3s linear infinite',
},
};
export function SystemPulse({ apiReachable }: { apiReachable: boolean | null }) {
const isStreaming = useAppStore((s) => s.streamState.isStreaming);
const [hasRunningAgent, setHasRunningAgent] = useState(false);
// Poll for running agents every 30s
useEffect(() => {
if (apiReachable === false) return;
const check = () =>
fetchManagedAgents()
.then((agents) => setHasRunningAgent(agents.some((a) => a.status === 'running')))
.catch(() => {});
check();
const interval = setInterval(check, 30000);
return () => clearInterval(interval);
}, [apiReachable]);
if (apiReachable === false) return null;
// Priority: agent-active > inferencing > idle
let state: PulseState = 'idle';
if (isStreaming) state = 'inferencing';
if (hasRunningAgent) state = 'agent-active';
const config = PULSE_CONFIG[state];
const isTravel = state === 'agent-active';
return (
<div
className="fixed top-0 left-0 right-0 h-[3px] z-50"
style={{
background: isTravel
? `linear-gradient(90deg, transparent, ${config.color}, transparent)`
: `linear-gradient(90deg, transparent 5%, ${config.color} 30%, ${config.color} 70%, transparent 95%)`,
backgroundSize: isTravel ? '200% 100%' : '100% 100%',
animation: config.animation,
}}
/>
);
}