karaoke-gen 0.57.0__py3-none-any.whl → 0.71.23__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 (268) hide show
  1. karaoke_gen/audio_fetcher.py +461 -0
  2. karaoke_gen/audio_processor.py +407 -30
  3. karaoke_gen/config.py +62 -113
  4. karaoke_gen/file_handler.py +32 -59
  5. karaoke_gen/karaoke_finalise/karaoke_finalise.py +148 -67
  6. karaoke_gen/karaoke_gen.py +270 -61
  7. karaoke_gen/lyrics_processor.py +13 -1
  8. karaoke_gen/metadata.py +78 -73
  9. karaoke_gen/pipeline/__init__.py +87 -0
  10. karaoke_gen/pipeline/base.py +215 -0
  11. karaoke_gen/pipeline/context.py +230 -0
  12. karaoke_gen/pipeline/executors/__init__.py +21 -0
  13. karaoke_gen/pipeline/executors/local.py +159 -0
  14. karaoke_gen/pipeline/executors/remote.py +257 -0
  15. karaoke_gen/pipeline/stages/__init__.py +27 -0
  16. karaoke_gen/pipeline/stages/finalize.py +202 -0
  17. karaoke_gen/pipeline/stages/render.py +165 -0
  18. karaoke_gen/pipeline/stages/screens.py +139 -0
  19. karaoke_gen/pipeline/stages/separation.py +191 -0
  20. karaoke_gen/pipeline/stages/transcription.py +191 -0
  21. karaoke_gen/style_loader.py +531 -0
  22. karaoke_gen/utils/bulk_cli.py +6 -0
  23. karaoke_gen/utils/cli_args.py +424 -0
  24. karaoke_gen/utils/gen_cli.py +26 -261
  25. karaoke_gen/utils/remote_cli.py +1815 -0
  26. karaoke_gen/video_background_processor.py +351 -0
  27. karaoke_gen-0.71.23.dist-info/METADATA +610 -0
  28. karaoke_gen-0.71.23.dist-info/RECORD +275 -0
  29. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.dist-info}/WHEEL +1 -1
  30. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.dist-info}/entry_points.txt +1 -0
  31. lyrics_transcriber/__init__.py +10 -0
  32. lyrics_transcriber/cli/__init__.py +0 -0
  33. lyrics_transcriber/cli/cli_main.py +285 -0
  34. lyrics_transcriber/core/__init__.py +0 -0
  35. lyrics_transcriber/core/config.py +50 -0
  36. lyrics_transcriber/core/controller.py +520 -0
  37. lyrics_transcriber/correction/__init__.py +0 -0
  38. lyrics_transcriber/correction/agentic/__init__.py +9 -0
  39. lyrics_transcriber/correction/agentic/adapter.py +71 -0
  40. lyrics_transcriber/correction/agentic/agent.py +313 -0
  41. lyrics_transcriber/correction/agentic/feedback/aggregator.py +12 -0
  42. lyrics_transcriber/correction/agentic/feedback/collector.py +17 -0
  43. lyrics_transcriber/correction/agentic/feedback/retention.py +24 -0
  44. lyrics_transcriber/correction/agentic/feedback/store.py +76 -0
  45. lyrics_transcriber/correction/agentic/handlers/__init__.py +24 -0
  46. lyrics_transcriber/correction/agentic/handlers/ambiguous.py +44 -0
  47. lyrics_transcriber/correction/agentic/handlers/background_vocals.py +68 -0
  48. lyrics_transcriber/correction/agentic/handlers/base.py +51 -0
  49. lyrics_transcriber/correction/agentic/handlers/complex_multi_error.py +46 -0
  50. lyrics_transcriber/correction/agentic/handlers/extra_words.py +74 -0
  51. lyrics_transcriber/correction/agentic/handlers/no_error.py +42 -0
  52. lyrics_transcriber/correction/agentic/handlers/punctuation.py +44 -0
  53. lyrics_transcriber/correction/agentic/handlers/registry.py +60 -0
  54. lyrics_transcriber/correction/agentic/handlers/repeated_section.py +44 -0
  55. lyrics_transcriber/correction/agentic/handlers/sound_alike.py +126 -0
  56. lyrics_transcriber/correction/agentic/models/__init__.py +5 -0
  57. lyrics_transcriber/correction/agentic/models/ai_correction.py +31 -0
  58. lyrics_transcriber/correction/agentic/models/correction_session.py +30 -0
  59. lyrics_transcriber/correction/agentic/models/enums.py +38 -0
  60. lyrics_transcriber/correction/agentic/models/human_feedback.py +30 -0
  61. lyrics_transcriber/correction/agentic/models/learning_data.py +26 -0
  62. lyrics_transcriber/correction/agentic/models/observability_metrics.py +28 -0
  63. lyrics_transcriber/correction/agentic/models/schemas.py +46 -0
  64. lyrics_transcriber/correction/agentic/models/utils.py +19 -0
  65. lyrics_transcriber/correction/agentic/observability/__init__.py +5 -0
  66. lyrics_transcriber/correction/agentic/observability/langfuse_integration.py +35 -0
  67. lyrics_transcriber/correction/agentic/observability/metrics.py +46 -0
  68. lyrics_transcriber/correction/agentic/observability/performance.py +19 -0
  69. lyrics_transcriber/correction/agentic/prompts/__init__.py +2 -0
  70. lyrics_transcriber/correction/agentic/prompts/classifier.py +227 -0
  71. lyrics_transcriber/correction/agentic/providers/__init__.py +6 -0
  72. lyrics_transcriber/correction/agentic/providers/base.py +36 -0
  73. lyrics_transcriber/correction/agentic/providers/circuit_breaker.py +145 -0
  74. lyrics_transcriber/correction/agentic/providers/config.py +73 -0
  75. lyrics_transcriber/correction/agentic/providers/constants.py +24 -0
  76. lyrics_transcriber/correction/agentic/providers/health.py +28 -0
  77. lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +212 -0
  78. lyrics_transcriber/correction/agentic/providers/model_factory.py +209 -0
  79. lyrics_transcriber/correction/agentic/providers/response_cache.py +218 -0
  80. lyrics_transcriber/correction/agentic/providers/response_parser.py +111 -0
  81. lyrics_transcriber/correction/agentic/providers/retry_executor.py +127 -0
  82. lyrics_transcriber/correction/agentic/router.py +35 -0
  83. lyrics_transcriber/correction/agentic/workflows/__init__.py +5 -0
  84. lyrics_transcriber/correction/agentic/workflows/consensus_workflow.py +24 -0
  85. lyrics_transcriber/correction/agentic/workflows/correction_graph.py +59 -0
  86. lyrics_transcriber/correction/agentic/workflows/feedback_workflow.py +24 -0
  87. lyrics_transcriber/correction/anchor_sequence.py +1043 -0
  88. lyrics_transcriber/correction/corrector.py +760 -0
  89. lyrics_transcriber/correction/feedback/__init__.py +2 -0
  90. lyrics_transcriber/correction/feedback/schemas.py +107 -0
  91. lyrics_transcriber/correction/feedback/store.py +236 -0
  92. lyrics_transcriber/correction/handlers/__init__.py +0 -0
  93. lyrics_transcriber/correction/handlers/base.py +52 -0
  94. lyrics_transcriber/correction/handlers/extend_anchor.py +149 -0
  95. lyrics_transcriber/correction/handlers/levenshtein.py +189 -0
  96. lyrics_transcriber/correction/handlers/llm.py +293 -0
  97. lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
  98. lyrics_transcriber/correction/handlers/no_space_punct_match.py +154 -0
  99. lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +85 -0
  100. lyrics_transcriber/correction/handlers/repeat.py +88 -0
  101. lyrics_transcriber/correction/handlers/sound_alike.py +259 -0
  102. lyrics_transcriber/correction/handlers/syllables_match.py +252 -0
  103. lyrics_transcriber/correction/handlers/word_count_match.py +80 -0
  104. lyrics_transcriber/correction/handlers/word_operations.py +187 -0
  105. lyrics_transcriber/correction/operations.py +352 -0
  106. lyrics_transcriber/correction/phrase_analyzer.py +435 -0
  107. lyrics_transcriber/correction/text_utils.py +30 -0
  108. lyrics_transcriber/frontend/.gitignore +23 -0
  109. lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +935 -0
  110. lyrics_transcriber/frontend/.yarnrc.yml +3 -0
  111. lyrics_transcriber/frontend/README.md +50 -0
  112. lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
  113. lyrics_transcriber/frontend/__init__.py +25 -0
  114. lyrics_transcriber/frontend/eslint.config.js +28 -0
  115. lyrics_transcriber/frontend/index.html +18 -0
  116. lyrics_transcriber/frontend/package.json +42 -0
  117. lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
  118. lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
  119. lyrics_transcriber/frontend/public/apple-touch-icon.png +0 -0
  120. lyrics_transcriber/frontend/public/favicon-16x16.png +0 -0
  121. lyrics_transcriber/frontend/public/favicon-32x32.png +0 -0
  122. lyrics_transcriber/frontend/public/favicon.ico +0 -0
  123. lyrics_transcriber/frontend/public/nomad-karaoke-logo.png +0 -0
  124. lyrics_transcriber/frontend/src/App.tsx +212 -0
  125. lyrics_transcriber/frontend/src/api.ts +239 -0
  126. lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +77 -0
  127. lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
  128. lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +204 -0
  129. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +180 -0
  130. lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +167 -0
  131. lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +359 -0
  132. lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +281 -0
  133. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +162 -0
  134. lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +257 -0
  135. lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
  136. lyrics_transcriber/frontend/src/components/EditModal.tsx +702 -0
  137. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +496 -0
  138. lyrics_transcriber/frontend/src/components/EditWordList.tsx +379 -0
  139. lyrics_transcriber/frontend/src/components/FileUpload.tsx +77 -0
  140. lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
  141. lyrics_transcriber/frontend/src/components/Header.tsx +387 -0
  142. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +1373 -0
  143. lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +51 -0
  144. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +67 -0
  145. lyrics_transcriber/frontend/src/components/ModelSelector.tsx +23 -0
  146. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +144 -0
  147. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +268 -0
  148. lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +688 -0
  149. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +354 -0
  150. lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
  151. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +376 -0
  152. lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +131 -0
  153. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +256 -0
  154. lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
  155. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +379 -0
  156. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +56 -0
  157. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +87 -0
  158. lyrics_transcriber/frontend/src/components/shared/constants.ts +20 -0
  159. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +180 -0
  160. lyrics_transcriber/frontend/src/components/shared/styles.ts +13 -0
  161. lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
  162. lyrics_transcriber/frontend/src/components/shared/types.ts +129 -0
  163. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +177 -0
  164. lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
  165. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +75 -0
  166. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +360 -0
  167. lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +110 -0
  168. lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
  169. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +435 -0
  170. lyrics_transcriber/frontend/src/main.tsx +17 -0
  171. lyrics_transcriber/frontend/src/theme.ts +177 -0
  172. lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
  173. lyrics_transcriber/frontend/src/types.js +2 -0
  174. lyrics_transcriber/frontend/src/types.ts +199 -0
  175. lyrics_transcriber/frontend/src/validation.ts +132 -0
  176. lyrics_transcriber/frontend/src/vite-env.d.ts +1 -0
  177. lyrics_transcriber/frontend/tsconfig.app.json +26 -0
  178. lyrics_transcriber/frontend/tsconfig.json +25 -0
  179. lyrics_transcriber/frontend/tsconfig.node.json +23 -0
  180. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -0
  181. lyrics_transcriber/frontend/update_version.js +11 -0
  182. lyrics_transcriber/frontend/vite.config.d.ts +2 -0
  183. lyrics_transcriber/frontend/vite.config.js +10 -0
  184. lyrics_transcriber/frontend/vite.config.ts +11 -0
  185. lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
  186. lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
  187. lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
  188. lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js +42039 -0
  189. lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js.map +1 -0
  190. lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
  191. lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
  192. lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
  193. lyrics_transcriber/frontend/web_assets/index.html +18 -0
  194. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
  195. lyrics_transcriber/frontend/yarn.lock +3752 -0
  196. lyrics_transcriber/lyrics/__init__.py +0 -0
  197. lyrics_transcriber/lyrics/base_lyrics_provider.py +211 -0
  198. lyrics_transcriber/lyrics/file_provider.py +95 -0
  199. lyrics_transcriber/lyrics/genius.py +384 -0
  200. lyrics_transcriber/lyrics/lrclib.py +231 -0
  201. lyrics_transcriber/lyrics/musixmatch.py +156 -0
  202. lyrics_transcriber/lyrics/spotify.py +290 -0
  203. lyrics_transcriber/lyrics/user_input_provider.py +44 -0
  204. lyrics_transcriber/output/__init__.py +0 -0
  205. lyrics_transcriber/output/ass/__init__.py +21 -0
  206. lyrics_transcriber/output/ass/ass.py +2088 -0
  207. lyrics_transcriber/output/ass/ass_specs.txt +732 -0
  208. lyrics_transcriber/output/ass/config.py +180 -0
  209. lyrics_transcriber/output/ass/constants.py +23 -0
  210. lyrics_transcriber/output/ass/event.py +94 -0
  211. lyrics_transcriber/output/ass/formatters.py +132 -0
  212. lyrics_transcriber/output/ass/lyrics_line.py +265 -0
  213. lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
  214. lyrics_transcriber/output/ass/section_detector.py +89 -0
  215. lyrics_transcriber/output/ass/section_screen.py +106 -0
  216. lyrics_transcriber/output/ass/style.py +187 -0
  217. lyrics_transcriber/output/cdg.py +619 -0
  218. lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
  219. lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
  220. lyrics_transcriber/output/cdgmaker/composer.py +2260 -0
  221. lyrics_transcriber/output/cdgmaker/config.py +151 -0
  222. lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
  223. lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
  224. lyrics_transcriber/output/cdgmaker/pack.py +507 -0
  225. lyrics_transcriber/output/cdgmaker/render.py +346 -0
  226. lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
  227. lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
  228. lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
  229. lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
  230. lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
  231. lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
  232. lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
  233. lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
  234. lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
  235. lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
  236. lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
  237. lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
  238. lyrics_transcriber/output/cdgmaker/utils.py +132 -0
  239. lyrics_transcriber/output/countdown_processor.py +267 -0
  240. lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
  241. lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
  242. lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
  243. lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
  244. lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
  245. lyrics_transcriber/output/fonts/arial.ttf +0 -0
  246. lyrics_transcriber/output/fonts/georgia.ttf +0 -0
  247. lyrics_transcriber/output/fonts/verdana.ttf +0 -0
  248. lyrics_transcriber/output/generator.py +257 -0
  249. lyrics_transcriber/output/lrc_to_cdg.py +61 -0
  250. lyrics_transcriber/output/lyrics_file.py +102 -0
  251. lyrics_transcriber/output/plain_text.py +96 -0
  252. lyrics_transcriber/output/segment_resizer.py +431 -0
  253. lyrics_transcriber/output/subtitles.py +397 -0
  254. lyrics_transcriber/output/video.py +544 -0
  255. lyrics_transcriber/review/__init__.py +0 -0
  256. lyrics_transcriber/review/server.py +676 -0
  257. lyrics_transcriber/storage/__init__.py +0 -0
  258. lyrics_transcriber/storage/dropbox.py +225 -0
  259. lyrics_transcriber/transcribers/__init__.py +0 -0
  260. lyrics_transcriber/transcribers/audioshake.py +290 -0
  261. lyrics_transcriber/transcribers/base_transcriber.py +157 -0
  262. lyrics_transcriber/transcribers/whisper.py +330 -0
  263. lyrics_transcriber/types.py +648 -0
  264. lyrics_transcriber/utils/__init__.py +0 -0
  265. lyrics_transcriber/utils/word_utils.py +27 -0
  266. karaoke_gen-0.57.0.dist-info/METADATA +0 -167
  267. karaoke_gen-0.57.0.dist-info/RECORD +0 -23
  268. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,239 @@
1
+ import { CorrectionData, CorrectionAnnotation } from './types';
2
+ import { validateCorrectionData } from './validation';
3
+
4
+ // New file to handle API communication
5
+ export interface ApiClient {
6
+ getCorrectionData: () => Promise<CorrectionData>;
7
+ submitCorrections: (data: CorrectionData) => Promise<void>;
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;
13
+ addLyrics: (source: string, lyrics: string) => Promise<CorrectionData>;
14
+ submitAnnotations: (annotations: Omit<CorrectionAnnotation, 'annotation_id' | 'timestamp'>[]) => Promise<void>;
15
+ getAnnotationStats: () => Promise<any>;
16
+ }
17
+
18
+ // Add new interface for the minimal update payload
19
+ interface CorrectionUpdate {
20
+ corrections: CorrectionData['corrections'];
21
+ corrected_segments: CorrectionData['corrected_segments'];
22
+ }
23
+
24
+ // Add new interface for preview response
25
+ interface PreviewVideoResponse {
26
+ status: "success" | "error";
27
+ preview_hash?: string;
28
+ message?: string;
29
+ }
30
+
31
+ // Add new interface for adding lyrics
32
+ interface AddLyricsRequest {
33
+ source: string;
34
+ lyrics: string;
35
+ }
36
+
37
+ export class LiveApiClient implements ApiClient {
38
+ constructor(private baseUrl: string) {
39
+ this.baseUrl = baseUrl.replace(/\/$/, '')
40
+ }
41
+
42
+ public isUpdatingHandlers = false;
43
+
44
+ async getCorrectionData(): Promise<CorrectionData> {
45
+ const response = await fetch(`${this.baseUrl}/correction-data`);
46
+ if (!response.ok) {
47
+ throw new Error(`API error: ${response.statusText}`);
48
+ }
49
+ const rawData = await response.json();
50
+
51
+ try {
52
+ // This will throw if validation fails
53
+ return validateCorrectionData(rawData);
54
+ } catch (error) {
55
+ console.error('Data validation failed:', error);
56
+ throw new Error('Invalid data received from server: missing or incorrect fields');
57
+ }
58
+ }
59
+
60
+ async submitCorrections(data: CorrectionData): Promise<void> {
61
+ // Extract only the needed fields
62
+ const updatePayload: CorrectionUpdate = {
63
+ corrections: data.corrections,
64
+ corrected_segments: data.corrected_segments
65
+ };
66
+
67
+ const response = await fetch(`${this.baseUrl}/complete`, {
68
+ method: 'POST',
69
+ headers: {
70
+ 'Content-Type': 'application/json',
71
+ },
72
+ body: JSON.stringify(updatePayload)
73
+ });
74
+
75
+ if (!response.ok) {
76
+ throw new Error(`API error: ${response.statusText}`);
77
+ }
78
+ }
79
+
80
+ getAudioUrl(audioHash: string): string {
81
+ return `${this.baseUrl}/audio/${audioHash}`
82
+ }
83
+
84
+ async generatePreviewVideo(data: CorrectionData): Promise<PreviewVideoResponse> {
85
+ // Extract only the needed fields, just like in submitCorrections
86
+ const updatePayload: CorrectionUpdate = {
87
+ corrections: data.corrections,
88
+ corrected_segments: data.corrected_segments
89
+ };
90
+
91
+ const response = await fetch(`${this.baseUrl}/preview-video`, {
92
+ method: 'POST',
93
+ headers: {
94
+ 'Content-Type': 'application/json',
95
+ },
96
+ body: JSON.stringify(updatePayload)
97
+ });
98
+
99
+ if (!response.ok) {
100
+ return {
101
+ status: 'error',
102
+ message: `API error: ${response.statusText}`
103
+ };
104
+ }
105
+
106
+ return await response.json();
107
+ }
108
+
109
+ getPreviewVideoUrl(previewHash: string): string {
110
+ return `${this.baseUrl}/preview-video/${previewHash}`;
111
+ }
112
+
113
+ async updateHandlers(enabledHandlers: string[]): Promise<CorrectionData> {
114
+ console.log('API: Starting handler update...');
115
+ this.isUpdatingHandlers = true;
116
+ console.log('API: Set isUpdatingHandlers to', this.isUpdatingHandlers);
117
+
118
+ try {
119
+ const response = await fetch(`${this.baseUrl}/handlers`, {
120
+ method: 'POST',
121
+ headers: {
122
+ 'Content-Type': 'application/json',
123
+ },
124
+ body: JSON.stringify(enabledHandlers)
125
+ });
126
+
127
+ if (!response.ok) {
128
+ throw new Error(`API error: ${response.statusText}`);
129
+ }
130
+
131
+ const data = await response.json();
132
+ if (data.status === 'error') {
133
+ throw new Error(data.message || 'Failed to update handlers');
134
+ }
135
+
136
+ console.log('API: Handler update successful');
137
+ return validateCorrectionData(data.data);
138
+ } finally {
139
+ this.isUpdatingHandlers = false;
140
+ console.log('API: Set isUpdatingHandlers to', this.isUpdatingHandlers);
141
+ }
142
+ }
143
+
144
+ async addLyrics(source: string, lyrics: string): Promise<CorrectionData> {
145
+ const payload: AddLyricsRequest = {
146
+ source,
147
+ lyrics
148
+ };
149
+
150
+ const response = await fetch(`${this.baseUrl}/add-lyrics`, {
151
+ method: 'POST',
152
+ headers: {
153
+ 'Content-Type': 'application/json',
154
+ },
155
+ body: JSON.stringify(payload)
156
+ });
157
+
158
+ if (!response.ok) {
159
+ throw new Error(`API error: ${response.statusText}`);
160
+ }
161
+
162
+ const data = await response.json();
163
+ if (data.status === 'error') {
164
+ throw new Error(data.message || 'Failed to add lyrics');
165
+ }
166
+
167
+ return validateCorrectionData(data.data);
168
+ }
169
+
170
+ async submitAnnotations(annotations: Omit<CorrectionAnnotation, 'annotation_id' | 'timestamp'>[]): Promise<void> {
171
+ // Submit each annotation to the backend
172
+ for (const annotation of annotations) {
173
+ const response = await fetch(`${this.baseUrl}/v1/annotations`, {
174
+ method: 'POST',
175
+ headers: {
176
+ 'Content-Type': 'application/json',
177
+ },
178
+ body: JSON.stringify(annotation)
179
+ });
180
+
181
+ if (!response.ok) {
182
+ console.error(`Failed to submit annotation:`, annotation);
183
+ // Continue with other annotations even if one fails
184
+ }
185
+ }
186
+ }
187
+
188
+ async getAnnotationStats(): Promise<any> {
189
+ const response = await fetch(`${this.baseUrl}/v1/annotations/stats`);
190
+ if (!response.ok) {
191
+ throw new Error(`API error: ${response.statusText}`);
192
+ }
193
+ return await response.json();
194
+ }
195
+ }
196
+
197
+ export class FileOnlyClient implements ApiClient {
198
+ async getCorrectionData(): Promise<CorrectionData> {
199
+ throw new Error('Not supported in file-only mode');
200
+ }
201
+
202
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
203
+ async submitCorrections(_data: CorrectionData): Promise<void> {
204
+ throw new Error('Not supported in file-only mode');
205
+ }
206
+
207
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
208
+ getAudioUrl(_audioHash: string): string {
209
+ throw new Error('Not supported in file-only mode');
210
+ }
211
+
212
+ async generatePreviewVideo(): Promise<PreviewVideoResponse> {
213
+ throw new Error('Not supported in file-only mode');
214
+ }
215
+
216
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
217
+ getPreviewVideoUrl(_previewHash: string): string {
218
+ throw new Error('Not supported in file-only mode');
219
+ }
220
+
221
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
222
+ async updateHandlers(_enabledHandlers: string[]): Promise<CorrectionData> {
223
+ throw new Error('Not supported in file-only mode');
224
+ }
225
+
226
+ async addLyrics(): Promise<CorrectionData> {
227
+ throw new Error('Not supported in file-only mode');
228
+ }
229
+
230
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
231
+ async submitAnnotations(_annotations: Omit<CorrectionAnnotation, 'annotation_id' | 'timestamp'>[]): Promise<void> {
232
+ throw new Error('Not supported in file-only mode');
233
+ }
234
+
235
+ async getAnnotationStats(): Promise<any> {
236
+ throw new Error('Not supported in file-only mode');
237
+ }
238
+ }
239
+
@@ -0,0 +1,77 @@
1
+ import React from "react";
2
+
3
+ type Props = {
4
+ isOpen: boolean;
5
+ onClose: () => void;
6
+ onSubmit: (payload: { reviewerAction: string; finalText?: string; reasonCategory: string; reasonDetail?: string }) => void;
7
+ suggestion?: { text: string; reasoning?: string; confidence?: number };
8
+ };
9
+
10
+ export const AIFeedbackModal: React.FC<Props> = ({ isOpen, onClose, onSubmit, suggestion }) => {
11
+ const [reviewerAction, setAction] = React.useState("ACCEPT");
12
+ const [finalText, setFinalText] = React.useState("");
13
+ const [reasonCategory, setReason] = React.useState("AI_CORRECT");
14
+ const [reasonDetail, setDetail] = React.useState("");
15
+
16
+ if (!isOpen) return null;
17
+
18
+ return (
19
+ <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)", display: "flex", alignItems: "center", justifyContent: "center" }}>
20
+ <div style={{ background: "#fff", padding: 16, width: 480, borderRadius: 8 }}>
21
+ <h3>AI Suggestion</h3>
22
+ <p style={{ marginTop: 8 }}>
23
+ {suggestion?.text ?? "No suggestion"}
24
+ {suggestion?.confidence != null ? ` (confidence ${Math.round((suggestion.confidence || 0) * 100)}%)` : null}
25
+ </p>
26
+ {suggestion?.reasoning ? <small>{suggestion.reasoning}</small> : null}
27
+
28
+ <div style={{ marginTop: 12 }}>
29
+ <label>Action</label>
30
+ <select value={reviewerAction} onChange={(e) => setAction(e.target.value)} style={{ marginLeft: 8 }}>
31
+ <option value="ACCEPT">Accept</option>
32
+ <option value="REJECT">Reject</option>
33
+ <option value="MODIFY">Modify</option>
34
+ </select>
35
+ </div>
36
+
37
+ {reviewerAction === "MODIFY" ? (
38
+ <div style={{ marginTop: 12 }}>
39
+ <label>Final Text</label>
40
+ <input value={finalText} onChange={(e) => setFinalText(e.target.value)} style={{ marginLeft: 8, width: "100%" }} />
41
+ </div>
42
+ ) : null}
43
+
44
+ <div style={{ marginTop: 12 }}>
45
+ <label>Reason</label>
46
+ <select value={reasonCategory} onChange={(e) => setReason(e.target.value)} style={{ marginLeft: 8 }}>
47
+ <option value="AI_CORRECT">AI_CORRECT</option>
48
+ <option value="AI_INCORRECT">AI_INCORRECT</option>
49
+ <option value="AI_SUBOPTIMAL">AI_SUBOPTIMAL</option>
50
+ <option value="CONTEXT_NEEDED">CONTEXT_NEEDED</option>
51
+ <option value="SUBJECTIVE_PREFERENCE">SUBJECTIVE_PREFERENCE</option>
52
+ </select>
53
+ </div>
54
+
55
+ <div style={{ marginTop: 12 }}>
56
+ <label>Details</label>
57
+ <textarea value={reasonDetail} onChange={(e) => setDetail(e.target.value)} style={{ marginLeft: 8, width: "100%" }} />
58
+ </div>
59
+
60
+ <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 16 }}>
61
+ <button onClick={onClose}>Cancel</button>
62
+ <button
63
+ onClick={() =>
64
+ onSubmit({ reviewerAction, finalText: finalText || undefined, reasonCategory, reasonDetail: reasonDetail || undefined })
65
+ }
66
+ >
67
+ Submit
68
+ </button>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ );
73
+ };
74
+
75
+ export default AIFeedbackModal;
76
+
77
+
@@ -0,0 +1,114 @@
1
+ import { useState } from 'react'
2
+ import {
3
+ Dialog,
4
+ DialogTitle,
5
+ DialogContent,
6
+ DialogActions,
7
+ Button,
8
+ TextField,
9
+ Box,
10
+ CircularProgress,
11
+ Typography
12
+ } from '@mui/material'
13
+
14
+ interface AddLyricsModalProps {
15
+ open: boolean
16
+ onClose: () => void
17
+ onSubmit: (source: string, lyrics: string) => Promise<void>
18
+ isSubmitting: boolean
19
+ }
20
+
21
+ export default function AddLyricsModal({
22
+ open,
23
+ onClose,
24
+ onSubmit,
25
+ isSubmitting
26
+ }: AddLyricsModalProps) {
27
+ const [source, setSource] = useState('')
28
+ const [lyrics, setLyrics] = useState('')
29
+ const [error, setError] = useState<string | null>(null)
30
+
31
+ const handleSubmit = async () => {
32
+ if (!source.trim()) {
33
+ setError('Please enter a source name')
34
+ return
35
+ }
36
+ if (!lyrics.trim()) {
37
+ setError('Please enter lyrics text')
38
+ return
39
+ }
40
+
41
+ try {
42
+ await onSubmit(source.trim(), lyrics.trim())
43
+ // Reset form on success
44
+ setSource('')
45
+ setLyrics('')
46
+ setError(null)
47
+ onClose()
48
+ } catch (err) {
49
+ setError(err instanceof Error ? err.message : 'Failed to add lyrics')
50
+ }
51
+ }
52
+
53
+ const handleClose = () => {
54
+ // Don't allow closing if currently submitting
55
+ if (isSubmitting) return
56
+
57
+ setSource('')
58
+ setLyrics('')
59
+ setError(null)
60
+ onClose()
61
+ }
62
+
63
+ return (
64
+ <Dialog
65
+ open={open}
66
+ onClose={handleClose}
67
+ maxWidth="md"
68
+ fullWidth
69
+ disableEscapeKeyDown={isSubmitting}
70
+ >
71
+ <DialogTitle>Add Reference Lyrics</DialogTitle>
72
+ <DialogContent>
73
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 1 }}>
74
+ {error && (
75
+ <Typography color="error" variant="body2">
76
+ {error}
77
+ </Typography>
78
+ )}
79
+ <TextField
80
+ label="Source Name"
81
+ value={source}
82
+ onChange={(e) => setSource(e.target.value)}
83
+ disabled={isSubmitting}
84
+ fullWidth
85
+ placeholder="e.g., Official Lyrics, Album Booklet"
86
+ />
87
+ <TextField
88
+ label="Lyrics"
89
+ value={lyrics}
90
+ onChange={(e) => setLyrics(e.target.value)}
91
+ disabled={isSubmitting}
92
+ fullWidth
93
+ multiline
94
+ rows={10}
95
+ placeholder="Paste lyrics text here (one line per segment)"
96
+ />
97
+ </Box>
98
+ </DialogContent>
99
+ <DialogActions>
100
+ <Button onClick={handleClose} disabled={isSubmitting}>
101
+ Cancel
102
+ </Button>
103
+ <Button
104
+ onClick={handleSubmit}
105
+ variant="contained"
106
+ disabled={isSubmitting}
107
+ startIcon={isSubmitting ? <CircularProgress size={20} /> : undefined}
108
+ >
109
+ {isSubmitting ? 'Adding...' : 'Add Lyrics'}
110
+ </Button>
111
+ </DialogActions>
112
+ </Dialog>
113
+ )
114
+ }
@@ -0,0 +1,204 @@
1
+ import { Paper, Box, Typography, Chip, Tooltip } from '@mui/material'
2
+ import { WordCorrection } from '../types'
3
+ import { useMemo } from 'react'
4
+
5
+ interface GapCategoryMetric {
6
+ category: string
7
+ count: number
8
+ avgConfidence: number
9
+ corrections: WordCorrection[]
10
+ }
11
+
12
+ interface AgenticCorrectionMetricsProps {
13
+ corrections: WordCorrection[]
14
+ onCategoryClick?: (category: string) => void
15
+ onConfidenceFilterClick?: (filter: 'low' | 'high') => void
16
+ }
17
+
18
+ export default function AgenticCorrectionMetrics({
19
+ corrections,
20
+ onCategoryClick,
21
+ onConfidenceFilterClick
22
+ }: AgenticCorrectionMetricsProps) {
23
+
24
+ const metrics = useMemo(() => {
25
+ // Filter only agentic corrections
26
+ const agenticCorrections = corrections.filter(c => c.handler === 'AgenticCorrector')
27
+
28
+ // Parse category from reason string (format: "reason [CATEGORY] (confidence: XX%)")
29
+ const categoryMap = new Map<string, GapCategoryMetric>()
30
+
31
+ agenticCorrections.forEach(correction => {
32
+ const categoryMatch = correction.reason?.match(/\[([A-Z_]+)\]/)
33
+ const category = categoryMatch ? categoryMatch[1] : 'UNKNOWN'
34
+
35
+ if (!categoryMap.has(category)) {
36
+ categoryMap.set(category, {
37
+ category,
38
+ count: 0,
39
+ avgConfidence: 0,
40
+ corrections: []
41
+ })
42
+ }
43
+
44
+ const metric = categoryMap.get(category)!
45
+ metric.count++
46
+ metric.corrections.push(correction)
47
+ })
48
+
49
+ // Calculate average confidence for each category
50
+ categoryMap.forEach((metric) => {
51
+ const totalConfidence = metric.corrections.reduce((sum, c) => sum + c.confidence, 0)
52
+ metric.avgConfidence = totalConfidence / metric.count
53
+ })
54
+
55
+ // Convert to array and sort by count descending
56
+ const sortedMetrics = Array.from(categoryMap.values()).sort((a, b) => b.count - a.count)
57
+
58
+ // Calculate overall stats
59
+ const totalCorrections = agenticCorrections.length
60
+ const avgConfidence = totalCorrections > 0
61
+ ? agenticCorrections.reduce((sum, c) => sum + c.confidence, 0) / totalCorrections
62
+ : 0
63
+
64
+ const lowConfidenceCount = agenticCorrections.filter(c => c.confidence < 0.6).length
65
+ const highConfidenceCount = agenticCorrections.filter(c => c.confidence >= 0.8).length
66
+
67
+ return {
68
+ categories: sortedMetrics,
69
+ totalCorrections,
70
+ avgConfidence,
71
+ lowConfidenceCount,
72
+ highConfidenceCount
73
+ }
74
+ }, [corrections])
75
+
76
+ // Format category name for display
77
+ const formatCategory = (category: string): string => {
78
+ return category
79
+ .split('_')
80
+ .map(word => word.charAt(0) + word.slice(1).toLowerCase())
81
+ .join(' ')
82
+ }
83
+
84
+ // Get emoji/icon for category
85
+ const getCategoryIcon = (category: string): string => {
86
+ const icons: Record<string, string> = {
87
+ 'SOUND_ALIKE': '🎵',
88
+ 'PUNCTUATION_ONLY': '✏️',
89
+ 'BACKGROUND_VOCALS': '🎤',
90
+ 'EXTRA_WORDS': '➕',
91
+ 'REPEATED_SECTION': '🔁',
92
+ 'COMPLEX_MULTI_ERROR': '🔧',
93
+ 'AMBIGUOUS': '❓',
94
+ 'NO_ERROR': '✅'
95
+ }
96
+ return icons[category] || '📝'
97
+ }
98
+
99
+ return (
100
+ <Paper
101
+ sx={{
102
+ p: 0.8,
103
+ height: '100%',
104
+ display: 'flex',
105
+ flexDirection: 'column'
106
+ }}
107
+ >
108
+ <Typography variant="subtitle2" color="text.secondary" sx={{ mb: 0.5, fontSize: '0.7rem' }}>
109
+ Agentic AI Corrections
110
+ </Typography>
111
+
112
+ {/* Overall stats */}
113
+ <Box sx={{ mb: 1 }}>
114
+ <Typography variant="body2" sx={{ fontSize: '0.75rem', mb: 0.3 }}>
115
+ Total: <strong>{metrics.totalCorrections}</strong>
116
+ </Typography>
117
+ <Typography variant="body2" sx={{ fontSize: '0.75rem', mb: 0.5 }}>
118
+ Avg Confidence: <strong>{(metrics.avgConfidence * 100).toFixed(0)}%</strong>
119
+ </Typography>
120
+
121
+ {/* Quick filters */}
122
+ <Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
123
+ <Chip
124
+ label={`Low (<60%): ${metrics.lowConfidenceCount}`}
125
+ size="small"
126
+ variant="outlined"
127
+ color="warning"
128
+ onClick={() => onConfidenceFilterClick?.('low')}
129
+ sx={{ fontSize: '0.65rem', height: '20px', cursor: 'pointer' }}
130
+ />
131
+ <Chip
132
+ label={`High (≥80%): ${metrics.highConfidenceCount}`}
133
+ size="small"
134
+ variant="outlined"
135
+ color="success"
136
+ onClick={() => onConfidenceFilterClick?.('high')}
137
+ sx={{ fontSize: '0.65rem', height: '20px', cursor: 'pointer' }}
138
+ />
139
+ </Box>
140
+ </Box>
141
+
142
+ {/* Category breakdown */}
143
+ <Typography variant="subtitle2" color="text.secondary" sx={{ mb: 0.5, fontSize: '0.7rem' }}>
144
+ By Category
145
+ </Typography>
146
+ <Box sx={{ flex: 1, overflow: 'auto' }}>
147
+ {metrics.categories.map((metric) => (
148
+ <Box
149
+ key={metric.category}
150
+ sx={{
151
+ mb: 0.5,
152
+ p: 0.5,
153
+ borderRadius: 1,
154
+ cursor: 'pointer',
155
+ '&:hover': {
156
+ bgcolor: 'action.hover'
157
+ }
158
+ }}
159
+ onClick={() => onCategoryClick?.(metric.category)}
160
+ >
161
+ <Tooltip
162
+ title={`${metric.count} correction${metric.count !== 1 ? 's' : ''} • Avg confidence: ${(metric.avgConfidence * 100).toFixed(0)}%`}
163
+ placement="right"
164
+ >
165
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
166
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
167
+ <span style={{ fontSize: '0.85rem' }}>{getCategoryIcon(metric.category)}</span>
168
+ <Typography variant="body2" sx={{ fontSize: '0.7rem' }}>
169
+ {formatCategory(metric.category)}
170
+ </Typography>
171
+ </Box>
172
+ <Box sx={{ display: 'flex', gap: 0.5, alignItems: 'center' }}>
173
+ <Typography variant="body2" sx={{ fontSize: '0.65rem', color: 'text.secondary' }}>
174
+ {(metric.avgConfidence * 100).toFixed(0)}%
175
+ </Typography>
176
+ <Typography
177
+ variant="body2"
178
+ sx={{
179
+ fontSize: '0.7rem',
180
+ fontWeight: 600,
181
+ bgcolor: 'action.selected',
182
+ px: 0.5,
183
+ borderRadius: 0.5,
184
+ minWidth: '24px',
185
+ textAlign: 'center'
186
+ }}
187
+ >
188
+ {metric.count}
189
+ </Typography>
190
+ </Box>
191
+ </Box>
192
+ </Tooltip>
193
+ </Box>
194
+ ))}
195
+ {metrics.categories.length === 0 && (
196
+ <Typography variant="body2" color="text.secondary" sx={{ fontSize: '0.7rem', fontStyle: 'italic' }}>
197
+ No agentic corrections
198
+ </Typography>
199
+ )}
200
+ </Box>
201
+ </Paper>
202
+ )
203
+ }
204
+