Commit graph

20 commits

Author SHA1 Message Date
avi
b4e335d720 Model registry: Large v3 speed label corrected to measured ~0.3-0.4x real-time (CPU int8 quiet-machine benchmark Sep 22: 0.27x on 300s, 0.43x on 1200s; prior '~1x' was optimistic) 2026-09-22 10:38:34 -05:00
avi
758842ae2a Icons: brand blue (#6190E5) for in-app sonar logo and window/taskbar icon; recolor icon.png, ship as window icon 2026-09-21 15:45:12 -05:00
avi
514d75fe92 Raw provider crash fails the job instead of escaping into the sweep requeue loop
A non-AIError from transcribe/summarize (av.InvalidDataError on corrupt
audio, seen live with 'My recording 63') escaped the consumer, left the
row 'running', and got requeued at every engine restart forever. Both
runners now catch it, fail the row with the exception type in the
message, and keep a usable transcript (summarize completes with a
'Summary failed' note). Regression tests pin both paths (proven red
without the fix).
2026-09-19 15:24:50 -05:00
avi
15381b2c41 Commit upload session row before responding; client's follow-up GET raced the post-response commit into a 404 2026-09-18 23:17:04 -05:00
avi
11bb13cb3d Fix re-summarize running one voice behind: commit the job row before poking the inline queue.
The reprocess endpoint poked transport_enqueue while its request session
was still uncommitted; the inline worker woke instantly, read the job row
with its own session, and summarized with the PREVIOUS click's tone
(TRACE: click Funny -> worker read 'dry, witty'; click Neutral -> ran
'funny'). The UI disabled the voice chips for the whole wrong-voice run,
which read as 'spinner does nothing until I leave and come back'.

Rows are the real queue: commit before the poke. Regression test pins
worker tone to the requested tone and Neutral clearing a prior voice;
verified red without the fix, green with it.
2026-09-18 17:03:37 -05:00
avi
39aaa04a00 Honest summarize progress from real completed work units
Backend: new ContractProgress parses the streamed summary JSON and
advances only when contract units finish — first token 5%, each closed
key 5→90 (arrays step per closed item), root close 95, stored=100.
Replaces the char-count ticker that counted reasoning chars against a
guessed 1200-char output and sat frozen at 99 for minutes. No timers,
no elapsed-time estimates; the unfinishable thinking span honestly
earns only the alive-tick. Both adapters (openai_compat, ollama) feed
the accumulated content prefix. /jobs now carries the job's tone.

App: summarize progress shows a real percentage + determinate bar with
the tone named ('Summarizing dry wit… 47% · 1:12') in both detail
widgets; indeterminate only while queued or pre-first-token.

Tests: milestone sequence verified identical for char-by-char and
chunked streaming; adapter thinking-phase test updated to the new
contract (83 passed).
2026-09-18 15:42:16 -05:00
avi
61f2124b1c AI progress: honest milestone bar from closed JSON contract units
Replace the char-count estimate (EST_OUTPUT_CHARS) with ContractProgress:
points are earned only when real output units complete — first token (5),
each closed contract key / array item (5-90), root closed (95), stored (100).
Reasoning/thinking streams no longer fake progress: the first thinking
delta fires the first-token milestone ('the model is alive') and holds.
Surface the job's tone through ProcessingJobOut/JobInfo so the bar can
label itself 'Summarizing dry wit…'. Test rewritten to pin the new
honest-thinking semantics.
2026-09-18 15:00:04 -05:00
avi
6ec811393a Migrations no longer clobber app logging: drop fileConfig(alembic.ini) from migrations/env.py. fileConfig rewrote the root logger (WARN + disable_existing) during the engine's startup migration, silently muting every INFO line afterwards — engine.log froze at the migration's last line and uvicorn access logs vanished, swallowing the TRACE instrumentation. The app owns logging (main.py basicConfig); alembic.ini's [loggers] applied only to standalone CLI runs. Verified: fresh engine boot now logs access lines live (GET /jobs flowing), 83 tests pass. 2026-09-18 13:13:40 -05:00
avi
768b75915a Instrumentation only: TRACE log lines for the tone-mismatch + progress investigation — reprocess (rid, requested tone, prior tone/status), summarize-start (rid, job, tone the worker actually read), progress (every distinct pct the provider emits), store_summary (rid, tone saved). rid minted per click joins all lines; zero behavior change. 2026-09-18 10:49:28 -05:00
avi
3061629dc5 Summarize: LAN primary with local-Ollama rescue + in-app failure guidance
- Engine: when the primary summarizer is out of retries (or misconfigured),
  run_summarize now finishes the job on the configured rescue provider
  (SHONAR_LLM_FALLBACK_*), tags the summary with the provider that wrote it,
  and stores a human note in job.error; success clears stale notes.
- App (auto/lan): passes Ollama as the rescue provider when it is up.
- Detail screen: shows the rescue-swap note in plain words, a red
  'Summary failed' line with Settings -> Summarizer fix instructions and a
  Retry summary button on hard failure.
- Settings copy explains the fallback. 3 new pytest cases (14/14 pass);
  live E2E on 2026-09-18: sarcastic summary v6 via LAN on attempt 2.
2026-09-17 21:44:02 -05:00
avi
0a0702f15d Summarize progress counts reasoning tokens: thinking models stream most of the run in reasoning_content, which the ticker ignored — UI sat at 'waiting for model' then jumped to done with no percentage 2026-09-16 18:28:59 -05:00
avi
3e1ec8eff8 Queue wait is visible: 'Queued… · Ns' + created_at on job payloads 2026-09-16 16:15:32 -05:00
avi
ba54cb853b Adopt engine jobs at startup and on 'already running'; live work outranks a saved report 2026-09-15 19:45:02 -05:00
avi
7fa99e8291 Summarize streams with real progress: adapters reassemble SSE/NDJSON deltas and report 0-99% as tokens arrive 2026-09-15 19:45:02 -05:00
avi
b63f49241d Summarize tone: re-summarize in a chosen voice (backend)
POST /reprocess?job=summarize&tone=<voice> persists the voice on the job
row (queue carries only ids, so a sweep re-enqueue keeps it), the LLM
prompt appends 'write every field in a <tone> tone — the tone colors
the wording, never the facts', and the resulting summary records its
tone (SummaryOut.tone). Plain re-summarize clears a previous tone.
Migration tone0000000001 (summaries.tone, processing_jobs.tone).
78 passed, 1 skipped; ruff clean.
2026-09-15 14:24:36 -05:00
avi
6599935e28 Config: drop unused search_backend knob
Nothing read it; services/search.py already picks SQLite vs Postgres by
dialect. The 'postgres_fts' default was a leftover from the server-era
config and only invited confusion.
2026-09-15 12:46:18 -05:00
avi
087e8386f5 Transcription: dwindle model choice to Whisper Base + Whisper Large v3
Registry now lists exactly two models instead of five:
- 'Whisper Base' — names the actual bundled model (was 'Base (default)').
- 'Whisper Large v3' — kept; honest copy on size/speed/RAM.
tiny/small/medium removed from the registry (validate_model_name now
rejects them; existing rows keep their saved model — all current data
is 'base', still valid).
Detail screen shows the model that WILL run by real display name
('Whisper Base (default)') instead of 'Use default (base)'; the picker
lists the two models with the default marked inline.
Backend 78 passed/1 skipped; app 34/34; engine hot-reloaded clean.
2026-09-15 07:24:57 -05:00
avi
c25d3ab150 Bundle the whisper base model + one-shot standalone provisioning
- models/hf-cache/ vendors Systran/faster-whisper-base (142MB, gitignored);
  engine spawn pins HF_HUB_CACHE there, so transcription works with a cold
  ~/.cache and fully offline
- verified: HOME=/tmp/fakehome HF_HUB_OFFLINE=1 model loads and transcribes
  via the bundled cache alone
- setup-standalone.sh: idempotent fresh-clone provisioning (local venv,
  model copy-or-download, engine boot smoke on :8123) — runs green
- backend/README rewritten for the desktop bundled-lite reality (was
  pointing at a nonexistent root README/docs and a Postgres quickstart)

Kotlin suite still green (gradlew test, 0 failures)
2026-09-14 21:05:41 -05:00
avi
a94609c07b Backend tests run on SQLite by default (78 passed, 1 skipped); no Postgres needed
- conftest: default SHONAR_TEST_DATABASE_URL is a temp SQLite file, matching
  the bundled-lite engine; set the env var to a PG URL to exercise that path
- models.py: add sqlite_where to the two partial unique indexes — without it
  SQLite built a FULL unique index on recording_id (WHERE not carried over),
  wrongly blocking a second export asset per recording
- test_m9: pass UUID objects (not str) to direct ORM inserts/gets; SQLite's
  GUID bind processor rejects strings (asyncpg tolerated them)

Verified: pytest -q = 78 passed, 1 skipped; ruff check clean
2026-09-14 20:59:49 -05:00
avi
76c867fca4 Standalone Shonar Desktop: vendor portable sources + local engine; decouple from ~/Projects/Shonar
- shared/ = portable Android-origin sources vendored from deferred/desktop-server
  (app/build.gradle.kts srcDir repointed; PlaybackController.kt excluded as Android-only)
- backend/ = bundled-lite engine (SQLite + inline queue); .venv symlinked from the
  old checkout, PYTHONPATH pins THIS backend's code over any editable install
- repoRoot() resolves this project dir (env SHONAR_REPO still wins); desktop-dev.sh
  watches shared/ + backend/
- Verified: :app:compileKotlin + :app:test green (23 tests); engine boots on :8010,
  self-migrates, /healthz ok
2026-09-14 17:14:54 -05:00