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
+31 -14
View File
@@ -202,6 +202,14 @@ def check(svc: Service) -> bool:
return False
# Poll granularity for both waiters below. 1s steps used to mean every
# start/stop paid up to a full second of dead latency per service on top of
# however long the process actually took - three services in sequence could
# lose several seconds to nothing but sleep(). 0.25s still amounts to one
# cheap local HTTP HEAD every quarter second, not a busy-loop.
_POLL_STEP = 0.25
def wait_for_port(svc: Service, timeout: int = 30) -> bool:
if http_ok(svc.url):
# "READY", not "already running": `ncp start` launches memory and backend
@@ -210,8 +218,8 @@ def wait_for_port(svc: Service, timeout: int = 30) -> bool:
# this same command started two seconds ago reads like a stale process.
print(f" {svc.label} READY (:{svc.port})")
return True
for _ in range(timeout):
time.sleep(1)
for _ in range(int(timeout / _POLL_STEP)):
time.sleep(_POLL_STEP)
if http_ok(svc.url):
print(f"{svc.label} STARTED")
return True
@@ -220,10 +228,10 @@ def wait_for_port(svc: Service, timeout: int = 30) -> bool:
def wait_for_port_close(port: int, timeout: int = 15) -> bool:
for _ in range(timeout):
for _ in range(int(timeout / _POLL_STEP)):
if not http_ok(f"http://localhost:{port}/"):
return True
time.sleep(1)
time.sleep(_POLL_STEP)
return False
@@ -353,24 +361,33 @@ def cmd_start(target) -> None:
elif target in ("--ai", "-a"):
start_ollama()
elif target in (None, "", "all"):
# Bring the UI up first, then warm Ollama in the background — the model
# loads concurrently and into the first chat instead of blocking boot.
# Bring the UI up first, then kick off Ollama and (on Linux) Vite in the
# background without waiting on either — neither gates the app being
# usable. The backend already serves the built interface/web/dist at
# :8000 on both platforms (single-process design), and that's the URL
# nexus-app.sh/ncp web actually opens; nothing points a user at :5173.
# Vite only exists for whoever is hot-reload-editing the frontend, and
# they'll open :5173 themselves once it's ready - polling for it here
# just delayed "boot done" for a benefit nobody in the critical path
# gets. Skipped outright on Windows, where it's not part of the normal
# workflow at all and is the slow part of `ncp stop` to boot (npm's
# cmd.exe -> node -> esbuild tree doesn't die from a plain terminate()
# and falls through to the ~30s force-kill path). Still available on
# demand via `ncp start --frontend`.
t0 = time.perf_counter()
launch(SERVICES["memory"])
launch(SERVICES["backend"])
wait_for_port(SERVICES["memory"])
wait_for_port(SERVICES["backend"])
t_services = time.perf_counter()
launch(SERVICES["frontend"])
wait_for_port(SERVICES["frontend"])
t_frontend = time.perf_counter()
start_ollama(background=True)
t_ollama = time.perf_counter()
if not WINDOWS:
launch(SERVICES["frontend"])
t_bg = time.perf_counter()
print("\nBoot timing:")
print(f" services (memory+backend) : {t_services - t0:5.1f}s")
print(f" frontend (UI ready) : {t_frontend - t_services:5.1f}s")
print(f" ollama kickoff (bg warm) : {t_ollama - t_frontend:5.1f}s")
print(f" total to interactive : {t_ollama - t0:5.1f}s")
print(f" services (memory+backend) : {t_services - t0:5.1f}s")
print(f" ollama + frontend (bg kickoff) : {t_bg - t_services:5.1f}s")
print(f" total to interactive : {t_bg - t0:5.1f}s")
else:
show_help()