| 1 | import React, { useEffect, useState } from 'react';
|
| 2 | import { createRoot } from 'react-dom/client';
|
| 3 | import './styles.css';
|
| 4 |
|
| 5 | type DashboardData = {
|
| 6 | agents: any[];
|
| 7 | events: any[];
|
| 8 | conflicts: any[];
|
| 9 | receipts: any[];
|
| 10 | profiles: any[];
|
| 11 | };
|
| 12 | const empty: DashboardData = { agents: [], events: [], conflicts: [], receipts: [], profiles: [] };
|
| 13 |
|
| 14 | function App() {
|
| 15 | const [data, setData] = useState(empty);
|
| 16 | const [offline, setOffline] = useState(false);
|
| 17 | useEffect(() => {
|
| 18 | const apiBase = location.hostname === 'localhost' ? 'http://localhost:3100' : location.origin;
|
| 19 | fetch(`${apiBase}/v0.1/dashboard`)
|
| 20 | .then((response) => response.json())
|
| 21 | .then(setData)
|
| 22 | .catch(() => setOffline(true));
|
| 23 | }, []);
|
| 24 | const bilateral = data.receipts.filter((receipt) => !receipt.unilateral).length;
|
| 25 | return (
|
| 26 | <main>
|
| 27 | <header>
|
| 28 | <div className="mark">OC</div>
|
| 29 | <div>
|
| 30 | <p className="eyebrow">ASSURANCE LAYER / V0.1</p>
|
| 31 | <h1>OpenClasp</h1>
|
| 32 | </div>
|
| 33 | <span className={offline ? 'status offline' : 'status'}>
|
| 34 | {offline ? 'API OFFLINE' : 'LOCAL NETWORK'}
|
| 35 | </span>
|
| 36 | </header>
|
| 37 | <section className="hero">
|
| 38 | <div>
|
| 39 | <p className="eyebrow">AGENT COORDINATION</p>
|
| 40 | <h2>
|
| 41 | Trust is contextual.
|
| 42 | <br />
|
| 43 | <em>Evidence makes it useful.</em>
|
| 44 | </h2>
|
| 45 | <p>
|
| 46 | Verify identities, watch agreements, surface clues, and learn from signed
|
| 47 | outcomes—without owning the conversation.
|
| 48 | </p>
|
| 49 | </div>
|
| 50 | <div className="signal">
|
| 51 | <span>Current posture</span>
|
| 52 | <strong>{data.profiles.length ? 'LEARNING' : 'AWAITING EVIDENCE'}</strong>
|
| 53 | <small>Raw messages remain local</small>
|
| 54 | </div>
|
| 55 | </section>
|
| 56 | <section className="metrics">
|
| 57 | <Metric label="Verified agents" value={data.agents.length} />
|
| 58 | <Metric label="Structured events" value={data.events.length} />
|
| 59 | <Metric label="Bilateral receipts" value={bilateral} />
|
| 60 | <Metric label="Contextual profiles" value={data.profiles.length} />
|
| 61 | </section>
|
| 62 | <section className="grid">
|
| 63 | <Panel title="Agent lineage" subtitle="Identity, operator, and version">
|
| 64 | {data.agents.length ? (
|
| 65 | data.agents.map((agent) => (
|
| 66 | <article className="row" key={agent.agentId}>
|
| 67 | <span className="dot" />
|
| 68 | <div>
|
| 69 | <strong>{agent.agentId}</strong>
|
| 70 | <small>
|
| 71 | {agent.operatorRef} · v{agent.agentVersion}
|
| 72 | </small>
|
| 73 | </div>
|
| 74 | <b>{agent.revoked ? 'REVOKED' : 'VERIFIED'}</b>
|
| 75 | </article>
|
| 76 | ))
|
| 77 | ) : (
|
| 78 | <Empty text="Run pnpm demo or register an agent through the API." />
|
| 79 | )}
|
| 80 | </Panel>
|
| 81 | <Panel title="Behavioural context" subtitle="Task-specific, never universal">
|
| 82 | {data.profiles.length ? (
|
| 83 | data.profiles.map((profile) => (
|
| 84 | <article className="profile" key={`${profile.agentId}${profile.taskCategory}`}>
|
| 85 | <div>
|
| 86 | <strong>{profile.agentId}</strong>
|
| 87 | <small>
|
| 88 | {profile.taskCategory} · {profile.sampleSize} signed outcome(s)
|
| 89 | </small>
|
| 90 | </div>
|
| 91 | <Meter value={profile.completion} label="completion" />
|
| 92 | <Meter value={profile.scope} label="scope" />
|
| 93 | </article>
|
| 94 | ))
|
| 95 | ) : (
|
| 96 | <Empty text="Profiles grow only from verified receipts and eligible feedback." />
|
| 97 | )}
|
| 98 | </Panel>
|
| 99 | <Panel title="Interaction timeline" subtitle="Attributable structured events">
|
| 100 | {data.events.length ? (
|
| 101 | data.events
|
| 102 | .slice(-6)
|
| 103 | .reverse()
|
| 104 | .map((event) => (
|
| 105 | <article className="row" key={event.eventId}>
|
| 106 | <span className={`dot ${event.eventType}`} />
|
| 107 | <div>
|
| 108 | <strong>{event.eventType.replaceAll('_', ' ')}</strong>
|
| 109 | <small>
|
| 110 | {event.agentId} · {new Date(event.timestamp).toLocaleTimeString()}
|
| 111 | </small>
|
| 112 | </div>
|
| 113 | <b>{event.visibility}</b>
|
| 114 | </article>
|
| 115 | ))
|
| 116 | ) : (
|
| 117 | <Empty text="No structured events recorded yet." />
|
| 118 | )}
|
| 119 | </Panel>
|
| 120 | <Panel title="Mediation and receipts" subtitle="Shared only with explicit consent">
|
| 121 | <div className="split">
|
| 122 | <div>
|
| 123 | <strong>{data.conflicts.filter((item) => item.status === 'resolved').length}</strong>
|
| 124 | <small>resolved conflicts</small>
|
| 125 | </div>
|
| 126 | <div>
|
| 127 | <strong>{data.receipts.length}</strong>
|
| 128 | <small>signed receipts</small>
|
| 129 | </div>
|
| 130 | </div>
|
| 131 | <p className="note">
|
| 132 | Unilateral receipts are labelled separately and never treated as bilateral proof.
|
| 133 | </p>
|
| 134 | </Panel>
|
| 135 | </section>
|
| 136 | </main>
|
| 137 | );
|
| 138 | }
|
| 139 |
|
| 140 | function Metric({ label, value }: { label: string; value: number }) {
|
| 141 | return (
|
| 142 | <div>
|
| 143 | <span>{label}</span>
|
| 144 | <strong>{String(value).padStart(2, '0')}</strong>
|
| 145 | </div>
|
| 146 | );
|
| 147 | }
|
| 148 | function Panel({
|
| 149 | title,
|
| 150 | subtitle,
|
| 151 | children,
|
| 152 | }: React.PropsWithChildren<{ title: string; subtitle: string }>) {
|
| 153 | return (
|
| 154 | <section className="panel">
|
| 155 | <div className="panelHead">
|
| 156 | <div>
|
| 157 | <h3>{title}</h3>
|
| 158 | <p>{subtitle}</p>
|
| 159 | </div>
|
| 160 | <span>↗</span>
|
| 161 | </div>
|
| 162 | {children}
|
| 163 | </section>
|
| 164 | );
|
| 165 | }
|
| 166 | function Empty({ text }: { text: string }) {
|
| 167 | return <div className="empty">{text}</div>;
|
| 168 | }
|
| 169 | function Meter({ value, label }: { value: number; label: string }) {
|
| 170 | return (
|
| 171 | <div className="meter">
|
| 172 | <span>{label}</span>
|
| 173 | <i>
|
| 174 | <b style={{ width: `${value * 100}%` }} />
|
| 175 | </i>
|
| 176 | <strong>{Math.round(value * 100)}%</strong>
|
| 177 | </div>
|
| 178 | );
|
| 179 | }
|
| 180 |
|
| 181 | createRoot(document.getElementById('root')!).render(
|
| 182 | <React.StrictMode>
|
| 183 | <App />
|
| 184 | </React.StrictMode>,
|
| 185 | );
|