lyrics-transcriber 0.41.0__py3-none-any.whl → 0.43.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 (78) hide show
  1. lyrics_transcriber/core/controller.py +30 -52
  2. lyrics_transcriber/correction/anchor_sequence.py +325 -150
  3. lyrics_transcriber/correction/corrector.py +224 -107
  4. lyrics_transcriber/correction/handlers/base.py +28 -10
  5. lyrics_transcriber/correction/handlers/extend_anchor.py +47 -24
  6. lyrics_transcriber/correction/handlers/levenshtein.py +75 -33
  7. lyrics_transcriber/correction/handlers/llm.py +290 -0
  8. lyrics_transcriber/correction/handlers/no_space_punct_match.py +81 -36
  9. lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +46 -26
  10. lyrics_transcriber/correction/handlers/repeat.py +28 -11
  11. lyrics_transcriber/correction/handlers/sound_alike.py +68 -32
  12. lyrics_transcriber/correction/handlers/syllables_match.py +80 -30
  13. lyrics_transcriber/correction/handlers/word_count_match.py +36 -19
  14. lyrics_transcriber/correction/handlers/word_operations.py +68 -22
  15. lyrics_transcriber/correction/text_utils.py +3 -7
  16. lyrics_transcriber/frontend/.yarn/install-state.gz +0 -0
  17. lyrics_transcriber/frontend/.yarn/releases/yarn-4.6.0.cjs +934 -0
  18. lyrics_transcriber/frontend/.yarnrc.yml +3 -0
  19. lyrics_transcriber/frontend/dist/assets/{index-DKnNJHRK.js → index-D0Gr3Ep7.js} +16509 -9038
  20. lyrics_transcriber/frontend/dist/assets/index-D0Gr3Ep7.js.map +1 -0
  21. lyrics_transcriber/frontend/dist/index.html +1 -1
  22. lyrics_transcriber/frontend/package.json +6 -2
  23. lyrics_transcriber/frontend/src/App.tsx +18 -2
  24. lyrics_transcriber/frontend/src/api.ts +103 -6
  25. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +14 -6
  26. lyrics_transcriber/frontend/src/components/DetailsModal.tsx +86 -59
  27. lyrics_transcriber/frontend/src/components/EditModal.tsx +281 -63
  28. lyrics_transcriber/frontend/src/components/FileUpload.tsx +2 -2
  29. lyrics_transcriber/frontend/src/components/Header.tsx +249 -0
  30. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +320 -266
  31. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +120 -0
  32. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +174 -52
  33. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +158 -114
  34. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +59 -78
  35. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +39 -16
  36. lyrics_transcriber/frontend/src/components/WordEditControls.tsx +4 -10
  37. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +134 -68
  38. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +1 -1
  39. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +85 -115
  40. lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
  41. lyrics_transcriber/frontend/src/components/shared/types.ts +15 -7
  42. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +67 -0
  43. lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
  44. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +7 -7
  45. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +121 -0
  46. lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
  47. lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
  48. lyrics_transcriber/frontend/src/types.js +2 -0
  49. lyrics_transcriber/frontend/src/types.ts +70 -49
  50. lyrics_transcriber/frontend/src/validation.ts +132 -0
  51. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  52. lyrics_transcriber/frontend/yarn.lock +3752 -0
  53. lyrics_transcriber/lyrics/base_lyrics_provider.py +75 -12
  54. lyrics_transcriber/lyrics/file_provider.py +6 -5
  55. lyrics_transcriber/lyrics/genius.py +5 -2
  56. lyrics_transcriber/lyrics/spotify.py +58 -21
  57. lyrics_transcriber/output/ass/config.py +16 -5
  58. lyrics_transcriber/output/cdg.py +1 -1
  59. lyrics_transcriber/output/generator.py +22 -8
  60. lyrics_transcriber/output/plain_text.py +15 -10
  61. lyrics_transcriber/output/segment_resizer.py +16 -3
  62. lyrics_transcriber/output/subtitles.py +27 -1
  63. lyrics_transcriber/output/video.py +107 -1
  64. lyrics_transcriber/review/__init__.py +0 -1
  65. lyrics_transcriber/review/server.py +337 -164
  66. lyrics_transcriber/transcribers/audioshake.py +3 -0
  67. lyrics_transcriber/transcribers/base_transcriber.py +11 -3
  68. lyrics_transcriber/transcribers/whisper.py +11 -1
  69. lyrics_transcriber/types.py +151 -105
  70. lyrics_transcriber/utils/word_utils.py +27 -0
  71. {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.43.0.dist-info}/METADATA +3 -1
  72. {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.43.0.dist-info}/RECORD +75 -61
  73. {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.43.0.dist-info}/WHEEL +1 -1
  74. lyrics_transcriber/frontend/dist/assets/index-DKnNJHRK.js.map +0 -1
  75. lyrics_transcriber/frontend/package-lock.json +0 -4260
  76. lyrics_transcriber/frontend/src/components/shared/utils/initializeDataWithIds.tsx +0 -202
  77. {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.43.0.dist-info}/LICENSE +0 -0
  78. {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.43.0.dist-info}/entry_points.txt +0 -0
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Lyrics Transcriber Analyzer</title>
8
- <script type="module" crossorigin src="/assets/index-DKnNJHRK.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-D0Gr3Ep7.js"></script>
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -18,8 +18,11 @@
18
18
  "@emotion/styled": "^11.14.0",
19
19
  "@mui/icons-material": "^6.3.0",
20
20
  "@mui/material": "^6.3.0",
21
+ "@mui/system": "^6.4.3",
22
+ "nanoid": "^5.0.9",
21
23
  "react": "^18.3.1",
22
- "react-dom": "^18.3.1"
24
+ "react-dom": "^18.3.1",
25
+ "zod": "^3.24.1"
23
26
  },
24
27
  "devDependencies": {
25
28
  "@eslint/js": "^9.17.0",
@@ -34,5 +37,6 @@
34
37
  "typescript": "~5.6.2",
35
38
  "typescript-eslint": "^8.18.2",
36
39
  "vite": "^6.0.5"
37
- }
40
+ },
41
+ "packageManager": "yarn@4.6.0"
38
42
  }
@@ -12,16 +12,21 @@ export default function App() {
12
12
  const [error, setError] = useState<string | null>(null)
13
13
  const [apiClient, setApiClient] = useState<ApiClient | null>(null)
14
14
  const [isReadOnly, setIsReadOnly] = useState(true)
15
+ const [audioHash, setAudioHash] = useState<string>('')
15
16
 
16
17
  useEffect(() => {
17
18
  // Parse query parameters
18
19
  const params = new URLSearchParams(window.location.search)
19
20
  const encodedApiUrl = params.get('baseApiUrl')
21
+ const audioHashParam = params.get('audioHash')
20
22
 
21
23
  if (encodedApiUrl) {
22
24
  const baseApiUrl = decodeURIComponent(encodedApiUrl)
23
25
  setApiClient(new LiveApiClient(baseApiUrl))
24
26
  setIsReadOnly(false)
27
+ if (audioHashParam) {
28
+ setAudioHash(audioHashParam)
29
+ }
25
30
  // Fetch initial data
26
31
  fetchData(baseApiUrl)
27
32
  } else {
@@ -34,6 +39,7 @@ export default function App() {
34
39
  try {
35
40
  const client = new LiveApiClient(baseUrl)
36
41
  const data = await client.getCorrectionData()
42
+ console.log('Full correction data from API:', data)
37
43
  setData(data)
38
44
  } catch (err) {
39
45
  const error = err as Error
@@ -52,9 +58,18 @@ export default function App() {
52
58
 
53
59
  try {
54
60
  const text = await file.text()
55
- console.log('File contents:', text.slice(0, 500) + '...') // Show first 500 chars
56
61
  const parsedData = JSON.parse(text) as CorrectionData
57
- console.log('Parsed file data:', parsedData)
62
+ console.log('File data loaded:', {
63
+ sampleGap: parsedData.gap_sequences?.[0],
64
+ sampleWord: parsedData.corrected_segments?.[0]?.words?.[0],
65
+ sampleCorrection: parsedData.corrections?.[0]
66
+ })
67
+
68
+ // Validate the structure
69
+ if (!parsedData.corrected_segments || !parsedData.gap_sequences) {
70
+ throw new Error('Invalid file format: missing required fields')
71
+ }
72
+
58
73
  setData(parsedData)
59
74
  } catch (err) {
60
75
  const error = err as Error
@@ -189,6 +204,7 @@ export default function App() {
189
204
  onShowMetadata={() => setShowMetadata(true)}
190
205
  apiClient={apiClient}
191
206
  isReadOnly={isReadOnly}
207
+ audioHash={audioHash}
192
208
  />
193
209
  {renderMetadataModal()}
194
210
  </Box>
@@ -1,10 +1,15 @@
1
1
  import { CorrectionData } from './types';
2
+ import { validateCorrectionData } from './validation';
2
3
 
3
4
  // New file to handle API communication
4
5
  export interface ApiClient {
5
6
  getCorrectionData: () => Promise<CorrectionData>;
6
7
  submitCorrections: (data: CorrectionData) => Promise<void>;
7
- getAudioUrl: () => string;
8
+ getAudioUrl: (audioHash: string) => string;
9
+ generatePreviewVideo: (data: CorrectionData) => Promise<PreviewVideoResponse>;
10
+ getPreviewVideoUrl: (previewHash: string) => string;
11
+ updateHandlers: (enabledHandlers: string[]) => Promise<CorrectionData>;
12
+ isUpdatingHandlers?: boolean;
8
13
  }
9
14
 
10
15
  // Add new interface for the minimal update payload
@@ -13,18 +18,34 @@ interface CorrectionUpdate {
13
18
  corrected_segments: CorrectionData['corrected_segments'];
14
19
  }
15
20
 
21
+ // Add new interface for preview response
22
+ interface PreviewVideoResponse {
23
+ status: "success" | "error";
24
+ preview_hash?: string;
25
+ message?: string;
26
+ }
27
+
16
28
  export class LiveApiClient implements ApiClient {
17
29
  constructor(private baseUrl: string) {
18
30
  this.baseUrl = baseUrl.replace(/\/$/, '')
19
31
  }
20
32
 
33
+ public isUpdatingHandlers = false;
34
+
21
35
  async getCorrectionData(): Promise<CorrectionData> {
22
36
  const response = await fetch(`${this.baseUrl}/correction-data`);
23
37
  if (!response.ok) {
24
38
  throw new Error(`API error: ${response.statusText}`);
25
39
  }
26
- const data = await response.json();
27
- return data;
40
+ const rawData = await response.json();
41
+
42
+ try {
43
+ // This will throw if validation fails
44
+ return validateCorrectionData(rawData);
45
+ } catch (error) {
46
+ console.error('Data validation failed:', error);
47
+ throw new Error('Invalid data received from server: missing or incorrect fields');
48
+ }
28
49
  }
29
50
 
30
51
  async submitCorrections(data: CorrectionData): Promise<void> {
@@ -47,8 +68,68 @@ export class LiveApiClient implements ApiClient {
47
68
  }
48
69
  }
49
70
 
50
- getAudioUrl(): string {
51
- return `${this.baseUrl}/audio`
71
+ getAudioUrl(audioHash: string): string {
72
+ return `${this.baseUrl}/audio/${audioHash}`
73
+ }
74
+
75
+ async generatePreviewVideo(data: CorrectionData): Promise<PreviewVideoResponse> {
76
+ // Extract only the needed fields, just like in submitCorrections
77
+ const updatePayload: CorrectionUpdate = {
78
+ corrections: data.corrections,
79
+ corrected_segments: data.corrected_segments
80
+ };
81
+
82
+ const response = await fetch(`${this.baseUrl}/preview-video`, {
83
+ method: 'POST',
84
+ headers: {
85
+ 'Content-Type': 'application/json',
86
+ },
87
+ body: JSON.stringify(updatePayload)
88
+ });
89
+
90
+ if (!response.ok) {
91
+ return {
92
+ status: 'error',
93
+ message: `API error: ${response.statusText}`
94
+ };
95
+ }
96
+
97
+ return await response.json();
98
+ }
99
+
100
+ getPreviewVideoUrl(previewHash: string): string {
101
+ return `${this.baseUrl}/preview-video/${previewHash}`;
102
+ }
103
+
104
+ async updateHandlers(enabledHandlers: string[]): Promise<CorrectionData> {
105
+ console.log('API: Starting handler update...');
106
+ this.isUpdatingHandlers = true;
107
+ console.log('API: Set isUpdatingHandlers to', this.isUpdatingHandlers);
108
+
109
+ try {
110
+ const response = await fetch(`${this.baseUrl}/handlers`, {
111
+ method: 'POST',
112
+ headers: {
113
+ 'Content-Type': 'application/json',
114
+ },
115
+ body: JSON.stringify(enabledHandlers)
116
+ });
117
+
118
+ if (!response.ok) {
119
+ throw new Error(`API error: ${response.statusText}`);
120
+ }
121
+
122
+ const data = await response.json();
123
+ if (data.status === 'error') {
124
+ throw new Error(data.message || 'Failed to update handlers');
125
+ }
126
+
127
+ console.log('API: Handler update successful');
128
+ return validateCorrectionData(data.data);
129
+ } finally {
130
+ this.isUpdatingHandlers = false;
131
+ console.log('API: Set isUpdatingHandlers to', this.isUpdatingHandlers);
132
+ }
52
133
  }
53
134
  }
54
135
 
@@ -62,7 +143,23 @@ export class FileOnlyClient implements ApiClient {
62
143
  throw new Error('Not supported in file-only mode');
63
144
  }
64
145
 
65
- getAudioUrl(): string {
146
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
147
+ getAudioUrl(_audioHash: string): string {
148
+ throw new Error('Not supported in file-only mode');
149
+ }
150
+
151
+ async generatePreviewVideo(): Promise<PreviewVideoResponse> {
152
+ throw new Error('Not supported in file-only mode');
153
+ }
154
+
155
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
156
+ getPreviewVideoUrl(_previewHash: string): string {
157
+ throw new Error('Not supported in file-only mode');
158
+ }
159
+
160
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
161
+ async updateHandlers(_enabledHandlers: string[]): Promise<CorrectionData> {
66
162
  throw new Error('Not supported in file-only mode');
67
163
  }
68
164
  }
165
+
@@ -6,10 +6,11 @@ import { ApiClient } from '../api'
6
6
 
7
7
  interface AudioPlayerProps {
8
8
  apiClient: ApiClient | null,
9
- onTimeUpdate?: (time: number) => void
9
+ onTimeUpdate?: (time: number) => void,
10
+ audioHash: string
10
11
  }
11
12
 
12
- export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProps) {
13
+ export default function AudioPlayer({ apiClient, onTimeUpdate, audioHash }: AudioPlayerProps) {
13
14
  const [isPlaying, setIsPlaying] = useState(false)
14
15
  const [currentTime, setCurrentTime] = useState(0)
15
16
  const [duration, setDuration] = useState(0)
@@ -18,7 +19,7 @@ export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProp
18
19
  useEffect(() => {
19
20
  if (!apiClient) return
20
21
 
21
- const audio = new Audio(apiClient.getAudioUrl())
22
+ const audio = new Audio(apiClient.getAudioUrl(audioHash))
22
23
  audioRef.current = audio
23
24
 
24
25
  // Add requestAnimationFrame for smoother updates
@@ -32,16 +33,21 @@ export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProp
32
33
  }
33
34
 
34
35
  audio.addEventListener('play', () => {
36
+ setIsPlaying(true)
37
+ window.isAudioPlaying = true
35
38
  updateTime()
36
39
  })
37
40
 
38
41
  audio.addEventListener('pause', () => {
42
+ setIsPlaying(false)
43
+ window.isAudioPlaying = false
39
44
  cancelAnimationFrame(animationFrameId)
40
45
  })
41
46
 
42
47
  audio.addEventListener('ended', () => {
43
48
  cancelAnimationFrame(animationFrameId)
44
49
  setIsPlaying(false)
50
+ window.isAudioPlaying = false
45
51
  setCurrentTime(0)
46
52
  })
47
53
 
@@ -54,8 +60,9 @@ export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProp
54
60
  audio.pause()
55
61
  audio.src = ''
56
62
  audioRef.current = null
63
+ window.isAudioPlaying = false
57
64
  }
58
- }, [apiClient, onTimeUpdate])
65
+ }, [apiClient, onTimeUpdate, audioHash])
59
66
 
60
67
  const handlePlayPause = () => {
61
68
  if (!audioRef.current) return
@@ -106,6 +113,7 @@ export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProp
106
113
  useEffect(() => {
107
114
  if (!apiClient) return
108
115
 
116
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
109
117
  const win = window as any
110
118
  win.seekAndPlayAudio = seekAndPlay
111
119
  win.toggleAudioPlayback = togglePlayback
@@ -130,7 +138,7 @@ export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProp
130
138
  <Typography variant="body2" color="text.secondary" sx={{ mr: 1 }}>
131
139
  Playback:
132
140
  </Typography>
133
-
141
+
134
142
  <IconButton
135
143
  onClick={handlePlayPause}
136
144
  size="small"
@@ -148,7 +156,7 @@ export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProp
148
156
  max={duration}
149
157
  onChange={handleSeek}
150
158
  size="small"
151
- sx={{
159
+ sx={{
152
160
  width: 200,
153
161
  mx: 1,
154
162
  '& .MuiSlider-thumb': {
@@ -9,32 +9,78 @@ import {
9
9
  } from '@mui/material'
10
10
  import CloseIcon from '@mui/icons-material/Close'
11
11
  import { ModalContent } from './LyricsAnalyzer'
12
+ import { WordCorrection, ReferenceSource, AnchorSequence } from '../types'
13
+ import { getWordsFromIds } from './shared/utils/wordUtils'
12
14
 
13
15
  interface DetailsModalProps {
14
16
  open: boolean
15
17
  content: ModalContent | null
16
18
  onClose: () => void
19
+ allCorrections: WordCorrection[]
20
+ referenceLyrics?: Record<string, ReferenceSource>
21
+ }
22
+
23
+ function formatReferenceWords(content: ModalContent | null, referenceLyrics?: Record<string, ReferenceSource>): string {
24
+ if (!content || !referenceLyrics) return ''
25
+
26
+ return Object.entries(content.data.reference_word_ids)
27
+ .map(([source, wordIds]) => {
28
+ const words = getWordsFromIds(
29
+ referenceLyrics[source]?.segments ?? [],
30
+ wordIds
31
+ )
32
+ return `${source}: "${words.map(w => w.text).join(' ')}"`
33
+ })
34
+ .join('\n')
35
+ }
36
+
37
+ function getAnchorText(anchorId: string | null, anchors: AnchorSequence[], referenceLyrics?: Record<string, ReferenceSource>): string {
38
+ if (!anchorId || !referenceLyrics) return anchorId || ''
39
+
40
+ const anchor = anchors.find(a => a.id === anchorId)
41
+ if (!anchor) return anchorId
42
+
43
+ // Get the first source's words as representative text
44
+ const firstSource = Object.entries(anchor.reference_word_ids)[0]
45
+ if (!firstSource) return anchorId
46
+
47
+ const [source, wordIds] = firstSource
48
+ const words = getWordsFromIds(
49
+ referenceLyrics[source]?.segments ?? [],
50
+ wordIds
51
+ )
52
+ return `${anchorId} ("${words.map(w => w.text).join(' ')}")`
17
53
  }
18
54
 
19
55
  export default function DetailsModal({
20
56
  open,
21
57
  content,
22
58
  onClose,
59
+ allCorrections,
60
+ referenceLyrics
23
61
  }: DetailsModalProps) {
24
62
  if (!content) return null
25
63
 
64
+ const referenceWordsText = formatReferenceWords(content, referenceLyrics)
65
+ const relevantCorrections = content.type === 'gap' ? allCorrections.filter(c =>
66
+ c.word_id === content.data.wordId ||
67
+ c.corrected_word_id === content.data.wordId ||
68
+ content.data.transcribed_word_ids.includes(c.word_id)
69
+ ) : []
70
+ const isCorrected = content.type === 'gap' && relevantCorrections.length > 0
71
+
26
72
  const getCurrentWord = () => {
27
73
  if (content.type === 'gap') {
28
74
  return content.data.word
29
75
  } else if (content.type === 'anchor') {
30
- return content.data.word ?? content.data.words[0]
76
+ return content.data.word ?? ''
31
77
  }
32
78
  return ''
33
79
  }
34
80
 
35
- const isCorrected = content.type === 'gap' && content.data.corrections?.length > 0
36
-
37
81
  const renderContent = () => {
82
+ const anchorWords = content.type === 'anchor' ? content.data.transcribed_word_ids?.length ?? 0 : 0
83
+
38
84
  switch (content.type) {
39
85
  case 'anchor':
40
86
  return (
@@ -45,16 +91,17 @@ export default function DetailsModal({
45
91
  />
46
92
  <GridItem
47
93
  title="Full Text"
48
- value={`"${content.data.text}"`}
94
+ value={`"${content.data.word ?? ''}"`}
49
95
  />
50
96
  <GridItem title="Word ID" value={content.data.wordId} />
51
- <GridItem title="Length" value={`${content.data.length} words`} />
52
97
  <GridItem
53
- title="Reference Word IDs"
54
- value={content.data.reference_word_ids ?
55
- Object.entries(content.data.reference_word_ids).map(([source, ids]) => (
56
- `${source}: ${ids?.join(', ') ?? 'No IDs'}`
57
- )).join('\n') : 'No reference word IDs'
98
+ title="Length"
99
+ value={`${anchorWords} words`}
100
+ />
101
+ <GridItem
102
+ title="Reference Words"
103
+ value={
104
+ <pre style={{ margin: 0, whiteSpace: 'pre-wrap' }}>{referenceWordsText}</pre>
58
105
  }
59
106
  />
60
107
  <GridItem title="Confidence" value={content.data.confidence?.toFixed(3) ?? 'N/A'} />
@@ -82,66 +129,45 @@ export default function DetailsModal({
82
129
  title="Selected Word"
83
130
  value={`"${getCurrentWord()}"`}
84
131
  />
85
- <GridItem
86
- title="Current Text"
87
- value={`"${content.data.words?.map(word => {
88
- const wordCorrection = content.data.corrections?.find(
89
- c => c.original_word === word
90
- )
91
- return wordCorrection ? wordCorrection.corrected_word : word
92
- }).join(' ') ?? content.data.word}"`}
93
- />
94
132
  <GridItem
95
133
  title="Word ID"
96
134
  value={content.data.wordId}
97
135
  />
98
136
  <GridItem
99
137
  title="Length"
100
- value={`${content.data.length} words`}
138
+ value={`${content.data.transcribed_word_ids?.length ?? 0} words`}
101
139
  />
102
- {content.data.preceding_anchor && (
140
+ {content.data.preceding_anchor_id && (
103
141
  <GridItem
104
142
  title="Preceding Anchor"
105
- value={`"${content.data.preceding_anchor.text}"`}
143
+ value={getAnchorText(content.data.preceding_anchor_id, content.data.anchor_sequences, referenceLyrics)}
106
144
  />
107
145
  )}
108
- <GridItem
109
- title="Transcribed Text"
110
- value={`"${content.data.text}"`}
111
- />
112
- {content.data.following_anchor && (
146
+ {content.data.following_anchor_id && (
113
147
  <GridItem
114
148
  title="Following Anchor"
115
- value={`"${content.data.following_anchor.text}"`}
149
+ value={getAnchorText(content.data.following_anchor_id, content.data.anchor_sequences, referenceLyrics)}
116
150
  />
117
151
  )}
118
- {content.data.reference_words && (
119
- <GridItem
120
- title="Reference Words"
121
- value={
122
- <>
123
- {content.data.reference_words.spotify && (
124
- <Typography>Spotify: "{content.data.reference_words.spotify.join(' ')}"</Typography>
125
- )}
126
- {content.data.reference_words.genius && (
127
- <Typography>Genius: "{content.data.reference_words.genius.join(' ')}"</Typography>
128
- )}
129
- </>
130
- }
131
- />
132
- )}
133
- {isCorrected && content.data.corrections && (
152
+ <GridItem
153
+ title="Reference Words"
154
+ value={
155
+ <pre style={{ margin: 0, whiteSpace: 'pre-wrap' }}>{referenceWordsText}</pre>
156
+ }
157
+ />
158
+ {isCorrected && (
134
159
  <GridItem
135
160
  title="Correction Details"
136
161
  value={
137
162
  <>
138
- {content.data.corrections.map((correction, index) => (
163
+ {relevantCorrections.map((correction, index) => (
139
164
  <Box key={index} sx={{ mb: 2, p: 1, border: '1px solid #ccc', borderRadius: '4px' }}>
140
165
  <Typography variant="subtitle2" fontWeight="bold">Correction {index + 1}</Typography>
141
166
  <Typography>Original: <strong>"{correction.original_word}"</strong></Typography>
142
167
  <Typography>Corrected: <strong>"{correction.corrected_word}"</strong></Typography>
143
168
  <Typography>Word ID: {correction.word_id}</Typography>
144
169
  <Typography>Confidence: {correction.confidence?.toFixed(3) ?? 'N/A'}</Typography>
170
+ <Typography>Handler: {correction.handler}</Typography>
145
171
  <Typography>Source: {correction.source}</Typography>
146
172
  <Typography>Reason: {correction.reason}</Typography>
147
173
  {correction.is_deletion && <Typography>Is Deletion: Yes</Typography>}
@@ -154,7 +180,7 @@ export default function DetailsModal({
154
180
  <Box sx={{ pl: 2 }}>
155
181
  {Object.entries(correction.alternatives).map(([word, score]) => (
156
182
  <Typography key={word}>
157
- "{word}": {score?.toFixed(3) ?? 'N/A'}
183
+ "{word}": {(score || 0).toFixed(3)}
158
184
  </Typography>
159
185
  ))}
160
186
  </Box>
@@ -178,24 +204,25 @@ export default function DetailsModal({
178
204
  <Dialog
179
205
  open={open}
180
206
  onClose={onClose}
181
- maxWidth="sm"
207
+ maxWidth="md"
182
208
  fullWidth
183
209
  >
184
- <IconButton
185
- onClick={onClose}
186
- sx={{
187
- position: 'absolute',
188
- right: 8,
189
- top: 8,
190
- }}
191
- >
192
- <CloseIcon />
193
- </IconButton>
194
210
  <DialogTitle>
195
- {content.type === 'gap' && (isCorrected ? 'Corrected ' : 'Uncorrected ')}
211
+ {content.type === 'gap' && (
212
+ isCorrected ? 'Corrected ' : 'Uncorrected '
213
+ )}
196
214
  {content.type.charAt(0).toUpperCase() + content.type.slice(1)} Details - "{getCurrentWord()}"
215
+ <IconButton
216
+ aria-label="close"
217
+ onClick={onClose}
218
+ sx={{ position: 'absolute', right: 8, top: 8 }}
219
+ >
220
+ <CloseIcon />
221
+ </IconButton>
197
222
  </DialogTitle>
198
- <DialogContent dividers>{renderContent()}</DialogContent>
223
+ <DialogContent>
224
+ {renderContent()}
225
+ </DialogContent>
199
226
  </Dialog>
200
227
  )
201
228
  }