Skip to content

Commit 4cb86dc

Browse files
committed
chore(release): v0.4.6 — "Restart now" inline action on savedToast
Follow-up to v0.4.5's restart hint: the hint told users *what* to do (Stop → Start) but they still had to scroll up to the header toggle and click twice. This adds a single-click [Restart now] button right next to the toast itself, so saving a TurboQuant / KV-quant / ctx setting and applying the change becomes a 2-click flow: 1. Change the dropdown / number input (auto-saves to disk) 2. Click [Restart now] in the toast `restartServer()` issues `stopServer` then polls `serverStatus` every 200ms until the supervisor drains to `stopped` / `errored` (30s timeout), then calls `startServer`. The poll-then-start sequence guarantees the new env vars from `spawn_server_command` are actually used — without the drain wait, `startServer` would race the still- tearing-down supervisor and surface a stale state. The action is optional state on `statusMessage`: * server running → "Saved. Restart to apply" + [Restart now] button, 6s dwell (longer to give time to read+click) * server stopped → plain "Saved.", 2s dwell, no button (next start picks up the change naturally) Three new i18n keys (EN + KO): - `config.restartNow` — button label - `config.restarting` — toast during the stop→start window - `config.restarted` — success confirmation (2s) No server-side changes — this is a pure UX layer over the existing `stop_server` / `start_server` Tauri commands. Hot-reload of individual env vars (without model reload) was considered and rejected for this patch: it would require new server infrastructure (`RuntimeConfig` singleton + admin endpoint + always-bake for TurboQuant) and a "partial apply" mental model where some knobs hot- reload and others don't. The 1-click restart pattern keeps the all-or-nothing guarantee (server's actual state == last spawn env) while still feeling fast.
1 parent 23d67c4 commit 4cb86dc

7 files changed

Lines changed: 91 additions & 21 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/lumen-app/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "lumen-app"
3-
version = "0.4.5"
3+
version = "0.4.6"
44
edition.workspace = true
55
rust-version.workspace = true
66
license.workspace = true

crates/lumen-app/frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "lumen-app-frontend",
33
"private": true,
4-
"version": "0.4.5",
4+
"version": "0.4.6",
55
"type": "module",
66
"scripts": {
77
"predev": "cargo build --manifest-path ../../../Cargo.toml -p lumen-server --release",

crates/lumen-app/frontend/src/App.svelte

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@
108108
// download lines wouldn't disappear after the 3s auto-dismiss timer.
109109
let downloads = new SvelteMap<string, DownloadProgress>();
110110
let statusMessage = $state<string | null>(null);
111+
// Optional inline action attached to the current statusMessage. Used by
112+
// `savedToast()` to surface a "Restart now" button on the same toast when
113+
// a config save would otherwise require manual Stop → Start. `null` =
114+
// plain informational toast (legacy behavior).
115+
let statusAction = $state<{ label: string; onClick: () => Promise<void> } | null>(null);
111116
let typedEnvKeys = $state<Set<string>>(new Set());
112117
let catalog = $state<Catalog>({ families: [], recommended: [], embeddings: [] });
113118
let systemInfo = $state<SystemInfo | null>(null);
@@ -435,32 +440,80 @@
435440
config = await api.updateServerConfig(config.server);
436441
}
437442
438-
// Shared toast text — restart hint only when the server is currently
439-
// running (otherwise just "Saved", since next start will pick the new
440-
// env vars naturally). Without this, users who change TurboQuant /
441-
// KV-quant / ctx caps while the server is running see the UI update
442-
// but the running process keeps the OLD env vars until manual
443-
// Stop → Start. The QUANT / CONTEXT / SERVER cards previously had no
444-
// hint at all (only env_overrides did).
445-
function savedToast(): string {
443+
// One-click "Stop → wait → Start" — used by the inline action button on
444+
// the savedToast when the server is running. Polls `serverStatus` until
445+
// it drains to `stopped` (or `errored`) before issuing `startServer` so
446+
// the new env vars from `spawn_server_command` are actually used. Without
447+
// the drain wait, `startServer` would race the still-tearing-down
448+
// supervisor and surface a stale state.
449+
async function restartServer() {
450+
statusMessage = t("config.restarting");
451+
statusAction = null;
452+
try {
453+
if (status.state === "running" || status.state === "starting") {
454+
status = await api.stopServer();
455+
let waited = 0;
456+
const POLL_MS = 200;
457+
const TIMEOUT_MS = 30_000;
458+
while (
459+
status.state !== "stopped" &&
460+
status.state !== "errored" &&
461+
waited < TIMEOUT_MS
462+
) {
463+
await new Promise((r) => setTimeout(r, POLL_MS));
464+
status = await api.serverStatus();
465+
waited += POLL_MS;
466+
}
467+
}
468+
status = await api.startServer();
469+
statusMessage = t("config.restarted");
470+
setTimeout(() => (statusMessage = null), 2000);
471+
} catch (e) {
472+
statusMessage = String(e);
473+
setTimeout(() => (statusMessage = null), 4000);
474+
}
475+
}
476+
477+
// Shared post-save toast. Two modes:
478+
// * server running → "Saved. Restart to apply" + inline [Restart now]
479+
// button (calls `restartServer`).
480+
// * server stopped → plain "Saved." (next start picks up the change
481+
// naturally — no restart needed).
482+
// Without this, users who change TurboQuant / KV-quant / ctx caps while
483+
// the server is running see the UI update but the running process keeps
484+
// the OLD env vars until manual Stop → Start. The QUANT / CONTEXT /
485+
// SERVER cards previously had no hint at all (only env_overrides did).
486+
function savedToast() {
446487
if (status.state === "running" || status.state === "starting") {
447-
return t("config.savedRestartHint");
488+
statusMessage = t("config.savedRestartHint");
489+
statusAction = { label: t("config.restartNow"), onClick: restartServer };
490+
setTimeout(() => {
491+
if (statusMessage === t("config.savedRestartHint")) {
492+
statusMessage = null;
493+
statusAction = null;
494+
}
495+
}, 6000);
496+
} else {
497+
statusMessage = t("config.saved");
498+
statusAction = null;
499+
setTimeout(() => {
500+
if (statusMessage === t("config.saved")) {
501+
statusMessage = null;
502+
}
503+
}, 2000);
448504
}
449-
return t("config.saved");
450505
}
451506
452507
async function saveServer() {
453508
if (!config) return;
454509
config = await api.updateServerConfig(config.server);
455-
statusMessage = savedToast();
456-
setTimeout(() => (statusMessage = null), 3000);
510+
savedToast();
457511
}
458512
459513
async function saveQuant() {
460514
if (!config) return;
461515
config = await api.updateQuantConfig(config.quant);
462-
statusMessage = savedToast();
463-
setTimeout(() => (statusMessage = null), 3000);
516+
savedToast();
464517
}
465518
466519
async function saveContext() {
@@ -469,8 +522,7 @@
469522
// ctx affects KV-cache headroom in the tuned memory recommendation
470523
// (~1 GB per 8K tokens). Re-sync so saved caps follow.
471524
await syncTunedMemoryCaps();
472-
statusMessage = savedToast();
473-
setTimeout(() => (statusMessage = null), 3000);
525+
savedToast();
474526
}
475527
476528
async function resetMemoryCaps() {
@@ -635,7 +687,15 @@
635687
{/if}
636688
</div>
637689
<div class="ml-auto flex items-center gap-2.5">
638-
{#if statusMessage}<span class="dim">{statusMessage}</span>{/if}
690+
{#if statusMessage}
691+
<span class="dim">{statusMessage}</span>
692+
{#if statusAction}
693+
<button
694+
class="text-[11px] px-2 py-0.5 border border-accent text-accent rounded-md hover:bg-accent/6 transition-colors"
695+
onclick={statusAction.onClick}
696+
>{statusAction.label}</button>
697+
{/if}
698+
{/if}
639699
{#if memoryUsage}
640700
{@const usedGb = memoryUsage.used_bytes / 1024 ** 3}
641701
{@const totalGb = memoryUsage.total_bytes / 1024 ** 3}

crates/lumen-app/frontend/src/messages/en.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ export const en: Record<string, string> = {
282282
// caps, etc.) to take effect.
283283
"config.savedRestartHint": "Saved. Restart the server to apply (Stop → Start).",
284284
"config.saved": "Saved.",
285+
// Inline action button on the savedToast — single-click "Stop → wait →
286+
// Start" so users don't have to find the toggle in the header.
287+
"config.restartNow": "Restart now",
288+
"config.restarting": "Restarting server…",
289+
"config.restarted": "Server restarted.",
285290

286291
// ── DoctorPanel ─────────────────────────────────────────────────
287292
"doctor.title": "Doctor",

crates/lumen-app/frontend/src/messages/ko.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,11 @@ export const ko: Record<string, string> = {
272272
// (TurboQuant, KV quant, ctx caps 등) 는 재시작 시점에만 반영됨.
273273
"config.savedRestartHint": "저장됨. 적용하려면 서버 재시작 (중지 → 시작).",
274274
"config.saved": "저장됨.",
275+
// savedToast 옆 인라인 액션 버튼 — 헤더의 토글을 찾지 않아도
276+
// 한 번에 "중지 → 대기 → 시작" 까지 끝내는 단축키.
277+
"config.restartNow": "지금 재시작",
278+
"config.restarting": "서버 재시작 중…",
279+
"config.restarted": "서버 재시작 완료.",
275280

276281
// ── 진단 패널 ───────────────────────────────────────────────────
277282
"doctor.title": "진단",

crates/lumen-app/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "Lumen",
4-
"version": "0.4.5",
4+
"version": "0.4.6",
55
"identifier": "ai.lumen.app",
66
"build": {
77
"frontendDist": "frontend/dist",

0 commit comments

Comments
 (0)