|
| 1 | +# AI Review Extension |
| 2 | + |
| 3 | +Status: Architecture decided. Implementation pending (1-2 sessions). |
| 4 | +Last updated: 2026-04-20 |
| 5 | +Source: pre-audit findings (this session) + UX review. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## 1. Context |
| 10 | + |
| 11 | +Bibliogon already ships AI chapter review as a **core** feature, not a |
| 12 | +plugin (`backend/app/ai/`). Today's review supports a flat focus list |
| 13 | +(`style`, `coherence`, `pacing`, `dialogue`, `tension`), runs synchronously, |
| 14 | +returns inline JSON only, and is not persisted. |
| 15 | + |
| 16 | +Need: three explicit, user-friendly review modes - stylistic, internal |
| 17 | +consistency, simulated beta-reader - with persisted Markdown reports the |
| 18 | +author can re-open after closing the panel. Constraint: AI is core, so |
| 19 | +extend it, do not duplicate via a new plugin. |
| 20 | + |
| 21 | +## 2. Existing state |
| 22 | + |
| 23 | +Pre-audit confirms what already exists. Implementation reuses: |
| 24 | + |
| 25 | +- LLM client: [backend/app/ai/llm_client.py:26](../../backend/app/ai/llm_client.py#L26) |
| 26 | +- Review endpoint: `POST /api/ai/review` in [routes.py:217](../../backend/app/ai/routes.py#L217) |
| 27 | +- System-prompt builder: `_build_review_system_prompt` in [routes.py:171](../../backend/app/ai/routes.py#L171) |
| 28 | +- Marketing language map (reusable): [routes.py:313](../../backend/app/ai/routes.py#L313) |
| 29 | +- Frontend handler: `handleAiReview` in [Editor.tsx:572](../../frontend/src/components/Editor.tsx#L572) |
| 30 | +- Async job store: [backend/app/job_store.py](../../backend/app/job_store.py) |
| 31 | +- File-delivery precedent: `uploads/{book_id}/audiobook/` mirrors the |
| 32 | + recommended `uploads/{book_id}/reviews/` |
| 33 | +- Existing focus values: `style`, `coherence`, `pacing`, `dialogue`, `tension` |
| 34 | + |
| 35 | +## 3. Resolved decisions |
| 36 | + |
| 37 | +### 3.1 Feature naming |
| 38 | + |
| 39 | +No new feature name. Existing `ai_review` is extended with new focus |
| 40 | +values and new output modes. UI keeps the "AI Review" label. No |
| 41 | +migration, no deprecation. |
| 42 | + |
| 43 | +### 3.2 API key storage |
| 44 | + |
| 45 | +Stay on plaintext YAML at `backend/config/app.yaml` (`ai.api_key`). No |
| 46 | +keyring integration in this scope. Keyring migration for ALL AI-related |
| 47 | +keys (Anthropic + ElevenLabs + DeepL + LanguageTool) is tracked as a |
| 48 | +separate ROADMAP item under security/hygiene. Mixing two storage |
| 49 | +patterns inside one feature creates inconsistency. |
| 50 | + |
| 51 | +### 3.3 Sync vs async execution |
| 52 | + |
| 53 | +Asynchronous. New review calls go through `app.job_store.JobStore` with |
| 54 | +SSE progress updates. The existing synchronous `POST /api/ai/review` |
| 55 | +stays for backward compatibility (removal out of scope). |
| 56 | + |
| 57 | +New endpoint: `POST /api/ai/review/async` returns `job_id`. SSE |
| 58 | +endpoint: implementation session decides between sharing the existing |
| 59 | +`/api/export/jobs/{job_id}/stream` (rename recommended) or mounting a |
| 60 | +new `/api/ai/jobs/{job_id}/stream`. |
| 61 | + |
| 62 | +### 3.4 Focus values vs new endpoints |
| 63 | + |
| 64 | +Single endpoint, extend the existing `focus[]` array. New values: |
| 65 | + |
| 66 | +- `style` (already exists - maps to "Style review") |
| 67 | +- `consistency` (NEW - within-chapter contradictions, distinct from |
| 68 | + existing `coherence` which checks logical flow) |
| 69 | +- `beta_reader` (NEW - open-ended simulated-reader feedback) |
| 70 | + |
| 71 | +Existing values (`coherence`, `pacing`, `dialogue`, `tension`) stay |
| 72 | +available for power users and back-compat. |
| 73 | + |
| 74 | +### 3.5 Output: inline + downloadable |
| 75 | + |
| 76 | +Both. Inline output in the existing AI panel stays unchanged. New |
| 77 | +capability: the generated Markdown is additionally written to disk and |
| 78 | +exposed via a download endpoint. |
| 79 | + |
| 80 | +Download endpoint: `GET /api/ai/review/{review_id}/report.md` returns |
| 81 | +`FileResponse`. Filename pattern: |
| 82 | +`{review_id}-{chapter_slug}-{YYYY-MM-DD}.md` for human readability. |
| 83 | + |
| 84 | +### 3.6 Focus naming for UI |
| 85 | + |
| 86 | +User-facing labels chosen for clarity, not technical precision: |
| 87 | + |
| 88 | +- `style` -> "Style" (DE: "Stil") |
| 89 | +- `consistency` -> "Consistency" (DE: "Konsistenz") - shorter and more |
| 90 | + intuitive than `consistency_internal` |
| 91 | +- `beta_reader` -> "Beta Reader" (DE: "Testleser") - avoids the |
| 92 | + negative connotation of "critical" and matches publishing-industry |
| 93 | + jargon |
| 94 | + |
| 95 | +### 3.7 UI pattern: radio buttons (single-select) |
| 96 | + |
| 97 | +The three primary focus values are mutually exclusive radio buttons. |
| 98 | +User selects exactly one per review call. |
| 99 | + |
| 100 | +Multi-focus stays supported by the backend (`focus[]` is still an |
| 101 | +array) but is NOT exposed in the primary UI. Power users combine via |
| 102 | +direct API calls. |
| 103 | + |
| 104 | +Rationale: single-select matches the user's mental model ("I want a |
| 105 | +stylistic review") and produces clearer structured reports than |
| 106 | +combined-focus calls. |
| 107 | + |
| 108 | +### 3.8 Review persistence |
| 109 | + |
| 110 | +Reviews persist to disk under |
| 111 | +`uploads/{book_id}/reviews/{review_id}-{chapter_slug}-{timestamp}.md`. |
| 112 | + |
| 113 | +Rationale: reviews cost real money (API tokens). Losing them on browser |
| 114 | +refresh is unacceptable UX. |
| 115 | + |
| 116 | +MVP UI surface: none. No history panel, no review browser. Files exist |
| 117 | +on disk; users re-access via the download endpoint with a known |
| 118 | +review_id, or via a future history UI. |
| 119 | + |
| 120 | +No database table for reviews in MVP. Filename is the metadata. |
| 121 | + |
| 122 | +### 3.9 Cascade delete on chapter removal |
| 123 | + |
| 124 | +When a chapter is deleted, all review files in |
| 125 | +`uploads/{book_id}/reviews/` matching that chapter's slug are deleted. |
| 126 | + |
| 127 | +Implementation: chapter-slug in the review filename is the link. |
| 128 | +Deletion walks the reviews directory, matches on filename prefix |
| 129 | +containing the chapter slug, removes matches. |
| 130 | + |
| 131 | +This is the only viable option given MVP lacks a review-listing UI. |
| 132 | +Orphaned review files would accumulate invisibly. |
| 133 | + |
| 134 | +### 3.10 Chapter-type context in prompt |
| 135 | + |
| 136 | +`chapter_type` injected into the system prompt. Prompt builder prepends: |
| 137 | +`"You are reviewing a {chapter_type_label}. {chapter_type_guidance}"` |
| 138 | + |
| 139 | +`CHAPTER_TYPE_GUIDANCE` dict maps all 31 ChapterType values to short |
| 140 | +guidance strings (e.g. `dedication` -> "brief, personal, keep feedback |
| 141 | +minimal and tone-focused"; `chapter` -> "narrative prose, standard |
| 142 | +review criteria"). Unknown values fall back to generic prose guidance. |
| 143 | +Guidance strings follow the book's language (3.12). |
| 144 | + |
| 145 | +Token cost: ~30-50 extra per call, negligible. |
| 146 | + |
| 147 | +### 3.11 Non-prose chapter-type warning |
| 148 | + |
| 149 | +For non-prose types (`title_page`, `copyright`, `toc`, `imprint`, |
| 150 | +`index`, `half_title`, `also_by_author`, `next_in_series`, |
| 151 | +`call_to_action`, `endnotes`, `bibliography`, `glossary`), the AI panel |
| 152 | +shows an inline warning above the start button. Non-blocking; user can |
| 153 | +proceed. |
| 154 | + |
| 155 | +Warning language: **book's language** (`book.language`), not UI |
| 156 | +language. Warning is content-related, matches review output language, |
| 157 | +avoids dedicated UI i18n keys. Pattern matches terse AI panel style; |
| 158 | +NOT a confirm dialog (those reserved for destructive ops). |
| 159 | + |
| 160 | +Examples: |
| 161 | +- EN: "This section is not typical prose. Review feedback may be |
| 162 | + limited." |
| 163 | +- DE: "Dieser Abschnitt ist kein typischer Prosa-Text. Das Review |
| 164 | + koennte eingeschraenkt sein." |
| 165 | + |
| 166 | +### 3.12 Language support: all 8 |
| 167 | + |
| 168 | +Pre-audit suggested DE/EN MVP with fallback. Rejected. |
| 169 | + |
| 170 | +All 8 supported Bibliogon languages (DE, EN, ES, FR, EL, PT, TR, JA) |
| 171 | +get explicit prompt support. Implementation: extend the existing |
| 172 | +`lang_map` dict at [routes.py:313](../../backend/app/ai/routes.py#L313) |
| 173 | +(already explicit for marketing prompts) and reuse from the review |
| 174 | +prompt builder. |
| 175 | + |
| 176 | +Effort: ~10 LoC. Full language parity at near-zero cost. No silent |
| 177 | +degradation for non-MVP-language books. |
| 178 | + |
| 179 | +### 3.13 Token cost estimation |
| 180 | + |
| 181 | +UI shows estimate inline on review button: |
| 182 | +`"Start Review (~5,000 tokens, ~$0.075)"` |
| 183 | + |
| 184 | +MVP method: character heuristic (`chars / 4 ~= tokens`). No new dep. |
| 185 | +Precise counting (`tiktoken` / Anthropic `count_tokens`) is post-MVP. |
| 186 | +Cost uses a small hardcoded pricing dict next to |
| 187 | +[providers.py](../../backend/app/ai/providers.py), updated on provider |
| 188 | +price changes. |
| 189 | + |
| 190 | +### 3.14 Async UI pattern |
| 191 | + |
| 192 | +While review runs (5-60s): |
| 193 | +- Spinner; panel stays visible (no editor takeover) |
| 194 | +- Rotating status messages: "Preparing review...", "Analyzing text...", "Generating report..." |
| 195 | +- No real progress bar (LLM token streaming does not map to percentage) |
| 196 | +- User can close panel; review continues via JobStore; result re-appears on reopen or via toast |
| 197 | + |
| 198 | +Status-message language: book language (3.11 rationale). |
| 199 | + |
| 200 | +## 4. Implementation skeleton |
| 201 | + |
| 202 | +1-2 sessions. NOT a multi-session blueprint. |
| 203 | + |
| 204 | +**Backend:** add `consistency`, `beta_reader` to focus enum + prompt |
| 205 | +mapping; add `chapter_type` to `ReviewRequest` + `CHAPTER_TYPE_GUIDANCE` |
| 206 | +dict; reuse `lang_map` from marketing path for all 8 langs in review |
| 207 | +builder; new `POST /api/ai/review/async` -> `JobStore.submit(...)`; |
| 208 | +worker writes Markdown to `uploads/{book_id}/reviews/`, publishes |
| 209 | +events; new `GET /api/ai/review/{review_id}/report.md` -> `FileResponse`; |
| 210 | +chapter-delete hook in [routers/chapters.py](../../backend/app/routers/chapters.py) |
| 211 | +walks reviews dir + deletes slug-prefixed files. |
| 212 | + |
| 213 | +**Frontend:** three radio buttons in AI panel review tab; inline |
| 214 | +non-prose warning above start button; cost estimate on button (chars/4 |
| 215 | ++ pricing dict); SSE subscribe in `handleAiReview`, rotating status |
| 216 | +messages, render result on `review_done` event with download button. |
| 217 | + |
| 218 | +**Tests:** extend [test_ai_review.py](../../backend/tests/test_ai_review.py) |
| 219 | +with new focus values, chapter_type prompt injection, all-8-lang |
| 220 | +coverage; new tests for async flow + FileResponse download + cascade |
| 221 | +delete. |
| 222 | + |
| 223 | +**i18n:** new UI keys in 8 langs for radio labels, "Start Review", |
| 224 | +"Download Report". Warning + status messages live as code constants |
| 225 | +per book language (see 3.11, 3.14), NOT in i18n YAML. |
| 226 | + |
| 227 | +## 5. Out of scope |
| 228 | + |
| 229 | +- Keyring API-key storage (separate ROADMAP item) |
| 230 | +- Review history UI / review browser |
| 231 | +- Multi-focus UI (backend supports, UI does not expose) |
| 232 | +- Multi-chapter review (single chapter per call) |
| 233 | +- Review comparison / diffing |
| 234 | +- `tiktoken` precision upgrade |
| 235 | +- Inline TipTap comment marks |
| 236 | +- Custom review-type plugins |
| 237 | +- Budget / monthly cost caps |
| 238 | +- Third-party review-engine support |
| 239 | + |
| 240 | +## 6. Cross-references |
| 241 | + |
| 242 | +- [backend/app/ai/routes.py](../../backend/app/ai/routes.py) - existing review endpoint to extend |
| 243 | +- [backend/app/ai/llm_client.py](../../backend/app/ai/llm_client.py) - LLM client reused as-is |
| 244 | +- [backend/app/job_store.py](../../backend/app/job_store.py) - async job pattern |
| 245 | +- [docs/explorations/desktop-packaging.md](desktop-packaging.md) - desktop distribution affects keyring relevance (out of scope here) |
| 246 | +- [.claude/rules/architecture.md](../../.claude/rules/architecture.md) - AI-as-core precedent |
| 247 | +- [docs/CONCEPT.md](../CONCEPT.md) - offline-first principle preserved via `ai.enabled` flag |
0 commit comments