Build the agent-native compute CRM platform
CI / verify (push) Successful in 3m6s

This commit is contained in:
2026-08-13 01:39:01 -07:00
parent bfd2f8d95a
commit 853bde2265
160 changed files with 61812 additions and 483 deletions
+87 -30
View File
@@ -7,9 +7,16 @@
*/
import { useState } from 'react';
import { useMutation, useQuery } from '@tanstack/react-query';
import { Search, Server, Zap } from 'lucide-react';
import type { PermissionGrant } from '@pig/core';
import { Search, Server, ShieldCheck, Zap } from 'lucide-react';
import { compactNumber, get, money, percent, post, shortDate } from '@/lib/api';
import { usePageTitle } from '@/lib/title';
import { can } from '@/lib/permissions';
import {
AllocationSheet,
type AvailabilityRow,
type MatchRow,
} from '@/components/AllocationSheet';
import {
Badge,
Button,
@@ -22,29 +29,20 @@ import {
Skeleton,
} from '@/components/ui';
interface AvailabilityRow {
commitmentId: string;
name: string;
gpuType: string;
gpuCount: number;
interconnectType: string;
securityTier: string;
startsAt: string;
endsAt: string;
totalGpuHours: number;
soldGpuHours: number;
heldGpuHours: number;
availableGpuHours: number;
costPerGpuHourCents: number;
utilisation: number;
breakEvenPriceCents: number | null;
}
type MatchRow = AvailabilityRow & { score: number; rationale: string[] };
export function Capacity() {
usePageTitle('Capacity');
const [tab, setTab] = useState<'available' | 'match'>('available');
const [allocation, setAllocation] = useState<{
open: boolean;
preferredCommitmentId?: string;
matches?: MatchRow[];
defaultGpuHours?: number;
}>({ open: false });
const { data: me } = useQuery({
queryKey: ['me'],
queryFn: () => get<{ permissions: PermissionGrant[] }>('/api/me'),
});
const writable = can(me, 'deal:write', 'demand');
return (
<div className="space-y-5">
@@ -78,12 +76,30 @@ export function Capacity() {
))}
</div>
{tab === 'available' ? <Availability /> : <Matcher />}
{tab === 'available' ? (
<Availability
writable={writable}
onAllocate={(preferredCommitmentId) =>
setAllocation({ open: true, preferredCommitmentId })
}
/>
) : (
<Matcher
writable={writable}
onAllocate={(preferredCommitmentId, matches, defaultGpuHours) =>
setAllocation({ open: true, preferredCommitmentId, matches, defaultGpuHours })
}
/>
)}
<AllocationSheet
{...allocation}
onOpenChange={(open) => setAllocation((state) => ({ ...state, open }))}
/>
</div>
);
}
function Availability() {
function Availability({ writable, onAllocate }: { writable: boolean; onAllocate(id: string): void }) {
const { data, isLoading } = useQuery({
queryKey: ['availability'],
queryFn: () => get<AvailabilityRow[]>('/api/capacity/availability'),
@@ -108,13 +124,13 @@ function Availability() {
return (
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
{data.map((row) => (
<CapacityCard key={row.commitmentId} row={row} />
<CapacityCard key={row.commitmentId} row={row} writable={writable} onAllocate={() => onAllocate(row.commitmentId)} />
))}
</div>
);
}
function CapacityCard({ row }: { row: AvailabilityRow }) {
function CapacityCard({ row, writable, onAllocate }: { row: AvailabilityRow; writable: boolean; onAllocate(): void }) {
const soldPct = row.totalGpuHours > 0 ? row.soldGpuHours / row.totalGpuHours : 0;
const heldPct = row.totalGpuHours > 0 ? row.heldGpuHours / row.totalGpuHours : 0;
@@ -170,16 +186,22 @@ function CapacityCard({ row }: { row: AvailabilityRow }) {
: `${money(row.breakEvenPriceCents)}/hr`}
</dd>
</dl>
<Button variant="outline" className="w-full" disabled={!writable || row.availableGpuHours <= 0} onClick={onAllocate} title={!writable ? 'Demand-team write permission is required' : undefined}>
<ShieldCheck data-icon="inline-start" aria-hidden />
Allocate or hold
</Button>
</CardContent>
</Card>
);
}
function Matcher() {
function Matcher({ writable, onAllocate }: { writable: boolean; onAllocate(id: string, matches: MatchRow[], defaultGpuHours?: number): void }) {
const [form, setForm] = useState({
gpuType: '',
gpuCount: '64',
totalGpuHours: '',
startsAt: '',
endsAt: '',
requiresHighSpeedInterconnect: true,
maxPrice: '',
});
@@ -190,6 +212,8 @@ function Matcher() {
gpuType: form.gpuType || undefined,
gpuCount: Number(form.gpuCount) || 1,
totalGpuHours: form.totalGpuHours ? Number(form.totalGpuHours) : undefined,
startsAt: form.startsAt ? new Date(`${form.startsAt}T00:00:00`).toISOString() : undefined,
endsAt: form.endsAt ? new Date(`${form.endsAt}T23:59:59`).toISOString() : undefined,
requiresHighSpeedInterconnect: form.requiresHighSpeedInterconnect,
maxPricePerGpuHourCents: form.maxPrice
? Math.round(Number(form.maxPrice) * 100)
@@ -249,6 +273,21 @@ function Matcher() {
placeholder="Optional"
/>
</Field>
<Field label="Needed from">
<Input
type="date"
value={form.startsAt}
onChange={(e) => setForm({ ...form, startsAt: e.target.value })}
/>
</Field>
<Field label="Needed until">
<Input
type="date"
value={form.endsAt}
min={form.startsAt || undefined}
onChange={(e) => setForm({ ...form, endsAt: e.target.value })}
/>
</Field>
<label className="tap flex items-center gap-2.5 text-sm sm:col-span-2 lg:col-span-3">
<input
@@ -265,8 +304,8 @@ function Matcher() {
</span>
</label>
<Button type="submit" variant="primary" disabled={mutation.isPending}>
<Search className="h-4 w-4" aria-hidden />
<Button type="submit" variant="primary" disabled={mutation.isPending} className="lg:col-start-4">
<Search data-icon="inline-start" aria-hidden />
{mutation.isPending ? 'Matching…' : 'Find capacity'}
</Button>
</form>
@@ -301,7 +340,7 @@ function Matcher() {
{percent(match.score)} fit
</Badge>
</div>
<ul className="mt-3 space-y-1 text-sm">
<ul className="mt-3 flex flex-col gap-1 text-sm">
{match.rationale.map((reason, i) => (
<li
key={i}
@@ -313,6 +352,24 @@ function Matcher() {
</li>
))}
</ul>
<div className="mt-4 flex flex-col gap-2 border-t border-border pt-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-xs text-muted">
{shortDate(match.startsAt)}{shortDate(match.endsAt)} · {money(match.breakEvenPriceCents)}/hr break even
</p>
<Button
variant="primary"
disabled={!writable}
onClick={() => onAllocate(
match.commitmentId,
mutation.data,
form.totalGpuHours ? Number(form.totalGpuHours) : undefined,
)}
title={!writable ? 'Demand-team write permission is required' : undefined}
>
<ShieldCheck data-icon="inline-start" aria-hidden />
Allocate this capacity
</Button>
</div>
</CardContent>
</Card>
))}
@@ -321,7 +378,7 @@ function Matcher() {
) : null}
{mutation.isError ? (
<p className="text-sm text-danger">
<p role="alert" className="text-sm text-danger">
{mutation.error instanceof Error ? mutation.error.message : 'Match failed.'}
</p>
) : null}