v1.3.0: Improve all AI prompts, refinement loops, and cover generation accuracy
story.py — write_chapter():
- Added POSITION context ("Chapter N of Total") so the AI calibrates narrative
tension correctly (setup vs escalation vs climax/payoff)
- Moved PACING_GUIDE to sit directly after PACING metadata instead of being
buried after 13 quality criteria items where the AI rarely reads it
- Removed duplicate pacing descriptions that appeared after QUALITY_CRITERIA
story.py — refinement loop:
- Capped critique history to last 2 entries (was accumulating all previous
attempts, wasting tokens and confusing the model on attempt 4-5)
- Added TARGET_WORDS and BEATS constraints to the refinement prompt to prevent
chapters from shrinking or losing plot beats during editing passes
- Restructured refinement prompt with explicit HARD_CONSTRAINTS section
story.py — check_and_propagate():
- Increased chapter context from 5000 to 12000 chars for continuity rewrites
(was asking for a full chapter rewrite but only providing a fragment)
- Added explicit word count target to rewrite so chapters are not truncated
- Added conservative decision bias: only rewrite on genuine contradictions
story.py — plan_structure():
- Now passes TARGET_CHAPTERS, TARGET_WORDS, GENRE, and CHARACTERS to the
structure AI — it was planning blindly without knowing the book's scale
marketing.py — generate_blurb():
- Rewrote prompt with 4-part structure: Hook → Stakes → Tension → Close
- Formats plot beats as a readable list instead of raw JSON array
- Extracts protagonist automatically for personalised blurb copy
- Added genre-tone matching, present-tense voice, and no-spoiler rule
marketing.py — generate_cover():
- Added genre-to-visual-style mapping (thriller → cinematic, fantasy → epic
digital painting, romance → painterly, etc.)
- Art prompt instructions now enforce: no text/letters/watermarks, rule-of-thirds
composition, explicit focal point, lighting description, colour palette
- Replaced generic image evaluation with a 5-criteria book-cover rubric:
visual impact, genre fit, composition, quality, and clean image (no text)
- Score penalties: -3 for visible text/watermarks, -2 for blur/deformed anatomy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
152
modules/story.py
152
modules/story.py
@@ -223,14 +223,32 @@ def plan_structure(bp, folder):
|
||||
if not beats_context:
|
||||
beats_context = bp.get('plot_beats', [])
|
||||
|
||||
target_chapters = bp.get('length_settings', {}).get('chapters', 'flexible')
|
||||
target_words = bp.get('length_settings', {}).get('words', 'flexible')
|
||||
chars_summary = [{"name": c.get("name"), "role": c.get("role")} for c in bp.get('characters', [])]
|
||||
|
||||
prompt = f"""
|
||||
ROLE: Story Architect
|
||||
TASK: Create a structural event outline.
|
||||
|
||||
ARCHETYPE: {structure_type}
|
||||
TITLE: {bp['book_metadata']['title']}
|
||||
EXISTING_BEATS: {json.dumps(beats_context)}
|
||||
|
||||
TASK: Create a detailed structural event outline for a {target_chapters}-chapter book.
|
||||
|
||||
BOOK:
|
||||
- TITLE: {bp['book_metadata']['title']}
|
||||
- GENRE: {bp.get('book_metadata', {}).get('genre', 'Fiction')}
|
||||
- TARGET_CHAPTERS: {target_chapters}
|
||||
- TARGET_WORDS: {target_words}
|
||||
- STRUCTURE: {structure_type}
|
||||
|
||||
CHARACTERS: {json.dumps(chars_summary)}
|
||||
|
||||
USER_BEATS (must all be preserved and woven into the outline):
|
||||
{json.dumps(beats_context)}
|
||||
|
||||
REQUIREMENTS:
|
||||
- Produce enough events to fill approximately {target_chapters} chapters.
|
||||
- Each event must serve a narrative purpose (setup, escalation, reversal, climax, resolution).
|
||||
- Distribute events across a beginning, middle, and end — avoid front-loading.
|
||||
- Character arcs must be visible through the events (growth, change, revelation).
|
||||
|
||||
OUTPUT_FORMAT (JSON): {{ "events": [{{ "description": "String", "purpose": "String" }}] }}
|
||||
"""
|
||||
try:
|
||||
@@ -612,16 +630,25 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None):
|
||||
trunc_content = prev_content[-3000:] if len(prev_content) > 3000 else prev_content
|
||||
prev_context_block = f"\nPREVIOUS CHAPTER TEXT (For Tone & Continuity):\n{trunc_content}\n"
|
||||
|
||||
total_chapters = ls.get('chapters', '?')
|
||||
prompt = f"""
|
||||
ROLE: Fiction Writer
|
||||
TASK: Write Chapter {chap['chapter_number']}: {chap['title']}
|
||||
|
||||
|
||||
METADATA:
|
||||
- GENRE: {genre}
|
||||
- FORMAT: {ls.get('label', 'Story')}
|
||||
- PACING: {pacing}
|
||||
- TARGET_WORDS: ~{est_words}
|
||||
- POSITION: Chapter {chap['chapter_number']} of {total_chapters} — calibrate narrative tension accordingly (early = setup/intrigue, middle = escalation, final third = payoff/climax)
|
||||
- PACING: {pacing} — see PACING_GUIDE below
|
||||
- TARGET_WORDS: ~{est_words} (write to this length; do not summarise to save space)
|
||||
- POV: {pov_char if pov_char else 'Protagonist'}
|
||||
|
||||
PACING_GUIDE:
|
||||
- 'Very Fast': Pure action/dialogue. Minimal description. Short punchy paragraphs.
|
||||
- 'Fast': Keep momentum. No lingering. Cut to the next beat quickly.
|
||||
- 'Standard': Balanced dialogue and description. Standard paragraph lengths.
|
||||
- 'Slow': Detailed, atmospheric. Linger on emotion and environment.
|
||||
- 'Very Slow': Deep introspection. Heavy sensory immersion. Slow burn tension.
|
||||
|
||||
STYLE_GUIDE:
|
||||
{style_block}
|
||||
@@ -662,12 +689,6 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None):
|
||||
12. PROSE DYNAMICS: Vary sentence length. Use strong verbs. Avoid passive voice.
|
||||
13. CLARITY: Ensure sentences are clear and readable. Avoid convoluted phrasing.
|
||||
|
||||
- 'Very Fast': Rapid fire, pure action/dialogue, minimal description.
|
||||
- 'Fast': Punchy, keep it moving.
|
||||
- 'Standard': Balanced dialogue and description.
|
||||
- 'Slow': Detailed, atmospheric, immersive.
|
||||
- 'Very Slow': Deep introspection, heavy sensory detail, slow burn.
|
||||
|
||||
CONTEXT:
|
||||
- STORY_SO_FAR: {prev_sum}
|
||||
{prev_context_block}
|
||||
@@ -750,43 +771,50 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None):
|
||||
guidelines = get_style_guidelines()
|
||||
fw_list = '", "'.join(guidelines['filter_words'])
|
||||
|
||||
# Exclude current critique from history to avoid duplication in prompt
|
||||
history_str = "\n".join(past_critiques[:-1]) if len(past_critiques) > 1 else "None"
|
||||
|
||||
# Cap history to last 2 critiques to avoid token bloat
|
||||
history_str = "\n".join(past_critiques[-3:-1]) if len(past_critiques) > 1 else "None"
|
||||
|
||||
refine_prompt = f"""
|
||||
ROLE: Automated Editor
|
||||
TASK: Rewrite text to satisfy critique and style rules.
|
||||
|
||||
CRITIQUE:
|
||||
TASK: Rewrite the draft chapter to address the critique. Preserve the narrative content and approximate word count.
|
||||
|
||||
CURRENT_CRITIQUE:
|
||||
{critique}
|
||||
|
||||
HISTORY:
|
||||
|
||||
PREVIOUS_ATTEMPTS (context only):
|
||||
{history_str}
|
||||
|
||||
CONSTRAINTS:
|
||||
|
||||
HARD_CONSTRAINTS:
|
||||
- TARGET_WORDS: ~{est_words} (maintain this length — do not condense or summarise)
|
||||
- BEATS MUST BE COVERED: {json.dumps(chap.get('beats', []))}
|
||||
- SUMMARY CONTEXT: {prev_sum[:1500]}
|
||||
|
||||
AUTHOR_VOICE:
|
||||
{persona_info}
|
||||
|
||||
STYLE:
|
||||
{style_block}
|
||||
{char_visuals}
|
||||
- BEATS: {json.dumps(chap.get('beats', []))}
|
||||
|
||||
OPTIMIZATION_RULES:
|
||||
1. NO_FILTERS: Remove [{fw_list}].
|
||||
2. VARIETY: No consecutive sentence starts.
|
||||
3. SUBTEXT: Indirect dialogue.
|
||||
4. TONE: Match {meta.get('genre', 'Fiction')}.
|
||||
5. INTERACTION: Use environment.
|
||||
6. DRAMA: No summary mode.
|
||||
7. ACTIVE_VERBS: No 'was/were' + ing.
|
||||
8. SHOWING: Physical emotion.
|
||||
9. LOGIC: Continuous staging.
|
||||
10. CLARITY: Simple structures.
|
||||
|
||||
INPUT_CONTEXT:
|
||||
- SUMMARY: {prev_sum}
|
||||
- PREVIOUS_TEXT: {prev_context_block}
|
||||
- DRAFT: {current_text}
|
||||
|
||||
OUTPUT: Polished Markdown.
|
||||
|
||||
PROSE_RULES (fix each one found in the draft):
|
||||
1. FILTER_REMOVAL: Remove filter words [{fw_list}] — rewrite to show the sensation directly.
|
||||
2. VARIETY: No two consecutive sentences starting with the same word or pronoun.
|
||||
3. SUBTEXT: Dialogue must imply meaning — not state it outright.
|
||||
4. TONE: Match {meta.get('genre', 'Fiction')} conventions throughout.
|
||||
5. ENVIRONMENT: Characters interact with their physical space.
|
||||
6. NO_SUMMARY_MODE: Dramatise key moments — do not skip or summarise them.
|
||||
7. ACTIVE_VOICE: Replace 'was/were + verb-ing' constructions with active alternatives.
|
||||
8. SHOWING: Render emotion through physical reactions, not labels.
|
||||
9. STAGING: Characters must enter and exit physically — no teleporting.
|
||||
10. CLARITY: Prefer simple sentence structures over convoluted ones.
|
||||
|
||||
DRAFT_TO_REWRITE:
|
||||
{current_text}
|
||||
|
||||
PREVIOUS_CHAPTER_ENDING (maintain continuity):
|
||||
{prev_context_block}
|
||||
|
||||
OUTPUT: Complete polished chapter in Markdown. Include the chapter header. Same approximate length as the draft.
|
||||
"""
|
||||
try:
|
||||
# Use Writer model (Flash) for refinement to save costs (Flash 1.5 is sufficient for editing)
|
||||
@@ -1159,25 +1187,33 @@ def check_and_propagate(bp, manuscript, changed_chap_num, folder, change_summary
|
||||
|
||||
utils.log("WRITER", f" -> Checking Ch {target_chap['num']} for continuity...")
|
||||
|
||||
chap_word_count = len(target_chap.get('content', '').split())
|
||||
prompt = f"""
|
||||
ROLE: Continuity Checker
|
||||
TASK: Determine if chapter needs rewrite based on new context.
|
||||
|
||||
INPUT_DATA:
|
||||
- CHANGED_CHAPTER: {changed_chap_num}
|
||||
- NEW_CONTEXT: {current_context}
|
||||
- CURRENT_CHAPTER_TEXT: {target_chap['content'][:5000]}...
|
||||
|
||||
TASK: Determine if a chapter contradicts a story change. If it does, rewrite it to fix the contradiction.
|
||||
|
||||
CHANGED_CHAPTER: {changed_chap_num}
|
||||
CHANGE_SUMMARY: {current_context}
|
||||
|
||||
CHAPTER_TO_CHECK (Ch {target_chap['num']}):
|
||||
{target_chap['content'][:12000]}
|
||||
|
||||
DECISION_LOGIC:
|
||||
- Compare CURRENT_CHAPTER_TEXT with NEW_CONTEXT.
|
||||
- If the chapter contradicts the new context (e.g. references events that didn't happen, or characters who are now dead/absent), it needs a REWRITE.
|
||||
- If it fits fine, NO_CHANGE.
|
||||
|
||||
- If the chapter directly contradicts the change (references dead characters, items that no longer exist, events that didn't happen), status = REWRITE.
|
||||
- If the chapter is consistent or only tangentially related, status = NO_CHANGE.
|
||||
- Be conservative — only rewrite if there is a genuine contradiction.
|
||||
|
||||
REWRITE_RULES (apply only if REWRITE):
|
||||
- Fix the specific contradiction. Preserve all other content.
|
||||
- The rewritten chapter MUST be approximately {chap_word_count} words (same length as original).
|
||||
- Include the chapter header formatted as Markdown H1.
|
||||
- Do not add new plot points not in the original.
|
||||
|
||||
OUTPUT_FORMAT (JSON):
|
||||
{{
|
||||
"status": "NO_CHANGE" or "REWRITE",
|
||||
"reason": "Brief explanation",
|
||||
"content": "Full Markdown text of the rewritten chapter (ONLY if status is REWRITE, otherwise null)"
|
||||
"reason": "Brief explanation of the contradiction or why it's consistent",
|
||||
"content": "Full Markdown rewritten chapter (ONLY if status is REWRITE, otherwise null)"
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user