mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
[eric] [eric] fix 9Router startup for users without Node.js, fix onboarding modal
- nine_router.py: use Electron binary as Node.js fallback (ELECTRON_RUN_AS_NODE=1) - electron/main.js: pass OPENSWARM_ELECTRON_PATH to backend - OnboardingModal: show once for users without subscription (persisted via localStorage) - settingsSlice: sync frontend default system prompt with backend
This commit is contained in:
+13
-12
@@ -60,19 +60,17 @@ def _find_9router_dir() -> str | None:
|
||||
|
||||
def _find_node() -> str | None:
|
||||
"""Find a Node.js binary (works in both dev and packaged mode)."""
|
||||
_is_packaged = os.environ.get("OPENSWARM_PACKAGED") == "1"
|
||||
# Check system node first
|
||||
node = shutil.which("node")
|
||||
if node:
|
||||
return node
|
||||
|
||||
if _is_packaged:
|
||||
# Electron bundles Node.js — find its binary
|
||||
# Electron's node is the electron binary itself with ELECTRON_RUN_AS_NODE=1
|
||||
# But we can also check for system node
|
||||
node = shutil.which("node")
|
||||
if node:
|
||||
return node
|
||||
# Electron's own node can be used with ELECTRON_RUN_AS_NODE
|
||||
return None
|
||||
else:
|
||||
return shutil.which("node")
|
||||
# In packaged Electron app, use the Electron binary with ELECTRON_RUN_AS_NODE=1
|
||||
electron_path = os.environ.get("OPENSWARM_ELECTRON_PATH")
|
||||
if electron_path and os.path.exists(electron_path):
|
||||
return electron_path
|
||||
|
||||
return None
|
||||
|
||||
|
||||
async def ensure_running():
|
||||
@@ -101,6 +99,9 @@ async def ensure_running():
|
||||
cmd = [node, standalone_server]
|
||||
cwd = os.path.join(_9router_dir, ".next", "standalone")
|
||||
env = {**os.environ, "PORT": str(NINE_ROUTER_PORT), "NODE_ENV": "production"}
|
||||
# If using Electron binary as node, enable ELECTRON_RUN_AS_NODE
|
||||
if node == os.environ.get("OPENSWARM_ELECTRON_PATH"):
|
||||
env["ELECTRON_RUN_AS_NODE"] = "1"
|
||||
|
||||
elif _9router_dir:
|
||||
# Dev mode with bundled 9Router — use next dev
|
||||
|
||||
@@ -152,6 +152,7 @@ async function startBackend() {
|
||||
PATH: shellPath,
|
||||
OPENSWARM_PACKAGED: isPackaged ? '1' : '0',
|
||||
OPENSWARM_PORT: String(backendPort),
|
||||
OPENSWARM_ELECTRON_PATH: process.execPath,
|
||||
PYTHONDONTWRITEBYTECODE: '1',
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ const OnboardingModal: React.FC = () => {
|
||||
const c = useClaudeTokens();
|
||||
const settings = useAppSelector((s) => s.settings);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
const [connecting, setConnecting] = useState<string | null>(null);
|
||||
const [nineRouterStatus, setNineRouterStatus] = useState<any>(null);
|
||||
|
||||
@@ -32,7 +31,7 @@ const OnboardingModal: React.FC = () => {
|
||||
fetch(`${API_BASE}/agents/subscriptions/status`)
|
||||
.then((r) => r.json())
|
||||
.then(setNineRouterStatus)
|
||||
.catch(() => setNineRouterStatus(null));
|
||||
.catch(() => setNineRouterStatus({ running: false, providers: [], models: [] }));
|
||||
}, []);
|
||||
|
||||
const hasSubscription = (() => {
|
||||
@@ -41,14 +40,17 @@ const OnboardingModal: React.FC = () => {
|
||||
return connections.some((p: any) => p.isActive);
|
||||
})();
|
||||
|
||||
// Show modal if no keys AND no subscriptions AND not dismissed
|
||||
// Show once: if no subscription connected AND not previously dismissed (persisted in localStorage)
|
||||
useEffect(() => {
|
||||
if (!hasAnyKey && !hasSubscription && !dismissed && nineRouterStatus !== null) {
|
||||
const alreadySeen = localStorage.getItem('openswarm_onboarding_seen');
|
||||
if (alreadySeen === 'true') return;
|
||||
if (nineRouterStatus === null) return; // still loading
|
||||
|
||||
// Show if no subscription, regardless of API key status
|
||||
if (!hasSubscription) {
|
||||
setOpen(true);
|
||||
} else {
|
||||
setOpen(false);
|
||||
}
|
||||
}, [hasAnyKey, hasSubscription, dismissed, nineRouterStatus]);
|
||||
}, [hasSubscription, nineRouterStatus]);
|
||||
|
||||
const handleConnect = async (providerId: string) => {
|
||||
setConnecting(providerId);
|
||||
@@ -75,7 +77,7 @@ const OnboardingModal: React.FC = () => {
|
||||
if (pd.success) {
|
||||
clearInterval(timer);
|
||||
setConnecting(null);
|
||||
setOpen(false);
|
||||
dismiss();
|
||||
}
|
||||
} catch {}
|
||||
}, 5000);
|
||||
@@ -101,7 +103,7 @@ const OnboardingModal: React.FC = () => {
|
||||
});
|
||||
} catch {}
|
||||
setConnecting(null);
|
||||
setOpen(false);
|
||||
dismiss();
|
||||
}
|
||||
};
|
||||
window.addEventListener('message', msgHandler);
|
||||
@@ -115,7 +117,7 @@ const OnboardingModal: React.FC = () => {
|
||||
clearInterval(statusPoller);
|
||||
window.removeEventListener('message', msgHandler);
|
||||
setConnecting(null);
|
||||
setOpen(false);
|
||||
dismiss();
|
||||
}
|
||||
} catch {}
|
||||
}, 2000);
|
||||
@@ -126,15 +128,13 @@ const OnboardingModal: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleApiKey = () => {
|
||||
setDismissed(true);
|
||||
const dismiss = () => {
|
||||
localStorage.setItem('openswarm_onboarding_seen', 'true');
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleSkip = () => {
|
||||
setDismissed(true);
|
||||
setOpen(false);
|
||||
};
|
||||
const handleApiKey = () => dismiss();
|
||||
const handleSkip = () => dismiss();
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user