diff --git a/desktop/src/main/index.ts b/desktop/src/main/index.ts index 42d8043a..347c52b9 100644 --- a/desktop/src/main/index.ts +++ b/desktop/src/main/index.ts @@ -77,7 +77,12 @@ ipcMain.handle('wizard:complete', async (_e, input: WizardInput) => { const b = base() await startStack(b, process.env) await waitHealthy(b) - await runAdminFixture(b, input.adminEmail, input.adminPassword) + const admin = await runAdminFixture(b, input.adminEmail, input.adminPassword) + if (admin.code !== 0) { + throw new Error( + `Could not set up the login: ${admin.stderr.trim() || admin.stdout.trim() || 'admin fixture failed'}` + ) + } saveConfig(cfg) return { ok: true } } catch (err) { diff --git a/desktop/src/renderer/wizard.ts b/desktop/src/renderer/wizard.ts index 90b5071b..20c2a728 100644 --- a/desktop/src/renderer/wizard.ts +++ b/desktop/src/renderer/wizard.ts @@ -1,15 +1,22 @@ /** - * First-run wizard. Secrets are auto-generated (no UI); the user picks inference + admin - * login. On submit the main process generates secrets, writes the .env, starts the stack, - * waits for HEALTHY, and runs the admin fixture. The API key is OPTIONAL — login and chat - * creation work without it, and keys can also be set later in the app's Settings (BYOK); - * the field just seeds the gateway for the simplest first run. + * First-run wizard. Secrets are auto-generated (no UI); the user picks inference + sets a + * password. The backend ships a fixed bootstrap admin (admin@lq.ai) and only a + * reset-admin-password command (no create-user), so the login email is admin@lq.ai — the + * user can change it later in the app's Settings. The API key is OPTIONAL (login/chat work + * without it; keys can also be set later in Settings). */ +const ADMIN_EMAIL = 'admin@lq.ai' + +interface Snapshot { + state: string + services?: { health: string }[] +} + export function renderWizard(root: HTMLElement, onDone: () => void): void { root.innerHTML = `

Welcome to Donna

-

Donna runs a private legal-AI workspace on your Mac. This one-time setup creates - your login and starts the engine. The first start downloads AI models and can take +

Donna runs a private legal-AI workspace on your Mac. This one-time setup sets your + password and starts the engine. The first start downloads AI models and can take several minutes.

@@ -20,26 +27,40 @@ export function renderWizard(root: HTMLElement, onDone: () => void): void {
-

2. Create your login

- - +

2. Set your password

+

Your login is ${ADMIN_EMAIL} — you can change it later in Settings → Account.

+
-

+

` const $ = (id: string): T => document.getElementById(id) as T + const status = $('status') + + // Live progress while the stack comes up (replaces a static, misleading message). + window.donna.onState((snap) => { + const s = snap as Snapshot + if (s.state === 'STACK_STARTING') { + const healthy = (s.services ?? []).filter((x) => x.health === 'healthy').length + status.style.color = '#555' + status.textContent = `Starting Donna… ${healthy}/8 services ready (first run pulls images + models; this can take a few minutes).` + } else if (s.state === 'NO_ENGINE') { + status.style.color = '#c00' + status.textContent = "Docker isn't running — start Docker Desktop and try again." + } + }) + $('go').addEventListener('click', async () => { const mode = (document.querySelector('input[name="inf"]:checked') as HTMLInputElement).value - const email = $('email').value.trim() const password = $('password').value - const err = $('err') - if (!email || password.length < 12) { - err.textContent = 'Enter an email and a password of at least 12 characters.' + if (password.length < 12) { + status.style.color = '#c00' + status.textContent = 'Choose a password of at least 12 characters.' return } const inference = @@ -49,11 +70,17 @@ export function renderWizard(root: HTMLElement, onDone: () => void): void { const goBtn = $('go') goBtn.disabled = true - err.textContent = 'Starting… downloading models on first run; this can take a few minutes.' - const res = await window.donna.completeWizard({ inference, adminEmail: email, adminPassword: password }) + status.style.color = '#555' + status.textContent = 'Starting Donna…' + const res = await window.donna.completeWizard({ + inference, + adminEmail: ADMIN_EMAIL, + adminPassword: password + }) if (res.ok) onDone() else { - err.textContent = res.error ?? 'Setup failed.' + status.style.color = '#c00' + status.textContent = res.error ?? 'Setup failed.' goBtn.disabled = false } })