Simplify shared compute settings experience

Signed-off-by: Alessandro Joabar <sandro@squareup.com>
This commit is contained in:
Alessandro Joabar
2026-08-27 12:23:42 -04:00
parent 00b4866e83
commit 0e60a22734
13 changed files with 118 additions and 1498 deletions

View File

@@ -1,59 +0,0 @@
import assert from "node:assert/strict";
import test from "node:test";
import { classifyModelRef } from "./classifyModelRef.ts";
test("empty string → unknown", () => {
assert.deepEqual(classifyModelRef(""), { kind: "unknown" });
assert.deepEqual(classifyModelRef(" "), { kind: "unknown" });
});
test("hf:// prefix → huggingface", () => {
assert.deepEqual(classifyModelRef("hf://meshllm/qwen3-8b@main"), {
kind: "huggingface",
ref: "hf://meshllm/qwen3-8b@main",
});
});
test("absolute path → local-path", () => {
assert.deepEqual(classifyModelRef("/Users/me/models/qwen.gguf"), {
kind: "local-path",
path: "/Users/me/models/qwen.gguf",
});
});
test("relative path with ./ → local-path", () => {
assert.deepEqual(classifyModelRef("./models/qwen.gguf"), {
kind: "local-path",
path: "./models/qwen.gguf",
});
});
test("home shortcut → local-path", () => {
assert.deepEqual(classifyModelRef("~/models/qwen.gguf"), {
kind: "local-path",
path: "~/models/qwen.gguf",
});
});
test(".gguf extension without path prefix → local-path", () => {
// Bare filename ending in .gguf — user clearly means a file.
assert.deepEqual(classifyModelRef("my-model.gguf"), {
kind: "local-path",
path: "my-model.gguf",
});
});
test("plain name → catalog", () => {
assert.deepEqual(classifyModelRef("Qwen3-8B-Q4_K_M"), {
kind: "catalog",
name: "Qwen3-8B-Q4_K_M",
});
});
test("trims whitespace before classifying", () => {
assert.deepEqual(classifyModelRef(" Qwen3-8B-Q4_K_M "), {
kind: "catalog",
name: "Qwen3-8B-Q4_K_M",
});
});

View File

@@ -1,43 +0,0 @@
/**
* Classification of a free-text model ref entered into the serve card.
* Mirrors mesh's own resolution categories for input validation.
*/
export type ModelRefKind =
| { kind: "catalog"; name: string }
| { kind: "huggingface"; ref: string }
| { kind: "local-path"; path: string }
| { kind: "unknown" };
/**
* Classify a model-ref string the way mesh-llm's runtime does:
* - `hf://…` → HuggingFace ref
* - starts with `/` or `./` or `~`, OR ends with `.gguf` → local file
* - otherwise non-empty → catalog name
* - empty/whitespace → unknown
*
* Source: mesh runtime/mod.rs:3390 ("local file, catalog name, or HuggingFace URL").
*
* This is validation-only — canonical resolution happens server-side via
* `mesh_start_node`.
*/
export function classifyModelRef(raw: string): ModelRefKind {
const trimmed = raw.trim();
if (trimmed.length === 0) {
return { kind: "unknown" };
}
if (trimmed.startsWith("hf://")) {
return { kind: "huggingface", ref: trimmed };
}
// Local path heuristics. Conservative: only mark as path when there are
// unambiguous signals (leading separator, home shortcut, .gguf extension).
const looksLikePath =
trimmed.startsWith("/") ||
trimmed.startsWith("./") ||
trimmed.startsWith("../") ||
trimmed.startsWith("~") ||
trimmed.toLowerCase().endsWith(".gguf");
if (looksLikePath) {
return { kind: "local-path", path: trimmed };
}
return { kind: "catalog", name: trimmed };
}

View File

@@ -1,119 +0,0 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import {
describeExperimentalEmpty,
describeSplitEntry,
deriveMeshExperimentalModel,
} from "./experimentalModel.ts";
function entry(overrides = {}) {
return {
name: "Model-Q4",
sizeLabel: "20GB",
sizeGb: 20,
layerCount: 64,
packageRepo: "meshllm/Model-layers",
fitsLocally: false,
description: null,
...overrides,
};
}
function catalog(overrides = {}) {
return { entries: [], vramGb: 96, stale: false, ...overrides };
}
test("only models beyond this machine are experimental", () => {
// A split-capable model that fits locally is not an experimental choice --
// Qwen3-8B is 5GB and split-capable, and mesh-llm returns SingleNodeFit for
// it before ever considering a split. Offering it here would tell someone a
// 5GB model needs a cluster.
const model = deriveMeshExperimentalModel(
catalog({
entries: [
entry({ name: "Fits", sizeGb: 5, fitsLocally: true }),
entry({ name: "Beyond", sizeGb: 300, fitsLocally: false }),
],
}),
);
assert.equal(model.options.length, 1);
assert.equal(model.options[0].entry.name, "Beyond");
});
test("a machine that fits everything is told so, not shown a failure", () => {
// The good outcome must not read like an error.
const model = deriveMeshExperimentalModel(
catalog({ entries: [entry({ fitsLocally: true })] }),
);
assert.equal(model.options.length, 0);
assert.equal(model.emptyReason, "machineFitsEverything");
assert.match(describeExperimentalEmpty(model.emptyReason), /on its own/);
});
test("empty has distinct causes that never read alike", () => {
// "Could not read the catalog", "the catalog has no packages", and "this
// machine is big enough" are three different facts.
assert.equal(deriveMeshExperimentalModel(undefined).emptyReason, "loading");
assert.equal(
deriveMeshExperimentalModel(null).emptyReason,
"catalogUnavailable",
);
assert.equal(
deriveMeshExperimentalModel(catalog()).emptyReason,
"noPackages",
);
assert.equal(
deriveMeshExperimentalModel(catalog({ stale: true })).emptyReason,
"catalogUnavailable",
);
const messages = new Set(
[
"loading",
"catalogUnavailable",
"noPackages",
"machineFitsEverything",
].map(describeExperimentalEmpty),
);
assert.equal(messages.size, 4, "each cause needs its own sentence");
});
test("detail states size and layers, and never invents either", () => {
assert.equal(describeSplitEntry(entry()), "20 GB · 64 layers");
// Two upstream entries publish "30 layers" where bytes belong. Layers are the
// unit actually distributed, so they stand alone; a size is never synthesized
// from a layer count, because they are not convertible.
assert.equal(
describeSplitEntry(
entry({ sizeGb: null, sizeLabel: "30 layers", layerCount: 30 }),
),
"30 layers",
);
// Neither figure published: fall back to the raw label rather than inventing.
assert.equal(
describeSplitEntry(
entry({ sizeGb: null, sizeLabel: "unknown", layerCount: null }),
),
"unknown",
);
});
test("a large model with no published size still reaches the list", () => {
// Unknown size is reported as not fitting, which is the honest direction:
// offering the split path is recoverable, promising a local start is not.
const model = deriveMeshExperimentalModel(
catalog({
entries: [
entry({
sizeGb: null,
sizeLabel: "61 layers",
layerCount: 61,
fitsLocally: false,
}),
],
}),
);
assert.equal(model.options.length, 1);
assert.equal(model.options[0].detail, "61 layers");
});

View File

@@ -1,133 +0,0 @@
import type {
MeshExperimentalCatalog,
MeshSplitEntry,
} from "@/shared/api/tauriMesh";
/**
* Pure projection for the experimental (split) tier of Advanced settings.
*
* ## Why "beyond this machine" is the partition
*
* Splittability alone is not the interesting fact. A model can carry a layer
* package and still fit on one machine — `Qwen3-8B-Q4_K_M` is 5 GB and
* split-capable — and mesh-llm agrees about precedence:
* `evaluate_model_target_capacity` returns `SingleNodeFit` first and only falls
* back to `SplitCandidate` when no single node can hold the model.
*
* So offering every split-capable model here would tell someone a 5 GB model
* needs a cluster. The experimental tier is the intersection: split-capable
* **and** beyond local memory. Anything that fits belongs in the ordinary
* picker, where it starts immediately.
*
* ## Why the consequence is stated before selection, not after
*
* A collective model does not load on demand. mesh-llm gathers a cohort over
* gossip and holds a settle barrier — at least two participants, membership
* unchanged for a dwell period — then elects a coordinator deterministically by
* VRAM. Until that forms, a node named for a collective model sits waiting.
*
* Nothing about that looks different from a slow download, so a person who was
* not told would reasonably read it as a hang and file a bug. The disclosure is
* therefore part of the option, not a footnote after the fact.
*/
export type MeshExperimentalOption = {
entry: MeshSplitEntry;
/** e.g. "134 GB · 80 layers", or "61 layers" when no byte size is published. */
detail: string;
};
export type MeshExperimentalModel = {
/** Split-capable models that exceed this machine — the experimental offer. */
options: MeshExperimentalOption[];
/**
* Why the list is empty, or null when it has entries.
*
* An empty list has three quite different causes and they must not read
* alike: the catalog could not be read, it holds no packages at all, or this
* machine is simply large enough that nothing needs splitting. The third is
* good news and must not look like a failure.
*/
emptyReason:
| "loading"
| "catalogUnavailable"
| "noPackages"
| "machineFitsEverything"
| null;
};
/** Bytes → GB label, matching the rest of the mesh surfaces. */
function formatGb(gb: number): string {
return `${gb >= 10 ? Math.round(gb) : Math.round(gb * 10) / 10} GB`;
}
/**
* One line describing reach: size where known, layers always.
*
* Layers are the unit actually distributed, so they are the honest figure when
* the catalog publishes a layer count in place of bytes (two upstream entries
* do). Never synthesizes a size from a layer count — they are not convertible.
*/
export function describeSplitEntry(entry: MeshSplitEntry): string {
const parts: string[] = [];
if (entry.sizeGb !== null) {
parts.push(formatGb(entry.sizeGb));
}
if (entry.layerCount !== null) {
parts.push(`${entry.layerCount} layers`);
}
if (parts.length === 0) {
// Neither figure published. Say nothing rather than invent a number.
return entry.sizeLabel ?? "";
}
return parts.join(" · ");
}
export function deriveMeshExperimentalModel(
catalog: MeshExperimentalCatalog | null | undefined,
): MeshExperimentalModel {
if (catalog === undefined) {
return { options: [], emptyReason: "loading" };
}
if (catalog === null) {
return { options: [], emptyReason: "catalogUnavailable" };
}
if (catalog.entries.length === 0) {
return {
options: [],
emptyReason: catalog.stale ? "catalogUnavailable" : "noPackages",
};
}
const beyondThisMachine = catalog.entries.filter(
(entry) => !entry.fitsLocally,
);
if (beyondThisMachine.length === 0) {
// Every split-capable model fits here. Not a failure — the machine is big
// enough that splitting buys nothing.
return { options: [], emptyReason: "machineFitsEverything" };
}
return {
options: beyondThisMachine.map((entry) => ({
entry,
detail: describeSplitEntry(entry),
})),
emptyReason: null,
};
}
/** Copy for an empty experimental tier, keyed on why. */
export function describeExperimentalEmpty(
reason: NonNullable<MeshExperimentalModel["emptyReason"]>,
): string {
switch (reason) {
case "loading":
return "Checking for models that can run across machines…";
case "catalogUnavailable":
return "Can't reach the model catalog right now.";
case "noPackages":
return "No models in the catalog can run across machines yet.";
case "machineFitsEverything":
// Deliberately not phrased as an absence: this is the good outcome.
return "This computer can run every model in the catalog on its own.";
}
}

View File

@@ -1,62 +0,0 @@
import * as React from "react";
import { meshExperimentalCatalog } from "@/shared/api/tauriMesh";
import type { MeshExperimentalCatalog } from "@/shared/api/tauriMesh";
/**
* Load the split-capable model catalog.
*
* Fetched once rather than polled: the community catalog is a published dataset
* that changes when someone packages a model, not live state. mesh-llm caches it
* on disk for 24h, so re-asking on a timer would add nothing.
*
* `undefined` while in flight and `null` on failure are deliberately distinct —
* the experimental tier has to tell "still looking" from "couldn't read the
* catalog" from "the catalog genuinely has no packages", and all three would
* otherwise render as an unexplained empty list.
*
* Requires no running node: you have to choose what to share before you start
* sharing it.
*/
export function useMeshExperimentalCatalog(enabled: boolean): {
catalog: MeshExperimentalCatalog | null | undefined;
error: string | null;
} {
const [catalog, setCatalog] = React.useState<
MeshExperimentalCatalog | null | undefined
>(undefined);
const [error, setError] = React.useState<string | null>(null);
const loaded = React.useRef(false);
React.useEffect(() => {
// Only when the section is actually opened: the first call can download the
// catalog, which is not work to do for a screen nobody expanded.
if (!enabled || loaded.current) return;
loaded.current = true;
let cancelled = false;
void (async () => {
try {
const next = await meshExperimentalCatalog();
if (!cancelled) {
setCatalog(next);
setError(null);
}
} catch (err) {
if (!cancelled) {
// null, not an empty catalog: "could not read" must not masquerade as
// "nothing published".
setCatalog(null);
setError(err instanceof Error ? err.message : String(err));
// Allow a retry on reopen — a transient network failure should not
// permanently empty the section.
loaded.current = false;
}
}
})();
return () => {
cancelled = true;
};
}, [enabled]);
return { catalog, error };
}

View File

@@ -22,8 +22,8 @@ describe("community compute visual components", () => {
);
assert.match(html, /community-compute-kpi-members-sharing/);
assert.match(html, /community-compute-kpi-contributed-vram/);
assert.match(html, /community-compute-kpi-vram-allocated/);
assert.match(html, /24 community members/);
assert.doesNotMatch(html, /community-compute-kpi-vram-allocated/);
assert.match(html, /24 people in this community/);
assert.match(html, />2,744 GB</);
});

View File

@@ -6,39 +6,33 @@ export type CommunityComputeKpiRowProps = {
className?: string;
};
/** Compact, responsive headline figures for the community compute pool. */
/** The two figures that help a member understand and grow the compute commons. */
export function CommunityComputeKpiRow({
kpis,
className,
}: CommunityComputeKpiRowProps) {
const items = [
{
label: "Members sharing",
label: "People contributing",
value: formatCount(kpis.contributorMemberCount),
detail: `${formatCount(kpis.memberCount)} community members`,
},
{
label: "Contributed VRAM",
value: formatCapacity(kpis.sharedCapacityGb),
detail: `${formatCount(kpis.deploymentCount)} models available`,
},
{
label: "VRAM allocated",
value:
kpis.allocatedPercent === null
? "—"
: `${Math.round(kpis.allocatedPercent)}%`,
detail:
kpis.allocatedCapacityGb === null
? "Not fully reported"
: `${formatCapacity(kpis.allocatedCapacityGb)} hosting models`,
kpis.contributorMemberCount === 0
? "Be the first contributor"
: `${formatCount(kpis.memberCount)} people in this community`,
testId: "members-sharing",
},
{
label: "Compute shared",
value: formatCapacity(kpis.sharedCapacityGb),
detail: "Available to community agents",
testId: "contributed-vram",
},
];
return (
<dl
className={cn(
"grid grid-cols-1 overflow-hidden rounded-xl border border-border/70 bg-muted/15 sm:grid-cols-3",
"grid grid-cols-1 overflow-hidden rounded-xl border border-border/70 bg-muted/15 sm:grid-cols-2",
className,
)}
data-testid="community-compute-kpis"
@@ -49,7 +43,7 @@ export function CommunityComputeKpiRow({
"min-w-0 px-3 py-2.5",
index > 0 && "border-border/60 border-t sm:border-l sm:border-t-0",
)}
data-testid={`community-compute-kpi-${kpiTestId(item.label)}`}
data-testid={`community-compute-kpi-${item.testId}`}
key={item.label}
>
<dt className="truncate text-2xs font-medium tracking-wide text-muted-foreground uppercase">
@@ -77,7 +71,3 @@ function formatCapacity(value: number | null): string {
if (value === null) return "—";
return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(value)} GB`;
}
function kpiTestId(label: string): string {
return label.toLowerCase().replaceAll(" ", "-");
}

View File

@@ -14,9 +14,11 @@ export function MeshComputeCommunityView({
error,
onRefresh,
snapshot,
isPreparing = false,
}: {
communityName: string;
error: string | null;
isPreparing?: boolean;
onRefresh: () => void;
snapshot: MeshSnapshot | CommunityComputeSnapshotInput | null;
}) {
@@ -43,13 +45,15 @@ export function MeshComputeCommunityView({
<div>
<h2 className="text-lg font-semibold tracking-tight">
{model.deployments.length === 1
? "Your community’s first compute node"
? "The mesh starts here"
: "Community mesh"}
</h2>
<p className="text-sm text-muted-foreground">
{model.deployments.length === 1
? "This machine is the foundation of the mesh. More contributors add capacity, model choice, and resilience."
: "See the compute currently available to your community. Larger territories offer more model memory."}
{isPreparing
? "Your contribution is being prepared and will appear here when it is ready."
: model.deployments.length === 1
? "One tile is a beginning. Every contributor makes the mesh more capable and resilient."
: "Each tile is compute a community member is making available."}
</p>
</div>
<Button

View File

@@ -1,52 +1,30 @@
import * as React from "react";
import { useCommunities } from "@/features/communities/useCommunities";
import { SettingsSectionHeader } from "@/features/settings/ui/SettingsSectionHeader";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/shared/ui/tabs";
import { useMeshComputeState } from "../hooks/useMeshComputeState";
import { MeshComputeCommunityView } from "./MeshComputeCommunityView";
import { MeshComputeSettingsCard } from "./MeshComputeSettingsCard";
import { MeshComputeShareBanner } from "./MeshComputeShareBanner";
export function MeshComputePage() {
const { activeCommunity } = useCommunities();
const mesh = useMeshComputeState();
const [tab, setTab] = React.useState("community");
const communityName = activeCommunity?.name ?? "this community";
return (
<section className="min-w-0" data-testid="settings-mesh-compute-page">
<SettingsSectionHeader
description={`Contribute this machine’s spare capacity to unlock more models and keep ${communityName}’s agents available when demand grows.`}
description={`Share this machine’s spare capacity with ${communityName}, then see everyone contributing to the mesh.`}
title="Compute"
/>
<div className="space-y-5">
<MeshComputeShareBanner communityName={communityName} mesh={mesh} />
<Tabs onValueChange={setTab} value={tab}>
<TabsList className="grid w-full grid-cols-2 sm:w-72">
<TabsTrigger data-testid="compute-tab-community" value="community">
Community
</TabsTrigger>
<TabsTrigger data-testid="compute-tab-settings" value="settings">
Settings
</TabsTrigger>
</TabsList>
<TabsContent className="mt-5 space-y-5" value="community">
<MeshComputeCommunityView
communityName={communityName}
error={mesh.error}
onRefresh={mesh.refreshSnapshot}
snapshot={mesh.snapshot}
/>
</TabsContent>
<TabsContent className="mt-8" value="settings">
<MeshComputeSettingsCard hideHeader />
</TabsContent>
</Tabs>
<MeshComputeCommunityView
communityName={communityName}
error={mesh.error}
isPreparing={mesh.pendingAction === "start"}
onRefresh={mesh.refreshSnapshot}
snapshot={mesh.snapshot}
/>
</div>
</section>
);

View File

@@ -1,670 +0,0 @@
import * as React from "react";
import { ChevronDown } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { Switch } from "@/shared/ui/switch";
import { cn } from "@/shared/lib/cn";
import {
AgentConfigTextInput,
AgentDropdownSelect,
type AgentDropdownOption,
} from "@/features/agents/ui/agentConfigControls";
import {
CUSTOM_MODEL_DROPDOWN_VALUE,
PERSONA_FIELD_CONTROL_CLASS,
PERSONA_FIELD_SHELL_CLASS,
} from "@/features/agents/ui/agentConfigOptions";
import {
meshStartNode,
meshStopNode,
meshInstalledModels,
meshModelCatalog,
} from "@/shared/api/tauriMesh";
import type {
MeshCatalogEntry,
MeshModelCatalog,
MeshModelOption,
MeshNodeStatus,
} from "@/shared/api/tauriMesh";
import { MeshExperimentalSection } from "./MeshExperimentalSection";
import { SettingsSectionHeader } from "@/features/settings/ui/SettingsSectionHeader";
import { classifyModelRef } from "../classifyModelRef";
import {
downloadPercent,
formatDownloadBytes,
useMeshDownloadProgress,
} from "../hooks/useMeshDownloadProgress";
import { useMeshNodeStatus } from "../hooks/useMeshNodeStatus";
import { useMeshServingUsage } from "../hooks/useMeshServingUsage";
import { deriveMeshShareToggle } from "../shareToggleState";
import { deriveServingIndicator } from "../servingUsage";
import {
getShareComputeModel,
setShareComputeMaxVramGb,
setShareComputeModel,
useShareComputeMaxVramGb,
useShareComputeModel,
} from "../shareComputePreferences";
// Keep the Share compute controls visually and behaviorally aligned with the
// agent configuration fields. This is intentionally the same shell used by
// AgentDefaultsEditor rather than a local approximation of a select.
const MESH_SELECT_TRIGGER_CLASS = cn(
PERSONA_FIELD_CONTROL_CLASS,
PERSONA_FIELD_SHELL_CLASS,
"h-11 px-3 py-2 leading-6 hover:bg-muted/40 focus:bg-muted/40 [&>svg]:text-muted-foreground/60",
);
const SHARE_COMPUTE_REVEAL_TRANSITION = {
duration: 0.22,
ease: [0.23, 1, 0.32, 1],
} as const;
/**
* Settings → Compute → Share compute.
*
* One toggle, one model field, an "Already installed" picklist, an Advanced
* group. User-facing copy describes the shared-compute behavior without
* exposing implementation protocols or raw mesh controls.
*/
export function MeshComputeSettingsCard({
hideHeader = false,
}: {
hideHeader?: boolean;
} = {}) {
const shouldReduceMotion = useReducedMotion();
const { status, error, refresh } = useMeshNodeStatus();
const [installedModels, setInstalledModels] = React.useState<
MeshModelOption[]
>([]);
const [catalog, setCatalog] = React.useState<MeshModelCatalog | null>(null);
const modelInput = useShareComputeModel();
const maxVramGb = useShareComputeMaxVramGb();
const [isCustomModelEditing, setIsCustomModelEditing] = React.useState(false);
const [advancedOpen, setAdvancedOpen] = React.useState(false);
const [actionInFlight, setActionInFlight] = React.useState(false);
const [pendingAction, setPendingAction] = React.useState<
"start" | "stop" | null
>(null);
const [actionError, setActionError] = React.useState<string | null>(null);
const { progress: downloadProgress, reset: resetDownloadProgress } =
useMeshDownloadProgress();
// Fetch installed models. Called on mount and whenever the running state
// changes (a fresh start may have downloaded a new model). Stale-tolerant —
// the picklist is a convenience, not load-bearing.
const refreshInstalled = React.useCallback(() => {
let cancelled = false;
(async () => {
try {
const list = await meshInstalledModels();
if (!cancelled) setInstalledModels(list);
} catch {
// Non-fatal — picklist just stays empty; user can still type a ref.
}
})();
return () => {
cancelled = true;
};
}, []);
// biome-ignore lint/correctness/useExhaustiveDependencies: status?.state is the intentional trigger — re-fetch installed models when the node transitions (a fresh start may have downloaded a new model)
React.useEffect(() => refreshInstalled(), [refreshInstalled, status?.state]);
// One-shot hardware-aware catalog fetch. Purely additive: when it fails
// (stub build, survey error) the card falls back to the free-text field.
// When there is no saved choice, make the curated recommendation the actual
// default so a new member can turn Share Compute on directly. An explicit
// saved draft always wins.
React.useEffect(() => {
let cancelled = false;
(async () => {
try {
const value = await meshModelCatalog();
if (cancelled) return;
setCatalog(value);
if (getShareComputeModel().trim() === "" && value.recommended) {
setShareComputeModel(value.recommended);
}
} catch {
// Non-fatal — picker just doesn't render.
}
})();
return () => {
cancelled = true;
};
}, []);
// Mirror only a SERVE runtime's model into the field. A client reports the
// remote model it is consuming; copying that value here both loses the
// member's local sharing choice and can cause this machine to download the
// remote member's much larger model when sharing is enabled.
React.useEffect(() => {
if (
status?.state === "running" &&
status.mode === "serve" &&
status.modelId &&
status.modelId !== modelInput
) {
setShareComputeModel(status.modelId);
}
}, [status?.state, status?.mode, status?.modelId, modelInput]);
// The Share toggle reflects ONLY serve-mode occupancy. A client-mode runtime
// (this machine consuming a peer's compute) shares the single runtime slot
// and also reports state:"running" — but it must NOT light this switch, and
// toggling off must never tear down that unrelated consume session.
const { isSharing, isConsuming, slotOccupied } =
deriveMeshShareToggle(status);
// Host-side "who is using the compute I'm sharing" — only polled while
// actively sharing (serve mode). Read-only; reads the node's own metrics.
const servingUsage = useMeshServingUsage(isSharing);
const servingIndicator = deriveServingIndicator(servingUsage, isSharing);
// A consuming client may be intentionally replaced by a serve runtime, so
// keep the local model controls available in client mode. Serve and unknown
// occupants remain locked until stopped/recovered.
const controlsDisabled = actionInFlight || (slotOccupied && !isConsuming);
const refClass = classifyModelRef(modelInput);
const canStart = refClass.kind !== "unknown" && !actionInFlight;
const showSharingControls = isSharing || pendingAction === "start";
async function handleToggle(next: boolean) {
// Never let the Share switch tear down a consume session. The switch is
// already disabled while consuming, but status can be stale between polls,
// so refuse a stop that isn't stopping OUR serve node as a belt-and-braces
// guard (the backend enforces this authoritatively too).
if (!next && !isSharing) {
return;
}
setActionError(null);
setPendingAction(next ? "start" : "stop");
setActionInFlight(true);
try {
if (next) {
const maxVram =
maxVramGb.trim() === "" ? undefined : Number.parseFloat(maxVramGb);
await meshStartNode({
mode: "serve",
modelId: modelInput.trim() || undefined,
maxVramGb:
typeof maxVram === "number" && !Number.isNaN(maxVram)
? maxVram
: undefined,
});
} else {
await meshStopNode();
}
refresh();
} catch (err) {
setActionError(err instanceof Error ? err.message : String(err));
} finally {
setActionInFlight(false);
setPendingAction(null);
resetDownloadProgress();
}
}
return (
<section className="min-w-0" data-testid="settings-mesh-share-compute">
{hideHeader ? null : (
<SettingsSectionHeader
title="Share compute"
description="Provide compute to your Buzz community. More capacity can increase intelligence and availability of models for agents."
/>
)}
{error ? (
<p className="mb-3 rounded-lg bg-destructive/10 px-3 py-2 text-sm text-destructive">
Couldn't check shared compute: {error}
</p>
) : null}
{actionError ? (
<p className="mb-3 rounded-lg bg-destructive/10 px-3 py-2 text-sm text-destructive">
{actionError}
</p>
) : null}
{downloadProgress ? (
<DownloadProgressBar progress={downloadProgress} />
) : null}
<div className="space-y-5">
<div className="flex min-w-0 items-start justify-between gap-6">
<div className="min-w-0">
<label
className="text-sm font-medium"
htmlFor="mesh-share-compute-toggle"
>
Share this machine
</label>
{!isSharing ? (
<StatusLine
isConsuming={isConsuming}
pendingAction={pendingAction}
status={status}
/>
) : null}
</div>
<Switch
checked={isSharing}
data-testid="mesh-share-compute-toggle"
disabled={
// A serve node can always be stopped. An off node or consuming
// client can start sharing once a valid local model is selected.
// Unknown occupants remain protected from replacement.
actionInFlight ||
(isSharing
? false
: slotOccupied && !isConsuming
? true
: !canStart)
}
id="mesh-share-compute-toggle"
onCheckedChange={handleToggle}
/>
</div>
<MeshModelPicker
catalog={catalog}
disabled={controlsDisabled}
installedModels={installedModels}
isCustomModelEditing={isCustomModelEditing}
model={modelInput}
onCustomModelEditingChange={setIsCustomModelEditing}
onModelChange={setShareComputeModel}
/>
<div className="pt-3">
<button
aria-expanded={advancedOpen}
className="inline-flex h-9 items-center gap-1.5 text-sm font-medium text-foreground transition-colors hover:text-foreground/80 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
data-testid="mesh-share-compute-advanced-toggle"
onClick={() => setAdvancedOpen((current) => !current)}
type="button"
>
<span>Advanced</span>
<ChevronDown
className={cn(
"h-4 w-4 text-muted-foreground transition-transform duration-150 ease-out",
advancedOpen && "rotate-180",
)}
/>
</button>
{advancedOpen ? (
<div className="mt-3 space-y-1.5">
<label className="text-sm font-medium" htmlFor="mesh-vram">
Max VRAM (GB)
</label>
<AgentConfigTextInput
data-testid="mesh-share-compute-vram"
disabled={controlsDisabled}
id="mesh-vram"
inputMode="decimal"
onChange={(e) => {
const next = e.target.value;
setShareComputeMaxVramGb(next);
}}
placeholder="No limit"
usePersonaInputStyle
value={maxVramGb}
/>
{/*
Nested one level deeper than the memory cap: capping memory is a
normal advanced adjustment, whereas a collective model changes
whether sharing starts at all. Two clicks to reach it is the
point.
*/}
<MeshExperimentalSection
disabled={controlsDisabled}
onModelChange={setShareComputeModel}
selectedModel={modelInput}
/>
</div>
) : null}
</div>
<AnimatePresence initial={false}>
{showSharingControls ? (
<motion.div
animate={{ height: "auto", opacity: 1 }}
className="overflow-hidden"
data-testid="mesh-share-compute-options-motion"
exit={{ height: 0, opacity: 0 }}
initial={{ height: 0, opacity: 0 }}
key="mesh-share-compute-options"
transition={
shouldReduceMotion
? { duration: 0 }
: SHARE_COMPUTE_REVEAL_TRANSITION
}
>
<section
className="space-y-1"
data-testid="mesh-share-compute-sharing-status"
>
<h3 className="text-sm font-medium">Sharing</h3>
<div className="space-y-1 rounded-lg bg-muted/30 px-3 py-2">
<StatusLine
isConsuming={isConsuming}
omitSharingVerb
pendingAction={pendingAction}
status={status}
/>
{servingIndicator.show ? (
<p
className={
servingIndicator.hasRemoteConsumers
? "text-2xs text-emerald-600 dark:text-emerald-400"
: "text-2xs text-muted-foreground"
}
data-testid="mesh-serving-usage"
title={servingIndicator.detail ?? undefined}
>
{servingIndicator.label}
{servingIndicator.detail ? (
<span className="text-muted-foreground">
{" "}
· {servingIndicator.detail}
</span>
) : null}
</p>
) : null}
</div>
</section>
</motion.div>
) : null}
</AnimatePresence>
</div>
</section>
);
}
/**
* Renders the lifecycle/health text under the toggle. Maps Max's `state` ×
* `health` matrix to honest copy — no "starting…" stuck forever when mesh
* is actually downloading weights or has failed.
*/
/**
* Live model-download progress: name, bytes, percent bar. Rendered above the
* option group while the backend streams mesh-download-progress events —
* the answer to "it just greys out while downloading".
*/
function DownloadProgressBar({
progress,
}: {
progress: NonNullable<ReturnType<typeof useMeshDownloadProgress>["progress"]>;
}) {
const percent = downloadPercent(progress);
const bytes = formatDownloadBytes(progress);
return (
<div
className="mb-3 rounded-lg bg-muted/30 px-3 py-2"
data-testid="mesh-download-progress"
>
<div className="flex items-baseline justify-between gap-2 text-sm">
<span className="min-w-0 truncate font-medium">
{progress.status === "preparing" ? "Preparing" : "Downloading"}{" "}
{progress.label}
</span>
<span className="shrink-0 text-muted-foreground">
{percent != null ? `${percent}%` : bytes || "…"}
</span>
</div>
{bytes && percent != null ? (
<p className="mt-0.5 text-sm font-normal text-muted-foreground">
{bytes}
</p>
) : null}
<div className="mt-1.5 h-1.5 w-full overflow-hidden rounded-full bg-muted">
<div
className={cn(
"h-full rounded-full bg-primary transition-[width] duration-300",
percent == null && "w-1/4 animate-pulse",
)}
style={percent != null ? { width: `${percent}%` } : undefined}
/>
</div>
</div>
);
}
const FIT_LABEL: Record<MeshCatalogEntry["fit"], string> = {
comfortable: "Fits well",
tight: "Tight fit",
tradeoff: "Trade-off",
too_large: "Too large",
};
const FIT_CLASS: Record<MeshCatalogEntry["fit"], string> = {
comfortable: "text-green-600 dark:text-green-400",
tight: "text-amber-600 dark:text-amber-400",
tradeoff: "text-orange-600 dark:text-orange-400",
too_large: "text-destructive",
};
/**
* Share Compute's models use the agent configuration picker instead of a
* second, hand-rolled option system. The catalog remains hardware-aware, but
* its recommendations, installed models, and advanced choices now appear in
* the same searchable dropdown used when customizing an agent.
*/
function MeshModelPicker({
catalog,
disabled,
installedModels,
isCustomModelEditing,
model,
onCustomModelEditingChange,
onModelChange,
}: {
catalog: MeshModelCatalog | null;
disabled: boolean;
installedModels: readonly MeshModelOption[];
isCustomModelEditing: boolean;
model: string;
onCustomModelEditingChange: (editing: boolean) => void;
onModelChange: (model: string) => void;
}) {
const options = React.useMemo<AgentDropdownOption[]>(() => {
const seen = new Set<string>();
const catalogOptions = (catalog?.entries ?? []).map((entry) => {
seen.add(entry.name);
return {
disabled: entry.fit === "too_large",
label: <MeshModelOptionLabel entry={entry} />,
value: entry.name,
};
});
const localOptions = installedModels.flatMap((installed) => {
if (seen.has(installed.id)) return [];
return [
{
label: (
<div className="flex min-w-0 items-center justify-between gap-3">
<span className="min-w-0 truncate">
{installed.name ?? installed.id}
</span>
<span className="shrink-0 text-2xs text-muted-foreground">
Installed
</span>
</div>
),
value: installed.id,
},
];
});
return [
...catalogOptions,
...localOptions,
{ label: "Custom model…", value: CUSTOM_MODEL_DROPDOWN_VALUE },
];
}, [catalog?.entries, installedModels]);
const knownModel = options.some((option) => option.value === model.trim());
const showCustomModelInput =
isCustomModelEditing || (model.trim().length > 0 && !knownModel);
const selectedValue = showCustomModelInput
? CUSTOM_MODEL_DROPDOWN_VALUE
: model.trim();
function handleModelChange(next: string) {
if (next === CUSTOM_MODEL_DROPDOWN_VALUE) {
onCustomModelEditingChange(true);
return;
}
onCustomModelEditingChange(false);
onModelChange(next);
}
return (
<div className="space-y-1.5" data-testid="mesh-share-compute-catalog">
<label className="text-sm font-medium" htmlFor="mesh-share-compute-model">
Model
</label>
<AgentDropdownSelect
className={MESH_SELECT_TRIGGER_CLASS}
disabled={disabled}
id="mesh-share-compute-model"
onValueChange={handleModelChange}
options={options}
placeholder="Select a model"
placeholderClassName="text-muted-foreground/55"
searchable
testId="mesh-share-compute-model"
value={selectedValue}
/>
{showCustomModelInput ? (
<AgentConfigTextInput
aria-label="Custom model reference"
autoCorrect="off"
disabled={disabled}
onChange={(event) => {
// A stored custom ref starts out inferred rather than explicitly
// selected. Mark it as an active custom edit before applying a
// cleared value so the field stays mounted while it is replaced.
onCustomModelEditingChange(true);
onModelChange(event.target.value);
}}
placeholder="Qwen3-8B-Q4_K_M or hf://meshllm/qwen3-8b@main"
usePersonaInputStyle
value={model}
/>
) : null}
<p className="text-sm font-normal text-muted-foreground">
{catalog
? `Recommended for this machine${catalog.gpuName ? ` (${catalog.gpuName}, ${catalog.vramDisplay} AI memory)` : ""}.`
: "Choose a model or enter a model reference or local file."}{" "}
Buzz downloads remote models when sharing starts.
</p>
</div>
);
}
function MeshModelOptionLabel({ entry }: { entry: MeshCatalogEntry }) {
return (
<div className="flex min-w-0 items-center gap-2">
<span className="min-w-0 flex-1 truncate font-medium">{entry.name}</span>
<span className="shrink-0 text-muted-foreground">{entry.size}</span>
<span className={cn("shrink-0", FIT_CLASS[entry.fit])}>
{FIT_LABEL[entry.fit]}
</span>
{entry.recommended ? (
<span className="shrink-0 rounded bg-primary/15 px-1.5 text-2xs font-medium text-primary">
Recommended
</span>
) : null}
{entry.installed ? (
<span className="shrink-0 text-2xs text-muted-foreground">
Installed
</span>
) : null}
{!entry.curated ? (
<span className="shrink-0 text-2xs text-muted-foreground">
Advanced
</span>
) : null}
</div>
);
}
function StatusLine({
displayModel,
isConsuming,
omitSharingVerb = false,
pendingAction,
status,
}: {
displayModel?: string;
isConsuming: boolean;
omitSharingVerb?: boolean;
pendingAction: "start" | "stop" | null;
status: MeshNodeStatus | null;
}) {
if (pendingAction === "start") {
return <p className="text-sm text-muted-foreground">Starting…</p>;
}
if (pendingAction === "stop") {
return <p className="text-sm text-muted-foreground">Stopping…</p>;
}
// A client-mode runtime owns the single slot: this machine is consuming a
// peer's compute, not sharing. The switch stays off, but remains available
// so the member can replace the client with a serving runtime.
if (isConsuming) {
return (
<p className="text-sm text-muted-foreground">
This machine is currently using another member's shared compute. Turn on
sharing to switch to the selected local model; Buzz may briefly restart.
</p>
);
}
if (!status) {
return <p className="text-sm text-muted-foreground">Checking status…</p>;
}
const { state, health, modelId, modelName } = status;
const modelLabel = displayModel ?? modelName ?? modelId ?? "";
if (state === "off") {
return null;
}
if (state === "starting") {
const reason =
health.status === "degraded" || health.status === "failed"
? health.reason
: "Starting…";
return <p className="text-sm text-muted-foreground">{reason}</p>;
}
if (state === "running") {
if (health.status === "failed") {
return (
<p className="text-sm text-destructive">
Couldn't load: {health.reason}
</p>
);
}
if (health.status === "degraded") {
return (
<p className="text-sm text-amber-600 dark:text-amber-400">
Active{modelLabel ? ` — ${modelLabel}` : ""}. {health.reason}
</p>
);
}
return (
<p className="text-sm text-muted-foreground">
{omitSharingVerb ? "" : "Sharing"}
{modelLabel ? `${omitSharingVerb ? "" : " "}${modelLabel}` : ""} with
relay members.
</p>
);
}
if (state === "stopping") {
return <p className="text-sm text-muted-foreground">Stopping…</p>;
}
if (state === "failed") {
const reason =
health.status === "failed" || health.status === "degraded"
? health.reason
: "Couldn't start.";
return <p className="text-sm text-destructive">{reason}</p>;
}
return null;
}

View File

@@ -1,8 +1,12 @@
import { Cpu, LoaderCircle, ShieldCheck, Sparkles, Zap } from "lucide-react";
import { Cpu, LoaderCircle } from "lucide-react";
import { Switch } from "@/shared/ui/switch";
import { cn } from "@/shared/lib/cn";
import { Switch } from "@/shared/ui/switch";
import type { useMeshComputeState } from "../hooks/useMeshComputeState";
import {
downloadPercent,
formatDownloadBytes,
} from "../hooks/useMeshDownloadProgress";
export function MeshComputeShareBanner({
communityName,
@@ -12,26 +16,17 @@ export function MeshComputeShareBanner({
mesh: ReturnType<typeof useMeshComputeState>;
}) {
const {
error,
memory,
modelToShare,
downloadProgress,
pendingAction,
shareSwitchChecked,
status,
toggle,
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,
pendingAction,
statusModel: status?.modelName ?? status?.modelId ?? null,
isConsuming: toggle.isConsuming,
isSharing: toggle.isSharing,
});
const progress = downloadProgress ? downloadPercent(downloadProgress) : null;
const progressDetail = downloadProgress
? formatDownloadBytes(downloadProgress)
: "";
return (
<section
@@ -57,56 +52,66 @@ export function MeshComputeShareBanner({
className="text-base font-semibold"
htmlFor="compute-share-banner-toggle"
>
Automatic contribution
Share compute
</label>
<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.
Contribute this machine’s spare capacity so {communityName}’s agents
have more compute available. Pause anytime.
</p>
</div>
<Switch
checked={shareSwitchChecked}
data-testid="compute-share-banner-toggle"
disabled={busy || (!toggle.isSharing && !modelToShare)}
disabled={busy || (!toggle.isSharing && !mesh.modelToShare)}
id="compute-share-banner-toggle"
onCheckedChange={setSharing}
/>
</div>
<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}
<div
aria-live="polite"
className="border-border/60 border-t bg-background/60 px-4 py-3"
>
<p className="text-sm" data-testid="compute-share-banner-status">
{statusCopy(mesh, communityName)}
</p>
{error ? (
{downloadProgress ? (
<div className="mt-2" data-testid="mesh-download-progress">
<div className="flex justify-between gap-3 text-xs text-muted-foreground">
<span className="truncate">
{downloadProgress.status === "preparing"
? "Preparing"
: "Downloading"}{" "}
{downloadProgress.label}
</span>
<span className="shrink-0">
{progress !== null
? `${progress}%`
: progressDetail || "Working…"}
</span>
</div>
{progressDetail && progress !== null ? (
<p className="mt-1 text-2xs text-muted-foreground">
{progressDetail}
</p>
) : null}
<div className="mt-1.5 h-1.5 overflow-hidden rounded-full bg-muted">
<div
className={cn(
"h-full rounded-full bg-primary transition-[width] duration-300",
progress === null &&
"w-1/4 animate-pulse motion-reduce:animate-none",
)}
style={
progress !== null ? { width: `${progress}%` } : undefined
}
/>
</div>
</div>
) : null}
{mesh.error ? (
<p className="mt-1 text-xs text-destructive" role="alert">
{error}
{mesh.error}
</p>
) : null}
</div>
@@ -114,58 +119,23 @@ export function MeshComputeShareBanner({
);
}
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,
isSharing,
modelToShare,
pendingAction,
statusModel,
}: {
communityName: string;
isConsuming: boolean;
isSharing: boolean;
modelToShare: string | null;
pendingAction: "start" | "stop" | null;
statusModel: string | null;
}): string {
if (pendingAction === "start")
return `Preparing ${statusModel ?? modelToShare ?? "a model"} for ${communityName}…`;
if (pendingAction === "stop") return "Stopping shared compute…";
if (isSharing)
return `${statusModel ?? modelToShare ?? "A model"} is available to ${communityName}`;
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`;
function statusCopy(
mesh: ReturnType<typeof useMeshComputeState>,
communityName: string,
): string {
const model =
mesh.status?.modelName ?? mesh.status?.modelId ?? mesh.modelToShare;
if (mesh.pendingAction === "start") {
return mesh.downloadProgress
? "Downloading the files needed to contribute. Your tile will appear when this machine is ready."
: "Checking this machine and preparing shared compute. Your tile will appear when it is ready.";
}
if (mesh.pendingAction === "stop") return "Stopping shared compute…";
if (mesh.toggle.isSharing)
return `This machine is sharing compute with ${communityName}${model ? ` using ${model}` : ""}.`;
if (mesh.toggle.isConsuming)
return "This machine is using shared compute. Turn this on to contribute too.";
if (mesh.modelToShare)
return "Ready to contribute. Buzz will choose compatible settings automatically.";
return "Checking whether this machine can contribute…";
}

View File

@@ -1,131 +0,0 @@
import { ChevronDown, FlaskConical } from "lucide-react";
import * as React from "react";
import { cn } from "@/shared/lib/cn";
import { useMeshExperimentalCatalog } from "../hooks/useMeshExperimentalCatalog";
import {
describeExperimentalEmpty,
deriveMeshExperimentalModel,
} from "../experimentalModel";
/**
* The experimental tier of Advanced settings: models served across machines.
*
* ## Why this is a separate, collapsed tier
*
* Every other model in the picker starts when you flip the switch. These do not.
* mesh-llm gathers a cohort over gossip and holds a settle barrier — at least
* two participants, membership unchanged for a dwell period — then elects a
* coordinator deterministically by VRAM. Until that forms, a node named for a
* collective model waits.
*
* That waiting is indistinguishable from a slow download, so someone who was not
* warned would read it as a hang. The disclosure therefore sits above the list,
* before a choice is made, rather than as a toast afterwards.
*
* ## Why it can be empty for good reasons
*
* Three different facts produce an empty list — the catalog could not be read,
* it holds no layer packages, or this machine is large enough that nothing needs
* splitting — and the third is good news. `deriveMeshExperimentalModel` keeps
* them distinct so this never renders an unexplained blank.
*/
export function MeshExperimentalSection({
disabled,
onModelChange,
selectedModel,
}: {
disabled: boolean;
onModelChange: (model: string) => void;
selectedModel: string;
}) {
const [open, setOpen] = React.useState(false);
// Fetch only once opened: the first call can download the catalog, which is
// not work to do for a section nobody expanded.
const { catalog } = useMeshExperimentalCatalog(open);
const model = React.useMemo(
() => deriveMeshExperimentalModel(catalog),
[catalog],
);
const selected = selectedModel.trim();
return (
<div className="pt-1">
<button
aria-expanded={open}
className="inline-flex h-9 items-center gap-1.5 text-sm font-medium text-foreground transition-colors hover:text-foreground/80 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
data-testid="mesh-experimental-toggle"
onClick={() => setOpen((current) => !current)}
type="button"
>
<FlaskConical className="h-4 w-4 text-muted-foreground" />
<span>Experimental</span>
<ChevronDown
className={cn(
"h-4 w-4 text-muted-foreground transition-transform duration-150 ease-out",
open && "rotate-180",
)}
/>
</button>
{open ? (
<div className="mt-2 space-y-3" data-testid="mesh-experimental-panel">
{/*
Stated before the list, not after a selection. The wait is the whole
character of these models, and it looks exactly like a hang to
anyone who was not told.
*/}
<p className="rounded-lg bg-muted/40 px-3 py-2 text-2xs leading-relaxed text-muted-foreground">
These models are too large for this computer alone and run across
several machines at once.{" "}
<span className="font-medium text-foreground">
Sharing will not start until enough well-connected computers are
sharing the same model
</span>{" "}
— it waits instead of loading. For advanced setups.
</p>
{model.emptyReason ? (
<p
className="text-2xs text-muted-foreground"
data-testid="mesh-experimental-empty"
>
{describeExperimentalEmpty(model.emptyReason)}
</p>
) : (
<ul className="space-y-1" data-testid="mesh-experimental-list">
{model.options.map((option) => {
const isSelected = selected === option.entry.name;
return (
<li key={option.entry.name}>
<button
className={cn(
"flex w-full min-w-0 items-center justify-between gap-3 rounded-lg px-3 py-2 text-left transition-colors",
isSelected
? "bg-primary/10 ring-1 ring-primary/40"
: "hover:bg-muted/50",
disabled && "pointer-events-none opacity-50",
)}
data-testid="mesh-experimental-option"
disabled={disabled}
onClick={() => onModelChange(option.entry.name)}
type="button"
>
<span className="min-w-0 truncate text-sm">
{option.entry.name}
</span>
<span className="shrink-0 text-2xs tabular-nums text-muted-foreground">
{option.detail}
</span>
</button>
</li>
);
})}
</ul>
)}
</div>
) : null}
</div>
);
}

View File

@@ -26,73 +26,32 @@ type E2eWindow = Window & {
}) => void;
};
test("Share compute chooses a model before sharing", async ({ page }) => {
const modelRef = "hf://demo/SmolLM2-135M-Instruct-GGUF:Q4_K_M";
test("Share compute uses one automatic contribution control", async ({
page,
}) => {
await installMockBridge(page);
await page.goto("/");
await openSettings(page, "compute");
await page.getByTestId("compute-tab-settings").click();
const card = page.getByTestId("settings-mesh-share-compute");
const toggle = page.getByTestId("mesh-share-compute-toggle");
const model = page.getByTestId("mesh-share-compute-model");
await expect(page.getByTestId("compute-tab-community")).toHaveCount(0);
await expect(page.getByTestId("compute-tab-settings")).toHaveCount(0);
await expect(page.getByTestId("settings-mesh-share-compute")).toHaveCount(0);
await expect(card).not.toContainText("Not sharing right now");
await expect(
page.getByTestId("mesh-share-compute-options-motion"),
).toHaveCount(0);
await expect(
page.getByTestId("mesh-share-compute-sharing-status"),
).toHaveCount(0);
await expect(model).toBeVisible();
const toggle = page.getByTestId("compute-share-banner-toggle");
await expect(toggle).toBeEnabled();
await model.click();
await page.getByRole("option", { name: "Custom model…" }).click();
await page.getByLabel("Custom model reference").fill(modelRef);
await toggle.click();
await expect(
page.getByTestId("mesh-share-compute-options-motion"),
).toBeVisible();
await expect(
page.getByTestId("mesh-share-compute-sharing-status"),
).toBeVisible();
await expect(model).toBeVisible();
await expect(card).toContainText(
"Buzz downloads remote models when sharing starts",
);
await expect(toggle).toBeChecked();
await expect(
page.getByTestId("mesh-share-compute-sharing-status"),
).toContainText("SmolLM2 135M with relay members");
await expect(page.getByTestId("compute-share-banner-status")).toContainText(
"Your tile will appear when it is ready",
);
await expect
.poll(() =>
page.evaluate(() => (window as E2eWindow).__BUZZ_E2E_COMMANDS__ ?? []),
)
.toContain("mesh_start_node");
await expect
.poll(() =>
page.evaluate(
() => (window as E2eWindow).__BUZZ_E2E_COMMAND_PAYLOADS__ ?? [],
),
)
.toContainEqual({
command: "mesh_start_node",
payload: {
request: { mode: "serve", modelId: modelRef },
},
});
await toggle.click();
await expect(toggle).not.toBeChecked();
await expect(card).not.toContainText("Not sharing right now");
await expect(
page.getByTestId("mesh-share-compute-options-motion"),
).toHaveCount(0);
await expect(
page.getByTestId("mesh-share-compute-sharing-status"),
).toHaveCount(0);
await expect(model).toBeVisible();
await expect
.poll(() =>
page.evaluate(() => (window as E2eWindow).__BUZZ_E2E_COMMANDS__ ?? []),
@@ -100,67 +59,6 @@ test("Share compute chooses a model before sharing", async ({ page }) => {
.toContain("mesh_stop_node");
});
test("a consuming client can switch to sharing its saved local model", async ({
page,
}) => {
// Regression: consuming someone else's shared compute starts a client-mode
// node in the single runtime slot, which reports state:"running". The Share
// toggle keyed off state alone and lit up. A later guard overcorrected by
// disabling the switch and copying the remote model over the local sharing
// choice. Keep the switch off, preserve the local model, then replace the
// client with one serve start (never a stop command).
const localModel = "hf://demo/local-small-model:Q4_K_M";
await page.addInitScript((model) => {
window.localStorage.setItem("buzz.mesh-compute.share.model.v1", model);
}, localModel);
await installMockBridge(page);
await page.goto("/");
// The mesh seed hook is installed when the mock bridge boots; calling it
// before then silently no-ops (optional chaining) and the seed is lost.
await page.waitForFunction(
() => typeof (window as E2eWindow).__BUZZ_E2E_SET_MESH__ === "function",
);
await page.evaluate(() => {
(window as E2eWindow).__BUZZ_E2E_SET_MESH__?.({
nodeState: "running",
nodeMode: "client",
});
});
await openSettings(page, "compute");
await page.getByTestId("compute-tab-settings").click();
const card = page.getByTestId("settings-mesh-share-compute");
const toggle = page.getByTestId("mesh-share-compute-toggle");
await expect(card).toContainText(
"This machine is currently using another member's shared compute",
);
await expect(card).toContainText("Buzz may briefly restart");
await expect(toggle).not.toBeChecked();
await expect(
page.getByTestId("mesh-share-compute-options-motion"),
).toHaveCount(0);
await expect(toggle).toBeEnabled();
const customModel = page.getByLabel("Custom model reference");
await expect(customModel).toHaveValue(localModel);
await customModel.fill("");
await expect(customModel).toBeVisible();
await customModel.fill("hf://demo/replacement-model:Q4_K_M");
await toggle.click();
await expect(toggle).toBeChecked();
const commands = await page.evaluate(() => ({
names: (window as E2eWindow).__BUZZ_E2E_COMMANDS__ ?? [],
payloads: (window as E2eWindow).__BUZZ_E2E_COMMAND_PAYLOADS__ ?? [],
}));
expect(commands.names).not.toContain("mesh_stop_node");
expect(commands.payloads).toContainEqual({
command: "mesh_start_node",
payload: {
request: { mode: "serve", modelId: "hf://demo/replacement-model:Q4_K_M" },
},
});
});
test("Shared Compute is only available from Settings", async ({ page }) => {
await installMockBridge(page);
await page.goto("/");
@@ -215,9 +113,6 @@ test("Community Compute keeps the live community topology focused", async ({
await expect(
page.getByTestId("community-compute-kpi-contributed-vram"),
).toContainText("96 GB");
await expect(
page.getByTestId("community-compute-kpi-vram-allocated"),
).toContainText("42%");
await expect(
page.getByTestId("community-compute-territory-map"),
).toBeVisible();