Skip to content

Conversation

steven-mpawulo
Copy link

@steven-mpawulo steven-mpawulo commented Aug 21, 2025

I have added a modal that displays a text area field with buttons cancel and save where a user can paste the transcript details.
I have also added a transcript parser to properly format the transcript line by line.
I have done this in regards to issue #1301
Below are the visual representations of what I have managed to achieve.
Screenshot 2025-08-21 at 2 54 54 PM
Screenshot 2025-08-21 at 2 55 44 PM
Screenshot 2025-08-21 at 2 55 58 PM

I would love to be guided on what should happen next.
Thank you for your time.


Summary by cubic

Adds a Paste Transcript flow to the Transcript view. Users can paste a transcript, we parse timestamps/speakers, and save words to the session for immediate editing. Addresses Linear #1301.

  • New Features
    • Paste Transcript modal in the empty state with textarea, Cancel/Save, loading, and toasts.
    • Format validation and parsing of lines (HH:MM:SS or MM:SS — Speaker: Text), speaker normalization, and duration estimation.
    • Save parsed words to the session (upsert), invalidate session queries, and update the editor.
    • New utilities: parseTranscript, parseTranscriptSimple, parseTranscriptAsync, and validateTranscriptFormat.

Copy link

coderabbitai bot commented Aug 21, 2025

📝 Walkthrough

Walkthrough

Adds a new transcript parsing utility module and integrates a paste-transcript modal into TranscriptView. The modal validates and parses pasted text, persists parsed words to the session, invalidates queries, updates the editor with new words, and provides user feedback.

Changes

Cohort / File(s) Summary
Transcript parsing utility
apps/desktop/src/components/right-panel/utils/transcript-parser.ts
New parser with options, result types, structured errors/warnings, multiple timestamp/speaker formats, duration estimation, validation helpers, simple and async APIs. Exports: parseTranscript, parseTranscriptSimple, parseTranscriptAsync, validateTranscriptFormat.
Transcript view paste integration
apps/desktop/src/components/right-panel/views/transcript-view.tsx
Adds paste modal UI, uses validateTranscriptFormat and parseTranscriptSimple, saves parsed words to session, invalidates queries, updates editor via onTranscriptSaved, adjusts RenderEmpty props/signature, adds translations and toasts.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant TranscriptView
  participant PasteModal
  participant Parser as transcript-parser.ts
  participant DB as SessionStore
  participant Query as QueryClient
  participant Editor

  User->>TranscriptView: Click "Paste transcript"
  TranscriptView->>PasteModal: Open modal
  User->>PasteModal: Paste text + Save
  PasteModal->>Parser: validateTranscriptFormat(raw)
  Parser-->>PasteModal: { isValid, issues }
  alt valid
    PasteModal->>Parser: parseTranscriptSimple(raw)
    Parser-->>PasteModal: Word2[]
    PasteModal->>DB: Persist words for session
    DB-->>PasteModal: OK
    PasteModal->>Query: Invalidate session queries
    Query-->>PasteModal: OK
    PasteModal->>TranscriptView: onTranscriptSaved(words)
    TranscriptView->>Editor: Set words and update view
    PasteModal-->>User: Success toast
    PasteModal->>PasteModal: Close
  else invalid
    PasteModal-->>User: Show issues / prevent save
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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 (13)
apps/desktop/src/components/right-panel/utils/transcript-parser.ts (7)

85-121: Support MM:SS.mmm timestamps and unify error type

timeToMs handles HH:MM:SS(.mmm) and MM:SS, but misses MM:SS.mmm. Also, throwing a generic Error here makes upstream handling inconsistent with TranscriptParseError.

Apply:

-  const formats = [
+  const formats = [
     /^(\d{1,2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/, // HH:MM:SS.mmm
     /^(\d{1,2}):(\d{2}):(\d{2})$/, // HH:MM:SS
-    /^(\d{1,2}):(\d{2})$/, // MM:SS (treat as 00:MM:SS)
+    /^(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?$/, // MM:SS(.mmm) (treat as 00:MM:SS.mmm)
   ];
@@
-        if (i === 2) {
+        if (i === 2) {
           s = m;
           m = h;
           h = "0";
         }
@@
-        if (minutes >= 60 || seconds >= 60) {
-          throw new Error(`Invalid time values: ${time}`);
-        }
+        if (minutes >= 60 || seconds >= 60) {
+          throw new TranscriptParseError(`Invalid time values: ${time}`, 0, time);
+        }
@@
-    throw new Error(`Invalid time format: ${time}`);
+    throw new TranscriptParseError(`Invalid time format: ${time}`, 0, time);

218-235: Tighten pattern comments and add mm:ss(.mmm) variants

The comment “Common formats without colons” is misleading; all patterns include colons. Also, we support MM:SS but not MM:SS.mmm variants here.

-  // Enhanced regex patterns for maximum compatibility
+  // Enhanced regex patterns for maximum compatibility (separator variations, bracketed timestamps)
   const patterns = [
-    // Original format: HH:MM:SS — Speaker: Text
+    // Primary format: HH:MM:SS — Speaker: Text
     /^(\d{1,2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s*—\s*(.+?):\s*(.*)$/,
     // Alternative separators
     /^(\d{1,2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s*[-–—]\s*(.+?):\s*(.*)$/,
     /^(\d{1,2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s+(.+?):\s*(.*)$/,
     // Bracketed timestamps
     /^\[(\d{1,2}:\d{2}:\d{2}(?:\.\d{1,3})?)\]\s*(.+?):\s*(.*)$/,
     // Parenthesized timestamps
     /^\((\d{1,2}:\d{2}:\d{2}(?:\.\d{1,3})?)\)\s*(.+?):\s*(.*)$/,
-    // Common formats without colons
+    // Space-separated variant
     /^(\d{1,2}:\d{2}:\d{2}(?:\.\d{1,3})?)\s*(.+?)\s*:\s*(.*)$/,
-    // MM:SS format (shorter timestamps)
-    /^(\d{1,2}:\d{2})\s*—\s*(.+?):\s*(.*)$/,
-    /^(\d{1,2}:\d{2})\s*[-–—]\s*(.+?):\s*(.*)$/,
+    // MM:SS(.mmm) format (shorter timestamps)
+    /^(\d{1,2}:\d{2}(?:\.\d{1,3})?)\s*—\s*(.+?):\s*(.*)$/,
+    /^(\d{1,2}:\d{2}(?:\.\d{1,3})?)\s*[-–—]\s*(.+?):\s*(.*)$/,
   ];

423-430: Use a consistent denominator for success-rate warning

You compare successfullyParsed against trimmed non-empty lines to decide warning, but print percentage using total lines. Use the same denominator for both.

-  if (hasTimestamps && successfullyParsed / lines.filter(l => l.trim()).length < 0.8) {
+  const nonEmptyLineCount = lines.filter(l => l.trim()).length;
+  if (hasTimestamps && nonEmptyLineCount > 0 && successfullyParsed / nonEmptyLineCount < 0.8) {
     warnings.push({
       line: 0,
-      message: `Low parsing success rate: ${Math.round((successfullyParsed / lines.length) * 100)}%`,
+      message: `Low parsing success rate: ${Math.round((successfullyParsed / nonEmptyLineCount) * 100)}%`,
       originalText: "",
       suggestion: "Check transcript format consistency",
     });
   }

450-484: Adhere to project guideline “No error handling” and reduce noisy logs

parseTranscriptSimple wraps parseTranscript in try/catch and logs to console. Given “No error handling” guideline for ts/tsx, consider letting callers decide how to handle errors and gating logs behind debug.

-export function parseTranscriptSimple(raw: string): Word2[] {
-  try {
-    const result = parseTranscript(raw, {
-      strictMode: false,
-      estimateDurationFromText: true,
-      allowEmptyLines: true,
-      wordsPerMinute: 150,
-    });
-
-    // Log warnings and errors for debugging
-    if (result.errors.length > 0) {
-      console.warn("Transcript parsing errors:", result.errors);
-    }
-
-    if (result.warnings.length > 0) {
-      console.info("Transcript parsing warnings:", result.warnings);
-    }
-
-    // Log success metrics
-    console.info(
-      `Transcript parsed: ${result.metadata.successfullyParsed}/${result.metadata.totalLines} lines, ${result.metadata.speakers.size} speakers`,
-    );
-
-    return result.words;
-  } catch (error) {
-    console.error("Failed to parse transcript:", error);
-
-    // Fallback: return empty array or throw based on error type
-    if (error instanceof TranscriptParseError) {
-      throw error;
-    }
-
-    return [];
-  }
-}
+export function parseTranscriptSimple(raw: string): Word2[] {
+  const result = parseTranscript(raw, {
+    strictMode: false,
+    estimateDurationFromText: true,
+    allowEmptyLines: true,
+    wordsPerMinute: 150,
+  });
+  return result.words;
+}

If you prefer to keep diagnostics, add an optional flag parameter or use a centralized logger.


486-505: Comment claims chunked async parsing, but implementation defers only one tick

The function defers parsing to the next macrotask; it doesn’t chunk. Either update the comment or implement actual chunking to avoid long-blocking parses.

Minimal doc fix:

- * Async version for large transcripts (recommended for production)
- * Processes transcript in chunks to avoid blocking the UI
+ * Async wrapper that defers parsing to the next macrotask.
+ * Consider implementing chunked processing for very large transcripts.

Or implement chunking (happy to provide if desired).


510-557: Reduce false positives in format validation

The speakerPattern /:\s*\w/ will match many non-speaker colons. Tighten to require non-empty speaker label and non-empty text part.

-  const speakerPattern = /:\s*\w/;
+  const speakerPattern = /.+?:\s+\S/;

12-23: Consider serializable metadata for external consumers

Returning a Set in metadata.speakers can be awkward for serialization. If this result is ever persisted or posted across boundaries, return speakerCount or an array.

-interface ParseResult {
+interface ParseResult {
   words: Word2[];
   errors: ParseError[];
   warnings: ParseWarning[];
   metadata: {
     totalLines: number;
     successfullyParsed: number;
-    speakers: Set<string>;
+    speakers: string[]; // or: speakerCount: number
     estimatedDuration: number;
     hasTimestamps: boolean;
   };
 }

And change producers/consumers accordingly.

apps/desktop/src/components/right-panel/views/transcript-view.tsx (6)

1-1: Avoid prop drilling QueryClient; use useQueryClient locally

You import QueryClient and pass an instance down to RenderEmpty, but RenderEmpty can call useQueryClient itself. This removes a prop and avoids a runtime import used as a type.

-import { QueryClient, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useQuery, useQueryClient } from "@tanstack/react-query";

206-216: Inline QueryClient in RenderEmpty and drop the prop

This simplifies the API and prevents unnecessary coupling.

-            <RenderEmpty
+            <RenderEmpty
               sessionId={sessionId}
               panelWidth={panelWidth}
               onTranscriptSaved={handleSetWords}
-              queryClient={queryClient}
             />
-function RenderEmpty({ sessionId, panelWidth, onTranscriptSaved, queryClient }: {
+function RenderEmpty({ sessionId, panelWidth, onTranscriptSaved }: {
   sessionId: string;
   panelWidth: number;
   onTranscriptSaved: (words: Word2[]) => void;
-  queryClient: QueryClient;
 }) {
+  const queryClient = useQueryClient();

Also applies to: 233-241


263-310: Tighten save flow: await invalidations and prefer console.error

  • Await invalidateQueries before closing to reduce “flash” from stale data.
  • Use console.error for failures.
  • Optional: align with guideline “No error handling” by centralizing toasts/logs.
   const handlePastedTranscript = async () => {
     try {
       setIsLoading(true);
       const value = textAreaRef.current?.value?.trim();
@@
-      queryClient.invalidateQueries({
-        queryKey: ["session", "words", sessionId],
-      });
-
-      queryClient.invalidateQueries({
-        queryKey: ["session", sessionId],
-      });
+      await Promise.all([
+        queryClient.invalidateQueries({ queryKey: ["session", "words", sessionId] }),
+        queryClient.invalidateQueries({ queryKey: ["session", sessionId] }),
+      ]);
@@
-      onclose();
+      onclose();
     } catch (error) {
       setIsLoading(false);
-      console.log("Failed to save transcript", error);
+      console.error("Failed to save transcript", error);
     }
   };

259-262: Clear textarea contents on close to avoid stale input on reopen

Depending on Modal behavior, the Textarea may persist its value across open/close. Clear it explicitly.

-  const onclose = () => {
-    setShowPasteTranscriptModal(false);
-  };
+  const onclose = () => {
+    setShowPasteTranscriptModal(false);
+    if (textAreaRef.current) textAreaRef.current.value = "";
+  };

Also applies to: 305-306


312-320: Toast messages: consider i18n and more specific copy

Optional: wrap title/content in for localization, and use more specific messages (e.g., “Paste transcript is empty”, “Invalid format”).

-    toast({
+    toast({
       id: "paste-transcript",
-      title: "Paste Transcript",
+      title: <Trans>Paste Transcript</Trans>,
-      content: content,
+      content,
       dismissible: true,
     });

393-402: Modal UX: minor polish

  • Self-close Textarea tag.
  • Add isLoading to Save is great; consider disabling Cancel while saving only if side effects occur (optional).
  • Placeholders/titles can use for consistency.
-              <Textarea
+              <Textarea
                 className="h-full"
                 ref={textAreaRef}
-                placeholder="Type or paste your transcript"
-              >
-              </Textarea>
+                placeholder="Type or paste your transcript"
+              />

Also applies to: 406-437

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1d8c689 and 26e28f5.

📒 Files selected for processing (2)
  • apps/desktop/src/components/right-panel/utils/transcript-parser.ts (1 hunks)
  • apps/desktop/src/components/right-panel/views/transcript-view.tsx (7 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}

⚙️ CodeRabbit configuration file

**/*.{js,ts,tsx,rs}: 1. No error handling.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

Files:

  • apps/desktop/src/components/right-panel/views/transcript-view.tsx
  • apps/desktop/src/components/right-panel/utils/transcript-parser.ts
🔇 Additional comments (2)
apps/desktop/src/components/right-panel/views/transcript-view.tsx (2)

141-147: handleSetWords looks good

Stable callback, keeps scroll behavior consistent after setting words.


450-484: Project guideline check: “No error handling”

This file (and the parser) use try/catch and user-facing toasts for failures. If “No error handling” means “centralize error handling/logging,” consider delegating to a shared handler and letting errors bubble. If not, ignore this note.

Would you like me to refactor to centralize error handling (e.g., hook or utility) and keep UI code focused on the happy path?

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

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.

1 participant