Strengthened writing.
This commit is contained in:
586
modules/story.py
586
modules/story.py
@@ -36,20 +36,19 @@ def refresh_style_guidelines(model, folder=None):
|
||||
current = get_style_guidelines()
|
||||
|
||||
prompt = f"""
|
||||
Act as a Literary Editor. Update our 'Banned Words' lists for AI writing.
|
||||
ROLE: Literary Editor
|
||||
TASK: Update 'Banned Words' lists for AI writing.
|
||||
|
||||
CURRENT AI-ISMS (Cliches to avoid):
|
||||
{json.dumps(current.get('ai_isms', []))}
|
||||
INPUT_DATA:
|
||||
- CURRENT_AI_ISMS: {json.dumps(current.get('ai_isms', []))}
|
||||
- CURRENT_FILTER_WORDS: {json.dumps(current.get('filter_words', []))}
|
||||
|
||||
CURRENT FILTER WORDS (Distancing language):
|
||||
{json.dumps(current.get('filter_words', []))}
|
||||
|
||||
TASK:
|
||||
1. Review the lists. Remove any that are too common/safe (false positives).
|
||||
INSTRUCTIONS:
|
||||
1. Review lists. Remove false positives.
|
||||
2. Add new common AI tropes (e.g. 'neon-lit', 'bustling', 'a sense of', 'mined', 'delved').
|
||||
3. Ensure the list is robust but not paralyzing.
|
||||
3. Ensure robustness.
|
||||
|
||||
RETURN JSON: {{ "ai_isms": [strings], "filter_words": [strings] }}
|
||||
OUTPUT_FORMAT (JSON): {{ "ai_isms": [strings], "filter_words": [strings] }}
|
||||
"""
|
||||
try:
|
||||
response = model.generate_content(prompt)
|
||||
@@ -131,25 +130,24 @@ def enrich(bp, folder, context=""):
|
||||
if 'plot_beats' not in bp: bp['plot_beats'] = []
|
||||
|
||||
prompt = f"""
|
||||
You are a Creative Director.
|
||||
The user has provided a minimal description. You must build a full Book Bible.
|
||||
ROLE: Creative Director
|
||||
TASK: Create a comprehensive Book Bible from the user description.
|
||||
|
||||
USER DESCRIPTION: "{bp.get('manual_instruction', 'A generic story')}"
|
||||
CONTEXT (Sequel): {context}
|
||||
INPUT DATA:
|
||||
- USER_DESCRIPTION: "{bp.get('manual_instruction', 'A generic story')}"
|
||||
- CONTEXT (Sequel): {context}
|
||||
|
||||
TASK:
|
||||
STEPS:
|
||||
1. Generate a catchy Title.
|
||||
2. Define the Genre and Tone.
|
||||
3. Determine the Time Period (e.g. "Modern", "1920s", "Sci-Fi Future").
|
||||
4. Define Formatting Rules for text messages, thoughts, and chapter headers.
|
||||
5. Create Protagonist and Antagonist/Love Interest.
|
||||
- IF SEQUEL: Decide if we continue with previous protagonists or shift to side characters based on USER DESCRIPTION.
|
||||
- IF NEW CHARACTERS: Create them.
|
||||
- IF RETURNING: Reuse details from CONTEXT.
|
||||
- Logic: If sequel, reuse context. If new, create.
|
||||
6. Outline 5-7 core Plot Beats.
|
||||
7. Define a 'structure_prompt' describing the narrative arc (e.g. "Hero's Journey", "3-Act Structure", "Detective Procedural").
|
||||
|
||||
RETURN JSON in this EXACT format:
|
||||
OUTPUT_FORMAT (JSON):
|
||||
{{
|
||||
"book_metadata": {{ "title": "Book Title", "genre": "Genre", "content_warnings": ["Violence", "Major Character Death"], "structure_prompt": "...", "style": {{ "tone": "Tone", "time_period": "Modern", "formatting_rules": ["Chapter Headers: Number + Title", "Text Messages: Italic", "Thoughts: Italic"] }} }},
|
||||
"characters": [ {{ "name": "John Doe", "role": "Protagonist", "description": "Description", "key_events": ["Planned injury in Act 2"] }} ],
|
||||
@@ -224,7 +222,16 @@ def plan_structure(bp, folder):
|
||||
if not beats_context:
|
||||
beats_context = bp.get('plot_beats', [])
|
||||
|
||||
prompt = f"{structure_type}\nTITLE: {bp['book_metadata']['title']}\nBEATS: {json.dumps(beats_context)}\nReturn JSON: {{'events': [{{'description':'...', 'purpose':'...'}}]}}"
|
||||
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)}
|
||||
|
||||
OUTPUT_FORMAT (JSON): {{ "events": [{{ "description": "String", "purpose": "String" }}] }}
|
||||
"""
|
||||
try:
|
||||
response = ai.model_logic.generate_content(prompt)
|
||||
utils.log_usage(folder, "logic-pro", response.usage_metadata)
|
||||
@@ -241,23 +248,23 @@ def expand(events, pass_num, target_chapters, bp, folder):
|
||||
beats_context = bp.get('plot_beats', [])
|
||||
|
||||
prompt = f"""
|
||||
You are a Story Architect.
|
||||
Goal: Flesh out this outline for a {target_chapters}-chapter book.
|
||||
Current Status: {len(events)} beats.
|
||||
ROLE: Story Architect
|
||||
TASK: Expand the outline to fit a {target_chapters}-chapter book.
|
||||
CURRENT_COUNT: {len(events)} beats.
|
||||
|
||||
ORIGINAL OUTLINE:
|
||||
INPUT_OUTLINE:
|
||||
{json.dumps(beats_context)}
|
||||
|
||||
INSTRUCTIONS:
|
||||
1. Look for jumps in time or logic.
|
||||
2. Insert new intermediate events to smooth the pacing.
|
||||
3. Deepen subplots while staying true to the ORIGINAL OUTLINE.
|
||||
4. Do NOT remove or drastically alter the original outline points; expand AROUND them.
|
||||
|
||||
CURRENT EVENTS:
|
||||
CURRENT_EVENTS:
|
||||
{json.dumps(events)}
|
||||
|
||||
Return JSON: {{'events': [ ...updated full list... ]}}
|
||||
RULES:
|
||||
1. Detect pacing gaps.
|
||||
2. Insert intermediate events.
|
||||
3. Deepen subplots.
|
||||
4. PRESERVE original beats.
|
||||
|
||||
OUTPUT_FORMAT (JSON): {{ "events": [{{ "description": "String", "purpose": "String" }}] }}
|
||||
"""
|
||||
try:
|
||||
response = ai.model_logic.generate_content(prompt)
|
||||
@@ -295,18 +302,19 @@ def create_chapter_plan(events, bp, folder):
|
||||
pov_instruction = f"- Assign a 'pov_character' for each chapter from this list: {json.dumps(pov_chars)}."
|
||||
|
||||
prompt = f"""
|
||||
Group events into Chapters.
|
||||
TARGET CHAPTERS: {target} (Approximate. Feel free to adjust +/- 20% for better pacing).
|
||||
TARGET WORDS: {words} (Total for the book).
|
||||
ROLE: Pacing Specialist
|
||||
TASK: Group events into Chapters.
|
||||
|
||||
INSTRUCTIONS:
|
||||
- Vary chapter pacing. Options: 'Very Fast', 'Fast', 'Standard', 'Slow', 'Very Slow'.
|
||||
- Assign an estimated word count to each chapter based on its pacing and content.
|
||||
CONSTRAINTS:
|
||||
- TARGET_CHAPTERS: {target}
|
||||
- TARGET_WORDS: {words}
|
||||
- INSTRUCTIONS:
|
||||
{structure_instructions}
|
||||
{pov_instruction}
|
||||
|
||||
EVENTS: {json.dumps(events)}
|
||||
Return JSON: [{{'chapter_number':1, 'title':'...', 'pov_character': 'Name', 'pacing': 'Standard', 'estimated_words': 2000, 'beats':[...]}}]
|
||||
INPUT_EVENTS: {json.dumps(events)}
|
||||
|
||||
OUTPUT_FORMAT (JSON): [{{ "chapter_number": 1, "title": "String", "pov_character": "String", "pacing": "String", "estimated_words": 2000, "beats": ["String"] }}]
|
||||
"""
|
||||
try:
|
||||
response = ai.model_logic.generate_content(prompt)
|
||||
@@ -345,24 +353,26 @@ def update_tracking(folder, chapter_num, chapter_text, current_tracking):
|
||||
utils.log("TRACKER", f"Updating world state & character visuals for Ch {chapter_num}...")
|
||||
|
||||
prompt = f"""
|
||||
Analyze this chapter text to update the Story Bible.
|
||||
ROLE: Continuity Tracker
|
||||
TASK: Update the Story Bible based on the new chapter.
|
||||
|
||||
CURRENT TRACKING DATA:
|
||||
INPUT_TRACKING:
|
||||
{json.dumps(current_tracking)}
|
||||
|
||||
NEW CHAPTER TEXT:
|
||||
NEW_TEXT:
|
||||
{chapter_text[:500000]}
|
||||
|
||||
TASK:
|
||||
1. EVENTS: Append 1-3 concise bullet points summarizing key plot events in this chapter to the 'events' list.
|
||||
2. CHARACTERS: Update entries for any characters appearing in the scene.
|
||||
OPERATIONS:
|
||||
1. EVENTS: Append 1-3 key plot points to 'events'.
|
||||
2. CHARACTERS: Update 'descriptors', 'likes_dislikes', 'speech_style', 'last_worn', 'major_events'.
|
||||
- "descriptors": List of strings. Add PERMANENT physical traits (height, hair, eyes), specific items (jewelry, weapons). Avoid duplicates.
|
||||
- "likes_dislikes": List of strings. Add specific preferences, likes, or dislikes mentioned (e.g., "Hates coffee", "Loves jazz").
|
||||
- "speech_style": String. Describe how they speak (e.g. "Formal, no contractions", "Uses slang", "Stutters", "Short sentences").
|
||||
- "last_worn": String. Update if specific clothing is described. IMPORTANT: If a significant time jump occurred (e.g. next day) and no new clothing is described, reset this to "Unknown".
|
||||
- "major_events": List of strings. Log significant life-altering events occurring in THIS chapter (e.g. "Lost an arm", "Married", "Betrayed by X").
|
||||
3. CONTENT_WARNINGS: List of strings. Identify specific triggers present in this chapter (e.g. "Graphic Violence", "Sexual Assault", "Torture", "Self-Harm"). Append to existing list.
|
||||
3. WARNINGS: Append new 'content_warnings'.
|
||||
|
||||
RETURN JSON with the SAME structure as CURRENT TRACKING DATA (events list, characters dict, content_warnings list).
|
||||
OUTPUT_FORMAT (JSON): Return the updated tracking object structure.
|
||||
"""
|
||||
try:
|
||||
response = ai.model_logic.generate_content(prompt)
|
||||
@@ -378,20 +388,27 @@ def evaluate_chapter_quality(text, chapter_title, genre, model, folder):
|
||||
ai_isms = "', '".join(guidelines['ai_isms'])
|
||||
fw_examples = ", ".join([f"'He {w}'" for w in guidelines['filter_words'][:5]])
|
||||
|
||||
# Calculate dynamic suggestion count based on length
|
||||
word_count = len(text.split()) if text else 0
|
||||
min_sugg = max(3, int(word_count / 500))
|
||||
max_sugg = min_sugg + 2
|
||||
suggestion_range = f"{min_sugg}-{max_sugg}"
|
||||
|
||||
prompt = f"""
|
||||
Act as a World-Class Literary Editor (e.g., Maxwell Perkins). Analyze this chapter draft with extreme scrutiny.
|
||||
CHAPTER TITLE: {chapter_title}
|
||||
GENRE: {genre}
|
||||
ROLE: Senior Literary Editor
|
||||
TASK: Critique chapter draft.
|
||||
|
||||
STRICT PROHIBITIONS (Automatic deduction):
|
||||
- "AI-isms": '{ai_isms}'. (Evaluate in context of {genre}. Allow genre-appropriate tropes, but penalize robotic clichés).
|
||||
- Filter Words: {fw_examples}, etc. (Show the sensation/action, don't state the internal process).
|
||||
- Stilted Dialogue: Characters speaking in perfect paragraphs without interruptions, slang, or subtext.
|
||||
- White Room Syndrome: Dialogue occurring in a void without interaction with the setting/props.
|
||||
- "As You Know, Bob": Characters explaining things to each other that they both already know.
|
||||
- Summary Mode: Summarizing conversation or action instead of dramatizing it (e.g. "They discussed the plan" vs writing the dialogue).
|
||||
METADATA:
|
||||
- TITLE: {chapter_title}
|
||||
- GENRE: {genre}
|
||||
|
||||
CRITERIA:
|
||||
PROHIBITED_PATTERNS:
|
||||
- AI_ISMS: {ai_isms}
|
||||
- FILTER_WORDS: {fw_examples}
|
||||
- CLICHES: White Room, As You Know Bob, Summary Mode, Anachronisms.
|
||||
- SYNTAX: Repetitive structure, Passive Voice, Adverb Reliance.
|
||||
|
||||
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?
|
||||
3. VOICE & TONE: Is the narrative voice distinct? Does it match the genre?
|
||||
@@ -399,13 +416,25 @@ def evaluate_chapter_quality(text, chapter_title, genre, model, folder):
|
||||
5. SHOW, DON'T TELL: Are emotions shown through physical reactions and subtext?
|
||||
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?
|
||||
9. DIALOGUE AUTHENTICITY: Do characters sound distinct? Is there subtext? Avoids "on-the-nose" dialogue.
|
||||
10. PLOT RELEVANCE: Does the chapter advance the plot or character arcs significantly? Avoids filler.
|
||||
11. STAGING & FLOW: Do characters enter/exit physically? Do paragraphs transition logically (Action -> Reaction)?
|
||||
12. PROSE DYNAMICS: Is there sentence variety? Is the rhythm pleasing? Avoids purple prose and excessive adjectives.
|
||||
13. CLARITY & READABILITY: Is the text easy to follow? Are sentences clear and concise?
|
||||
|
||||
Rate on a scale of 1-10. (Be harsh. 10 is Pulitzer level. 6 is average. Anything below 8 needs work).
|
||||
SCORING_SCALE:
|
||||
- 10 (Masterpiece): Flawless, impactful, ready for print.
|
||||
- 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.
|
||||
|
||||
Return JSON: {{
|
||||
'score': int,
|
||||
'critique': 'Detailed analysis of flaws, citing specific examples from the text.',
|
||||
'actionable_feedback': 'List of 3-5 specific, ruthless instructions for the rewrite (e.g. "Expand the middle dialogue", "Add sensory details about the rain", "Dramatize the argument instead of summarizing it").'
|
||||
OUTPUT_FORMAT (JSON):
|
||||
{{
|
||||
"score": int,
|
||||
"critique": "Detailed analysis of flaws, citing specific examples from the text.",
|
||||
"actionable_feedback": "List of {suggestion_range} specific, ruthless instructions for the rewrite (e.g. 'Expand the middle dialogue', 'Add sensory details about the rain', 'Dramatize the argument instead of summarizing it')."
|
||||
}}
|
||||
"""
|
||||
try:
|
||||
@@ -431,29 +460,21 @@ def check_pacing(bp, summary, last_chapter_text, last_chapter_data, remaining_ch
|
||||
genre = meta.get('genre', 'Fiction')
|
||||
|
||||
prompt = f"""
|
||||
Act as a Senior Structural Editor.
|
||||
We just finished Chapter {last_chapter_data['chapter_number']}: "{last_chapter_data['title']}".
|
||||
ROLE: Structural Editor
|
||||
TASK: Analyze pacing.
|
||||
|
||||
STORY SO FAR (Summary):
|
||||
{summary[-3000:]}
|
||||
CONTEXT:
|
||||
- PREVIOUS_SUMMARY: {summary[-3000:]}
|
||||
- CURRENT_CHAPTER: {last_chapter_text[-2000:]}
|
||||
- UPCOMING: {json.dumps([c['title'] for c in remaining_chapters[:3]])}
|
||||
- REMAINING_COUNT: {len(remaining_chapters)}
|
||||
|
||||
JUST WRITTEN (Last 2000 chars):
|
||||
{last_chapter_text[-2000:]}
|
||||
LOGIC:
|
||||
- IF skipped major beats -> ADD_BRIDGE
|
||||
- IF covered next chapter's beats -> CUT_NEXT
|
||||
- ELSE -> OK
|
||||
|
||||
UPCOMING CHAPTERS (Next 3):
|
||||
{json.dumps([c['title'] for c in remaining_chapters[:3]])}
|
||||
|
||||
TOTAL REMAINING: {len(remaining_chapters)} chapters.
|
||||
|
||||
ANALYSIS TASK:
|
||||
Determine if the story is moving too fast (Rushed) or too slow (Dragging) based on the {genre} genre.
|
||||
|
||||
DECISION RULES:
|
||||
- If the last chapter skipped over major emotional reactions, travel, or necessary setup -> ADD_BRIDGE.
|
||||
- If the last chapter already covered the events of the NEXT chapter -> CUT_NEXT.
|
||||
- If the pacing is fine -> OK.
|
||||
|
||||
RETURN JSON:
|
||||
OUTPUT_FORMAT (JSON):
|
||||
{{
|
||||
"status": "ok" or "add_bridge" or "cut_next",
|
||||
"reason": "Explanation...",
|
||||
@@ -474,17 +495,16 @@ def create_initial_persona(bp, folder):
|
||||
style = meta.get('style', {})
|
||||
|
||||
prompt = f"""
|
||||
Create a fictional 'Author Persona' best suited to write this book.
|
||||
ROLE: Creative Director
|
||||
TASK: Create a fictional 'Author Persona'.
|
||||
|
||||
BOOK DETAILS:
|
||||
Title: {meta.get('title')}
|
||||
Genre: {meta.get('genre')}
|
||||
Tone: {style.get('tone')}
|
||||
Target Audience: {meta.get('target_audience')}
|
||||
METADATA:
|
||||
- TITLE: {meta.get('title')}
|
||||
- GENRE: {meta.get('genre')}
|
||||
- TONE: {style.get('tone')}
|
||||
- AUDIENCE: {meta.get('target_audience')}
|
||||
|
||||
TASK:
|
||||
Create a profile for the ideal writer of this book.
|
||||
Return JSON: {{ "name": "Pen Name", "bio": "Description of writing style (voice, sentence structure, vocabulary)...", "age": "...", "gender": "..." }}
|
||||
OUTPUT_FORMAT (JSON): {{ "name": "Pen Name", "bio": "Description of writing style (voice, sentence structure, vocabulary)...", "age": "...", "gender": "..." }}
|
||||
"""
|
||||
try:
|
||||
response = ai.model_logic.generate_content(prompt)
|
||||
@@ -500,20 +520,16 @@ def refine_persona(bp, text, folder):
|
||||
current_bio = ad.get('bio', 'Standard style.')
|
||||
|
||||
prompt = f"""
|
||||
Act as a Literary Stylist. Analyze this text sample from the book.
|
||||
ROLE: Literary Stylist
|
||||
TASK: Refine Author Bio based on text sample.
|
||||
|
||||
TEXT:
|
||||
{text[:3000]}
|
||||
INPUT_DATA:
|
||||
- TEXT_SAMPLE: {text[:3000]}
|
||||
- CURRENT_BIO: {current_bio}
|
||||
|
||||
CURRENT AUTHOR BIO:
|
||||
{current_bio}
|
||||
GOAL: Ensure future chapters sound exactly like the sample. Highlight quirks, patterns, vocabulary.
|
||||
|
||||
TASK:
|
||||
Refine the Author Bio to better match the actual text produced.
|
||||
Highlight specific stylistic quirks, sentence patterns, or vocabulary choices found in the text.
|
||||
The goal is to ensure future chapters sound exactly like this one.
|
||||
|
||||
Return JSON: {{ "bio": "Updated bio..." }}
|
||||
OUTPUT_FORMAT (JSON): {{ "bio": "Updated bio..." }}
|
||||
"""
|
||||
try:
|
||||
response = ai.model_logic.generate_content(prompt)
|
||||
@@ -572,8 +588,9 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None):
|
||||
for name, data in tracking['characters'].items():
|
||||
desc = ", ".join(data.get('descriptors', []))
|
||||
likes = ", ".join(data.get('likes_dislikes', []))
|
||||
speech = data.get('speech_style', 'Unknown')
|
||||
worn = data.get('last_worn', 'Unknown')
|
||||
char_visuals += f"- {name}: {desc}\n * Likes/Dislikes: {likes}\n"
|
||||
char_visuals += f"- {name}: {desc}\n * Speech: {speech}\n * Likes/Dislikes: {likes}\n"
|
||||
|
||||
major = data.get('major_events', [])
|
||||
if major: char_visuals += f" * Major Events: {'; '.join(major)}\n"
|
||||
@@ -594,45 +611,69 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None):
|
||||
prev_context_block = f"\nPREVIOUS CHAPTER TEXT (For Tone & Continuity):\n{trunc_content}\n"
|
||||
|
||||
prompt = f"""
|
||||
Write Chapter {chap['chapter_number']}: {chap['title']}
|
||||
GENRE: {genre}
|
||||
ROLE: Fiction Writer
|
||||
TASK: Write Chapter {chap['chapter_number']}: {chap['title']}
|
||||
|
||||
PACING GUIDE:
|
||||
- Format: {ls.get('label', 'Story')}
|
||||
- Chapter Pacing: {pacing}
|
||||
- Target Word Count: ~{est_words} (Use this as a guide, but prioritize story flow. Allow flexibility.)
|
||||
- POV Character: {pov_char if pov_char else 'Protagonist'}
|
||||
METADATA:
|
||||
- GENRE: {genre}
|
||||
- FORMAT: {ls.get('label', 'Story')}
|
||||
- PACING: {pacing}
|
||||
- TARGET_WORDS: ~{est_words}
|
||||
- POV: {pov_char if pov_char else 'Protagonist'}
|
||||
|
||||
STYLE & FORMATTING:
|
||||
STYLE_GUIDE:
|
||||
{style_block}
|
||||
|
||||
AUTHOR VOICE (CRITICAL):
|
||||
AUTHOR_VOICE:
|
||||
{persona_info}
|
||||
|
||||
INSTRUCTION:
|
||||
Write the scene.
|
||||
INSTRUCTIONS:
|
||||
- Start with the Chapter Header formatted as Markdown H1 (e.g. '# Chapter X: Title'). Follow the 'Formatting Rules' for the header style.
|
||||
|
||||
- DEEP POV: Immerse the reader in the POV character's immediate experience. Filter descriptions through their specific worldview and emotional state.
|
||||
- SENSORY ANCHORING: Start scenes by establishing Who, Where, and When immediately, anchored with a sensory detail.
|
||||
- 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.
|
||||
- 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, grounding sensory details (smell, touch, sound) rather than generic descriptions.
|
||||
- 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').
|
||||
- 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.
|
||||
- GENRE CONSISTENCY: Ensure all introductions of characters, places, items, or actions are strictly appropriate for the {genre} genre. Avoid anachronisms or tonal clashes.
|
||||
|
||||
QUALITY_CRITERIA:
|
||||
1. ENGAGEMENT & TENSION: Grip the reader. Ensure conflict/tension in every scene.
|
||||
2. SCENE EXECUTION: Flesh out the middle. Avoid summarizing key moments.
|
||||
3. VOICE & TONE: Distinct narrative voice matching the genre.
|
||||
4. SENSORY IMMERSION: Engage all five senses.
|
||||
5. SHOW, DON'T TELL: Show emotions through physical reactions and subtext.
|
||||
6. CHARACTER AGENCY: Characters must drive the plot through active choices.
|
||||
7. PACING: Avoid rushing. Ensure the ending lands with impact.
|
||||
8. GENRE APPROPRIATENESS: Introductions of characters, places, items, or actions must be consistent with {genre} conventions.
|
||||
9. DIALOGUE AUTHENTICITY: Characters must sound distinct. Use subtext. Avoid "on-the-nose" dialogue.
|
||||
10. PLOT RELEVANCE: Every scene must advance the plot or character arcs. No filler.
|
||||
11. STAGING & FLOW: Characters must enter/exit physically. Paragraphs must transition logically.
|
||||
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.
|
||||
|
||||
PREVIOUS CONTEXT (Story So Far): {prev_sum}
|
||||
CONTEXT:
|
||||
- STORY_SO_FAR: {prev_sum}
|
||||
{prev_context_block}
|
||||
CHARACTERS: {json.dumps(bp['characters'])}
|
||||
- CHARACTERS: {json.dumps(bp['characters'])}
|
||||
{char_visuals}
|
||||
SCENE BEATS: {json.dumps(chap['beats'])}
|
||||
- SCENE_BEATS: {json.dumps(chap['beats'])}
|
||||
|
||||
Output Markdown.
|
||||
OUTPUT: Markdown text.
|
||||
"""
|
||||
current_text = ""
|
||||
try:
|
||||
@@ -647,6 +688,7 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None):
|
||||
max_attempts = 5
|
||||
SCORE_AUTO_ACCEPT = 9
|
||||
SCORE_PASSING = 7
|
||||
SCORE_REWRITE_THRESHOLD = 6
|
||||
|
||||
best_score = 0
|
||||
best_text = current_text
|
||||
@@ -681,6 +723,26 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None):
|
||||
utils.log("WRITER", f" ⚠️ Quality low ({best_score}/{SCORE_PASSING}) but max attempts reached. Proceeding.")
|
||||
return best_text
|
||||
|
||||
if score < SCORE_REWRITE_THRESHOLD:
|
||||
utils.log("WRITER", f" -> Score {score} < {SCORE_REWRITE_THRESHOLD}. Triggering FULL REWRITE (Fresh Draft)...")
|
||||
|
||||
full_rewrite_prompt = prompt + f"""
|
||||
|
||||
[SYSTEM ALERT: QUALITY CHECK FAILED]
|
||||
The previous draft was rejected.
|
||||
CRITIQUE: {critique}
|
||||
|
||||
NEW TASK: Discard the previous attempt. Write a FRESH version of the chapter that addresses the critique above.
|
||||
"""
|
||||
|
||||
try:
|
||||
resp_rewrite = ai.model_writer.generate_content(full_rewrite_prompt)
|
||||
utils.log_usage(folder, "writer-flash", resp_rewrite.usage_metadata)
|
||||
current_text = resp_rewrite.text
|
||||
continue
|
||||
except Exception as e:
|
||||
utils.log("WRITER", f"Full rewrite failed: {e}. Falling back to refinement.")
|
||||
|
||||
utils.log("WRITER", f" -> Refining Ch {chap['chapter_number']} based on feedback...")
|
||||
|
||||
guidelines = get_style_guidelines()
|
||||
@@ -690,32 +752,39 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None):
|
||||
history_str = "\n".join(past_critiques[:-1]) if len(past_critiques) > 1 else "None"
|
||||
|
||||
refine_prompt = f"""
|
||||
Act as a Senior Editor. Rewrite this chapter to fix the issues identified below and ELEVATE the writing quality.
|
||||
ROLE: Automated Editor
|
||||
TASK: Rewrite text to satisfy critique and style rules.
|
||||
|
||||
CRITIQUE TO ADDRESS (MANDATORY):
|
||||
CRITIQUE:
|
||||
{critique}
|
||||
|
||||
PREVIOUS CRITIQUES (Reference):
|
||||
HISTORY:
|
||||
{history_str}
|
||||
|
||||
STYLIZED REWRITE INSTRUCTIONS:
|
||||
1. REMOVE FILTER WORDS: Delete "{fw_list}". Describe the image, sensation, or sound directly.
|
||||
2. VARY SENTENCE STRUCTURE: Do not start consecutive sentences with "He", "She", or "The". Use introductory clauses and varying lengths.
|
||||
3. SUBTEXT: Ensure dialogue implies meaning rather than stating it outright. People rarely say exactly what they mean.
|
||||
4. GENRE CONSISTENCY: Ensure the tone matches {meta.get('genre', 'Fiction')}.
|
||||
5. SETTING INTERACTION: Ensure characters interact with their environment (props, weather, lighting) during dialogue.
|
||||
6. DRAMATIZE, DON'T SUMMARIZE: Expand summarized moments into full scenes with dialogue and action. Ensure the scene feels "full" and immersive.
|
||||
7. STRONG VERBS: Avoid "was/were" constructions. Use active, specific verbs to drive the prose.
|
||||
8. EMOTIONAL RESONANCE: Ensure the POV character's internal state is clear and drives the narrative.
|
||||
CONSTRAINTS:
|
||||
{persona_info}
|
||||
{style_block}
|
||||
{char_visuals}
|
||||
- BEATS: {json.dumps(chap.get('beats', []))}
|
||||
|
||||
STORY SO FAR:
|
||||
{prev_sum}
|
||||
{prev_context_block}
|
||||
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.
|
||||
|
||||
CURRENT DRAFT:
|
||||
{current_text}
|
||||
INPUT_CONTEXT:
|
||||
- SUMMARY: {prev_sum}
|
||||
- PREVIOUS_TEXT: {prev_context_block}
|
||||
- DRAFT: {current_text}
|
||||
|
||||
Return the polished, final version of the chapter in Markdown.
|
||||
OUTPUT: Polished Markdown.
|
||||
"""
|
||||
try:
|
||||
# Use Logic model (Pro) for refinement to ensure higher quality prose
|
||||
@@ -733,11 +802,15 @@ def harvest_metadata(bp, folder, full_manuscript):
|
||||
full_text = "\n".join([c.get('content', '') for c in full_manuscript])[:500000]
|
||||
|
||||
prompt = f"""
|
||||
Analyze this manuscript text.
|
||||
EXISTING CHARACTERS: {json.dumps(bp['characters'])}
|
||||
ROLE: Data Extractor
|
||||
TASK: Identify NEW significant characters.
|
||||
|
||||
TASK: Identify NEW significant characters that appear in the text but are missing from the list.
|
||||
RETURN JSON: {{'new_characters': [{{'name':'...', 'role':'...', 'description':'...'}}]}}
|
||||
INPUT_TEXT:
|
||||
{full_text}
|
||||
|
||||
KNOWN_CHARACTERS: {json.dumps(bp['characters'])}
|
||||
|
||||
OUTPUT_FORMAT (JSON): {{ "new_characters": [{{ "name": "String", "role": "String", "description": "String" }}] }}
|
||||
"""
|
||||
try:
|
||||
response = ai.model_logic.generate_content(prompt)
|
||||
@@ -786,7 +859,12 @@ def update_persona_sample(bp, folder):
|
||||
|
||||
if author_name not in personas:
|
||||
utils.log("SYSTEM", f"Generating new persona profile for '{author_name}'...")
|
||||
prompt = f"Analyze this writing style (Tone, Voice, Vocabulary). Write a 1-sentence author bio describing it.\nTEXT: {sample_text[:1000]}"
|
||||
prompt = f"""
|
||||
ROLE: Literary Analyst
|
||||
TASK: Analyze writing style (Tone, Voice, Vocabulary).
|
||||
TEXT: {sample_text[:1000]}
|
||||
OUTPUT: 1-sentence author bio.
|
||||
"""
|
||||
try:
|
||||
response = ai.model_logic.generate_content(prompt)
|
||||
utils.log_usage(folder, "logic-pro", response.usage_metadata)
|
||||
@@ -810,14 +888,18 @@ def update_persona_sample(bp, folder):
|
||||
def refine_bible(bible, instruction, folder):
|
||||
utils.log("SYSTEM", f"Refining Bible with instruction: {instruction}")
|
||||
prompt = f"""
|
||||
Act as a Senior Developmental Editor.
|
||||
CURRENT JSON: {json.dumps(bible)}
|
||||
USER INSTRUCTION: {instruction}
|
||||
ROLE: Senior Developmental Editor
|
||||
TASK: Update the Bible JSON based on instruction.
|
||||
|
||||
TASK: Update the JSON based on the instruction. Maintain valid JSON structure.
|
||||
Ensure character motivations remain consistent and plot holes are avoided.
|
||||
INPUT_DATA:
|
||||
- CURRENT_JSON: {json.dumps(bible)}
|
||||
- INSTRUCTION: {instruction}
|
||||
|
||||
RETURN ONLY THE JSON.
|
||||
CONSTRAINTS:
|
||||
- Maintain valid JSON structure.
|
||||
- Ensure consistency.
|
||||
|
||||
OUTPUT_FORMAT (JSON): The full updated Bible JSON object.
|
||||
"""
|
||||
try:
|
||||
response = ai.model_logic.generate_content(prompt)
|
||||
@@ -845,18 +927,15 @@ def analyze_consistency(bp, manuscript, folder):
|
||||
context = "\n".join(chapter_summaries)
|
||||
|
||||
prompt = f"""
|
||||
Act as a Continuity Editor. Analyze this book summary for plot holes and inconsistencies.
|
||||
ROLE: Continuity Editor
|
||||
TASK: Analyze book summary for plot holes.
|
||||
|
||||
CHARACTERS: {json.dumps(bp.get('characters', []))}
|
||||
|
||||
CHAPTER SUMMARIES:
|
||||
INPUT_DATA:
|
||||
- CHARACTERS: {json.dumps(bp.get('characters', []))}
|
||||
- SUMMARIES:
|
||||
{context}
|
||||
|
||||
TASK:
|
||||
Identify 3-5 major continuity errors or plot holes (e.g. dead characters appearing, teleporting, forgotten injuries, motivation flips).
|
||||
If none, say "No major issues found."
|
||||
|
||||
Return JSON: {{ "issues": ["Issue 1", "Issue 2"], "score": 8, "summary": "Brief overall assessment." }} (Score 1-10 on logical consistency)
|
||||
OUTPUT_FORMAT (JSON): {{ "issues": ["Issue 1", "Issue 2"], "score": 8, "summary": "Brief overall assessment." }}
|
||||
"""
|
||||
try:
|
||||
response = ai.model_logic.generate_content(prompt)
|
||||
@@ -888,56 +967,115 @@ def rewrite_chapter_content(bp, manuscript, chapter_num, instruction, folder):
|
||||
prev_text = prev_chap.get('content', '')[-3000:] # Last 3000 chars for context
|
||||
|
||||
meta = bp.get('book_metadata', {})
|
||||
style = meta.get('style', {})
|
||||
|
||||
# Construct Persona Info (Maintain Voice)
|
||||
ad = meta.get('author_details', {})
|
||||
if not ad and 'author_bio' in meta:
|
||||
persona_info = meta['author_bio']
|
||||
else:
|
||||
persona_info = f"Name: {ad.get('name', meta.get('author', 'Unknown'))}\n"
|
||||
if ad.get('bio'): persona_info += f"Style/Bio: {ad['bio']}\n"
|
||||
|
||||
# Construct Character Visuals (Load tracking for consistency)
|
||||
char_visuals = ""
|
||||
tracking_path = os.path.join(folder, "tracking_characters.json")
|
||||
if os.path.exists(tracking_path):
|
||||
try:
|
||||
tracking_chars = utils.load_json(tracking_path)
|
||||
if tracking_chars:
|
||||
char_visuals = "\nCHARACTER TRACKING (Visuals & Preferences):\n"
|
||||
for name, data in tracking_chars.items():
|
||||
desc = ", ".join(data.get('descriptors', []))
|
||||
speech = data.get('speech_style', 'Unknown')
|
||||
char_visuals += f"- {name}: {desc}\n * Speech: {speech}\n"
|
||||
except: pass
|
||||
|
||||
# Fix: Define fw_list for the prompt
|
||||
guidelines = get_style_guidelines()
|
||||
fw_list = '", "'.join(guidelines['filter_words'])
|
||||
|
||||
prompt = f"""
|
||||
Act as a Ghostwriter. Rewrite Chapter {chapter_num}: {target_chap.get('title', '')}
|
||||
You are an expert fiction writing AI. Your task is to rewrite a specific chapter based on a user directive.
|
||||
|
||||
USER INSTRUCTION (PRIMARY DIRECTIVE):
|
||||
INPUT DATA:
|
||||
- TITLE: {meta.get('title')}
|
||||
- GENRE: {meta.get('genre')}
|
||||
- TONE: {meta.get('style', {}).get('tone')}
|
||||
- AUTHOR_VOICE: {persona_info}
|
||||
- PREVIOUS_CONTEXT: {prev_text}
|
||||
- CURRENT_DRAFT: {target_chap.get('content', '')[:5000]}
|
||||
- CHARACTERS: {json.dumps(bp.get('characters', []))}
|
||||
{char_visuals}
|
||||
|
||||
PRIMARY DIRECTIVE (USER INSTRUCTION):
|
||||
{instruction}
|
||||
|
||||
STORY CONTEXT:
|
||||
- Title: {meta.get('title')}
|
||||
- Genre: {meta.get('genre')}
|
||||
- Tone: {meta.get('style', {}).get('tone')}
|
||||
EXECUTION RULES:
|
||||
1. CONTINUITY: The new text must flow logically from PREVIOUS_CONTEXT.
|
||||
2. ADHERENCE: The PRIMARY DIRECTIVE overrides any conflicting details in CURRENT_DRAFT.
|
||||
3. VOICE: Strictly emulate the AUTHOR_VOICE.
|
||||
4. GENRE: Enforce {meta.get('genre')} conventions. No anachronisms.
|
||||
5. LOGIC: Enforce strict causality (Action -> Reaction). No teleporting characters.
|
||||
|
||||
PREVIOUS CHAPTER ENDING (Continuity):
|
||||
{prev_text}
|
||||
PROSE OPTIMIZATION RULES (STRICT ENFORCEMENT):
|
||||
- FILTER_REMOVAL: Scan for words [{fw_list}]. If found, rewrite the sentence to remove the filter and describe the sensation directly.
|
||||
- SENTENCE_VARIETY: Penalize consecutive sentences starting with the same pronoun or article. Vary structure.
|
||||
- SHOW_DONT_TELL: Convert internal summaries of emotion into physical actions or subtextual dialogue.
|
||||
- ACTIVE_VOICE: Convert passive voice ("was [verb]ed") to active voice.
|
||||
- SENSORY_ANCHORING: The first paragraph must establish the setting using at least one non-visual sense (smell, sound, touch).
|
||||
- SUBTEXT: Dialogue must imply meaning rather than stating it outright.
|
||||
|
||||
CURRENT DRAFT (Reference only - feel free to change significantly based on instruction):
|
||||
{target_chap.get('content', '')[:5000]}
|
||||
|
||||
CHARACTERS:
|
||||
{json.dumps(bp.get('characters', []))}
|
||||
|
||||
TASK:
|
||||
Write the full chapter content in Markdown.
|
||||
- Ensure it flows naturally from the previous chapter ending.
|
||||
- Follow the User Instruction strictly, even if it contradicts the current draft.
|
||||
- Maintain the established character voices.
|
||||
RETURN JSON:
|
||||
{{
|
||||
"content": "The full chapter text in Markdown...",
|
||||
"summary": "A concise summary of the chapter's events and ending state (for continuity checks)."
|
||||
}}
|
||||
"""
|
||||
|
||||
try:
|
||||
response = ai.model_writer.generate_content(prompt)
|
||||
utils.log_usage(folder, "writer-flash", response.usage_metadata)
|
||||
return response.text
|
||||
try:
|
||||
data = json.loads(utils.clean_json(response.text))
|
||||
return data.get('content'), data.get('summary')
|
||||
except:
|
||||
# Fallback if model returns raw text instead of JSON
|
||||
return response.text, None
|
||||
except Exception as e:
|
||||
utils.log("WRITER", f"Rewrite failed: {e}")
|
||||
return None
|
||||
return None, None
|
||||
|
||||
def check_and_propagate(bp, manuscript, changed_chap_num, folder):
|
||||
def check_and_propagate(bp, manuscript, changed_chap_num, folder, change_summary=None):
|
||||
utils.log("WRITER", f"Checking ripple effects from Ch {changed_chap_num}...")
|
||||
|
||||
# Find the changed chapter
|
||||
changed_chap = next((c for c in manuscript if c['num'] == changed_chap_num), None)
|
||||
if not changed_chap: return None
|
||||
|
||||
# Summarize the change to save tokens
|
||||
change_summary_prompt = f"Summarize the key events and ending state of this chapter:\n{changed_chap.get('content', '')[:10000]}"
|
||||
try:
|
||||
resp = ai.model_writer.generate_content(change_summary_prompt)
|
||||
current_context = resp.text
|
||||
except:
|
||||
current_context = changed_chap.get('content', '')[-2000:] # Fallback
|
||||
if change_summary:
|
||||
current_context = change_summary
|
||||
else:
|
||||
# Summarize the change to save tokens (Fallback if no summary provided)
|
||||
change_summary_prompt = f"""
|
||||
ROLE: Summarizer
|
||||
TASK: Summarize the key events and ending state of this chapter for continuity tracking.
|
||||
|
||||
TEXT:
|
||||
{changed_chap.get('content', '')[:10000]}
|
||||
|
||||
FOCUS:
|
||||
- Major plot points.
|
||||
- Character status changes (injuries, items acquired, location changes).
|
||||
- New information revealed.
|
||||
|
||||
OUTPUT: Concise text summary.
|
||||
"""
|
||||
try:
|
||||
resp = ai.model_writer.generate_content(change_summary_prompt)
|
||||
current_context = resp.text
|
||||
except:
|
||||
current_context = changed_chap.get('content', '')[-2000:] # Fallback
|
||||
|
||||
original_change_context = current_context
|
||||
# Iterate subsequent chapters
|
||||
@@ -979,20 +1117,18 @@ def check_and_propagate(bp, manuscript, changed_chap_num, folder):
|
||||
chapter_summaries.append(f"Ch {rc['num']}: {excerpt}")
|
||||
|
||||
scan_prompt = f"""
|
||||
We are propagating a change from Chapter {changed_chap_num}.
|
||||
The immediate ripple effect seems to have stopped.
|
||||
ROLE: Continuity Scanner
|
||||
TASK: Identify chapters impacted by a change.
|
||||
|
||||
ORIGINAL CHANGE CONTEXT:
|
||||
CHANGE_CONTEXT:
|
||||
{original_change_context}
|
||||
|
||||
REMAINING CHAPTERS:
|
||||
CHAPTER_SUMMARIES:
|
||||
{json.dumps(chapter_summaries)}
|
||||
|
||||
TASK:
|
||||
Identify any later chapters that mention items, characters, or locations involved in the Change Context.
|
||||
Return a JSON list of Chapter Numbers (integers) that might need updating.
|
||||
Example: [5, 12]
|
||||
If none, return [].
|
||||
CRITERIA: Identify later chapters that mention items, characters, or locations involved in the Change Context.
|
||||
|
||||
OUTPUT_FORMAT (JSON): [Chapter_Number_Int, ...]
|
||||
"""
|
||||
|
||||
try:
|
||||
@@ -1020,40 +1156,50 @@ def check_and_propagate(bp, manuscript, changed_chap_num, folder):
|
||||
utils.log("WRITER", f" -> Checking Ch {target_chap['num']} for continuity...")
|
||||
|
||||
prompt = f"""
|
||||
Chapter {changed_chap_num} was just rewritten.
|
||||
NEW CONTEXT/ENDING of previous section:
|
||||
{current_context}
|
||||
ROLE: Continuity Checker
|
||||
TASK: Determine if chapter needs rewrite based on new context.
|
||||
|
||||
CURRENT TEXT of Ch {target_chap['num']}:
|
||||
{target_chap['content'][:5000]}... (truncated)
|
||||
INPUT_DATA:
|
||||
- CHANGED_CHAPTER: {changed_chap_num}
|
||||
- NEW_CONTEXT: {current_context}
|
||||
- CURRENT_CHAPTER_TEXT: {target_chap['content'][:5000]}...
|
||||
|
||||
TASK:
|
||||
Does Ch {target_chap['num']} need to be rewritten to maintain continuity with the new context?
|
||||
- If YES (e.g. references old events that changed, character states don't match): Rewrite the chapter fully in Markdown.
|
||||
- If NO (it fits fine): Return ONLY the string "NO_CHANGE".
|
||||
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.
|
||||
|
||||
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)"
|
||||
}}
|
||||
"""
|
||||
|
||||
try:
|
||||
response = ai.model_writer.generate_content(prompt)
|
||||
text = response.text.strip()
|
||||
data = json.loads(utils.clean_json(response.text))
|
||||
|
||||
if "NO_CHANGE" in text[:20] and len(text) < 100:
|
||||
if data.get('status') == 'NO_CHANGE':
|
||||
utils.log("WRITER", f" -> Ch {target_chap['num']} is consistent.")
|
||||
# Update context for next iteration using existing text
|
||||
current_context = f"Ch {target_chap['num']} Summary: " + target_chap.get('content', '')[-2000:]
|
||||
consecutive_no_changes += 1
|
||||
else:
|
||||
utils.log("WRITER", f" -> Rewriting Ch {target_chap['num']} to fix continuity.")
|
||||
target_chap['content'] = text
|
||||
changes_made = True
|
||||
# Update context with NEW text
|
||||
current_context = f"Ch {target_chap['num']} Summary: " + text[-2000:]
|
||||
consecutive_no_changes = 0
|
||||
|
||||
# Save immediately to prevent data loss if subsequent checks fail
|
||||
try:
|
||||
with open(os.path.join(folder, "manuscript.json"), 'w') as f: json.dump(manuscript, f, indent=2)
|
||||
except: pass
|
||||
elif data.get('status') == 'REWRITE' and data.get('content'):
|
||||
new_text = data.get('content')
|
||||
if new_text:
|
||||
utils.log("WRITER", f" -> Rewriting Ch {target_chap['num']} to fix continuity.")
|
||||
target_chap['content'] = new_text
|
||||
changes_made = True
|
||||
# Update context with NEW text
|
||||
current_context = f"Ch {target_chap['num']} Summary: " + new_text[-2000:]
|
||||
consecutive_no_changes = 0
|
||||
|
||||
# Save immediately to prevent data loss if subsequent checks fail
|
||||
try:
|
||||
with open(os.path.join(folder, "manuscript.json"), 'w') as f: json.dump(manuscript, f, indent=2)
|
||||
except: pass
|
||||
|
||||
except Exception as e:
|
||||
utils.log("WRITER", f" -> Check failed: {e}")
|
||||
|
||||
Reference in New Issue
Block a user