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:
2026-02-20 10:59:08 -05:00
parent 958a6d0ea0
commit edabc4d4fa
5 changed files with 105 additions and 29 deletions

View File

@@ -337,20 +337,69 @@
const statusText = document.getElementById('status-text');
const statusBar = document.getElementById('status-bar');
const costEl = document.getElementById('run-cost');
let lastLog = '';
// Phase → colour mapping (matches utils.log phase labels)
const PHASE_COLORS = {
'WRITER': '#4fc3f7',
'ARCHITECT': '#81c784',
'TIMING': '#78909c',
'SYSTEM': '#fff176',
'TRACKER': '#ce93d8',
'RESUME': '#ffb74d',
'SERIES': '#64b5f6',
'ENRICHER': '#4dd0e1',
'HARVESTER': '#ff8a65',
'EDITOR': '#f48fb1',
};
function escapeHtml(str) {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function colorizeLog(logText) {
if (!logText) return '';
return logText.split('\n').map(line => {
const m = line.match(/^(\[[\d:]+\])\s+(\w+)\s+\|(.*)$/);
if (!m) return '<span style="color:#666">' + escapeHtml(line) + '</span>';
const [, ts, phase, msg] = m;
const color = PHASE_COLORS[phase] || '#aaaaaa';
return '<span style="color:#555">' + escapeHtml(ts) + '</span> '
+ '<span style="color:' + color + ';font-weight:bold">' + phase.padEnd(14) + '</span>'
+ '<span style="color:#ccc">|' + escapeHtml(msg) + '</span>';
}).join('\n');
}
function getCurrentPhase(logText) {
if (!logText) return '';
const lines = logText.split('\n').filter(l => l.trim());
for (let k = lines.length - 1; k >= 0; k--) {
const m = lines[k].match(/\]\s+(\w+)\s+\|/);
if (m) return m[1];
}
return '';
}
function updateLog() {
fetch(`/run/${runId}/status`)
.then(response => response.json())
.then(data => {
// Update Status Text
statusText.innerText = "Status: " + data.status.charAt(0).toUpperCase() + data.status.slice(1);
// Update Status Text + current phase
const statusLabel = data.status.charAt(0).toUpperCase() + data.status.slice(1);
if (data.status === 'running') {
const phase = getCurrentPhase(data.log);
statusText.innerText = 'Status: Running' + (phase ? ' — ' + phase : '');
} else {
statusText.innerText = 'Status: ' + statusLabel;
}
costEl.innerText = '$' + parseFloat(data.cost).toFixed(4);
// Update Status Bar
if (data.status === 'running' || data.status === 'queued') {
statusBar.className = "progress-bar progress-bar-striped progress-bar-animated";
statusBar.style.width = (data.percent || 5) + "%";
let label = (data.percent || 0) + "%";
if (data.status === 'running' && data.percent > 2 && data.start_time) {
const elapsed = (Date.now() / 1000) - data.start_time;
@@ -371,15 +420,16 @@
statusBar.innerText = "";
}
// Update Log (only if changed to avoid scroll jitter)
if (consoleEl.innerText !== data.log) {
// Update Log with phase colorization (only if changed to avoid scroll jitter)
if (lastLog !== data.log) {
lastLog = data.log;
const isScrolledToBottom = consoleEl.scrollHeight - consoleEl.clientHeight <= consoleEl.scrollTop + 50;
consoleEl.innerText = data.log;
consoleEl.innerHTML = colorizeLog(data.log);
if (isScrolledToBottom) {
consoleEl.scrollTop = consoleEl.scrollHeight;
}
}
// Poll if running
if (data.status === 'running' || data.status === 'queued') {
setTimeout(updateLog, 2000);