lyrics-transcriber 0.34.2__py3-none-any.whl → 0.35.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 +10 -1
- lyrics_transcriber/correction/corrector.py +4 -3
- lyrics_transcriber/frontend/dist/assets/index-CQCER5Fo.js +181 -0
- lyrics_transcriber/frontend/dist/index.html +1 -1
- lyrics_transcriber/frontend/src/App.tsx +6 -2
- lyrics_transcriber/frontend/src/api.ts +9 -0
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +155 -0
- lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +1 -1
- lyrics_transcriber/frontend/src/components/DetailsModal.tsx +23 -191
- lyrics_transcriber/frontend/src/components/EditModal.tsx +407 -0
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +255 -221
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +39 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +35 -264
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +232 -0
- lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +315 -0
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +116 -138
- lyrics_transcriber/frontend/src/components/WordEditControls.tsx +116 -0
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +243 -0
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +28 -0
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +52 -0
- lyrics_transcriber/frontend/src/components/{constants.ts → shared/constants.ts} +1 -0
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +137 -0
- lyrics_transcriber/frontend/src/components/{styles.ts → shared/styles.ts} +1 -1
- lyrics_transcriber/frontend/src/components/shared/types.ts +99 -0
- lyrics_transcriber/frontend/src/components/shared/utils/newlineCalculator.ts +37 -0
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +76 -0
- lyrics_transcriber/frontend/src/types.ts +2 -43
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/lyrics/spotify.py +11 -0
- lyrics_transcriber/output/generator.py +28 -11
- lyrics_transcriber/review/server.py +38 -12
- {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/METADATA +1 -1
- {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/RECORD +37 -24
- lyrics_transcriber/frontend/dist/assets/index-DqFgiUni.js +0 -245
- lyrics_transcriber/frontend/src/components/DebugPanel.tsx +0 -311
- {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.0.dist-info}/WHEEL +0 -0
- {lyrics_transcriber-0.34.2.dist-info → lyrics_transcriber-0.35.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-CQCER5Fo.js"></script>
|
9
9
|
</head>
|
10
10
|
<body>
|
11
11
|
<div id="root"></div>
|
@@ -34,7 +34,6 @@ export default function App() {
|
|
34
34
|
try {
|
35
35
|
const client = new LiveApiClient(baseUrl)
|
36
36
|
const data = await client.getCorrectionData()
|
37
|
-
console.log('Fetched data:', data)
|
38
37
|
setData(data)
|
39
38
|
} catch (err) {
|
40
39
|
const error = err as Error
|
@@ -168,7 +167,12 @@ export default function App() {
|
|
168
167
|
}
|
169
168
|
|
170
169
|
return (
|
171
|
-
<Box sx={{
|
170
|
+
<Box sx={{
|
171
|
+
p: 3,
|
172
|
+
pb: 6, // Add bottom padding to ensure content doesn't get cut off
|
173
|
+
maxWidth: '100%',
|
174
|
+
overflowX: 'hidden'
|
175
|
+
}}>
|
172
176
|
{error && (
|
173
177
|
<Alert severity="error" sx={{ mb: 2 }} onClose={() => setError(null)}>
|
174
178
|
{error}
|
@@ -4,6 +4,7 @@ import { CorrectionData } from './types';
|
|
4
4
|
export interface ApiClient {
|
5
5
|
getCorrectionData: () => Promise<CorrectionData>;
|
6
6
|
submitCorrections: (data: CorrectionData) => Promise<void>;
|
7
|
+
getAudioUrl: () => string;
|
7
8
|
}
|
8
9
|
|
9
10
|
// Add new interface for the minimal update payload
|
@@ -45,6 +46,10 @@ export class LiveApiClient implements ApiClient {
|
|
45
46
|
throw new Error(`API error: ${response.statusText}`);
|
46
47
|
}
|
47
48
|
}
|
49
|
+
|
50
|
+
getAudioUrl(): string {
|
51
|
+
return `${this.baseUrl}/audio`
|
52
|
+
}
|
48
53
|
}
|
49
54
|
|
50
55
|
export class FileOnlyClient implements ApiClient {
|
@@ -56,4 +61,8 @@ export class FileOnlyClient implements ApiClient {
|
|
56
61
|
async submitCorrections(_data: CorrectionData): Promise<void> {
|
57
62
|
throw new Error('Not supported in file-only mode');
|
58
63
|
}
|
64
|
+
|
65
|
+
getAudioUrl(): string {
|
66
|
+
throw new Error('Not supported in file-only mode');
|
67
|
+
}
|
59
68
|
}
|
@@ -0,0 +1,155 @@
|
|
1
|
+
import { Box, IconButton, Slider, Typography } from '@mui/material'
|
2
|
+
import PlayArrowIcon from '@mui/icons-material/PlayArrow'
|
3
|
+
import PauseIcon from '@mui/icons-material/Pause'
|
4
|
+
import { useEffect, useRef, useState, useCallback } from 'react'
|
5
|
+
import { ApiClient } from '../api'
|
6
|
+
|
7
|
+
interface AudioPlayerProps {
|
8
|
+
apiClient: ApiClient | null,
|
9
|
+
onTimeUpdate?: (time: number) => void
|
10
|
+
}
|
11
|
+
|
12
|
+
export default function AudioPlayer({ apiClient, onTimeUpdate }: AudioPlayerProps) {
|
13
|
+
const [isPlaying, setIsPlaying] = useState(false)
|
14
|
+
const [currentTime, setCurrentTime] = useState(0)
|
15
|
+
const [duration, setDuration] = useState(0)
|
16
|
+
const audioRef = useRef<HTMLAudioElement | null>(null)
|
17
|
+
|
18
|
+
useEffect(() => {
|
19
|
+
if (!apiClient) return
|
20
|
+
|
21
|
+
const audio = new Audio(apiClient.getAudioUrl())
|
22
|
+
audioRef.current = audio
|
23
|
+
|
24
|
+
// Add requestAnimationFrame for smoother updates
|
25
|
+
let animationFrameId: number
|
26
|
+
|
27
|
+
const updateTime = () => {
|
28
|
+
const time = audio.currentTime
|
29
|
+
setCurrentTime(time)
|
30
|
+
onTimeUpdate?.(time)
|
31
|
+
animationFrameId = requestAnimationFrame(updateTime)
|
32
|
+
}
|
33
|
+
|
34
|
+
audio.addEventListener('play', () => {
|
35
|
+
updateTime()
|
36
|
+
})
|
37
|
+
|
38
|
+
audio.addEventListener('pause', () => {
|
39
|
+
cancelAnimationFrame(animationFrameId)
|
40
|
+
})
|
41
|
+
|
42
|
+
audio.addEventListener('ended', () => {
|
43
|
+
cancelAnimationFrame(animationFrameId)
|
44
|
+
setIsPlaying(false)
|
45
|
+
setCurrentTime(0)
|
46
|
+
})
|
47
|
+
|
48
|
+
audio.addEventListener('loadedmetadata', () => {
|
49
|
+
setDuration(audio.duration)
|
50
|
+
})
|
51
|
+
|
52
|
+
return () => {
|
53
|
+
cancelAnimationFrame(animationFrameId)
|
54
|
+
audio.pause()
|
55
|
+
audio.src = ''
|
56
|
+
audioRef.current = null
|
57
|
+
}
|
58
|
+
}, [apiClient, onTimeUpdate])
|
59
|
+
|
60
|
+
const handlePlayPause = () => {
|
61
|
+
if (!audioRef.current) return
|
62
|
+
|
63
|
+
if (isPlaying) {
|
64
|
+
audioRef.current.pause()
|
65
|
+
} else {
|
66
|
+
audioRef.current.play()
|
67
|
+
}
|
68
|
+
setIsPlaying(!isPlaying)
|
69
|
+
}
|
70
|
+
|
71
|
+
const handleSeek = (_: Event, newValue: number | number[]) => {
|
72
|
+
if (!audioRef.current) return
|
73
|
+
const time = newValue as number
|
74
|
+
audioRef.current.currentTime = time
|
75
|
+
setCurrentTime(time)
|
76
|
+
}
|
77
|
+
|
78
|
+
const formatTime = (seconds: number) => {
|
79
|
+
const mins = Math.floor(seconds / 60)
|
80
|
+
const secs = Math.floor(seconds % 60)
|
81
|
+
return `${mins}:${secs.toString().padStart(2, '0')}`
|
82
|
+
}
|
83
|
+
|
84
|
+
// Add this method to expose seeking functionality
|
85
|
+
const seekAndPlay = (time: number) => {
|
86
|
+
if (!audioRef.current) return
|
87
|
+
|
88
|
+
audioRef.current.currentTime = time
|
89
|
+
setCurrentTime(time)
|
90
|
+
audioRef.current.play()
|
91
|
+
setIsPlaying(true)
|
92
|
+
}
|
93
|
+
|
94
|
+
const togglePlayback = useCallback(() => {
|
95
|
+
if (!audioRef.current) return
|
96
|
+
|
97
|
+
if (isPlaying) {
|
98
|
+
audioRef.current.pause()
|
99
|
+
} else {
|
100
|
+
audioRef.current.play()
|
101
|
+
}
|
102
|
+
setIsPlaying(!isPlaying)
|
103
|
+
}, [isPlaying])
|
104
|
+
|
105
|
+
// Expose both methods globally
|
106
|
+
useEffect(() => {
|
107
|
+
if (!apiClient) return
|
108
|
+
|
109
|
+
const win = window as any
|
110
|
+
win.seekAndPlayAudio = seekAndPlay
|
111
|
+
win.toggleAudioPlayback = togglePlayback
|
112
|
+
|
113
|
+
return () => {
|
114
|
+
delete win.seekAndPlayAudio
|
115
|
+
delete win.toggleAudioPlayback
|
116
|
+
}
|
117
|
+
}, [apiClient, togglePlayback])
|
118
|
+
|
119
|
+
if (!apiClient) return null
|
120
|
+
|
121
|
+
return (
|
122
|
+
<Box sx={{
|
123
|
+
display: 'flex',
|
124
|
+
alignItems: 'center',
|
125
|
+
gap: 2,
|
126
|
+
p: 2,
|
127
|
+
backgroundColor: 'background.paper',
|
128
|
+
borderRadius: 1,
|
129
|
+
boxShadow: 1
|
130
|
+
}}>
|
131
|
+
<IconButton
|
132
|
+
onClick={handlePlayPause}
|
133
|
+
size="large"
|
134
|
+
>
|
135
|
+
{isPlaying ? <PauseIcon /> : <PlayArrowIcon />}
|
136
|
+
</IconButton>
|
137
|
+
|
138
|
+
<Typography sx={{ minWidth: 45 }}>
|
139
|
+
{formatTime(currentTime)}
|
140
|
+
</Typography>
|
141
|
+
|
142
|
+
<Slider
|
143
|
+
value={currentTime}
|
144
|
+
min={0}
|
145
|
+
max={duration}
|
146
|
+
onChange={handleSeek}
|
147
|
+
sx={{ mx: 2 }}
|
148
|
+
/>
|
149
|
+
|
150
|
+
<Typography sx={{ minWidth: 45 }}>
|
151
|
+
{formatTime(duration)}
|
152
|
+
</Typography>
|
153
|
+
</Box>
|
154
|
+
)
|
155
|
+
}
|
@@ -4,82 +4,31 @@ import {
|
|
4
4
|
DialogContent,
|
5
5
|
IconButton,
|
6
6
|
Grid,
|
7
|
-
Typography
|
8
|
-
Box,
|
9
|
-
TextField,
|
10
|
-
Button,
|
7
|
+
Typography
|
11
8
|
} from '@mui/material'
|
12
9
|
import CloseIcon from '@mui/icons-material/Close'
|
13
10
|
import { ModalContent } from './LyricsAnalyzer'
|
14
|
-
import { WordCorrection } from '../types'
|
15
|
-
import { useState, useEffect } from 'react'
|
16
11
|
|
17
12
|
interface DetailsModalProps {
|
18
13
|
open: boolean
|
19
14
|
content: ModalContent | null
|
20
15
|
onClose: () => void
|
21
|
-
onUpdateCorrection?: (position: number, updatedWords: string[]) => void
|
22
|
-
isReadOnly?: boolean
|
23
16
|
}
|
24
17
|
|
25
18
|
export default function DetailsModal({
|
26
19
|
open,
|
27
20
|
content,
|
28
21
|
onClose,
|
29
|
-
onUpdateCorrection,
|
30
|
-
isReadOnly = true
|
31
22
|
}: DetailsModalProps) {
|
32
|
-
const [editedWord, setEditedWord] = useState('')
|
33
|
-
const [isEditing, setIsEditing] = useState(false)
|
34
|
-
|
35
|
-
useEffect(() => {
|
36
|
-
// Reset editing state when modal content changes
|
37
|
-
if (content?.type === 'gap') {
|
38
|
-
setEditedWord(content.data.word)
|
39
|
-
setIsEditing(false)
|
40
|
-
}
|
41
|
-
}, [content])
|
42
|
-
|
43
23
|
if (!content) return null
|
44
24
|
|
45
|
-
const
|
46
|
-
console.group('DetailsModal Edit Debug')
|
47
|
-
console.log('Starting edit for content:', JSON.stringify(content, null, 2))
|
48
|
-
if (content.type === 'gap') {
|
49
|
-
console.log('Setting edited word:', content.data.word)
|
50
|
-
setEditedWord(content.data.word)
|
51
|
-
}
|
52
|
-
console.groupEnd()
|
53
|
-
setIsEditing(true)
|
54
|
-
}
|
55
|
-
|
56
|
-
const handleSaveEdit = () => {
|
57
|
-
console.group('DetailsModal Save Debug')
|
58
|
-
console.log('Current content:', JSON.stringify(content, null, 2))
|
59
|
-
console.log('Edited word:', editedWord)
|
60
|
-
|
61
|
-
if (content?.type === 'gap' && onUpdateCorrection) {
|
62
|
-
// Use the editedWord state instead of the original word
|
63
|
-
console.log('Saving edit with new word:', editedWord)
|
64
|
-
onUpdateCorrection(
|
65
|
-
content.data.position,
|
66
|
-
[editedWord] // Use the edited word here
|
67
|
-
)
|
68
|
-
}
|
69
|
-
console.groupEnd()
|
70
|
-
onClose()
|
71
|
-
}
|
72
|
-
|
73
|
-
const handleCancelEdit = () => {
|
25
|
+
const getCurrentWord = () => {
|
74
26
|
if (content.type === 'gap') {
|
75
|
-
|
76
|
-
|
27
|
+
return content.data.word
|
28
|
+
} else if (content.type === 'anchor') {
|
29
|
+
return content.data.word ?? content.data.words[0]
|
77
30
|
}
|
78
|
-
|
79
|
-
|
80
|
-
const handleWordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
81
|
-
console.log('Word changed to:', event.target.value)
|
82
|
-
setEditedWord(event.target.value)
|
31
|
+
return ''
|
83
32
|
}
|
84
33
|
|
85
34
|
const renderContent = () => {
|
@@ -87,155 +36,41 @@ export default function DetailsModal({
|
|
87
36
|
case 'anchor':
|
88
37
|
return (
|
89
38
|
<Grid container spacing={2}>
|
90
|
-
<GridItem title="Text" value={`"${content.data.text}"`} />
|
91
|
-
<GridItem title="Words" value={content.data.words.join(' ')} />
|
92
|
-
<GridItem title="Position" value={content.data.position} />
|
93
39
|
<GridItem
|
94
|
-
title="
|
95
|
-
value={
|
96
|
-
<Box component="pre" sx={{ margin: 0, fontSize: '0.875rem' }}>
|
97
|
-
{JSON.stringify(content.data.reference_positions, null, 2)}
|
98
|
-
</Box>
|
99
|
-
}
|
40
|
+
title="Selected Word"
|
41
|
+
value={`"${getCurrentWord()}"`}
|
100
42
|
/>
|
101
43
|
<GridItem
|
102
|
-
title="
|
103
|
-
value={
|
44
|
+
title="Full Text"
|
45
|
+
value={`"${content.data.text}"`}
|
104
46
|
/>
|
47
|
+
<GridItem title="Position" value={content.data.position} />
|
105
48
|
<GridItem title="Length" value={`${content.data.length} words`} />
|
106
|
-
{
|
107
|
-
<>
|
108
|
-
<GridItem title="Phrase Type" value={content.data.phrase_score.phrase_type} />
|
109
|
-
<GridItem
|
110
|
-
title="Scores"
|
111
|
-
value={
|
112
|
-
<Box sx={{ pl: 2 }}>
|
113
|
-
<Typography>
|
114
|
-
Total: {content.data?.total_score?.toFixed(2) ?? 'N/A'}
|
115
|
-
</Typography>
|
116
|
-
<Typography>
|
117
|
-
Natural Break: {content.data?.phrase_score?.natural_break_score?.toFixed(2) ?? 'N/A'}
|
118
|
-
</Typography>
|
119
|
-
<Typography>
|
120
|
-
Length: {content.data.phrase_score.length_score.toFixed(2)}
|
121
|
-
</Typography>
|
122
|
-
<Typography>
|
123
|
-
Phrase: {content.data.phrase_score.total_score.toFixed(2)}
|
124
|
-
</Typography>
|
125
|
-
</Box>
|
126
|
-
}
|
127
|
-
/>
|
128
|
-
</>
|
129
|
-
)}
|
49
|
+
{/* ... rest of anchor details rendering ... */}
|
130
50
|
</Grid>
|
131
51
|
)
|
132
52
|
|
133
53
|
case 'gap':
|
134
54
|
return (
|
135
55
|
<Grid container spacing={2}>
|
56
|
+
<GridItem
|
57
|
+
title="Selected Word"
|
58
|
+
value={`"${getCurrentWord()}"`}
|
59
|
+
/>
|
136
60
|
<GridItem
|
137
61
|
title="Transcribed Text"
|
138
62
|
value={`"${content.data.text}"`}
|
139
63
|
/>
|
140
64
|
<GridItem
|
141
65
|
title="Current Text"
|
142
|
-
value={
|
143
|
-
|
144
|
-
|
145
|
-
<TextField
|
146
|
-
value={editedWord}
|
147
|
-
onChange={handleWordChange}
|
148
|
-
fullWidth
|
149
|
-
label="Edit word"
|
150
|
-
variant="outlined"
|
151
|
-
size="small"
|
152
|
-
/>
|
153
|
-
<Box sx={{ display: 'flex', gap: 1 }}>
|
154
|
-
<Button
|
155
|
-
variant="contained"
|
156
|
-
onClick={handleSaveEdit}
|
157
|
-
>
|
158
|
-
Save Changes
|
159
|
-
</Button>
|
160
|
-
<Button
|
161
|
-
variant="outlined"
|
162
|
-
onClick={handleCancelEdit}
|
163
|
-
>
|
164
|
-
Cancel
|
165
|
-
</Button>
|
166
|
-
</Box>
|
167
|
-
</Box>
|
168
|
-
) : (
|
169
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
170
|
-
<Typography>
|
171
|
-
"{content.data.words.map(word => {
|
172
|
-
const correction = content.data.corrections.find(
|
173
|
-
c => c.original_word === word
|
174
|
-
);
|
175
|
-
return correction ? correction.corrected_word : word;
|
176
|
-
}).join(' ')}"
|
177
|
-
</Typography>
|
178
|
-
{!isReadOnly && (
|
179
|
-
<Button
|
180
|
-
variant="outlined"
|
181
|
-
size="small"
|
182
|
-
onClick={handleStartEdit}
|
183
|
-
>
|
184
|
-
Edit
|
185
|
-
</Button>
|
186
|
-
)}
|
187
|
-
</Box>
|
66
|
+
value={`"${content.data.words.map(word => {
|
67
|
+
const correction = content.data.corrections.find(
|
68
|
+
c => c.original_word === word
|
188
69
|
)
|
189
|
-
|
190
|
-
|
191
|
-
<GridItem title="Position" value={content.data.position} />
|
192
|
-
<GridItem title="Length" value={`${content.data.length} words`} />
|
193
|
-
{content.data.corrections.length > 0 && (
|
194
|
-
<GridItem
|
195
|
-
title="Corrections"
|
196
|
-
value={
|
197
|
-
<Box sx={{ pl: 2 }}>
|
198
|
-
{content.data.corrections.map((correction: WordCorrection, i: number) => (
|
199
|
-
<Box key={i} sx={{ mb: 2 }}>
|
200
|
-
<Typography>
|
201
|
-
"{correction.original_word}" → "{correction.corrected_word}"
|
202
|
-
</Typography>
|
203
|
-
<Typography>
|
204
|
-
Confidence: {(correction.confidence * 100).toFixed(2)}%
|
205
|
-
</Typography>
|
206
|
-
<Typography>Source: {correction.source}</Typography>
|
207
|
-
<Typography>Reason: {correction.reason}</Typography>
|
208
|
-
{Object.keys(correction.alternatives).length > 0 && (
|
209
|
-
<Typography component="pre" sx={{ fontSize: '0.875rem' }}>
|
210
|
-
Alternatives: {JSON.stringify(correction.alternatives, null, 2)}
|
211
|
-
</Typography>
|
212
|
-
)}
|
213
|
-
</Box>
|
214
|
-
))}
|
215
|
-
</Box>
|
216
|
-
}
|
217
|
-
/>
|
218
|
-
)}
|
219
|
-
<GridItem
|
220
|
-
title="Reference Words"
|
221
|
-
value={
|
222
|
-
<Box component="pre" sx={{ margin: 0, fontSize: '0.875rem' }}>
|
223
|
-
{JSON.stringify(content.data.reference_words, null, 2)}
|
224
|
-
</Box>
|
225
|
-
}
|
70
|
+
return correction ? correction.corrected_word : word
|
71
|
+
}).join(' ')}"`}
|
226
72
|
/>
|
227
|
-
{
|
228
|
-
<GridItem
|
229
|
-
title="Preceding Anchor"
|
230
|
-
value={`"${content.data.preceding_anchor.text}"`}
|
231
|
-
/>
|
232
|
-
)}
|
233
|
-
{content.data.following_anchor && (
|
234
|
-
<GridItem
|
235
|
-
title="Following Anchor"
|
236
|
-
value={`"${content.data.following_anchor.text}"`}
|
237
|
-
/>
|
238
|
-
)}
|
73
|
+
{/* ... rest of gap details rendering ... */}
|
239
74
|
</Grid>
|
240
75
|
)
|
241
76
|
|
@@ -250,9 +85,6 @@ export default function DetailsModal({
|
|
250
85
|
onClose={onClose}
|
251
86
|
maxWidth="sm"
|
252
87
|
fullWidth
|
253
|
-
PaperProps={{
|
254
|
-
sx: { position: 'relative' },
|
255
|
-
}}
|
256
88
|
>
|
257
89
|
<IconButton
|
258
90
|
onClick={onClose}
|
@@ -265,7 +97,7 @@ export default function DetailsModal({
|
|
265
97
|
<CloseIcon />
|
266
98
|
</IconButton>
|
267
99
|
<DialogTitle>
|
268
|
-
{content.type.charAt(0).toUpperCase() + content.type.slice(1)} Details
|
100
|
+
{content.type.charAt(0).toUpperCase() + content.type.slice(1)} Details - "{getCurrentWord()}"
|
269
101
|
</DialogTitle>
|
270
102
|
<DialogContent dividers>{renderContent()}</DialogContent>
|
271
103
|
</Dialog>
|