new editor features

This commit is contained in:
2026-02-04 08:42:42 -05:00
parent c2e7ed01b4
commit 6e7ff0ae1d
4 changed files with 101 additions and 8 deletions

35
main.py
View File

@@ -106,7 +106,8 @@ def process_book(bp, folder, context="", resume=False):
session_chapters = 0
session_time = 0
for i in range(len(ms), len(chapters)):
i = len(ms)
while i < len(chapters):
ch_start = time.time()
ch = chapters[i]
@@ -165,6 +166,38 @@ def process_book(bp, folder, context="", resume=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 ---
remaining = chapters[i+1:]
if remaining:
pacing = story.check_pacing(bp, summary, txt, ch, remaining, folder)
if pacing and pacing.get('status') == 'add_bridge':
new_data = pacing.get('new_chapter', {})
new_ch = {
"chapter_number": ch['chapter_number'] + 1,
"title": new_data.get('title', 'Bridge Chapter'),
"pov_character": new_data.get('pov_character', ch.get('pov_character')),
"pacing": "Slow",
"estimated_words": 1500,
"beats": new_data.get('beats', [])
}
chapters.insert(i+1, new_ch)
# 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: Added bridge chapter '{new_ch['title']}' to fix rushing.")
elif pacing and pacing.get('status') == 'cut_next':
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']}'.")
# Increment loop
i += 1
duration = time.time() - ch_start
session_chapters += 1
session_time += duration

View File

@@ -365,19 +365,23 @@ def evaluate_chapter_quality(text, chapter_title, model, folder):
- 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).
CRITERIA:
1. VOICE & TONE: Is the narrative voice distinct, or generic? Does it match the genre?
2. SHOW, DON'T TELL: Are emotions demonstrated through action/viscera, or summarized?
3. PACING: Does the scene drag? Is there conflict in every beat?
4. CHARACTER AGENCY: Do characters make choices, or do things just happen to them?
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?
4. SENSORY IMMERSION: Does the text engage all five senses (smell, sound, touch, etc.)?
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?
Rate on a scale of 1-10. (Be harsh. 10 is Pulitzer level. 6 is average. Anything below 8 needs work).
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. "Cut the first 3 paragraphs", "Make the dialogue in the middle argument more aggressive", "Describe the smell of the room").'
'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").'
}}
"""
try:
@@ -393,6 +397,53 @@ def evaluate_chapter_quality(text, chapter_title, model, folder):
except Exception as e:
return 0, f"Evaluation error: {str(e)}"
def check_pacing(bp, summary, last_chapter_text, last_chapter_data, remaining_chapters, folder):
utils.log("ARCHITECT", "Checking pacing and structure health...")
if not remaining_chapters:
return None
meta = bp.get('book_metadata', {})
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']}".
STORY SO FAR (Summary):
{summary[-3000:]}
JUST WRITTEN (Last 2000 chars):
{last_chapter_text[-2000:]}
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:
{{
"status": "ok" or "add_bridge" or "cut_next",
"reason": "Explanation...",
"new_chapter": {{ "title": "...", "beats": ["..."], "pov_character": "..." }} (Required if add_bridge)
}}
"""
try:
response = ai.model_logic.generate_content(prompt)
utils.log_usage(folder, "logic-pro", response.usage_metadata)
return json.loads(utils.clean_json(response.text))
except Exception as e:
utils.log("ARCHITECT", f"Pacing check failed: {e}")
return None
def create_initial_persona(bp, folder):
utils.log("SYSTEM", "Generating initial Author Persona based on genre/tone...")
meta = bp.get('book_metadata', {})
@@ -618,6 +669,7 @@ def write_chapter(chap, bp, folder, prev_sum, tracking=None, prev_content=None):
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.
STORY SO FAR:
{prev_sum}

View File

@@ -459,6 +459,7 @@
let activeInterval = null;
// Only auto-poll if we have a latest run
let currentRunId = {{ active_run.id if active_run else 'null' }};
const initialRunStatus = "{{ active_run.status if active_run else '' }}";
function fetchLog() {
if (!currentRunId) return;
@@ -509,8 +510,9 @@
} else {
if (activeInterval) clearInterval(activeInterval);
activeInterval = null;
// Reload page on completion to show download buttons
if (data.status === 'completed' && !document.querySelector('.alert-success')) {
// Reload if we were polling (watched it finish) OR if page loaded as running but is now done
if (initialRunStatus === 'running' || initialRunStatus === 'queued') {
window.location.reload();
}
}

View File

@@ -260,6 +260,7 @@
<script>
const runId = {{ run.id }};
const initialStatus = "{{ run.status }}";
const consoleEl = document.getElementById('console-log');
const statusText = document.getElementById('status-text');
const statusBar = document.getElementById('status-bar');
@@ -297,6 +298,11 @@
// Poll if running
if (data.status === 'running' || data.status === 'queued') {
setTimeout(updateLog, 2000);
} else {
// If the run was active when we loaded the page, reload now that it's finished to show artifacts
if (initialStatus === 'running' || initialStatus === 'queued') {
window.location.reload();
}
}
})
.catch(err => console.error(err));