karaoke-gen 0.75.54__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.

Potentially problematic release.


This version of karaoke-gen might be problematic. Click here for more details.

Files changed (287) hide show
  1. karaoke_gen/__init__.py +38 -0
  2. karaoke_gen/audio_fetcher.py +1614 -0
  3. karaoke_gen/audio_processor.py +790 -0
  4. karaoke_gen/config.py +83 -0
  5. karaoke_gen/file_handler.py +387 -0
  6. karaoke_gen/instrumental_review/__init__.py +45 -0
  7. karaoke_gen/instrumental_review/analyzer.py +408 -0
  8. karaoke_gen/instrumental_review/editor.py +322 -0
  9. karaoke_gen/instrumental_review/models.py +171 -0
  10. karaoke_gen/instrumental_review/server.py +475 -0
  11. karaoke_gen/instrumental_review/static/index.html +1529 -0
  12. karaoke_gen/instrumental_review/waveform.py +409 -0
  13. karaoke_gen/karaoke_finalise/__init__.py +1 -0
  14. karaoke_gen/karaoke_finalise/karaoke_finalise.py +1833 -0
  15. karaoke_gen/karaoke_gen.py +1026 -0
  16. karaoke_gen/lyrics_processor.py +474 -0
  17. karaoke_gen/metadata.py +160 -0
  18. karaoke_gen/pipeline/__init__.py +87 -0
  19. karaoke_gen/pipeline/base.py +215 -0
  20. karaoke_gen/pipeline/context.py +230 -0
  21. karaoke_gen/pipeline/executors/__init__.py +21 -0
  22. karaoke_gen/pipeline/executors/local.py +159 -0
  23. karaoke_gen/pipeline/executors/remote.py +257 -0
  24. karaoke_gen/pipeline/stages/__init__.py +27 -0
  25. karaoke_gen/pipeline/stages/finalize.py +202 -0
  26. karaoke_gen/pipeline/stages/render.py +165 -0
  27. karaoke_gen/pipeline/stages/screens.py +139 -0
  28. karaoke_gen/pipeline/stages/separation.py +191 -0
  29. karaoke_gen/pipeline/stages/transcription.py +191 -0
  30. karaoke_gen/resources/AvenirNext-Bold.ttf +0 -0
  31. karaoke_gen/resources/Montserrat-Bold.ttf +0 -0
  32. karaoke_gen/resources/Oswald-Bold.ttf +0 -0
  33. karaoke_gen/resources/Oswald-SemiBold.ttf +0 -0
  34. karaoke_gen/resources/Zurich_Cn_BT_Bold.ttf +0 -0
  35. karaoke_gen/style_loader.py +531 -0
  36. karaoke_gen/utils/__init__.py +18 -0
  37. karaoke_gen/utils/bulk_cli.py +492 -0
  38. karaoke_gen/utils/cli_args.py +432 -0
  39. karaoke_gen/utils/gen_cli.py +978 -0
  40. karaoke_gen/utils/remote_cli.py +3268 -0
  41. karaoke_gen/video_background_processor.py +351 -0
  42. karaoke_gen/video_generator.py +424 -0
  43. karaoke_gen-0.75.54.dist-info/METADATA +718 -0
  44. karaoke_gen-0.75.54.dist-info/RECORD +287 -0
  45. karaoke_gen-0.75.54.dist-info/WHEEL +4 -0
  46. karaoke_gen-0.75.54.dist-info/entry_points.txt +5 -0
  47. karaoke_gen-0.75.54.dist-info/licenses/LICENSE +21 -0
  48. lyrics_transcriber/__init__.py +10 -0
  49. lyrics_transcriber/cli/__init__.py +0 -0
  50. lyrics_transcriber/cli/cli_main.py +285 -0
  51. lyrics_transcriber/core/__init__.py +0 -0
  52. lyrics_transcriber/core/config.py +50 -0
  53. lyrics_transcriber/core/controller.py +594 -0
  54. lyrics_transcriber/correction/__init__.py +0 -0
  55. lyrics_transcriber/correction/agentic/__init__.py +9 -0
  56. lyrics_transcriber/correction/agentic/adapter.py +71 -0
  57. lyrics_transcriber/correction/agentic/agent.py +313 -0
  58. lyrics_transcriber/correction/agentic/feedback/aggregator.py +12 -0
  59. lyrics_transcriber/correction/agentic/feedback/collector.py +17 -0
  60. lyrics_transcriber/correction/agentic/feedback/retention.py +24 -0
  61. lyrics_transcriber/correction/agentic/feedback/store.py +76 -0
  62. lyrics_transcriber/correction/agentic/handlers/__init__.py +24 -0
  63. lyrics_transcriber/correction/agentic/handlers/ambiguous.py +44 -0
  64. lyrics_transcriber/correction/agentic/handlers/background_vocals.py +68 -0
  65. lyrics_transcriber/correction/agentic/handlers/base.py +51 -0
  66. lyrics_transcriber/correction/agentic/handlers/complex_multi_error.py +46 -0
  67. lyrics_transcriber/correction/agentic/handlers/extra_words.py +74 -0
  68. lyrics_transcriber/correction/agentic/handlers/no_error.py +42 -0
  69. lyrics_transcriber/correction/agentic/handlers/punctuation.py +44 -0
  70. lyrics_transcriber/correction/agentic/handlers/registry.py +60 -0
  71. lyrics_transcriber/correction/agentic/handlers/repeated_section.py +44 -0
  72. lyrics_transcriber/correction/agentic/handlers/sound_alike.py +126 -0
  73. lyrics_transcriber/correction/agentic/models/__init__.py +5 -0
  74. lyrics_transcriber/correction/agentic/models/ai_correction.py +31 -0
  75. lyrics_transcriber/correction/agentic/models/correction_session.py +30 -0
  76. lyrics_transcriber/correction/agentic/models/enums.py +38 -0
  77. lyrics_transcriber/correction/agentic/models/human_feedback.py +30 -0
  78. lyrics_transcriber/correction/agentic/models/learning_data.py +26 -0
  79. lyrics_transcriber/correction/agentic/models/observability_metrics.py +28 -0
  80. lyrics_transcriber/correction/agentic/models/schemas.py +46 -0
  81. lyrics_transcriber/correction/agentic/models/utils.py +19 -0
  82. lyrics_transcriber/correction/agentic/observability/__init__.py +5 -0
  83. lyrics_transcriber/correction/agentic/observability/langfuse_integration.py +35 -0
  84. lyrics_transcriber/correction/agentic/observability/metrics.py +46 -0
  85. lyrics_transcriber/correction/agentic/observability/performance.py +19 -0
  86. lyrics_transcriber/correction/agentic/prompts/__init__.py +2 -0
  87. lyrics_transcriber/correction/agentic/prompts/classifier.py +227 -0
  88. lyrics_transcriber/correction/agentic/providers/__init__.py +6 -0
  89. lyrics_transcriber/correction/agentic/providers/base.py +36 -0
  90. lyrics_transcriber/correction/agentic/providers/circuit_breaker.py +145 -0
  91. lyrics_transcriber/correction/agentic/providers/config.py +73 -0
  92. lyrics_transcriber/correction/agentic/providers/constants.py +24 -0
  93. lyrics_transcriber/correction/agentic/providers/health.py +28 -0
  94. lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +212 -0
  95. lyrics_transcriber/correction/agentic/providers/model_factory.py +209 -0
  96. lyrics_transcriber/correction/agentic/providers/response_cache.py +218 -0
  97. lyrics_transcriber/correction/agentic/providers/response_parser.py +111 -0
  98. lyrics_transcriber/correction/agentic/providers/retry_executor.py +127 -0
  99. lyrics_transcriber/correction/agentic/router.py +35 -0
  100. lyrics_transcriber/correction/agentic/workflows/__init__.py +5 -0
  101. lyrics_transcriber/correction/agentic/workflows/consensus_workflow.py +24 -0
  102. lyrics_transcriber/correction/agentic/workflows/correction_graph.py +59 -0
  103. lyrics_transcriber/correction/agentic/workflows/feedback_workflow.py +24 -0
  104. lyrics_transcriber/correction/anchor_sequence.py +919 -0
  105. lyrics_transcriber/correction/corrector.py +760 -0
  106. lyrics_transcriber/correction/feedback/__init__.py +2 -0
  107. lyrics_transcriber/correction/feedback/schemas.py +107 -0
  108. lyrics_transcriber/correction/feedback/store.py +236 -0
  109. lyrics_transcriber/correction/handlers/__init__.py +0 -0
  110. lyrics_transcriber/correction/handlers/base.py +52 -0
  111. lyrics_transcriber/correction/handlers/extend_anchor.py +149 -0
  112. lyrics_transcriber/correction/handlers/levenshtein.py +189 -0
  113. lyrics_transcriber/correction/handlers/llm.py +293 -0
  114. lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
  115. lyrics_transcriber/correction/handlers/no_space_punct_match.py +154 -0
  116. lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +85 -0
  117. lyrics_transcriber/correction/handlers/repeat.py +88 -0
  118. lyrics_transcriber/correction/handlers/sound_alike.py +259 -0
  119. lyrics_transcriber/correction/handlers/syllables_match.py +252 -0
  120. lyrics_transcriber/correction/handlers/word_count_match.py +80 -0
  121. lyrics_transcriber/correction/handlers/word_operations.py +187 -0
  122. lyrics_transcriber/correction/operations.py +352 -0
  123. lyrics_transcriber/correction/phrase_analyzer.py +435 -0
  124. lyrics_transcriber/correction/text_utils.py +30 -0
  125. lyrics_transcriber/frontend/.gitignore +23 -0
  126. lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +935 -0
  127. lyrics_transcriber/frontend/.yarnrc.yml +3 -0
  128. lyrics_transcriber/frontend/README.md +50 -0
  129. lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
  130. lyrics_transcriber/frontend/__init__.py +25 -0
  131. lyrics_transcriber/frontend/eslint.config.js +28 -0
  132. lyrics_transcriber/frontend/index.html +18 -0
  133. lyrics_transcriber/frontend/package.json +42 -0
  134. lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
  135. lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
  136. lyrics_transcriber/frontend/public/apple-touch-icon.png +0 -0
  137. lyrics_transcriber/frontend/public/favicon-16x16.png +0 -0
  138. lyrics_transcriber/frontend/public/favicon-32x32.png +0 -0
  139. lyrics_transcriber/frontend/public/favicon.ico +0 -0
  140. lyrics_transcriber/frontend/public/nomad-karaoke-logo.png +0 -0
  141. lyrics_transcriber/frontend/src/App.tsx +214 -0
  142. lyrics_transcriber/frontend/src/api.ts +254 -0
  143. lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +77 -0
  144. lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
  145. lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +204 -0
  146. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +180 -0
  147. lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +167 -0
  148. lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +359 -0
  149. lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +281 -0
  150. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +162 -0
  151. lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +257 -0
  152. lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
  153. lyrics_transcriber/frontend/src/components/EditModal.tsx +702 -0
  154. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +496 -0
  155. lyrics_transcriber/frontend/src/components/EditWordList.tsx +379 -0
  156. lyrics_transcriber/frontend/src/components/FileUpload.tsx +77 -0
  157. lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
  158. lyrics_transcriber/frontend/src/components/Header.tsx +413 -0
  159. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +1387 -0
  160. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +185 -0
  161. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +704 -0
  162. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/UpcomingWordsBar.tsx +80 -0
  163. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +905 -0
  164. lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +51 -0
  165. lyrics_transcriber/frontend/src/components/ModeSelectionModal.tsx +127 -0
  166. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +67 -0
  167. lyrics_transcriber/frontend/src/components/ModelSelector.tsx +23 -0
  168. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +144 -0
  169. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +268 -0
  170. lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +336 -0
  171. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +354 -0
  172. lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
  173. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +376 -0
  174. lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +131 -0
  175. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +256 -0
  176. lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
  177. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +379 -0
  178. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +56 -0
  179. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +87 -0
  180. lyrics_transcriber/frontend/src/components/shared/constants.ts +20 -0
  181. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +180 -0
  182. lyrics_transcriber/frontend/src/components/shared/styles.ts +13 -0
  183. lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
  184. lyrics_transcriber/frontend/src/components/shared/types.ts +129 -0
  185. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +177 -0
  186. lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
  187. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +75 -0
  188. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +360 -0
  189. lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +110 -0
  190. lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
  191. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +435 -0
  192. lyrics_transcriber/frontend/src/main.tsx +17 -0
  193. lyrics_transcriber/frontend/src/theme.ts +177 -0
  194. lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
  195. lyrics_transcriber/frontend/src/types.js +2 -0
  196. lyrics_transcriber/frontend/src/types.ts +199 -0
  197. lyrics_transcriber/frontend/src/validation.ts +132 -0
  198. lyrics_transcriber/frontend/src/vite-env.d.ts +1 -0
  199. lyrics_transcriber/frontend/tsconfig.app.json +26 -0
  200. lyrics_transcriber/frontend/tsconfig.json +25 -0
  201. lyrics_transcriber/frontend/tsconfig.node.json +23 -0
  202. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -0
  203. lyrics_transcriber/frontend/update_version.js +11 -0
  204. lyrics_transcriber/frontend/vite.config.d.ts +2 -0
  205. lyrics_transcriber/frontend/vite.config.js +10 -0
  206. lyrics_transcriber/frontend/vite.config.ts +11 -0
  207. lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
  208. lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
  209. lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
  210. lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js +43288 -0
  211. lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js.map +1 -0
  212. lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
  213. lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
  214. lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
  215. lyrics_transcriber/frontend/web_assets/index.html +18 -0
  216. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
  217. lyrics_transcriber/frontend/yarn.lock +3752 -0
  218. lyrics_transcriber/lyrics/__init__.py +0 -0
  219. lyrics_transcriber/lyrics/base_lyrics_provider.py +211 -0
  220. lyrics_transcriber/lyrics/file_provider.py +95 -0
  221. lyrics_transcriber/lyrics/genius.py +384 -0
  222. lyrics_transcriber/lyrics/lrclib.py +231 -0
  223. lyrics_transcriber/lyrics/musixmatch.py +156 -0
  224. lyrics_transcriber/lyrics/spotify.py +290 -0
  225. lyrics_transcriber/lyrics/user_input_provider.py +44 -0
  226. lyrics_transcriber/output/__init__.py +0 -0
  227. lyrics_transcriber/output/ass/__init__.py +21 -0
  228. lyrics_transcriber/output/ass/ass.py +2088 -0
  229. lyrics_transcriber/output/ass/ass_specs.txt +732 -0
  230. lyrics_transcriber/output/ass/config.py +180 -0
  231. lyrics_transcriber/output/ass/constants.py +23 -0
  232. lyrics_transcriber/output/ass/event.py +94 -0
  233. lyrics_transcriber/output/ass/formatters.py +132 -0
  234. lyrics_transcriber/output/ass/lyrics_line.py +265 -0
  235. lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
  236. lyrics_transcriber/output/ass/section_detector.py +89 -0
  237. lyrics_transcriber/output/ass/section_screen.py +106 -0
  238. lyrics_transcriber/output/ass/style.py +187 -0
  239. lyrics_transcriber/output/cdg.py +619 -0
  240. lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
  241. lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
  242. lyrics_transcriber/output/cdgmaker/composer.py +2260 -0
  243. lyrics_transcriber/output/cdgmaker/config.py +151 -0
  244. lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
  245. lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
  246. lyrics_transcriber/output/cdgmaker/pack.py +507 -0
  247. lyrics_transcriber/output/cdgmaker/render.py +346 -0
  248. lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
  249. lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
  250. lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
  251. lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
  252. lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
  253. lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
  254. lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
  255. lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
  256. lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
  257. lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
  258. lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
  259. lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
  260. lyrics_transcriber/output/cdgmaker/utils.py +132 -0
  261. lyrics_transcriber/output/countdown_processor.py +306 -0
  262. lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
  263. lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
  264. lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
  265. lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
  266. lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
  267. lyrics_transcriber/output/fonts/arial.ttf +0 -0
  268. lyrics_transcriber/output/fonts/georgia.ttf +0 -0
  269. lyrics_transcriber/output/fonts/verdana.ttf +0 -0
  270. lyrics_transcriber/output/generator.py +257 -0
  271. lyrics_transcriber/output/lrc_to_cdg.py +61 -0
  272. lyrics_transcriber/output/lyrics_file.py +102 -0
  273. lyrics_transcriber/output/plain_text.py +96 -0
  274. lyrics_transcriber/output/segment_resizer.py +431 -0
  275. lyrics_transcriber/output/subtitles.py +397 -0
  276. lyrics_transcriber/output/video.py +544 -0
  277. lyrics_transcriber/review/__init__.py +0 -0
  278. lyrics_transcriber/review/server.py +676 -0
  279. lyrics_transcriber/storage/__init__.py +0 -0
  280. lyrics_transcriber/storage/dropbox.py +225 -0
  281. lyrics_transcriber/transcribers/__init__.py +0 -0
  282. lyrics_transcriber/transcribers/audioshake.py +379 -0
  283. lyrics_transcriber/transcribers/base_transcriber.py +157 -0
  284. lyrics_transcriber/transcribers/whisper.py +330 -0
  285. lyrics_transcriber/types.py +650 -0
  286. lyrics_transcriber/utils/__init__.py +0 -0
  287. lyrics_transcriber/utils/word_utils.py +27 -0
@@ -0,0 +1,254 @@
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
+ private reviewToken?: string;
39
+
40
+ constructor(private baseUrl: string, reviewToken?: string) {
41
+ this.baseUrl = baseUrl.replace(/\/$/, '')
42
+ this.reviewToken = reviewToken
43
+ }
44
+
45
+ public isUpdatingHandlers = false;
46
+
47
+ /**
48
+ * Build URL with reviewToken query parameter if available
49
+ */
50
+ private buildUrl(path: string): string {
51
+ const url = `${this.baseUrl}${path}`
52
+ if (this.reviewToken) {
53
+ const separator = url.includes('?') ? '&' : '?'
54
+ return `${url}${separator}review_token=${encodeURIComponent(this.reviewToken)}`
55
+ }
56
+ return url
57
+ }
58
+
59
+ async getCorrectionData(): Promise<CorrectionData> {
60
+ const response = await fetch(this.buildUrl('/correction-data'));
61
+ if (!response.ok) {
62
+ throw new Error(`API error: ${response.statusText}`);
63
+ }
64
+ const rawData = await response.json();
65
+
66
+ try {
67
+ // This will throw if validation fails
68
+ return validateCorrectionData(rawData);
69
+ } catch (error) {
70
+ console.error('Data validation failed:', error);
71
+ throw new Error('Invalid data received from server: missing or incorrect fields');
72
+ }
73
+ }
74
+
75
+ async submitCorrections(data: CorrectionData): Promise<void> {
76
+ // Extract only the needed fields
77
+ const updatePayload: CorrectionUpdate = {
78
+ corrections: data.corrections,
79
+ corrected_segments: data.corrected_segments
80
+ };
81
+
82
+ const response = await fetch(this.buildUrl('/complete'), {
83
+ method: 'POST',
84
+ headers: {
85
+ 'Content-Type': 'application/json',
86
+ },
87
+ body: JSON.stringify(updatePayload)
88
+ });
89
+
90
+ if (!response.ok) {
91
+ throw new Error(`API error: ${response.statusText}`);
92
+ }
93
+ }
94
+
95
+ getAudioUrl(audioHash: string): string {
96
+ return this.buildUrl(`/audio/${audioHash}`)
97
+ }
98
+
99
+ async generatePreviewVideo(data: CorrectionData): Promise<PreviewVideoResponse> {
100
+ // Extract only the needed fields, just like in submitCorrections
101
+ const updatePayload: CorrectionUpdate = {
102
+ corrections: data.corrections,
103
+ corrected_segments: data.corrected_segments
104
+ };
105
+
106
+ const response = await fetch(this.buildUrl('/preview-video'), {
107
+ method: 'POST',
108
+ headers: {
109
+ 'Content-Type': 'application/json',
110
+ },
111
+ body: JSON.stringify(updatePayload)
112
+ });
113
+
114
+ if (!response.ok) {
115
+ return {
116
+ status: 'error',
117
+ message: `API error: ${response.statusText}`
118
+ };
119
+ }
120
+
121
+ return await response.json();
122
+ }
123
+
124
+ getPreviewVideoUrl(previewHash: string): string {
125
+ return this.buildUrl(`/preview-video/${previewHash}`);
126
+ }
127
+
128
+ async updateHandlers(enabledHandlers: string[]): Promise<CorrectionData> {
129
+ console.log('API: Starting handler update...');
130
+ this.isUpdatingHandlers = true;
131
+ console.log('API: Set isUpdatingHandlers to', this.isUpdatingHandlers);
132
+
133
+ try {
134
+ const response = await fetch(this.buildUrl('/handlers'), {
135
+ method: 'POST',
136
+ headers: {
137
+ 'Content-Type': 'application/json',
138
+ },
139
+ body: JSON.stringify(enabledHandlers)
140
+ });
141
+
142
+ if (!response.ok) {
143
+ throw new Error(`API error: ${response.statusText}`);
144
+ }
145
+
146
+ const data = await response.json();
147
+ if (data.status === 'error') {
148
+ throw new Error(data.message || 'Failed to update handlers');
149
+ }
150
+
151
+ console.log('API: Handler update successful');
152
+ return validateCorrectionData(data.data);
153
+ } finally {
154
+ this.isUpdatingHandlers = false;
155
+ console.log('API: Set isUpdatingHandlers to', this.isUpdatingHandlers);
156
+ }
157
+ }
158
+
159
+ async addLyrics(source: string, lyrics: string): Promise<CorrectionData> {
160
+ const payload: AddLyricsRequest = {
161
+ source,
162
+ lyrics
163
+ };
164
+
165
+ const response = await fetch(this.buildUrl('/add-lyrics'), {
166
+ method: 'POST',
167
+ headers: {
168
+ 'Content-Type': 'application/json',
169
+ },
170
+ body: JSON.stringify(payload)
171
+ });
172
+
173
+ if (!response.ok) {
174
+ throw new Error(`API error: ${response.statusText}`);
175
+ }
176
+
177
+ const data = await response.json();
178
+ if (data.status === 'error') {
179
+ throw new Error(data.message || 'Failed to add lyrics');
180
+ }
181
+
182
+ return validateCorrectionData(data.data);
183
+ }
184
+
185
+ async submitAnnotations(annotations: Omit<CorrectionAnnotation, 'annotation_id' | 'timestamp'>[]): Promise<void> {
186
+ // Submit each annotation to the backend
187
+ for (const annotation of annotations) {
188
+ const response = await fetch(this.buildUrl('/v1/annotations'), {
189
+ method: 'POST',
190
+ headers: {
191
+ 'Content-Type': 'application/json',
192
+ },
193
+ body: JSON.stringify(annotation)
194
+ });
195
+
196
+ if (!response.ok) {
197
+ console.error(`Failed to submit annotation:`, annotation);
198
+ // Continue with other annotations even if one fails
199
+ }
200
+ }
201
+ }
202
+
203
+ async getAnnotationStats(): Promise<any> {
204
+ const response = await fetch(this.buildUrl('/v1/annotations/stats'));
205
+ if (!response.ok) {
206
+ throw new Error(`API error: ${response.statusText}`);
207
+ }
208
+ return await response.json();
209
+ }
210
+ }
211
+
212
+ export class FileOnlyClient implements ApiClient {
213
+ async getCorrectionData(): Promise<CorrectionData> {
214
+ throw new Error('Not supported in file-only mode');
215
+ }
216
+
217
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
218
+ async submitCorrections(_data: CorrectionData): Promise<void> {
219
+ throw new Error('Not supported in file-only mode');
220
+ }
221
+
222
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
223
+ getAudioUrl(_audioHash: string): string {
224
+ throw new Error('Not supported in file-only mode');
225
+ }
226
+
227
+ async generatePreviewVideo(): Promise<PreviewVideoResponse> {
228
+ throw new Error('Not supported in file-only mode');
229
+ }
230
+
231
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
232
+ getPreviewVideoUrl(_previewHash: string): string {
233
+ throw new Error('Not supported in file-only mode');
234
+ }
235
+
236
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
237
+ async updateHandlers(_enabledHandlers: string[]): Promise<CorrectionData> {
238
+ throw new Error('Not supported in file-only mode');
239
+ }
240
+
241
+ async addLyrics(): Promise<CorrectionData> {
242
+ throw new Error('Not supported in file-only mode');
243
+ }
244
+
245
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
246
+ async submitAnnotations(_annotations: Omit<CorrectionAnnotation, 'annotation_id' | 'timestamp'>[]): Promise<void> {
247
+ throw new Error('Not supported in file-only mode');
248
+ }
249
+
250
+ async getAnnotationStats(): Promise<any> {
251
+ throw new Error('Not supported in file-only mode');
252
+ }
253
+ }
254
+
@@ -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
+