diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 110add25..231bc33d 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -12,7 +12,7 @@ import { openSharedSessionFromDeepLink } from './sessionLinks'; import { type SharedSessionDetails } from './sharedSessions'; import { ErrorUI } from './components/ErrorBoundary'; import { ExtensionInstallModal } from './components/ExtensionInstallModal'; -import { ToastContainer } from 'react-toastify'; +import { toast, ToastContainer } from 'react-toastify'; import AnnouncementModal from './components/AnnouncementModal'; import TelemetryConsentPrompt from './components/TelemetryConsentPrompt'; import OnboardingGuard from './components/onboarding/OnboardingGuard'; @@ -455,6 +455,18 @@ export function AppInner() { }; }, []); + // Show a toast if mesh is the configured provider but isn't running. + useEffect(() => { + const handler = () => { + toast.warn('Inference Mesh is set as your provider but isn\'t running. Open Settings → Mesh to start it. Keep goose running to stay connected.', { + autoClose: false, + toastId: 'mesh-not-running', + }); + }; + window.electron.on('mesh-not-running', handler); + return () => { window.electron.off('mesh-not-running', handler); }; + }, []); + // Prevent default drag and drop behavior globally to avoid opening files in new windows // but allow our React components to handle drops in designated areas useEffect(() => { diff --git a/ui/desktop/src/components/settings/mesh/MeshSettings.tsx b/ui/desktop/src/components/settings/mesh/MeshSettings.tsx index ce60edba..4f30ecfb 100644 --- a/ui/desktop/src/components/settings/mesh/MeshSettings.tsx +++ b/ui/desktop/src/components/settings/mesh/MeshSettings.tsx @@ -9,7 +9,6 @@ import { Check, ChevronDown, ChevronRight, - Download, } from 'lucide-react'; import { Button } from '../../ui/button'; import { Input } from '../../ui/input'; @@ -47,7 +46,7 @@ interface MeshStatusInfo { export const MeshSettings = () => { const { refreshCurrentModelAndProvider } = useModelAndProvider(); - const canAutoDownload = window.electron.platform === 'darwin' && window.electron.arch === 'arm64'; + const isMacOS = window.electron.platform === 'darwin' && window.electron.arch === 'arm64'; const [status, setStatus] = useState('unknown'); const [statusInfo, setStatusInfo] = useState({ running: false, @@ -57,7 +56,7 @@ export const MeshSettings = () => { const [mode, setMode] = useState('auto'); const [selectedModel, setSelectedModel] = useState(MESH_DEFAULT_MODEL); const [joinToken, setJoinToken] = useState(''); - const [contributeGpu, setContributeGpu] = useState(true); + const [contributeGpu, setContributeGpu] = useState(false); const [copiedToken, setCopiedToken] = useState(false); const [showAdvanced, setShowAdvanced] = useState(false); const [saving, setSaving] = useState(false); @@ -80,10 +79,12 @@ export const MeshSettings = () => { if (result.running) { setStatus('running'); setStatusInfo(result); - } else if (!result.installed) { + } else if (!result.installed && !isMacOS) { + // On non-macOS, binary must be manually installed. setStatus((prev) => (prev === 'downloading' ? prev : 'not-installed')); setStatusInfo({ running: false, installed: false, models: [] }); } else { + // On macOS, start-mesh handles downloading, so treat not-installed as stopped. setStatus((prev) => (prev === 'starting' || prev === 'downloading' ? prev : 'stopped')); setStatusInfo({ ...result, models: [] }); } @@ -92,7 +93,7 @@ export const MeshSettings = () => { } finally { setChecking(false); } - }, []); + }, [isMacOS]); useEffect(() => { checkStatus(); @@ -165,7 +166,8 @@ export const MeshSettings = () => { const startMesh = async () => { setError(null); - setStatus('starting'); + // On macOS, start-mesh downloads the latest binary first. + setStatus(isMacOS ? 'downloading' : 'starting'); try { const args: string[] = []; @@ -195,6 +197,7 @@ export const MeshSettings = () => { setStatus('stopped'); return; } + setStatus('starting'); // Polling will pick up when it's ready. Timeout after 5 min so // the UI doesn't get stuck in "starting" if the daemon crashes. if (startTimeoutRef.current) { @@ -230,28 +233,6 @@ export const MeshSettings = () => { } }; - const downloadMesh = async () => { - setError(null); - setStatus('downloading'); - try { - const result = await window.electron.downloadMesh(); - if (result.downloaded) { - setStatusInfo((prev) => ({ - ...prev, - installed: true, - binaryPath: result.binaryPath, - })); - setStatus('stopped'); - } else { - setError(result.error || 'Download failed'); - setStatus('not-installed'); - } - } catch (err) { - setError(`Download failed: ${err}`); - setStatus('not-installed'); - } - }; - const copyToken = () => { if (statusInfo.token) { navigator.clipboard.writeText(statusInfo.token); @@ -286,7 +267,7 @@ export const MeshSettings = () => { return ( - Downloading mesh-llm (~19 MB)... + Downloading latest mesh-llm (~19 MB)... ); case 'not-installed': @@ -352,21 +333,15 @@ export const MeshSettings = () => { {error &&

{error}

} - {/* Not installed — offer download or install link */} + {/* Not installed — non-macOS only; on macOS start-mesh handles the download */} {status === 'not-installed' && (

Get started

- mesh-llm is a small download (~19 MB) that manages local inference and mesh networking. - Models are downloaded separately when you start a mesh. + mesh-llm is not installed. Follow the install guide to set it up, or connect to + a mesh already running on this machine.

- {canAutoDownload && ( - - )} + +

+ When you start the mesh, keep goose running to stay connected. +

)} @@ -605,6 +584,10 @@ export const MeshSettings = () => {

)} +

+ Keep goose running to stay connected to the mesh. +

+ {/* Actions row */}