Skip to content

feat(video-studio): AI video-generation studio (create + library, wired to /api/video)#1572

Merged
jaylfc merged 5 commits into
devfrom
feat/video-studio
Jul 3, 2026
Merged

feat(video-studio): AI video-generation studio (create + library, wired to /api/video)#1572
jaylfc merged 5 commits into
devfrom
feat/video-studio

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • New "Video Studio" desktop app: Create (prompt, model tier, resolution, duration, seed) and Library (grid of generated clips) views, wired to the existing backend at tinyagentos/routes/video.py (no backend changes beyond the app allowlist).
  • POST /api/video/generate runs synchronously with no job/status endpoint, so the Create stage shows a spinner with a real elapsed-time counter rather than a fake progress bar. A 503 (no backend configured/reachable) surfaces the backend's own error text plus an "Open Store" prompt; other errors show as a plain error banner.
  • Library lists GET /api/video, plays clips inline with a real <video> element, and deletes via DELETE /api/video/{filename}.
  • Registered as an optional "studio" category app (video-studio, Clapperboard icon) in app-registry.ts, allowlisted in tinyagentos/routes/apps.py (version 1.0.0, first-party), and added as a Store catalog card in StudiosView.tsx.
  • README updated (Creative Studios + AI Generation sections) since the doc-gate flags new app additions.

Test plan

  • cd desktop && npm install && npm run build succeeds
  • cd desktop && npx vitest run — 278 files / 2321 tests pass, including new VideoStudioApp.test.tsx (renders, POST body shape, Library grid from GET, DELETE wiring, 503 -> Open Store prompt) and updated StudiosView.test.tsx
  • python3 -m pytest tests/test_routes_apps.py tests/test_apps_installed.py tests/test_installed_apps.py — 58 passed (allowlist-driven, no hardcoded id lists to update)
  • python3 scripts/check_doc_gate.py diff-gate --base origin/dev — clean

Summary by CodeRabbit

  • New Features

    • Added a new Video Studio experience for creating videos and managing a video library.
    • Users can generate videos, view progress, download results, and open saved videos from the library.
    • The studio now appears in the app catalog and is listed in the product documentation.
  • Bug Fixes

    • Added support for opening generated videos directly from the library.
    • Improved empty and error states for video creation and library management.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a new Video Studio desktop app with Create and Library views for video generation and management. It registers the app in the app registry and StoreApp studios catalog, extends the backend optional-app allowlist, adds a video file-serving endpoint, and updates README documentation and tests.

Changes

Video Studio Feature

Layer / File(s) Summary
Shared types and constants
desktop/src/apps/videostudio/types.ts
Defines GeneratedVideo, GenerateParams, StudioView, model/resolution option lists, and default/min/max duration constants.
VideoStudioApp shell and backend wiring
desktop/src/apps/VideoStudioApp.tsx
Implements app state (view, library, create-form, generation progress), fetchVideos, runGenerate with elapsed-time tracking and error handling, optimistic handleDelete, handleDownload, openStore, and layout rendering CreateView/LibraryView.
CreateView UI
desktop/src/apps/videostudio/CreateView.tsx
Renders preview stage, model/resolution/duration/seed controls, auto-growing prompt textarea with keyboard shortcut, and backend/error banners.
LibraryView UI
desktop/src/apps/videostudio/LibraryView.tsx
Renders a responsive video grid with loading skeletons and empty state, plus a detail pane with metadata, download, and delete actions.
VideoStudioApp test suite
desktop/src/apps/VideoStudioApp.test.tsx
Adds Vitest/RTL tests for default rendering, generation payload, 503 error handling, library rendering/empty state, and delete flow.
App registry, Store catalog, backend allowlist, video serving, and docs
desktop/src/registry/app-registry.ts, desktop/src/apps/StoreApp/StudiosView.tsx, desktop/src/apps/StoreApp/StudiosView.test.tsx, tinyagentos/routes/apps.py, tinyagentos/routes/video.py, README.md
Registers video-studio in the app registry and StoreApp catalog, extends backend allowlist/versions/trust maps, adds a GET /api/video/{filename} file-serving endpoint with path traversal validation, and updates README.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CreateView
  participant VideoStudioApp
  participant Backend

  User->>CreateView: Enter prompt, click Generate
  CreateView->>VideoStudioApp: onGenerate()
  VideoStudioApp->>Backend: POST /api/video/generate
  alt success
    Backend-->>VideoStudioApp: generated video data
    VideoStudioApp->>Backend: GET /api/video (refresh library)
    Backend-->>VideoStudioApp: video list
    VideoStudioApp-->>CreateView: latest video result
  else 503 or error
    Backend-->>VideoStudioApp: error response
    VideoStudioApp-->>CreateView: error message, needsBackend flag
  end
Loading
sequenceDiagram
  participant User
  participant LibraryView
  participant VideoStudioApp
  participant Backend

  User->>LibraryView: Click Delete video
  LibraryView->>VideoStudioApp: onDelete(filename)
  VideoStudioApp->>VideoStudioApp: optimistic removal from list
  VideoStudioApp->>Backend: DELETE /api/video/:filename
  alt success
    Backend-->>VideoStudioApp: 200 OK
  else failure
    Backend-->>VideoStudioApp: error
    VideoStudioApp->>VideoStudioApp: rollback list, set libraryError
  end
  VideoStudioApp-->>LibraryView: updated video list/state
Loading

Possibly related PRs

  • jaylfc/taOS#963: Both PRs update the README.md "Creative Studios" section to add new gated studio entries alongside their descriptive bullets.
  • jaylfc/taOS#967: Both PRs modify tinyagentos/routes/apps.py's APP_VERSIONS/APP_TRUST maps and optional-app allowlist infrastructure.
  • jaylfc/taOS#1567: Both PRs wire a new studio app into the same StudiosView catalog and backend allowlist plumbing for different studio apps.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main change: adding the Video Studio app with create and library flows wired to /api/video.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/video-studio

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

const handleReroll = useCallback(() => {
setSeed(String(randomSeed()));
}, []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Stale result not cleared on next Generate.

runGenerate sets latest only on success. On a failed or retried Generate, the previous latest video remains on stage, so users may watch the old clip while the spinner says "Generating… Ns". Reset setLatest(null) (and clear selectedLibraryId) at the start of runGenerate so the stage shows only the spinner / fresh state.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

onClick={() => setView(r.id)}
className={`flex h-[46px] w-[46px] flex-col items-center justify-center gap-0.5 rounded-xl text-[9px] font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
on
? "bg-gradient-to-b from-accent/25 to-transparent text-accent"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Optimistic delete swallows network failure.

handleDelete removes the video from local state immediately and .catch(() => {}) silently ignores the DELETE request. If the backend DELETE fails (network blip, 403, 404), the UI shows the video as gone but the file remains on disk, and a subsequent GET will re-surface it, leaving the UI in an inconsistent state. Track the request, surface an error if it fails, and roll back the optimistic removal.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

<Icon size={21} />
{r.label}
</button>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Download uses the server path field as a URL.

toGeneratedVideo maps backend path (e.g. /data/videos/123_456.mp4) into url and the download anchor uses video.url directly. That string is an absolute filesystem path from the backend, not a publicly addressable URL — clicking Download will navigate to e.g. /data/videos/... relative to the current origin (almost certainly 404) instead of streaming the binary. Use GET /api/video/{filename} (or whatever download route the backend exposes) and trigger the download from a Blob, or have the backend return a /files/... public URL.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

const a = document.createElement("a");
a.href = video.url;
a.download = video.filename || "video.mp4";
a.click();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Interval not started fresh per request when runGenerate is re-entered.

timerRef.current is only cleared at the end of runGenerate. If runGenerate is somehow re-entered while a previous request is still in flight (e.g. an older callback or fast double invocation outside the canGenerate guard — think StrictMode double-effect or a future refactor), the old interval keeps ticking and the new one is overwritten, so elapsedSeconds will jump. Also, setInterval(... , 1000) can fire s + 1 after cleanup — clear the interval at the start of runGenerate too, or guard timerRef.current before assigning.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

const openStore = useCallback(() => {
window.dispatchEvent(
new CustomEvent("taos:open-app", { detail: { app: "store" } }),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Seed input parsed only with parseInt, no bounds check.

parseInt(seed, 10) accepts negative numbers, scientific notation, or arbitrary integers. The backend may reject out-of-range seeds with a 400/422 that surfaces as a generic "Generation failed (N)" banner, which is a confusing UX. Either clamp/validate the input (warn the user) and disable Generate, or surface backend validation errors distinctly.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

muted
preload="metadata"
className="h-full w-full object-cover"
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Every grid thumbnail preloads metadata.

Every visible grid <video> uses preload="metadata". With many library clips, the browser will fetch metadata for all of them on render. Consider preload="none" on offscreen thumbs and switch to preload="metadata" once they're in/near the viewport (or rely on IntersectionObserver) to keep the Library cheap on large libraries.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

className={`grid flex-1 content-start gap-3.5 overflow-auto p-[22px] ${
selected ? "grid-cols-2" : "grid-cols-3"
}`}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Auto-fallback to videos[0] silently changes the user's selection.

selected = videos.find(...) ?? videos[0] ?? null causes the detail pane to jump to whatever happens to be videos[0] whenever the explicitly selected id is missing — including after a delete of the selected item. That can be surprising: the user deletes a clip and the pane immediately shows a different clip's metadata. Prefer leaving selected as null (showing the empty detail/grid-only state) or remembering the user's last selection by index.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

<Clapperboard size={18} />
{generating ? `Generating… ${elapsedSeconds}s` : "Generate"}
</button>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Textarea rows={1} and no auto-grow.

The prompt textarea is locked to a single row, so longer prompts scroll inside the tiny box instead of growing. Combined with the Generate button being clickable via Cmd/Ctrl+Enter, an auto-grow (a few lines, max) would materially improve prompt authoring UX. Not a bug, but worth flagging given the "describe a scene" CTA.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

CRITICAL

File Line Issue
tinyagentos/routes/video.py 212 serve_video serves .json metadata sidecars (prompt/model/seed) without extension check or nosniff

WARNING

File Line Issue
desktop/src/apps/VideoStudioApp.tsx 145 Generic Generation failed (200) when backend returns 200 without data.filename

SUGGESTION

File Line Issue
desktop/src/apps/VideoStudioApp.tsx 134 Seed input silently clamped to MAX_SEED breaks reproducibility
Files Reviewed (11 files)
  • README.md - 0 issues
  • tinyagentos/routes/apps.py - 0 issues
  • tinyagentos/routes/video.py - 1 issue (NEW)
  • desktop/src/registry/app-registry.ts - 0 issues
  • desktop/src/apps/StoreApp/StudiosView.tsx - 0 issues
  • desktop/src/apps/StoreApp/StudiosView.test.tsx - 0 issues
  • desktop/src/apps/VideoStudioApp.tsx - 2 issues (NEW)
  • desktop/src/apps/VideoStudioApp.test.tsx - 0 issues
  • desktop/src/apps/videostudio/CreateView.tsx - 0 issues
  • desktop/src/apps/videostudio/LibraryView.tsx - 0 issues
  • desktop/src/apps/videostudio/types.ts - 0 issues

Force-push reconciliation

The previous review SHA (d20d4c54…) was not reachable from this checkout (force-push or shallow fetch — git diff A..HEAD returned Invalid revision range), so this run fell back to a full review against current HEAD 88749f9. All 8 previously reported issues were re-verified on the current source and are resolved in this revision.

Fix these issues in Kilo Cloud

Previous Review Summary (commit d20d4c5)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit d20d4c5)

Status: 8 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 3
SUGGESTION 4
Issue Details (click to expand)

CRITICAL

File Line Issue
desktop/src/apps/VideoStudioApp.tsx 213 Optimistic delete swallows network failure (no rollback)

WARNING

File Line Issue
desktop/src/apps/VideoStudioApp.tsx 165 Stale latest not reset between Generate attempts
desktop/src/apps/VideoStudioApp.tsx 220 Download uses backend path field (absolute FS path) as anchor href
desktop/src/apps/VideoStudioApp.tsx 188 Seed input parsed with parseInt only — no bounds/validation

SUGGESTION

File Line Issue
desktop/src/apps/VideoStudioApp.tsx 182 setInterval cleared only at end of runGenerate, not at start
desktop/src/apps/videostudio/LibraryView.tsx 104 Every grid <video> uses preload="metadata"
desktop/src/apps/videostudio/LibraryView.tsx 73 Auto-fallback to videos[0] silently changes user's selection
desktop/src/apps/videostudio/CreateView.tsx 222 Textarea rows={1} with no auto-grow
Files Reviewed (10 files)
  • README.md - 0 issues
  • tinyagentos/routes/apps.py - 0 issues
  • desktop/src/registry/app-registry.ts - 0 issues
  • desktop/src/apps/StoreApp/StudiosView.tsx - 0 issues
  • desktop/src/apps/StoreApp/StudiosView.test.tsx - 0 issues
  • desktop/src/apps/VideoStudioApp.tsx - 5 issues
  • desktop/src/apps/VideoStudioApp.test.tsx - 0 issues
  • desktop/src/apps/videostudio/CreateView.tsx - 1 issue
  • desktop/src/apps/videostudio/LibraryView.tsx - 2 issues
  • desktop/src/apps/videostudio/types.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by minimax-m3 · Input: 66.2K · Output: 7.7K · Cached: 1.2M

jaylfc added 5 commits July 3, 2026 11:32
Adds video-studio to the frontend-app allowlist alongside the other
Creative Studios, versioned 1.0.0.
New desktop app wired to the existing /api/video backend:

- Create: prompt textarea, model tier, resolution, duration, and seed
  controls, posting to POST /api/video/generate. The backend generates
  synchronously with no job/status endpoint, so the stage shows a
  spinner with a real elapsed-time counter instead of a fake progress
  bar. A 503 response (no backend configured/reachable) surfaces the
  backend's own error text with an Open Store prompt; other errors
  show as a plain error banner.
- Library: grid of generated clips from GET /api/video, inline
  <video> playback, download, and delete via DELETE
  /api/video/{filename}.

Registered in app-registry.ts as an optional "studio" category app.
Lists Video Studio in the Store's Creative Studios grid so it can be
installed like the other optional studio apps.
Adds Video Studio to the Creative Studios and AI Generation sections.
…on, download href, timer/preload hygiene)

- Roll back the optimistic delete and surface an error when DELETE fails,
  instead of silently swallowing the failure.
- Reset `latest` and clear any existing timer at the start of a generate
  run, so a prior result/timer can't leak into a new attempt.
- Serve generated videos through a new GET /api/video/{filename} route and
  point the frontend at it instead of the backend's raw (unservable)
  `path` field.
- Validate the seed input: reject NaN/negative values, clamp to the
  backend's accepted range, and ignore it when blank.
- Use preload="none" for library grid thumbnails instead of "metadata".
- Only fall back to the first video when there's no explicit library
  selection, instead of silently overriding the user's choice.
- Auto-grow the Create prompt textarea with its content, up to a max
  height.
@jaylfc jaylfc force-pushed the feat/video-studio branch from d20d4c5 to 88749f9 Compare July 3, 2026 10:41
if not video_path.exists():
return JSONResponse({"error": f"Video '{filename}' not found"}, status_code=404)

return FileResponse(video_path, media_type="video/mp4", filename=filename)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: serve_video does not restrict by extension, so it will happily serve the .json metadata sidecar files written by _list_videos (meta_path = vid.with_suffix(".json"), video.py:63). Filenames like 1749999999_12345.json pass the traversal check (no /, \, or ..) and contain the prompt, model, duration, resolution, and seed. Combine that with media_type="video/mp4" and no X-Content-Type-Options: nosniff and the response is content-sniffed in the browser — any user with the ability to call the endpoint (or anyone who can guess the <timestamp>_<seed>.json pattern from a public seed) can read raw metadata. Filter on extension (filename.lower().endswith((".mp4", ".webm"))) or derive the on-disk filename from the base name and append .mp4, and add headers={"X-Content-Type-Options": "nosniff"} to the FileResponse.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

body: JSON.stringify(body),
});
const data = await res.json().catch(() => ({}));
if (res.ok && data.filename) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Success path requires data.filename (line 145), but the backend is only required to return status: "generated" plus metadata. If a future backend returns 200 with a different shape (e.g. only status or only data.data[0].url) the user sees the generic "Generation failed (200)" banner instead of a clear "backend returned an unexpected response" message. Either tighten the parse (if (!res.ok || !data?.filename) { ... }) or surface data in the error so the bad shape is debuggable from the UI.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

if (trimmedSeed) {
const parsedSeed = parseInt(trimmedSeed, 10);
if (!Number.isNaN(parsedSeed) && parsedSeed >= 0) {
body.seed = Math.min(parsedSeed, MAX_SEED);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Silently clamping parsedSeed to MAX_SEED (line 134) makes the request non-reproducible from the seed the user typed — they enter 9999999999 and the backend gets 4294967295, so the next run with the same input won't produce the same clip. Either (a) reject values > MAX_SEED in canGenerate / the input's max attribute and show an inline error, or (b) at minimum echo the clamped value back into the seed field after clamping so the user sees what was actually sent.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
tinyagentos/routes/video.py (1)

196-214: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared filename-safety/lookup logic.

serve_video duplicates the exact validation and existence-check pattern already in delete_video (lines 220-225). Consider factoring both into a shared helper to avoid the two copies drifting if the traversal checks are ever hardened.

♻️ Proposed refactor
+def _resolve_safe_video_path(videos_dir: Path, filename: str) -> Path | None:
+    """Return the on-disk path for `filename` if it is safe and exists, else None."""
+    if "/" in filename or "\\" in filename or ".." in filename:
+        return None
+    video_path = videos_dir / filename
+    return video_path if video_path.exists() else None
+
+
 `@router.get`("/api/video/{filename}")
 async def serve_video(request: Request, filename: str):
     """Serve a generated video file for playback/download.
 
     The ``path`` field returned by generate/list is not itself a mounted
     static route -- the frontend hits this endpoint by filename instead.
     """
     videos_dir = _videos_dir(request)
-
-    if "/" in filename or "\\" in filename or ".." in filename:
-        return JSONResponse({"error": "Invalid filename"}, status_code=400)
-
-    video_path = videos_dir / filename
-    if not video_path.exists():
-        return JSONResponse({"error": f"Video '{filename}' not found"}, status_code=404)
+    video_path = _resolve_safe_video_path(videos_dir, filename)
+    if video_path is None:
+        return JSONResponse({"error": f"Video '{filename}' not found or invalid"}, status_code=404)
 
     return FileResponse(video_path, media_type="video/mp4", filename=filename)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/routes/video.py` around lines 196 - 214, The filename validation
and on-disk lookup logic in serve_video duplicates the same safety checks used
by delete_video, so factor that shared behavior into a common helper and have
both endpoints call it. Extract the path-safety and existence handling around
serve_video and delete_video into a single reusable function so future hardening
only needs one update, while keeping serve_video and delete_video as the entry
points that use the shared helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@desktop/src/apps/videostudio/LibraryView.tsx`:
- Around line 144-149: Seed display is using a falsy check in the MetaRow for
selected.seed, so a valid value of 0 falls back to the placeholder. Update the
seed rendering in LibraryView so it only treats null/undefined as missing, and
preserve 0 as a real value; use the selected.seed handling near the existing
MetaRow entries and keep the fallback “—” only for absent values.

In `@desktop/src/apps/VideoStudioApp.tsx`:
- Around line 130-136: The seed parsing in VideoStudioApp silently drops invalid
values, so users get no feedback when their entered seed is ignored. Update the
seed handling around trimmedSeed/parsedSeed in VideoStudioApp to surface an
invalid-input state or error instead of omitting body.seed when parseInt fails
or the value is negative, and only send the request once the seed input is
either valid or explicitly cleared. Keep the existing MAX_SEED clamping for
valid seeds.
- Around line 180-207: The optimistic delete rollback in handleDelete restores
the video list but not the related selection state. Update the failure path in
the fetch(...).catch block to also restore selectedLibraryId and latest when the
deleted filename was the current selection or latest clip, using the same
removed/removedIndex tracking already in handleDelete. Ensure the rollback puts
the video back exactly as it was before deletion, including library selection
and latest display state.

In `@README.md`:
- Around line 262-266: The Video Studio description uses backend names that
don’t match the AI Generation section, so update the “Video Studio” bullet to
use the same supported backend names as the rest of the README. Adjust the
wording in the Video Studio copy to reference the current video backends
consistently with the AI Generation “Video” entry and the backend table, using
the same symbols/text in the README section so readers see one unified list.

---

Nitpick comments:
In `@tinyagentos/routes/video.py`:
- Around line 196-214: The filename validation and on-disk lookup logic in
serve_video duplicates the same safety checks used by delete_video, so factor
that shared behavior into a common helper and have both endpoints call it.
Extract the path-safety and existence handling around serve_video and
delete_video into a single reusable function so future hardening only needs one
update, while keeping serve_video and delete_video as the entry points that use
the shared helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b9971bb3-8e89-4730-903a-8c9021d7b5cf

📥 Commits

Reviewing files that changed from the base of the PR and between c19e2f7 and 88749f9.

📒 Files selected for processing (11)
  • README.md
  • desktop/src/apps/StoreApp/StudiosView.test.tsx
  • desktop/src/apps/StoreApp/StudiosView.tsx
  • desktop/src/apps/VideoStudioApp.test.tsx
  • desktop/src/apps/VideoStudioApp.tsx
  • desktop/src/apps/videostudio/CreateView.tsx
  • desktop/src/apps/videostudio/LibraryView.tsx
  • desktop/src/apps/videostudio/types.ts
  • desktop/src/registry/app-registry.ts
  • tinyagentos/routes/apps.py
  • tinyagentos/routes/video.py

Comment on lines +144 to +149
<div className="flex flex-col gap-2">
<MetaRow label="Model" value={selected.model || "—"} />
<MetaRow label="Resolution" value={selected.resolution || "—"} />
<MetaRow label="Duration" value={`${selected.duration || 0}s`} />
<MetaRow label="Seed" value={String(selected.seed || "—")} />
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Seed of 0 incorrectly displays as "—".

String(selected.seed || "—") treats a valid seed value of 0 as falsy, showing "—" instead of "0". Since seed defaults to 0 when the backend omits it (see toGeneratedVideo in VideoStudioApp.tsx), users who generated with an explicit seed of 0 (or got the default) will see a placeholder instead of the actual seed value.

🐛 Proposed fix
-              <MetaRow label="Seed" value={String(selected.seed || "—")} />
+              <MetaRow
+                label="Seed"
+                value={selected.seed != null ? String(selected.seed) : "—"}
+              />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="flex flex-col gap-2">
<MetaRow label="Model" value={selected.model || "—"} />
<MetaRow label="Resolution" value={selected.resolution || "—"} />
<MetaRow label="Duration" value={`${selected.duration || 0}s`} />
<MetaRow label="Seed" value={String(selected.seed || "—")} />
</div>
<div className="flex flex-col gap-2">
<MetaRow label="Model" value={selected.model || "—"} />
<MetaRow label="Resolution" value={selected.resolution || "—"} />
<MetaRow label="Duration" value={`${selected.duration || 0}s`} />
<MetaRow
label="Seed"
value={selected.seed != null ? String(selected.seed) : "—"}
/>
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/apps/videostudio/LibraryView.tsx` around lines 144 - 149, Seed
display is using a falsy check in the MetaRow for selected.seed, so a valid
value of 0 falls back to the placeholder. Update the seed rendering in
LibraryView so it only treats null/undefined as missing, and preserve 0 as a
real value; use the selected.seed handling near the existing MetaRow entries and
keep the fallback “—” only for absent values.

Comment on lines +130 to +136
const trimmedSeed = seed.trim();
if (trimmedSeed) {
const parsedSeed = parseInt(trimmedSeed, 10);
if (!Number.isNaN(parsedSeed) && parsedSeed >= 0) {
body.seed = Math.min(parsedSeed, MAX_SEED);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Invalid seed input is silently discarded.

If trimmedSeed fails the parsedSeed >= 0 / NaN check, the request is sent with no seed at all — the user has no indication their entered value was ignored and the backend will pick a different (random) seed than expected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/apps/VideoStudioApp.tsx` around lines 130 - 136, The seed parsing
in VideoStudioApp silently drops invalid values, so users get no feedback when
their entered seed is ignored. Update the seed handling around
trimmedSeed/parsedSeed in VideoStudioApp to surface an invalid-input state or
error instead of omitting body.seed when parseInt fails or the value is
negative, and only send the request once the seed input is either valid or
explicitly cleared. Keep the existing MAX_SEED clamping for valid seeds.

Comment on lines +180 to +207
const handleDelete = useCallback((filename: string) => {
let removed: GeneratedVideo | undefined;
let removedIndex = -1;
setVideos((prev) => {
removedIndex = prev.findIndex((v) => v.filename === filename);
removed = prev[removedIndex];
return prev.filter((v) => v.filename !== filename);
});
setSelectedLibraryId((cur) => (cur === filename ? null : cur));
setLatest((cur) => (cur?.filename === filename ? null : cur));

fetch(`/api/video/${encodeURIComponent(filename)}`, { method: "DELETE" })
.then((res) => {
if (!res.ok) throw new Error(`Delete failed (${res.status})`);
})
.catch((e) => {
// Roll back the optimistic removal and surface the failure --
// don't let a failed delete silently vanish the item.
setVideos((prev) => {
if (!removed || prev.some((v) => v.filename === filename)) {
return prev;
}
const at = Math.min(removedIndex < 0 ? prev.length : removedIndex, prev.length);
return [...prev.slice(0, at), removed, ...prev.slice(at)];
});
setLibraryError(`Failed to delete video: ${(e as Error).message}`);
});
}, []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Selection/latest state isn't restored alongside the rolled-back video.

On a failed delete, videos correctly rolls back (lines 198-204), but selectedLibraryId and latest — cleared optimistically at lines 188-189 — stay cleared. The video reappears in the library unselected, and if it was the just-generated clip, latest won't show it again on the Create stage.

Proposed fix
       .catch((e) => {
         // Roll back the optimistic removal and surface the failure --
         // don't let a failed delete silently vanish the item.
         setVideos((prev) => {
           if (!removed || prev.some((v) => v.filename === filename)) {
             return prev;
           }
           const at = Math.min(removedIndex < 0 ? prev.length : removedIndex, prev.length);
           return [...prev.slice(0, at), removed, ...prev.slice(at)];
         });
+        if (removed) {
+          setSelectedLibraryId((cur) => cur ?? removed!.filename);
+          setLatest((cur) => cur ?? (wasLatest ? removed! : cur));
+        }
         setLibraryError(`Failed to delete video: ${(e as Error).message}`);
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/apps/VideoStudioApp.tsx` around lines 180 - 207, The optimistic
delete rollback in handleDelete restores the video list but not the related
selection state. Update the failure path in the fetch(...).catch block to also
restore selectedLibraryId and latest when the deleted filename was the current
selection or latest clip, using the same removed/removedIndex tracking already
in handleDelete. Ensure the rollback puts the video back exactly as it was
before deletion, including library selection and latest display state.

Comment thread README.md
Comment on lines +262 to +266
- **Video Studio.** Create and Library in one app. Describe a scene, pick a resolution and duration, and generate a clip on any discovered video backend (WanGP / Wan 2.1). Generated clips land in a library with inline playback, download, and delete.

### AI Generation
- **Images**. Stable Diffusion via NPU, GPU, or CPU (multi-backend auto-discovery), surfaced through Image Studio
- **Video**. WanGP, LTX Video (unlocks with 6GB+ GPU worker)
- **Video**. WanGP, LTX Video (unlocks with 6GB+ GPU worker), surfaced through Video Studio

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== README context ==\n'
nl -ba README.md | sed -n '240,275p'

printf '\n== Search backend mentions ==\n'
rg -n "WanGP|Wan 2\.1|LTX Video|LTX|WanGP" README.md . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' || true

Repository: jaylfc/taOS

Length of output: 211


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
text = Path("README.md").read_text(encoding="utf-8").splitlines()
for start, end in [(240, 275)]:
    print(f"== README {start}-{end} ==")
    for i in range(start-1, min(end, len(text))):
        print(f"{i+1:4d}: {text[i]}")
    print()

needles = ["WanGP", "Wan 2.1", "LTX Video", "LTX"]
print("== Search backend mentions ==")
for idx, line in enumerate(text, 1):
    if any(n in line for n in needles):
        print(f"{idx}: {line}")
PY

Repository: jaylfc/taOS

Length of output: 4814


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
text = Path("README.md").read_text(encoding="utf-8").splitlines()
start, end = 374, 386
print(f"== README {start}-{end} ==")
for i in range(start-1, min(end, len(text))):
    print(f"{i+1:4d}: {text[i]}")
PY

Repository: jaylfc/taOS

Length of output: 2026


Align the Video Studio backend list
The Video Studio bullet still uses “WanGP / Wan 2.1”, while the AI Generation section lists “WanGP, LTX Video” and the backend table expands this further. Update the wording so the supported video backends are named consistently in both places.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 262 - 266, The Video Studio description uses backend
names that don’t match the AI Generation section, so update the “Video Studio”
bullet to use the same supported backend names as the rest of the README. Adjust
the wording in the Video Studio copy to reference the current video backends
consistently with the AI Generation “Video” entry and the backend table, using
the same symbols/text in the README section so readers see one unified list.

@jaylfc jaylfc merged commit ea53444 into dev Jul 3, 2026
9 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in TinyAgentOS Roadmap Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant