343 lines
28 KiB
TypeScript
343 lines
28 KiB
TypeScript
import { FormEvent, useCallback, useEffect, useRef, useState } from "react";
|
||
import type { ReactNode } from "react";
|
||
import { CapabilityDetails } from "./CapabilityDetails";
|
||
import { PromotionGate } from "./PromotionGate";
|
||
import type { OperatorIdentity } from "../lib/api";
|
||
import { CircleGauge, Clock3, KeyRound, Play, RefreshCw, ShieldCheck } from "lucide-react";
|
||
|
||
import { api, ApiError, operatorCredentialReference, operatorSessionVersion } from "../lib/api";
|
||
import { SafetyDialog } from "./SafetyDialog";
|
||
import { TabList, TabPanel } from "./TabList";
|
||
import type { TabDescriptor } from "./TabList";
|
||
import type {
|
||
CapabilityDeployment, CapabilityContractRecord, CapabilityEstate, DeploymentCandidate, GatewayInvokeResponse,
|
||
GatewayRequest, OCRInvokeResponse, PlacementPlan, ProductionApproval, RuntimeProfile, SchedulerBudget, SchedulerPolicy, ServiceClient,
|
||
ServiceClientCreated, SpeechTranscriptionResponse, VisionEmbeddingResponse,
|
||
} from "../types";
|
||
|
||
type View = "overview" | "scheduler" | "requests" | "clients" | "promotion" | "playground";
|
||
type CapabilityReadFailure = {
|
||
kind: "offline" | "error" | "unknown";
|
||
message: string;
|
||
code: string;
|
||
correlationId: string | null;
|
||
retryable: boolean;
|
||
nextAction: string;
|
||
};
|
||
type CapabilityReadState =
|
||
| { kind: "loading" }
|
||
| { kind: "fresh"; lastUpdatedAt: number }
|
||
| { kind: "unavailable"; failure: CapabilityReadFailure }
|
||
| { kind: "stale"; failure: CapabilityReadFailure; lastUpdatedAt: number };
|
||
|
||
const capabilityViews: TabDescriptor<View>[] = [
|
||
{ key: "overview", label: "overview" }, { key: "scheduler", label: "scheduler" },
|
||
{ key: "requests", label: "requests" }, { key: "clients", label: "clients" },
|
||
{ key: "promotion", label: "promotion" }, { key: "playground", label: "playground" },
|
||
];
|
||
|
||
export function CapabilityWorkspace({ onError, role = "viewer", targetDeploymentId }: { onError: (message: string | null) => void; role?: OperatorIdentity["role"]; targetDeploymentId?: string | null }) {
|
||
const sessionReference = operatorCredentialReference();
|
||
const sessionActive = Boolean(sessionReference) && role === "admin";
|
||
const generation = operatorSessionVersion();
|
||
const [view, setView] = useState<View>("overview");
|
||
const [deployments, setDeployments] = useState<CapabilityDeployment[]>([]);
|
||
const [contracts, setContracts] = useState<CapabilityContractRecord[]>([]);
|
||
const [estate, setEstate] = useState<CapabilityEstate[]>([]);
|
||
const [budgets, setBudgets] = useState<SchedulerBudget[]>([]);
|
||
const [profiles, setProfiles] = useState<RuntimeProfile[]>([]);
|
||
const [candidates, setCandidates] = useState<DeploymentCandidate[]>([]);
|
||
const [requests, setRequests] = useState<GatewayRequest[]>([]);
|
||
const [clients, setClients] = useState<ServiceClient[]>([]);
|
||
const [approvals, setApprovals] = useState<ProductionApproval[]>([]);
|
||
const [plans, setPlans] = useState<PlacementPlan[]>([]);
|
||
const [policy, setPolicy] = useState<SchedulerPolicy | null>(null);
|
||
const adminToken = sessionReference;
|
||
const adminUnlocked = sessionActive;
|
||
const [loading, setLoading] = useState(true);
|
||
const [refreshing, setRefreshing] = useState(false);
|
||
const [publicRead, setPublicRead] = useState<CapabilityReadState>({ kind: "loading" });
|
||
|
||
const publicRequest = useRef(0);
|
||
const publicSnapshotAt = useRef<number | null>(null);
|
||
const mounted = useRef(false);
|
||
useEffect(() => {
|
||
mounted.current = true;
|
||
return () => { mounted.current = false; publicRequest.current += 1; };
|
||
}, []);
|
||
|
||
const loadPublic = useCallback(async () => {
|
||
const request = ++publicRequest.current;
|
||
const session = operatorSessionVersion();
|
||
const current = () => mounted.current && request === publicRequest.current && session === operatorSessionVersion();
|
||
try {
|
||
const [nextDeployments, nextBudgets, nextCandidates, nextEstate, nextProfiles, nextContracts] = await Promise.all([
|
||
api.capabilityDeployments(), api.scheduler(), api.deploymentCandidates(), api.capabilityEstate(),
|
||
api.runtimeProfiles(), api.capabilityContracts(),
|
||
]);
|
||
if (!current()) return;
|
||
setContracts(nextContracts);
|
||
setDeployments(nextDeployments);
|
||
setBudgets(nextBudgets);
|
||
setCandidates(nextCandidates);
|
||
setEstate(nextEstate);
|
||
setProfiles(nextProfiles);
|
||
const lastUpdatedAt = Date.now();
|
||
publicSnapshotAt.current = lastUpdatedAt;
|
||
setPublicRead({ kind: "fresh", lastUpdatedAt });
|
||
} catch (cause: unknown) {
|
||
if (!current()) return;
|
||
const failure = capabilityReadFailure(cause);
|
||
const lastUpdatedAt = publicSnapshotAt.current;
|
||
setPublicRead(lastUpdatedAt === null
|
||
? { kind: "unavailable", failure }
|
||
: { kind: "stale", failure, lastUpdatedAt });
|
||
}
|
||
}, []);
|
||
|
||
const loadAdmin = useCallback(async (token: string) => {
|
||
if (!sessionActive || generation !== operatorSessionVersion()) return;
|
||
const [nextRequests, nextClients, nextApprovals, nextPlans, nextPolicy] = await Promise.all([
|
||
api.gatewayRequests(token), api.serviceClients(token), api.productionApprovals(token),
|
||
api.placementHistory(token), api.schedulerPolicy(token),
|
||
]);
|
||
if (generation !== operatorSessionVersion()) return;
|
||
setRequests(nextRequests);
|
||
setClients(nextClients);
|
||
setApprovals(nextApprovals);
|
||
setPlans(nextPlans);
|
||
setPolicy(nextPolicy);
|
||
}, [sessionActive, generation]);
|
||
|
||
useEffect(() => {
|
||
Promise.all([loadPublic(), sessionActive ? loadAdmin(sessionReference) : Promise.resolve()])
|
||
.catch((cause: unknown) => onError(errorMessage(cause)))
|
||
.finally(() => setLoading(false));
|
||
}, [loadAdmin, loadPublic, onError, sessionActive, sessionReference]);
|
||
|
||
useEffect(() => {
|
||
const interval = window.setInterval(() => {
|
||
void loadPublic();
|
||
if (adminUnlocked) loadAdmin(adminToken).catch((cause: unknown) => onError(errorMessage(cause)));
|
||
}, 5000);
|
||
return () => window.clearInterval(interval);
|
||
}, [adminToken, adminUnlocked, loadAdmin, loadPublic, onError]);
|
||
|
||
const refresh = async () => {
|
||
onError(null);
|
||
setRefreshing(true);
|
||
try { await Promise.all([loadPublic(), sessionActive ? loadAdmin(adminToken) : Promise.resolve()]); }
|
||
catch (cause: unknown) { onError(errorMessage(cause)); }
|
||
finally { setRefreshing(false); }
|
||
};
|
||
if (loading || publicRead.kind === "loading") return <div className="state-card"><span className="state-spinner" />Loading capability serving state…</div>;
|
||
if (publicRead.kind === "unavailable") return <CapabilityReadNotice failure={publicRead.failure} onRetry={refresh} refreshing={refreshing} />;
|
||
return <div className="capability-stack">
|
||
{publicRead.kind === "stale" ? <CapabilityReadNotice failure={publicRead.failure} lastUpdatedAt={publicRead.lastUpdatedAt} onRetry={refresh} refreshing={refreshing} /> : null}
|
||
<section className="capability-hero panel">
|
||
<div><span className="eyebrow">MULTI-CAPABILITY ESTATE</span><h2>{contracts.length} declared contracts</h2><p>Capability-first routing across RAG, document, vision and audio · no client-visible model names</p></div>
|
||
<div className="capability-health"><strong>{deployments.length} deployment records</strong><span>Inspect health per exact binding below.</span></div>
|
||
</section>
|
||
<div className="capability-toolbar">
|
||
<TabList idPrefix="capability" label="Capability operations" className="capability-tabs" tabs={capabilityViews} active={view} onSelect={setView} />
|
||
<button className="icon-action" aria-label="Refresh capability state" onClick={refresh}><RefreshCw size={15} /></button>
|
||
</div>
|
||
<TabPanel idPrefix="capability" active={view} className="capability-tabpanel">
|
||
{view === "overview" ? <CapabilityDetails contracts={contracts} deployments={deployments} estate={estate} targetDeploymentId={targetDeploymentId} /> : null}
|
||
{view === "scheduler" ? <><SchedulerView budgets={budgets} deployments={deployments} plans={plans} policy={policy} token={adminToken} unlocked={adminUnlocked} onChanged={refresh} onError={onError} /><AdminBoundary unlocked={adminUnlocked}><></></AdminBoundary></> : null}
|
||
{view === "requests" ? <AdminBoundary unlocked={adminUnlocked}><RequestHistory requests={requests} /></AdminBoundary> : null}
|
||
{view === "clients" ? <AdminBoundary unlocked={adminUnlocked}><Clients clients={clients} token={adminToken} onChanged={() => loadAdmin(adminToken)} onError={onError} /></AdminBoundary> : null}
|
||
{view === "promotion" ? <AdminBoundary unlocked={adminUnlocked}><PromotionGate key={generation} deployments={deployments} candidates={candidates} profiles={profiles} approvals={approvals} token={adminToken} onChanged={refresh} /></AdminBoundary> : null}
|
||
{view === "playground" ? <Playground /> : null}
|
||
</TabPanel>
|
||
</div>;
|
||
}
|
||
|
||
function CapabilityReadNotice({ failure, lastUpdatedAt, onRetry, refreshing }: {
|
||
failure: CapabilityReadFailure;
|
||
lastUpdatedAt?: number;
|
||
onRetry: () => Promise<void>;
|
||
refreshing: boolean;
|
||
}) {
|
||
const hasSnapshot = lastUpdatedAt !== undefined;
|
||
const stateLabel = failure.kind === "offline" ? "OFFLINE" : hasSnapshot ? "STALE" : failure.kind.toUpperCase();
|
||
const stateClass = failure.kind === "offline" ? "offline" : hasSnapshot ? "stale" : failure.kind === "unknown" ? "unknown" : "error";
|
||
return <section className={hasSnapshot ? "panel capability-read-notice" : "error-banner capability-read-notice"} role={hasSnapshot ? "status" : "alert"} aria-labelledby="capability-read-notice-title">
|
||
<div><span className={`status-chip ${stateClass}`}>{stateLabel}</span><h2 id="capability-read-notice-title">{hasSnapshot ? "Capability data may be out of date" : "Capability serving state is unavailable"}</h2></div>
|
||
{hasSnapshot
|
||
? <p>The last successful snapshot was read {formatTime(new Date(lastUpdatedAt).toISOString())}. Counts below show that snapshot; the latest read failed.</p>
|
||
: <p>No authoritative snapshot was loaded, so contract and deployment totals are unknown. A failed read does not mean the estate is empty.</p>}
|
||
<div className="evidence-list">
|
||
<Evidence label="Read failure" value={failure.message} />
|
||
<Evidence label="Failure code" value={failure.code} />
|
||
<Evidence label="Correlation ID" value={failure.correlationId ?? "Not provided"} />
|
||
<Evidence label="Retryable" value={failure.retryable ? "Yes" : "No"} />
|
||
</div>
|
||
<p><strong>Next action:</strong> {failure.nextAction}</p>
|
||
<button type="button" onClick={() => void onRetry()} disabled={refreshing}>{refreshing ? "Retrying capability state…" : "Retry capability state"}</button>
|
||
</section>;
|
||
}
|
||
|
||
function SchedulerView({ budgets, deployments, plans, policy, token, unlocked, onChanged, onError }: { budgets: SchedulerBudget[]; deployments: CapabilityDeployment[]; plans: PlacementPlan[]; policy: SchedulerPolicy | null; token: string; unlocked: boolean; onChanged: () => Promise<void>; onError: (message: string | null) => void }) {
|
||
const [latest, setLatest] = useState<Record<string, PlacementPlan>>({});
|
||
const [pendingUnload, setPendingUnload] = useState<CapabilityDeployment | null>(null);
|
||
const [unloadAcknowledged, setUnloadAcknowledged] = useState(false);
|
||
const [unloadPending, setUnloadPending] = useState(false);
|
||
const [unloadError, setUnloadError] = useState<string | null>(null);
|
||
const [unloadOutcome, setUnloadOutcome] = useState<string | null>(null);
|
||
if (!budgets.length) return <div className="state-card">No accelerator telemetry is available for scheduling.</div>;
|
||
const simulate = async (deploymentId: string) => {
|
||
try { const plan = await api.dryRunPlacement(deploymentId, "interactive", token); setLatest((current) => ({ ...current, [deploymentId]: plan })); }
|
||
catch (cause: unknown) { onError(errorMessage(cause)); }
|
||
};
|
||
const previewUnload = (deployment: CapabilityDeployment) => {
|
||
setUnloadAcknowledged(false); setUnloadError(null); setUnloadOutcome(null); setPendingUnload(deployment);
|
||
};
|
||
const confirmUnload = async (deployment: CapabilityDeployment) => {
|
||
// Guarded against a replayed handler so one acknowledgement can never evict residency twice.
|
||
if (unloadPending) return;
|
||
setUnloadPending(true); setUnloadError(null);
|
||
try {
|
||
await api.unloadDeployment(deployment.id, token);
|
||
setPendingUnload(null); setUnloadAcknowledged(false);
|
||
setUnloadOutcome(`Residency for ${deployment.capability}@${deployment.contract_version} was unloaded.`);
|
||
await onChanged();
|
||
} catch (cause: unknown) { setUnloadError(errorMessage(cause)); }
|
||
finally { setUnloadPending(false); }
|
||
};
|
||
const changePolicy = async (deployment: CapabilityDeployment, value: string) => {
|
||
try { await api.updateResidencyPolicy(deployment.id, value, token); await onChanged(); }
|
||
catch (cause: unknown) { onError(errorMessage(cause)); }
|
||
};
|
||
const pauseLab = async () => {
|
||
try { await api.updateSchedulerPolicy(!Boolean(policy?.configuration.lab_paused), token); await onChanged(); }
|
||
catch (cause: unknown) { onError(errorMessage(cause)); }
|
||
};
|
||
return <div className="capability-stack"><section className="scheduler-grid">{budgets.map((budget) => {
|
||
const segments = [
|
||
["External / unmanaged", budget.external_vram_bytes], ["Safety reserve", budget.safety_reserve_bytes],
|
||
["ModelForge resident", budget.resident_vram_bytes], ["Active leases", budget.leased_vram_bytes],
|
||
["Schedulable free", budget.schedulable_free_vram_bytes],
|
||
] as const;
|
||
return <article className="panel scheduler-card" key={budget.accelerator_id}><div className="panel-header"><div><span className="eyebrow">GPU SCHEDULING COCKPIT · {budget.node_name}</span><h2>{budget.accelerator_name}</h2></div><span className={`status-chip ${budget.pressure_state === "NORMAL" ? "active" : "degraded"}`}>{budget.pressure_state}</span></div><div className="budget-total"><strong>{formatBytes(budget.total_vram_bytes)}</strong><span>Total VRAM · attribution {budget.attribution_confidence}</span></div><div className="budget-bar" aria-label="GPU capacity distribution">{segments.map(([label, bytes], index) => <span key={label} className={`budget-segment segment-${index}`} style={{ width: `${Math.min(100, (bytes / budget.total_vram_bytes) * 100)}%` }} title={`${label}: ${formatBytes(bytes)}`} />)}</div><div className="budget-legend">{segments.map(([label, bytes]) => <Evidence key={label} label={label} value={formatBytes(bytes)} />)}</div><small>Observed {formatBytes(budget.observed_used_vram_bytes)} · invariant delta {formatBytes(budget.invariant_delta_bytes)} · policy {budget.policy_revision} · telemetry {formatTime(budget.observed_at)}. External usage is observation-only and has no controls.</small></article>;
|
||
})}</section>{unlocked ? <><article className="panel evidence-list"><div className="panel-header"><div><span className="eyebrow">RESIDENCY MAP · WHAT CAN RUN NOW?</span><h2>Managed capability placements</h2></div><button onClick={pauseLab}>{Boolean(policy?.configuration.lab_paused) ? "Resume LAB" : "Pause LAB"}</button></div>{deployments.map((item) => { const plan = latest[item.id]; return <div className="client-row" key={item.id}><div><strong>{item.capability}@{item.contract_version} · {item.residency.state.toUpperCase()}</strong><span>{item.production ? "production" : "lab"} · {formatBytes(item.residency.measured_resident_vram_bytes)} · {item.residency.active_requests} active · cold load {formatMs(item.resource_envelope.cold_load_time_ms)}</span>{plan ? <span>{plan.verdict} · {plan.reason_codes.join(", ")} · after {formatBytes(plan.headroom_after_bytes)}{plan.evictions.length ? ` · evict ${plan.evictions.map((entry) => entry.capability).join(", ")}` : ""}</span> : null}</div><div className="promotion-actions"><button onClick={() => simulate(item.id)}>Dry run</button><select aria-label={`Residency policy for ${item.capability}`} value={item.residency_policy} onChange={(event) => changePolicy(item, event.target.value)}><option value="always_warm">ALWAYS_WARM</option><option value="keep_warm">KEEP_WARM</option><option value="load_on_demand">LOAD_ON_DEMAND</option><option value="lab_only">LAB_ONLY</option></select><button className="danger" disabled={item.residency.active_requests > 0 || item.residency.state === "cold"} onClick={() => previewUnload(item)}>Unload idle</button></div></div>; })}</article><article className="panel evidence-list"><div className="panel-header"><div><span className="eyebrow">PLACEMENT HISTORY</span><h2>Recent bounded decisions</h2></div><CircleGauge size={19}/></div>{plans.slice(0, 20).map((plan) => <Evidence key={plan.id ?? plan.decision_fingerprint} label={`${plan.capability} · ${plan.verdict}`} value={`${plan.reason_codes.join(", ")} · ${formatBytes(plan.headroom_before_bytes)} → ${formatBytes(plan.headroom_after_bytes)} · ${formatTime(plan.created_at)}`}/>)}</article></> : null}
|
||
{unloadOutcome ? <p className="field-success" role="status">{unloadOutcome}</p> : null}
|
||
{pendingUnload ? <SafetyDialog
|
||
idPrefix="residency-unload"
|
||
eyebrow="DESTRUCTIVE ACTION PREVIEW"
|
||
title="Unload idle residency?"
|
||
subject={`${pendingUnload.capability}@${pendingUnload.contract_version}`}
|
||
description="ModelForge will evict this deployment from accelerator memory. Serving is not withdrawn: the next request reloads the model and pays the cold-load cost. Residency is unloaded only while no request is active."
|
||
facts={[
|
||
{ label: "Channel", value: pendingUnload.production ? "production" : "lab" },
|
||
{ label: "Residency state", value: pendingUnload.residency.state },
|
||
{ label: "Active requests", value: String(pendingUnload.residency.active_requests) },
|
||
{ label: "Resident VRAM released", value: formatBytes(pendingUnload.residency.measured_resident_vram_bytes) },
|
||
{ label: "Next cold load", value: formatMs(pendingUnload.resource_envelope.cold_load_time_ms) },
|
||
]}
|
||
acknowledgement="I understand the next request for this capability will pay the cold-load cost."
|
||
confirmLabel="Unload residency"
|
||
acknowledged={unloadAcknowledged}
|
||
onAcknowledgedChange={setUnloadAcknowledged}
|
||
pending={unloadPending}
|
||
errorTitle="Unload blocked"
|
||
error={unloadError}
|
||
closeLabel="Close residency unload preview"
|
||
onCancel={() => setPendingUnload(null)}
|
||
onConfirm={() => void confirmUnload(pendingUnload)}
|
||
/> : null}</div>;
|
||
}
|
||
|
||
function RequestHistory({ requests }: { requests: GatewayRequest[] }) {
|
||
if (!requests.length) return <div className="state-card">No gateway request metadata exists yet. Input text is never displayed or persisted.</div>;
|
||
return <article className="panel table-panel"><div className="panel-header"><div><span className="eyebrow">PRIVACY-PRESERVING HISTORY</span><h2>Gateway requests</h2></div><Clock3 size={19}/></div><div className="operation-table"><div className="table-head"><span>Request</span><span>Client</span><span>State</span><span>Path</span><span>Queue</span><span>Total</span><span>Time</span></div>{requests.map((request) => <div className="table-row" key={request.request_id}><code>{request.request_id.slice(0, 8)}…</code><span>{request.client ?? "rejected identity"}</span><span className={`registry-status ${request.status}`}>{request.failure_code ?? request.status}</span><span>{request.cold == null ? "—" : request.cold ? "cold" : "warm"}</span><span>{formatMs(request.queue_time_ms)}</span><span>{formatMs(request.total_latency_ms)}</span><span>{formatTime(request.created_at)}</span></div>)}</div></article>;
|
||
}
|
||
|
||
function Clients({ clients, token, onChanged, onError }: { clients: ServiceClient[]; token: string; onChanged: () => Promise<void>; onError: (message: string | null) => void }) {
|
||
const [name, setName] = useState("modelforge-m5-acceptance");
|
||
const [rpm, setRpm] = useState(60);
|
||
const [created, setCreated] = useState<ServiceClientCreated | null>(null);
|
||
const submit = async (event: FormEvent) => {
|
||
event.preventDefault(); onError(null);
|
||
try { setCreated(await api.createServiceClient({ name, allowed_capabilities: ["rag.embedding@1"], requests_per_minute: rpm, max_concurrent_requests: 1 }, token)); await onChanged(); }
|
||
catch (cause: unknown) { onError(errorMessage(cause)); }
|
||
};
|
||
const revoke = async (clientId: string) => {
|
||
onError(null);
|
||
try { await api.revokeServiceCredential(clientId, token); await onChanged(); }
|
||
catch (cause: unknown) { onError(errorMessage(cause)); }
|
||
};
|
||
return <section className="grid-two"><form className="panel client-form" onSubmit={submit}><span className="eyebrow">SCOPED IDENTITY</span><h2>Create service client</h2><label>Name<input required pattern="[a-z][a-z0-9-]*" value={name} onChange={(event) => setName(event.target.value)} /></label><label>Requests per minute<input type="number" min="1" max="10000" value={rpm} onChange={(event) => setRpm(Number(event.target.value))} /></label><p>Scope is fixed to <code>rag.embedding@1</code>; concurrency starts conservatively at one.</p><button type="submit"><KeyRound size={15}/>Create one-time credential</button>{created ? <div className="one-time-secret"><strong>Copy this credential now</strong><code>{created.credential}</code><small>Only its hash is stored. It cannot be retrieved again.</small></div> : null}</form><article className="panel evidence-list"><div className="panel-header"><div><span className="eyebrow">CLIENTS</span><h2>Authorized consumers</h2></div><KeyRound size={19}/></div>{clients.length ? clients.map((client) => <div className="client-row" key={client.id}><div><strong>{client.name}</strong><span>{client.allowed_capabilities.join(", ")} · {client.requests_per_minute}/min · last used {formatTime(client.last_used_at)}</span></div><button className="danger" disabled={client.status !== "active"} onClick={() => revoke(client.id)}>{client.status === "active" ? "Revoke" : "Revoked"}</button></div>) : <p>No service clients created.</p>}</article></section>;
|
||
}
|
||
|
||
type ModalityResult = GatewayInvokeResponse | OCRInvokeResponse | VisionEmbeddingResponse | SpeechTranscriptionResponse;
|
||
|
||
async function fileBase64(file: File): Promise<string> {
|
||
const bytes = new Uint8Array(await file.arrayBuffer());
|
||
let binary = "";
|
||
for (const byte of bytes) binary += String.fromCharCode(byte);
|
||
return btoa(binary);
|
||
}
|
||
|
||
function Playground() {
|
||
const [capability, setCapability] = useState<"rag.embedding" | "document.ocr" | "vision.embedding" | "speech.transcription">("rag.embedding");
|
||
const [credential, setCredential] = useState("");
|
||
const [input, setInput] = useState("ModelForge capability gateway test");
|
||
const [file, setFile] = useState<File | null>(null);
|
||
const [result, setResult] = useState<ModalityResult | null>(null);
|
||
const [failure, setFailure] = useState<string | null>(null);
|
||
const [running, setRunning] = useState(false);
|
||
const invoke = async (event: FormEvent) => {
|
||
event.preventDefault(); setFailure(null); setRunning(true);
|
||
try {
|
||
if (capability === "rag.embedding") setResult(await api.invokeEmbedding(input, credential));
|
||
else {
|
||
if (!file) throw new Error("Choose a local fixture first");
|
||
const encoded = await fileBase64(file);
|
||
if (capability === "document.ocr") {
|
||
if (file.type !== "image/png" && file.type !== "image/jpeg") throw new Error("OCR accepts PNG or JPEG");
|
||
setResult(await api.invokeOcr(encoded, file.type, credential));
|
||
} else if (capability === "vision.embedding") {
|
||
if (file.type !== "image/png" && file.type !== "image/jpeg") throw new Error("Vision accepts PNG or JPEG");
|
||
setResult(await api.invokeVisionEmbedding(encoded, file.type, credential));
|
||
} else {
|
||
if (file.type !== "audio/wav" && !file.name.toLowerCase().endsWith(".wav")) throw new Error("Transcription accepts WAV");
|
||
setResult(await api.invokeTranscription(encoded, credential));
|
||
}
|
||
}
|
||
}
|
||
catch (cause: unknown) { setFailure(errorMessage(cause)); }
|
||
finally { setRunning(false); }
|
||
};
|
||
const output = result ? ("data" in result ? `${result.data.length} × ${result.dimension} finite values` : "text" in result ? result.text || "valid empty text" : "typed output") : "";
|
||
return <form className="panel playground" onSubmit={invoke}><div className="panel-header"><div><span className="eyebrow">REAL GATEWAY PATH</span><h2>Capability playground</h2></div><Play size={20}/></div><p>The client selects a capability contract. No model name, path, or runtime engine is accepted here.</p><label>Capability<select value={capability} onChange={(event) => { setCapability(event.target.value as typeof capability); setResult(null); }}><option value="rag.embedding">rag.embedding@1</option><option value="document.ocr">document.ocr@1</option><option value="vision.embedding">vision.embedding@1</option><option value="speech.transcription">speech.transcription@1</option></select></label><label>Service credential<input required type="password" autoComplete="off" value={credential} onChange={(event) => setCredential(event.target.value)} /></label>{capability === "rag.embedding" ? <label>Input text<textarea required maxLength={8192} value={input} onChange={(event) => setInput(event.target.value)} /></label> : <label>{capability === "speech.transcription" ? "WAV audio" : "PNG or JPEG image"}<input required type="file" accept={capability === "speech.transcription" ? ".wav,audio/wav" : "image/png,image/jpeg"} onChange={(event) => setFile(event.target.files?.[0] ?? null)} /></label>}<button disabled={running}>{running ? "Invoking…" : "Invoke capability"}</button>{failure ? <div className="error-banner"><strong>Typed gateway failure</strong><span>{failure}</span></div> : null}{result ? <div className="playground-result"><span className={`registry-status ${result.execution.cold ? "candidate" : "ready"}`}>{result.execution.cold ? "cold" : "warm"}</span><Evidence label="Request ID" value={result.request_id}/><Evidence label="Output" value={output}/>{"embedding_space_id" in result ? <Evidence label="Embedding space" value={result.embedding_space_id}/> : null}<Evidence label="Total / inference" value={`${formatMs(result.execution.timings.total_ms)} / ${formatMs(result.execution.timings.inference_ms)}`}/></div> : null}</form>;
|
||
}
|
||
|
||
function AdminBoundary({ unlocked, children }: { unlocked: boolean; children: ReactNode }) {
|
||
return unlocked ? <>{children}</> : <div className="state-card">Read-only access. Administrative operations require an active admin Console session.</div>;
|
||
}
|
||
|
||
function Evidence({ label, value }: { label: string; value: string }) { return <div className="evidence-row"><span>{label}</span><strong title={value}>{value}</strong></div>; }
|
||
function formatBytes(value: number): string { return `${(value / 1024 ** 3).toFixed(2)} GiB`; }
|
||
function formatMs(value?: number | null): string { return value == null ? "—" : `${value.toFixed(1)} ms`; }
|
||
function formatTime(value?: string | null): string { return value ? new Date(value).toLocaleString() : "never"; }
|
||
function capabilityReadFailure(cause: unknown): CapabilityReadFailure {
|
||
const apiFailure = cause instanceof ApiError ? cause : null;
|
||
const offline = apiFailure === null && ((cause instanceof TypeError) || (typeof navigator !== "undefined" && navigator.onLine === false));
|
||
const kind: CapabilityReadFailure["kind"] = offline ? "offline" : apiFailure ? "error" : "unknown";
|
||
const failureCode = apiFailure?.details.failure_code;
|
||
const code = typeof failureCode === "string" ? failureCode : apiFailure?.code ?? (apiFailure ? `HTTP_${apiFailure.status}` : offline ? "NETWORK_UNAVAILABLE" : "UNKNOWN_ERROR");
|
||
const retryable = apiFailure ? apiFailure.status === 408 || apiFailure.status === 429 || apiFailure.status >= 500 : offline;
|
||
const rawMessage = cause instanceof Error ? cause.message : "Capability state could not be read";
|
||
const message = rawMessage.replace(/\s+\(correlation [^)]+\)$/, "");
|
||
const nextAction = offline
|
||
? "Restore the connection to the ModelForge API, then retry this read."
|
||
: apiFailure?.status === 401 || apiFailure?.status === 403
|
||
? "Check the active Console identity and its read access, then retry."
|
||
: retryable
|
||
? "Retry this read. If it continues to fail, share the failure code and correlation ID with the operator team."
|
||
: "Review the API response and Console access, then retry after addressing the reported failure.";
|
||
return { kind, message, code, correlationId: apiFailure?.correlationId ?? null, retryable, nextAction };
|
||
}
|
||
function errorMessage(cause: unknown): string { return cause instanceof ApiError ? `${cause.message}${typeof cause.details.failure_code === "string" ? ` (${cause.details.failure_code})` : ""}` : cause instanceof Error ? cause.message : "Capability operation failed"; }
|