Skip to content

Conversation

@ameer2468
Copy link
Contributor

@ameer2468 ameer2468 commented Sep 8, 2025

This PR: improves the loading state of transcriptions, as for videos without any audio it would show loading.

When AI generation is kicked off:

  • The first response may have aiProcessing false (aiLoading false) until the worker sets metadata.aiProcessing = true in the DB.
  • On a subsequent poll (useQuery refetchInterval), once the DB flag flips to true, aiLoading will turn true.
    When AI finishes and the worker writes summary/chapters and sets aiProcessing back to false, aiLoading turns false and the content appears.

Summary by CodeRabbit

  • Bug Fixes

    • Status now reflects the latest transcription and AI processing state from the database.
    • Eliminates lingering or misleading loading spinners when AI generation is off or AI data is absent.
  • Performance

    • Reduces unnecessary background polling after transcription completes unless AI generation is actively processing.
  • UI/UX

    • Tighter, more consistent loading behavior during transcription and AI processing; fewer confusing states.
  • Refactor

    • Internal function signature updated to use a typed video ID.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 8, 2025

Walkthrough

Server-side status retrieval now re-fetches the video row from the DB when transcription hasn’t started to derive transcription and AI metadata (aiProcessing, aiTitle, summary, chapters). The Share page tightens polling/loading: polling runs only when aiGenerationEnabled && (aiProcessing || noAiData); loading visibility was simplified and decoupled from AI flag. The transcribe helper’s signature now uses typed Video.VideoId.

Changes

Cohort / File(s) Summary of Changes
Video status API
apps/web/actions/videos/get-status.ts
When transcription is not started, re-reads the video row from the DB and derives transcriptionStatus and AI metadata (aiProcessing defaults false if absent, aiTitle, summary, chapters) from that row; falls back to in-memory values if no DB row found. No public signature changes.
Share UI polling/loading
apps/web/app/s/[videoId]/Share.tsx
Introduces noAiData concept; polling now occurs only when aiGenerationEnabled && (aiProcessing
Transcribe type update
apps/web/lib/transcribe.ts
Added type-only import of Video and changed transcribeVideo's videoId parameter type from string to Video.VideoId; behavior unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant S as get-status action
  participant DB as Database

  C->>S: Request video status(videoId)
  alt Transcription not started
    S->>DB: Fetch latest video row by videoId
    alt Row found
      S->>S: Derive transcriptionStatus, aiProcessing, aiTitle, summary, chapters from DB metadata
      S-->>C: Return status from updated DB state
    else Row not found
      S->>S: Fallback to original in-memory metadata path
      S-->>C: Return status from original path
    end
  else Transcription in progress/complete
    S->>S: Use existing logic
    S-->>C: Return status
  end
Loading
sequenceDiagram
  autonumber
  participant U as Share.tsx
  participant T as Poll Timer
  participant A as get-status API

  note over U: Transcription PENDING/PROCESSING
  T-->>U: tick
  U->>A: fetch status
  A-->>U: status + aiData
  U->>U: Show loading (independent of aiGenerationEnabled)

  note over U: Transcription COMPLETE
  alt aiGenerationEnabled && (aiData.processing || noAiData)
    T-->>U: continue polling
    U->>U: Show loading (if aiData.processing)
  else Otherwise
    T-->>U: stop polling
    U->>U: Hide loading
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit taps keys with a hop and a twirl,
DB whispers truth so the status can unfurl.
Pollers pause when AI naps or finds its cue,
Loading shows only when the work is due.
Typed IDs snug in a cozy burrow — hop, woohoo! 🥕

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ai-transcription-loading-fix

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web/actions/videos/get-status.ts (1)

216-236: After kicking off AI, optimistically set aiProcessing and tighten the refresh query.

Small race: the immediate SELECT may read the row before generateAiMetadata flips aiProcessing. Set it up front and limit the SELECT.

Apply:

-      (async () => {
+      (async () => {
         try {
           console.log(
             `[Get Status] Starting AI metadata generation for video ${videoId}`,
           );
           await generateAiMetadata(videoId, video.ownerId);
           console.log(
             `[Get Status] AI metadata generation completed for video ${videoId}`,
           );
         } catch (error) {
           console.error(
             `[Get Status] Error generating AI metadata for video ${videoId}:`,
             error,
           );
@@
         }
       })();
 
-      const updatedVideo = await db()
+      // Optimistically reflect processing in DB so clients see it immediately
+      await db()
+        .update(videos)
+        .set({
+          metadata: {
+            ...metadata,
+            aiProcessing: true,
+          },
+        })
+        .where(eq(videos.id, videoId));
+
+      const updatedVideo = await db()
         .select()
         .from(videos)
-        .where(eq(videos.id, videoId));
+        .where(eq(videos.id, videoId))
+        .limit(1);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 737c7ee and 4480b85.

📒 Files selected for processing (3)
  • apps/web/actions/videos/get-status.ts (1 hunks)
  • apps/web/app/s/[videoId]/Share.tsx (1 hunks)
  • apps/web/lib/transcribe.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for client-side server state and data fetching in the web app
Mutations should call Server Actions and perform precise cache updates with setQueryData/setQueriesData, avoiding broad invalidations
Prefer Server Components for initial data and pass initialData to client components for React Query hydration

Files:

  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/lib/transcribe.ts
  • apps/web/actions/videos/get-status.ts
{apps/web,packages/ui}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps/web,packages/ui}/**/*.{ts,tsx}: Use Tailwind CSS exclusively for styling in the web app and shared React UI components
Component naming: React components in PascalCase; hooks in camelCase starting with 'use'

Files:

  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/lib/transcribe.ts
  • apps/web/actions/videos/get-status.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; prefer shared types from packages

Files:

  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/lib/transcribe.ts
  • apps/web/actions/videos/get-status.ts
apps/web/actions/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/actions/**/*.ts: All Groq/OpenAI calls must be implemented in Next.js Server Actions under apps/web/actions
Place Server Actions for the web app under apps/web/actions and mark them with 'use server'

Files:

  • apps/web/actions/videos/get-status.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (2)
apps/web/lib/transcribe.ts (1)

4-4: Type-only domain import is correct.

Prevents bundling runtime code and keeps the module side‑effect free.

apps/web/app/s/[videoId]/Share.tsx (1)

203-204: LGTM: Loading only when aiProcessing is true.

Removes misleading loader when AI data is absent post-transcription.

Comment on lines +16 to 19
videoId: Video.VideoId,
userId: string,
aiGenerationEnabled = false,
): Promise<TranscribeResult> {
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Signature now uses Video.VideoId—verify all call sites.

Change looks good; ensure no callers still pass plain string ids.

Run to spot call sites:


🏁 Script executed:

#!/bin/bash
rg -nP --type=ts --type=tsx -C2 '\btranscribeVideo\s*\('

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for transcribeVideo call sites in TS and TSX files using glob patterns
rg -nP -C2 '\btranscribeVideo\s*\(' --glob '*.ts' --glob '*.tsx'

Length of output: 1326


🏁 Script executed:

#!/bin/bash
# Locate the Video namespace and VideoId type alias
rg -nP --type=ts 'export (namespace|type) Video' -nC3

Length of output: 422


Enforce Video.VideoId on all transcribeVideo calls
Call sites still passing plain strings—update to cast or type-annotate as Video.VideoId:

  • apps/web/app/embed/[videoId]/page.tsx:250 – transcribeVideo(video.id, …)
  • apps/web/app/s/[videoId]/page.tsx:437 – transcribeVideo(videoId, …) (route param is string)
  • apps/web/actions/videos/get-status.ts:52 – transcribeVideo(videoId, …)
🤖 Prompt for AI Agents
apps/web/lib/transcribe.ts around lines 16-19: the transcribeVideo API must
enforce Video.VideoId at call sites; update the three callers to pass a properly
typed Video.VideoId instead of plain string — in
apps/web/app/embed/[videoId]/page.tsx line ~250 change transcribeVideo(video.id,
...) to transcribeVideo(video.id as Video.VideoId, ...) or ensure video.id is
typed Video.VideoId; in apps/web/app/s/[videoId]/page.tsx line ~437 cast the
route param before calling (e.g. const id = params.videoId as Video.VideoId) and
call transcribeVideo(id, ...); in apps/web/actions/videos/get-status.ts line ~52
cast the videoId variable to Video.VideoId when calling transcribeVideo(videoId
as Video.VideoId, ...); ensure Video is imported where required.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/web/lib/transcribe.ts (1)

16-19: Signature hardened to Video.VideoId—ensure callers are updated.

This enforces correct branding; verify all call sites now pass a Video.VideoId.

Re-run:

#!/bin/bash
# Verify all transcribeVideo call sites and their argument types
rg -nP -C2 '\btranscribeVideo\s*\(' --type=ts --type=tsx
🧹 Nitpick comments (2)
apps/web/actions/videos/get-status.ts (1)

216-236: Select only needed fields and limit(1) to reduce I/O.

No need to pull full rows here.

Apply within this block:

-      const updatedVideo = await db()
-        .select()
-        .from(videos)
-        .where(eq(videos.id, videoId));
-      if (updatedVideo.length > 0 && updatedVideo[0]) {
-        const updatedMetadata =
-          (updatedVideo[0].metadata as VideoMetadata) || {};
+      const updatedRows = await db()
+        .select({
+          transcriptionStatus: videos.transcriptionStatus,
+          metadata: videos.metadata,
+        })
+        .from(videos)
+        .where(eq(videos.id, videoId))
+        .limit(1);
+      if (updatedRows.length > 0) {
+        const updatedMetadata = (updatedRows[0].metadata as VideoMetadata) || {};
         return {
-          transcriptionStatus:
-            (updatedVideo[0].transcriptionStatus as
+          transcriptionStatus:
+            (updatedRows[0].transcriptionStatus as
               | "PROCESSING"
               | "COMPLETE"
               | "ERROR") || null,
           aiProcessing: updatedMetadata.aiProcessing || false,
           aiTitle: updatedMetadata.aiTitle || null,
           summary: updatedMetadata.summary || null,
           chapters: updatedMetadata.chapters || null,
         };
       }
apps/web/app/s/[videoId]/Share.tsx (1)

205-211: Gate debug logs for production.

Avoid noisy logs in the client bundle.

- console.log({
-   aiLoading,
-   aiData,
-   transcriptionStatus,
-   aiGenerationEnabled,
- });
+ if (process.env.NODE_ENV !== "production") {
+   // eslint-disable-next-line no-console
+   console.log({ aiLoading, aiData, transcriptionStatus, aiGenerationEnabled });
+ }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 737c7ee and 0eb6fab.

📒 Files selected for processing (3)
  • apps/web/actions/videos/get-status.ts (1 hunks)
  • apps/web/app/s/[videoId]/Share.tsx (2 hunks)
  • apps/web/lib/transcribe.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for client-side server state and data fetching in the web app
Mutations should call Server Actions and perform precise cache updates with setQueryData/setQueriesData, avoiding broad invalidations
Prefer Server Components for initial data and pass initialData to client components for React Query hydration

Files:

  • apps/web/lib/transcribe.ts
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/actions/videos/get-status.ts
{apps/web,packages/ui}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps/web,packages/ui}/**/*.{ts,tsx}: Use Tailwind CSS exclusively for styling in the web app and shared React UI components
Component naming: React components in PascalCase; hooks in camelCase starting with 'use'

Files:

  • apps/web/lib/transcribe.ts
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/actions/videos/get-status.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; prefer shared types from packages

Files:

  • apps/web/lib/transcribe.ts
  • apps/web/app/s/[videoId]/Share.tsx
  • apps/web/actions/videos/get-status.ts
apps/web/actions/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/actions/**/*.ts: All Groq/OpenAI calls must be implemented in Next.js Server Actions under apps/web/actions
Place Server Actions for the web app under apps/web/actions and mark them with 'use server'

Files:

  • apps/web/actions/videos/get-status.ts
🧬 Code graph analysis (2)
apps/web/lib/transcribe.ts (1)
packages/web-domain/src/Video.ts (3)
  • Video (13-35)
  • VideoId (9-9)
  • VideoId (10-10)
apps/web/actions/videos/get-status.ts (2)
packages/database/index.ts (1)
  • db (30-35)
packages/database/schema.ts (1)
  • videos (231-277)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (3)
apps/web/lib/transcribe.ts (1)

4-4: Type-only import is correct.

Good move to avoid bundling the domain module at runtime.

apps/web/actions/videos/get-status.ts (1)

216-236: DB re-fetch after kicking off AI: LGTM.

This aligns the response with near-real-time metadata without blocking on the job.

apps/web/app/s/[videoId]/Share.tsx (1)

183-198: Loading behavior matches the intended lifecycle.

Shows spinner during transcription; only shows AI spinner when enabled and processing. Nice.

@ameer2468 ameer2468 merged commit 71a6f94 into main Sep 8, 2025
14 of 15 checks passed
@ameer2468 ameer2468 deleted the ai-transcription-loading-fix branch September 8, 2025 16:46
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/web/actions/videos/get-status.ts (1)

224-239: Remove unreachable guard and simplify indexing.

Inside the length > 0 branch, row cannot be falsy. Simplify with destructuring and a single null check.

-      if (updatedVideo.length > 0) {
-        const row = updatedVideo[0];
-        if (!row) {
-          return {
-            transcriptionStatus:
-              (video.transcriptionStatus as
-                | "PROCESSING"
-                | "COMPLETE"
-                | "ERROR") || null,
-            aiProcessing: metadata.aiProcessing || false,
-            aiTitle: metadata.aiTitle || null,
-            summary: metadata.summary || null,
-            chapters: metadata.chapters || null,
-            // generationError: metadata.generationError || null,
-          };
-        }
-        const updatedMetadata = (row.metadata as VideoMetadata) || {};
+      const [row] = updatedVideo;
+      if (!row) {
+        return {
+          transcriptionStatus:
+            (video.transcriptionStatus as "PROCESSING" | "COMPLETE" | "ERROR") ||
+            null,
+          aiProcessing: metadata.aiProcessing || false,
+          aiTitle: metadata.aiTitle || null,
+          summary: metadata.summary || null,
+          chapters: metadata.chapters || null,
+          // generationError: metadata.generationError || null,
+        };
+      }
+      const updatedMetadata = (row.metadata as VideoMetadata) || {};
apps/web/app/s/[videoId]/Share.tsx (1)

207-213: Gate debug logs behind NODE_ENV or remove before release.

Avoid noisy console output in production.

-  console.log({
-    aiLoading,
-    aiData,
-    transcriptionStatus,
-    aiGenerationEnabled,
-  });
+  if (process.env.NODE_ENV !== "production") {
+    console.log({
+      aiLoading,
+      aiData,
+      transcriptionStatus,
+      aiGenerationEnabled,
+    });
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0eb6fab and 7530353.

📒 Files selected for processing (2)
  • apps/web/actions/videos/get-status.ts (1 hunks)
  • apps/web/app/s/[videoId]/Share.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/actions/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/actions/**/*.ts: All Groq/OpenAI calls must be implemented in Next.js Server Actions under apps/web/actions
Place Server Actions for the web app under apps/web/actions and mark them with 'use server'

Files:

  • apps/web/actions/videos/get-status.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for client-side server state and data fetching in the web app
Mutations should call Server Actions and perform precise cache updates with setQueryData/setQueriesData, avoiding broad invalidations
Prefer Server Components for initial data and pass initialData to client components for React Query hydration

Files:

  • apps/web/actions/videos/get-status.ts
  • apps/web/app/s/[videoId]/Share.tsx
{apps/web,packages/ui}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps/web,packages/ui}/**/*.{ts,tsx}: Use Tailwind CSS exclusively for styling in the web app and shared React UI components
Component naming: React components in PascalCase; hooks in camelCase starting with 'use'

Files:

  • apps/web/actions/videos/get-status.ts
  • apps/web/app/s/[videoId]/Share.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; prefer shared types from packages

Files:

  • apps/web/actions/videos/get-status.ts
  • apps/web/app/s/[videoId]/Share.tsx
🧬 Code graph analysis (1)
apps/web/actions/videos/get-status.ts (2)
packages/database/index.ts (1)
  • db (30-35)
packages/database/schema.ts (1)
  • videos (231-277)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (4)
apps/web/actions/videos/get-status.ts (1)

216-224: Good: immediate DB re-fetch after enqueue.

This reduces stale reads and better reflects aiProcessing flips from the worker. Nicely scoped select with limit(1).

apps/web/app/s/[videoId]/Share.tsx (3)

117-125: Tighter polling condition looks right.

Gating polling to aiGenerationEnabled && (aiProcessing || noAiData) aligns with the lifecycle and avoids unnecessary polling.


185-185: Comment clarifies intent.

Decoupling loading from the AI flag during transcription is clear and matches the UX goal.


196-200: Loading logic for COMPLETE state is correct.

Only showing the AI skeleton when aiGenerationEnabled && processing prevents spinners for videos without audio.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants