Harden the backend, modernise the frontend, and make LLM setup a choice from a list - #30
Merged
Merged
Conversation
Addresses the weak spots found in the review, plus three requested features.
Security
- Refuse API requests from off-machine until a password is set. Loopback is
unchanged (no password, no prompt), so local use is unaffected, but the
documented Traefik deployment no longer serves the library, the audio and the
stored Hugging Face token to anyone who finds the hostname.
- Stop echoing secrets: settings report whether a token/key is stored, never
its value, and an unedited field cannot overwrite a stored credential.
- Add a Security panel, an API token for headless clients, and login throttling.
Reliability
- Requeue interrupted transcriptions on restart instead of flipping them to
'error' with no explanation; mark the unresumable ones with a reason.
- Replace ad-hoc ALTER TABLE statements wrapped in `except: pass` with numbered
migrations recorded in schema_version, which fail loudly.
- Escape FTS5 query terms so ordinary punctuation ("covid-19", "C++") stops
raising syntax errors and silently downgrading the search.
- Chunk LLM analysis that exceeds the context budget, so long recordings are
summarised in parts and merged rather than truncated from the front.
- Prefetch URL downloads concurrently while keeping model inference serialized.
- Keep a tombstone for expired jobs so their routes answer 410 with the
recording id rather than a 404.
Features
- Export/import the whole library as a zip bundle (settings excluded).
- WebVTT and CSV exports, in the web UI, bulk menu and TUI.
- Optional automatic summary when a captured meeting finishes.
Maintenance
- Split the 4,800-line inline script into 20 ES modules under frontend/js/,
loaded natively — no build step, no bundler.
- Add route-level tests (128 -> 329) and make CI run the whole suite instead of
ten named files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JmgRm98BjMyfRok7eN9L12
Configuring an analysis backend meant typing a base URL and hoping. Three things went wrong often enough to fix in code rather than documentation. Provider presets - Ollama, LM Studio, Unsloth Studio, llama.cpp, vLLM, Jan, LocalAI, OpenRouter and a generic OpenAI-compatible option. Each fills in its address, states whether a key is required and what it looks like, and links to its setup docs. - Unsloth Studio always needs a key (sk-unsloth-); LM Studio needs none. Model downloading is advertised only for Ollama, which is the only one that has it. Finding a server - GET /api/llm/detect probes the well-known ports and reports what answered, which models it has loaded, and whether it wants a key. One click in the UI adopts it. A 401 still counts as found — that is Unsloth's normal state. Addresses - Every one of these tools shows a URL ending in /v1, but AmicoScript appends /v1/chat/completions itself, so pasting what LM Studio displayed produced /v1/v1 and a 404 that looked like a broken server. Base URLs are now normalized, and the UI reports what changed. Docker - docker-compose.yml maps host.docker.internal to the host gateway, which Docker Desktop provides and Linux does not. - The app detects that it is containerised and rewrites localhost addresses to the host alias, saying so; scanning probes the host, not the container. Hosted providers - OpenRouter and other remote endpoints are gated behind an explicit confirmation, because they receive the transcript text. Manual analyses and automatic meeting summaries both refuse to run until it is given. Also: connection failures now explain which tool is not running, whether the key was rejected, or whether the address has a stray /v1; and LLM Settings moved to the main sidebar, having previously been reachable only after transcribing something. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JmgRm98BjMyfRok7eN9L12
Drove the real application in a browser — upload, live transcription, segment
editing, all six exports, folders, tags, search, backup round trip, the LLM
setup panel — with only Whisper inference stubbed (huggingface.co is blocked in
this environment, so no model could be downloaded). Everything else ran for
real: the routes, the job queue, the worker, SSE, SQLite and the whole UI.
Bugs it found
- Creating a tag that already exists returned HTTP 500: the unique constraint
reached the client as a server error. Now a 409 naming the clash, with the
same treatment for renaming a tag onto an existing name.
- API errors were shown to users as raw JSON — `Save failed: {"detail":"…"}`.
Folder, tag and library actions now surface the sentence the server sent.
- The password fields were not inside a form and had no username field, so
browsers warned and password managers had nothing to bind a credential to.
The Hugging Face and LLM key inputs are now marked so password managers skip
them: they are tokens, not logins.
Gaps it exposed
- No way to retry. A failed transcription was a dead end even though the audio
was still on disk — the only recovery was deleting the recording and
re-importing the file. Adds POST /api/recordings/{id}/retry plus a button on
failed, cancelled and interrupted rows, reusing the original options. The
requeue logic is now shared with startup recovery rather than duplicated.
- The reason a recording was interrupted only existed as a tooltip, invisible on
a touch screen. It is shown in the card.
- Recordings never showed their origin, so an auto-captured call and a dragged-in
file looked identical. Captures and link imports now carry a badge.
- Automatic summaries were indistinguishable from ones the user requested; they
are labelled "automatic".
- Library rows carry a data-recording-id so a row can be identified without
counting its position.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JmgRm98BjMyfRok7eN9L12
Auditing the terminal UI against this session's work turned up a regression I had introduced, plus a set of features it never gained. The regression - Masking secrets in the API broke the TUI's settings form: it still read `hf_token`, so the field came back empty, and saving wrote that emptiness back. Opening settings and pressing Save erased a stored Hugging Face token. The form now shows the masked preview, tracks that it is a placeholder, and sends the __unchanged__ sentinel unless the user actually types something. The LLM API key gets the same treatment. Parity with the web UI - /retry and Ctrl+R re-run a failed, cancelled or interrupted transcription. - /backup export and /backup import for the library bundle. - /llm-providers lists the supported backends; /llm-detect scans for a running server and offers to adopt it. - Settings gained provider, context budget, cloud consent and the auto-summarise toggle. - /export accepts vtt and csv and rejects unknown formats with the valid list. Display and errors - interrupted and cancelled have their own marks; captured meetings and link imports are prefixed in the list. - Transport errors are explained: a 401 says which token to set and where it comes from, a 410 points at the library, an unreachable backend says so. - Durations under an hour keep their seconds (a 22s clip read "0h 00m"), and the MODEL column reads the model out of transcription_options, where it lives. Verified by driving the real TUI headless against a running backend: the retry, backup round trip, provider list and VTT export all go through, and a save with an untouched token leaves it intact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JmgRm98BjMyfRok7eN9L12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Started as a review of where AmicoScript was weakest, then grew into fixing those, shipping three requested features, reworking LLM configuration, and running the app end to end to see what that turned up.
79 files changed · 439 tests passing (was 128).
🔐 Access control
AmicoScript now refuses network requests until a password is set. The project documents a Traefik deployment on a public domain, but every API route was open there — anyone who found the hostname could read the library, download the audio, and read the stored Hugging Face token straight out of
GET /api/settings.Requests from the machine AmicoScript runs on behave exactly as before: no password, no prompt. Requests from anywhere else are refused with an explanation until a password exists. Exposing the app unconfigured now fails closed.
AMICOSCRIPT_AUTH=auto|always|off,AMICOSCRIPT_PASSWORDfor startupX-Forwarded-For🧠 LLM setup that does not require guesswork
/v1trap is gone. Every one of these tools displays a URL ending in/v1, but AmicoScript appends/v1/chat/completionsitself, so pasting what LM Studio showed you produced/v1/v1/…and a 404 that looked like a broken server. All four forms now resolve to the same thing, with the change explained.docker-compose.ymlmapshost.docker.internalto the host gateway (Docker Desktop provides it, Linux does not), and the app rewriteslocalhostaddresses when containerised, saying so.✨ Features
settings.json.<v Name>voice spans; CSV ships with a BOM for Excel and defuses formula injection in transcript text.🔧 Reliability
errorwith no explanation. Anything whose audio is on disk is requeued; anything unresumable is markedinterruptedwith a reason.covid-19,C++,hello "worldand a bareANDwere FTS5 syntax errors that silently downgraded the query.ALTER TABLEwrapped inexcept: pass, which left broken databases looking healthy.🧹 Maintenance
<script>. Still no build step, no bundler. Split with a real JS parser computing the import graph, then verified against the pre-split page for behavioural parity.test_search_escaping.pyre-implemented the code it claimed to test.🐛 Found by actually running the app
Drove the real application in a browser — upload, live transcription, editing, all six exports, folders, tags, search, backup round trip — with only Whisper inference stubbed (huggingface.co is blocked in the dev sandbox).
Save failed: {"detail":"…"}⌨️ The TUI
Auditing it turned up a regression this branch introduced: masking secrets broke the settings form, so opening it and pressing Save erased the stored Hugging Face token. Fixed, plus
/retry,/backup export|import,/llm-providers,/llm-detect, VTT/CSV export, the new status marks, and readable transport errors.Reviewer notes
AMICOSCRIPT_AUTH=always.GET /api/settingschanged shape —hf_tokenis nowhf_token_set+hf_token_preview;GET /api/llm/settingsreportsllm_api_key_set. Any other client reading those fields needs the same treatment the TUI got.docs/ROADMAP.md.🤖 Generated with Claude Code
https://claude.ai/code/session_01JmgRm98BjMyfRok7eN9L12
Generated by Claude Code