lyrics-transcriber 0.41.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.
- lyrics_transcriber/core/controller.py +30 -52
- lyrics_transcriber/correction/anchor_sequence.py +325 -150
- lyrics_transcriber/correction/corrector.py +224 -107
- lyrics_transcriber/correction/handlers/base.py +28 -10
- lyrics_transcriber/correction/handlers/extend_anchor.py +47 -24
- lyrics_transcriber/correction/handlers/levenshtein.py +75 -33
- lyrics_transcriber/correction/handlers/llm.py +290 -0
- lyrics_transcriber/correction/handlers/no_space_punct_match.py +81 -36
- lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +46 -26
- lyrics_transcriber/correction/handlers/repeat.py +28 -11
- lyrics_transcriber/correction/handlers/sound_alike.py +68 -32
- lyrics_transcriber/correction/handlers/syllables_match.py +80 -30
- lyrics_transcriber/correction/handlers/word_count_match.py +36 -19
- lyrics_transcriber/correction/handlers/word_operations.py +68 -22
- lyrics_transcriber/correction/text_utils.py +3 -7
- lyrics_transcriber/frontend/.yarn/install-state.gz +0 -0
- lyrics_transcriber/frontend/.yarn/releases/yarn-4.6.0.cjs +934 -0
- lyrics_transcriber/frontend/.yarnrc.yml +3 -0
- lyrics_transcriber/frontend/dist/assets/{index-DKnNJHRK.js → index-coH8y7gV.js} +16284 -9032
- lyrics_transcriber/frontend/dist/assets/index-coH8y7gV.js.map +1 -0
- lyrics_transcriber/frontend/dist/index.html +1 -1
- lyrics_transcriber/frontend/package.json +6 -2
- lyrics_transcriber/frontend/src/App.tsx +18 -2
- lyrics_transcriber/frontend/src/api.ts +103 -6
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +7 -6
- lyrics_transcriber/frontend/src/components/DetailsModal.tsx +86 -59
- lyrics_transcriber/frontend/src/components/EditModal.tsx +93 -43
- lyrics_transcriber/frontend/src/components/FileUpload.tsx +2 -2
- lyrics_transcriber/frontend/src/components/Header.tsx +251 -0
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +303 -265
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +117 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +125 -40
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +129 -115
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +59 -78
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +40 -16
- lyrics_transcriber/frontend/src/components/WordEditControls.tsx +4 -10
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +137 -68
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +1 -1
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +85 -115
- lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
- lyrics_transcriber/frontend/src/components/shared/types.ts +15 -7
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +35 -0
- lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +7 -7
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +121 -0
- lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
- lyrics_transcriber/frontend/src/types.js +2 -0
- lyrics_transcriber/frontend/src/types.ts +70 -49
- lyrics_transcriber/frontend/src/validation.ts +132 -0
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/frontend/yarn.lock +3752 -0
- lyrics_transcriber/lyrics/base_lyrics_provider.py +75 -12
- lyrics_transcriber/lyrics/file_provider.py +6 -5
- lyrics_transcriber/lyrics/genius.py +5 -2
- lyrics_transcriber/lyrics/spotify.py +58 -21
- lyrics_transcriber/output/ass/config.py +16 -5
- lyrics_transcriber/output/cdg.py +1 -1
- lyrics_transcriber/output/generator.py +22 -8
- lyrics_transcriber/output/plain_text.py +15 -10
- lyrics_transcriber/output/segment_resizer.py +16 -3
- lyrics_transcriber/output/subtitles.py +27 -1
- lyrics_transcriber/output/video.py +107 -1
- lyrics_transcriber/review/__init__.py +0 -1
- lyrics_transcriber/review/server.py +337 -164
- lyrics_transcriber/transcribers/audioshake.py +3 -0
- lyrics_transcriber/transcribers/base_transcriber.py +11 -3
- lyrics_transcriber/transcribers/whisper.py +11 -1
- lyrics_transcriber/types.py +151 -105
- lyrics_transcriber/utils/word_utils.py +27 -0
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/METADATA +3 -1
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/RECORD +74 -61
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/WHEEL +1 -1
- lyrics_transcriber/frontend/dist/assets/index-DKnNJHRK.js.map +0 -1
- lyrics_transcriber/frontend/package-lock.json +0 -4260
- lyrics_transcriber/frontend/src/components/shared/utils/initializeDataWithIds.tsx +0 -202
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.41.0.dist-info → lyrics_transcriber-0.42.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-
|
8
|
+
<script type="module" crossorigin src="/assets/index-coH8y7gV.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('
|
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
|
27
|
-
|
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
|
-
|
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
|
@@ -55,7 +56,7 @@ export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProp
|
|
55
56
|
audio.src = ''
|
56
57
|
audioRef.current = null
|
57
58
|
}
|
58
|
-
}, [apiClient, onTimeUpdate])
|
59
|
+
}, [apiClient, onTimeUpdate, audioHash])
|
59
60
|
|
60
61
|
const handlePlayPause = () => {
|
61
62
|
if (!audioRef.current) return
|
@@ -130,7 +131,7 @@ export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProp
|
|
130
131
|
<Typography variant="body2" color="text.secondary" sx={{ mr: 1 }}>
|
131
132
|
Playback:
|
132
133
|
</Typography>
|
133
|
-
|
134
|
+
|
134
135
|
<IconButton
|
135
136
|
onClick={handlePlayPause}
|
136
137
|
size="small"
|
@@ -148,7 +149,7 @@ export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProp
|
|
148
149
|
max={duration}
|
149
150
|
onChange={handleSeek}
|
150
151
|
size="small"
|
151
|
-
sx={{
|
152
|
+
sx={{
|
152
153
|
width: 200,
|
153
154
|
mx: 1,
|
154
155
|
'& .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 ??
|
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.
|
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="
|
54
|
-
value={
|
55
|
-
|
56
|
-
|
57
|
-
|
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.
|
140
|
+
{content.data.preceding_anchor_id && (
|
103
141
|
<GridItem
|
104
142
|
title="Preceding Anchor"
|
105
|
-
value={
|
143
|
+
value={getAnchorText(content.data.preceding_anchor_id, content.data.anchor_sequences, referenceLyrics)}
|
106
144
|
/>
|
107
145
|
)}
|
108
|
-
|
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={
|
149
|
+
value={getAnchorText(content.data.following_anchor_id, content.data.anchor_sequences, referenceLyrics)}
|
116
150
|
/>
|
117
151
|
)}
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
{
|
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
|
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="
|
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' && (
|
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
|
223
|
+
<DialogContent>
|
224
|
+
{renderContent()}
|
225
|
+
</DialogContent>
|
199
226
|
</Dialog>
|
200
227
|
)
|
201
228
|
}
|