mirror of
https://github.com/block/buzz.git
synced 2026-11-01 06:51:17 +00:00
compute view polish and leaderboard
Signed-off-by: Alessandro Joabar <sandro@squareup.com>
This commit is contained in:
@@ -90,6 +90,7 @@ export function CommunityComputeTerritoryMap({
|
||||
const hoveredDeployment = hoveredId
|
||||
? (deploymentById.get(hoveredId) ?? null)
|
||||
: null;
|
||||
const isHeroDeployment = model.deployments.length === 1;
|
||||
|
||||
function selectDeployment(deploymentId: string) {
|
||||
if (selectedDeploymentId === undefined) {
|
||||
@@ -193,6 +194,12 @@ export function CommunityComputeTerritoryMap({
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
{isHeroDeployment && activeDeployment ? (
|
||||
<HeroDeploymentOverlay
|
||||
deployment={activeDeployment}
|
||||
inferenceActive={inferenceIds.has(activeDeployment.id)}
|
||||
/>
|
||||
) : null}
|
||||
{hoveredDeployment ? (
|
||||
<div
|
||||
className="pointer-events-none absolute bottom-3 left-3 max-w-64 rounded-xl border border-border/70 bg-popover/95 px-3 py-2 shadow-lg backdrop-blur-sm"
|
||||
@@ -235,6 +242,69 @@ export function CommunityComputeTerritoryMap({
|
||||
);
|
||||
}
|
||||
|
||||
function HeroDeploymentOverlay({
|
||||
deployment,
|
||||
inferenceActive,
|
||||
}: {
|
||||
deployment: CommunityComputeDeployment;
|
||||
inferenceActive: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className="pointer-events-none absolute left-1/2 top-1/2 w-[min(19rem,calc(100%-2rem))] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-background/20 bg-background/90 p-3 text-center shadow-xl backdrop-blur-md"
|
||||
data-testid="community-compute-hero-deployment"
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"size-2 rounded-full",
|
||||
deployment.health === "healthy"
|
||||
? "bg-emerald-500"
|
||||
: "bg-muted-foreground",
|
||||
)}
|
||||
/>
|
||||
<span className="text-2xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
{inferenceActive ? "Serving now" : deployment.health}
|
||||
</span>
|
||||
</div>
|
||||
<h3
|
||||
className="mt-1 truncate text-sm font-semibold"
|
||||
title={deployment.modelId}
|
||||
>
|
||||
{shortModelName(deployment.modelId)}
|
||||
</h3>
|
||||
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
||||
{deployment.deviceLabel}
|
||||
{deployment.isSelf ? " · This machine" : ""}
|
||||
</p>
|
||||
<dl className="mt-2 grid grid-cols-3 divide-x divide-border/60 rounded-lg border border-border/60 bg-background/70 py-2">
|
||||
<HeroMetric
|
||||
label="Shared memory"
|
||||
value={formatCapacity(deployment.capacityGb)}
|
||||
/>
|
||||
<HeroMetric
|
||||
label="Model footprint"
|
||||
value={formatCapacity(deployment.source.modelSizeGb ?? null)}
|
||||
/>
|
||||
<HeroMetric label="Availability" value="Community ready" />
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HeroMetric({ label, value }: { label: string; value: string }) {
|
||||
return (
|
||||
<div className="min-w-0 px-2">
|
||||
<dt className="truncate text-3xs uppercase tracking-wide text-muted-foreground">
|
||||
{label}
|
||||
</dt>
|
||||
<dd className="mt-0.5 truncate text-2xs font-semibold" title={value}>
|
||||
{value}
|
||||
</dd>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TerritoryShape({
|
||||
cells,
|
||||
inferenceActive,
|
||||
|
||||
@@ -67,10 +67,15 @@ export function MeshComputeCommunityView({
|
||||
<div>
|
||||
<div className="mb-2 flex items-end justify-between gap-3">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold tracking-tight">Model mesh</h2>
|
||||
<h2 className="text-lg font-semibold tracking-tight">
|
||||
{model.deployments.length === 1
|
||||
? "Your community’s first compute node"
|
||||
: "Model mesh"}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Each territory is one ready model on one device. Area reflects its
|
||||
reported model-memory tier.
|
||||
{model.deployments.length === 1
|
||||
? "This machine is the foundation of the mesh. More contributors add capacity, model choice, and resilience."
|
||||
: "Each territory is one ready model on one device. Area reflects its reported model-memory tier."}
|
||||
</p>
|
||||
</div>
|
||||
{!simulated ? (
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { Activity, Cpu, Network, Sparkles } from "lucide-react";
|
||||
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import type { useMeshComputeState } from "../hooks/useMeshComputeState";
|
||||
|
||||
export function MeshComputeImpactReceipt({
|
||||
mesh,
|
||||
}: {
|
||||
mesh: ReturnType<typeof useMeshComputeState>;
|
||||
}) {
|
||||
const { memory, toggle, usage } = mesh;
|
||||
const isSharing = toggle.isSharing;
|
||||
const tokensProcessed = usage?.tokensServed ?? 0;
|
||||
const peers = usage?.peers ?? 0;
|
||||
const capacity = memory.totalGb == null ? "—" : formatGb(memory.totalGb);
|
||||
|
||||
const items = [
|
||||
{
|
||||
icon: Cpu,
|
||||
label: "Capacity offered",
|
||||
value: isSharing ? capacity : "Not sharing",
|
||||
},
|
||||
{
|
||||
icon: Sparkles,
|
||||
label: "Tokens served",
|
||||
value: isSharing ? formatCount(tokensProcessed) : "—",
|
||||
},
|
||||
{
|
||||
icon: Network,
|
||||
label: "Mesh peers",
|
||||
value: isSharing ? formatCount(peers) : "—",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"overflow-hidden rounded-2xl border border-border/70 bg-background",
|
||||
!isSharing && "bg-muted/10",
|
||||
)}
|
||||
data-testid="compute-impact-receipt"
|
||||
>
|
||||
<div className="flex items-start gap-3 border-border/60 border-b px-4 py-3">
|
||||
<span className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-foreground text-background">
|
||||
<Activity className="size-4" />
|
||||
</span>
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold">Your contribution</h2>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
||||
{isSharing
|
||||
? usage?.inflight
|
||||
? `Helping run ${usage.inflight} request${usage.inflight === 1 ? "" : "s"} right now.`
|
||||
: "Available when your community needs more compute."
|
||||
: "Turn on automatic contribution to start building your impact."}
|
||||
</p>
|
||||
</div>
|
||||
{isSharing ? (
|
||||
<span className="ml-auto flex shrink-0 items-center gap-1.5 text-xs font-medium">
|
||||
<span className="size-2 rounded-full bg-emerald-500" /> Live
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<dl className="grid grid-cols-3 divide-x divide-border/60">
|
||||
{items.map(({ icon: Icon, label, value }) => (
|
||||
<div className="min-w-0 px-4 py-3" key={label}>
|
||||
<dt className="flex items-center gap-1.5 truncate text-2xs text-muted-foreground">
|
||||
<Icon className="size-3" /> {label}
|
||||
</dt>
|
||||
<dd className="mt-1 truncate text-lg font-semibold tabular-nums">
|
||||
{value}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
<p className="border-border/60 border-t px-4 py-2 text-2xs text-muted-foreground">
|
||||
Session totals from this machine. Request attribution to other members
|
||||
is coming soon.
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function formatCount(value: number): string {
|
||||
return new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 }).format(
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
function formatGb(value: number): string {
|
||||
return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(value)} GB`;
|
||||
}
|
||||
@@ -12,8 +12,10 @@ import {
|
||||
import { useMeshComputeState } from "../hooks/useMeshComputeState";
|
||||
import { CommunityComputeTutorialPanel } from "./CommunityComputeTutorialPanel";
|
||||
import { MeshComputeCommunityView } from "./MeshComputeCommunityView";
|
||||
import { MeshComputeImpactReceipt } from "./MeshComputeImpactReceipt";
|
||||
import { MeshComputeSettingsCard } from "./MeshComputeSettingsCard";
|
||||
import { MeshComputeShareBanner } from "./MeshComputeShareBanner";
|
||||
import { MeshComputeTokenLeaderboard } from "./MeshComputeTokenLeaderboard";
|
||||
|
||||
export function MeshComputePage() {
|
||||
const { activeCommunity } = useCommunities();
|
||||
@@ -52,7 +54,7 @@ export function MeshComputePage() {
|
||||
<BookOpen /> Tutorial
|
||||
</Button>
|
||||
}
|
||||
description={`See what ${communityName} can run and contribute this machine’s spare capacity.`}
|
||||
description={`Contribute this machine’s spare capacity to unlock more models and keep ${communityName}’s agents available when demand grows.`}
|
||||
title="Compute"
|
||||
/>
|
||||
|
||||
@@ -80,7 +82,10 @@ export function MeshComputePage() {
|
||||
onStepChange={setTutorialStep}
|
||||
step={tutorialStep}
|
||||
/>
|
||||
) : null}
|
||||
) : (
|
||||
<MeshComputeImpactReceipt mesh={mesh} />
|
||||
)}
|
||||
<MeshComputeTokenLeaderboard mesh={mesh} simulated={tutorialOpen} />
|
||||
<MeshComputeCommunityView
|
||||
communityName={
|
||||
tutorialOpen ? tutorialFixture.title : communityName
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Cpu, LoaderCircle } from "lucide-react";
|
||||
import { Cpu, LoaderCircle, ShieldCheck, Sparkles, Zap } from "lucide-react";
|
||||
|
||||
import { Switch } from "@/shared/ui/switch";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
@@ -13,6 +13,7 @@ export function MeshComputeShareBanner({
|
||||
}) {
|
||||
const {
|
||||
error,
|
||||
memory,
|
||||
modelToShare,
|
||||
pendingAction,
|
||||
shareSwitchChecked,
|
||||
@@ -21,6 +22,8 @@ export function MeshComputeShareBanner({
|
||||
setSharing,
|
||||
} = mesh;
|
||||
const busy = pendingAction !== null;
|
||||
const model = status?.modelName ?? status?.modelId ?? modelToShare;
|
||||
const capacity = memory.totalGb == null ? null : formatGb(memory.totalGb);
|
||||
const statusCopy = shareStatusCopy({
|
||||
communityName,
|
||||
modelToShare,
|
||||
@@ -32,14 +35,14 @@ export function MeshComputeShareBanner({
|
||||
|
||||
return (
|
||||
<section
|
||||
className="rounded-2xl border border-border/70 bg-muted/15 px-4 py-3.5"
|
||||
className="overflow-hidden rounded-2xl border border-foreground/15 bg-linear-to-br from-foreground/[0.07] to-muted/10"
|
||||
data-testid="compute-share-banner"
|
||||
>
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<div className="flex min-w-0 items-start gap-3 px-4 py-4">
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
"flex size-9 shrink-0 items-center justify-center rounded-xl border border-border/70 bg-background",
|
||||
"flex size-10 shrink-0 items-center justify-center rounded-xl border border-border/70 bg-background",
|
||||
toggle.isSharing && "border-foreground/30",
|
||||
)}
|
||||
>
|
||||
@@ -51,17 +54,14 @@ export function MeshComputeShareBanner({
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<label
|
||||
className="text-sm font-semibold"
|
||||
className="text-base font-semibold"
|
||||
htmlFor="compute-share-banner-toggle"
|
||||
>
|
||||
Share this machine
|
||||
Automatic contribution
|
||||
</label>
|
||||
<p
|
||||
className="mt-0.5 truncate text-xs text-muted-foreground"
|
||||
data-testid="compute-share-banner-status"
|
||||
title={statusCopy}
|
||||
>
|
||||
{statusCopy}
|
||||
<p className="mt-1 max-w-2xl text-sm leading-relaxed text-muted-foreground">
|
||||
Let Buzz choose the most useful compatible model and make this
|
||||
machine available when {communityName} needs it.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -72,15 +72,73 @@ export function MeshComputeShareBanner({
|
||||
onCheckedChange={setSharing}
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p className="mt-2 text-xs text-destructive" role="alert">
|
||||
{error}
|
||||
|
||||
<div className="grid gap-px border-border/60 border-t bg-border/60 sm:grid-cols-3">
|
||||
<Benefit
|
||||
icon={Sparkles}
|
||||
label="Unlock community models"
|
||||
detail={
|
||||
model ? `Recommended: ${model}` : "Hardware-aware model selection"
|
||||
}
|
||||
/>
|
||||
<Benefit
|
||||
icon={Zap}
|
||||
label="Increase availability"
|
||||
detail={
|
||||
capacity
|
||||
? `Offer up to ${capacity}`
|
||||
: "Uses compatible spare capacity"
|
||||
}
|
||||
/>
|
||||
<Benefit
|
||||
icon={ShieldCheck}
|
||||
label="You stay in control"
|
||||
detail="Pause anytime; advanced limits in Settings"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="border-border/60 border-t bg-background/60 px-4 py-2.5">
|
||||
<p
|
||||
className="text-xs text-muted-foreground"
|
||||
data-testid="compute-share-banner-status"
|
||||
>
|
||||
{statusCopy}
|
||||
</p>
|
||||
) : null}
|
||||
{error ? (
|
||||
<p className="mt-1 text-xs text-destructive" role="alert">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Benefit({
|
||||
icon: Icon,
|
||||
label,
|
||||
detail,
|
||||
}: {
|
||||
icon: typeof Sparkles;
|
||||
label: string;
|
||||
detail: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-start gap-2 bg-background/80 px-4 py-3">
|
||||
<Icon className="mt-0.5 size-3.5 shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-medium">{label}</p>
|
||||
<p
|
||||
className="mt-0.5 truncate text-2xs text-muted-foreground"
|
||||
title={detail}
|
||||
>
|
||||
{detail}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function shareStatusCopy({
|
||||
communityName,
|
||||
isConsuming,
|
||||
@@ -96,18 +154,18 @@ function shareStatusCopy({
|
||||
pendingAction: "start" | "stop" | null;
|
||||
statusModel: string | null;
|
||||
}): string {
|
||||
if (pendingAction === "start") {
|
||||
if (pendingAction === "start")
|
||||
return `Preparing ${statusModel ?? modelToShare ?? "a model"} for ${communityName}…`;
|
||||
}
|
||||
if (pendingAction === "stop") return "Stopping shared compute…";
|
||||
if (isSharing) {
|
||||
if (isSharing)
|
||||
return `${statusModel ?? modelToShare ?? "A model"} is available to ${communityName}`;
|
||||
}
|
||||
if (isConsuming) {
|
||||
return "Using another member’s compute; turn this on to contribute instead";
|
||||
}
|
||||
if (modelToShare) {
|
||||
return `${modelToShare} is ready to contribute to ${communityName}`;
|
||||
}
|
||||
return "Choose a model in Settings before sharing";
|
||||
if (isConsuming)
|
||||
return "Using another member’s compute; enable this to contribute instead";
|
||||
if (modelToShare)
|
||||
return "Ready to contribute automatically when you turn this on";
|
||||
return "Checking this machine and choosing a compatible model…";
|
||||
}
|
||||
|
||||
function formatGb(value: number): string {
|
||||
return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(value)} GB`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Medal, Trophy } from "lucide-react";
|
||||
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import type { useMeshComputeState } from "../hooks/useMeshComputeState";
|
||||
|
||||
const PREVIEW_LEADERS = [
|
||||
{ name: "Maya", tokens: 2_840_912 },
|
||||
{ name: "Jon", tokens: 1_967_401 },
|
||||
{ name: "Priya", tokens: 1_204_885 },
|
||||
] as const;
|
||||
|
||||
export function MeshComputeTokenLeaderboard({
|
||||
mesh,
|
||||
simulated = false,
|
||||
}: {
|
||||
mesh: ReturnType<typeof useMeshComputeState>;
|
||||
simulated?: boolean;
|
||||
}) {
|
||||
const yourTokens = simulated ? 1_638_240 : (mesh.usage?.tokensServed ?? 0);
|
||||
const communityLeaders = simulated ? PREVIEW_LEADERS : [];
|
||||
const rows = [...communityLeaders, { name: "You", tokens: yourTokens }]
|
||||
.sort((a, b) => b.tokens - a.tokens)
|
||||
.map((entry, index) => ({ ...entry, rank: index + 1 }));
|
||||
const yourRank =
|
||||
rows.find((entry) => entry.name === "You")?.rank ?? rows.length;
|
||||
|
||||
return (
|
||||
<section
|
||||
className="overflow-hidden rounded-2xl border border-border/70 bg-background"
|
||||
data-testid="compute-token-leaderboard"
|
||||
>
|
||||
<div className="flex items-start gap-3 border-border/60 border-b px-4 py-3.5">
|
||||
<span className="flex size-9 shrink-0 items-center justify-center rounded-xl bg-amber-500/15 text-amber-700 dark:text-amber-300">
|
||||
<Trophy className="size-4" />
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="text-sm font-semibold">
|
||||
Community token contributors
|
||||
</h2>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
||||
{simulated
|
||||
? "Tokenmaxxing, inverted: celebrate the tokens you serve—not the tokens you consume."
|
||||
: "Your locally reported total. Community rankings will appear when aggregate reporting is available."}
|
||||
</p>
|
||||
</div>
|
||||
{simulated ? (
|
||||
<div className="shrink-0 text-right">
|
||||
<p className="text-2xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
Your rank
|
||||
</p>
|
||||
<p className="mt-0.5 text-lg font-semibold tabular-nums">
|
||||
#{yourRank}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<ol className="divide-y divide-border/60">
|
||||
{rows.map((entry) => (
|
||||
<li
|
||||
className={cn(
|
||||
"grid grid-cols-[2.5rem_1fr_auto] items-center gap-2 px-4 py-2.5",
|
||||
entry.name === "You" && "bg-foreground/[0.045]",
|
||||
)}
|
||||
key={entry.name}
|
||||
>
|
||||
<span className="flex items-center gap-1 text-xs font-semibold tabular-nums text-muted-foreground">
|
||||
{entry.rank <= 3 ? <Medal className="size-3.5" /> : null}#
|
||||
{entry.rank}
|
||||
</span>
|
||||
<span className="truncate text-sm font-medium">
|
||||
{entry.name}
|
||||
{entry.name === "You" ? (
|
||||
<span className="ml-2 rounded-full bg-foreground px-2 py-0.5 text-3xs font-semibold text-background">
|
||||
YOU
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
<span className="text-sm font-semibold tabular-nums">
|
||||
{formatTokens(entry.tokens)}
|
||||
<span className="ml-1 text-2xs font-normal text-muted-foreground">
|
||||
tokens
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
|
||||
<p className="border-border/60 border-t bg-muted/10 px-4 py-2 text-2xs text-muted-foreground">
|
||||
{simulated
|
||||
? "Tutorial preview · example contribution totals"
|
||||
: "Community rankings preview · aggregate contribution reporting is coming soon."}
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function formatTokens(value: number): string {
|
||||
return new Intl.NumberFormat(undefined, {
|
||||
notation: value >= 10_000 ? "compact" : "standard",
|
||||
maximumFractionDigits: 1,
|
||||
}).format(value);
|
||||
}
|
||||
Reference in New Issue
Block a user