Gaurav vashistha commited on
Commit
78eeeb4
·
1 Parent(s): e9456a0

fix: resolve server blocking and frontend status caching issues

Browse files
Files changed (2) hide show
  1. server.py +7 -24
  2. stitch_continuity_dashboard/code.html +11 -58
server.py CHANGED
@@ -7,7 +7,6 @@ import os
7
  import shutil
8
  import uuid
9
  import json
10
- # FIXED IMPORT: Importing from root agent.py instead of continuity_agent
11
  from agent import analyze_only, generate_only
12
 
13
  app = FastAPI(title="Continuity", description="AI Video Bridging Service")
@@ -25,16 +24,15 @@ os.makedirs(OUTPUT_DIR, exist_ok=True)
25
  app.mount("/outputs", StaticFiles(directory=OUTPUT_DIR), name="outputs")
26
 
27
  @app.get("/")
28
- async def read_root():
29
- # Serve the dashboard HTML
30
  return FileResponse("stitch_continuity_dashboard/code.html")
31
 
32
  @app.post("/analyze")
33
- async def analyze_endpoint(
34
- background_tasks: BackgroundTasks,
35
  video_a: UploadFile = File(...),
36
  video_c: UploadFile = File(...)
37
  ):
 
38
  try:
39
  request_id = str(uuid.uuid4())
40
  ext_a = os.path.splitext(video_a.filename)[1] or ".mp4"
@@ -48,23 +46,7 @@ async def analyze_endpoint(
48
  with open(path_c, "wb") as buffer:
49
  shutil.copyfileobj(video_c.file, buffer)
50
 
51
- # Call Agent synchronously for analysis (it's relatively fast usually, but could be async too if desired)
52
- # For now keeping it sync as per user previous flows, but could be made async easily.
53
- # However, user request specifically focused on "generate" being async.
54
- # But wait, analyze also calls Gemini which can be slow.
55
- # Refactoring to also include job_id for analyze might be good practice but not explicitly requested for "analyze" in prompt detail,
56
- # BUT the user request says: "Update analyze_videos node: Call utils.update_job_status...".
57
- # So we should probably treat analyze as async too OR just pass the job_id.
58
- # But the frontend flow for analyze currently awaits the response to get the prompt.
59
- # If we make it async, we break the frontend flow unless we refactor that too.
60
- # The prompt says: "Update code.html ... Update the generate button JavaScript."
61
- # It doesn't explicitly say to update the analyze button logic to be async.
62
- # However, update_job_status IS called in analyze_videos.
63
- # So, we can pass a job_id if we want status updates, but if we await it, the status updates are only useful if polled in parallel.
64
- # For now, I will keep analyze synchronous but pass a dummy job_id if we want logging, or just let it block.
65
- # Actually, let's keep it blocking as per original server code, but pass a job_id so at least logs are written.
66
-
67
- # Call Agent with local paths
68
  result = analyze_only(os.path.abspath(path_a), os.path.abspath(path_c), job_id=request_id)
69
 
70
  if result.get("status") == "error":
@@ -80,12 +62,13 @@ async def analyze_endpoint(
80
  raise HTTPException(status_code=500, detail=str(e))
81
 
82
  @app.post("/generate")
83
- async def generate_endpoint(
84
  background_tasks: BackgroundTasks,
85
  prompt: str = Body(...),
86
  video_a_path: str = Body(...),
87
  video_c_path: str = Body(...)
88
  ):
 
89
  try:
90
  if not os.path.exists(video_a_path) or not os.path.exists(video_c_path):
91
  raise HTTPException(status_code=400, detail="Video files not found on server.")
@@ -107,7 +90,7 @@ async def generate_endpoint(
107
  raise HTTPException(status_code=500, detail=str(e))
108
 
109
  @app.get("/status/{job_id}")
110
- async def get_status(job_id: str):
111
  file_path = os.path.join(OUTPUT_DIR, f"{job_id}.json")
112
  if not os.path.exists(file_path):
113
  raise HTTPException(status_code=404, detail="Job not found")
 
7
  import shutil
8
  import uuid
9
  import json
 
10
  from agent import analyze_only, generate_only
11
 
12
  app = FastAPI(title="Continuity", description="AI Video Bridging Service")
 
24
  app.mount("/outputs", StaticFiles(directory=OUTPUT_DIR), name="outputs")
25
 
26
  @app.get("/")
27
+ def read_root():
 
28
  return FileResponse("stitch_continuity_dashboard/code.html")
29
 
30
  @app.post("/analyze")
31
+ def analyze_endpoint(
 
32
  video_a: UploadFile = File(...),
33
  video_c: UploadFile = File(...)
34
  ):
35
+ # Changed to 'def' to run in threadpool (prevent blocking)
36
  try:
37
  request_id = str(uuid.uuid4())
38
  ext_a = os.path.splitext(video_a.filename)[1] or ".mp4"
 
46
  with open(path_c, "wb") as buffer:
47
  shutil.copyfileobj(video_c.file, buffer)
48
 
49
+ # This is blocking, so 'def' is required
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  result = analyze_only(os.path.abspath(path_a), os.path.abspath(path_c), job_id=request_id)
51
 
52
  if result.get("status") == "error":
 
62
  raise HTTPException(status_code=500, detail=str(e))
63
 
64
  @app.post("/generate")
65
+ def generate_endpoint(
66
  background_tasks: BackgroundTasks,
67
  prompt: str = Body(...),
68
  video_a_path: str = Body(...),
69
  video_c_path: str = Body(...)
70
  ):
71
+ # Changed to 'def' for safety
72
  try:
73
  if not os.path.exists(video_a_path) or not os.path.exists(video_c_path):
74
  raise HTTPException(status_code=400, detail="Video files not found on server.")
 
90
  raise HTTPException(status_code=500, detail=str(e))
91
 
92
  @app.get("/status/{job_id}")
93
+ def get_status(job_id: str):
94
  file_path = os.path.join(OUTPUT_DIR, f"{job_id}.json")
95
  if not os.path.exists(file_path):
96
  raise HTTPException(status_code=404, detail="Job not found")
stitch_continuity_dashboard/code.html CHANGED
@@ -389,7 +389,8 @@
389
  // 2. Poll for Status
390
  const pollInterval = setInterval(async () => {
391
  try {
392
- const statusRes = await fetch(`/status/${jobId}`);
 
393
  if (!statusRes.ok) return;
394
 
395
  const statusData = await statusRes.json();
@@ -403,66 +404,18 @@
403
  // Show Video
404
  const bridgeCard = document.getElementById("bridge-card");
405
  if (bridgeCard) {
406
- // If video_url is a relative path like "outputs/...", make sure it starts with /
407
- let videoUrl = statusData.video_url;
408
- // The backend returns full local path generally, but server logic for update_job_status
409
- // kept the local path. We need to serve it via the /outputs/ mount.
410
- // Wait, utils.save_video_bytes returns absolute path.
411
- // server.py: app.mount("/outputs", StaticFiles(directory=OUTPUT_DIR), name="outputs")
412
- // config.py/utils.py save to temp folder?
413
- // Agent returns local path.
414
- // In server.py for generate_only, we don't have the final file move logic anymore locally in server.py?
415
- // Ah, right. Agent calls update_job_status with the video_url (local path).
416
- // The frontend needs a URL it can access.
417
- // We need to move the file to OUTPUT_DIR or make sure Agent saves to OUTPUT_DIR.
418
- // Agent uses download_to_temp / save_video_bytes which use tempfile.
419
- // So the file in 'video_url' is in /tmp/...
420
- // This won't be accessible via /outputs/.
421
- // We need to fix this.
422
-
423
- // This requires a minor fix in server or agent.
424
- // Since I can't edit server.py inside this JS block logic easily to change python logic...
425
- // I should probably have updated the Agent to save to OUTPUT_DIR or Server to handle the move?
426
- // BUT, the server.py I just wrote launches generate_only in background.
427
- // generate_only returns the result, but since it's in background, we ignore return.
428
- // The only communication channel is the status file.
429
- // The status file contains 'video_url' which is the temp path.
430
- // Frontend cannot access temp path.
431
-
432
- // Quick FIX:
433
- // The frontend can't move files.
434
- // The AGENT needs to know where to save, OR Utils.
435
- // OR, we update the logic in agent.py to try to move it? No that's messy.
436
- // Let's look at where static files are served. `/outputs`.
437
- // If the file is in /tmp, we can't serve it.
438
-
439
- // Maybe I should modify `utils.save_video_bytes` to accept a directory?
440
- // Or I can modify `server.py` to launch a wrapper that moves the file?
441
-
442
- // I will implement a wrapper in server.py in the NEXT step or modify server.py now if I catch it?
443
- // I already wrote server.py.
444
- // I will apply a fix to `agent.py` or `utils.py` to ensure it saves to `outputs/` OR
445
- // modify `server.py` to wrap the task.
446
- // Wrapping in server.py is cleanest for separation.
447
- // "generate_only" is imported.
448
-
449
- // Wait, `utils.update_job_status` writes to `outputs/{job_id}.json`.
450
- // If I change `utils.py` to also move the video if provided?
451
- // That seems like a good side effect for a "helper" dealing with job status in this specific app context.
452
- // Or `agent.py` can move it.
453
-
454
- // Let's handle this in the JS for now assuming the URL *might* work if I fix it in backend in a sec.
455
- // I'll assume the backend will provide a valid relative URL like `/outputs/filename.mp4`.
456
-
457
- bridgeCard.innerHTML = `
458
  <video controls autoplay loop class="w-full h-full object-cover rounded-2xl border-2 border-primary shadow-neon">
459
- <source src="${videoUrl}" type="video/mp4">
460
  Your browser does not support the video tag.
461
  </video>
462
- `;
463
-
464
- document.getElementById("analysis-panel").classList.remove("hidden");
465
- document.getElementById("review-panel").classList.add("hidden");
 
 
466
  btn.innerHTML = originalContent;
467
  btn.disabled = false;
468
  btn.classList.remove("opacity-70", "cursor-not-allowed");
 
389
  // 2. Poll for Status
390
  const pollInterval = setInterval(async () => {
391
  try {
392
+ // ADDED TIMESTAMP TO PREVENT CACHING
393
+ const statusRes = await fetch(`/status/${jobId}?t=${Date.now()}`);
394
  if (!statusRes.ok) return;
395
 
396
  const statusData = await statusRes.json();
 
404
  // Show Video
405
  const bridgeCard = document.getElementById("bridge-card");
406
  if (bridgeCard) {
407
+ if (statusData.video_url) {
408
+ bridgeCard.innerHTML = `
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  <video controls autoplay loop class="w-full h-full object-cover rounded-2xl border-2 border-primary shadow-neon">
410
+ <source src="${statusData.video_url}" type="video/mp4">
411
  Your browser does not support the video tag.
412
  </video>
413
+ `;
414
+ document.getElementById("analysis-panel").classList.remove("hidden");
415
+ document.getElementById("review-panel").classList.add("hidden");
416
+ } else {
417
+ alert("Video generated but URL missing.");
418
+ }
419
  btn.innerHTML = originalContent;
420
  btn.disabled = false;
421
  btn.classList.remove("opacity-70", "cursor-not-allowed");