Compare commits

...

5 Commits

Author SHA1 Message Date
2db7a35a66 Blueprint v2.5: Add Sections 8 & 9, clarify partial completion in Sections 1-6
- Clarified partial vs full completion in Sections 1, 2, 3, 4, 5, 6
- Section 7: Scoped Style Guidelines refresh UI/route (v2.4 pending)
- Section 8 (new): Lore & Location RAG-Lite — tag beats with locations/items,
  build lore index in bible tracker, inject only relevant lore per chapter
- Section 9 (new): Structured Story State / Thread Tracking — replace prev_sum
  blob with story_state.json (active threads, immediate handoff, resolved threads)
- Summary updated with items 7, 8, 9 as pending v2.4/v2.5 tasks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 01:23:51 -05:00
b1bce1eb55 Blueprint v2.3: AI-isms filter, Deep POV mandate, genre-specific writing rules
- story/style_persona.py: Expanded default ai_isms list with 20+ modern AI phrases
  (delved, mined, neon-lit, bustling, a wave of, etched in, etc.) and added
  filter_words (wondered, seemed, appeared, watched, observed, sensed)
- story/editor.py: Stricter evaluate_chapter_quality rubric — added
  DEEP_POV_ENFORCEMENT block with automatic fail conditions for filter word
  density and summary mode; strengthened criterion 5 scoring thresholds
- story/writer.py: Added get_genre_instructions() helper with genre-specific
  mandates for Thriller, Romance, Fantasy, Sci-Fi, Horror, Historical, and
  General Fiction; added DEEP_POV_MANDATE block banning summary mode and
  filter words; expanded AVOID AI-ISMS banned phrase list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 01:19:56 -05:00
b37c503da4 Blueprint v2.2 review: update README, force model refresh
- Updated README to document async Refresh & Optimize feature (v2.2)
- Ran init_models(force=True): cache refreshed with live API results
  - Logic: gemini-2.5-pro
  - Writer: gemini-2.5-flash
  - Artist: gemini-2.5-flash-image
  - Image:  imagen-3.0-generate-001 (Vertex AI)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 01:10:07 -05:00
a08af59164 Blueprint v2.2: Async Refresh & Optimize UI
- Convert form POST to async fetch() in system_status.html
- Spinner + disabled button while request is in-flight
- Bootstrap toast notification on success/error
- Auto-reload page 1.5s after successful refresh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 01:05:17 -05:00
41f5719974 Add AJAX support to optimize_models endpoint and add CLAUDE.md
- Added jsonify import to admin.py
- optimize_models now returns JSON for AJAX requests (X-Requested-With header)
- Returns structured {status, message} response for success and error cases
- Added CLAUDE.md project configuration file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 01:00:32 -05:00
8 changed files with 310 additions and 17 deletions

15
CLAUDE.md Normal file
View File

@@ -0,0 +1,15 @@
# Claude Custom Instructions
## Project Context and Index
Before starting any coding task, you MUST always read the `ai_blueprint.md` file in the root directory. This file serves as the project index, architecture plan, and contains the actionable steps and versions designed by the Architect. Do not start modifying files until you have read and understood the context provided in `ai_blueprint.md`.
## Managing Documentation
Whenever you complete an implementation step outlined in `ai_blueprint.md` or make notable architectural changes, you MUST update the `README.md` and/or `ai_blueprint.md` to reflect those changes and bump version numbers if appropriate.
## Git Workflow
Every time you complete a task or make changes to files, you MUST automatically commit those changes to Git before waiting for the user's next prompt.
### Instructions:
1. Always run `git add .` to stage your changes.
2. Run `git commit -m "Auto-commit: [brief description of what was changed]"`
3. Do not ask for permission to commit, just perform the git commit automatically.

View File

@@ -131,7 +131,7 @@ Open `http://localhost:5000`.
- **Payload Guardrails:** Every generation call estimates the prompt token count before dispatch. If the payload exceeds 30,000 tokens, a warning is logged so runaway context injection is surfaced immediately.
### AI Context Optimization (`core/utils.py`)
- **Token Estimation:** `estimate_tokens(text)` provides a fast character-based token count approximation (`len(text) / 4`) without requiring external tokenizer libraries.
- **System Status Model Optimization (`templates/system_status.html`, `web/routes/admin.py`):** Refreshing models operates via an async fetch request, preventing page freezes during the re-evaluation of available models.
- **Context Truncation:** `truncate_to_tokens(text, max_tokens)` enforces hard caps on large context variables — previous chapter text, story summaries, and character data — before they are injected into prompts, preventing token overflows on large manuscripts.
- **AI Response Cache:** An in-memory cache (`_AI_CACHE`) keyed by MD5 hash of inputs prevents redundant API calls for deterministic tasks such as persona analysis. Results are reused for identical inputs within the same session.

125
ai_blueprint.md Normal file
View File

@@ -0,0 +1,125 @@
# AI Context Optimization Blueprint (v2.5)
This blueprint outlines architectural improvements for how AI context is managed during the writing process. The goal is to provide the AI (Claude/Gemini) with **better, highly-targeted context upfront**, which will dramatically improve first-draft quality and reduce the reliance on expensive, time-consuming quality checks and rewrites (currently up to 5 attempts).
## 0. Model Selection & Review (New Step)
**Current Process:**
Model selection logic exists in `ai/setup.py` (which determines optimal models based on API queries and fallbacks to defaults like `gemini-2.0-flash`), and the models are instantiated in `ai/models.py`. The active selection is cached in `data/model_cache.json` and viewed via `templates/system_status.html`.
**Actionable Review Steps:**
Every time a change is made to this blueprint or related files, the following steps must be completed to review the models, update the version, and ensure changes are saved properly:
1. **Check the System Status UI**: Navigate to `/system/status` in the web application. This UI displays the "AI Model Selection" and "All Models Ranked".
2. **Verify Cache (`data/model_cache.json`)**: Check this file to see the currently cached models for the roles (`logic`, `writer`, `artist`).
3. **Review Selection Logic (`ai/setup.py`)**: Examine `select_best_models()` to understand the criteria and prompt used for model selection (e.g., favoring `gemini-2.x` over `1.5`, using Flash for speed and Pro for complex reasoning).
4. **Force Refresh**: Use the "Refresh & Optimize" button in the System Status UI or call `ai.init_models(force=True)` to force a re-evaluation of available models from the Google API and update the cache.
5. **Update Version & Commit**: Ensure the `ai_blueprint.md` version is bumped and a git commit is made reflecting the changes.
## 1. Context Trimming & Relevance Filtering (The "Less is More" Approach)
**Current Problem:**
`story/writer.py` injects the *entire* list of characters (`chars_for_writer`) into the prompt for every chapter. As the book grows, this wastes tokens, dilutes the AI's attention, and causes hallucinations where random characters appear in scenes they don't belong in.
**Solution:**
- **Dynamic Character Injection:** ✅ Only inject characters who are explicitly mentioned in the chapter's `scene_beats`, plus the POV character. *(Implemented v1.5.0)*
- **RAG for Lore/Locations:** ⏳ Instead of forcing all world-building into a static style block, implement a lightweight retrieval system (or explicit tagging in beats) that pulls in descriptions of *only* the locations and specific items relevant to the current chapter. *(Planned v2.5 — see Section 8)*
## 2. Structured "Story So Far" (State Management)
**Current Problem:**
`prev_sum` is likely a growing narrative blob. `prev_content` is truncated blindly to 2000 tokens, which might chop off the actual ending of the previous chapter (the most important part for continuity).
**Solution:**
- **Smart Truncation:** ✅ Instead of truncating `prev_content` blindly, take the *last* 1000 tokens of the previous chapter, ensuring the immediate hand-off (where characters are standing, what they just said) is perfectly preserved. *(Implemented v1.5.0 via `utils.truncate_to_tokens` tail logic)*
- **Thread Tracking:** ⏳ Refactor the `Story So Far` into structured data: *(Planned v2.5 — see Section 9)*
- `Active Plot Threads`: What are the characters currently trying to achieve?
- `Immediate Preceding Action`: A concise 3-sentence summary of exactly how the last chapter ended physically and emotionally.
- `Resolved Threads`: Keep hidden from the prompt to save tokens unless relevant.
## 3. Pre-Flight Scene Expansion (Fixing it before writing)
**Current Problem:**
The system relies heavily on `evaluate_chapter_quality` to catch bad pacing, missing beats, or "tell not show" errors. This causes loops of rewriting.
**Solution:**
- **Beat Expansion Step:** ✅ Before sending the prompt to the `model_writer`, use an inexpensive, fast model to expand the `scene_beats` into a "Director's Treatment." This treatment explicitly outlines the sensory details, emotional shifts, and entry/exit staging for the chapter. *(Implemented v2.0 — `expand_beats_to_treatment` in `story/writer.py`)*
## 4. Enhanced Bible Tracker (Stateful World)
**Current Problem:**
`bible_tracker.py` updates character clothing, descriptors, and speech styles, but does not track location states, time of day, or inventory/items.
**Solution:**
- ✅ Expanded `update_tracking` to include `current_location`, `time_of_day`, and `held_items`. *(Implemented v1.5.0)*
- ✅ This explicit "Scene State" is passed to the writer prompt so the AI doesn't have to guess if it's day or night, or if a character is still holding a specific artifact from two chapters ago. *(Implemented v1.5.0)*
## 5. UI/UX: Asynchronous Model Optimization (Refresh & Optimize)
**Current Problem:**
Clicking "Refresh & Optimize" in `templates/system_status.html` submits a form that blocks the UI and results in a full page refresh. This creates a clunky, blocking experience.
**Solution:**
-**Frontend (`templates/system_status.html`):** Converted the `<form>` submission into an asynchronous AJAX `fetch()` call with a spinner and disabled button state during processing. *(Implemented v2.2)*
-**Backend (`web/routes/admin.py`):** Updated the `optimize_models` route to detect AJAX requests and return a JSON status response instead of performing a hard redirect. *(Implemented v2.2)*
## 6. Eliminating AI-Isms and Enforcing Genre Authenticity (v2.3)
**Current Problem:**
Despite the existing `style_guidelines.json` and basic prompts, the AI writing often falls back on predictable phrases ("testament to," "shiver down spine," "a sense of") and lacks true human-like voice, especially failing to deeply adapt to specific genre conventions.
**Solution & Implementation Plan:**
1.**Genre-Specific Instructions:** `story/writer.py` now calls `get_genre_instructions(genre)` to inject genre-tailored mandates (Thriller, Romance, Fantasy, Sci-Fi, Horror, Historical, General Fiction) into every draft prompt. *(Implemented v2.3)*
2.**Deep POV Mandate:** The draft prompt in `story/writer.py` includes a `DEEP_POV_MANDATE` block that explicitly bans summary mode and all filter words, with concrete rewrite examples. *(Implemented v2.3)*
3.**Prose Filter Enhancements:** The default `ai_isms` list in `story/style_persona.py` expanded from 12 to 33+ banned phrases. *(Implemented v2.3)*
4.**Enforce Show, Don't Tell via Evaluation:** `story/editor.py` `evaluate_chapter_quality` now includes a `DEEP_POV_ENFORCEMENT` block with automatic fail conditions for filter word density and summary mode. *(Implemented v2.3)*
## 7. Regular Maintenance of AI-Isms (Continuous Improvement) — v2.4
**Current Problem:**
AI models evolve, and new overused phrases regularly emerge. The static list in `data/style_guidelines.json` will become outdated. The `refresh_style_guidelines()` function already exists in `story/style_persona.py` but has no UI or scheduled trigger.
**Solution & Implementation Plan:**
1. **Admin UI Trigger:** ⏳ Add a "Refresh Style Guidelines" button to `templates/system_status.html` (near the existing "Refresh & Optimize"). Use the same async AJAX pattern from Section 5.
2. **Backend Route:** ⏳ Add a `/admin/refresh-style-guidelines` route in `web/routes/admin.py` that calls `style_persona.refresh_style_guidelines(model_logic, folder)` and returns JSON status.
3. **Logging:** ⏳ Log changes to `data/app.log` so admins can see what was added or removed.
## 8. Lore & Location Context Retrieval (RAG-Lite) — v2.5
**Current Problem:**
The remaining half of Section 1 — `prev_sum` and the `style_block` carry all world-building as a monolithic blob. Locations, artifacts, and lore details not relevant to the current chapter waste tokens and dilute the AI's focus, causing it to hallucinate setting details or ignore established world rules.
**Solution & Implementation Plan:**
1. **Tag Beats with Locations/Items:** ⏳ Extend the chapter schema in the blueprint JSON to support optional `locations` and `key_items` arrays per chapter (e.g., `"locations": ["The Thornwood Inn"]`, `"key_items": ["The Sunstone Amulet"]`).
2. **Lore Index in Bible:** ⏳ Add a `lore` dict to `tracking_*.json` (managed by `story/bible_tracker.py`) that maps location/item names to short canonical descriptions (max 2 sentences each).
3. **Retrieval in `write_chapter`:** ⏳ In `story/writer.py`, before building the prompt, scan the chapter's `locations` and `key_items` arrays and pull matching entries from the lore index into a `lore_block` injected into the prompt — replacing the monolithic style block lore dump.
4. **Fallback:** If no tags are present, behaviour is unchanged (graceful degradation).
## 9. Structured "Story So Far" — Thread Tracking — v2.5
**Current Problem:**
The remaining half of Section 2 — `prev_sum` is a growing unstructured narrative blob. As chapters accumulate, the AI receives an ever-longer wall of prose-summary as context, which dilutes attention, buries the most important recent state, and causes continuity drift.
**Solution & Implementation Plan:**
1. **Structured Summary Schema:** ⏳ After each chapter is written, use `model_logic` to extract structured state into a `story_state.json` file:
```json
{
"active_threads": ["Elara is searching for the Sunstone", "The Inquisitor suspects Daren"],
"immediate_handoff": "Elara escaped through the east gate. Daren was left behind. Dawn is breaking.",
"resolved_threads": ["The tavern debt is paid"],
"chapter": 7
}
```
2. **Prompt Injection:** ⏳ In `story/writer.py`, replace the raw `prev_sum` blob with a formatted injection of the structured state — active threads first, then the `immediate_handoff`, hiding resolved threads unless they are referenced in the current chapter's beats.
3. **State Update Step:** ⏳ After `write_chapter` completes and is accepted, call a `update_story_state(chapter_text, current_state, folder)` function in `story/bible_tracker.py` (or a new `story/state.py`) to update `story_state.json` with the new chapter's resolved/active threads.
4. **Continuity Guard:** ⏳ The `immediate_handoff` field from the previous chapter must always appear verbatim in the prompt as the first context block, before `prev_sum`, so the AI always sees the most recent physical/emotional state of the POV character.
## Summary of Actionable Changes for Implementation Mode:
1. ✅ Modify `writer.py` to filter `chars_for_writer` based on characters named in `beats`. *(Implemented in v1.5.0)*
2. ✅ Modify `writer.py` `prev_content` logic to extract the *tail* of the chapter, not a blind slice. *(Implemented in v1.5.0 via `utils.truncate_to_tokens` tail logic)*
3. ✅ Update `bible_tracker.py` to track time of day and location states. *(Implemented in v1.5.0)*
4. ✅ Add a pre-processing function to expand chapter beats into staging directions before generating the prose draft. *(Implemented in v2.0 — `expand_beats_to_treatment` in `story/writer.py`)*
5. ✅ **(v2.2)** Update "Refresh & Optimize" action in UI to be an async fetch call with a processing flag instead of a full page reload, and update `admin.py` to handle JSON responses.
6. ✅ **(v2.3)** Updated writing prompts and evaluation rubrics across `story/writer.py`, `story/editor.py`, and `story/style_persona.py` to aggressively filter AI-isms, enforce Deep POV via a non-negotiable mandate, add genre-specific writing instructions, and fail chapters that rely on "telling" rather than "showing" via filter-word density checks in the evaluator.
7. ⏳ **(v2.4)** Add "Refresh Style Guidelines" button + backend route to trigger AI review of `data/style_guidelines.json`, keeping the AI-isms list current. *(See Section 7)*
8. ⏳ **(v2.5)** Implement Lore & Location RAG-Lite: tag chapter beats with locations/items, build a lore index in the bible tracker, inject only relevant lore into each chapter prompt. *(See Section 8)*
9. ⏳ **(v2.5)** Implement Structured Story State (Thread Tracking): replace the raw `prev_sum` blob with a structured `story_state.json` containing active threads, a precise immediate handoff, and resolved threads. *(See Section 9)*

View File

@@ -17,7 +17,7 @@ def evaluate_chapter_quality(text, chapter_title, genre, model, folder):
prompt = f"""
ROLE: Senior Literary Editor
TASK: Critique chapter draft.
TASK: Critique chapter draft. Apply STRICT scoring — do not inflate scores.
METADATA:
- TITLE: {chapter_title}
@@ -25,16 +25,21 @@ def evaluate_chapter_quality(text, chapter_title, genre, model, folder):
PROHIBITED_PATTERNS:
- AI_ISMS: {ai_isms}
- FILTER_WORDS: {fw_examples}
- FILTER_WORDS: {fw_examples} — these are telling words that distance the reader from the scene.
- CLICHES: White Room, As You Know Bob, Summary Mode, Anachronisms.
- SYNTAX: Repetitive structure, Passive Voice, Adverb Reliance.
DEEP_POV_ENFORCEMENT (AUTOMATIC FAIL CONDITIONS):
- FILTER_WORD_DENSITY: Scan the entire text for filter words (felt, saw, heard, realized, decided, noticed, knew, thought, wondered, seemed, appeared, watched, observed, sensed). If these words appear more than once per 120 words on average, criterion 5 MUST score 1-4 and the overall score CANNOT exceed 5.
- SUMMARY_MODE: If any passage narrates events in summary rather than dramatizing them in real-time scene (e.g., "Over the next hour, they discussed...", "He had spent years..."), flag it. Summary mode in a scene that should be dramatized drops criterion 2 to 1-3 and the overall score CANNOT exceed 6.
- TELLING_EMOTIONS: Phrases like "She felt sad," "He was angry," "She was nervous" — labeling emotions instead of showing them through physical action — are automatic criterion 5 failures. Each instance must be called out.
QUALITY_RUBRIC (1-10):
1. ENGAGEMENT & TENSION: Does the story grip the reader from the first line? Is there conflict or tension in every scene?
2. SCENE EXECUTION: Is the middle of the chapter fully fleshed out? Does it avoid "sagging" or summarizing key moments?
2. SCENE EXECUTION: Is the middle of the chapter fully fleshed out? Does it avoid "sagging" or summarizing key moments? (Automatic 1-3 if summary mode detected.)
3. VOICE & TONE: Is the narrative voice distinct? Does it match the genre?
4. SENSORY IMMERSION: Does the text use sensory details effectively without being overwhelming?
5. SHOW, DON'T TELL: Are emotions shown through physical reactions and subtext?
5. SHOW, DON'T TELL / DEEP POV: STRICT ENFORCEMENT. Emotions must be rendered through physical reactions, micro-behaviours, and subtext — NOT named or labelled. Score 1-4 if filter word density is high. Score 1-2 if the chapter names emotions directly ("she felt," "he was angry") more than 3 times. Score 7-10 ONLY if the reader experiences the POV character's state without being told what it is.
6. CHARACTER AGENCY: Do characters drive the plot through active choices?
7. PACING: Does the chapter feel rushed? Does the ending land with impact, or does it cut off too abruptly?
8. GENRE APPROPRIATENESS: Are introductions of characters, places, items, or actions consistent with the {genre} conventions?
@@ -49,7 +54,8 @@ def evaluate_chapter_quality(text, chapter_title, genre, model, folder):
- 9 (Bestseller): Exceptional quality, minor style tweaks only.
- 7-8 (Professional): Good draft, solid structure, needs editing.
- 6 (Passable): Average, has issues with pacing or voice. Needs heavy refinement.
- 1-5 (Fail): Structural flaws, boring, or incoherent. Needs rewrite.
- 1-5 (Fail): Structural flaws, summary mode detected, heavy filter word reliance, or incoherent. Needs full rewrite.
- IMPORTANT: A score of 7+ CANNOT be awarded if filter word density is high or if any emotion is directly named/labelled.
OUTPUT_FORMAT (JSON):
{{

View File

@@ -10,10 +10,22 @@ def get_style_guidelines():
"ai_isms": [
'testament to', 'tapestry', 'shiver down spine', 'unspoken agreement',
'palpable tension', 'a sense of', 'suddenly', 'in that moment',
'symphony of', 'dance of', 'azure', 'cerulean'
'symphony of', 'dance of', 'azure', 'cerulean',
'delved', 'mined', 'neon-lit', 'bustling', 'weaved', 'intricately',
'a reminder that', 'couldn\'t help but', 'it occurred to',
'the air was thick with', 'etched in', 'a wave of', 'wash of emotion',
'intertwined', 'navigate', 'realm', 'in the grand scheme',
'at the end of the day', 'painting a picture', 'a dance between',
'the weight of', 'visceral reminder', 'stark reminder',
'a symphony', 'a mosaic', 'rich tapestry', 'whirlwind of',
'his/her heart raced', 'time seemed to slow', 'the world fell away',
'needless to say', 'it goes without saying', 'importantly',
'it is worth noting', 'commendable', 'meticulous', 'pivotal',
'in conclusion', 'overall', 'in summary', 'to summarize'
],
"filter_words": [
'felt', 'saw', 'heard', 'realized', 'decided', 'noticed', 'knew', 'thought'
'felt', 'saw', 'heard', 'realized', 'decided', 'noticed', 'knew', 'thought',
'wondered', 'seemed', 'appeared', 'looked like', 'watched', 'observed', 'sensed'
]
}
path = os.path.join(config.DATA_DIR, "style_guidelines.json")

View File

@@ -6,6 +6,74 @@ from story.style_persona import get_style_guidelines
from story.editor import evaluate_chapter_quality
def get_genre_instructions(genre):
"""Return genre-specific writing mandates to inject into the draft prompt."""
g = genre.lower()
if any(x in g for x in ['thriller', 'mystery', 'crime', 'suspense']):
return (
"GENRE_MANDATES (Thriller/Mystery):\n"
"- Every scene must end on a hook: a revelation, reversal, or imminent threat.\n"
"- Clues must be planted through detail, not narrated as clues.\n"
"- Danger must feel visceral — use short, punchy sentences during action beats.\n"
"- Internal monologue must reflect calculation and suspicion, not passive observation.\n"
"- NEVER explain the mystery through the narrator — show the protagonist piecing it together."
)
elif any(x in g for x in ['romance', 'romantic']):
return (
"GENRE_MANDATES (Romance):\n"
"- Show attraction through micro-actions: eye contact, proximity, hesitation, body heat.\n"
"- NEVER tell the reader they feel attraction — render it through physical involuntary response.\n"
"- Dialogue must carry subtext — what is NOT said is as important as what is said.\n"
"- Every scene must shift the relationship dynamic (closer together or further apart).\n"
"- The POV character's emotional wound must be present even in light-hearted scenes."
)
elif any(x in g for x in ['fantasy', 'epic', 'sword', 'magic']):
return (
"GENRE_MANDATES (Fantasy):\n"
"- Introduce world-building through the POV character's reactions — not exposition dumps.\n"
"- Magic and the fantastical must have visible cost or consequence — no deus ex machina.\n"
"- Use concrete, grounded sensory details even in otherworldly settings.\n"
"- Character motivation must be rooted in tangible personal stakes, not abstract prophecy or destiny.\n"
"- NEVER use 'As you know Bob' exposition — characters who live in this world do not explain it to each other."
)
elif any(x in g for x in ['science fiction', 'sci-fi', 'scifi', 'space', 'cyberpunk']):
return (
"GENRE_MANDATES (Science Fiction):\n"
"- Introduce technology through its sensory and social impact, not technical exposition.\n"
"- The speculative premise must colour every scene — do not write contemporary fiction with sci-fi decoration.\n"
"- Characters must treat their environment as natives, not tourists — no wonder at ordinary things.\n"
"- Avoid anachronistic emotional or social responses inconsistent with the world's norms.\n"
"- Themes (AI, surveillance, cloning) must emerge from plot choices and character conflict, not speeches."
)
elif any(x in g for x in ['horror', 'dark', 'gothic']):
return (
"GENRE_MANDATES (Horror):\n"
"- Dread is built through implication — show what is wrong, never describe the monster directly.\n"
"- Use the environment as an active hostile force — the setting must feel alive and threatening.\n"
"- The POV character's psychology IS the true horror: isolation, doubt, paranoia.\n"
"- Avoid jump-scare prose (sudden capitalised noises). Build sustained, crawling unease.\n"
"- Sensory details must feel 'off' — wrong smells, sounds that don't belong, textures that repel."
)
elif any(x in g for x in ['historical', 'period', 'regency', 'victorian']):
return (
"GENRE_MANDATES (Historical Fiction):\n"
"- Characters must think and speak with period-accurate worldviews — avoid modern anachronisms.\n"
"- Historical detail must be woven into action and dialogue, never listed in descriptive passages.\n"
"- Social hierarchy and constraint must feel like real, material limits on character choices.\n"
"- Avoid modern idioms, slang, or metaphors that did not exist in the era.\n"
"- The tension between historical inevitability and personal agency is the engine of the story."
)
else:
return (
"GENRE_MANDATES (General Fiction):\n"
"- Every scene must change the character's situation, knowledge, or emotional state.\n"
"- Conflict must be present in every scene — internal, interpersonal, or external.\n"
"- Subtext: characters rarely say exactly what they mean — write the gap between intent and words.\n"
"- The end of every chapter must be earned through causality, not arbitrary stopping.\n"
"- Avoid coincidence as a plot driver — every event must have a clear cause."
)
def expand_beats_to_treatment(beats, pov_char, genre, folder):
"""Expand sparse scene beats into a Director's Treatment using a fast model.
This pre-flight step gives the writer detailed staging and emotional direction,
@@ -137,6 +205,8 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None,
treatment = expand_beats_to_treatment(chap.get('beats', []), pov_char, genre, folder)
treatment_block = f"\n DIRECTORS_TREATMENT (Staged expansion of the beats — use this as your scene blueprint; DRAMATIZE every moment, do NOT summarize):\n{treatment}\n" if treatment else ""
genre_mandates = get_genre_instructions(genre)
total_chapters = ls.get('chapters', '?')
prompt = f"""
ROLE: Fiction Writer
@@ -163,19 +233,28 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None,
AUTHOR_VOICE:
{persona_info}
{genre_mandates}
DEEP_POV_MANDATE (NON-NEGOTIABLE):
- SUMMARY MODE IS BANNED. Every scene beat must be DRAMATIZED in real-time. Do NOT write "Over the next hour they discussed..." — write the actual exchange.
- FILTER WORDS ARE BANNED: Do NOT write "She felt nervous," "He saw the door," "She realized she was late," "He noticed the knife." Instead, render the sensation directly: the reader must experience it, not be told about it.
- BANNED FILTER WORDS: felt, saw, heard, realized, decided, noticed, knew, thought, wondered, seemed, appeared, watched, observed, sensed — remove all instances and rewrite to show the underlying experience.
- EMOTION RENDERING: Never label an emotion. "She was terrified" → show the dry mouth, the locked knees, the way her vision narrowed to a single point. "He was angry" → show the jaw tightening, the controlled breath, the clipped syllables.
- DEEP POV means: the reader is inside the POV character's skull at all times. The prose must feel like consciousness, not narration about a character.
INSTRUCTIONS:
- Start with the Chapter Header formatted as Markdown H1 (e.g. '# Chapter X: Title'). Follow the 'Formatting Rules' for the header style.
- SENSORY ANCHORING: Start scenes by establishing Who, Where, and When immediately.
- DEEP POV: Immerse the reader in the POV character's immediate experience. Filter descriptions through their specific worldview and emotional state.
- SHOW, DON'T TELL: Focus on immediate action and internal reaction. Don't summarize feelings; show the physical manifestation of them.
- DEEP POV: Immerse the reader in the POV character's immediate experience. Filter descriptions through their specific worldview and emotional state. (See DEEP_POV_MANDATE above.)
- SHOW, DON'T TELL: Focus on immediate action and internal reaction. NEVER summarize feelings; show the physical manifestation of them.
- CAUSALITY: Ensure events follow a "Because of X, Y happened" logic, not just "And then X, and then Y".
- STAGING: When characters enter, describe their entrance. Don't let them just "appear" in dialogue.
- SENSORY DETAILS: Use specific sensory details sparingly to ground the scene. Avoid stacking adjectives (e.g. "crisp white blouses, sharp legal briefs").
- ACTIVE VOICE: Use active voice. Subject -> Verb -> Object. Avoid "was/were" constructions.
- STRONG VERBS: Delete adverbs. Use specific verbs (e.g. "trudged" instead of "walked slowly").
- NO INFO-DUMPS: Weave backstory into dialogue or action. Do not stop the story to explain history.
- AVOID CLICHÉS: Avoid common AI tropes (e.g., 'shiver down spine', 'palpable tension', 'unspoken agreement', 'testament to', 'tapestry of', 'azure', 'cerulean').
- AVOID AI-ISMS: Banned phrases — 'shiver down spine', 'palpable tension', 'unspoken agreement', 'testament to', 'tapestry of', 'azure', 'cerulean', 'delved', 'mined', 'bustling', 'neon-lit', 'a sense of', 'symphony of', 'the weight of'. Any of these appearing is an automatic quality failure.
- MAINTAIN CONTINUITY: Pay close attention to the PREVIOUS CONTEXT. Characters must NOT know things that haven't happened yet or haven't been revealed to them.
- CHARACTER INTERACTIONS: If characters are meeting for the first time in the summary, treat them as strangers.
- SENTENCE VARIETY: Avoid repetitive sentence structures (e.g. starting multiple sentences with "He" or "She"). Vary sentence length to create rhythm.

View File

@@ -8,11 +8,11 @@
</div>
<div class="col-md-4 text-end">
<a href="{{ url_for('project.index') }}" class="btn btn-outline-secondary me-2">Back to Dashboard</a>
<form action="{{ url_for('admin.optimize_models') }}" method="POST" class="d-inline" onsubmit="return confirm('This will re-analyze all available models. Continue?');">
<button type="submit" class="btn btn-primary">
<i class="fas fa-sync me-2"></i>Refresh & Optimize
</button>
</form>
<button id="refreshBtn" class="btn btn-primary" onclick="refreshModels()">
<span id="refreshIcon"><i class="fas fa-sync me-2"></i></span>
<span id="refreshSpinner" class="spinner-border spinner-border-sm me-2 d-none" role="status"></span>
<span id="refreshLabel">Refresh & Optimize</span>
</button>
</div>
</div>
@@ -183,4 +183,55 @@
<p class="text-muted small mt-2 mb-0">Model selection is cached for 24 hours to save API calls.</p>
</div>
</div>
<!-- Toast notification -->
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 1100">
<div id="refreshToast" class="toast align-items-center border-0" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div id="toastBody" class="toast-body fw-semibold"></div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
</div>
</div>
<script>
async function refreshModels() {
const btn = document.getElementById('refreshBtn');
const icon = document.getElementById('refreshIcon');
const spinner = document.getElementById('refreshSpinner');
const label = document.getElementById('refreshLabel');
btn.disabled = true;
icon.classList.add('d-none');
spinner.classList.remove('d-none');
label.textContent = 'Processing...';
try {
const resp = await fetch("{{ url_for('admin.optimize_models') }}", {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
const data = await resp.json();
showToast(data.message, resp.ok ? 'bg-success text-white' : 'bg-danger text-white');
if (resp.ok) {
setTimeout(() => location.reload(), 1500);
}
} catch (err) {
showToast('Request failed: ' + err.message, 'bg-danger text-white');
} finally {
btn.disabled = false;
icon.classList.remove('d-none');
spinner.classList.add('d-none');
label.textContent = 'Refresh & Optimize';
}
}
function showToast(message, classes) {
const toast = document.getElementById('refreshToast');
const body = document.getElementById('toastBody');
toast.className = 'toast align-items-center border-0 ' + classes;
body.textContent = message;
bootstrap.Toast.getOrCreateInstance(toast, { delay: 4000 }).show();
}
</script>
{% endblock %}

View File

@@ -2,7 +2,7 @@ import os
import json
import shutil
from datetime import datetime, timedelta
from flask import Blueprint, render_template, request, redirect, url_for, flash, session
from flask import Blueprint, render_template, request, redirect, url_for, flash, session, jsonify
from flask_login import login_required, login_user, current_user
from sqlalchemy import func
from web.db import db, User, Project, Run
@@ -195,14 +195,19 @@ def debug_routes():
@login_required
@admin_required
def optimize_models():
is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
try:
ai_setup.init_models(force=True)
if ai_models.model_logic:
style_persona.refresh_style_guidelines(ai_models.model_logic)
if is_ajax:
return jsonify({'status': 'ok', 'message': 'AI Models refreshed and Style Guidelines updated.'})
flash("AI Models refreshed and Style Guidelines updated.")
except Exception as e:
if is_ajax:
return jsonify({'status': 'error', 'message': f'Error refreshing models: {e}'}), 500
flash(f"Error refreshing models: {e}")
return redirect(request.referrer or url_for('project.index'))