Skip to content

feat(server): ✨ change AI settings from the status page - #135

Merged
vitofico merged 1 commit into
mainfrom
feat/admin-settings-panel
Sep 28, 2026
Merged

vitofico merged 1 commit into
mainfrom
feat/admin-settings-panel

Conversation

@vitofico

Copy link
Copy Markdown
Owner

Closes #102.

Stacked on #123: the base is feat/admin-status-page, so this diff shows only this change. Once #123 merges, this branch gets rebased onto main and retargeted.

The story

Phillip's AI setup failed on a CPU-only Ollama: the model needed longer than the 120-second limit, and looking books up on Wikipedia and Open Library made every prompt longer still. The fix was two lines in .env, QUIRE_SERVER_AI_TIMEOUT_S and QUIRE_SERVER_AI_SOURCES=. Getting there meant finding the variables in the docs, editing a file on the server and recreating the container, because a plain docker compose restart keeps the old values. #123 added a page that shows what the server is running with. This lets the same page change the AI settings people actually tune, and they apply to the next request.

What changes in practice

  • An "AI settings" form on /quire-admin with six settings: the insight time limit, the Reader Profile time limit, which sites to look books up on, generations per minute, generations per reader per day, and regenerations per reader per day. Press Save and the next AI request uses the new values, with no .env edit and no restart. Raising the insight limit to 285 seconds for a CPU model is typing 285 and pressing Save. The app follows on its own: it sizes its wait from generation_timeout_s in GET /ai/v1/config, which now reports the value in force.
  • The environment still wins. When .env or a Kubernetes manifest sets one of the six, the page shows that field greyed out with "Set in the server's environment", because a value saved on the page would silently disagree with the file the server is managed from. Otherwise a saved value wins over the built-in default. Each saved value says who saved it and when, and a Use default button removes it.
  • Saved values live in the database, in a new server_settings table (migration ai_008 on the ai branch), so they survive restarts and upgrades. The process that saves applies the change at once. Any other server process re-reads the table within 30 seconds, at its next AI request.
  • The same for scripts: PUT /quire-admin/v1/settings with {"QUIRE_SERVER_AI_TIMEOUT_S": 285}, where null brings back the default. It saves every change or none: one refused value and the answer is a 422 naming each variable and the rule it broke, for example "QUIRE_SERVER_AI_TIMEOUT_S must be a number of seconds above 0, at most 3600." The ceilings (3600 seconds, and 1000000 for the counts) only keep out typos; the environment has none.
  • Two admins do not undo each other. The form sends back the values it showed, and only fields the admin edited count as changes. So a page left open while someone else saved a new budget does not put the old budget back when its owner changes the time limit.
  • The same guards as the Test AI connection button: only the accounts in QUIRE_SERVER_ADMIN_USERS, and requests the browser marks as coming from another site are refused, so a page you visit cannot use your saved login to change settings.
  • Two deviations from the Web UI for Configuration #102 roadmap. The model stays in .env, next to the provider address and the API key. The server decides at startup, from the model and the address, whether AI is configured at all, and stored insights are filed under the model that wrote them, so changing it at runtime needs that startup decision moved first, which is a change of its own. The Reader Profile time limit is added: the configuration guide says to raise it together with the insight limit, and a page that could change only one of the pair would invite the mismatch the guide warns about.

What does not change

Until an admin saves something, every setting behaves exactly as before. A server without QUIRE_SERVER_ADMIN_USERS has no page and no endpoint. With AI switched off there is no form, the endpoint answers 404, and the table is never read, since it lives on the ai migration branch like the other AI tables. GET /ai/v1/config keeps its shape. The guide (docs/configuration.md, "Changing AI settings on the page"), the server README and the endpoint table in docs/sync-api.md describe the form and the precedence; each of the six settings' entries points to it.

How it was checked

  • 67 new tests. Unit: the parsing and bounds of each setting, precedence, the form's rules for what counts as a change, rendering and escaping. Integration, against Postgres and the real calibre-web login path: a saved value reaching /ai/v1/config and the insight service, a second app instance picking it up, the environment winning and staying locked, all-or-nothing refusals, null resetting, non-admins and cross-site requests refused on both the API and the form, AI switched off, the form saving only what changed, and a timeout message quoting the limit saved on the page, and a save that holds even when reading it back fails.
  • I broke the code on purpose fourteen times (environment no longer wins, the insight service not told, no re-read before AI requests, the PUT without the cross-site guard, /ai/v1/config reading the environment, locked settings accepted, the form saving untouched fields, a zero time limit accepted, the timeout message quoting the startup value, and one for each review fix below), and each time a test failed.
  • The full suite passes in all three CI modes: 881 passed (full), 744 (sync only), 779 (AI only). ruff 0.16.8 is clean.
  • I used it in a browser against a throwaway Postgres, with stand-ins for calibre-web and the AI provider, and one setting given in the environment. Through a real form POST I saved a daily budget and unticked Wikipedia, and the AI provider section stopped listing Wikipedia. Use default reset the budget alone. A zero time limit showed "Nothing was saved" with the reason. I checked dark mode, and light mode at phone width.
  • A second-opinion review of the diff found no way around the login or the cross-site guard, and five real problems, all fixed in this commit with a test each: an old page could undo another admin's save; a huge rate (10^309) was accepted and then crashed the rate limiter; Infinity in the JSON body gave a 500; raising the rate left requests already waiting asleep for their old, longer wait; and a save could report success but not apply if reading it back failed right after the write.

In short: the AI settings people actually tune can now be changed from the status page and apply to the next request, while anything the environment sets stays in charge.

Issue #102, phase 3. The /quire-admin page gains an "AI settings" form, and
PUT /quire-admin/v1/settings does the same for scripts. Six settings can
change without editing .env or restarting: both AI time limits, the lookup
sources, the rate limit and the two daily quotas.

The value in force is the environment's if it sets one (shown locked on
the page), else a value saved on the page (new server_settings table,
migration ai_008), else the default. The orchestrator takes new values
through apply_tunables, and raising the rate wakes requests already
waiting under the old one. The AI router refreshes saved values before
each request, re-reading the table at most every 30 s, so other processes
follow; the saving process applies its change even if the read-back fails.
Writes are all or nothing, bounded (time limits up to 3600 s, counts up
to 1000000), and sit behind the same admin allowlist and same-origin guard
as the probe. The form sends the values it showed, so only fields the
admin edited count as changes and an old page cannot undo another admin's
save. The model, provider address and API key stay environment-only.
@vitofico
vitofico force-pushed the feat/admin-settings-panel branch from 6383734 to 6d3522e Compare September 28, 2026 13:53
@vitofico
vitofico changed the base branch from feat/admin-status-page to main September 28, 2026 13:53
@vitofico
vitofico merged commit 0cd7e83 into main Sep 28, 2026
5 checks passed
@vitofico
vitofico deleted the feat/admin-settings-panel branch September 28, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Web UI for Configuration

1 participant