diora-web/docs/ai.md

4.9 KiB

KI: Claude schlägt vor, der Nutzer entscheidet

Since 2026-10-24. Organisation help with Claude (Anthropic) across diora: books and magazines first; podcasts, news/blogs and projects follow (same building blocks).

The key: the user's own, end-to-end encrypted, only on the devices

Two providers, chosen in the settings (decided 2026-10-25): Anthropic (the official SDK) or OpenRouter (the same Claude model, anthropic/claude-opus-5.5). OpenRouter's Anthropic-compatible endpoint refuses the headers a browser call needs (anthropic-version …, checked by a CORS preflight), so diora talks to OpenRouter's chat completions instead: plain fetch, response_format json_schema (strict), reasoning.effort; no fallback on refusals, and OpenRouter sees what is sent too. The vault keeps {provider, anthropic_key, openrouter_key} (an old {anthropic_key} still reads as Anthropic); a key is checked before it is kept (Anthropic: the model's info; OpenRouter: GET /api/v1/key).

Decided with the user: the API key is entered in the app ("KI-Assistent", the ✦ of a feature or the app menu) and stored like the books — encrypted with the book key on the device, kept by the server as a blob it can't read:

  • GET/POST /accounts/vault/<name>/ (accounts.UserVault, name ai): {ct, iv}; {ct: ''} removes it. Its own store, not the library prefs (/books/prefs/ is rewritten whole when the folder order is saved).
  • The devices decrypt it into memory only and call Claude directly from the page (browser, the apps' WebView, the desktop). The diora server sees neither the key nor what is sent; Anthropic sees what a feature needs (titles, authors, folder names …). Anthropic bills the user.
  • Without the book key on a device (or after a password change, which changes the book key) the key can't be read: the settings ask for it again. Not on diora Reader (e-readers).
  • Server-side jobs (in the background, without an app open) were left out on purpose: they would need the key on the server. The project Radar has its own server key (docs/projects.md). Podcast articles wait on the server as a wish until a device with the key is open (docs/transcription.md).

Building blocks (static/js/ai.js, an ES module)

  • The official SDK, @anthropic-ai/sdk 0.133.0, bundled for the browser with esbuild 0.28.2: static/vendor/anthropic-sdk-0.133.0.mjs (licence beside it). dangerouslyAllowBrowser: the key is the user's own. To update: npm install @anthropic-ai/sdk@<v> esbuild in an empty folder, esbuild entry.mjs --bundle --format=esm --platform=browser --target=es2020 --minify with export { default } from '@anthropic-ai/sdk', and change the import in ai.js.
  • DioraAI.ask({system, prompt, schema, effort}): model claude-opus-5-5, structured output (output_config.format json_schema), effort low by default, streamed, fallbacks: "default" (beta server-side-fallback-2026-07-01) for declined requests; refusals and over-long answers become plain German messages.
  • ask also takes onProgress(words) (words written so far while streaming; OpenRouter is not streamed and reports once at the end) and signal (an AbortSignal), both optional; DioraAI.modelName() is the model of the chosen provider. Used by the podcast articles.
  • DioraAI.review({title, intro, items, onApply}): the suggestions with checkboxes — nothing changes until the user ticks and presses "Übernehmen".
  • DioraAI.settings(), withKey(fn), progress(), showError(), toast(); the sheets close with Android's back key; ⚙ in each sheet opens the settings.

Features

area where what Claude gets what it suggests
Bücher, Zeitschriften ✦ in the library's head (static/js/app/55-library-tools.js) per book: id, title, author, folders, series, magazine mark; the existing folders folders to add (existing first; new ones only for ≥ 3 books), series with volume, cleaned title/author where broken, magazine mark
Podcasts ✦ in Abos (diora-android web-native/static/native-podcast.js, copied to static/podcasts/) per subscription: title, author, folders, level, heard in 12 months, publishing interval, three newest episode titles folders to add (≤ 2), level Stamm / Strom / Auf Abruf (only with a clear reason)
Nachrichten, Blogs ✦ in the feeds' bar (static/js/feeds.js; the apps' section bar presses it) per source: title, address, kind, Ressort or folders, recent titles shown Ressort for news, folders for blogs, news ↔ blog when clearly wrong
Podcast-Artikel ✦ Artikel in an episode (static/js/podcast-article.js, docs/transcription.md) the episode's transcript (feed or Whisper), title, author, shownotes, chapters an article from the speakers' standpoint with summary and sections, saved to the episode and to Blogs (no review sheet: it is new text, removable)

Applied through _updateBookMeta (the encrypted meta, as when editing by hand).