SPB Git

Repository Public

page.tsx 5.7 KB 58 lines tsx

1import { A, Meter, PageHead, Panel, Stat, bytes, usd, when } from "@/components/studio/ui";2import { requireRole } from "@/lib/auth";3import { costSummary } from "@/lib/studio-ops";4import { IMAGE_USD, PRICES_RETRIEVED, TTS_USD_PER_CHAR, VIDEO_USD_PER_S } from "@/providers/pricing";56export const metadata = { title: "Costs" };7export default async function Costs() {8  await requireRole("viewer");9  const c = await costSummary();10  const max = Math.max(0.01, ...c.byDay.map((d) => d.usd));11  const cad = (n: number) => (c.fx ? ` · ${(n * c.fx.rate).toFixed(2)} CAD` : "");12  return (13    <>14      <PageHead title="Costs" sub="Actual spend comes from the provider's own usage figures (cost in USD ticks). Estimates are the pre-flight figures the budget gate used." />15      <div className="grid grid-cols-2 gap-3 lg:grid-cols-4">16        <Stat label="Today" value={usd(c.totals.day)} sub={`cap ${usd(c.policy.per_day_usd)}${cad(c.totals.day)}`} />17        <Stat label="This month" value={usd(c.totals.month)} sub={`cap ${usd(c.policy.per_month_usd)}${cad(c.totals.month)}`} />18        <Stat label="All time" value={usd(c.totals.all_time)} sub={cad(c.totals.all_time).slice(3) || undefined} />19        <Stat label="Failed generations" value={usd(c.totals.failed)} sub="billed by the provider, no usable output" tone={c.totals.failed > 0 ? "warn" : undefined} />20      </div>21      <p className="mt-2 text-xs text-[var(--color-faint)]">{c.fx ? `CAD at ${c.fx.rate} USD→CAD, rate entered ${when(c.fx.at)}.` : "No USD→CAD rate set (Settings): amounts are shown in USD only."}</p>22      <div className="mt-5 grid gap-5 lg:grid-cols-2">23        <Panel title="Ceilings"><div className="space-y-4"><Meter label="Today" spent={c.totals.day} cap={Number(c.policy.per_day_usd)} /><Meter label="This month" spent={c.totals.month} cap={Number(c.policy.per_month_usd)} /></div>24          <p className="mt-4 text-xs text-[var(--color-faint)]">Per shot {usd(c.policy.per_shot_usd)} · per film {usd(c.policy.per_film_usd)} · {c.policy.max_regenerations} regenerations per shot before owner approval. <A href="/studio/settings">Edit</A></p></Panel>25        <Panel title="Last 30 days">26          {c.byDay.length ? <div className="flex h-32 items-end gap-1" role="img" aria-label="Daily spend, last 30 days">{c.byDay.map((d) => <div key={d.day} className="group relative flex-1"><div className="rounded-t bg-[var(--color-lamp)]" style={{ height: `${Math.max(2, (d.usd / max) * 120)}px` }} title={`${d.day}: ${usd(d.usd)}`} /></div>)}</div> : <p className="text-sm text-[var(--color-faint)]">No spend yet.</p>}27          {c.byDay.length > 0 && <table className="studio-table mt-3"><tbody>{c.byDay.slice(-5).reverse().map((d) => <tr key={d.day}><td>{d.day}</td><td className="text-right tabular-nums">{usd(d.usd, 3)}</td></tr>)}</tbody></table>}28        </Panel>29      </div>30      <div className="mt-5 grid gap-5 lg:grid-cols-2">31        <Panel title="Per model — estimated vs actual">32          <table className="studio-table"><thead><tr><th>Kind · model</th><th>Calls</th><th>Estimated</th><th>Actual</th></tr></thead><tbody>33            {c.byModel.map((m) => <tr key={m.kind + m.model}><td className="mono">{m.kind} · {m.model}</td><td className="tabular-nums">{m.calls}</td><td className="tabular-nums">{usd(m.est, 3)}</td><td className="tabular-nums">{usd(m.usd, 3)}</td></tr>)}34            {!c.byModel.length && <tr><td colSpan={4} className="text-[var(--color-faint)]">No provider call yet.</td></tr>}35          </tbody></table>36        </Panel>37        <Panel title="Per film — estimated vs actual">38          <table className="studio-table"><thead><tr><th>Project</th><th>Status</th><th>Estimated</th><th>Actual</th></tr></thead><tbody>39            {c.byProject.filter((p) => p.usd > 0).map((p) => <tr key={p.id}><td><A href={`/studio/projects/${p.id}`}>{p.title}</A></td><td className="text-[var(--color-muted)]">{p.status.replace(/_/g, " ")}</td><td className="tabular-nums">{usd(p.est, 3)}</td><td className="tabular-nums">{usd(p.usd, 3)}</td></tr>)}40            {!c.byProject.some((p) => p.usd > 0) && <tr><td colSpan={4} className="text-[var(--color-faint)]">No spend yet.</td></tr>}41          </tbody></table>42        </Panel>43      </div>44      <div className="mt-5 grid gap-5 lg:grid-cols-2">45        <Panel title={`Price table used for estimates (retrieved ${PRICES_RETRIEVED})`}>46          <table className="studio-table"><tbody>47            {Object.entries(VIDEO_USD_PER_S).map(([k, v]) => <tr key={k}><td className="mono">{k}</td><td className="tabular-nums">${v.usd}/s</td><td className="text-[var(--color-muted)]">{v.basis}</td></tr>)}48            {Object.entries(IMAGE_USD).flatMap(([m, t]) => Object.entries(t).slice(0, 1).map(([k, v]) => <tr key={m + k}><td className="mono">{m} ({k})</td><td className="tabular-nums">${v}/image</td><td className="text-[var(--color-muted)]">list</td></tr>))}49            <tr><td className="mono">text to speech</td><td className="tabular-nums">${(TTS_USD_PER_CHAR * 1e6).toFixed(0)}/1M chars</td><td className="text-[var(--color-muted)]">list · computed (no usage returned)</td></tr>50          </tbody></table>51          <p className="mt-3 text-xs text-[var(--color-faint)]">“measured” = observed on a real call; “assumed” = no official per-resolution price was found, replaced by the observed mean as soon as a real job exists. Unknown prices block autonomous execution.</p>52        </Panel>53        <Panel title="Storage"><p className="display text-4xl tabular-nums">{bytes(c.storage.bytes)}</p><p className="mt-1 text-sm text-[var(--color-faint)]">{c.storage.files} distinct objects on the node&apos;s disk (content-addressed). No provider storage is used, so there is no storage charge; sources, intermediates and masters are all kept.</p></Panel>54      </div>55    </>56  );57}58
Files main
Menu

Theme