diff --git a/dashboard/src/routes/+page.svelte b/dashboard/src/routes/+page.svelte index 45a2cb6c..10c6d319 100644 --- a/dashboard/src/routes/+page.svelte +++ b/dashboard/src/routes/+page.svelte @@ -295,8 +295,8 @@ 0; const memGB = Math.round(totalMem / (1024 * 1024 * 1024)); const name = firstNode.friendly_name || "Your Mac"; - const deviceType = (firstNode.device_type || "macbook pro").toLowerCase(); - return { name, memoryGB: memGB || 36, deviceType }; + const modelId = (firstNode.system_info?.model_id || "macbook pro").toLowerCase(); + return { name, memoryGB: memGB || 36, deviceType: modelId }; }); let showContinueButton = $state(false); @@ -532,7 +532,7 @@ connectionIsRed.set(0); }, 3700); const t7 = setTimeout(() => { - modelBlockY.set(115); // Settle back down just above the device + modelBlockY.set(125); // Settle back down just above the device }, 4800); const t8 = setTimeout(() => { advanceStep(6); @@ -552,13 +552,39 @@ } }); - // Recommended models for onboarding (biggest to smallest that fit, limited to 6) + // Recommended models for onboarding: 2 large, 2 medium, 2 small + // Always includes Llama-3.2-3B-4bit as a fast-loading small option + const PINNED_ONBOARDING_MODEL = "mlx-community/Llama-3.2-3B-Instruct-4bit"; const onboardingModels = $derived.by(() => { if (models.length === 0) return []; - return [...models] + const sorted = [...models] .filter((m) => hasEnoughMemory(m) && getModelSizeGB(m) > 0) - .sort((a, b) => getModelSizeGB(b) - getModelSizeGB(a)) - .slice(0, 6); + .sort((a, b) => getModelSizeGB(b) - getModelSizeGB(a)); + if (sorted.length <= 6) return sorted; + + // Split into thirds by size: large (top third), medium (middle), small (bottom) + const third = Math.max(1, Math.floor(sorted.length / 3)); + const large = sorted.slice(0, third); + const medium = sorted.slice(third, third * 2); + const small = sorted.slice(third * 2); + + // Pick 2 from each tier, ensuring pinned model counts as a small pick + const pinned = small.find((m) => m.id === PINNED_ONBOARDING_MODEL) + || sorted.find((m) => m.id === PINNED_ONBOARDING_MODEL); + const pickLarge = large.slice(0, 2); + const pickMedium = medium.slice(0, 2); + const pickSmall = pinned + ? [small.find((m) => m.id !== PINNED_ONBOARDING_MODEL) || small[0], pinned].filter(Boolean) + : small.slice(0, 2); + + const result = [...pickLarge, ...pickMedium, ...pickSmall]; + // Deduplicate (in case pinned was already picked) + const seen = new Set(); + return result.filter((m) => { + if (seen.has(m.id)) return false; + seen.add(m.id); + return true; + }); }); // Track onboarding instance status for auto-advancing steps. @@ -1259,13 +1285,27 @@ // Use the specific preview if provided, otherwise fall back to filtered preview const preview = specificPreview ?? filteredPreview(); - let instanceData: unknown; - - const response = await fetch("/meta_instance", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ instance: instanceData }), - }); + let response: Response; + if (preview?.instance) { + // Launch with pre-computed placement from preview + response = await fetch("/instance", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ instance: preview.instance }), + }); + } else { + // No preview available — use place_instance to let server decide placement + response = await fetch("/place_instance", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + model_id: modelId, + sharding: selectedSharding, + instance_meta: selectedInstanceType, + min_nodes: 1, + }), + }); + } if (!response.ok) { const errorText = await response.text(); @@ -2948,19 +2988,34 @@

- +

{Math.max(1, nodeCount)} {Math.max(1, nodeCount) === 1 ? 'Device' : 'Devices'} in your EXO network

+ + {#if onboardingStep === 1} + +
+ +
+ {:else} + + {/if} + LLM @@ -3250,7 +3305,7 @@ {@const splitX = $modelSplitProgress * (($device2X - $device1X) / 2)} {@const centerX = ($device1X + $device2X) / 2} - {@const splitY = $modelBlockY + $modelSplitProgress * 30} + {@const splitY = $modelBlockY + $modelSplitProgress * 80} Shard 1/2 @@ -3284,10 +3339,10 @@ opacity={$modelBlockOpacity * $device2Opacity} > Shard 2/2 @@ -3496,7 +3551,7 @@ class="text-center max-w-lg px-8" style="opacity: 0; animation: onb-fade-in 0.5s ease forwards;" > -
+

@@ -3509,9 +3564,23 @@ {/if}

+
+ + + +
+ +