feat: add video job cancellation functionality and error tracking in generated videos
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -65,29 +65,42 @@ Self-service profile management and admin user CRUD.
|
||||
|
||||
Operational endpoints for application management.
|
||||
|
||||
| Method | Path | Auth required | Admin only | Description |
|
||||
| ------ | --------------------- | ------------- | ---------- | ------------------------------------- |
|
||||
| GET | `/admin/stats` | ✓ | ✓ | User counts by role, token activity |
|
||||
| GET | `/admin/health/db` | ✓ | ✓ | DuckDB connectivity check |
|
||||
| POST | `/admin/tokens/purge` | ✓ | ✓ | Remove expired/revoked refresh tokens |
|
||||
| Method | Path | Auth required | Admin only | Description |
|
||||
| ------ | --------------------------- | ------------- | ---------- | ------------------------------------------ |
|
||||
| GET | `/admin/stats` | ✓ | ✓ | User counts by role, token activity |
|
||||
| GET | `/admin/health/db` | ✓ | ✓ | DuckDB connectivity check |
|
||||
| POST | `/admin/tokens/purge` | ✓ | ✓ | Remove expired/revoked refresh tokens |
|
||||
| GET | `/admin/videos` | ✓ | ✓ | List all video jobs with user emails |
|
||||
| POST | `/admin/videos/{id}/cancel` | ✓ | ✓ | Cancel a queued/processing video job |
|
||||
| POST | `/admin/videos/{id}/retry` | ✓ | ✓ | Retry a failed/cancelled video job |
|
||||
| DELETE | `/admin/videos/{id}` | ✓ | ✓ | Permanently delete a video job |
|
||||
| POST | `/admin/videos/purge` | ✓ | ✓ | Delete old completed/failed/cancelled jobs |
|
||||
| POST | `/admin/videos/timed-out` | ✓ | ✓ | Mark stale processing jobs as failed |
|
||||
| GET | `/admin/models` | ✓ | ✓ | List cached OpenRouter models |
|
||||
| POST | `/admin/models/refresh` | ✓ | ✓ | Refresh model cache from OpenRouter |
|
||||
|
||||
### White Box AI Service (`/ai`, `/generate`)
|
||||
|
||||
Model listing and multi-modal generation via openrouter.ai.
|
||||
|
||||
| Method | Path | Auth required | Description |
|
||||
| ------ | ---------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| GET | `/ai/models` | ✓ | List available OpenRouter models |
|
||||
| POST | `/ai/chat` | ✓ | Multi-turn chat completion |
|
||||
| POST | `/generate/text` | ✓ | Single-prompt text generation (optional system prompt) |
|
||||
| POST | `/generate/image` | ✓ | Text-to-image (DALL-E via `/images/generations` or FLUX/GPT-5 Image Mini via `/chat/completions` with `modalities`) |
|
||||
| POST | `/generate/video` | ✓ | Text-to-video (Sora 2 Pro, Veo 3.1 Fast) — returns `polling_url` |
|
||||
| POST | `/generate/video/from-image` | ✓ | Image-to-video — returns `polling_url` |
|
||||
| GET | `/generate/video/status` | ✓ | Poll video generation status via `polling_url` |
|
||||
| Method | Path | Auth required | Description |
|
||||
| ------ | ------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| GET | `/ai/models` | ✓ | List available OpenRouter models |
|
||||
| POST | `/ai/chat` | ✓ | Multi-turn chat completion |
|
||||
| POST | `/generate/text` | ✓ | Single-prompt text generation (optional system prompt) |
|
||||
| POST | `/generate/image` | ✓ | Text-to-image (DALL-E via `/images/generations` or FLUX/GPT-5 Image Mini via `/chat/completions` with `modalities`) |
|
||||
| POST | `/generate/video` | ✓ | Text-to-video (Sora 2 Pro, Veo 3.1 Fast) — returns `polling_url` |
|
||||
| POST | `/generate/video/from-image` | ✓ | Image-to-video — returns `polling_url` |
|
||||
| GET | `/generate/video/status` | ✓ | Poll video generation status via `polling_url` |
|
||||
| GET | `/generate/images` | ✓ | List current user's generated images |
|
||||
| GET | `/generate/images/{id}` | ✓ | Get a single generated image |
|
||||
| GET | `/generate/videos` | ✓ | List current user's video jobs |
|
||||
| GET | `/generate/videos/{id}` | ✓ | Get a single video job |
|
||||
| POST | `/generate/videos/{id}/cancel` | ✓ | Cancel a queued/processing video job |
|
||||
|
||||
**Video generation flow:** The `/generate/video` and `/generate/video/from-image` endpoints submit a job to OpenRouter's `/api/v1/videos` endpoint and return immediately with `status: "queued"` and a `polling_url`. Clients poll `/generate/video/status?polling_url=...` every 5 seconds until `status` is `"completed"` (returns `unsigned_urls`) or `"failed"`.
|
||||
**Video generation flow:** The `/generate/video` and `/generate/video/from-image` endpoints queue a job in the local database and return immediately with `status: "queued"`. A background worker (`video_worker.py`) submits the job to OpenRouter's `/api/v1/videos` endpoint, receives a `polling_url`, and polls it periodically until the job reaches `"completed"` or `"failed"`. The frontend polls `GET /generate/video/{id}/status` every 5 seconds to show live status updates.
|
||||
|
||||
**Image generation routing:** The router auto-detects the model type — models containing `"flux"` or `"gpt-5-image-mini"` are routed to `/chat/completions` with `modalities: ["image"]`, while others (e.g. DALL-E 3) use the legacy `/images/generations` endpoint.
|
||||
**Image generation routing:** The router auto-detects the model type — models containing `"flux"` or `"gpt-5-image-mini"` are routed to `/chat/completions` with `modalities: ["image"]` (or `["image", "text"]` depending on cached output modalities), while others (e.g. DALL-E 3) use the legacy `/images/generations` endpoint.
|
||||
|
||||
### White Box DB Service (`db.py`)
|
||||
|
||||
|
||||
+20
-9
@@ -48,20 +48,31 @@ Describes concrete behavior and interactions of the system's building blocks in
|
||||
1. User submits video generation form with prompt, model, aspect ratio, resolution, and duration
|
||||
2. Flask POSTs to `POST /generate/video` with JWT header
|
||||
3. Auth Service validates JWT
|
||||
4. Backend calls OpenRouter `POST /api/v1/videos` with model, prompt, aspect_ratio, resolution, duration_seconds
|
||||
5. OpenRouter returns `{"id": "...", "polling_url": "..."}` with `status: "queued"`
|
||||
6. FastAPI returns `VideoResponse` with `polling_url` to Flask
|
||||
7. Flask renders result page with polling UI
|
||||
8. Frontend JavaScript polls `GET /generate/video/status?polling_url=...` every 5 seconds
|
||||
9. When `status` becomes `"completed"`, the response includes `unsigned_urls` — the video is displayed in a `<video>` element
|
||||
10. If `status` becomes `"failed"`, an error message is shown
|
||||
4. Backend inserts a row into `generated_videos` with `status: "queued"` and returns the DB job ID
|
||||
5. Flask renders result page with polling UI
|
||||
6. Background worker (`video_worker.py`) picks up queued jobs every 15 seconds:
|
||||
- Calls OpenRouter `POST /api/v1/videos` with model, prompt, and parameters
|
||||
- Receives `{"id": "...", "polling_url": "..."}` and updates the DB row to `status: "processing"`
|
||||
- Polls the `polling_url` every 15 seconds until `status` is `"completed"` or `"failed"`
|
||||
- Updates the DB row with the final status and video URL
|
||||
7. Frontend JavaScript polls `GET /generate/video/{db_id}/status` every 5 seconds
|
||||
8. When `status` becomes `"completed"`, the response includes `video_url` — the video is displayed in a `<video>` element
|
||||
9. If `status` becomes `"failed"`, an error message is shown
|
||||
10. User can click "Cancel Job" to mark the job as `"cancelled"` (stops local polling, does not stop the provider job)
|
||||
|
||||
## Scenario 4a: Video Generation (Image-to-Video)
|
||||
|
||||
1. User provides an image URL, motion prompt, model, aspect ratio, resolution, and duration
|
||||
2. Flask POSTs to `POST /generate/video/from-image` with JWT header
|
||||
3. Backend calls OpenRouter `POST /api/v1/videos` with `image_url`, prompt, and parameters
|
||||
4. Same polling flow as Scenario 4
|
||||
3. Same background worker flow as Scenario 4, with `generation_type: "image_to_video"`
|
||||
|
||||
## Scenario 4b: Video Job Cancellation
|
||||
|
||||
1. User clicks "Cancel Job" on the video detail page or gallery pending card
|
||||
2. Frontend POSTs to `/generate/video/{id}/cancel`
|
||||
3. Backend verifies the job belongs to the user and is not in a terminal state
|
||||
4. Backend updates the DB row `status` to `"cancelled"`
|
||||
5. Frontend stops polling and updates the UI to show "Job cancelled"
|
||||
|
||||
## Scenario 5: Token Refresh
|
||||
|
||||
|
||||
+11
-6
@@ -16,11 +16,16 @@ The router auto-detects the model type and routes accordingly. Image configurati
|
||||
|
||||
## Video Generation
|
||||
|
||||
Video generation uses OpenRouter's `/api/v1/videos` endpoint with a **submit-and-poll** pattern:
|
||||
Video generation uses OpenRouter's `/api/v1/videos` endpoint with a **submit-and-poll** pattern orchestrated by a background worker:
|
||||
|
||||
1. `POST /api/v1/videos` with `model`, `prompt`, `aspect_ratio`, `resolution`, `duration_seconds`
|
||||
2. Response: `{"id": "job_id", "polling_url": "https://..."}` with `status: "queued"`
|
||||
3. Poll `GET polling_url` every 5 seconds until `status` is `"completed"` or `"failed"`
|
||||
4. Completed response includes `unsigned_urls: [str]` array with video download URLs
|
||||
1. User submits a video request via `POST /generate/video` (or `/generate/video/from-image`)
|
||||
2. Backend inserts a row into `generated_videos` with `status: "queued"` and returns immediately
|
||||
3. Background worker (`video_worker.py`) picks up queued jobs every 15 seconds:
|
||||
- Calls `POST /api/v1/videos` with `model`, `prompt`, `aspect_ratio`, `resolution`, `duration`
|
||||
- Receives `{"id": "job_id", "polling_url": "https://..."}` and updates DB to `status: "processing"`
|
||||
- Polls `GET polling_url` every 15 seconds until `status` is `"completed"` or `"failed"`
|
||||
- Updates DB with final status, `video_url`, and any `error` message
|
||||
4. Frontend polls `GET /generate/video/{db_id}/status` every 5 seconds to show live updates
|
||||
5. Completed response includes `video_url` — the video is displayed in a `<video>` element
|
||||
|
||||
Supported models: `openai/sora-2-pro`, `google/veo-3.1-fast`. Both text-to-video and image-to-video use the same `/api/v1/videos` endpoint (image-to-video includes `image_url` in the request body).
|
||||
Supported models: `openai/sora-2-pro`, `google/veo-3.1-fast`. Both text-to-video and image-to-video use the same `/api/v1/videos` endpoint (image-to-video includes `frame_images` with `first_frame` in the request body).
|
||||
|
||||
Reference in New Issue
Block a user