karaoke-gen 0.75.53__py3-none-any.whl → 0.81.1__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.
- karaoke_gen/audio_fetcher.py +218 -0
- karaoke_gen/instrumental_review/static/index.html +179 -16
- karaoke_gen/karaoke_gen.py +191 -25
- karaoke_gen/lyrics_processor.py +39 -31
- karaoke_gen/utils/__init__.py +26 -0
- karaoke_gen/utils/cli_args.py +9 -1
- karaoke_gen/utils/gen_cli.py +1 -1
- karaoke_gen/utils/remote_cli.py +33 -6
- {karaoke_gen-0.75.53.dist-info → karaoke_gen-0.81.1.dist-info}/METADATA +80 -4
- {karaoke_gen-0.75.53.dist-info → karaoke_gen-0.81.1.dist-info}/RECORD +50 -43
- lyrics_transcriber/core/config.py +8 -0
- lyrics_transcriber/core/controller.py +43 -1
- lyrics_transcriber/correction/agentic/providers/config.py +6 -0
- lyrics_transcriber/correction/agentic/providers/model_factory.py +24 -1
- lyrics_transcriber/correction/agentic/router.py +17 -13
- lyrics_transcriber/frontend/.gitignore +1 -0
- lyrics_transcriber/frontend/e2e/agentic-corrections.spec.ts +207 -0
- lyrics_transcriber/frontend/e2e/fixtures/agentic-correction-data.json +226 -0
- lyrics_transcriber/frontend/index.html +5 -1
- lyrics_transcriber/frontend/package-lock.json +4553 -0
- lyrics_transcriber/frontend/package.json +7 -1
- lyrics_transcriber/frontend/playwright.config.ts +69 -0
- lyrics_transcriber/frontend/public/nomad-karaoke-logo.svg +5 -0
- lyrics_transcriber/frontend/src/App.tsx +88 -59
- lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +55 -21
- lyrics_transcriber/frontend/src/components/AppHeader.tsx +65 -0
- lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +39 -35
- lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +9 -9
- lyrics_transcriber/frontend/src/components/EditModal.tsx +1 -1
- lyrics_transcriber/frontend/src/components/EditWordList.tsx +1 -1
- lyrics_transcriber/frontend/src/components/Header.tsx +96 -3
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +120 -3
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +22 -21
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +1 -1
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +12 -2
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +3 -3
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +122 -35
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +2 -2
- lyrics_transcriber/frontend/src/components/shared/constants.ts +15 -5
- lyrics_transcriber/frontend/src/components/shared/types.ts +6 -0
- lyrics_transcriber/frontend/src/main.tsx +1 -7
- lyrics_transcriber/frontend/src/theme.ts +337 -135
- lyrics_transcriber/frontend/vite.config.ts +5 -0
- lyrics_transcriber/frontend/yarn.lock +1005 -1046
- lyrics_transcriber/output/generator.py +50 -3
- lyrics_transcriber/review/server.py +1 -1
- lyrics_transcriber/transcribers/local_whisper.py +260 -0
- {karaoke_gen-0.75.53.dist-info → karaoke_gen-0.81.1.dist-info}/WHEEL +0 -0
- {karaoke_gen-0.75.53.dist-info → karaoke_gen-0.81.1.dist-info}/entry_points.txt +0 -0
- {karaoke_gen-0.75.53.dist-info → karaoke_gen-0.81.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
"lint": "eslint .",
|
|
12
12
|
"preview": "vite preview",
|
|
13
13
|
"predeploy": "npm run build-prod",
|
|
14
|
-
"deploy": "gh-pages -d dist"
|
|
14
|
+
"deploy": "gh-pages -d dist",
|
|
15
|
+
"test": "playwright test",
|
|
16
|
+
"test:ui": "playwright test --ui",
|
|
17
|
+
"test:headed": "playwright test --headed"
|
|
15
18
|
},
|
|
16
19
|
"dependencies": {
|
|
17
20
|
"@emotion/react": "^11.14.0",
|
|
@@ -19,6 +22,7 @@
|
|
|
19
22
|
"@mui/icons-material": "^6.3.0",
|
|
20
23
|
"@mui/material": "^6.3.0",
|
|
21
24
|
"@mui/system": "^6.4.3",
|
|
25
|
+
"lucide-react": "^0.562.0",
|
|
22
26
|
"nanoid": "^5.0.9",
|
|
23
27
|
"react": "^18.3.1",
|
|
24
28
|
"react-dom": "^18.3.1",
|
|
@@ -26,6 +30,7 @@
|
|
|
26
30
|
},
|
|
27
31
|
"devDependencies": {
|
|
28
32
|
"@eslint/js": "^9.17.0",
|
|
33
|
+
"@playwright/test": "^1.57.0",
|
|
29
34
|
"@types/react": "^18.3.18",
|
|
30
35
|
"@types/react-dom": "^18.3.5",
|
|
31
36
|
"@vitejs/plugin-react": "^4.3.4",
|
|
@@ -34,6 +39,7 @@
|
|
|
34
39
|
"eslint-plugin-react-refresh": "^0.4.16",
|
|
35
40
|
"gh-pages": "^6.3.0",
|
|
36
41
|
"globals": "^15.14.0",
|
|
42
|
+
"playwright": "^1.57.0",
|
|
37
43
|
"typescript": "~5.6.2",
|
|
38
44
|
"typescript-eslint": "^8.18.2",
|
|
39
45
|
"vite": "^6.0.5"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Playwright configuration for lyrics-transcriber frontend E2E tests.
|
|
5
|
+
* Tests run against the local dev server (localhost:5173) served by vite.
|
|
6
|
+
*/
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
testDir: './e2e',
|
|
9
|
+
|
|
10
|
+
// Run tests sequentially for now
|
|
11
|
+
fullyParallel: false,
|
|
12
|
+
|
|
13
|
+
// Fail the build on CI if you accidentally left test.only in the source code
|
|
14
|
+
forbidOnly: !!process.env.CI,
|
|
15
|
+
|
|
16
|
+
// Retry on CI only
|
|
17
|
+
retries: process.env.CI ? 2 : 0,
|
|
18
|
+
|
|
19
|
+
// Single worker
|
|
20
|
+
workers: 1,
|
|
21
|
+
|
|
22
|
+
// Reporter to use
|
|
23
|
+
reporter: [
|
|
24
|
+
['html', { open: 'never' }],
|
|
25
|
+
['list'],
|
|
26
|
+
],
|
|
27
|
+
|
|
28
|
+
// Shared settings for all tests
|
|
29
|
+
use: {
|
|
30
|
+
// Base URL for the local dev server
|
|
31
|
+
baseURL: 'http://localhost:5173',
|
|
32
|
+
|
|
33
|
+
// Collect trace on failure for debugging
|
|
34
|
+
trace: 'on-first-retry',
|
|
35
|
+
|
|
36
|
+
// Screenshot on failure
|
|
37
|
+
screenshot: 'only-on-failure',
|
|
38
|
+
|
|
39
|
+
// Video on failure
|
|
40
|
+
video: 'on-first-retry',
|
|
41
|
+
|
|
42
|
+
// Increase timeout for complex interactions
|
|
43
|
+
actionTimeout: 30000,
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Longer timeout for tests
|
|
47
|
+
timeout: 120000, // 2 minutes per test
|
|
48
|
+
|
|
49
|
+
// Expect timeout for assertions
|
|
50
|
+
expect: {
|
|
51
|
+
timeout: 30000, // 30 seconds for expects
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// Configure projects (browsers)
|
|
55
|
+
projects: [
|
|
56
|
+
{
|
|
57
|
+
name: 'chromium',
|
|
58
|
+
use: { ...devices['Desktop Chrome'] },
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
|
|
62
|
+
// Run local dev server before starting tests
|
|
63
|
+
webServer: {
|
|
64
|
+
command: 'npm run dev',
|
|
65
|
+
url: 'http://localhost:5173',
|
|
66
|
+
reuseExistingServer: !process.env.CI,
|
|
67
|
+
timeout: 120000,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
3
|
+
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="45 60 255 135">
|
|
4
|
+
<g data-v-70b83f88="" fill="#ff7acc" class="basesvg" transform="translate(48.368003845214844,62.62239074707031)"><g fill-rule="" class="tp-name" transform="translate(0,0)"><g transform="scale(1.4000000000000004)"><g stroke="#ff7acc" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" paint-order="stroke" data-gra-attr="stroke" fill-opacity="0"><path d="M1.4-33.44L1.4 0C31.48 0 3.96 0 34.04 0L34.04-32.64 17.81-32.64 17.81-20.61 1.4-33.44ZM52.21-33.76C42.6-33.76 34.81-25.97 34.81-16.37 34.81-6.76 42.6 1.03 52.21 1.03 61.81 1.03 69.6-6.76 69.6-16.37 69.6-25.97 61.81-33.76 52.21-33.76ZM70.7 0L103.34 0 103.25-33.9 86.93-21.54 70.61-33.9 70.7 0ZM102.34 0C138.2 0 107.52 0 143.56 0L122.95-34.93 102.34 0ZM158.84 0.05C167.84 0.05 175.16-7.27 175.16-16.27 175.16-25.27 167.84-32.64 158.84-32.64L142.52-32.64C142.52 0.75 142.52-33.34 142.52 0.05L158.84 0.05Z" transform="translate(-1.399999976158142, 34.93000030517578)"></path></g><g transform="translate(0,38.959999084472656)"><g stroke="#ff7acc" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" paint-order="stroke" data-gra-attr="stroke" fill="#ff7acc" fill-opacity="0" transform="scale(1.02719)"><path d="M24.43 0L17.67 0 7.98-11.87 7.98 0 2.63 0 2.63-26.64 7.98-26.64 7.98-14.69 17.67-26.64 24.12-26.64 13.13-13.44 24.43 0ZM45.03 0L43.27-5.08 32.66-5.08 30.91 0 25.3 0 34.88-26.68 41.1-26.68 50.68 0 45.03 0ZM34.11-9.35L41.82-9.35 37.97-20.5 34.11-9.35ZM73.19 0L67.01 0 61.13-10.38 58.61-10.38 58.61 0 53.27 0 53.27-26.64 63.27-26.64Q66.36-26.64 68.53-25.55 70.71-24.47 71.8-22.61 72.89-20.76 72.89-18.47L72.89-18.47Q72.89-15.84 71.36-13.72 69.83-11.6 66.82-10.8L66.82-10.8 73.19 0ZM58.61-22.21L58.61-14.39 63.08-14.39Q65.25-14.39 66.32-15.44 67.39-16.49 67.39-18.36L67.39-18.36Q67.39-20.19 66.32-21.2 65.25-22.21 63.08-22.21L63.08-22.21 58.61-22.21ZM94.82 0L93.07-5.08 82.46-5.08 80.7 0 75.09 0 84.67-26.68 90.89-26.68 100.47 0 94.82 0ZM83.91-9.35L91.62-9.35 87.76-20.5 83.91-9.35ZM115.43 0.27Q111.69 0.27 108.56-1.49 105.43-3.24 103.6-6.35 101.76-9.47 101.76-13.4L101.76-13.4Q101.76-17.29 103.6-20.4 105.43-23.51 108.56-25.27 111.69-27.02 115.43-27.02L115.43-27.02Q119.21-27.02 122.32-25.27 125.43-23.51 127.24-20.4 129.05-17.29 129.05-13.4L129.05-13.4Q129.05-9.47 127.24-6.35 125.43-3.24 122.3-1.49 119.17 0.27 115.43 0.27L115.43 0.27ZM115.43-4.5Q117.83-4.5 119.66-5.59 121.5-6.68 122.53-8.7 123.56-10.73 123.56-13.4L123.56-13.4Q123.56-16.07 122.53-18.07 121.5-20.08 119.66-21.15 117.83-22.21 115.43-22.21L115.43-22.21Q113.02-22.21 111.17-21.15 109.32-20.08 108.29-18.07 107.26-16.07 107.26-13.4L107.26-13.4Q107.26-10.73 108.29-8.7 109.32-6.68 111.17-5.59 113.02-4.5 115.43-4.5L115.43-4.5ZM153.82 0L147.06 0 137.37-11.87 137.37 0 132.02 0 132.02-26.64 137.37-26.64 137.37-14.69 147.06-26.64 153.51-26.64 142.52-13.44 153.82 0ZM171.79-22.33L161.67-22.33 161.67-15.65 170.64-15.65 170.64-11.41 161.67-11.41 161.67-4.35 171.79-4.35 171.79 0 156.33 0 156.33-26.68 171.79-26.68 171.79-22.33Z" transform="translate(-2.630000114440918, 27.020000457763672)"></path></g></g></g></g> <g data-gra="path-slogan" fill-rule="" class="tp-slogan" fill="#ffdf6b" transform="translate(5,111.788818359375)"><rect x="0" height="1" y="5.9832000732421875" width="14.278396606445312"></rect> <rect height="1" y="5.9832000732421875" width="14.278396606445312" x="218.985595703125"></rect> <g transform="translate(17.278396606445312,0)"><g transform="scale(1.2800000000000002)"><path d="M10.21-8.38L12.02-8.38L9.68 0L7.70 0L6.13-5.96L4.49 0L2.52 0.01L0.26-8.38L2.06-8.38L3.54-1.87L5.24-8.38L7.12-8.38L8.72-1.91L10.21-8.38ZM18.38-8.38L20.06-8.38L20.06 0L18.38 0L18.38-3.56L14.80-3.56L14.80 0L13.12 0L13.12-8.38L14.80-8.38L14.80-4.93L18.38-4.93L18.38-8.38ZM26.58-7.02L23.40-7.02L23.40-4.92L26.22-4.92L26.22-3.59L23.40-3.59L23.40-1.37L26.58-1.37L26.58 0L21.72 0L21.72-8.39L26.58-8.39L26.58-7.02ZM34.37 0L32.42 0L30.58-3.26L29.78-3.26L29.78 0L28.10 0L28.10-8.38L31.25-8.38Q32.22-8.38 32.90-8.03Q33.59-7.69 33.93-7.11Q34.27-6.53 34.27-5.81L34.27-5.81Q34.27-4.98 33.79-4.31Q33.31-3.65 32.36-3.40L32.36-3.40L34.37 0ZM29.78-6.98L29.78-4.52L31.19-4.52Q31.87-4.52 32.21-4.85Q32.54-5.18 32.54-5.77L32.54-5.77Q32.54-6.35 32.21-6.67Q31.87-6.98 31.19-6.98L31.19-6.98L29.78-6.98ZM40.66-7.02L37.48-7.02L37.48-4.92L40.30-4.92L40.30-3.59L37.48-3.59L37.48-1.37L40.66-1.37L40.66 0L35.80 0L35.80-8.39L40.66-8.39L40.66-7.02ZM47.92-8.38L49.70-8.38L46.63 0L44.59 0L41.52-8.38L43.32-8.38L45.62-1.72L47.92-8.38ZM55.57-7.02L52.39-7.02L52.39-4.92L55.21-4.92L55.21-3.59L52.39-3.59L52.39-1.37L55.57-1.37L55.57 0L50.71 0L50.71-8.39L55.57-8.39L55.57-7.02ZM63.36 0L61.42 0L59.57-3.26L58.78-3.26L58.78 0L57.10 0L57.10-8.38L60.24-8.38Q61.21-8.38 61.90-8.03Q62.58-7.69 62.92-7.11Q63.26-6.53 63.26-5.81L63.26-5.81Q63.26-4.98 62.78-4.31Q62.30-3.65 61.36-3.40L61.36-3.40L63.36 0ZM58.78-6.98L58.78-4.52L60.18-4.52Q60.86-4.52 61.20-4.85Q61.54-5.18 61.54-5.77L61.54-5.77Q61.54-6.35 61.20-6.67Q60.86-6.98 60.18-6.98L60.18-6.98L58.78-6.98ZM72.43-8.38L74.30-8.38L71.47-2.92L71.47 0L69.79 0L69.79-2.92L66.95-8.38L68.84-8.38L70.64-4.55L72.43-8.38ZM79.16 0.08Q77.99 0.08 77.00-0.47Q76.02-1.02 75.44-2.00Q74.87-2.98 74.87-4.21L74.87-4.21Q74.87-5.44 75.44-6.41Q76.02-7.39 77.00-7.94Q77.99-8.50 79.16-8.50L79.16-8.50Q80.35-8.50 81.33-7.94Q82.31-7.39 82.88-6.41Q83.45-5.44 83.45-4.21L83.45-4.21Q83.45-2.98 82.88-2.00Q82.31-1.02 81.32-0.47Q80.34 0.08 79.16 0.08L79.16 0.08ZM79.16-1.42Q79.92-1.42 80.50-1.76Q81.07-2.10 81.40-2.74Q81.72-3.37 81.72-4.21L81.72-4.21Q81.72-5.05 81.40-5.68Q81.07-6.31 80.50-6.65Q79.92-6.98 79.16-6.98L79.16-6.98Q78.41-6.98 77.83-6.65Q77.24-6.31 76.92-5.68Q76.60-5.05 76.60-4.21L76.60-4.21Q76.60-3.37 76.92-2.74Q77.24-2.10 77.83-1.76Q78.41-1.42 79.16-1.42L79.16-1.42ZM84.67-8.38L86.35-8.38L86.35-3.19Q86.35-2.34 86.80-1.89Q87.24-1.44 88.04-1.44L88.04-1.44Q88.86-1.44 89.30-1.89Q89.75-2.34 89.75-3.19L89.75-3.19L89.75-8.38L91.44-8.38L91.44-3.20Q91.44-2.14 90.98-1.40Q90.52-0.66 89.74-0.29Q88.97 0.08 88.02 0.08L88.02 0.08Q87.08 0.08 86.32-0.29Q85.56-0.66 85.12-1.40Q84.67-2.14 84.67-3.20L84.67-3.20L84.67-8.38ZM101.60 0L101.05-1.60L97.72-1.60L97.16 0L95.40 0L98.41-8.39L100.37-8.39L103.38 0L101.60 0ZM98.17-2.94L100.60-2.94L99.38-6.44L98.17-2.94ZM110.77 0L108.83 0L106.98-3.26L106.19-3.26L106.19 0L104.51 0L104.51-8.38L107.65-8.38Q108.62-8.38 109.31-8.03Q109.99-7.69 110.33-7.11Q110.68-6.53 110.68-5.81L110.68-5.81Q110.68-4.98 110.20-4.31Q109.72-3.65 108.77-3.40L108.77-3.40L110.77 0ZM106.19-6.98L106.19-4.52L107.59-4.52Q108.28-4.52 108.61-4.85Q108.95-5.18 108.95-5.77L108.95-5.77Q108.95-6.35 108.61-6.67Q108.28-6.98 107.59-6.98L107.59-6.98L106.19-6.98ZM117.06-7.02L113.88-7.02L113.88-4.92L116.70-4.92L116.70-3.59L113.88-3.59L113.88-1.37L117.06-1.37L117.06 0L112.20 0L112.20-8.39L117.06-8.39L117.06-7.02ZM118.72-1.76L120.42-1.76L119.02 1.61L117.94 1.61L118.72-1.76ZM127.38 0.08Q126.50 0.08 125.80-0.22Q125.10-0.52 124.69-1.08Q124.28-1.64 124.27-2.41L124.27-2.41L126.07-2.41Q126.11-1.90 126.44-1.60Q126.77-1.30 127.34-1.30L127.34-1.30Q127.93-1.30 128.27-1.58Q128.60-1.86 128.60-2.32L128.60-2.32Q128.60-2.69 128.38-2.93Q128.15-3.17 127.81-3.31Q127.46-3.44 126.86-3.61L126.86-3.61Q126.05-3.85 125.54-4.09Q125.03-4.32 124.66-4.79Q124.30-5.27 124.30-6.06L124.30-6.06Q124.30-6.80 124.67-7.36Q125.04-7.91 125.71-8.20Q126.38-8.50 127.25-8.50L127.25-8.50Q128.54-8.50 129.35-7.87Q130.16-7.24 130.25-6.11L130.25-6.11L128.40-6.11Q128.38-6.54 128.03-6.82Q127.69-7.10 127.13-7.10L127.13-7.10Q126.64-7.10 126.34-6.85Q126.05-6.60 126.05-6.12L126.05-6.12Q126.05-5.78 126.27-5.56Q126.49-5.34 126.82-5.20Q127.15-5.06 127.75-4.88L127.75-4.88Q128.57-4.64 129.08-4.40Q129.60-4.16 129.97-3.68Q130.34-3.20 130.34-2.42L130.34-2.42Q130.34-1.75 130.00-1.18Q129.65-0.60 128.98-0.26Q128.30 0.08 127.38 0.08L127.38 0.08ZM131.80-8.38L133.48-8.38L133.48 0L131.80 0L131.80-8.38ZM142.30-8.39L142.30 0L140.62 0L136.81-5.75L136.81 0L135.13 0L135.13-8.39L136.81-8.39L140.62-2.63L140.62-8.39L142.30-8.39ZM151.72-5.86L149.78-5.86Q149.50-6.38 148.99-6.66Q148.49-6.94 147.82-6.94L147.82-6.94Q147.07-6.94 146.50-6.60Q145.92-6.26 145.60-5.64Q145.27-5.02 145.27-4.20L145.27-4.20Q145.27-3.36 145.60-2.74Q145.93-2.11 146.52-1.78Q147.11-1.44 147.89-1.44L147.89-1.44Q148.85-1.44 149.46-1.95Q150.07-2.46 150.26-3.37L150.26-3.37L147.38-3.37L147.38-4.66L151.92-4.66L151.92-3.19Q151.75-2.32 151.20-1.57Q150.65-0.83 149.78-0.38Q148.91 0.07 147.83 0.07L147.83 0.07Q146.62 0.07 145.64-0.47Q144.66-1.02 144.10-1.99Q143.54-2.96 143.54-4.20L143.54-4.20Q143.54-5.44 144.10-6.41Q144.66-7.39 145.64-7.94Q146.62-8.48 147.82-8.48L147.82-8.48Q149.23-8.48 150.28-7.79Q151.32-7.10 151.72-5.86L151.72-5.86ZM153.53-8.52L155.34-8.52L155.15-2.72L153.73-2.72L153.53-8.52ZM154.48 0.08Q154.02 0.08 153.73-0.20Q153.43-0.48 153.43-0.90L153.43-0.90Q153.43-1.32 153.73-1.60Q154.02-1.88 154.48-1.88L154.48-1.88Q154.92-1.88 155.21-1.60Q155.50-1.32 155.50-0.90L155.50-0.90Q155.50-0.48 155.21-0.20Q154.92 0.08 154.48 0.08L154.48 0.08Z" transform="translate(-0.264, 8.52)"></path></g></g></g></g>
|
|
5
|
+
</svg>
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import UploadFileIcon from '@mui/icons-material/UploadFile'
|
|
2
2
|
import { Alert, Box, Button, Modal, Typography } from '@mui/material'
|
|
3
|
-
import {
|
|
3
|
+
import { ThemeProvider } from '@mui/material/styles'
|
|
4
|
+
import CssBaseline from '@mui/material/CssBaseline'
|
|
5
|
+
import { useEffect, useState, useMemo } from 'react'
|
|
4
6
|
import { ApiClient, FileOnlyClient, LiveApiClient } from './api'
|
|
5
7
|
import CorrectionMetrics from './components/CorrectionMetrics'
|
|
6
8
|
import LyricsAnalyzer from './components/LyricsAnalyzer'
|
|
9
|
+
import AppHeader from './components/AppHeader'
|
|
7
10
|
import { CorrectionData } from './types'
|
|
11
|
+
import { createAppTheme } from './theme'
|
|
12
|
+
|
|
13
|
+
const THEME_STORAGE_KEY = 'nomad-karaoke-lyrics-theme'
|
|
8
14
|
|
|
9
15
|
export default function App() {
|
|
16
|
+
const [isDarkMode, setIsDarkMode] = useState(() => {
|
|
17
|
+
// Initialize from localStorage
|
|
18
|
+
const stored = localStorage.getItem(THEME_STORAGE_KEY)
|
|
19
|
+
return stored !== 'light' // Default to dark
|
|
20
|
+
})
|
|
10
21
|
const [data, setData] = useState<CorrectionData | null>(null)
|
|
11
22
|
const [showMetadata, setShowMetadata] = useState(false)
|
|
12
23
|
const [error, setError] = useState<string | null>(null)
|
|
@@ -14,6 +25,16 @@ export default function App() {
|
|
|
14
25
|
const [isReadOnly, setIsReadOnly] = useState(true)
|
|
15
26
|
const [audioHash, setAudioHash] = useState<string>('')
|
|
16
27
|
|
|
28
|
+
// Create theme based on mode
|
|
29
|
+
const theme = useMemo(() => createAppTheme(isDarkMode ? 'dark' : 'light'), [isDarkMode])
|
|
30
|
+
|
|
31
|
+
// Handle theme toggle
|
|
32
|
+
const handleToggleTheme = () => {
|
|
33
|
+
const newIsDark = !isDarkMode
|
|
34
|
+
setIsDarkMode(newIsDark)
|
|
35
|
+
localStorage.setItem(THEME_STORAGE_KEY, newIsDark ? 'dark' : 'light')
|
|
36
|
+
}
|
|
37
|
+
|
|
17
38
|
useEffect(() => {
|
|
18
39
|
// Parse query parameters
|
|
19
40
|
const params = new URLSearchParams(window.location.search)
|
|
@@ -145,70 +166,78 @@ export default function App() {
|
|
|
145
166
|
|
|
146
167
|
if (!data) {
|
|
147
168
|
return (
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
<>
|
|
156
|
-
<Alert severity="info" sx={{ mb: 2 }}>
|
|
157
|
-
Running in read-only mode. Connect to an API to enable editing.
|
|
169
|
+
<ThemeProvider theme={theme}>
|
|
170
|
+
<CssBaseline />
|
|
171
|
+
<AppHeader isDarkMode={isDarkMode} onToggleTheme={handleToggleTheme} />
|
|
172
|
+
<Box sx={{ p: 3 }}>
|
|
173
|
+
{error && (
|
|
174
|
+
<Alert severity="error" sx={{ mb: 2 }} onClose={() => setError(null)}>
|
|
175
|
+
{error}
|
|
158
176
|
</Alert>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
177
|
+
)}
|
|
178
|
+
{isReadOnly ? (
|
|
179
|
+
<>
|
|
180
|
+
<Alert severity="info" sx={{ mb: 2 }}>
|
|
181
|
+
Running in read-only mode. Connect to an API to enable editing.
|
|
182
|
+
</Alert>
|
|
183
|
+
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
|
184
|
+
<Typography variant="h4">
|
|
185
|
+
Lyrics Correction Review
|
|
186
|
+
</Typography>
|
|
187
|
+
<Button
|
|
188
|
+
variant="outlined"
|
|
189
|
+
startIcon={<UploadFileIcon />}
|
|
190
|
+
onClick={handleFileLoad}
|
|
191
|
+
>
|
|
192
|
+
Load File
|
|
193
|
+
</Button>
|
|
194
|
+
</Box>
|
|
195
|
+
<Box sx={{ mb: 3 }}>
|
|
196
|
+
<CorrectionMetrics />
|
|
197
|
+
</Box>
|
|
198
|
+
</>
|
|
199
|
+
) : (
|
|
200
|
+
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '50vh' }}>
|
|
201
|
+
<Typography variant="h6" color="text.secondary">
|
|
202
|
+
Loading Lyrics Transcription Review...
|
|
162
203
|
</Typography>
|
|
163
|
-
<Button
|
|
164
|
-
variant="outlined"
|
|
165
|
-
startIcon={<UploadFileIcon />}
|
|
166
|
-
onClick={handleFileLoad}
|
|
167
|
-
>
|
|
168
|
-
Load File
|
|
169
|
-
</Button>
|
|
170
|
-
</Box>
|
|
171
|
-
<Box sx={{ mb: 3 }}>
|
|
172
|
-
<CorrectionMetrics />
|
|
173
204
|
</Box>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
<Typography variant="h6" color="text.secondary">
|
|
178
|
-
Loading Lyrics Correction Review...
|
|
179
|
-
</Typography>
|
|
180
|
-
</Box>
|
|
181
|
-
)}
|
|
182
|
-
</Box>
|
|
205
|
+
)}
|
|
206
|
+
</Box>
|
|
207
|
+
</ThemeProvider>
|
|
183
208
|
)
|
|
184
209
|
}
|
|
185
210
|
|
|
186
211
|
return (
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
<ThemeProvider theme={theme}>
|
|
213
|
+
<CssBaseline />
|
|
214
|
+
<AppHeader isDarkMode={isDarkMode} onToggleTheme={handleToggleTheme} />
|
|
215
|
+
<Box sx={{
|
|
216
|
+
p: 1.5,
|
|
217
|
+
pb: 3,
|
|
218
|
+
maxWidth: '100%',
|
|
219
|
+
overflowX: 'hidden'
|
|
220
|
+
}}>
|
|
221
|
+
{error && (
|
|
222
|
+
<Alert severity="error" sx={{ mb: 1 }} onClose={() => setError(null)}>
|
|
223
|
+
{error}
|
|
224
|
+
</Alert>
|
|
225
|
+
)}
|
|
226
|
+
{isReadOnly && (
|
|
227
|
+
<Alert severity="info" sx={{ mb: 1 }}>
|
|
228
|
+
Running in read-only mode. Connect to an API to enable editing.
|
|
229
|
+
</Alert>
|
|
230
|
+
)}
|
|
231
|
+
<LyricsAnalyzer
|
|
232
|
+
data={data}
|
|
233
|
+
onFileLoad={handleFileLoad}
|
|
234
|
+
onShowMetadata={() => setShowMetadata(true)}
|
|
235
|
+
apiClient={apiClient}
|
|
236
|
+
isReadOnly={isReadOnly}
|
|
237
|
+
audioHash={audioHash}
|
|
238
|
+
/>
|
|
239
|
+
{renderMetadataModal()}
|
|
240
|
+
</Box>
|
|
241
|
+
</ThemeProvider>
|
|
213
242
|
)
|
|
214
243
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
2
|
|
|
3
3
|
type Props = {
|
|
4
4
|
isOpen: boolean;
|
|
@@ -12,22 +12,57 @@ export const AIFeedbackModal: React.FC<Props> = ({ isOpen, onClose, onSubmit, su
|
|
|
12
12
|
const [finalText, setFinalText] = React.useState("");
|
|
13
13
|
const [reasonCategory, setReason] = React.useState("AI_CORRECT");
|
|
14
14
|
const [reasonDetail, setDetail] = React.useState("");
|
|
15
|
+
const modalRef = useRef<HTMLDivElement>(null);
|
|
16
|
+
|
|
17
|
+
// Handle Escape key to close modal
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
20
|
+
if (e.key === 'Escape' && isOpen) {
|
|
21
|
+
onClose();
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
25
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
26
|
+
}, [isOpen, onClose]);
|
|
27
|
+
|
|
28
|
+
// Focus modal on open
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (isOpen && modalRef.current) {
|
|
31
|
+
modalRef.current.focus();
|
|
32
|
+
}
|
|
33
|
+
}, [isOpen]);
|
|
15
34
|
|
|
16
35
|
if (!isOpen) return null;
|
|
17
36
|
|
|
37
|
+
// Dark theme colors matching karaoke-gen
|
|
38
|
+
const colors = {
|
|
39
|
+
background: '#1a1a1a', // slate-800
|
|
40
|
+
text: '#f8fafc', // slate-50
|
|
41
|
+
textSecondary: '#888888', // slate-400
|
|
42
|
+
border: '#2a2a2a', // slate-700
|
|
43
|
+
inputBg: '#0f0f0f', // slate-900
|
|
44
|
+
};
|
|
45
|
+
|
|
18
46
|
return (
|
|
19
|
-
<div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.
|
|
20
|
-
<div
|
|
21
|
-
|
|
22
|
-
|
|
47
|
+
<div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.7)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1300 }}>
|
|
48
|
+
<div
|
|
49
|
+
ref={modalRef}
|
|
50
|
+
role="dialog"
|
|
51
|
+
aria-modal="true"
|
|
52
|
+
aria-labelledby="ai-feedback-title"
|
|
53
|
+
tabIndex={-1}
|
|
54
|
+
style={{ background: colors.background, padding: 16, width: 480, borderRadius: 8, border: `1px solid ${colors.border}`, color: colors.text, outline: 'none' }}
|
|
55
|
+
>
|
|
56
|
+
<h3 id="ai-feedback-title" style={{ color: colors.text, margin: 0 }}>AI Suggestion</h3>
|
|
57
|
+
<p style={{ marginTop: 8, color: colors.text }}>
|
|
23
58
|
{suggestion?.text ?? "No suggestion"}
|
|
24
59
|
{suggestion?.confidence != null ? ` (confidence ${Math.round((suggestion.confidence || 0) * 100)}%)` : null}
|
|
25
60
|
</p>
|
|
26
|
-
{suggestion?.reasoning ? <small>{suggestion.reasoning}</small> : null}
|
|
61
|
+
{suggestion?.reasoning ? <small style={{ color: colors.textSecondary }}>{suggestion.reasoning}</small> : null}
|
|
27
62
|
|
|
28
|
-
<div style={{ marginTop: 12 }}>
|
|
29
|
-
<label>Action</label>
|
|
30
|
-
<select value={reviewerAction} onChange={(e) => setAction(e.target.value)} style={{
|
|
63
|
+
<div style={{ marginTop: 12, display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
64
|
+
<label htmlFor="ai-action-select" style={{ color: colors.text }}>Action</label>
|
|
65
|
+
<select id="ai-action-select" value={reviewerAction} onChange={(e) => setAction(e.target.value)} style={{ background: colors.inputBg, color: colors.text, border: `1px solid ${colors.border}`, borderRadius: 4, padding: '4px 8px' }}>
|
|
31
66
|
<option value="ACCEPT">Accept</option>
|
|
32
67
|
<option value="REJECT">Reject</option>
|
|
33
68
|
<option value="MODIFY">Modify</option>
|
|
@@ -35,15 +70,15 @@ export const AIFeedbackModal: React.FC<Props> = ({ isOpen, onClose, onSubmit, su
|
|
|
35
70
|
</div>
|
|
36
71
|
|
|
37
72
|
{reviewerAction === "MODIFY" ? (
|
|
38
|
-
<div style={{ marginTop: 12 }}>
|
|
39
|
-
<label>Final Text</label>
|
|
40
|
-
<input value={finalText} onChange={(e) => setFinalText(e.target.value)} style={{
|
|
73
|
+
<div style={{ marginTop: 12, display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
74
|
+
<label htmlFor="ai-final-text" style={{ color: colors.text }}>Final Text</label>
|
|
75
|
+
<input id="ai-final-text" value={finalText} onChange={(e) => setFinalText(e.target.value)} style={{ width: "100%", background: colors.inputBg, color: colors.text, border: `1px solid ${colors.border}`, borderRadius: 4, padding: '4px 8px', boxSizing: 'border-box' }} />
|
|
41
76
|
</div>
|
|
42
77
|
) : null}
|
|
43
78
|
|
|
44
|
-
<div style={{ marginTop: 12 }}>
|
|
45
|
-
<label>Reason</label>
|
|
46
|
-
<select value={reasonCategory} onChange={(e) => setReason(e.target.value)} style={{
|
|
79
|
+
<div style={{ marginTop: 12, display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
80
|
+
<label htmlFor="ai-reason-select" style={{ color: colors.text }}>Reason</label>
|
|
81
|
+
<select id="ai-reason-select" value={reasonCategory} onChange={(e) => setReason(e.target.value)} style={{ background: colors.inputBg, color: colors.text, border: `1px solid ${colors.border}`, borderRadius: 4, padding: '4px 8px' }}>
|
|
47
82
|
<option value="AI_CORRECT">AI_CORRECT</option>
|
|
48
83
|
<option value="AI_INCORRECT">AI_INCORRECT</option>
|
|
49
84
|
<option value="AI_SUBOPTIMAL">AI_SUBOPTIMAL</option>
|
|
@@ -52,17 +87,18 @@ export const AIFeedbackModal: React.FC<Props> = ({ isOpen, onClose, onSubmit, su
|
|
|
52
87
|
</select>
|
|
53
88
|
</div>
|
|
54
89
|
|
|
55
|
-
<div style={{ marginTop: 12 }}>
|
|
56
|
-
<label>Details</label>
|
|
57
|
-
<textarea value={reasonDetail} onChange={(e) => setDetail(e.target.value)} style={{
|
|
90
|
+
<div style={{ marginTop: 12, display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
91
|
+
<label htmlFor="ai-details-textarea" style={{ color: colors.text }}>Details</label>
|
|
92
|
+
<textarea id="ai-details-textarea" value={reasonDetail} onChange={(e) => setDetail(e.target.value)} style={{ width: "100%", background: colors.inputBg, color: colors.text, border: `1px solid ${colors.border}`, borderRadius: 4, padding: '4px 8px', boxSizing: 'border-box' }} />
|
|
58
93
|
</div>
|
|
59
94
|
|
|
60
95
|
<div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 16 }}>
|
|
61
|
-
<button onClick={onClose}>Cancel</button>
|
|
96
|
+
<button onClick={onClose} style={{ background: colors.border, color: colors.text, border: 'none', borderRadius: 4, padding: '6px 12px', cursor: 'pointer' }}>Cancel</button>
|
|
62
97
|
<button
|
|
63
98
|
onClick={() =>
|
|
64
99
|
onSubmit({ reviewerAction, finalText: finalText || undefined, reasonCategory, reasonDetail: reasonDetail || undefined })
|
|
65
100
|
}
|
|
101
|
+
style={{ background: '#f97316', color: '#fff', border: 'none', borderRadius: 4, padding: '6px 12px', cursor: 'pointer' }}
|
|
66
102
|
>
|
|
67
103
|
Submit
|
|
68
104
|
</button>
|
|
@@ -73,5 +109,3 @@ export const AIFeedbackModal: React.FC<Props> = ({ isOpen, onClose, onSubmit, su
|
|
|
73
109
|
};
|
|
74
110
|
|
|
75
111
|
export default AIFeedbackModal;
|
|
76
|
-
|
|
77
|
-
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Box, IconButton, Tooltip, Typography, useTheme } from '@mui/material'
|
|
2
|
+
import { Sun, Moon } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
interface AppHeaderProps {
|
|
5
|
+
isDarkMode?: boolean
|
|
6
|
+
onToggleTheme?: () => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function AppHeader({ isDarkMode = true, onToggleTheme }: AppHeaderProps) {
|
|
10
|
+
const theme = useTheme()
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Box
|
|
14
|
+
component="header"
|
|
15
|
+
sx={{
|
|
16
|
+
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
17
|
+
backgroundColor: theme.palette.background.paper,
|
|
18
|
+
backdropFilter: 'blur(8px)',
|
|
19
|
+
position: 'sticky',
|
|
20
|
+
top: 0,
|
|
21
|
+
zIndex: 1100,
|
|
22
|
+
px: 2,
|
|
23
|
+
py: 1.5,
|
|
24
|
+
display: 'flex',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
justifyContent: 'space-between',
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
30
|
+
<img src="/nomad-karaoke-logo.svg" alt="Nomad Karaoke" style={{ height: 40 }} />
|
|
31
|
+
<Typography
|
|
32
|
+
variant="h6"
|
|
33
|
+
sx={{
|
|
34
|
+
fontWeight: 'bold',
|
|
35
|
+
color: theme.palette.text.primary,
|
|
36
|
+
fontSize: '1.1rem',
|
|
37
|
+
lineHeight: 1,
|
|
38
|
+
m: 0,
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
Lyrics Transcription Review
|
|
42
|
+
</Typography>
|
|
43
|
+
</Box>
|
|
44
|
+
|
|
45
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
46
|
+
{onToggleTheme && (
|
|
47
|
+
<Tooltip title={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}>
|
|
48
|
+
<IconButton
|
|
49
|
+
onClick={onToggleTheme}
|
|
50
|
+
sx={{
|
|
51
|
+
color: theme.palette.text.secondary,
|
|
52
|
+
'&:hover': {
|
|
53
|
+
color: theme.palette.text.primary,
|
|
54
|
+
backgroundColor: theme.palette.action.hover,
|
|
55
|
+
},
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
{isDarkMode ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
|
|
59
|
+
</IconButton>
|
|
60
|
+
</Tooltip>
|
|
61
|
+
)}
|
|
62
|
+
</Box>
|
|
63
|
+
</Box>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
@@ -23,6 +23,7 @@ interface CorrectedWordWithActionsProps {
|
|
|
23
23
|
onClick?: () => void
|
|
24
24
|
backgroundColor?: string
|
|
25
25
|
shouldFlash?: boolean
|
|
26
|
+
showActions?: boolean // Controls whether inline action buttons are visible
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const WordContainer = styled(Box, {
|
|
@@ -42,7 +43,7 @@ const WordContainer = styled(Box, {
|
|
|
42
43
|
'50%': { opacity: 0.5 }
|
|
43
44
|
},
|
|
44
45
|
'&:hover': {
|
|
45
|
-
backgroundColor: '
|
|
46
|
+
backgroundColor: 'rgba(34, 197, 94, 0.35)' // green tint hover for dark mode
|
|
46
47
|
}
|
|
47
48
|
}))
|
|
48
49
|
|
|
@@ -51,7 +52,7 @@ const OriginalWordLabel = styled(Box)({
|
|
|
51
52
|
top: '-14px',
|
|
52
53
|
left: '0',
|
|
53
54
|
fontSize: '0.6rem',
|
|
54
|
-
color: '#
|
|
55
|
+
color: '#888888', // slate-400 for dark mode
|
|
55
56
|
textDecoration: 'line-through',
|
|
56
57
|
opacity: 0.7,
|
|
57
58
|
whiteSpace: 'nowrap',
|
|
@@ -71,10 +72,10 @@ const ActionButton = styled(IconButton)(({ theme }) => ({
|
|
|
71
72
|
minHeight: '20px',
|
|
72
73
|
width: '20px',
|
|
73
74
|
height: '20px',
|
|
74
|
-
backgroundColor: 'rgba(
|
|
75
|
-
border: '1px solid rgba(
|
|
75
|
+
backgroundColor: 'rgba(30, 41, 59, 0.9)', // slate-800 with opacity for dark mode
|
|
76
|
+
border: '1px solid rgba(248, 250, 252, 0.1)', // light border for dark mode
|
|
76
77
|
'&:hover': {
|
|
77
|
-
backgroundColor: 'rgba(
|
|
78
|
+
backgroundColor: 'rgba(51, 65, 85, 1)', // slate-700 for dark mode
|
|
78
79
|
transform: 'scale(1.1)'
|
|
79
80
|
},
|
|
80
81
|
'& .MuiSvgIcon-root': {
|
|
@@ -98,7 +99,8 @@ export default function CorrectedWordWithActions({
|
|
|
98
99
|
onAccept,
|
|
99
100
|
onClick,
|
|
100
101
|
backgroundColor,
|
|
101
|
-
shouldFlash
|
|
102
|
+
shouldFlash,
|
|
103
|
+
showActions = true
|
|
102
104
|
}: CorrectedWordWithActionsProps) {
|
|
103
105
|
const theme = useTheme()
|
|
104
106
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
|
|
@@ -115,7 +117,7 @@ export default function CorrectedWordWithActions({
|
|
|
115
117
|
onClick={onClick}
|
|
116
118
|
>
|
|
117
119
|
<OriginalWordLabel>{originalWord}</OriginalWordLabel>
|
|
118
|
-
|
|
120
|
+
|
|
119
121
|
<Box
|
|
120
122
|
component="span"
|
|
121
123
|
sx={{
|
|
@@ -127,40 +129,42 @@ export default function CorrectedWordWithActions({
|
|
|
127
129
|
{word}
|
|
128
130
|
</Box>
|
|
129
131
|
|
|
130
|
-
|
|
131
|
-
<
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<Tooltip title="Edit correction" placement="top" arrow>
|
|
142
|
-
<ActionButton
|
|
143
|
-
size="small"
|
|
144
|
-
onClick={(e) => handleAction(e, onEdit)}
|
|
145
|
-
aria-label="edit correction"
|
|
146
|
-
>
|
|
147
|
-
<EditIcon />
|
|
148
|
-
</ActionButton>
|
|
149
|
-
</Tooltip>
|
|
132
|
+
{showActions && (
|
|
133
|
+
<ActionsContainer>
|
|
134
|
+
<Tooltip title="Revert to original" placement="top" arrow>
|
|
135
|
+
<ActionButton
|
|
136
|
+
size="small"
|
|
137
|
+
onClick={(e) => handleAction(e, onRevert)}
|
|
138
|
+
aria-label="revert correction"
|
|
139
|
+
>
|
|
140
|
+
<UndoIcon />
|
|
141
|
+
</ActionButton>
|
|
142
|
+
</Tooltip>
|
|
150
143
|
|
|
151
|
-
|
|
152
|
-
<Tooltip title="Accept correction" placement="top" arrow>
|
|
144
|
+
<Tooltip title="Edit correction" placement="top" arrow>
|
|
153
145
|
<ActionButton
|
|
154
146
|
size="small"
|
|
155
|
-
onClick={(e) => handleAction(e,
|
|
156
|
-
aria-label="
|
|
157
|
-
sx={{ color: 'success.main' }}
|
|
147
|
+
onClick={(e) => handleAction(e, onEdit)}
|
|
148
|
+
aria-label="edit correction"
|
|
158
149
|
>
|
|
159
|
-
<
|
|
150
|
+
<EditIcon />
|
|
160
151
|
</ActionButton>
|
|
161
152
|
</Tooltip>
|
|
162
|
-
|
|
163
|
-
|
|
153
|
+
|
|
154
|
+
{!isMobile && (
|
|
155
|
+
<Tooltip title="Accept correction" placement="top" arrow>
|
|
156
|
+
<ActionButton
|
|
157
|
+
size="small"
|
|
158
|
+
onClick={(e) => handleAction(e, onAccept)}
|
|
159
|
+
aria-label="accept correction"
|
|
160
|
+
sx={{ color: 'success.main' }}
|
|
161
|
+
>
|
|
162
|
+
<CheckCircleOutlineIcon />
|
|
163
|
+
</ActionButton>
|
|
164
|
+
</Tooltip>
|
|
165
|
+
)}
|
|
166
|
+
</ActionsContainer>
|
|
167
|
+
)}
|
|
164
168
|
</WordContainer>
|
|
165
169
|
)
|
|
166
170
|
}
|