Skip to content

Commit 4b3addc

Browse files
docs(explorations): AI review extension architecture and decisions
Capture architecture for extending the existing core ai_review feature with three user-facing modes (style, consistency, beta_reader), async execution via JobStore, persisted Markdown reports, chapter-type context injection, and full 8-language prompt support. Document 14 resolved decisions covering naming, key storage, UI pattern, persistence, cascade delete, cost estimation, and async UX. No code changes. Implementation follows in 1-2 sessions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ff130bd commit 4b3addc

1 file changed

Lines changed: 247 additions & 0 deletions

File tree

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
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

Comments
 (0)