Skip to content
69 linesCodeBlameRaw
1import { useOutletContext } from "react-router";
2
3import type { WorkspaceAgent } from "@g1t/contracts";
4
5import { formatDollars } from "../../../lib/agent-form";
6
7/** Spend this month against the agent's budget; by task and by model as tasks land. */
8export default function Spend() {
9 const agent = useOutletContext<WorkspaceAgent>();
10 const spent = agent.spent_month_micros ?? 0;
11 const cap = agent.budget.monthly_micros;
12 const share = cap ? Math.min(1, spent / cap) : 0;
13 const tone = share >= 1 ? "bg-danger" : share >= 0.8 ? "bg-warn" : "bg-accent";
14 return (
15 <div className="space-y-8">
16 <section className="rounded-2xl border border-line bg-surface p-6">
17 <p className="text-sm text-muted">Spent this month</p>
18 <p className="mt-1 flex flex-wrap items-baseline gap-x-2">
19 <span className="text-3xl font-semibold tracking-tight tabular-nums">{formatDollars(spent)}</span>
20 <span className="text-sm text-muted">{cap ? `of ${formatDollars(cap)}` : "No monthly cap: the workspace limit applies"}</span>
21 </p>
22 {cap != null && (
23 <>
24 <div
25 className="mt-4 h-2 overflow-hidden rounded-full bg-line"
26 role="meter"
27 aria-label="Spent of the monthly cap"
28 aria-valuemin={0}
29 aria-valuemax={cap}
30 aria-valuenow={spent}
31 >
32 <div className={`h-full rounded-full ${tone} transition-[width]`} style={{ width: `${Math.max(share * 100, spent > 0 ? 1.5 : 0)}%` }} />
33 </div>
34 <p className="mt-2 text-xs text-faint">
35 {Math.round(share * 100)}% used. At 80% it tells the channel that pays for it; at 100% it takes no new tasks.
36 </p>
37 </>
38 )}
39 <dl className="mt-6 grid grid-cols-2 gap-4 border-t border-line pt-5 sm:grid-cols-3">
40 <div>
41 <dt className="text-xs text-faint">Daily cap</dt>
42 <dd className="mt-0.5 text-sm tabular-nums">{agent.budget.daily_micros != null ? formatDollars(agent.budget.daily_micros) : "None"}</dd>
43 </div>
44 <div>
45 <dt className="text-xs text-faint">Per task</dt>
46 <dd className="mt-0.5 text-sm tabular-nums">{agent.budget.task_micros != null ? formatDollars(agent.budget.task_micros) : "None"}</dd>
47 </div>
48 <div>
49 <dt className="text-xs text-faint">While idle</dt>
50 <dd className="mt-0.5 text-sm">Costs nothing</dd>
51 </div>
52 </dl>
53 </section>
54 <section>
55 <h2 className="text-sm font-medium">By task and by model</h2>
56 <div className="mt-3 grid gap-3 sm:grid-cols-2">
57 <div className="rounded-xl border border-dashed border-line px-5 py-8">
58 <p className="text-sm font-medium">By task</p>
59 <p className="mt-1 text-[0.8125rem] text-muted">Each task&apos;s spend against its cap shows here once it has worked one.</p>
60 </div>
61 <div className="rounded-xl border border-dashed border-line px-5 py-8">
62 <p className="text-sm font-medium">By model</p>
63 <p className="mt-1 text-[0.8125rem] text-muted">Every step records the model that ran it; the split shows here once it has worked.</p>
64 </div>
65 </div>
66 </section>
67 </div>
68 );
69}