File size: 4,217 Bytes
4fdaf19 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | import React, { useEffect, useState } from 'react';
import { Activity, Cpu, Gauge, KeyRound, RefreshCw } from 'lucide-react';
import { apiData } from '../services/api';
/** Dev & Account: credits, usage, capabilities (tier/engine/queue), jobs. */
const AccountPanel: React.FC = () => {
const [credits, setCredits] = useState<any>(null);
const [usage, setUsage] = useState<any>(null);
const [caps, setCaps] = useState<any>(null);
const [jobs, setJobs] = useState<any>(null);
const load = () => {
apiData('/api/account/credits').then(setCredits).catch(() => undefined);
apiData('/api/account/usage').then(setUsage).catch(() => undefined);
apiData('/api/account/capabilities').then(setCaps).catch(() => undefined);
apiData('/api/jobs').then(setJobs).catch(() => undefined);
};
useEffect(() => {
load();
const t = setInterval(load, 4000);
return () => clearInterval(t);
}, []);
const Card: React.FC<{ title: string; icon: any; children: React.ReactNode }> = ({ title, icon: Icon, children }) => (
<div className="rounded-2xl border border-gray-800 bg-[#0c0c0c] p-5">
<div className="mb-3 flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-teal-400"><Icon size={14} />{title}</div>
{children}
</div>
);
return (
<div className="mx-auto max-w-6xl space-y-5 px-6 py-8 text-white">
<div className="grid gap-5 lg:grid-cols-3">
<Card title="Credits" icon={KeyRound}>
{credits && (
<>
<div className="text-3xl font-bold text-teal-300">{credits.credits}</div>
<div className="text-[10px] text-gray-500">{credits.budget_scope} · via {credits.auth_via}</div>
</>
)}
</Card>
<Card title="Deployment tier" icon={Cpu}>
{caps && (
<div className="space-y-1 text-[11px]">
<div className="flex justify-between"><span className="text-gray-400">perception</span><span className={caps.perception_tier === 'zerogpu' ? 'text-teal-300' : 'text-gray-300'}>{caps.perception_tier}</span></div>
<div className="flex justify-between"><span className="text-gray-400">engine</span><span className="text-gray-300">{caps.engine}</span></div>
<div className="flex justify-between"><span className="text-gray-400">nova key</span><span>{caps.nova_configured ? '✓' : '—'}</span></div>
</div>
)}
</Card>
<Card title="Job queue" icon={Gauge}>
{caps?.job_queue && (
<div className="text-[11px]">
<div className="flex justify-between"><span className="text-gray-400">active</span><span className="font-mono text-teal-300">{caps.job_queue.active}</span></div>
<div className="flex justify-between"><span className="text-gray-400">max workers</span><span className="font-mono">{caps.job_queue.max_workers}</span></div>
</div>
)}
</Card>
</div>
<Card title="Usage by category" icon={Activity}>
{usage?.usage && Object.keys(usage.usage).length > 0 ? (
<div className="grid grid-cols-2 gap-x-6 gap-y-1 text-[11px] lg:grid-cols-3">
{Object.entries(usage.usage).map(([k, v]) => (
<div key={k} className="flex justify-between"><span className="text-gray-400">{k}</span><span className="font-mono text-teal-300">{String(v)}</span></div>
))}
</div>
) : <p className="text-[11px] text-gray-600">No usage yet.</p>}
</Card>
<Card title="Recent jobs" icon={RefreshCw}>
{jobs?.jobs?.length ? (
<div className="space-y-1 text-[10px] font-mono">
{jobs.jobs.slice(0, 8).map((j: any) => (
<div key={j.job_id} className="flex justify-between">
<span className="text-gray-400">{j.kind}</span>
<span className={j.status === 'done' ? 'text-green-400' : j.status === 'failed' ? 'text-rose-400' : 'text-amber-300'}>{j.status}</span>
</div>
))}
</div>
) : <p className="text-[11px] text-gray-600">No jobs yet.</p>}
</Card>
</div>
);
};
export default AccountPanel;
|