-
Notifications
You must be signed in to change notification settings - Fork 351
enables pasting of transcript #1380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
enables pasting of transcript #1380
Conversation
📝 WalkthroughWalkthroughAdds 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
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
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 unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this 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 typetimeToMs 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) variantsThe 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 warningYou 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 logsparseTranscriptSimple 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 tickThe 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 validationThe 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 consumersReturning 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 locallyYou 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 propThis 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 reopenDepending 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 copyOptional: 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.
📒 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 goodStable 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?
There was a problem hiding this 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
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.
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.