lyrics-transcriber 0.40.0__py3-none-any.whl → 0.42.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. lyrics_transcriber/cli/cli_main.py +7 -0
  2. lyrics_transcriber/core/config.py +1 -0
  3. lyrics_transcriber/core/controller.py +30 -52
  4. lyrics_transcriber/correction/anchor_sequence.py +325 -150
  5. lyrics_transcriber/correction/corrector.py +224 -107
  6. lyrics_transcriber/correction/handlers/base.py +28 -10
  7. lyrics_transcriber/correction/handlers/extend_anchor.py +47 -24
  8. lyrics_transcriber/correction/handlers/levenshtein.py +75 -33
  9. lyrics_transcriber/correction/handlers/llm.py +290 -0
  10. lyrics_transcriber/correction/handlers/no_space_punct_match.py +81 -36
  11. lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +46 -26
  12. lyrics_transcriber/correction/handlers/repeat.py +28 -11
  13. lyrics_transcriber/correction/handlers/sound_alike.py +68 -32
  14. lyrics_transcriber/correction/handlers/syllables_match.py +80 -30
  15. lyrics_transcriber/correction/handlers/word_count_match.py +36 -19
  16. lyrics_transcriber/correction/handlers/word_operations.py +68 -22
  17. lyrics_transcriber/correction/text_utils.py +3 -7
  18. lyrics_transcriber/frontend/.yarn/install-state.gz +0 -0
  19. lyrics_transcriber/frontend/.yarn/releases/yarn-4.6.0.cjs +934 -0
  20. lyrics_transcriber/frontend/.yarnrc.yml +3 -0
  21. lyrics_transcriber/frontend/dist/assets/{index-DKnNJHRK.js → index-coH8y7gV.js} +16284 -9032
  22. lyrics_transcriber/frontend/dist/assets/index-coH8y7gV.js.map +1 -0
  23. lyrics_transcriber/frontend/dist/index.html +1 -1
  24. lyrics_transcriber/frontend/package.json +6 -2
  25. lyrics_transcriber/frontend/src/App.tsx +18 -2
  26. lyrics_transcriber/frontend/src/api.ts +103 -6
  27. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +7 -6
  28. lyrics_transcriber/frontend/src/components/DetailsModal.tsx +86 -59
  29. lyrics_transcriber/frontend/src/components/EditModal.tsx +93 -43
  30. lyrics_transcriber/frontend/src/components/FileUpload.tsx +2 -2
  31. lyrics_transcriber/frontend/src/components/Header.tsx +251 -0
  32. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +303 -265
  33. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +117 -0
  34. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +125 -40
  35. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +129 -115
  36. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +59 -78
  37. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +40 -16
  38. lyrics_transcriber/frontend/src/components/WordEditControls.tsx +4 -10
  39. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +137 -68
  40. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +1 -1
  41. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +85 -115
  42. lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
  43. lyrics_transcriber/frontend/src/components/shared/types.ts +15 -7
  44. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +35 -0
  45. lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
  46. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +7 -7
  47. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +121 -0
  48. lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
  49. lyrics_transcriber/frontend/src/types.js +2 -0
  50. lyrics_transcriber/frontend/src/types.ts +70 -49
  51. lyrics_transcriber/frontend/src/validation.ts +132 -0
  52. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  53. lyrics_transcriber/frontend/yarn.lock +3752 -0
  54. lyrics_transcriber/lyrics/base_lyrics_provider.py +75 -12
  55. lyrics_transcriber/lyrics/file_provider.py +6 -5
  56. lyrics_transcriber/lyrics/genius.py +5 -2
  57. lyrics_transcriber/lyrics/spotify.py +58 -21
  58. lyrics_transcriber/output/ass/config.py +16 -5
  59. lyrics_transcriber/output/cdg.py +8 -8
  60. lyrics_transcriber/output/generator.py +29 -14
  61. lyrics_transcriber/output/plain_text.py +15 -10
  62. lyrics_transcriber/output/segment_resizer.py +16 -3
  63. lyrics_transcriber/output/subtitles.py +56 -2
  64. lyrics_transcriber/output/video.py +107 -1
  65. lyrics_transcriber/review/__init__.py +0 -1
  66. lyrics_transcriber/review/server.py +337 -164
  67. lyrics_transcriber/transcribers/audioshake.py +3 -0
  68. lyrics_transcriber/transcribers/base_transcriber.py +11 -3
  69. lyrics_transcriber/transcribers/whisper.py +11 -1
  70. lyrics_transcriber/types.py +151 -105
  71. lyrics_transcriber/utils/word_utils.py +27 -0
  72. {lyrics_transcriber-0.40.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/METADATA +3 -1
  73. {lyrics_transcriber-0.40.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/RECORD +76 -63
  74. {lyrics_transcriber-0.40.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/WHEEL +1 -1
  75. lyrics_transcriber/frontend/dist/assets/index-DKnNJHRK.js.map +0 -1
  76. lyrics_transcriber/frontend/package-lock.json +0 -4260
  77. lyrics_transcriber/frontend/src/components/shared/utils/initializeDataWithIds.tsx +0 -202
  78. {lyrics_transcriber-0.40.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/LICENSE +0 -0
  79. {lyrics_transcriber-0.40.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/entry_points.txt +0 -0
@@ -41,11 +41,14 @@ export default function TranscriptionView({
41
41
  onElementClick,
42
42
  onWordClick,
43
43
  flashingType,
44
+ flashingHandler,
44
45
  highlightInfo,
45
46
  mode,
46
47
  onPlaySegment,
47
- currentTime = 0
48
+ currentTime = 0,
49
+ anchors = []
48
50
  }: TranscriptionViewProps) {
51
+ console.log('TranscriptionView props:', { flashingType, flashingHandler });
49
52
  const [selectedSegmentIndex, setSelectedSegmentIndex] = useState<number | null>(null)
50
53
 
51
54
  return (
@@ -56,31 +59,49 @@ export default function TranscriptionView({
56
59
  <Box sx={{ display: 'flex', flexDirection: 'column' }}>
57
60
  {data.corrected_segments.map((segment, segmentIndex) => {
58
61
  const segmentWords: TranscriptionWordPosition[] = segment.words.map(word => {
62
+ // Find if this word is part of a correction
63
+ const correction = data.corrections?.find(c =>
64
+ c.corrected_word_id === word.id ||
65
+ c.word_id === word.id
66
+ )
67
+
68
+ // Find if this word is part of an anchor sequence
59
69
  const anchor = data.anchor_sequences?.find(a =>
60
- a?.word_ids?.includes(word.id)
70
+ a.transcribed_word_ids.includes(word.id)
61
71
  )
62
72
 
63
- // If not in an anchor, check if it belongs to a gap sequence
64
- const gap = !anchor ? data.gap_sequences?.find(g =>
65
- g?.word_ids?.includes(word.id)
66
- ) : undefined
73
+ // If not in anchor, check if it belongs to a gap sequence
74
+ const gap = data.gap_sequences?.find(g => {
75
+ // Check transcribed words
76
+ const inTranscribed = g.transcribed_word_ids.includes(word.id)
67
77
 
68
- // Check if this specific word has been corrected
69
- const isWordCorrected = gap?.corrections?.some(
70
- correction => correction.word_id === word.id
71
- )
78
+ // Check reference words
79
+ const inReference = Object.values(g.reference_word_ids).some(ids =>
80
+ ids.includes(word.id)
81
+ )
82
+
83
+ // Check if this word is a corrected version
84
+ const isCorrection = data.corrections.some(c =>
85
+ (c.corrected_word_id === word.id || c.word_id === word.id) &&
86
+ g.transcribed_word_ids.includes(c.word_id)
87
+ )
88
+
89
+ return inTranscribed || inReference || isCorrection
90
+ })
72
91
 
73
92
  return {
74
93
  word: {
75
94
  id: word.id,
76
95
  text: word.text,
77
- start_time: word.start_time,
78
- end_time: word.end_time
96
+ start_time: word.start_time ?? undefined,
97
+ end_time: word.end_time ?? undefined
79
98
  },
80
99
  type: anchor ? 'anchor' : gap ? 'gap' : 'other',
81
100
  sequence: anchor || gap,
101
+ sequencePosition: anchor?.transcription_position ?? gap?.transcription_position ?? undefined,
82
102
  isInRange: true,
83
- isCorrected: isWordCorrected
103
+ isCorrected: Boolean(correction),
104
+ gap: gap
84
105
  }
85
106
  })
86
107
 
@@ -93,10 +114,10 @@ export default function TranscriptionView({
93
114
  >
94
115
  {segmentIndex}
95
116
  </SegmentIndex>
96
- {segment.start_time !== undefined && (
117
+ {segment.start_time !== null && (
97
118
  <IconButton
98
119
  size="small"
99
- onClick={() => onPlaySegment?.(segment.start_time)}
120
+ onClick={() => onPlaySegment?.(segment.start_time!)}
100
121
  sx={{ padding: '2px' }}
101
122
  >
102
123
  <PlayCircleOutlineIcon fontSize="small" />
@@ -106,14 +127,17 @@ export default function TranscriptionView({
106
127
  <TextContainer>
107
128
  <HighlightedText
108
129
  wordPositions={segmentWords}
109
- anchors={data.anchor_sequences}
130
+ anchors={anchors}
110
131
  onElementClick={onElementClick}
111
132
  onWordClick={onWordClick}
112
133
  flashingType={flashingType}
134
+ flashingHandler={flashingHandler}
113
135
  highlightInfo={highlightInfo}
114
136
  mode={mode}
115
137
  preserveSegments={true}
116
138
  currentTime={currentTime}
139
+ gaps={data.gap_sequences}
140
+ corrections={data.corrections}
117
141
  />
118
142
  </TextContainer>
119
143
  </Box>
@@ -15,7 +15,7 @@ export function useWordEdit(content: ModalContent | null) {
15
15
 
16
16
  useEffect(() => {
17
17
  if (content) {
18
- setEditedWord(content.type === 'gap' ? content.data.word : content.type === 'anchor' ? content.data.words[0] : '')
18
+ setEditedWord(content.data.word || '')
19
19
  setIsEditing(false)
20
20
  }
21
21
  }, [content])
@@ -37,11 +37,7 @@ export default function WordEditControls({ content, onUpdateCorrection, onClose
37
37
  } = useWordEdit(content)
38
38
 
39
39
  const handleStartEdit = () => {
40
- if (content.type === 'gap') {
41
- setEditedWord(content.data.word)
42
- } else if (content.type === 'anchor') {
43
- setEditedWord(content.data.words[0])
44
- }
40
+ setEditedWord(content.data.word || '')
45
41
  setIsEditing(true)
46
42
  }
47
43
 
@@ -59,10 +55,8 @@ export default function WordEditControls({ content, onUpdateCorrection, onClose
59
55
  }
60
56
 
61
57
  const handleCancelEdit = () => {
62
- if (content.type === 'gap') {
63
- setEditedWord(content.data.word)
64
- setIsEditing(false)
65
- }
58
+ setEditedWord(content.data.word || '')
59
+ setIsEditing(false)
66
60
  }
67
61
 
68
62
  const handleWordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -1,25 +1,32 @@
1
1
  import { Typography, Box } from '@mui/material'
2
- import { Word } from './Word'
2
+ import { WordComponent } from './Word'
3
3
  import { useWordClick } from '../hooks/useWordClick'
4
- import { AnchorSequence, GapSequence, HighlightInfo, InteractionMode } from '../../../types'
4
+ import {
5
+ AnchorSequence,
6
+ GapSequence,
7
+ HighlightInfo,
8
+ InteractionMode,
9
+ LyricsSegment,
10
+ Word,
11
+ WordCorrection
12
+ } from '../../../types'
5
13
  import { ModalContent } from '../../LyricsAnalyzer'
6
- import { WordClickInfo, TranscriptionWordPosition, FlashType, LinePosition } from '../types'
14
+ import type { FlashType, LinePosition, TranscriptionWordPosition, WordClickInfo } from '../types'
7
15
  import React from 'react'
8
- import ContentCopyIcon from '@mui/icons-material/ContentCopy';
9
- import IconButton from '@mui/material/IconButton';
16
+ import ContentCopyIcon from '@mui/icons-material/ContentCopy'
17
+ import IconButton from '@mui/material/IconButton'
18
+ import { getWordsFromIds } from '../utils/wordUtils'
10
19
 
11
20
  export interface HighlightedTextProps {
12
- // Input can be either raw text or pre-processed word positions
13
21
  text?: string
14
- wordPositions?: TranscriptionWordPosition[]
15
- // Common props
22
+ segments?: LyricsSegment[]
23
+ wordPositions: TranscriptionWordPosition[]
16
24
  anchors: AnchorSequence[]
17
25
  highlightInfo: HighlightInfo | null
18
26
  mode: InteractionMode
19
27
  onElementClick: (content: ModalContent) => void
20
28
  onWordClick?: (info: WordClickInfo) => void
21
29
  flashingType: FlashType
22
- // Reference-specific props
23
30
  isReference?: boolean
24
31
  currentSource?: string
25
32
  preserveSegments?: boolean
@@ -27,11 +34,14 @@ export interface HighlightedTextProps {
27
34
  currentTime?: number
28
35
  referenceCorrections?: Map<string, string>
29
36
  gaps?: GapSequence[]
37
+ flashingHandler?: string | null
38
+ corrections?: WordCorrection[]
30
39
  }
31
40
 
32
41
  export function HighlightedText({
33
42
  text,
34
- wordPositions,
43
+ segments,
44
+ wordPositions = [] as TranscriptionWordPosition[],
35
45
  anchors,
36
46
  highlightInfo,
37
47
  mode,
@@ -39,87 +49,109 @@ export function HighlightedText({
39
49
  onWordClick,
40
50
  flashingType,
41
51
  isReference,
42
- currentSource,
52
+ currentSource = '',
43
53
  preserveSegments = false,
44
54
  linePositions = [],
45
55
  currentTime = 0,
46
56
  referenceCorrections = new Map(),
47
- gaps = []
57
+ gaps = [],
58
+ flashingHandler,
59
+ corrections = [],
48
60
  }: HighlightedTextProps) {
61
+ console.log('HighlightedText props:', { flashingType, flashingHandler });
62
+
49
63
  const { handleWordClick } = useWordClick({
50
64
  mode,
51
65
  onElementClick,
52
66
  onWordClick,
53
67
  isReference,
54
68
  currentSource,
55
- gaps
69
+ gaps,
70
+ anchors,
71
+ corrections
56
72
  })
57
73
 
58
74
  const shouldWordFlash = (wordPos: TranscriptionWordPosition | { word: string; id: string }): boolean => {
59
- if (!flashingType) return false
75
+ if (!flashingType) {
76
+ console.log('No flashingType');
77
+ return false;
78
+ }
60
79
 
61
80
  if ('type' in wordPos) {
62
- // Handle TranscriptionWordPosition
81
+ // Add handler-specific flashing
82
+ if (flashingType === 'handler' && flashingHandler) {
83
+ console.log('Checking handler flash for word:', wordPos.word.text);
84
+ console.log('Current flashingHandler:', flashingHandler);
85
+ console.log('Word ID:', wordPos.word.id);
86
+
87
+ const shouldFlash = corrections.some(correction =>
88
+ correction.handler === flashingHandler &&
89
+ (correction.corrected_word_id === wordPos.word.id ||
90
+ correction.word_id === wordPos.word.id)
91
+ );
92
+
93
+ console.log('Should flash:', shouldFlash);
94
+ return shouldFlash;
95
+ }
96
+
63
97
  const gap = wordPos.sequence as GapSequence
64
- const isCorrected = wordPos.type === 'gap' &&
65
- gap?.corrections?.some(correction =>
66
- correction.word_id === wordPos.word.id
67
- )
98
+ const isCorrected = (
99
+ // Check corrections array for this word
100
+ corrections.some(correction =>
101
+ (correction.word_id === wordPos.word.id ||
102
+ correction.corrected_word_id === wordPos.word.id) &&
103
+ gap.transcribed_word_ids.includes(correction.word_id)
104
+ ) ||
105
+ // Also check if marked as corrected in wordPos
106
+ wordPos.isCorrected
107
+ )
68
108
 
69
109
  return Boolean(
70
110
  (flashingType === 'anchor' && wordPos.type === 'anchor') ||
71
111
  (flashingType === 'corrected' && isCorrected) ||
72
112
  (flashingType === 'uncorrected' && wordPos.type === 'gap' && !isCorrected) ||
73
113
  (flashingType === 'word' && (
74
- // Handle anchor highlighting
114
+ // For anchors
75
115
  (highlightInfo?.type === 'anchor' && wordPos.type === 'anchor' &&
76
- wordPos.sequence && highlightInfo.word_ids?.includes(wordPos.word.id)) ||
77
- // Handle gap highlighting - only highlight the specific word
116
+ (isReference && currentSource && highlightInfo.sequence
117
+ ? getWordsFromIds(segments || [],
118
+ (highlightInfo.sequence as AnchorSequence).reference_word_ids[currentSource] || []
119
+ ).some(w => w.id === wordPos.word.id)
120
+ : getWordsFromIds(segments || [],
121
+ (highlightInfo.sequence as AnchorSequence).transcribed_word_ids
122
+ ).some(w => w.id === wordPos.word.id)
123
+ )) ||
124
+ // For gaps
78
125
  (highlightInfo?.type === 'gap' && wordPos.type === 'gap' &&
79
- highlightInfo.word_ids?.includes(wordPos.word.id))
80
- ))
81
- )
82
- } else {
83
- // Handle reference word
84
- if (!currentSource) return false
85
-
86
- const anchor = anchors?.find(a =>
87
- a?.reference_word_ids?.[currentSource]?.includes(wordPos.id)
88
- )
89
-
90
- // Check if this word should flash as part of a gap
91
- const shouldFlashGap = flashingType === 'word' &&
92
- highlightInfo?.type === 'gap' &&
93
- // Check if this reference word corresponds to the clicked word
94
- gaps?.some(gap =>
95
- gap.corrections.some(correction => {
96
- // Only flash if this correction corresponds to the clicked word
97
- if (!highlightInfo.word_ids?.includes(correction.word_id)) {
98
- return false;
99
- }
100
-
101
- const refPosition = correction.reference_positions?.[currentSource];
102
- const wordPosition = parseInt(wordPos.id.split('-').pop() || '', 10);
103
- return typeof refPosition === 'number' && refPosition === wordPosition;
104
- })
105
- )
106
-
107
- return Boolean(
108
- (flashingType === 'anchor' && anchor) ||
109
- (flashingType === 'word' && (
110
- // Handle anchor highlighting
111
- (highlightInfo?.type === 'anchor' &&
112
- highlightInfo.reference_word_ids?.[currentSource]?.includes(wordPos.id)) ||
113
- // Handle gap highlighting
114
- shouldFlashGap
126
+ (isReference && currentSource && highlightInfo.sequence
127
+ ? getWordsFromIds(segments || [],
128
+ (highlightInfo.sequence as GapSequence).reference_word_ids[currentSource] || []
129
+ ).some(w => w.id === wordPos.word.id)
130
+ : getWordsFromIds(segments || [],
131
+ (highlightInfo.sequence as GapSequence).transcribed_word_ids
132
+ ).some(w => w.id === wordPos.word.id))
133
+ ) ||
134
+ // For corrections
135
+ (highlightInfo?.type === 'correction' && isReference && currentSource &&
136
+ highlightInfo.correction?.reference_positions?.[currentSource]?.toString() === wordPos.word.id)
115
137
  ))
116
138
  )
117
139
  }
140
+ return false
118
141
  }
119
142
 
120
- const shouldHighlightWord = (wordPos: TranscriptionWordPosition): boolean => {
121
- if (!currentTime || !wordPos.word.start_time || !wordPos.word.end_time) return false
122
- return currentTime >= wordPos.word.start_time && currentTime <= wordPos.word.end_time
143
+ const shouldHighlightWord = (wordPos: TranscriptionWordPosition | { word: string; id: string }): boolean => {
144
+ // Don't highlight words in reference view
145
+ if (isReference) return false
146
+
147
+ if ('type' in wordPos && currentTime !== undefined && 'start_time' in wordPos.word) {
148
+ const word = wordPos.word as Word
149
+ return word.start_time !== null &&
150
+ word.end_time !== null &&
151
+ currentTime >= word.start_time &&
152
+ currentTime <= word.end_time
153
+ }
154
+ return false
123
155
  }
124
156
 
125
157
  const handleCopyLine = (text: string) => {
@@ -127,16 +159,17 @@ export function HighlightedText({
127
159
  };
128
160
 
129
161
  const renderContent = () => {
130
- if (wordPositions) {
162
+ if (wordPositions && !segments) {
131
163
  return wordPositions.map((wordPos, index) => (
132
164
  <React.Fragment key={wordPos.word.id}>
133
- <Word
165
+ <WordComponent
166
+ key={`${wordPos.word.id}-${index}`}
134
167
  word={wordPos.word.text}
135
168
  shouldFlash={shouldWordFlash(wordPos)}
136
- isCurrentlyPlaying={shouldHighlightWord(wordPos)}
137
169
  isAnchor={wordPos.type === 'anchor'}
138
- isCorrectedGap={wordPos.type === 'gap' && wordPos.isCorrected}
170
+ isCorrectedGap={wordPos.isCorrected}
139
171
  isUncorrectedGap={wordPos.type === 'gap' && !wordPos.isCorrected}
172
+ isCurrentlyPlaying={shouldHighlightWord(wordPos)}
140
173
  onClick={() => handleWordClick(
141
174
  wordPos.word.text,
142
175
  wordPos.word.id,
@@ -147,6 +180,42 @@ export function HighlightedText({
147
180
  {index < wordPositions.length - 1 && ' '}
148
181
  </React.Fragment>
149
182
  ))
183
+ } else if (segments) {
184
+ return segments.map((segment) => (
185
+ <Box key={segment.id} sx={{ display: 'flex', alignItems: 'flex-start' }}>
186
+ <Box sx={{ flex: 1 }}>
187
+ {segment.words.map((word, wordIndex) => {
188
+ const wordPos = wordPositions.find((pos: TranscriptionWordPosition) =>
189
+ pos.word.id === word.id
190
+ );
191
+
192
+ const anchor = wordPos?.type === 'anchor' ? anchors?.find(a =>
193
+ (a.reference_word_ids[currentSource] || []).includes(word.id)
194
+ ) : undefined;
195
+
196
+ const hasCorrection = referenceCorrections.has(word.id);
197
+ const isUncorrectedGap = wordPos?.type === 'gap' && !hasCorrection;
198
+
199
+ const sequence = wordPos?.type === 'gap' ? wordPos.sequence as GapSequence : undefined;
200
+
201
+ return (
202
+ <React.Fragment key={word.id}>
203
+ <WordComponent
204
+ word={word.text}
205
+ shouldFlash={shouldWordFlash(wordPos || { word: word.text, id: word.id })}
206
+ isAnchor={Boolean(anchor)}
207
+ isCorrectedGap={hasCorrection}
208
+ isUncorrectedGap={isUncorrectedGap}
209
+ isCurrentlyPlaying={shouldHighlightWord(wordPos || { word: word.text, id: word.id })}
210
+ onClick={() => handleWordClick(word.text, word.id, anchor, sequence)}
211
+ />
212
+ {wordIndex < segment.words.length - 1 && ' '}
213
+ </React.Fragment>
214
+ );
215
+ })}
216
+ </Box>
217
+ </Box>
218
+ ));
150
219
  } else if (text) {
151
220
  const lines = text.split('\n')
152
221
  let wordCount = 0
@@ -219,20 +288,20 @@ export function HighlightedText({
219
288
  wordCount++
220
289
 
221
290
  const anchor = currentSource ? anchors?.find(a =>
222
- a?.reference_word_ids?.[currentSource]?.includes(wordId)
291
+ a.reference_word_ids[currentSource]?.includes(wordId)
223
292
  ) : undefined
224
293
 
225
- // Check if this word has a correction
226
294
  const hasCorrection = referenceCorrections.has(wordId)
227
295
 
228
296
  return (
229
- <Word
297
+ <WordComponent
230
298
  key={wordId}
231
299
  word={word}
232
300
  shouldFlash={shouldWordFlash({ word, id: wordId })}
233
301
  isAnchor={Boolean(anchor)}
234
302
  isCorrectedGap={hasCorrection}
235
303
  isUncorrectedGap={false}
304
+ isCurrentlyPlaying={shouldHighlightWord({ word, id: wordId })}
236
305
  onClick={() => handleWordClick(word, wordId, anchor, undefined)}
237
306
  />
238
307
  )
@@ -3,7 +3,7 @@ import { COLORS } from '../constants'
3
3
  import { HighlightedWord } from '../styles'
4
4
  import { WordProps } from '../types'
5
5
 
6
- export const Word = React.memo(function Word({
6
+ export const WordComponent = React.memo(function Word({
7
7
  word,
8
8
  shouldFlash,
9
9
  isAnchor,