Auto-commit: v2.12 — Fix frontend stuck on Initializing/Waiting for logs
- web/tasks.py: db_log_callback now writes non-OperationalError exceptions to data/app.log for visibility - web/tasks.py: generate_book_task restructured with try...finally to guarantee final status update — run can never be left in 'running' state if worker crashes - templates/project.html: added .catch() to fetchLog() with console.error + polling resume on failure; added manual Refresh button to status bar - templates/run_details.html: improved .catch() in updateLog() with descriptive message + 5s retry; added manual Refresh button to status bar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
66
web/tasks.py
66
web/tasks.py
@@ -29,6 +29,14 @@ def db_log_callback(db_path, run_id, phase, msg):
|
||||
time.sleep(0.1)
|
||||
except Exception as _e:
|
||||
print(f"[db_log_callback ERROR run={run_id}] {type(_e).__name__}: {_e}", flush=True, file=_sys.stdout)
|
||||
try:
|
||||
import os as _os
|
||||
from core import config as _cfg
|
||||
_app_log = _os.path.join(_cfg.DATA_DIR, "app.log")
|
||||
with open(_app_log, 'a', encoding='utf-8') as _f:
|
||||
_f.write(f"[db_log_callback ERROR run={run_id}] {type(_e).__name__}: {_e}\n")
|
||||
except Exception:
|
||||
pass
|
||||
break
|
||||
|
||||
def db_progress_callback(db_path, run_id, percent):
|
||||
@@ -92,6 +100,10 @@ def generate_book_task(run_id, project_path, bible_path, allow_copy=True, feedba
|
||||
|
||||
utils.log("SYSTEM", f"Starting Job #{run_id}")
|
||||
|
||||
status = "failed" # Default to failed; overwritten to "completed" only on clean success
|
||||
total_cost = 0.0
|
||||
final_log_path = initial_log
|
||||
|
||||
try:
|
||||
# 1.1 Handle Feedback / Modification (Re-run logic)
|
||||
if feedback and source_run_id:
|
||||
@@ -190,38 +202,36 @@ def generate_book_task(run_id, project_path, bible_path, allow_copy=True, feedba
|
||||
_task_log(f"ERROR: Job failed — {type(e).__name__}: {e}")
|
||||
_task_log(_tb.format_exc())
|
||||
utils.log("ERROR", f"Job Failed: {e}")
|
||||
status = "failed"
|
||||
# status remains "failed" (set before try block)
|
||||
|
||||
# 3. Calculate Cost & Cleanup
|
||||
run_dir = os.path.join(project_path, "runs", f"run_{run_id}")
|
||||
finally:
|
||||
# 3. Calculate Cost & Cleanup — guaranteed to run even if worker crashes
|
||||
run_dir = os.path.join(project_path, "runs", f"run_{run_id}")
|
||||
|
||||
total_cost = 0.0
|
||||
final_log_path = initial_log
|
||||
if os.path.exists(run_dir):
|
||||
final_log_path = os.path.join(run_dir, "web_console.log")
|
||||
if os.path.exists(initial_log):
|
||||
try:
|
||||
os.rename(initial_log, final_log_path)
|
||||
except OSError:
|
||||
shutil.copy2(initial_log, final_log_path)
|
||||
os.remove(initial_log)
|
||||
|
||||
if os.path.exists(run_dir):
|
||||
final_log_path = os.path.join(run_dir, "web_console.log")
|
||||
if os.path.exists(initial_log):
|
||||
try:
|
||||
os.rename(initial_log, final_log_path)
|
||||
except OSError:
|
||||
shutil.copy2(initial_log, final_log_path)
|
||||
os.remove(initial_log)
|
||||
for item in os.listdir(run_dir):
|
||||
item_path = os.path.join(run_dir, item)
|
||||
if os.path.isdir(item_path) and item.startswith("Book_"):
|
||||
usage_path = os.path.join(item_path, "usage_log.json")
|
||||
if os.path.exists(usage_path):
|
||||
data = utils.load_json(usage_path)
|
||||
total_cost += data.get('totals', {}).get('est_cost_usd', 0.0)
|
||||
|
||||
for item in os.listdir(run_dir):
|
||||
item_path = os.path.join(run_dir, item)
|
||||
if os.path.isdir(item_path) and item.startswith("Book_"):
|
||||
usage_path = os.path.join(item_path, "usage_log.json")
|
||||
if os.path.exists(usage_path):
|
||||
data = utils.load_json(usage_path)
|
||||
total_cost += data.get('totals', {}).get('est_cost_usd', 0.0)
|
||||
|
||||
# 4. Update Database with Final Status
|
||||
try:
|
||||
with sqlite3.connect(db_path, timeout=30, check_same_thread=False) as conn:
|
||||
conn.execute("UPDATE run SET status = ?, cost = ?, end_time = ?, log_file = ?, progress = 100 WHERE id = ?",
|
||||
(status, total_cost, datetime.utcnow(), final_log_path, run_id))
|
||||
except Exception as e:
|
||||
print(f"Failed to update run status in DB: {e}")
|
||||
# 4. Update Database with Final Status — run is never left in 'running' state
|
||||
try:
|
||||
with sqlite3.connect(db_path, timeout=30, check_same_thread=False) as conn:
|
||||
conn.execute("UPDATE run SET status = ?, cost = ?, end_time = ?, log_file = ?, progress = 100 WHERE id = ?",
|
||||
(status, total_cost, datetime.utcnow(), final_log_path, run_id))
|
||||
except Exception as e:
|
||||
print(f"Failed to update run status in DB: {e}")
|
||||
|
||||
_task_log(f"Task finished. status={status} cost=${total_cost:.4f}")
|
||||
return {"run_id": run_id, "status": status, "cost": total_cost, "final_log": final_log_path}
|
||||
|
||||
Reference in New Issue
Block a user