fix(launch): reliable Windows launcher; feat(ui): compact sidebar, Vite toggle, chat Think toggle

Ported from the private repo via bin/publish.sh, plus a manual catch-up
on files that had drifted out of sync before today:

- launch_nexus.ps1: health-check based restart decisions instead of a
  bare port-listen check (a wedged leftover process squatting a port
  used to look "already running" and block the real service from
  starting), a script-path quoting fix for Start-Process, hidden
  console via a wscript.exe wrapper (bin/launch_nexus_hidden.vbs), and
  a taskbar/window icon for the native app window.
- Sidebar: slim icon+text nav rows instead of bulky bordered buttons,
  tighter spacing throughout.
- Settings: full-width layout, a Vite dev-server Start/Stop toggle
  (synapse/frontend_manager.py + /frontend/* endpoints), and the
  Linux-only Icon Branding section now gated on the new /status
  `platform` field instead of always rendering.
- Chatbot: a Think toggle next to the model picker, so extended
  thinking can be flipped without leaving the chat page.
- management/ncp.py: faster start/stop polling (0.25s steps instead of
  1s), Vite no longer blocks `ncp start` on Linux and is skipped
  outright on Windows.

Note: the private repo also has a Mail (IMAP/SMTP) feature; it's
intentionally not included here, so the Mail-only pieces of main.py,
App.jsx, and requirements-windows.txt were left out of this port.
This commit is contained in:
Jon Wingender
2026-07-28 11:23:35 -05:00
parent 63c93346ae
commit cc20ceac64
11 changed files with 490 additions and 109 deletions
+32 -24
View File
@@ -1,16 +1,21 @@
# NexusOS launcher for Windows (native, no Vite).
#
# Single-process app: the backend on :8000 serves the built web UI itself, so
# this starts only the memory service + backend, then opens the UI as an Edge
# app window. The AI (Ollama) does NOT auto-start - turn it on from the UI's
# Start AI button. Closing the app window stops the services.
# this starts only the memory service + backend, opening a native app window
# (bin\nexus_window.py, pywebview/WebView2) immediately alongside them with its
# own live-updating loading screen. The AI (Ollama) does NOT auto-start - turn
# it on from the UI's Start AI button. Closing the app window stops the
# services.
#
# Right-click -> Run with PowerShell (or: powershell -File launch_nexus.ps1)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue" # Invoke-WebRequest's progress bar adds real latency for no benefit here
$Root = $PSScriptRoot
$Py = Join-Path $Root "Promethean\Scripts\python.exe"
if (-not (Test-Path $Py)) { $Py = "python" } # fall back to PATH
$PyW = Join-Path $Root "Promethean\Scripts\pythonw.exe"
if (-not (Test-Path $PyW)) { $PyW = "pythonw" } # fall back to PATH
$Runtime = Join-Path $Root "runtime"
$Logs = Join-Path $Runtime "logs"
@@ -22,38 +27,41 @@ function Start-Svc($log, $argList) {
-RedirectStandardOutput $log -RedirectStandardError "$log.err"
}
function Test-ServiceUp($url) {
try {
$r = Invoke-WebRequest -Uri $url -TimeoutSec 2 -UseBasicParsing
return $r.StatusCode -eq 200
} catch { return $false }
}
Write-Host "Starting NexusOS..." -ForegroundColor Cyan
# Only start a service if its port is free. If it's already listening (a prior
# launch, or started by hand) reuse it instead of spawning a duplicate that
# fails to bind and stalls the readiness wait below.
# Only reuse a port if something actually answers there. A bare port-listen
# check isn't enough: a wedged process left over from a prior crashed launch
# can be squatting the port without ever responding, and treating that as
# "already running" skips starting a real service - the window then sits on
# its loading screen for the full 40s with no way to tell why.
$memory = $null
if (-not (Get-NetTCPConnection -LocalPort 8001 -State Listen -ErrorAction SilentlyContinue)) {
if (-not (Test-ServiceUp "http://127.0.0.1:8001/")) {
$memory = Start-Svc (Join-Path $Runtime "memory.log") @("-m","uvicorn","synapse.memory.service:app","--host","127.0.0.1","--port","8001")
}
$backend = $null
if (-not (Get-NetTCPConnection -LocalPort 8000 -State Listen -ErrorAction SilentlyContinue)) {
if (-not (Test-ServiceUp "http://127.0.0.1:8000/status")) {
$backend = Start-Svc (Join-Path $Runtime "backend.log") @("-m","uvicorn","synapse.main:sio_app","--host","127.0.0.1","--port","8000")
}
# Wait for the backend to answer on :8000.
$ok = $false
foreach ($i in 1..30) {
try {
Invoke-WebRequest -UseBasicParsing -Uri "http://localhost:8000/status" -TimeoutSec 2 | Out-Null
$ok = $true; break
} catch { Start-Sleep -Seconds 1 }
}
if (-not $ok) {
Write-Host "Backend did not come up - check runtime\backend.log" -ForegroundColor Red
}
# Open the UI in a native window (pywebview / WebView2) - no browser, no Edge
# profile cold-start. bin\nexus_window.py blocks until the window is closed and
# falls back to the default browser if WebView2 is unavailable.
$app = Start-Process -FilePath $Py -ArgumentList @((Join-Path $Root "bin\nexus_window.py")) `
# profile cold-start - IMMEDIATELY, in parallel with memory/backend still
# coming up. bin\nexus_window.py opens on its own loading page and polls
# :8001 then :8000 itself, updating that page's status line live as each
# comes up, so the window is on screen from the first instant instead of
# this script blocking silently (in a hidden window) for up to 30s first and
# only then showing anything. It blocks until the window is closed and falls
# back to the default browser if WebView2 is unavailable.
$WindowScript = Join-Path $Root "bin\nexus_window.py"
$app = Start-Process -FilePath $PyW -ArgumentList "`"$WindowScript`"" `
-WorkingDirectory $Root -PassThru
Write-Host "NexusOS is running at http://localhost:8000 (AI is off - start it in the UI)" -ForegroundColor Green
Write-Host "NexusOS window opened; services are starting at http://localhost:8000 (AI is off - start it in the UI)" -ForegroundColor Green
# Block until the app window (or a service we started) exits, so this process
# stays alive to hold and later stop the services. No blind Read-Host: the
# shortcut runs hidden, where a prompt no one can answer would hang forever.