Final changes and update

This commit is contained in:
2026-02-04 20:19:07 -05:00
parent 6e7ff0ae1d
commit 9f8f094564
21 changed files with 1816 additions and 645 deletions

View File

@@ -81,6 +81,13 @@
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Initialize Bootstrap Tooltips globally
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
})
</script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,35 @@
{% extends "base.html" %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="fas fa-search me-2"></i>Consistency Report</h2>
<a href="{{ url_for('view_run', id=run.id) }}" class="btn btn-outline-secondary">Back to Run</a>
</div>
<div class="card shadow-sm mb-4">
<div class="card-header bg-{{ 'success' if report.score >= 8 else 'warning' if report.score >= 5 else 'danger' }} text-white">
<h4 class="mb-0">Consistency Score: {{ report.score }}/10</h4>
</div>
<div class="card-body">
<p class="lead">{{ report.summary }}</p>
<hr>
<h5 class="text-danger"><i class="fas fa-exclamation-circle me-2"></i>Issues Detected</h5>
<ul class="list-group list-group-flush">
{% for issue in report.issues %}
<li class="list-group-item">
<i class="fas fa-bug text-danger me-2"></i> {{ issue }}
</li>
{% else %}
<li class="list-group-item text-success">No major issues found.</li>
{% endfor %}
</ul>
</div>
<div class="card-footer bg-light">
<small class="text-muted">Tip: Use the "Read & Edit" feature to fix these issues manually, or use "Modify & Re-run" to have AI rewrite sections.</small>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -13,10 +13,15 @@
<a href="/system/status" class="btn btn-outline-secondary me-2">
<i class="fas fa-server me-2"></i>System Status
</a>
<button class="btn btn-outline-info me-2" onclick="optimizeModels()">
{% if current_user.is_admin %}
<button class="btn btn-outline-info me-2" onclick="optimizeModels()" data-bs-toggle="tooltip" title="Check API limits and select the best AI models for Logic, Writing, and Art.">
<i class="fas fa-sync me-2"></i>Find New Models
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#newProjectModal">
{% endif %}
<button class="btn btn-outline-primary me-2" data-bs-toggle="modal" data-bs-target="#importProjectModal" data-bs-toggle="tooltip" title="Upload a bible.json file to restore a project.">
<i class="fas fa-file-upload me-2"></i>Import Bible
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#newProjectModal" data-bs-toggle="tooltip" title="Start the Wizard to create a new book series from scratch.">
<i class="fas fa-plus me-2"></i>New Project
</button>
</div>
@@ -35,7 +40,16 @@
</div>
{% else %}
<div class="col-12 text-center py-5">
<h4 class="text-muted">No projects yet. Start writing!</h4>
<h4 class="text-muted mb-3">No projects yet. Start writing!</h4>
<div class="alert alert-info d-inline-block text-start" style="max-width: 600px;">
<h5><i class="fas fa-info-circle me-2"></i>How to use BookApp:</h5>
<ol class="mb-0">
<li>Click <strong>New Project</strong> to launch the AI Wizard.</li>
<li>Describe your idea, and the AI will plan your characters and plot.</li>
<li>Review the "Bible" (the plan), then click <strong>Generate</strong>.</li>
<li>Read the book, edit it, and export to EPUB/Kindle.</li>
</ol>
</div>
</div>
{% endfor %}
</div>
@@ -62,6 +76,29 @@
</form>
</div>
</div>
<!-- Import Project Modal -->
<div class="modal fade" id="importProjectModal" tabindex="-1">
<div class="modal-dialog">
<form class="modal-content" action="/project/import" method="POST" enctype="multipart/form-data">
<div class="modal-header">
<h5 class="modal-title">Import Existing Bible</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="text-muted">Upload a <code>bible.json</code> file to create a new project from it.</p>
<div class="mb-3">
<label class="form-label">Bible JSON File</label>
<input type="file" name="bible_file" class="form-control" accept=".json" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Import Project</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block scripts %}

View File

@@ -5,7 +5,12 @@
<div>
<div class="d-flex align-items-center">
<h1 class="mb-0 me-3">{{ project.name }}</h1>
{% if not locked %}
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#editProjectModal"><i class="fas fa-edit"></i></button>
{% endif %}
<button class="btn btn-sm btn-outline-info ms-2" data-bs-toggle="modal" data-bs-target="#cloneProjectModal" title="Clone/Fork Project" data-bs-toggle="tooltip">
<i class="fas fa-code-branch"></i>
</button>
</div>
<div class="mt-2">
<span class="badge bg-secondary">{{ bible.project_metadata.genre }}</span>
@@ -14,7 +19,7 @@
</div>
<div>
<form action="/project/{{ project.id }}/run" method="POST" class="d-inline">
<button class="btn btn-success shadow px-4 py-2" {% if active_run and active_run.status in ['running', 'queued'] %}disabled{% endif %}>
<button class="btn btn-success shadow px-4 py-2" {% if active_run and active_run.status in ['running', 'queued'] %}disabled{% endif %} data-bs-toggle="tooltip" title="Start the AI writer. It will write the next book in the plan.">
<i class="fas fa-play me-2"></i>{{ 'Generating...' if runs and runs[0].status in ['running', 'queued'] else 'Generate New Book' }}
</button>
</form>
@@ -28,9 +33,21 @@
</div>
</div>
<!-- Workflow Help -->
<div class="alert alert-light border shadow-sm mb-4">
<div class="d-flex align-items-center">
<i class="fas fa-info-circle text-primary fa-2x me-3"></i>
<div>
<strong>Workflow:</strong>
1. Review the <a href="#bible-section">World Bible</a> below. &nbsp;&nbsp;
2. Click <span class="badge bg-success">Generate New Book</span>. &nbsp;&nbsp;
3. When finished, <a href="#latest-run">Download</a> the files or click <span class="badge bg-primary">Read & Edit</span> to refine the text.
</div>
</div>
</div>
<!-- LATEST RUN CARD -->
<div class="card mb-4 border-0 shadow-sm">
<div class="card mb-4 border-0 shadow-sm" id="latest-run">
<div class="card-header bg-white border-bottom-0 pt-4 px-4">
<h4 class="card-title"><i class="fas fa-bolt text-warning me-2"></i>Active Run (ID: {{ active_run.id if active_run else '-' }})</h4>
</div>
@@ -66,7 +83,7 @@
<i class="fas fa-download me-1"></i> Download {{ file.type }}
</a>
{% endfor %}
<button class="btn btn-outline-dark" data-bs-toggle="modal" data-bs-target="#regenerateModal">
<button class="btn btn-outline-dark" data-bs-toggle="modal" data-bs-target="#regenerateModal" data-bs-toggle="tooltip" title="Re-create the cover art or re-compile the EPUB without rewriting the text.">
<i class="fas fa-paint-brush me-1"></i> Regenerate Cover / Files
</button>
</div>
@@ -93,7 +110,7 @@
</div>
<h5 class="card-title mb-3" id="statusMessage">Preparing environment...</h5>
<div class="progress" style="height: 10px;">
<div class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" style="width: 100%"></div>
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" style="width: 0%"></div>
</div>
<small class="text-muted mt-2 d-block" id="statusTime"></small>
</div>
@@ -160,7 +177,7 @@
{% endif %}
{% if r.status not in ['running', 'queued'] %}
<form action="/run/{{ r.id }}/restart" method="POST" class="d-inline ms-1" onsubmit="return confirm('This will delete all files for this run and start over. Are you sure?');">
<input type="hidden" name="mode" value="restart">
<input type="hidden" name="mode" value="restart_clean">
<button class="btn btn-sm btn-outline-danger" title="Re-run (Wipe & Restart)">
<i class="fas fa-redo"></i>
</button>
@@ -220,11 +237,13 @@
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editBookModal{{ book.book_number }}" title="Edit Details">
<i class="fas fa-edit"></i> Edit
</button>
{% if not locked %}
{% if not generated_books.get(book.book_number) %}
<form action="/project/{{ project.id }}/delete_book/{{ book.book_number }}" method="POST" onsubmit="return confirm('Remove this book from the plan?');">
<button class="btn btn-sm btn-outline-danger"><i class="fas fa-trash"></i></button>
</form>
{% endif %}
{% endif %}
</div>
</div>
</div>
@@ -240,16 +259,18 @@
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Title</label>
<input type="text" name="title" class="form-control" value="{{ book.title }}">
<input type="text" name="title" class="form-control" value="{{ book.title }}" {% if locked %}disabled{% endif %}>
</div>
<div class="mb-3">
<label class="form-label">Plot Summary / Instruction</label>
<textarea name="instruction" class="form-control" rows="6">{{ book.manual_instruction }}</textarea>
<textarea name="instruction" class="form-control" rows="6" {% if locked %}disabled{% endif %}>{{ book.manual_instruction }}</textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
{% if not locked %}
<button type="submit" class="btn btn-primary">Save Changes</button>
{% endif %}
</div>
</form>
</div>
@@ -258,6 +279,7 @@
{% endfor %}
<!-- Add Book Card -->
{% if not locked %}
<div class="col-md-4 col-lg-3" style="min-width: 200px;">
<div class="card h-100 border-dashed d-flex align-items-center justify-content-center bg-light" style="border: 2px dashed #ccc; cursor: pointer;" data-bs-toggle="modal" data-bs-target="#addBookModal">
<div class="text-center text-muted py-5">
@@ -266,11 +288,12 @@
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- WORLD BIBLE & LINKED SERIES -->
<div class="row mb-4">
<div class="row mb-4" id="bible-section">
<div class="col-md-12">
<div class="card shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
@@ -279,12 +302,14 @@
<a href="/project/{{ project.id }}/review" class="btn btn-sm btn-outline-info me-1">
<i class="fas fa-list-alt me-1"></i> Full Review
</a>
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#importCharModal">
{% if not locked %}
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#importCharModal" data-bs-toggle="tooltip" title="Import characters from another project to create a shared universe.">
<i class="fas fa-link me-1"></i> Link / Import Series
</button>
<button class="btn btn-sm btn-outline-primary ms-1" data-bs-toggle="modal" data-bs-target="#refineBibleModal">
<button class="btn btn-sm btn-outline-primary ms-1" data-bs-toggle="modal" data-bs-target="#refineBibleModal" data-bs-toggle="tooltip" title="Use AI to bulk-edit characters or plot points based on your instructions.">
<i class="fas fa-magic me-1"></i> Refine
</button>
{% endif %}
</div>
</div>
<div class="card-body">
@@ -337,6 +362,27 @@
</div>
</div>
<!-- Clone Project Modal -->
<div class="modal fade" id="cloneProjectModal" tabindex="-1">
<div class="modal-dialog">
<form class="modal-content" action="/project/{{ project.id }}/clone" method="POST" onsubmit="showLoading(this)">
<div class="modal-header">
<h5 class="modal-title">Clone & Modify Project</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="text-muted small">Create a new project based on this one, with AI modifications.</p>
<div class="mb-3"><label class="form-label">New Project Name</label><input type="text" name="new_name" class="form-control" value="{{ project.name }} (Copy)" required></div>
<div class="mb-3"><label class="form-label">AI Instructions (Optional)</label><textarea name="instruction" class="form-control" rows="3" placeholder="e.g. 'Change the genre to Sci-Fi', 'Make the protagonist a villain', 'Rewrite as a comedy'."></textarea></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-info">Clone Project</button>
</div>
</form>
</div>
</div>
<!-- Add Book Modal -->
<div class="modal fade" id="addBookModal" tabindex="-1">
<div class="modal-dialog">
@@ -482,6 +528,12 @@
const costSpan = document.querySelector(`.cost-${currentRunId}`);
if (costSpan) costSpan.innerText = parseFloat(data.cost).toFixed(4);
// Update Progress Bar Width
const progBar = document.getElementById('progressBar');
if (progBar && data.percent !== undefined) {
progBar.style.width = data.percent + "%";
}
// Update Status Bar
if (data.progress && data.progress.message) {
const phaseEl = document.getElementById('statusPhase');

View File

@@ -16,7 +16,7 @@
<!-- Refinement Bar -->
<div class="input-group mb-4 shadow-sm">
<span class="input-group-text bg-warning text-dark"><i class="fas fa-magic"></i></span>
<span class="input-group-text bg-warning text-dark" data-bs-toggle="tooltip" title="Ask AI to change the suggestions"><i class="fas fa-magic"></i></span>
<input type="text" name="refine_instruction" class="form-control" placeholder="AI Instruction: e.g. 'Make it a trilogy', 'Change genre to Cyberpunk', 'Make the tone darker'">
<button type="submit" formaction="/project/setup/refine" class="btn btn-warning">Refine with AI</button>
</div>
@@ -106,14 +106,14 @@
<!-- Style -->
<h5 class="text-primary mb-3">Style & Tone</h5>
<div class="row mb-3">
<div class="col-md-6 mb-2"><label class="form-label">Tone</label><input type="text" name="tone" class="form-control" value="{{ s.tone }}"></div>
<div class="col-md-6 mb-2"><label class="form-label">Tone</label><input type="text" name="tone" class="form-control" value="{{ s.tone }}" data-bs-toggle="tooltip" title="e.g. Dark, Whimsical, Cynical, Hopeful"></div>
<div class="col-md-6 mb-2"><label class="form-label">POV Style</label><input type="text" name="pov_style" class="form-control" value="{{ s.pov_style }}"></div>
<div class="col-md-6 mb-2"><label class="form-label">Time Period</label><input type="text" name="time_period" class="form-control" value="{{ s.time_period }}"></div>
<div class="col-md-6 mb-2"><label class="form-label">Spice Level</label><input type="text" name="spice" class="form-control" value="{{ s.spice }}"></div>
<div class="col-md-6 mb-2"><label class="form-label">Violence</label><input type="text" name="violence" class="form-control" value="{{ s.violence }}"></div>
<div class="col-md-6 mb-2"><label class="form-label">Spice Level</label><input type="text" name="spice" class="form-control" value="{{ s.spice }}" data-bs-toggle="tooltip" title="e.g. Clean, Fade-to-Black, Explicit"></div>
<div class="col-md-6 mb-2"><label class="form-label">Violence</label><input type="text" name="violence" class="form-control" value="{{ s.violence }}" data-bs-toggle="tooltip" title="e.g. None, Mild, Graphic"></div>
<div class="col-md-6 mb-2"><label class="form-label">Narrative Tense</label><input type="text" name="narrative_tense" class="form-control" value="{{ s.narrative_tense }}"></div>
<div class="col-md-6 mb-2"><label class="form-label">Language Style</label><input type="text" name="language_style" class="form-control" value="{{ s.language_style }}"></div>
<div class="col-md-6 mb-2"><label class="form-label">Dialogue Style</label><input type="text" name="dialogue_style" class="form-control" value="{{ s.dialogue_style }}"></div>
<div class="col-md-6 mb-2"><label class="form-label">Dialogue Style</label><input type="text" name="dialogue_style" class="form-control" value="{{ s.dialogue_style }}" data-bs-toggle="tooltip" title="e.g. Witty, Formal, Slang-heavy"></div>
<div class="col-md-6 mb-2"><label class="form-label">Page Orientation</label>
<select name="page_orientation" class="form-select"><option value="Portrait" {% if s.page_orientation == 'Portrait' %}selected{% endif %}>Portrait</option><option value="Landscape" {% if s.page_orientation == 'Landscape' %}selected{% endif %}>Landscape</option><option value="Square" {% if s.page_orientation == 'Square' %}selected{% endif %}>Square</option></select>
</div>

207
templates/read_book.html Normal file
View File

@@ -0,0 +1,207 @@
{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4 sticky-top bg-white py-3 border-bottom" style="z-index: 100;">
<div>
<h3 class="mb-0"><i class="fas fa-book-reader me-2"></i>{{ book_folder }}</h3>
<small class="text-muted">Run #{{ run.id }}</small>
</div>
<div>
<form action="{{ url_for('sync_book_metadata', run_id=run.id, book_folder=book_folder) }}" method="POST" class="d-inline me-2" onsubmit="return confirm('This will re-scan your manuscript to update the character list and author persona. Continue?');">
<button type="submit" class="btn btn-outline-info" data-bs-toggle="tooltip" title="Scans your manual edits to update the character database and author writing style. Use this after making significant edits.">
<i class="fas fa-sync me-2"></i>Sync Metadata
</button>
</form>
<a href="{{ url_for('view_run', id=run.id) }}" class="btn btn-outline-secondary">Back to Run</a>
</div>
</div>
<div class="row justify-content-center">
<div class="col-lg-8">
{% for ch in manuscript %}
<div class="card shadow-sm mb-5" id="ch-{{ ch.num }}">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h5 class="mb-0">Chapter {{ ch.num }}: {{ ch.title }}</h5>
<div>
<button class="btn btn-sm btn-outline-warning me-1" data-bs-toggle="modal" data-bs-target="#rewriteModal{{ ch.num|string|replace(' ', '') }}" data-bs-toggle="tooltip" title="Ask AI to rewrite this chapter based on new instructions.">
<i class="fas fa-magic"></i> Rewrite
</button>
<button class="btn btn-sm btn-outline-primary" onclick="toggleEdit('{{ ch.num }}')">
<i class="fas fa-edit"></i> Edit
</button>
</div>
</div>
<!-- View Mode -->
<div class="card-body chapter-content" id="view-{{ ch.num }}">
<div class="prose" style="font-family: 'Georgia', serif; font-size: 1.1rem; line-height: 1.6; color: #333;">
{{ ch.html_content|safe }}
</div>
</div>
<!-- Edit Mode -->
<div class="card-body d-none" id="edit-{{ ch.num }}">
<textarea class="form-control font-monospace" id="text-{{ ch.num }}" rows="20">{{ ch.content }}</textarea>
<div class="d-flex justify-content-end mt-2">
<button class="btn btn-secondary me-2" onclick="toggleEdit('{{ ch.num }}')">Cancel</button>
<button class="btn btn-success" onclick="saveChapter('{{ ch.num }}')">Save Changes</button>
</div>
</div>
<!-- Rewrite Modal -->
<div class="modal fade" id="rewriteModal{{ ch.num|string|replace(' ', '') }}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Rewrite Chapter {{ ch.num }} with AI</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Instructions</label>
<textarea name="instruction" class="form-control" rows="4" placeholder="e.g. 'Change the setting to a train station', 'Make the protagonist refuse the offer', 'Fix the pacing in the middle section'." required></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-warning" onclick="startRewrite('{{ ch.num }}')">Rewrite Chapter</button>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<!-- Table of Contents Sidebar -->
<div class="col-lg-3 d-none d-lg-block">
<div class="sticky-top" style="top: 100px;">
<div class="card shadow-sm">
<div class="card-header">Table of Contents</div>
<div class="list-group list-group-flush" style="max-height: 70vh; overflow-y: auto;">
{% for ch in manuscript %}
<a href="#ch-{{ ch.num }}" class="list-group-item list-group-item-action small">
{{ ch.num }}. {{ ch.title }}
</a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<!-- Progress Modal -->
<div class="modal fade" id="progressModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-body text-center p-4">
<div class="spinner-border text-primary mb-3" style="width: 3rem; height: 3rem;"></div>
<h4>Processing AI Request...</h4>
<p class="text-muted mb-0">This may take a few minutes, especially if subsequent chapters need updates. Please wait.</p>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let rewritePollInterval = null;
function toggleEdit(num) {
const viewDiv = document.getElementById(`view-${num}`);
const editDiv = document.getElementById(`edit-${num}`);
if (editDiv.classList.contains('d-none')) {
editDiv.classList.remove('d-none');
viewDiv.classList.add('d-none');
} else {
editDiv.classList.add('d-none');
viewDiv.classList.remove('d-none');
}
}
function startRewrite(num) {
const modal = document.getElementById(`rewriteModal${String(num).replace(' ', '')}`);
const instruction = modal.querySelector('textarea[name="instruction"]').value;
if (!instruction) {
alert("Please provide an instruction for the AI.");
return;
}
const modalInstance = bootstrap.Modal.getInstance(modal);
modalInstance.hide();
const progressModal = new bootstrap.Modal(document.getElementById('progressModal'));
progressModal.show();
const data = {
book_folder: "{{ book_folder }}",
chapter_num: num,
instruction: instruction
};
fetch(`/project/{{ run.id }}/rewrite_chapter`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
})
.then(res => {
if (!res.ok) throw new Error("Failed to start rewrite task.");
return res.json();
})
.then(data => {
if (data.task_id) {
rewritePollInterval = setInterval(() => pollRewriteStatus(data.task_id), 3000);
} else {
throw new Error("Did not receive task ID.");
}
})
.catch(err => {
progressModal.hide();
alert("Error: " + err.message);
});
}
function pollRewriteStatus(taskId) {
fetch(`/task_status/${taskId}`)
.then(res => res.json())
.then(data => {
if (data.status === 'completed') {
clearInterval(rewritePollInterval);
setTimeout(() => { window.location.reload(); }, 500);
}
})
.catch(err => { clearInterval(rewritePollInterval); alert("Error checking status. Please reload manually."); });
}
function saveChapter(num) {
const content = document.getElementById(`text-${num}`).value;
const btn = event.target;
const originalText = btn.innerText;
btn.disabled = true;
btn.innerText = "Saving...";
const formData = new FormData();
formData.append('book_folder', "{{ book_folder }}");
formData.append('chapter_num', num);
formData.append('content', content);
fetch(`/project/{{ run.id }}/save_chapter`, {
method: 'POST',
body: formData
}).then(res => {
if (res.ok) {
alert("Chapter saved! Reloading to render changes...");
window.location.reload();
} else {
alert("Error saving chapter.");
btn.disabled = false;
btn.innerText = originalText;
}
});
}
</script>
{% endblock %}

View File

@@ -10,6 +10,9 @@
<button class="btn btn-outline-primary me-2" type="button" data-bs-toggle="collapse" data-bs-target="#bibleCollapse" aria-expanded="false" aria-controls="bibleCollapse">
<i class="fas fa-scroll me-2"></i>Show Bible
</button>
<button class="btn btn-primary me-2" data-bs-toggle="modal" data-bs-target="#modifyRunModal" data-bs-toggle="tooltip" title="Create a new run based on this one, but with different instructions (e.g. 'Make it darker').">
<i class="fas fa-pen-fancy me-2"></i>Modify & Re-run
</button>
<a href="{{ url_for('view_project', id=run.project_id) }}" class="btn btn-outline-secondary">Back to Project</a>
</div>
</div>
@@ -103,8 +106,8 @@
</div>
<div class="progress" style="height: 20px;">
<div id="status-bar" class="progress-bar {% if run.status == 'running' %}progress-bar-striped progress-bar-animated{% elif run.status == 'failed' %}bg-danger{% else %}bg-success{% endif %}"
role="progressbar" style="width: {% if run.status == 'completed' %}100%{% elif run.status == 'running' %}100%{% else %}5%{% endif %}">
</div>
role="progressbar" style="width: {% if run.status == 'completed' %}100%{% else %}{{ run.progress }}%{% endif %}">
{% if run.status == 'running' %}{{ run.progress }}%{% endif %}</div>
</div>
</div>
</div>
@@ -159,6 +162,15 @@
{% else %}
<span class="text-muted small">No files found.</span>
{% endfor %}
<div class="mt-3">
<a href="{{ url_for('read_book', run_id=run.id, book_folder=book.folder) }}" class="btn btn-primary">
<i class="fas fa-book-reader me-2"></i>Read & Edit
</a>
<a href="{{ url_for('check_consistency', run_id=run.id, book_folder=book.folder) }}" class="btn btn-outline-warning ms-2">
<i class="fas fa-search me-2"></i>Check Consistency
</a>
</div>
</div>
</div>
</div>
@@ -258,6 +270,40 @@
</div>
</div>
<!-- Modify Run Modal -->
<div class="modal fade" id="modifyRunModal" tabindex="-1">
<div class="modal-dialog">
<form class="modal-content" action="/run/{{ run.id }}/restart" method="POST">
<div class="modal-header">
<h5 class="modal-title">Modify & Re-run</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="text-muted small">This will create a <strong>new run</strong> based on this one. You can ask the AI to change the plot, style, or characters.</p>
<div class="mb-3">
<label class="form-label">Instructions / Feedback</label>
<textarea name="feedback" class="form-control" rows="4" placeholder="e.g. 'Make the ending happier', 'Change the setting to Mars', 'Rewrite Chapter 1 to be faster paced'." required></textarea>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="keep_cover" id="keepCoverCheck" checked>
<label class="form-check-label" for="keepCoverCheck">Keep existing cover art (if possible)</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="force_regenerate" id="forceRegenCheck">
<label class="form-check-label" for="forceRegenCheck">Force Regenerate (Don't copy text from previous run)</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Start New Run</button>
</div>
</form>
</div>
</div>
<script>
const runId = {{ run.id }};
const initialStatus = "{{ run.status }}";
@@ -277,13 +323,15 @@
// Update Status Bar
if (data.status === 'running' || data.status === 'queued') {
statusBar.className = "progress-bar progress-bar-striped progress-bar-animated";
statusBar.style.width = "100%";
statusBar.style.width = (data.percent || 5) + "%";
statusBar.innerText = (data.percent || 0) + "%";
} else if (data.status === 'failed') {
statusBar.className = "progress-bar bg-danger";
statusBar.style.width = "100%";
} else {
statusBar.className = "progress-bar bg-success";
statusBar.style.width = "100%";
statusBar.innerText = "";
}
// Update Log (only if changed to avoid scroll jitter)

View File

@@ -37,18 +37,10 @@
<tr>
<td class="fw-bold text-uppercase">{{ role }}</td>
<td>
{% if info is mapping %}
<span class="badge bg-info text-dark">{{ info.model }}</span>
{% else %}
<span class="badge bg-secondary">{{ info }}</span>
{% endif %}
<span class="badge bg-info text-dark">{{ info.model }}</span>
</td>
<td class="small text-muted">
{% if info is mapping %}
{{ info.reason }}
{% else %}
<em>Legacy format. Please refresh models.</em>
{% endif %}
{{ info.reason }}
</td>
</tr>
{% endif %}