Compare commits
2 Commits
28a1308fbc
...
f71a04c03c
| Author | SHA1 | Date | |
|---|---|---|---|
| f71a04c03c | |||
| fd4ce634d4 |
@@ -112,6 +112,9 @@ Open `http://localhost:5000`.
|
|||||||
- **Series Continuity:** When generating Book 2+, carries forward character visual tracking, established relationships, plot threads, and a cumulative "Story So Far" summary.
|
- **Series Continuity:** When generating Book 2+, carries forward character visual tracking, established relationships, plot threads, and a cumulative "Story So Far" summary.
|
||||||
- **Persona Refinement Loop:** Every 5 chapters, analyzes actual written text to refine the author persona model, maintaining stylistic consistency throughout the book.
|
- **Persona Refinement Loop:** Every 5 chapters, analyzes actual written text to refine the author persona model, maintaining stylistic consistency throughout the book.
|
||||||
- **Consistency Checker (`editor.py`):** Scores chapters on 8 rubrics (engagement, voice, sensory detail, scene execution, etc.) and flags AI-isms ("tapestry", "palpable tension") and weak filter verbs ("felt", "realized").
|
- **Consistency Checker (`editor.py`):** Scores chapters on 8 rubrics (engagement, voice, sensory detail, scene execution, etc.) and flags AI-isms ("tapestry", "palpable tension") and weak filter verbs ("felt", "realized").
|
||||||
|
- **Dynamic Character Injection (`writer.py`):** Only injects characters explicitly named in the chapter's `scene_beats` plus the POV character into the writer prompt. Eliminates token waste from unused characters and reduces hallucinated appearances.
|
||||||
|
- **Smart Context Tail (`writer.py`):** Extracts the final ~1,000 tokens of the previous chapter (the actual ending) rather than blindly truncating from the front. Ensures the hand-off point — where characters are standing and what was last said — is always preserved.
|
||||||
|
- **Stateful Scene Tracking (`bible_tracker.py`):** After each chapter, the tracker records each character's `current_location`, `time_of_day`, and `held_items` in addition to appearance and events. This scene state is injected into subsequent chapter prompts so the writer knows exactly where characters are, what time it is, and what they're carrying.
|
||||||
|
|
||||||
### Marketing Assets (`marketing/`)
|
### Marketing Assets (`marketing/`)
|
||||||
- **Cover Art:** Generates a visual prompt from book themes and tracking data, then calls Imagen (Gemini or Vertex AI) to produce the cover. Evaluates image quality with multimodal AI critique before accepting.
|
- **Cover Art:** Generates a visual prompt from book themes and tracking data, then calls Imagen (Gemini or Vertex AI) to produce the cover. Evaluates image quality with multimodal AI critique before accepting.
|
||||||
@@ -316,7 +319,10 @@ data/
|
|||||||
"descriptors": ["Blue eyes", "Tall"],
|
"descriptors": ["Blue eyes", "Tall"],
|
||||||
"likes_dislikes": ["Loves coffee"],
|
"likes_dislikes": ["Loves coffee"],
|
||||||
"last_worn": "Red dress (Ch 4)",
|
"last_worn": "Red dress (Ch 4)",
|
||||||
"major_events": ["Injured leg in Ch 2"]
|
"major_events": ["Injured leg in Ch 2"],
|
||||||
|
"current_location": "The King's Throne Room",
|
||||||
|
"time_of_day": "Late afternoon",
|
||||||
|
"held_items": ["Iron sword", "Stolen ledger"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -65,4 +65,4 @@ LENGTH_DEFINITIONS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# --- SYSTEM ---
|
# --- SYSTEM ---
|
||||||
VERSION = "1.4.0"
|
VERSION = "1.5.0"
|
||||||
|
|||||||
@@ -70,12 +70,15 @@ def update_tracking(folder, chapter_num, chapter_text, current_tracking):
|
|||||||
|
|
||||||
OPERATIONS:
|
OPERATIONS:
|
||||||
1. EVENTS: Append 1-3 key plot points to 'events'.
|
1. EVENTS: Append 1-3 key plot points to 'events'.
|
||||||
2. CHARACTERS: Update 'descriptors', 'likes_dislikes', 'speech_style', 'last_worn', 'major_events'.
|
2. CHARACTERS: Update 'descriptors', 'likes_dislikes', 'speech_style', 'last_worn', 'major_events', 'current_location', 'time_of_day', 'held_items'.
|
||||||
- "descriptors": List of strings. Add PERMANENT physical traits (height, hair, eyes), specific items (jewelry, weapons). Avoid duplicates.
|
- "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").
|
- "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").
|
- "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".
|
- "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").
|
- "major_events": List of strings. Log significant life-altering events occurring in THIS chapter (e.g. "Lost an arm", "Married", "Betrayed by X").
|
||||||
|
- "current_location": String. The character's physical location at the END of this chapter (e.g., "The King's Throne Room", "Aboard the Nighthawk ship"). Update whenever the character moves.
|
||||||
|
- "time_of_day": String. The approximate time of day at the END of this chapter (e.g., "Dawn", "Late afternoon", "Midnight"). Reset to "Unknown" if unclear.
|
||||||
|
- "held_items": List of strings. Items the character is actively carrying or holding at chapter end (e.g., "Iron sword", "Stolen ledger"). Remove items they have dropped or given away.
|
||||||
3. WARNINGS: Append new 'content_warnings'.
|
3. WARNINGS: Append new 'content_warnings'.
|
||||||
|
|
||||||
OUTPUT_FORMAT (JSON): Return the updated tracking object structure.
|
OUTPUT_FORMAT (JSON): Return the updated tracking object structure.
|
||||||
|
|||||||
@@ -46,10 +46,30 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None,
|
|||||||
if samples:
|
if samples:
|
||||||
persona_info += "\nWRITING STYLE SAMPLES:\n" + "\n".join(samples)
|
persona_info += "\nWRITING STYLE SAMPLES:\n" + "\n".join(samples)
|
||||||
|
|
||||||
|
# Only inject characters named in the chapter beats + the POV character
|
||||||
|
beats_text = " ".join(str(b) for b in chap.get('beats', []))
|
||||||
|
pov_lower = pov_char.lower() if pov_char else ""
|
||||||
|
chars_for_writer = [
|
||||||
|
{"name": c.get("name"), "role": c.get("role"), "description": c.get("description", "")}
|
||||||
|
for c in bp.get('characters', [])
|
||||||
|
if c.get("name") and (
|
||||||
|
c["name"].lower() in beats_text.lower() or
|
||||||
|
(pov_lower and c["name"].lower() == pov_lower)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if not chars_for_writer:
|
||||||
|
chars_for_writer = [
|
||||||
|
{"name": c.get("name"), "role": c.get("role"), "description": c.get("description", "")}
|
||||||
|
for c in bp.get('characters', [])
|
||||||
|
]
|
||||||
|
relevant_names = {c["name"] for c in chars_for_writer}
|
||||||
|
|
||||||
char_visuals = ""
|
char_visuals = ""
|
||||||
if tracking and 'characters' in tracking:
|
if tracking and 'characters' in tracking:
|
||||||
char_visuals = "\nCHARACTER TRACKING (Visuals & Preferences):\n"
|
char_visuals = "\nCHARACTER TRACKING (Visuals, State & Scene Position):\n"
|
||||||
for name, data in tracking['characters'].items():
|
for name, data in tracking['characters'].items():
|
||||||
|
if name not in relevant_names:
|
||||||
|
continue
|
||||||
desc = ", ".join(data.get('descriptors', []))
|
desc = ", ".join(data.get('descriptors', []))
|
||||||
likes = ", ".join(data.get('likes_dislikes', []))
|
likes = ", ".join(data.get('likes_dislikes', []))
|
||||||
speech = data.get('speech_style', 'Unknown')
|
speech = data.get('speech_style', 'Unknown')
|
||||||
@@ -62,6 +82,13 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None,
|
|||||||
if worn and worn != 'Unknown':
|
if worn and worn != 'Unknown':
|
||||||
char_visuals += f" * Last Worn: {worn} (NOTE: Only relevant if scene is continuous from previous chapter)\n"
|
char_visuals += f" * Last Worn: {worn} (NOTE: Only relevant if scene is continuous from previous chapter)\n"
|
||||||
|
|
||||||
|
location = data.get('current_location', '')
|
||||||
|
items = data.get('held_items', [])
|
||||||
|
if location:
|
||||||
|
char_visuals += f" * Current Location: {location}\n"
|
||||||
|
if items:
|
||||||
|
char_visuals += f" * Held Items: {', '.join(items)}\n"
|
||||||
|
|
||||||
style_block = "\n".join([f"- {k.replace('_', ' ').title()}: {v}" for k, v in style.items() if isinstance(v, (str, int, float))])
|
style_block = "\n".join([f"- {k.replace('_', ' ').title()}: {v}" for k, v in style.items() if isinstance(v, (str, int, float))])
|
||||||
if 'tropes' in style and isinstance(style['tropes'], list):
|
if 'tropes' in style and isinstance(style['tropes'], list):
|
||||||
style_block += f"\n- Tropes: {', '.join(style['tropes'])}"
|
style_block += f"\n- Tropes: {', '.join(style['tropes'])}"
|
||||||
@@ -71,13 +98,8 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None,
|
|||||||
|
|
||||||
prev_context_block = ""
|
prev_context_block = ""
|
||||||
if prev_content:
|
if prev_content:
|
||||||
trunc_content = utils.truncate_to_tokens(prev_content, 2000)
|
trunc_content = utils.truncate_to_tokens(prev_content, 1000)
|
||||||
prev_context_block = f"\nPREVIOUS CHAPTER TEXT (For Tone & Continuity):\n{trunc_content}\n"
|
prev_context_block = f"\nPREVIOUS CHAPTER TEXT (Last ~1000 Tokens — For Immediate Continuity):\n{trunc_content}\n"
|
||||||
|
|
||||||
chars_for_writer = [
|
|
||||||
{"name": c.get("name"), "role": c.get("role"), "description": c.get("description", "")}
|
|
||||||
for c in bp.get('characters', [])
|
|
||||||
]
|
|
||||||
|
|
||||||
total_chapters = ls.get('chapters', '?')
|
total_chapters = ls.get('chapters', '?')
|
||||||
prompt = f"""
|
prompt = f"""
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ with app.app_context():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import threading
|
import threading
|
||||||
from huey.contrib.mini import MiniHuey
|
|
||||||
|
|
||||||
# Start Huey consumer in background thread
|
# Start Huey consumer in background thread
|
||||||
def run_huey():
|
def run_huey():
|
||||||
|
|||||||
Reference in New Issue
Block a user