repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
The Living OS cockpit
stars
latest
clone command
git clone gitlawb://did:key:z6Mku78K...XywC/living-os-cockp...git clone gitlawb://did:key:z6Mku78K.../living-os-cockp...59751530feat: surface worker supervisor health in live work6h ago| #1 | 'use client'; |
| #2 | |
| #3 | import { useEffect, useRef, useState } from 'react'; |
| #4 | import Image from 'next/image'; |
| #5 | import useSWR from 'swr'; |
| #6 | import { Activity } from 'lucide-react'; |
| #7 | import AethonTasks from '@/components/AethonTasks'; |
| #8 | import ApprovalQueuePanel from '@/components/ApprovalQueuePanel'; |
| #9 | import ContentIntakePanel from '@/components/ContentIntakePanel'; |
| #10 | import EventsFeed from '@/components/EventsFeed'; |
| #11 | import LegalCases from '@/components/LegalCases'; |
| #12 | import SovereignInfra from '@/components/SovereignInfra'; |
| #13 | import SovereignProfileCard from '@/components/SovereignProfileCard'; |
| #14 | import { isAuthError, swrFetcher } from '@/lib/client-api'; |
| #15 | |
| #16 | const fetcher = swrFetcher; |
| #17 | |
| #18 | function staggerStyle(index: number) { |
| #19 | return { '--stagger-index': index } as any; |
| #20 | } |
| #21 | |
| #22 | export default function CommandClient() { |
| #23 | const { data: me, error: meError } = useSWR('/api/me', fetcher, { shouldRetryOnError: error => !isAuthError(error) }); |
| #24 | const { data: profile, error: profileError } = useSWR('/api/profile', fetcher, { shouldRetryOnError: error => !isAuthError(error) }); |
| #25 | const { data: status } = useSWR('/api/status', fetcher, { refreshInterval: 5000, shouldRetryOnError: error => !isAuthError(error) }); |
| #26 | const [glassesLog, setGlassesLog] = useState<any[]>([]); |
| #27 | const glassesLogRef = useRef<HTMLDivElement>(null); |
| #28 | |
| #29 | useEffect(() => { |
| #30 | if (!isAuthError(meError)) return; |
| #31 | const next = `${window.location.origin}/command`; |
| #32 | window.location.href = `https://theliving.ai/auth/login?redirect=${encodeURIComponent(next)}`; |
| #33 | }, [meError]); |
| #34 | |
| #35 | useEffect(() => { |
| #36 | const es = new EventSource('/api/glasses-log', { withCredentials: true }); |
| #37 | es.onmessage = event => { |
| #38 | try { |
| #39 | const evt = JSON.parse(event.data); |
| #40 | setGlassesLog(prev => [...prev.slice(-199), evt]); |
| #41 | } catch {} |
| #42 | }; |
| #43 | return () => es.close(); |
| #44 | }, []); |
| #45 | |
| #46 | useEffect(() => { |
| #47 | if (glassesLogRef.current) glassesLogRef.current.scrollTop = glassesLogRef.current.scrollHeight; |
| #48 | }, [glassesLog]); |
| #49 | |
| #50 | const safeProfile = profile && !profileError && profile.trust && profile.paidTier ? profile : undefined; |
| #51 | |
| #52 | return ( |
| #53 | <main className="min-h-screen px-4 py-6 md:px-8"> |
| #54 | <header className="mb-6 flex flex-wrap items-center gap-5"> |
| #55 | <div className="relative h-20 w-20 shrink-0 overflow-hidden rounded-xl border border-[color:var(--gold-3)] shadow-lg"> |
| #56 | <Image src="/brand/aethon-emblem.png" alt="Aethon emblem" fill className="object-cover" /> |
| #57 | </div> |
| #58 | <div className="min-w-[220px] flex-1"> |
| #59 | <div className="living-kicker">Command view</div> |
| #60 | <h1 className="text-4xl font-serif font-bold leading-tight gold">Operator Machinery</h1> |
| #61 | <div className="mt-1 text-sm text-[color:var(--text-3)]"> |
| #62 | Infra, logs, pipelines, diagnostics, compute, and utility panels live here off the body. |
| #63 | </div> |
| #64 | <div className="mt-1 text-sm text-[color:var(--text-2)]">{me?.displayName ?? 'Loading operator...'}</div> |
| #65 | </div> |
| #66 | <a className="command-link" href="/">Back to Aethon body</a> |
| #67 | </header> |
| #68 | |
| #69 | <div className="divider-gold mb-6" /> |
| #70 | |
| #71 | <div className="grid grid-cols-1 gap-4 lg:grid-cols-12"> |
| #72 | <SovereignProfileCard profile={safeProfile} /> |
| #73 | |
| #74 | <section className="card p-4 lg:col-span-3"> |
| #75 | <h2 className="card-header mb-3 text-base">Pipeline</h2> |
| #76 | <div className="space-y-1.5 text-sm"> |
| #77 | {status?.services?.map((s: any, index: number) => ( |
| #78 | <div key={s.id} className="stagger-row flex items-center justify-between" style={staggerStyle(index)}> |
| #79 | <span className="truncate pr-2 text-[color:var(--text-2)]">{s.label}</span> |
| #80 | <span className={`shrink-0 rounded px-2 py-0.5 font-mono text-[10px] ${s.status === 'up' ? 'status-up' : 'status-down'}`}>{s.status}</span> |
| #81 | </div> |
| #82 | )) ?? <div className="text-sm text-[color:var(--text-3)]">Loading pipeline...</div>} |
| #83 | </div> |
| #84 | </section> |
| #85 | |
| #86 | <section className="card p-4 lg:col-span-5"> |
| #87 | <div className="mb-3 flex items-center justify-between"> |
| #88 | <h2 className="card-header text-base">Glasses Live Log</h2> |
| #89 | <div className="flex items-center gap-3 text-xs"> |
| #90 | <span className={glassesLog.length > 0 ? 'text-green-400' : 'text-[color:var(--text-3)]'}> |
| #91 | {glassesLog.length > 0 ? `● ${glassesLog.length} events` : '○ idle'} |
| #92 | </span> |
| #93 | <button type="button" onClick={() => setGlassesLog([])} className="text-[color:var(--text-3)] underline hover:text-[color:var(--text-1)]">clear</button> |
| #94 | </div> |
| #95 | </div> |
| #96 | <div ref={glassesLogRef} className="h-56 overflow-y-auto rounded border border-[color:var(--border)] bg-[color:var(--bg-1)] p-2 font-mono text-[11px]"> |
| #97 | {glassesLog.length === 0 ? ( |
| #98 | <div className="p-2 leading-relaxed text-[color:var(--text-3)]"> |
| #99 | Awaiting wake phrase and bridge events. |
| #100 | </div> |
| #101 | ) : glassesLog.map((evt, index) => ( |
| #102 | <div key={index} className="stagger-row py-0.5 text-[color:var(--text-2)]" style={staggerStyle(Math.min(index, 12))}> |
| #103 | <span className="text-[color:var(--text-4)]">{evt.ts ? new Date(evt.ts).toLocaleTimeString() : ''} [{evt.type}] </span> |
| #104 | <span>{evt.text || evt.trigger || evt.message || ''}</span> |
| #105 | </div> |
| #106 | ))} |
| #107 | </div> |
| #108 | </section> |
| #109 | |
| #110 | <section className="card p-4 lg:col-span-12"> |
| #111 | <h2 className="card-header mb-3 text-base">Sovereign Infrastructure</h2> |
| #112 | <SovereignInfra /> |
| #113 | </section> |
| #114 | |
| #115 | <section className="card p-4 lg:col-span-6"> |
| #116 | <h2 className="card-header mb-3 text-base">Approval Queue</h2> |
| #117 | <div className="command-scroll-panel"> |
| #118 | <ApprovalQueuePanel /> |
| #119 | </div> |
| #120 | </section> |
| #121 | |
| #122 | <section className="card p-4 lg:col-span-6"> |
| #123 | <h2 className="card-header mb-3 text-base">Aethon Tasks</h2> |
| #124 | <div className="command-scroll-panel"> |
| #125 | <AethonTasks /> |
| #126 | </div> |
| #127 | </section> |
| #128 | |
| #129 | <section className="card p-4 lg:col-span-12"> |
| #130 | <ContentIntakePanel /> |
| #131 | </section> |
| #132 | |
| #133 | <section className="card p-4 lg:col-span-6"> |
| #134 | <h2 className="card-header mb-3 text-base">Legal Cases</h2> |
| #135 | <LegalCases /> |
| #136 | </section> |
| #137 | |
| #138 | <section className="card p-4 lg:col-span-6"> |
| #139 | <h2 className="card-header mb-3 text-base inline-flex items-center gap-2"><Activity size={14} /> System Events</h2> |
| #140 | <div className="command-scroll-panel compact"> |
| #141 | <EventsFeed /> |
| #142 | </div> |
| #143 | </section> |
| #144 | </div> |
| #145 | </main> |
| #146 | ); |
| #147 | } |
| #148 |