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:
27
web/app.py
27
web/app.py
@@ -116,27 +116,20 @@ with app.app_context():
|
||||
_log("System: Added 'last_heartbeat' column to Run table.")
|
||||
except: pass
|
||||
|
||||
# Reset stuck runs on startup
|
||||
# Reset all non-terminal runs on startup (running, queued, interrupted)
|
||||
# The Huey consumer restarts with the app, so any in-flight tasks are gone.
|
||||
try:
|
||||
stuck_runs = Run.query.filter_by(status='running').all()
|
||||
if stuck_runs:
|
||||
_log(f"System: Found {len(stuck_runs)} stuck run(s) — resetting to 'failed'.")
|
||||
for r in stuck_runs:
|
||||
_NON_TERMINAL = ['running', 'queued', 'interrupted']
|
||||
non_terminal = Run.query.filter(Run.status.in_(_NON_TERMINAL)).all()
|
||||
if non_terminal:
|
||||
_log(f"System: Resetting {len(non_terminal)} non-terminal run(s) to 'failed' on startup:")
|
||||
for r in non_terminal:
|
||||
_log(f" - Run #{r.id} was '{r.status}' — now 'failed'.")
|
||||
r.status = 'failed'
|
||||
r.end_time = datetime.utcnow()
|
||||
db.session.commit()
|
||||
# Also reset stuck 'queued' runs whose task entry was lost from queue.db
|
||||
import sqlite3 as _sqlite3
|
||||
_queue_path = os.path.join(config.DATA_DIR, 'queue.db')
|
||||
if os.path.exists(_queue_path):
|
||||
with _sqlite3.connect(_queue_path, timeout=5) as _qconn:
|
||||
pending_count = _qconn.execute("SELECT COUNT(*) FROM task").fetchone()[0]
|
||||
queued_runs = Run.query.filter_by(status='queued').count()
|
||||
_log(f"System: Queue has {pending_count} pending task(s), DB has {queued_runs} queued run(s).")
|
||||
if queued_runs > 0 and pending_count == 0:
|
||||
_log("System: WARNING — queued runs exist but queue is empty (tasks lost). Resetting to 'failed'.")
|
||||
Run.query.filter_by(status='queued').update({'status': 'failed', 'end_time': datetime.utcnow()})
|
||||
db.session.commit()
|
||||
else:
|
||||
_log("System: No non-terminal runs found. Clean startup.")
|
||||
except Exception as e:
|
||||
_log(f"System: Startup cleanup error: {e}")
|
||||
|
||||
|
||||
@@ -337,6 +337,7 @@ def view_project(id):
|
||||
|
||||
runs = Run.query.filter_by(project_id=id).order_by(Run.id.desc()).all()
|
||||
latest_run = runs[0] if runs else None
|
||||
active_runs = [r for r in runs if r.status in ('running', 'queued')]
|
||||
|
||||
other_projects = Project.query.filter(Project.user_id == current_user.id, Project.id != id).all()
|
||||
|
||||
@@ -385,7 +386,7 @@ def view_project(id):
|
||||
'type': f.split('.')[-1].upper()
|
||||
})
|
||||
|
||||
return render_template('project.html', project=proj, bible=bible_data, runs=runs, active_run=latest_run, artifacts=artifacts, cover_image=cover_image, personas=personas, generated_books=generated_books, other_projects=other_projects, locked=locked, has_draft=has_draft, is_refining=is_refining)
|
||||
return render_template('project.html', project=proj, bible=bible_data, runs=runs, active_run=latest_run, active_runs=active_runs, artifacts=artifacts, cover_image=cover_image, personas=personas, generated_books=generated_books, other_projects=other_projects, locked=locked, has_draft=has_draft, is_refining=is_refining)
|
||||
|
||||
|
||||
@project_bp.route('/project/<int:id>/run', methods=['POST'])
|
||||
|
||||
Reference in New Issue
Block a user