Auto-commit: v2.15 — Startup state cleanup + concurrent jobs UI

- Remove ai_blueprint.md from git tracking (already gitignored)
- web/app.py: Unify startup reset — all non-terminal states (running,
  queued, interrupted) are reset to 'failed' with per-job logging
- web/routes/project.py: Add active_runs list to view_project() context
- templates/project.html: Add Active Jobs card showing all running/queued
  jobs with status badge, start time, progress bar, and View Details link;
  Generate button and Stop buttons now driven by active_runs list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 19:12:33 -05:00
parent 81340a18ea
commit ba56bc1ec1
4 changed files with 49 additions and 81 deletions

View File

@@ -19,17 +19,17 @@
</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 %} 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 class="btn btn-success shadow px-4 py-2" {% if active_runs %}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 active_runs else 'Generate New Book' }}
</button>
</form>
{% if runs and runs[0].status in ['running', 'queued'] %}
<form action="/run/{{ runs[0].id }}/stop" method="POST" class="d-inline ms-2">
<button class="btn btn-danger shadow px-3 py-2" title="Stop/Cancel Run" onclick="return confirm('Are you sure you want to stop this job? If the server restarted, this will simply unlock the UI.')">
<i class="fas fa-stop"></i>
{% for ar in active_runs %}
<form action="/run/{{ ar.id }}/stop" method="POST" class="d-inline ms-2">
<button class="btn btn-danger shadow px-3 py-2" title="Stop Run #{{ ar.id }}" onclick="return confirm('Stop Run #{{ ar.id }}? If the server restarted, this will simply unlock the UI.')">
<i class="fas fa-stop me-1"></i>#{{ ar.id }}
</button>
</form>
{% endif %}
{% endfor %}
</div>
</div>
@@ -46,6 +46,36 @@
</div>
</div>
<!-- ACTIVE JOBS CARD — shows all currently running/queued jobs -->
{% if active_runs %}
<div class="card mb-4 border-0 shadow-sm border-start border-warning border-4">
<div class="card-header bg-warning bg-opacity-10 border-0 pt-3 px-4 pb-2">
<h5 class="mb-0"><i class="fas fa-spinner fa-spin text-warning me-2"></i>Active Jobs ({{ active_runs|length }})</h5>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
{% for ar in active_runs %}
<div class="list-group-item d-flex align-items-center px-4 py-3">
<span class="badge bg-{{ 'warning text-dark' if ar.status == 'queued' else 'primary' }} me-3">{{ ar.status|upper }}</span>
<div class="flex-grow-1">
<strong>Run #{{ ar.id }}</strong>
<span class="text-muted ms-2 small">Started: {{ ar.start_time.strftime('%Y-%m-%d %H:%M') if ar.start_time else 'Pending' }}</span>
{% if ar.progress %}
<div class="progress mt-1" style="height: 6px; max-width: 200px;">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ ar.progress }}%"></div>
</div>
{% endif %}
</div>
<a href="{{ url_for('run.view_run', id=ar.id) }}" class="btn btn-sm btn-outline-primary me-2">
<i class="fas fa-eye me-1"></i>View Details
</a>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- LATEST RUN CARD -->
<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">