diff --git a/backend/app/models/ai.py b/backend/app/models/ai.py index 7b671e2..ca4b943 100644 --- a/backend/app/models/ai.py +++ b/backend/app/models/ai.py @@ -91,7 +91,8 @@ class VideoFromImageRequest(BaseModel): class VideoResponse(BaseModel): - id: str + id: str # This is the job_id from the provider + db_id: str | None = None # This is the UUID from our generated_videos table model: str status: str # "queued" | "processing" | "completed" | "failed" polling_url: str | None = None diff --git a/backend/app/routers/generate.py b/backend/app/routers/generate.py index 490523e..687af4f 100644 --- a/backend/app/routers/generate.py +++ b/backend/app/routers/generate.py @@ -234,18 +234,22 @@ async def generate_video( polling_url = result.get("polling_url") job_status = result.get("status", "pending") now = datetime.now(timezone.utc).replace(tzinfo=None) + db_id = None async with get_write_lock(): conn = get_conn() - conn.execute( + row = conn.execute( """INSERT INTO generated_videos (user_id, job_id, model_id, prompt, polling_url, status, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", + VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING id""", [user_id, job_id, body.model, body.prompt, polling_url, job_status, now, now], - ) + ).fetchone() + if row: + db_id = str(row[0]) - urls = result.get("unsigned_urls") or result.get("video_urls") + urls = result.get("unsigned_urls") or result.get("video_urls") return VideoResponse( id=job_id, + db_id=db_id, model=body.model, status=job_status, polling_url=polling_url, @@ -287,26 +291,30 @@ async def generate_video_from_image( polling_url = result.get("polling_url") job_status = result.get("status", "pending") now = datetime.now(timezone.utc).replace(tzinfo=None) + db_id = None async with get_write_lock(): conn = get_conn() - conn.execute( + row = conn.execute( """INSERT INTO generated_videos (user_id, job_id, model_id, prompt, polling_url, status, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", + VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING id""", [user_id, job_id, body.model, body.prompt, polling_url, job_status, now, now], - ) + ).fetchone() + if row: + db_id = str(row[0]) - urls = result.get("unsigned_urls") or result.get("video_urls") - return VideoResponse( - id=job_id, - model=body.model, - status=job_status, - polling_url=polling_url, - video_urls=urls, - video_url=(urls or [None])[0], - error=result.get("error"), - metadata=result.get("metadata"), - ) + urls = result.get("unsigned_urls") or result.get("video_urls") + return VideoResponse( + id=job_id, + db_id=db_id, + model=body.model, + status=job_status, + polling_url=polling_url, + video_urls=urls, + video_url=(urls or [None])[0], + error=result.get("error"), + metadata=result.get("metadata"), + ) @router.get("/video/status", response_model=VideoResponse) diff --git a/frontend/app/main.py b/frontend/app/main.py index 6599bc3..47a3a9a 100644 --- a/frontend/app/main.py +++ b/frontend/app/main.py @@ -217,11 +217,16 @@ def dashboard(): images = img_resp.json() if img_resp.status_code == 200 else [] gen_resp = _api("GET", "/generate/images", token=token) generated_images = gen_resp.json() if gen_resp.status_code == 200 else [] + vid_resp = _api("GET", "/generate/videos", token=token) - generated_videos = vid_resp.json() if vid_resp.status_code == 200 else [] + videos = vid_resp.json() if vid_resp.status_code == 200 else [] + pending_videos = [v for v in videos if v.get("status") not in ("completed", "failed")] + completed_videos = [v for v in videos if v.get("status") == "completed"] + return render_template("dashboard.html", user=user, images=images, generated_images=generated_images, - generated_videos=generated_videos) + pending_videos=pending_videos, + completed_videos=completed_videos) @app.get("/gallery") @@ -405,7 +410,7 @@ def generate_image(): @app.route("/generate/video", methods=["GET", "POST"]) @login_required def generate_video(): - result = error = None + error = None token = session["access_token"] if request.method == "POST": mode = request.form.get("mode", "text") @@ -413,6 +418,7 @@ def generate_video(): duration = int( duration_raw) if duration_raw.strip().isdigit() else None resolution = request.form.get("resolution", "").strip() or None + if mode == "image": resp = _api("POST", "/generate/video/from-image", token=token, json={ "model": request.form.get("model", "").strip(), @@ -430,12 +436,21 @@ def generate_video(): "duration_seconds": duration, "resolution": resolution, }) + if resp.status_code == 200: result = resp.json() + # On success, redirect to the detail page to monitor progress + db_id = result.get("db_id") + if db_id: + return redirect(url_for("video_detail", video_id=db_id)) + # Fallback for older backend versions + flash("Video job started.", "success") + return redirect(url_for("gallery")) else: error = resp.json().get("detail", "Generation failed.") + models = _load_models(token, "video") - return render_template("generate_video.html", result=result, error=error, models=models) + return render_template("generate_video.html", error=error, models=models) @app.get("/generate/video/status") diff --git a/frontend/app/templates/dashboard.html b/frontend/app/templates/dashboard.html index 537bd4b..b08b500 100644 --- a/frontend/app/templates/dashboard.html +++ b/frontend/app/templates/dashboard.html @@ -6,12 +6,42 @@ endblock %} {% block content %} Start generating -{% if generated_images %} +{% if pending_videos %} +
+

Pending Video Jobs

+
+ {% for vid in pending_videos %} + +
+ {{ vid.status | capitalize }} … +
+

+ {{ vid.model_id }}
{{ vid.prompt[:80] }}{% if + vid.prompt|length > 80 %}…{% endif %} +

+
+ {% endfor %} +
+
+{% endif %} {% if generated_images %}

Generated images

{% for img in generated_images %} - + {% endfor %}
-{% endif %} {% if generated_videos %} +{% endif %} {% if completed_videos %}

Generated videos

- {% for vid in generated_videos %} - + {% endfor %}
@@ -63,7 +96,10 @@ endblock %} {% block content %}

Uploaded reference images

{% for img in images %} -
+ {{ img.filename }} {{ img.filename }} — {{ (img.size_bytes / 1024) | round(1) }} KB

-
+ {% endfor %}