v1.4.0: Organic writing, speed, and log improvements
Organic book quality: - write_chapter: strip key_events spoilers from character context so the writer doesn't know planned future events when writing early chapters - write_chapter: added next_chapter_hint — seeds anticipation for the next scene in the final paragraphs of each chapter for natural story flow - write_chapter: added DIALOGUE VOICE instruction referencing CHARACTER TRACKING speech styles so every character sounds distinctly different - Lowered SCORE_AUTO_ACCEPT 9→8 to stop over-refining already-professional drafts Speed improvements: - check_pacing: reduced from every chapter to every other chapter (~50% fewer calls) - refine_persona: reduced from every 3 to every 5 chapters (~40% fewer calls) - Resume summary rebuild: uses first + last-4 chapters instead of all chapters to avoid massive prompts when resuming mid-book - Summary context sent to writer capped at 8000 chars (most-recent events) - update_tracking text cap lowered 500000→20000 (covers any realistic chapter) Logging and progress bars: - Progress bar updates at chapter START, not just after completion - Chapter banner logged before each write so the log shows which chapter is active - Word count logged after first draft (e.g. "Draft: 2,341 words (target: ~2200)") - Word count added to chapter completion TIMING line - Pacing check now logs "Pacing OK" with reason when no intervention needed - utils: added log_banner() helper for phase separator lines UI: - run_details.html: log lines are now phase-coloured (WRITER=cyan, ARCHITECT=green, TIMING=gray, SYSTEM=yellow, TRACKER=purple, RESUME=orange, etc.) - Status bar shows current active phase (e.g. "Status: Running — WRITER") Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
37
main.py
37
main.py
@@ -97,10 +97,11 @@ def process_book(bp, folder, context="", resume=False, interactive=False):
|
||||
|
||||
summary = "The story begins."
|
||||
if ms:
|
||||
# Generate summary from ALL written chapters to maintain continuity
|
||||
utils.log("RESUME", "Rebuilding 'Story So Far' from existing manuscript...")
|
||||
try:
|
||||
combined_text = "\n".join([f"Chapter {c['num']}: {c['content']}" for c in ms])
|
||||
# Efficient rebuild: first chapter (setup) + last 4 (recent events) avoids huge prompts
|
||||
utils.log("RESUME", f"Rebuilding story context from {len(ms)} existing chapters...")
|
||||
try:
|
||||
selected = ms[:1] + ms[-4:] if len(ms) > 5 else ms
|
||||
combined_text = "\n".join([f"Chapter {c['num']}: {c['content'][:3000]}" for c in selected])
|
||||
resp_sum = ai.model_writer.generate_content(f"""
|
||||
ROLE: Series Historian
|
||||
TASK: Create a cumulative 'Story So Far' summary.
|
||||
@@ -133,13 +134,20 @@ def process_book(bp, folder, context="", resume=False, interactive=False):
|
||||
if any(str(c.get('num')) == str(ch['chapter_number']) for c in ms):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
|
||||
# Progress Banner — update bar and log chapter header before writing begins
|
||||
utils.update_progress(15 + int((i / len(chapters)) * 75))
|
||||
utils.log_banner("WRITER", f"Chapter {ch['chapter_number']}/{len(chapters)}: {ch['title']}")
|
||||
|
||||
# Pass previous chapter content for continuity if available
|
||||
prev_content = ms[-1]['content'] if ms else None
|
||||
|
||||
while True:
|
||||
try:
|
||||
txt = story.write_chapter(ch, bp, folder, summary, tracking, prev_content)
|
||||
# Cap summary to most-recent 8000 chars; pass next chapter title as hook hint
|
||||
summary_ctx = summary[-8000:] if len(summary) > 8000 else summary
|
||||
next_hint = chapters[i+1]['title'] if i + 1 < len(chapters) else ""
|
||||
txt = story.write_chapter(ch, bp, folder, summary_ctx, tracking, prev_content, next_chapter_hint=next_hint)
|
||||
except Exception as e:
|
||||
utils.log("SYSTEM", f"Chapter generation failed: {e}")
|
||||
if interactive:
|
||||
@@ -156,8 +164,8 @@ def process_book(bp, folder, context="", resume=False, interactive=False):
|
||||
else:
|
||||
break
|
||||
|
||||
# Refine Persona to match the actual output (Consistency Loop)
|
||||
if (i == 0 or i % 3 == 0) and txt:
|
||||
# Refine Persona to match the actual output (every 5 chapters to save API calls)
|
||||
if (i == 0 or i % 5 == 0) and txt:
|
||||
bp['book_metadata']['author_details'] = story.refine_persona(bp, txt, folder)
|
||||
with open(bp_path, "w") as f: json.dump(bp, f, indent=2)
|
||||
|
||||
@@ -207,9 +215,9 @@ def process_book(bp, folder, context="", resume=False, interactive=False):
|
||||
with open(chars_track_path, "w") as f: json.dump(tracking['characters'], f, indent=2)
|
||||
with open(warn_track_path, "w") as f: json.dump(tracking.get('content_warnings', []), f, indent=2)
|
||||
|
||||
# --- DYNAMIC PACING CHECK ---
|
||||
# --- DYNAMIC PACING CHECK (every other chapter to halve API overhead) ---
|
||||
remaining = chapters[i+1:]
|
||||
if remaining:
|
||||
if remaining and len(remaining) >= 2 and i % 2 == 1:
|
||||
pacing = story.check_pacing(bp, summary, txt, ch, remaining, folder)
|
||||
if pacing and pacing.get('status') == 'add_bridge':
|
||||
new_data = pacing.get('new_chapter', {})
|
||||
@@ -237,10 +245,12 @@ def process_book(bp, folder, context="", resume=False, interactive=False):
|
||||
removed = chapters.pop(i+1)
|
||||
# Renumber subsequent chapters
|
||||
for k in range(i+1, len(chapters)): chapters[k]['chapter_number'] = k + 1
|
||||
|
||||
|
||||
with open(chapters_path, "w") as f: json.dump(chapters, f, indent=2)
|
||||
utils.log("ARCHITECT", f" -> ⚠️ Pacing Intervention: Removed redundant chapter '{removed['title']}'.")
|
||||
|
||||
elif pacing:
|
||||
utils.log("ARCHITECT", f" -> Pacing OK. {pacing.get('reason', '')[:100]}")
|
||||
|
||||
# Increment loop
|
||||
i += 1
|
||||
|
||||
@@ -254,7 +264,8 @@ def process_book(bp, folder, context="", resume=False, interactive=False):
|
||||
prog = 15 + int((i / len(chapters)) * 75)
|
||||
utils.update_progress(prog)
|
||||
|
||||
utils.log("TIMING", f" -> Chapter {ch['chapter_number']} finished in {duration:.1f}s | Avg: {avg_time:.1f}s | ETA: {int(eta//60)}m {int(eta%60)}s")
|
||||
word_count = len(txt.split()) if txt else 0
|
||||
utils.log("TIMING", f" -> Ch {ch['chapter_number']} done in {duration:.1f}s | {word_count:,} words | Avg: {avg_time:.1f}s | ETA: {int(eta//60)}m {int(eta%60)}s")
|
||||
|
||||
utils.log("TIMING", f"Writing Phase: {time.time() - t_step:.1f}s")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user