karaoke-gen 0.103.1__py3-none-any.whl → 0.107.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. backend/Dockerfile.base +1 -0
  2. backend/api/routes/admin.py +226 -3
  3. backend/api/routes/push.py +238 -0
  4. backend/api/routes/users.py +14 -3
  5. backend/config.py +12 -1
  6. backend/main.py +2 -1
  7. backend/models/job.py +4 -0
  8. backend/models/user.py +20 -2
  9. backend/services/encoding_interface.py +4 -0
  10. backend/services/gce_encoding/main.py +22 -8
  11. backend/services/job_manager.py +68 -11
  12. backend/services/job_notification_service.py +4 -21
  13. backend/services/push_notification_service.py +409 -0
  14. backend/services/stripe_service.py +2 -2
  15. backend/tests/conftest.py +2 -1
  16. backend/tests/test_admin_delete_outputs.py +352 -0
  17. backend/tests/test_gce_encoding_worker.py +229 -0
  18. backend/tests/test_impersonation.py +18 -3
  19. backend/tests/test_job_notification_service.py +24 -58
  20. backend/tests/test_push_notification_service.py +460 -0
  21. backend/tests/test_push_routes.py +357 -0
  22. backend/tests/test_stripe_service.py +205 -0
  23. backend/tests/test_video_worker_orchestrator.py +189 -0
  24. backend/workers/video_worker_orchestrator.py +23 -0
  25. karaoke_gen/instrumental_review/server.py +145 -35
  26. karaoke_gen/nextjs_frontend/__init__.py +98 -0
  27. karaoke_gen/nextjs_frontend/out/404/index.html +1 -0
  28. karaoke_gen/nextjs_frontend/out/404.html +1 -0
  29. karaoke_gen/nextjs_frontend/out/__next.__PAGE__.txt +9 -0
  30. karaoke_gen/nextjs_frontend/out/__next._full.txt +22 -0
  31. karaoke_gen/nextjs_frontend/out/__next._head.txt +8 -0
  32. karaoke_gen/nextjs_frontend/out/__next._index.txt +9 -0
  33. karaoke_gen/nextjs_frontend/out/__next._tree.txt +2 -0
  34. karaoke_gen/nextjs_frontend/out/_next/static/chunks/01a7f8fe40f1ff47.js +1 -0
  35. karaoke_gen/nextjs_frontend/out/_next/static/chunks/112f346e31f991df.js +4 -0
  36. karaoke_gen/nextjs_frontend/out/_next/static/chunks/16d1a4dd9d8a873a.js +3 -0
  37. karaoke_gen/nextjs_frontend/out/_next/static/chunks/1ab85c362b8b0e86.js +9 -0
  38. karaoke_gen/nextjs_frontend/out/_next/static/chunks/247eb132b7f7b574.js +1 -0
  39. karaoke_gen/nextjs_frontend/out/_next/static/chunks/2b80d15cc95e4818.js +1 -0
  40. karaoke_gen/nextjs_frontend/out/_next/static/chunks/32c7eba5cd46c1bc.js +7 -0
  41. karaoke_gen/nextjs_frontend/out/_next/static/chunks/483f26794eae53d0.js +1 -0
  42. karaoke_gen/nextjs_frontend/out/_next/static/chunks/550c3b02e85f196a.js +1 -0
  43. karaoke_gen/nextjs_frontend/out/_next/static/chunks/55c5ade44387bef8.js +1 -0
  44. karaoke_gen/nextjs_frontend/out/_next/static/chunks/5628d92b5893add2.css +1 -0
  45. karaoke_gen/nextjs_frontend/out/_next/static/chunks/56ebf7665e4341c8.js +7 -0
  46. karaoke_gen/nextjs_frontend/out/_next/static/chunks/5997132b61dec430.js +1 -0
  47. karaoke_gen/nextjs_frontend/out/_next/static/chunks/5ea55255bce3eb9e.js +5 -0
  48. karaoke_gen/nextjs_frontend/out/_next/static/chunks/5eda89a57490b3cd.js +1 -0
  49. karaoke_gen/nextjs_frontend/out/_next/static/chunks/692f5d9e0d700c76.js +3 -0
  50. karaoke_gen/nextjs_frontend/out/_next/static/chunks/71d7a05b14f9f0f4.js +1 -0
  51. karaoke_gen/nextjs_frontend/out/_next/static/chunks/81ac355749ef3302.js +1 -0
  52. karaoke_gen/nextjs_frontend/out/_next/static/chunks/95f7e5934dbb0e5d.js +1 -0
  53. karaoke_gen/nextjs_frontend/out/_next/static/chunks/9bce8f19eaa46940.js +1 -0
  54. karaoke_gen/nextjs_frontend/out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
  55. karaoke_gen/nextjs_frontend/out/_next/static/chunks/a9ed54eed3e14c92.js +2 -0
  56. karaoke_gen/nextjs_frontend/out/_next/static/chunks/b35cd41238ecfb17.js +1 -0
  57. karaoke_gen/nextjs_frontend/out/_next/static/chunks/b5bc3c3d5ebd49eb.js +1 -0
  58. karaoke_gen/nextjs_frontend/out/_next/static/chunks/b5c078c08db5ae32.js +5 -0
  59. karaoke_gen/nextjs_frontend/out/_next/static/chunks/be9c44a178104187.js +1 -0
  60. karaoke_gen/nextjs_frontend/out/_next/static/chunks/c4c840e18cb4861c.js +1 -0
  61. karaoke_gen/nextjs_frontend/out/_next/static/chunks/c645af7d6b65f73e.js +1 -0
  62. karaoke_gen/nextjs_frontend/out/_next/static/chunks/d2c5e2575df784d4.js +1 -0
  63. karaoke_gen/nextjs_frontend/out/_next/static/chunks/d30af02b96d81462.js +1 -0
  64. karaoke_gen/nextjs_frontend/out/_next/static/chunks/d9bdf64f4ec1e9b7.js +7 -0
  65. karaoke_gen/nextjs_frontend/out/_next/static/chunks/dcde6ed684dacd0e.js +5 -0
  66. karaoke_gen/nextjs_frontend/out/_next/static/chunks/e422cbe931246000.js +1 -0
  67. karaoke_gen/nextjs_frontend/out/_next/static/chunks/e483af34fc792d38.js +1 -0
  68. karaoke_gen/nextjs_frontend/out/_next/static/chunks/e57422aad6b897da.js +1 -0
  69. karaoke_gen/nextjs_frontend/out/_next/static/chunks/ef02697fb404726a.js +1 -0
  70. karaoke_gen/nextjs_frontend/out/_next/static/chunks/ff1a16fafef87110.js +1 -0
  71. karaoke_gen/nextjs_frontend/out/_next/static/chunks/turbopack-2d9ca3017a9deedf.js +3 -0
  72. karaoke_gen/nextjs_frontend/out/_next/static/zpw_-rjFIDV5tlPPtnvRI/_buildManifest.js +11 -0
  73. karaoke_gen/nextjs_frontend/out/_next/static/zpw_-rjFIDV5tlPPtnvRI/_clientMiddlewareManifest.json +1 -0
  74. karaoke_gen/nextjs_frontend/out/_next/static/zpw_-rjFIDV5tlPPtnvRI/_ssgManifest.js +1 -0
  75. karaoke_gen/nextjs_frontend/out/_not-found/__next._full.txt +18 -0
  76. karaoke_gen/nextjs_frontend/out/_not-found/__next._head.txt +8 -0
  77. karaoke_gen/nextjs_frontend/out/_not-found/__next._index.txt +9 -0
  78. karaoke_gen/nextjs_frontend/out/_not-found/__next._not-found.__PAGE__.txt +5 -0
  79. karaoke_gen/nextjs_frontend/out/_not-found/__next._not-found.txt +4 -0
  80. karaoke_gen/nextjs_frontend/out/_not-found/__next._tree.txt +2 -0
  81. karaoke_gen/nextjs_frontend/out/_not-found/index.html +1 -0
  82. karaoke_gen/nextjs_frontend/out/_not-found/index.txt +18 -0
  83. karaoke_gen/nextjs_frontend/out/admin/__next._full.txt +25 -0
  84. karaoke_gen/nextjs_frontend/out/admin/__next._head.txt +8 -0
  85. karaoke_gen/nextjs_frontend/out/admin/__next._index.txt +9 -0
  86. karaoke_gen/nextjs_frontend/out/admin/__next._tree.txt +2 -0
  87. karaoke_gen/nextjs_frontend/out/admin/__next.admin.__PAGE__.txt +9 -0
  88. karaoke_gen/nextjs_frontend/out/admin/__next.admin.txt +7 -0
  89. karaoke_gen/nextjs_frontend/out/admin/beta/__next._full.txt +25 -0
  90. karaoke_gen/nextjs_frontend/out/admin/beta/__next._head.txt +8 -0
  91. karaoke_gen/nextjs_frontend/out/admin/beta/__next._index.txt +9 -0
  92. karaoke_gen/nextjs_frontend/out/admin/beta/__next._tree.txt +2 -0
  93. karaoke_gen/nextjs_frontend/out/admin/beta/__next.admin.beta.__PAGE__.txt +9 -0
  94. karaoke_gen/nextjs_frontend/out/admin/beta/__next.admin.beta.txt +4 -0
  95. karaoke_gen/nextjs_frontend/out/admin/beta/__next.admin.txt +7 -0
  96. karaoke_gen/nextjs_frontend/out/admin/beta/index.html +1 -0
  97. karaoke_gen/nextjs_frontend/out/admin/beta/index.txt +25 -0
  98. karaoke_gen/nextjs_frontend/out/admin/index.html +1 -0
  99. karaoke_gen/nextjs_frontend/out/admin/index.txt +25 -0
  100. karaoke_gen/nextjs_frontend/out/admin/jobs/__next._full.txt +25 -0
  101. karaoke_gen/nextjs_frontend/out/admin/jobs/__next._head.txt +8 -0
  102. karaoke_gen/nextjs_frontend/out/admin/jobs/__next._index.txt +9 -0
  103. karaoke_gen/nextjs_frontend/out/admin/jobs/__next._tree.txt +2 -0
  104. karaoke_gen/nextjs_frontend/out/admin/jobs/__next.admin.jobs.__PAGE__.txt +9 -0
  105. karaoke_gen/nextjs_frontend/out/admin/jobs/__next.admin.jobs.txt +4 -0
  106. karaoke_gen/nextjs_frontend/out/admin/jobs/__next.admin.txt +7 -0
  107. karaoke_gen/nextjs_frontend/out/admin/jobs/index.html +1 -0
  108. karaoke_gen/nextjs_frontend/out/admin/jobs/index.txt +25 -0
  109. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next._full.txt +25 -0
  110. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next._head.txt +8 -0
  111. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next._index.txt +9 -0
  112. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next._tree.txt +2 -0
  113. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next.admin.rate-limits.__PAGE__.txt +9 -0
  114. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next.admin.rate-limits.txt +4 -0
  115. karaoke_gen/nextjs_frontend/out/admin/rate-limits/__next.admin.txt +7 -0
  116. karaoke_gen/nextjs_frontend/out/admin/rate-limits/index.html +1 -0
  117. karaoke_gen/nextjs_frontend/out/admin/rate-limits/index.txt +25 -0
  118. karaoke_gen/nextjs_frontend/out/admin/searches/__next._full.txt +25 -0
  119. karaoke_gen/nextjs_frontend/out/admin/searches/__next._head.txt +8 -0
  120. karaoke_gen/nextjs_frontend/out/admin/searches/__next._index.txt +9 -0
  121. karaoke_gen/nextjs_frontend/out/admin/searches/__next._tree.txt +2 -0
  122. karaoke_gen/nextjs_frontend/out/admin/searches/__next.admin.searches.__PAGE__.txt +9 -0
  123. karaoke_gen/nextjs_frontend/out/admin/searches/__next.admin.searches.txt +4 -0
  124. karaoke_gen/nextjs_frontend/out/admin/searches/__next.admin.txt +7 -0
  125. karaoke_gen/nextjs_frontend/out/admin/searches/index.html +1 -0
  126. karaoke_gen/nextjs_frontend/out/admin/searches/index.txt +25 -0
  127. karaoke_gen/nextjs_frontend/out/admin/users/__next._full.txt +25 -0
  128. karaoke_gen/nextjs_frontend/out/admin/users/__next._head.txt +8 -0
  129. karaoke_gen/nextjs_frontend/out/admin/users/__next._index.txt +9 -0
  130. karaoke_gen/nextjs_frontend/out/admin/users/__next._tree.txt +2 -0
  131. karaoke_gen/nextjs_frontend/out/admin/users/__next.admin.txt +7 -0
  132. karaoke_gen/nextjs_frontend/out/admin/users/__next.admin.users.__PAGE__.txt +9 -0
  133. karaoke_gen/nextjs_frontend/out/admin/users/__next.admin.users.txt +4 -0
  134. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next._full.txt +25 -0
  135. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next._head.txt +8 -0
  136. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next._index.txt +9 -0
  137. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next._tree.txt +2 -0
  138. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next.admin.txt +7 -0
  139. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next.admin.users.detail.__PAGE__.txt +9 -0
  140. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next.admin.users.detail.txt +4 -0
  141. karaoke_gen/nextjs_frontend/out/admin/users/detail/__next.admin.users.txt +4 -0
  142. karaoke_gen/nextjs_frontend/out/admin/users/detail/index.html +1 -0
  143. karaoke_gen/nextjs_frontend/out/admin/users/detail/index.txt +25 -0
  144. karaoke_gen/nextjs_frontend/out/admin/users/index.html +1 -0
  145. karaoke_gen/nextjs_frontend/out/admin/users/index.txt +25 -0
  146. karaoke_gen/nextjs_frontend/out/app/__next._full.txt +22 -0
  147. karaoke_gen/nextjs_frontend/out/app/__next._head.txt +8 -0
  148. karaoke_gen/nextjs_frontend/out/app/__next._index.txt +9 -0
  149. karaoke_gen/nextjs_frontend/out/app/__next._tree.txt +2 -0
  150. karaoke_gen/nextjs_frontend/out/app/__next.app.__PAGE__.txt +9 -0
  151. karaoke_gen/nextjs_frontend/out/app/__next.app.txt +4 -0
  152. karaoke_gen/nextjs_frontend/out/app/index.html +1 -0
  153. karaoke_gen/nextjs_frontend/out/app/index.txt +22 -0
  154. karaoke_gen/nextjs_frontend/out/app/jobs/__next._full.txt +19 -0
  155. karaoke_gen/nextjs_frontend/out/app/jobs/__next._head.txt +8 -0
  156. karaoke_gen/nextjs_frontend/out/app/jobs/__next._index.txt +9 -0
  157. karaoke_gen/nextjs_frontend/out/app/jobs/__next._tree.txt +2 -0
  158. karaoke_gen/nextjs_frontend/out/app/jobs/__next.app.jobs.$oc$slug.__PAGE__.txt +6 -0
  159. karaoke_gen/nextjs_frontend/out/app/jobs/__next.app.jobs.$oc$slug.txt +4 -0
  160. karaoke_gen/nextjs_frontend/out/app/jobs/__next.app.jobs.txt +4 -0
  161. karaoke_gen/nextjs_frontend/out/app/jobs/__next.app.txt +4 -0
  162. karaoke_gen/nextjs_frontend/out/app/jobs/index.html +1 -0
  163. karaoke_gen/nextjs_frontend/out/app/jobs/index.txt +19 -0
  164. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next._full.txt +19 -0
  165. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next._head.txt +8 -0
  166. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next._index.txt +9 -0
  167. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next._tree.txt +2 -0
  168. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next.app.jobs.$oc$slug.__PAGE__.txt +6 -0
  169. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next.app.jobs.$oc$slug.txt +4 -0
  170. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next.app.jobs.txt +4 -0
  171. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/__next.app.txt +4 -0
  172. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/index.html +1 -0
  173. karaoke_gen/nextjs_frontend/out/app/jobs/local/instrumental/index.txt +19 -0
  174. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next._full.txt +19 -0
  175. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next._head.txt +8 -0
  176. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next._index.txt +9 -0
  177. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next._tree.txt +2 -0
  178. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next.app.jobs.$oc$slug.__PAGE__.txt +6 -0
  179. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next.app.jobs.$oc$slug.txt +4 -0
  180. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next.app.jobs.txt +4 -0
  181. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/__next.app.txt +4 -0
  182. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/index.html +1 -0
  183. karaoke_gen/nextjs_frontend/out/app/jobs/local/review/index.txt +19 -0
  184. karaoke_gen/nextjs_frontend/out/auth/verify/__next._full.txt +22 -0
  185. karaoke_gen/nextjs_frontend/out/auth/verify/__next._head.txt +8 -0
  186. karaoke_gen/nextjs_frontend/out/auth/verify/__next._index.txt +9 -0
  187. karaoke_gen/nextjs_frontend/out/auth/verify/__next._tree.txt +2 -0
  188. karaoke_gen/nextjs_frontend/out/auth/verify/__next.auth.txt +4 -0
  189. karaoke_gen/nextjs_frontend/out/auth/verify/__next.auth.verify.__PAGE__.txt +9 -0
  190. karaoke_gen/nextjs_frontend/out/auth/verify/__next.auth.verify.txt +4 -0
  191. karaoke_gen/nextjs_frontend/out/auth/verify/index.html +1 -0
  192. karaoke_gen/nextjs_frontend/out/auth/verify/index.txt +22 -0
  193. karaoke_gen/nextjs_frontend/out/index.html +1 -0
  194. karaoke_gen/nextjs_frontend/out/index.txt +22 -0
  195. karaoke_gen/nextjs_frontend/out/manifest.webmanifest +31 -0
  196. karaoke_gen/nextjs_frontend/out/order/success/__next._full.txt +22 -0
  197. karaoke_gen/nextjs_frontend/out/order/success/__next._head.txt +8 -0
  198. karaoke_gen/nextjs_frontend/out/order/success/__next._index.txt +9 -0
  199. karaoke_gen/nextjs_frontend/out/order/success/__next._tree.txt +2 -0
  200. karaoke_gen/nextjs_frontend/out/order/success/__next.order.success.__PAGE__.txt +9 -0
  201. karaoke_gen/nextjs_frontend/out/order/success/__next.order.success.txt +4 -0
  202. karaoke_gen/nextjs_frontend/out/order/success/__next.order.txt +4 -0
  203. karaoke_gen/nextjs_frontend/out/order/success/index.html +1 -0
  204. karaoke_gen/nextjs_frontend/out/order/success/index.txt +22 -0
  205. karaoke_gen/nextjs_frontend/out/payment/success/__next._full.txt +22 -0
  206. karaoke_gen/nextjs_frontend/out/payment/success/__next._head.txt +8 -0
  207. karaoke_gen/nextjs_frontend/out/payment/success/__next._index.txt +9 -0
  208. karaoke_gen/nextjs_frontend/out/payment/success/__next._tree.txt +2 -0
  209. karaoke_gen/nextjs_frontend/out/payment/success/__next.payment.success.__PAGE__.txt +9 -0
  210. karaoke_gen/nextjs_frontend/out/payment/success/__next.payment.success.txt +4 -0
  211. karaoke_gen/nextjs_frontend/out/payment/success/__next.payment.txt +4 -0
  212. karaoke_gen/nextjs_frontend/out/payment/success/index.html +1 -0
  213. karaoke_gen/nextjs_frontend/out/payment/success/index.txt +22 -0
  214. karaoke_gen/nextjs_frontend/out/screenshots/email-action_reminder.png +0 -0
  215. karaoke_gen/nextjs_frontend/out/screenshots/email-beta_welcome.png +0 -0
  216. karaoke_gen/nextjs_frontend/out/screenshots/email-job_completion.png +0 -0
  217. karaoke_gen/nextjs_frontend/out/screenshots/example-output.avif +0 -0
  218. karaoke_gen/nextjs_frontend/out/screenshots/homepage-full.png +0 -0
  219. karaoke_gen/nextjs_frontend/out/screenshots/homepage-hero.png +0 -0
  220. karaoke_gen/nextjs_frontend/out/screenshots/instrumental-review.avif +0 -0
  221. karaoke_gen/nextjs_frontend/out/screenshots/instrumental-review.png +0 -0
  222. karaoke_gen/nextjs_frontend/out/screenshots/job-dashboard.avif +0 -0
  223. karaoke_gen/nextjs_frontend/out/screenshots/lyrics-review.avif +0 -0
  224. karaoke_gen/nextjs_frontend/out/screenshots/lyrics-review.png +0 -0
  225. karaoke_gen/nextjs_frontend/out/sw.js +183 -0
  226. karaoke_gen/utils/cli_args.py +3 -3
  227. karaoke_gen/utils/gen_cli.py +4 -0
  228. karaoke_gen/utils/remote_cli.py +8 -40
  229. {karaoke_gen-0.103.1.dist-info → karaoke_gen-0.107.0.dist-info}/METADATA +2 -1
  230. {karaoke_gen-0.103.1.dist-info → karaoke_gen-0.107.0.dist-info}/RECORD +244 -131
  231. {karaoke_gen-0.103.1.dist-info → karaoke_gen-0.107.0.dist-info}/WHEEL +1 -1
  232. lyrics_transcriber/correction/agentic/agent.py +83 -60
  233. lyrics_transcriber/correction/anchor_sequence.py +48 -3
  234. lyrics_transcriber/correction/corrector.py +92 -58
  235. lyrics_transcriber/review/server.py +165 -33
  236. lyrics_transcriber/utils/tracing.py +214 -0
  237. karaoke_gen/instrumental_review/static/index.html +0 -1695
  238. lyrics_transcriber/frontend/.gitignore +0 -24
  239. lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +0 -935
  240. lyrics_transcriber/frontend/.yarnrc.yml +0 -3
  241. lyrics_transcriber/frontend/README.md +0 -50
  242. lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +0 -210
  243. lyrics_transcriber/frontend/__init__.py +0 -25
  244. lyrics_transcriber/frontend/e2e/agentic-corrections.spec.ts +0 -207
  245. lyrics_transcriber/frontend/e2e/fixtures/agentic-correction-data.json +0 -226
  246. lyrics_transcriber/frontend/eslint.config.js +0 -28
  247. lyrics_transcriber/frontend/index.html +0 -22
  248. lyrics_transcriber/frontend/package-lock.json +0 -4553
  249. lyrics_transcriber/frontend/package.json +0 -48
  250. lyrics_transcriber/frontend/playwright.config.ts +0 -69
  251. lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
  252. lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
  253. lyrics_transcriber/frontend/src/App.tsx +0 -243
  254. lyrics_transcriber/frontend/src/api.ts +0 -262
  255. lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +0 -111
  256. lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +0 -114
  257. lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +0 -204
  258. lyrics_transcriber/frontend/src/components/AppHeader.tsx +0 -65
  259. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +0 -180
  260. lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +0 -175
  261. lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +0 -359
  262. lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +0 -281
  263. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +0 -162
  264. lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +0 -257
  265. lyrics_transcriber/frontend/src/components/EditActionBar.tsx +0 -94
  266. lyrics_transcriber/frontend/src/components/EditModal.tsx +0 -720
  267. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +0 -592
  268. lyrics_transcriber/frontend/src/components/EditWordList.tsx +0 -431
  269. lyrics_transcriber/frontend/src/components/FileUpload.tsx +0 -77
  270. lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +0 -467
  271. lyrics_transcriber/frontend/src/components/Header.tsx +0 -520
  272. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +0 -1526
  273. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +0 -216
  274. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +0 -721
  275. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/UpcomingWordsBar.tsx +0 -80
  276. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +0 -999
  277. lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +0 -51
  278. lyrics_transcriber/frontend/src/components/ModeSelectionModal.tsx +0 -127
  279. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +0 -67
  280. lyrics_transcriber/frontend/src/components/ModelSelector.tsx +0 -23
  281. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +0 -177
  282. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +0 -268
  283. lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +0 -336
  284. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +0 -354
  285. lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +0 -64
  286. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +0 -383
  287. lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +0 -131
  288. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +0 -266
  289. lyrics_transcriber/frontend/src/components/WordDivider.tsx +0 -191
  290. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +0 -466
  291. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +0 -56
  292. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +0 -89
  293. lyrics_transcriber/frontend/src/components/shared/constants.ts +0 -30
  294. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +0 -180
  295. lyrics_transcriber/frontend/src/components/shared/styles.ts +0 -13
  296. lyrics_transcriber/frontend/src/components/shared/types.js +0 -2
  297. lyrics_transcriber/frontend/src/components/shared/types.ts +0 -135
  298. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +0 -177
  299. lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +0 -78
  300. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +0 -75
  301. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +0 -360
  302. lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +0 -110
  303. lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +0 -22
  304. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +0 -537
  305. lyrics_transcriber/frontend/src/main.tsx +0 -11
  306. lyrics_transcriber/frontend/src/theme.ts +0 -406
  307. lyrics_transcriber/frontend/src/types/global.d.ts +0 -9
  308. lyrics_transcriber/frontend/src/types.js +0 -2
  309. lyrics_transcriber/frontend/src/types.ts +0 -199
  310. lyrics_transcriber/frontend/src/validation.ts +0 -132
  311. lyrics_transcriber/frontend/src/vite-env.d.ts +0 -1
  312. lyrics_transcriber/frontend/tsconfig.app.json +0 -26
  313. lyrics_transcriber/frontend/tsconfig.json +0 -25
  314. lyrics_transcriber/frontend/tsconfig.node.json +0 -23
  315. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +0 -1
  316. lyrics_transcriber/frontend/update_version.js +0 -11
  317. lyrics_transcriber/frontend/vite.config.d.ts +0 -2
  318. lyrics_transcriber/frontend/vite.config.js +0 -15
  319. lyrics_transcriber/frontend/vite.config.ts +0 -16
  320. lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
  321. lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
  322. lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
  323. lyrics_transcriber/frontend/web_assets/assets/index-BSMgOq4Z.js +0 -44465
  324. lyrics_transcriber/frontend/web_assets/assets/index-BSMgOq4Z.js.map +0 -1
  325. lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
  326. lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
  327. lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
  328. lyrics_transcriber/frontend/web_assets/index.html +0 -22
  329. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
  330. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.svg +0 -5
  331. lyrics_transcriber/frontend/yarn.lock +0 -3711
  332. {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/apple-touch-icon.png +0 -0
  333. {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/favicon-16x16.png +0 -0
  334. {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/favicon-32x32.png +0 -0
  335. {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/favicon.ico +0 -0
  336. {lyrics_transcriber/frontend/public → karaoke_gen/nextjs_frontend/out}/nomad-karaoke-logo.svg +0 -0
  337. /lyrics_transcriber/frontend/public/nomad-karaoke-logo.png → /karaoke_gen/nextjs_frontend/out/nomad-logo.png +0 -0
  338. {karaoke_gen-0.103.1.dist-info → karaoke_gen-0.107.0.dist-info}/entry_points.txt +0 -0
  339. {karaoke_gen-0.103.1.dist-info → karaoke_gen-0.107.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,111 +0,0 @@
1
- import React, { useEffect, useRef } 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
- 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]);
34
-
35
- if (!isOpen) return null;
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
-
46
- return (
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 }}>
58
- {suggestion?.text ?? "No suggestion"}
59
- {suggestion?.confidence != null ? ` (confidence ${Math.round((suggestion.confidence || 0) * 100)}%)` : null}
60
- </p>
61
- {suggestion?.reasoning ? <small style={{ color: colors.textSecondary }}>{suggestion.reasoning}</small> : null}
62
-
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' }}>
66
- <option value="ACCEPT">Accept</option>
67
- <option value="REJECT">Reject</option>
68
- <option value="MODIFY">Modify</option>
69
- </select>
70
- </div>
71
-
72
- {reviewerAction === "MODIFY" ? (
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' }} />
76
- </div>
77
- ) : null}
78
-
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' }}>
82
- <option value="AI_CORRECT">AI_CORRECT</option>
83
- <option value="AI_INCORRECT">AI_INCORRECT</option>
84
- <option value="AI_SUBOPTIMAL">AI_SUBOPTIMAL</option>
85
- <option value="CONTEXT_NEEDED">CONTEXT_NEEDED</option>
86
- <option value="SUBJECTIVE_PREFERENCE">SUBJECTIVE_PREFERENCE</option>
87
- </select>
88
- </div>
89
-
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' }} />
93
- </div>
94
-
95
- <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 16 }}>
96
- <button onClick={onClose} style={{ background: colors.border, color: colors.text, border: 'none', borderRadius: 4, padding: '6px 12px', cursor: 'pointer' }}>Cancel</button>
97
- <button
98
- onClick={() =>
99
- onSubmit({ reviewerAction, finalText: finalText || undefined, reasonCategory, reasonDetail: reasonDetail || undefined })
100
- }
101
- style={{ background: '#ff7acc', color: '#fff', border: 'none', borderRadius: 4, padding: '6px 12px', cursor: 'pointer' }}
102
- >
103
- Submit
104
- </button>
105
- </div>
106
- </div>
107
- </div>
108
- );
109
- };
110
-
111
- export default AIFeedbackModal;
@@ -1,114 +0,0 @@
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
- }
@@ -1,204 +0,0 @@
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
-
@@ -1,65 +0,0 @@
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
- }
@@ -1,180 +0,0 @@
1
- import { Box, IconButton, Slider, Typography } from '@mui/material'
2
- import PlayArrowIcon from '@mui/icons-material/PlayArrow'
3
- import PauseIcon from '@mui/icons-material/Pause'
4
- import { useEffect, useRef, useState, useCallback } from 'react'
5
- import { ApiClient } from '../api'
6
-
7
- interface AudioPlayerProps {
8
- apiClient: ApiClient | null,
9
- onTimeUpdate?: (time: number) => void,
10
- audioHash: string
11
- }
12
-
13
- export default function AudioPlayer({ apiClient, onTimeUpdate, audioHash }: AudioPlayerProps) {
14
- const [isPlaying, setIsPlaying] = useState(false)
15
- const [currentTime, setCurrentTime] = useState(0)
16
- const [duration, setDuration] = useState(0)
17
- const audioRef = useRef<HTMLAudioElement | null>(null)
18
-
19
- useEffect(() => {
20
- if (!apiClient) return
21
-
22
- const audio = new Audio(apiClient.getAudioUrl(audioHash))
23
- audioRef.current = audio
24
-
25
- // Add requestAnimationFrame for smoother updates
26
- let animationFrameId: number
27
-
28
- const updateTime = () => {
29
- const time = audio.currentTime
30
- setCurrentTime(time)
31
- onTimeUpdate?.(time)
32
- animationFrameId = requestAnimationFrame(updateTime)
33
- }
34
-
35
- audio.addEventListener('play', () => {
36
- setIsPlaying(true)
37
- window.isAudioPlaying = true
38
- updateTime()
39
- })
40
-
41
- audio.addEventListener('pause', () => {
42
- setIsPlaying(false)
43
- window.isAudioPlaying = false
44
- cancelAnimationFrame(animationFrameId)
45
- })
46
-
47
- audio.addEventListener('ended', () => {
48
- cancelAnimationFrame(animationFrameId)
49
- setIsPlaying(false)
50
- window.isAudioPlaying = false
51
- setCurrentTime(0)
52
- })
53
-
54
- audio.addEventListener('loadedmetadata', () => {
55
- setDuration(audio.duration)
56
- })
57
-
58
- return () => {
59
- cancelAnimationFrame(animationFrameId)
60
- audio.pause()
61
- audio.src = ''
62
- audioRef.current = null
63
- window.isAudioPlaying = false
64
- }
65
- }, [apiClient, onTimeUpdate, audioHash])
66
-
67
- const handlePlayPause = () => {
68
- if (!audioRef.current) return
69
-
70
- if (isPlaying) {
71
- audioRef.current.pause()
72
- } else {
73
- audioRef.current.play()
74
- }
75
- setIsPlaying(!isPlaying)
76
- }
77
-
78
- const handleSeek = (_: Event, newValue: number | number[]) => {
79
- if (!audioRef.current) return
80
- const time = newValue as number
81
- audioRef.current.currentTime = time
82
- setCurrentTime(time)
83
- }
84
-
85
- const formatTime = (seconds: number) => {
86
- const mins = Math.floor(seconds / 60)
87
- const secs = Math.floor(seconds % 60)
88
- return `${mins}:${secs.toString().padStart(2, '0')}`
89
- }
90
-
91
- // Add this method to expose seeking functionality
92
- const seekAndPlay = (time: number) => {
93
- if (!audioRef.current) return
94
-
95
- audioRef.current.currentTime = time
96
- setCurrentTime(time)
97
- audioRef.current.play()
98
- setIsPlaying(true)
99
- }
100
-
101
- const togglePlayback = useCallback(() => {
102
- if (!audioRef.current) return
103
-
104
- if (isPlaying) {
105
- audioRef.current.pause()
106
- } else {
107
- audioRef.current.play()
108
- }
109
- setIsPlaying(!isPlaying)
110
- }, [isPlaying])
111
-
112
- // Expose methods and duration globally
113
- useEffect(() => {
114
- if (!apiClient) return
115
-
116
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
117
- const win = window as any
118
- win.seekAndPlayAudio = seekAndPlay
119
- win.toggleAudioPlayback = togglePlayback
120
- win.getAudioDuration = () => duration
121
-
122
- return () => {
123
- delete win.seekAndPlayAudio
124
- delete win.toggleAudioPlayback
125
- delete win.getAudioDuration
126
- }
127
- }, [apiClient, togglePlayback, duration])
128
-
129
- if (!apiClient) return null
130
-
131
- return (
132
- <Box sx={{
133
- display: 'flex',
134
- alignItems: 'center',
135
- gap: 0.5,
136
- backgroundColor: 'background.paper',
137
- borderRadius: 1,
138
- height: '32px',
139
- }}>
140
- <Typography variant="body2" color="text.secondary" sx={{ mr: 0.5, fontSize: '0.75rem' }}>
141
- Playback:
142
- </Typography>
143
-
144
- <IconButton
145
- onClick={handlePlayPause}
146
- size="small"
147
- sx={{ p: 0.5 }}
148
- >
149
- {isPlaying ? <PauseIcon fontSize="small" /> : <PlayArrowIcon fontSize="small" />}
150
- </IconButton>
151
-
152
- <Typography variant="body2" sx={{ minWidth: 32, fontSize: '0.75rem' }}>
153
- {formatTime(currentTime)}
154
- </Typography>
155
-
156
- <Slider
157
- value={currentTime}
158
- min={0}
159
- max={duration}
160
- onChange={handleSeek}
161
- size="small"
162
- sx={{
163
- width: 150,
164
- mx: 0.5,
165
- '& .MuiSlider-thumb': {
166
- width: 10,
167
- height: 10,
168
- },
169
- '& .MuiSlider-rail, & .MuiSlider-track': {
170
- height: 3
171
- }
172
- }}
173
- />
174
-
175
- <Typography variant="body2" sx={{ minWidth: 32, fontSize: '0.75rem' }}>
176
- {formatTime(duration)}
177
- </Typography>
178
- </Box>
179
- )
180
- }