@slycode/slycode 0.2.9 → 0.2.11

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 (74) hide show
  1. package/dist/web/.next/BUILD_ID +1 -1
  2. package/dist/web/.next/build-manifest.json +2 -2
  3. package/dist/web/.next/server/app/_global-error.html +2 -2
  4. package/dist/web/.next/server/app/_global-error.rsc +1 -1
  5. package/dist/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  6. package/dist/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  7. package/dist/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  8. package/dist/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  9. package/dist/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/dist/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  11. package/dist/web/.next/server/app/_not-found.html +1 -1
  12. package/dist/web/.next/server/app/_not-found.rsc +10 -10
  13. package/dist/web/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
  14. package/dist/web/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  15. package/dist/web/.next/server/app/_not-found.segments/_index.segment.rsc +5 -5
  16. package/dist/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  17. package/dist/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  18. package/dist/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  19. package/dist/web/.next/server/app/api/cli-assets/assistant/route.js.nft.json +1 -1
  20. package/dist/web/.next/server/app/api/cli-assets/fix/route.js.nft.json +1 -1
  21. package/dist/web/.next/server/app/api/cli-assets/import/route.js.nft.json +1 -1
  22. package/dist/web/.next/server/app/api/cli-assets/route.js.nft.json +1 -1
  23. package/dist/web/.next/server/app/api/cli-assets/store/preview/route.js.nft.json +1 -1
  24. package/dist/web/.next/server/app/api/cli-assets/store/route.js.nft.json +1 -1
  25. package/dist/web/.next/server/app/api/cli-assets/sync/route.js.nft.json +1 -1
  26. package/dist/web/.next/server/app/api/cli-assets/updates/route.js.nft.json +1 -1
  27. package/dist/web/.next/server/app/api/dashboard/route.js.nft.json +1 -1
  28. package/dist/web/.next/server/app/api/file/route.js.nft.json +1 -1
  29. package/dist/web/.next/server/app/api/git-status/route.js.nft.json +1 -1
  30. package/dist/web/.next/server/app/api/kanban/route.js.nft.json +1 -1
  31. package/dist/web/.next/server/app/api/kanban/stream/route.js.nft.json +1 -1
  32. package/dist/web/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
  33. package/dist/web/.next/server/app/api/projects/reorder/route.js.nft.json +1 -1
  34. package/dist/web/.next/server/app/api/projects/route.js.nft.json +1 -1
  35. package/dist/web/.next/server/app/api/scheduler/route.js.nft.json +1 -1
  36. package/dist/web/.next/server/app/api/search/route.js.nft.json +1 -1
  37. package/dist/web/.next/server/app/api/sly-actions/invalidate/route.js.nft.json +1 -1
  38. package/dist/web/.next/server/app/api/sly-actions/route.js.nft.json +1 -1
  39. package/dist/web/.next/server/app/api/transcribe/route.js +3 -3
  40. package/dist/web/.next/server/app/api/transcribe/route.js.nft.json +1 -1
  41. package/dist/web/.next/server/app/api/version-check/route.js.nft.json +1 -1
  42. package/dist/web/.next/server/app/page.js.nft.json +1 -1
  43. package/dist/web/.next/server/app/page_client-reference-manifest.js +1 -1
  44. package/dist/web/.next/server/app/project/[id]/page.js.nft.json +1 -1
  45. package/dist/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
  46. package/dist/web/.next/server/chunks/{[root-of-the-server]__934c9bb4._.js → [root-of-the-server]__0f9b71fd._.js} +1 -1
  47. package/dist/web/.next/server/chunks/[root-of-the-server]__1dec5018._.js +3 -0
  48. package/dist/web/.next/server/chunks/{[root-of-the-server]__e00fb85f._.js → [root-of-the-server]__2605d761._.js} +1 -1
  49. package/dist/web/.next/server/chunks/{[root-of-the-server]__41df0777._.js → [root-of-the-server]__53ef96b9._.js} +2 -2
  50. package/dist/web/.next/server/chunks/{[root-of-the-server]__1cdc76ef._.js → [root-of-the-server]__8ce673c9._.js} +2 -2
  51. package/dist/web/.next/server/chunks/[root-of-the-server]__a43db24d._.js +19 -0
  52. package/dist/web/.next/server/chunks/{[root-of-the-server]__104b2da3._.js → [root-of-the-server]__a4d171b5._.js} +3 -3
  53. package/dist/web/.next/server/chunks/{[root-of-the-server]__3dc5531f._.js → [root-of-the-server]__c7ac3578._.js} +1 -1
  54. package/dist/web/.next/server/chunks/ssr/src_contexts_VoiceContext_tsx_cfba7292._.js +1 -1
  55. package/dist/web/.next/server/pages/404.html +1 -1
  56. package/dist/web/.next/server/pages/500.html +2 -2
  57. package/dist/web/.next/static/chunks/3df3846316317676.css +1 -0
  58. package/dist/web/.next/static/chunks/{0452f599128364c9.js → 8fb2a99c64580de7.js} +1 -1
  59. package/dist/web/.next/static/chunks/{2a22edae0b1198eb.js → b8e0c1aeea4a14bc.js} +1 -1
  60. package/dist/web/package-lock.json +37 -0
  61. package/dist/web/package.json +1 -0
  62. package/dist/web/src/app/api/transcribe/route.ts +7 -1
  63. package/dist/web/src/components/VoiceControlBar.tsx +1 -0
  64. package/dist/web/src/hooks/useVoiceRecorder.ts +7 -5
  65. package/dist/web/src/lib/webm-to-ogg-opus.ts +254 -0
  66. package/dist/web/tsconfig.tsbuildinfo +1 -1
  67. package/package.json +1 -1
  68. package/templates/kanban-seed.json +1 -1
  69. package/dist/web/.next/server/chunks/[root-of-the-server]__3998d59e._.js +0 -19
  70. package/dist/web/.next/server/chunks/[root-of-the-server]__403750fd._.js +0 -3
  71. package/dist/web/.next/static/chunks/f3d7065d54a0b9ac.css +0 -1
  72. /package/dist/web/.next/static/{wK5osty7Iax92ZOE_Skb7 → eGpcv9LfmamGIm1II8SKL}/_buildManifest.js +0 -0
  73. /package/dist/web/.next/static/{wK5osty7Iax92ZOE_Skb7 → eGpcv9LfmamGIm1II8SKL}/_clientMiddlewareManifest.json +0 -0
  74. /package/dist/web/.next/static/{wK5osty7Iax92ZOE_Skb7 → eGpcv9LfmamGIm1II8SKL}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,1088,39269,9083,49996,e=>{"use strict";e.s(["VoiceProvider",()=>b,"useVoice",()=>v],1088);var r=e.i(18050),t=e.i(71645);function n(e,r){let t,n,o=(n=(t=r.split("+").map(e=>e.trim().toLowerCase()))[t.length-1],{ctrlKey:t.includes("ctrl"),shiftKey:t.includes("shift"),altKey:t.includes("alt"),metaKey:t.includes("meta")||t.includes("cmd"),key:"space"===n?" ":"escape"===n?"Escape":"enter"===n?"Enter":n});return(e.key.toLowerCase()===o.key.toLowerCase()||e.key===o.key)&&o.ctrlKey===e.ctrlKey&&o.shiftKey===e.shiftKey&&o.altKey===e.altKey&&o.metaKey===e.metaKey}let o={autoSubmitTerminal:!0,maxRecordingSeconds:300,shortcuts:{startRecording:"Ctrl+.",pauseResume:"Space",submit:"Enter",submitPasteOnly:"Shift+Enter",clear:"Escape"}},a={voice:o};var s=e.i(74080);function i({voiceState:e,elapsedSeconds:t,disabled:n,error:o,onRecord:a,onPause:s,onResume:i,onClear:d,onSubmit:l,onRetry:c,onOpenSettings:u}){let m;return(0,r.jsxs)("div",{className:`flex items-center gap-1.5 ${n?"opacity-40 pointer-events-none":""}`,children:[("idle"===e||"disabled"===e)&&(0,r.jsx)("button",{onClick:a,disabled:n,className:"rounded-md border border-void-400/30 bg-void-200/50 p-1.5 text-void-500 transition-all hover:border-red-400/40 hover:bg-red-400/10 hover:text-red-400 disabled:opacity-50 dark:border-void-500/25 dark:bg-void-700/50 dark:text-void-400 dark:hover:border-red-400/40 dark:hover:bg-red-400/10 dark:hover:text-red-400",title:"Start recording (Ctrl+.)",children:(0,r.jsx)("svg",{className:"h-4 w-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"})})}),("recording"===e||"paused"===e)&&(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)("div",{className:"flex items-center gap-1.5 px-1",children:[(0,r.jsx)("span",{className:`inline-block h-2 w-2 rounded-full bg-red-500 ${"recording"===e?"animate-pulse":""}`}),(0,r.jsx)("span",{className:"min-w-[2.5rem] font-mono text-xs tabular-nums text-red-600 dark:text-red-400",children:(m=Math.floor(t/60),`${m}:${(t%60).toString().padStart(2,"0")}`)})]}),(0,r.jsx)("button",{onClick:"recording"===e?s:i,className:"rounded-md border border-void-400/30 bg-void-200/50 p-1.5 text-void-600 transition-all hover:border-neon-blue-400/40 hover:bg-neon-blue-400/10 hover:text-neon-blue-400 dark:border-void-500/25 dark:bg-void-700/50 dark:text-void-400 dark:hover:border-neon-blue-400/40 dark:hover:bg-neon-blue-400/10 dark:hover:text-neon-blue-400",title:"recording"===e?"Pause (Space)":"Resume (Space)",children:"recording"===e?(0,r.jsxs)("svg",{className:"h-3.5 w-3.5",fill:"currentColor",viewBox:"0 0 24 24",children:[(0,r.jsx)("rect",{x:"6",y:"4",width:"4",height:"16"}),(0,r.jsx)("rect",{x:"14",y:"4",width:"4",height:"16"})]}):(0,r.jsx)("svg",{className:"h-3.5 w-3.5",fill:"currentColor",viewBox:"0 0 24 24",children:(0,r.jsx)("polygon",{points:"5,3 19,12 5,21"})})}),(0,r.jsx)("button",{onClick:d,className:"rounded-md border border-void-400/30 bg-void-200/50 p-1.5 text-void-600 transition-all hover:border-red-400/40 hover:bg-red-400/10 hover:text-red-400 dark:border-void-500/25 dark:bg-void-700/50 dark:text-void-400 dark:hover:border-red-400/40 dark:hover:bg-red-400/10 dark:hover:text-red-400",title:"Clear recording (Escape)",children:(0,r.jsx)("svg",{className:"h-3.5 w-3.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})}),(0,r.jsx)("button",{onClick:l,className:"rounded-md border border-green-400/40 bg-green-400/15 px-2 py-1.5 text-xs font-medium text-green-600 transition-all hover:bg-green-400/25 hover:shadow-[0_0_8px_rgba(34,197,94,0.2)] dark:text-green-400",title:"Submit for transcription (Enter)",children:(0,r.jsx)("svg",{className:"h-3.5 w-3.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})})]}),"transcribing"===e&&(0,r.jsxs)("div",{className:"flex items-center gap-1.5 px-1",children:[(0,r.jsxs)("svg",{className:"h-4 w-4 animate-spin text-[#2490b5] dark:text-neon-blue-400",viewBox:"0 0 24 24",fill:"none",children:[(0,r.jsx)("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),(0,r.jsx)("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"})]}),(0,r.jsx)("span",{className:"text-xs text-[#2490b5] dark:text-neon-blue-400",children:"Transcribing..."})]}),"error"===e&&(0,r.jsxs)("div",{className:"flex items-center gap-1.5",children:[(0,r.jsx)("span",{className:"text-xs text-red-400",title:o||"Transcription failed",children:"Failed"}),(0,r.jsx)("button",{onClick:c,className:"rounded-md border border-neon-blue-400/40 bg-neon-blue-400/15 px-1.5 py-1 text-xs font-medium text-neon-blue-400 transition-all hover:bg-neon-blue-400/25",title:"Retry transcription",children:"Retry"}),(0,r.jsx)("button",{onClick:d,className:"rounded-md border border-void-400/30 bg-void-200/50 p-1.5 text-void-600 transition-all hover:border-red-400/40 hover:bg-red-400/10 hover:text-red-400 dark:border-void-500/25 dark:bg-void-700/50 dark:text-void-400 dark:hover:border-red-400/40 dark:hover:bg-red-400/10 dark:hover:text-red-400",title:"Clear",children:(0,r.jsx)("svg",{className:"h-3.5 w-3.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),(0,r.jsx)("button",{onClick:u,className:"rounded-md border border-void-400/30 bg-void-200/50 p-1.5 text-void-500 transition-all hover:border-void-400/50 hover:text-void-700 dark:border-void-500/25 dark:bg-void-700/50 dark:text-void-400 dark:hover:border-void-400/50 dark:hover:text-void-300",title:"Voice settings",children:(0,r.jsxs)("svg",{className:"h-3.5 w-3.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"}),(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"})]})})]})}function d({label:e,value:n,onChange:o}){let[a,s]=(0,t.useState)(!1),i=(0,t.useRef)(null),d=(0,t.useCallback)(e=>{if(!a)return;e.preventDefault(),e.stopPropagation();let r=[];e.ctrlKey&&r.push("Ctrl"),e.shiftKey&&r.push("Shift"),e.altKey&&r.push("Alt"),e.metaKey&&r.push("Cmd");let t=e.key;if(!["Control","Shift","Alt","Meta"].includes(t)){let e=" "===t?"Space":"Escape"===t?"Escape":"Enter"===t?"Enter":(t.length,t);r.push(e),o(r.join("+")),s(!1)}},[a,o]);return(0,r.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,r.jsx)("span",{className:"text-xs text-void-600 dark:text-void-400",children:e}),(0,r.jsx)("input",{ref:i,type:"text",value:a?"Press keys...":n,readOnly:!0,onFocus:()=>s(!0),onBlur:()=>s(!1),onKeyDown:d,className:`w-28 rounded border px-2 py-1 text-center text-xs ${a?"border-neon-blue-400 bg-neon-blue-400/10 text-neon-blue-600 dark:text-neon-blue-400":"border-void-300 bg-void-50 text-void-700 dark:border-void-600 dark:bg-void-700 dark:text-void-300"} cursor-pointer outline-none`})]})}function l({settings:e,onSave:n,onClose:o}){let a=(0,t.useRef)(null),[s,i]=(0,t.useState)({...e.shortcuts}),[l,c]=(0,t.useState)(e.autoSubmitTerminal),[u,m]=(0,t.useState)(Math.round(e.maxRecordingSeconds/60)),v=(0,t.useRef)({shortcuts:s,autoSubmit:l,maxMinutes:u});return v.current={shortcuts:s,autoSubmit:l,maxMinutes:u},(0,t.useEffect)(()=>()=>{let{shortcuts:e,autoSubmit:r,maxMinutes:t}=v.current;n({shortcuts:e,autoSubmitTerminal:r,maxRecordingSeconds:60*Math.max(1,t)})},[]),(0,t.useEffect)(()=>{let e=e=>{a.current&&!a.current.contains(e.target)&&o()};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[o]),(0,r.jsxs)("div",{ref:a,className:"w-80 rounded-lg border border-void-200 bg-white p-4 shadow-(--shadow-overlay) dark:border-void-600 dark:bg-void-800",children:[(0,r.jsx)("h3",{className:"mb-3 text-sm font-medium text-void-800 dark:text-void-200",children:"Voice Settings"}),(0,r.jsxs)("div",{className:"mb-4 space-y-2",children:[(0,r.jsx)("div",{className:"text-xs font-medium text-void-500 dark:text-void-400",children:"Keyboard Shortcuts"}),(0,r.jsx)(d,{label:"Start recording",value:s.startRecording,onChange:e=>i(r=>({...r,startRecording:e}))}),(0,r.jsx)(d,{label:"Pause / Resume",value:s.pauseResume,onChange:e=>i(r=>({...r,pauseResume:e}))}),(0,r.jsx)(d,{label:"Submit",value:s.submit,onChange:e=>i(r=>({...r,submit:e}))}),(0,r.jsx)(d,{label:"Paste only",value:s.submitPasteOnly,onChange:e=>i(r=>({...r,submitPasteOnly:e}))}),(0,r.jsx)(d,{label:"Clear / Cancel",value:s.clear,onChange:e=>i(r=>({...r,clear:e}))})]}),(0,r.jsxs)("div",{className:"space-y-3 border-t border-void-200 pt-3 dark:border-void-600",children:[(0,r.jsx)("div",{className:"text-xs font-medium text-void-500 dark:text-void-400",children:"Behaviour"}),(0,r.jsxs)("label",{className:"flex items-center justify-between gap-2",children:[(0,r.jsx)("span",{className:"text-xs text-void-600 dark:text-void-400",children:"Auto-submit (terminal)"}),(0,r.jsx)("button",{type:"button",role:"switch","aria-checked":l,onClick:()=>c(!l),className:`relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors ${l?"bg-green-500":"bg-void-300 dark:bg-void-600"}`,children:(0,r.jsx)("span",{className:`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow transition ${l?"translate-x-4":"translate-x-0"}`})})]}),(0,r.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,r.jsx)("span",{className:"text-xs text-void-600 dark:text-void-400",children:"Max recording (min)"}),(0,r.jsx)("input",{type:"number",min:1,max:30,value:u,onChange:e=>m(parseInt(e.target.value)||5),className:"w-16 rounded border border-void-300 bg-void-50 px-2 py-1 text-center text-xs text-void-700 outline-none dark:border-void-600 dark:bg-void-700 dark:text-void-300"})]})]})]})}function c({error:e,hasRecording:n,onRetry:o,onClear:a,onClose:s}){let i=(0,t.useRef)(null);(0,t.useEffect)(()=>{let e=e=>{i.current&&!i.current.contains(e.target)&&s()};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[s]);let d=window.isSecureContext,l=!n;return(0,r.jsxs)("div",{ref:i,className:"w-72 rounded-lg border border-red-400/30 bg-white p-3 shadow-(--shadow-overlay) dark:border-red-400/20 dark:bg-void-800",children:[(0,r.jsxs)("div",{className:"mb-2 flex items-start gap-2",children:[(0,r.jsx)("svg",{className:"mt-0.5 h-4 w-4 flex-shrink-0 text-red-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"})}),(0,r.jsxs)("div",{children:[(0,r.jsx)("div",{className:"text-xs font-medium text-red-600 dark:text-red-400",children:l?"Recording Failed":"Transcription Failed"}),(0,r.jsx)("div",{className:"mt-1 text-xs text-void-600 dark:text-void-300",children:e})]})]}),(0,r.jsx)("div",{className:"mt-2 text-xs text-void-500 dark:text-void-400",children:l&&!d?"Microphone requires HTTPS or localhost. Try accessing via localhost:3003, or add this origin to chrome://flags/#unsafely-treat-insecure-origin-as-secure.":l?"Could not access the microphone.":"Your recording is preserved. You can retry or clear and start over."}),(0,r.jsxs)("div",{className:"mt-3 flex justify-end gap-2",children:[(0,r.jsx)("button",{onClick:a,className:"rounded border border-void-300 bg-void-100 px-2 py-1 text-xs text-void-600 hover:text-void-800 dark:border-void-600 dark:bg-void-700 dark:text-void-400 dark:hover:text-void-300",children:l?"Dismiss":"Clear"}),n&&o&&(0,r.jsx)("button",{onClick:o,className:"rounded border border-neon-blue-400/40 bg-neon-blue-400/15 px-2 py-1 text-xs text-neon-blue-600 hover:bg-neon-blue-400/25 dark:text-neon-blue-400",children:"Retry"})]})]})}function u(){let e=v(),n=(0,t.useRef)(null),o=(0,t.useRef)(0),[a,d]=(0,t.useState)(!1);return((0,t.useEffect)(()=>{d(!0)},[]),null===e.currentClaimantId&&a&&("idle"!==e.voiceState&&"disabled"!==e.voiceState||e.showSettings))?(0,s.createPortal)((0,r.jsxs)("div",{className:"fixed bottom-4 right-4 z-50 animate-in fade-in slide-in-from-bottom-2 duration-200 rounded-xl border border-red-400/30 bg-white/95 px-3 py-2 shadow-(--shadow-card) backdrop-blur-sm dark:border-red-400/20 dark:bg-void-800/95",ref:n,children:[(0,r.jsx)(i,{voiceState:e.voiceState,elapsedSeconds:e.elapsedSeconds,disabled:!1,error:e.error,onRecord:e.startRecording,onPause:e.pauseRecording,onResume:e.resumeRecording,onClear:e.clearRecording,onSubmit:e.submitRecording,onRetry:e.retryTranscription,onOpenSettings:()=>{Date.now()-o.current<200||e.setShowSettings(!e.showSettings)}}),e.showSettings&&(0,r.jsx)("div",{style:{position:"fixed",bottom:60,right:16,zIndex:9999},children:(0,r.jsx)(l,{settings:e.settings.voice,onSave:r=>e.updateSettings({voice:r}),onClose:()=>{o.current=Date.now(),e.setShowSettings(!1)}})}),"error"===e.voiceState&&e.error&&(0,r.jsx)("div",{style:{position:"fixed",bottom:60,right:16,zIndex:9999},children:(0,r.jsx)(c,{error:e.error,hasRecording:e.hasRecording,onRetry:()=>e.retryTranscription(),onClear:()=>e.clearRecording(),onClose:()=>e.clearRecording()})})]}),document.body):null}e.s(["VoiceControlBar",()=>i],39269),e.s(["VoiceSettingsPopover",()=>l],9083),e.s(["VoiceErrorPopup",()=>c],49996);let m=(0,t.createContext)(null);function v(){let e=(0,t.useContext)(m);if(!e)throw Error("useVoice() must be used inside <VoiceProvider>");return e}function b({children:e}){let{settings:s,updateSettings:i}=function(){let[e,r]=(0,t.useState)(a),[n,s]=(0,t.useState)(!0);return(0,t.useEffect)(()=>{fetch("/api/settings").then(e=>e.json()).then(e=>{r({...a,...e,voice:{...o,...e?.voice}})}).catch(()=>{}).finally(()=>s(!1))},[]),{settings:e,updateSettings:(0,t.useCallback)(async e=>{try{let t=await fetch("/api/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(t.ok){let e=await t.json();return r({...a,...e,voice:{...o,...e?.voice}}),e}}catch{}return null},[]),isLoading:n}}(),d=(0,t.useRef)(new Map),l=(0,t.useRef)(null),c=(0,t.useCallback)((e,r)=>{d.current.set(e,r),l.current=e},[]),v=(0,t.useCallback)(e=>{if(d.current.delete(e),l.current===e){let e=Array.from(d.current.keys());l.current=e.length>0?e[0]:null}},[]),b=(0,t.useRef)(null),[x,h]=(0,t.useState)(null),p=(0,t.useRef)(null),g=(0,t.useRef)("auto"),[f,k]=(0,t.useState)(!1),[y,R]=(0,t.useState)(!1),j=(0,t.useCallback)(e=>{let r=p.current;if(r){if("input"===r.type&&r.element){if(!document.contains(r.element))return;let t=r.element,n=t.selectionStart??t.value.length,o=t.selectionEnd??t.value.length;t.focus(),t.setSelectionRange(n,o),document.execCommand("insertText",!1,e)}else if("terminal"===r.type&&r.terminalId){let t=d.current.get(r.terminalId);if(!t)return;let n="auto"===g.current&&s.voice.autoSubmitTerminal;t.sendInput(e),n&&setTimeout(()=>t.sendInput("\r"),300)}g.current="auto"}},[s.voice.autoSubmitTerminal]),w=(0,t.useCallback)(e=>{b.current?b.current.onTranscriptionComplete(e):j(e)},[j]),C=function({maxRecordingSeconds:e,onTranscriptionComplete:r}){let[n,o]=(0,t.useState)("idle"),[a,s]=(0,t.useState)(0),[i,d]=(0,t.useState)(null),[l,c]=(0,t.useState)(null),u=(0,t.useRef)(null),m=(0,t.useRef)([]),v=(0,t.useRef)(null),b=(0,t.useRef)(null),x=(0,t.useRef)(null),h=(0,t.useRef)(0),p=(0,t.useRef)(!1),g=(0,t.useRef)(r);(0,t.useEffect)(()=>{g.current=r},[r]);let f=(0,t.useCallback)(()=>{v.current||(v.current=setInterval(()=>{s(e=>e+1)},1e3))},[]),k=(0,t.useCallback)(()=>{v.current&&(clearInterval(v.current),v.current=null)},[]);(0,t.useEffect)(()=>{"recording"===n&&a-h.current>=e&&(u.current?.state==="recording"&&u.current.pause(),p.current=!0,k(),o("paused"))},[n,a,e,k]),(0,t.useEffect)(()=>()=>{if(k(),u.current?.state!=="inactive")try{u.current?.stop()}catch{}x.current?.getTracks().forEach(e=>e.stop())},[k]);let y=(0,t.useCallback)(()=>new Promise(e=>{let r=u.current;r&&"inactive"!==r.state?(r.ondataavailable=e=>{e.data.size>0&&m.current.push(e.data)},r.onstop=()=>{e(new Blob(m.current,{type:m.current[0]?.type||"audio/webm"}))},r.stop()):e(new Blob(m.current,{type:m.current[0]?.type||"audio/webm"}))}),[]),R=(0,t.useCallback)(async e=>{let r=new FormData;r.append("audio",e,"recording.webm");let t=await fetch("/api/transcribe",{method:"POST",body:r}),n=await t.json();if(!t.ok)throw Error(n.error||"Transcription failed");return n.text},[]),j=(0,t.useCallback)(()=>{x.current?.getTracks().forEach(e=>e.stop()),x.current=null},[]),w=(0,t.useCallback)(async()=>{if("idle"===n||"disabled"===n)try{d(null),c(null),m.current=[],b.current=null,s(0),h.current=0;let e=await navigator.mediaDevices.getUserMedia({audio:!0});x.current=e;let r=MediaRecorder.isTypeSupported("audio/webm;codecs=opus")?"audio/webm;codecs=opus":MediaRecorder.isTypeSupported("audio/mp4")?"audio/mp4":"",t=new MediaRecorder(e,r?{mimeType:r}:void 0);u.current=t,t.ondataavailable=e=>{e.data.size>0&&m.current.push(e.data)},t.start(1e3),o("recording"),f()}catch(r){let e=r.message;e.includes("Permission")||e.includes("NotAllowed")?d("Microphone permission denied. Please allow microphone access."):d(e||"Failed to start recording"),o("error"),j()}},[n,f,j]),C=(0,t.useCallback)(()=>{"recording"===n&&(u.current?.state==="recording"&&u.current.pause(),k(),o("paused"))},[n,k]),S=(0,t.useCallback)(()=>{"paused"===n&&(u.current?.state==="paused"&&u.current.resume(),p.current&&(h.current=a,p.current=!1),f(),o("recording"))},[n,a,f]),N=(0,t.useCallback)(()=>{if(k(),u.current?.state!=="inactive")try{u.current?.stop()}catch{}m.current=[],b.current=null,s(0),d(null),c(null),j(),o("idle")},[k,j]),E=(0,t.useCallback)(async()=>{if("recording"===n||"paused"===n){k(),o("transcribing");try{let e=await y();b.current=e,j();let r=await R(e);c(r),s(0),m.current=[],o("idle"),g.current?.(r)}catch(e){d(e.message||"Transcription failed"),o("error")}}},[n,k,y,R,j]),T=(0,t.useCallback)(async()=>{if("error"===n&&b.current){o("transcribing"),d(null);try{let e=await R(b.current);c(e),b.current=null,m.current=[],s(0),o("idle"),g.current?.(e)}catch(e){d(e.message||"Transcription failed"),o("error")}}},[n,R]);return{state:n,elapsedSeconds:a,error:i,transcribedText:l,hasRecording:null!==b.current||m.current.length>0,startRecording:w,pauseRecording:C,resumeRecording:S,clearRecording:N,submitRecording:E,retryTranscription:T}}({maxRecordingSeconds:s.voice.maxRecordingSeconds,onTranscriptionComplete:w}),S=(0,t.useCallback)(e=>{b.current&&b.current.id!==e.id&&(b.current.onRelease?.(),"idle"!==C.state&&"disabled"!==C.state&&C.clearRecording()),b.current=e,h(e.id)},[C]),N=(0,t.useCallback)(e=>{b.current?.id===e.id&&("idle"!==C.state&&"error"!==C.state&&"disabled"!==C.state&&C.clearRecording(),b.current=null,h(null))},[C]);(0,t.useEffect)(()=>{let e=e=>{let r=e.target;if(r.closest("[data-voice-target]")&&("INPUT"===r.tagName||"TEXTAREA"===r.tagName))return void R(!0);if(r.closest("[data-terminal-id]")){let e=r.closest("[data-terminal-id]").dataset.terminalId;e&&d.current.has(e)&&(l.current=e,R(!0))}},r=()=>{setTimeout(()=>{let e=document.activeElement;if(!e)return void R(!1);let r=e.closest("[data-voice-target]")&&("INPUT"===e.tagName||"TEXTAREA"===e.tagName),t=e.closest("[data-terminal-id]");r||t||R(!1)},100)};return document.addEventListener("focusin",e),document.addEventListener("focusout",r),()=>{document.removeEventListener("focusin",e),document.removeEventListener("focusout",r)}},[]);let E=(0,t.useCallback)(()=>{if(b.current)b.current.onRecordStart?.(),C.startRecording();else{let e=document.activeElement;if(e&&("INPUT"===e.tagName||"TEXTAREA"===e.tagName)&&e.closest("[data-voice-target]"))p.current={type:"input",element:e};else if(e?.closest("[data-terminal-id]")){let r=e.closest("[data-terminal-id]").dataset.terminalId||l.current;if(!(r&&d.current.has(r)))return;p.current={type:"terminal",terminalId:r}}else{if(!(l.current&&d.current.has(l.current)))return;p.current={type:"terminal",terminalId:l.current}}C.startRecording()}},[C]),T=(0,t.useCallback)(()=>{g.current="paste",C.submitRecording()},[C]),P=(0,t.useCallback)(()=>{"recording"===C.state?C.pauseRecording():"paused"===C.state&&C.resumeRecording()},[C]);!function({voiceState:e,shortcuts:r,callbacks:o,enabled:a,hasFieldFocus:s,suspended:i}){let d=(0,t.useRef)(o),l=(0,t.useRef)(e),c=(0,t.useRef)(r),u=(0,t.useRef)(s),m=(0,t.useRef)(i);(0,t.useEffect)(()=>{d.current=o,l.current=e,c.current=r,u.current=s,m.current=i}),(0,t.useEffect)(()=>{if(!a)return;let e=e=>{if(m.current)return;let r=l.current,t=c.current,o=d.current;if("idle"===r&&u.current&&n(e,t.startRecording)){e.preventDefault(),e.stopPropagation(),o.onStartRecording();return}if("recording"===r||"paused"===r){if(n(e,t.pauseResume)){e.preventDefault(),e.stopPropagation(),o.onPauseResume();return}if(n(e,t.submitPasteOnly)){e.preventDefault(),e.stopPropagation(),o.onSubmitPasteOnly();return}if(n(e,t.submit)){e.preventDefault(),e.stopPropagation(),o.onSubmit();return}if(n(e,t.clear)){e.preventDefault(),e.stopPropagation(),o.onClear();return}}};return document.addEventListener("keydown",e,!0),()=>document.removeEventListener("keydown",e,!0)},[a])}({voiceState:C.state,shortcuts:s.voice.shortcuts,callbacks:{onStartRecording:E,onPauseResume:P,onSubmit:C.submitRecording,onSubmitPasteOnly:T,onClear:C.clearRecording},enabled:!0,hasFieldFocus:!!b.current||y,suspended:f});let L={voiceState:C.state,elapsedSeconds:C.elapsedSeconds,error:C.error,hasRecording:C.hasRecording,startRecording:C.startRecording,pauseRecording:C.pauseRecording,resumeRecording:C.resumeRecording,clearRecording:C.clearRecording,submitRecording:C.submitRecording,retryTranscription:C.retryTranscription,claimVoiceControl:S,releaseVoiceControl:N,currentClaimantId:x,settings:s,updateSettings:i,registerTerminal:c,unregisterTerminal:v,submitModeRef:g,showSettings:f,setShowSettings:k,hasFieldFocus:y,setHasFieldFocus:R};return(0,r.jsxs)(m.Provider,{value:L,children:[e,(0,r.jsx)(u,{})]})}}]);
1
+ (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,1088,39269,9083,49996,e=>{"use strict";e.s(["VoiceProvider",()=>b,"useVoice",()=>v],1088);var r=e.i(18050),t=e.i(71645);function n(e,r){let t,n,o=(n=(t=r.split("+").map(e=>e.trim().toLowerCase()))[t.length-1],{ctrlKey:t.includes("ctrl"),shiftKey:t.includes("shift"),altKey:t.includes("alt"),metaKey:t.includes("meta")||t.includes("cmd"),key:"space"===n?" ":"escape"===n?"Escape":"enter"===n?"Enter":n});return(e.key.toLowerCase()===o.key.toLowerCase()||e.key===o.key)&&o.ctrlKey===e.ctrlKey&&o.shiftKey===e.shiftKey&&o.altKey===e.altKey&&o.metaKey===e.metaKey}let o={autoSubmitTerminal:!0,maxRecordingSeconds:300,shortcuts:{startRecording:"Ctrl+.",pauseResume:"Space",submit:"Enter",submitPasteOnly:"Shift+Enter",clear:"Escape"}},a={voice:o};var s=e.i(74080);function i({voiceState:e,elapsedSeconds:t,disabled:n,error:o,onRecord:a,onPause:s,onResume:i,onClear:d,onSubmit:c,onRetry:l,onOpenSettings:u}){let m;return(0,r.jsxs)("div",{className:`flex items-center gap-1.5 ${n?"opacity-40 pointer-events-none":""}`,children:[("idle"===e||"disabled"===e)&&(0,r.jsx)("button",{onClick:a,disabled:n,className:"rounded-md border border-void-400/30 bg-void-200/50 p-1.5 text-void-500 transition-all hover:border-red-400/40 hover:bg-red-400/10 hover:text-red-400 disabled:opacity-50 dark:border-void-500/25 dark:bg-void-700/50 dark:text-void-400 dark:hover:border-red-400/40 dark:hover:bg-red-400/10 dark:hover:text-red-400",title:"Start recording (Ctrl+.)",children:(0,r.jsx)("svg",{className:"h-4 w-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"})})}),("recording"===e||"paused"===e)&&(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)("div",{className:"flex items-center gap-1.5 px-1",children:[(0,r.jsx)("span",{className:`inline-block h-2 w-2 rounded-full bg-red-500 ${"recording"===e?"animate-pulse":""}`}),(0,r.jsx)("span",{className:"min-w-[2.5rem] font-mono text-xs tabular-nums text-red-600 dark:text-red-400",children:(m=Math.floor(t/60),`${m}:${(t%60).toString().padStart(2,"0")}`)})]}),(0,r.jsx)("button",{onClick:"recording"===e?s:i,className:"rounded-md border border-void-400/30 bg-void-200/50 p-1.5 text-void-600 transition-all hover:border-neon-blue-400/40 hover:bg-neon-blue-400/10 hover:text-neon-blue-400 dark:border-void-500/25 dark:bg-void-700/50 dark:text-void-400 dark:hover:border-neon-blue-400/40 dark:hover:bg-neon-blue-400/10 dark:hover:text-neon-blue-400",title:"recording"===e?"Pause (Space)":"Resume (Space)",children:"recording"===e?(0,r.jsxs)("svg",{className:"h-3.5 w-3.5",fill:"currentColor",viewBox:"0 0 24 24",children:[(0,r.jsx)("rect",{x:"6",y:"4",width:"4",height:"16"}),(0,r.jsx)("rect",{x:"14",y:"4",width:"4",height:"16"})]}):(0,r.jsx)("svg",{className:"h-3.5 w-3.5",fill:"currentColor",viewBox:"0 0 24 24",children:(0,r.jsx)("polygon",{points:"5,3 19,12 5,21"})})}),(0,r.jsx)("button",{onClick:d,className:"rounded-md border border-void-400/30 bg-void-200/50 p-1.5 text-void-600 transition-all hover:border-red-400/40 hover:bg-red-400/10 hover:text-red-400 dark:border-void-500/25 dark:bg-void-700/50 dark:text-void-400 dark:hover:border-red-400/40 dark:hover:bg-red-400/10 dark:hover:text-red-400",title:"Clear recording (Escape)",children:(0,r.jsx)("svg",{className:"h-3.5 w-3.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})}),(0,r.jsx)("button",{onClick:c,className:"rounded-md border border-green-400/40 bg-green-400/15 px-2 py-1.5 text-xs font-medium text-green-600 transition-all hover:bg-green-400/25 hover:shadow-[0_0_8px_rgba(34,197,94,0.2)] dark:text-green-400",title:"Submit for transcription (Enter)",children:(0,r.jsx)("svg",{className:"h-3.5 w-3.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})})]}),"transcribing"===e&&(0,r.jsxs)("div",{className:"flex items-center gap-1.5 px-1",children:[(0,r.jsxs)("svg",{className:"h-4 w-4 animate-spin text-[#2490b5] dark:text-neon-blue-400",viewBox:"0 0 24 24",fill:"none",children:[(0,r.jsx)("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),(0,r.jsx)("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"})]}),(0,r.jsx)("span",{className:"text-xs text-[#2490b5] dark:text-neon-blue-400",children:"Transcribing..."})]}),"error"===e&&(0,r.jsxs)("div",{className:"flex items-center gap-1.5",children:[(0,r.jsx)("span",{className:"text-xs text-red-400",title:o||"Transcription failed",children:"Failed"}),(0,r.jsx)("button",{onClick:l,className:"rounded-md border border-neon-blue-400/40 bg-neon-blue-400/15 px-1.5 py-1 text-xs font-medium text-neon-blue-400 transition-all hover:bg-neon-blue-400/25",title:"Retry transcription",children:"Retry"}),(0,r.jsx)("button",{onClick:d,className:"rounded-md border border-void-400/30 bg-void-200/50 p-1.5 text-void-600 transition-all hover:border-red-400/40 hover:bg-red-400/10 hover:text-red-400 dark:border-void-500/25 dark:bg-void-700/50 dark:text-void-400 dark:hover:border-red-400/40 dark:hover:bg-red-400/10 dark:hover:text-red-400",title:"Clear",children:(0,r.jsx)("svg",{className:"h-3.5 w-3.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),(0,r.jsx)("button",{onClick:u,className:"rounded-md border border-void-400/30 bg-void-200/50 p-1.5 text-void-500 transition-all hover:border-void-400/50 hover:text-void-700 dark:border-void-500/25 dark:bg-void-700/50 dark:text-void-400 dark:hover:border-void-400/50 dark:hover:text-void-300",title:"Voice settings",children:(0,r.jsxs)("svg",{className:"h-3.5 w-3.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:[(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"}),(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"})]})})]})}function d({label:e,value:n,onChange:o}){let[a,s]=(0,t.useState)(!1),i=(0,t.useRef)(null),d=(0,t.useCallback)(e=>{if(!a)return;e.preventDefault(),e.stopPropagation();let r=[];e.ctrlKey&&r.push("Ctrl"),e.shiftKey&&r.push("Shift"),e.altKey&&r.push("Alt"),e.metaKey&&r.push("Cmd");let t=e.key;if(!["Control","Shift","Alt","Meta"].includes(t)){let e=" "===t?"Space":"Escape"===t?"Escape":"Enter"===t?"Enter":(t.length,t);r.push(e),o(r.join("+")),s(!1)}},[a,o]);return(0,r.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,r.jsx)("span",{className:"text-xs text-void-600 dark:text-void-400",children:e}),(0,r.jsx)("input",{ref:i,type:"text",value:a?"Press keys...":n,readOnly:!0,onFocus:()=>s(!0),onBlur:()=>s(!1),onKeyDown:d,className:`w-28 rounded border px-2 py-1 text-center text-xs ${a?"border-neon-blue-400 bg-neon-blue-400/10 text-neon-blue-600 dark:text-neon-blue-400":"border-void-300 bg-void-50 text-void-700 dark:border-void-600 dark:bg-void-700 dark:text-void-300"} cursor-pointer outline-none`})]})}function c({settings:e,onSave:n,onClose:o}){let a=(0,t.useRef)(null),[s,i]=(0,t.useState)({...e.shortcuts}),[c,l]=(0,t.useState)(e.autoSubmitTerminal),[u,m]=(0,t.useState)(Math.round(e.maxRecordingSeconds/60)),v=(0,t.useRef)({shortcuts:s,autoSubmit:c,maxMinutes:u});return v.current={shortcuts:s,autoSubmit:c,maxMinutes:u},(0,t.useEffect)(()=>()=>{let{shortcuts:e,autoSubmit:r,maxMinutes:t}=v.current;n({shortcuts:e,autoSubmitTerminal:r,maxRecordingSeconds:60*Math.max(1,t)})},[]),(0,t.useEffect)(()=>{let e=e=>{a.current&&!a.current.contains(e.target)&&o()};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[o]),(0,r.jsxs)("div",{ref:a,className:"w-80 rounded-lg border border-void-200 bg-white p-4 shadow-(--shadow-overlay) dark:border-void-600 dark:bg-void-800",children:[(0,r.jsx)("h3",{className:"mb-3 text-sm font-medium text-void-800 dark:text-void-200",children:"Voice Settings"}),(0,r.jsxs)("div",{className:"mb-4 space-y-2",children:[(0,r.jsx)("div",{className:"text-xs font-medium text-void-500 dark:text-void-400",children:"Keyboard Shortcuts"}),(0,r.jsx)(d,{label:"Start recording",value:s.startRecording,onChange:e=>i(r=>({...r,startRecording:e}))}),(0,r.jsx)(d,{label:"Pause / Resume",value:s.pauseResume,onChange:e=>i(r=>({...r,pauseResume:e}))}),(0,r.jsx)(d,{label:"Submit",value:s.submit,onChange:e=>i(r=>({...r,submit:e}))}),(0,r.jsx)(d,{label:"Paste only",value:s.submitPasteOnly,onChange:e=>i(r=>({...r,submitPasteOnly:e}))}),(0,r.jsx)(d,{label:"Clear / Cancel",value:s.clear,onChange:e=>i(r=>({...r,clear:e}))})]}),(0,r.jsxs)("div",{className:"space-y-3 border-t border-void-200 pt-3 dark:border-void-600",children:[(0,r.jsx)("div",{className:"text-xs font-medium text-void-500 dark:text-void-400",children:"Behaviour"}),(0,r.jsxs)("label",{className:"flex items-center justify-between gap-2",children:[(0,r.jsx)("span",{className:"text-xs text-void-600 dark:text-void-400",children:"Auto-submit (terminal)"}),(0,r.jsx)("button",{type:"button",role:"switch","aria-checked":c,onClick:()=>l(!c),className:`relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors ${c?"bg-green-500":"bg-void-300 dark:bg-void-600"}`,children:(0,r.jsx)("span",{className:`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow transition ${c?"translate-x-4":"translate-x-0"}`})})]}),(0,r.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,r.jsx)("span",{className:"text-xs text-void-600 dark:text-void-400",children:"Max recording (min)"}),(0,r.jsx)("input",{type:"number",min:1,max:30,value:u,onChange:e=>m(parseInt(e.target.value)||5),className:"w-16 rounded border border-void-300 bg-void-50 px-2 py-1 text-center text-xs text-void-700 outline-none dark:border-void-600 dark:bg-void-700 dark:text-void-300"})]})]})]})}function l({error:e,hasRecording:n,onRetry:o,onClear:a,onClose:s}){let i=(0,t.useRef)(null);(0,t.useEffect)(()=>{let e=e=>{i.current&&!i.current.contains(e.target)&&s()};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[s]);let d=window.isSecureContext,c=!n;return(0,r.jsxs)("div",{ref:i,className:"w-72 rounded-lg border border-red-400/30 bg-white p-3 shadow-(--shadow-overlay) dark:border-red-400/20 dark:bg-void-800",children:[(0,r.jsxs)("div",{className:"mb-2 flex items-start gap-2",children:[(0,r.jsx)("svg",{className:"mt-0.5 h-4 w-4 flex-shrink-0 text-red-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,r.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"})}),(0,r.jsxs)("div",{children:[(0,r.jsx)("div",{className:"text-xs font-medium text-red-600 dark:text-red-400",children:c?"Recording Failed":"Transcription Failed"}),(0,r.jsx)("div",{className:"mt-1 text-xs text-void-600 dark:text-void-300",children:e})]})]}),(0,r.jsx)("div",{className:"mt-2 text-xs text-void-500 dark:text-void-400",children:c&&!d?"Microphone requires HTTPS or localhost. Try accessing via localhost:3003, or add this origin to chrome://flags/#unsafely-treat-insecure-origin-as-secure.":c?"Could not access the microphone.":"Your recording is preserved. You can retry or clear and start over."}),(0,r.jsxs)("div",{className:"mt-3 flex justify-end gap-2",children:[(0,r.jsx)("button",{onClick:a,className:"rounded border border-void-300 bg-void-100 px-2 py-1 text-xs text-void-600 hover:text-void-800 dark:border-void-600 dark:bg-void-700 dark:text-void-400 dark:hover:text-void-300",children:c?"Dismiss":"Clear"}),n&&o&&(0,r.jsx)("button",{onClick:o,className:"rounded border border-neon-blue-400/40 bg-neon-blue-400/15 px-2 py-1 text-xs text-neon-blue-600 hover:bg-neon-blue-400/25 dark:text-neon-blue-400",children:"Retry"})]})]})}function u(){let e=v(),n=(0,t.useRef)(null),o=(0,t.useRef)(0),[a,d]=(0,t.useState)(!1);return((0,t.useEffect)(()=>{d(!0)},[]),null===e.currentClaimantId&&a&&("idle"!==e.voiceState&&"disabled"!==e.voiceState||e.showSettings))?(0,s.createPortal)((0,r.jsxs)("div",{className:"fixed bottom-4 right-4 z-50 animate-in fade-in slide-in-from-bottom-2 duration-200 rounded-xl border border-red-400/30 bg-white/95 px-3 py-2 shadow-(--shadow-card) backdrop-blur-sm dark:border-red-400/20 dark:bg-void-800/95",ref:n,children:[(0,r.jsx)(i,{voiceState:e.voiceState,elapsedSeconds:e.elapsedSeconds,disabled:!1,error:e.error,onRecord:e.startRecording,onPause:e.pauseRecording,onResume:e.resumeRecording,onClear:e.clearRecording,onSubmit:e.submitRecording,onRetry:e.retryTranscription,onOpenSettings:()=>{Date.now()-o.current<200||e.setShowSettings(!e.showSettings)}}),e.showSettings&&(0,r.jsx)("div",{style:{position:"fixed",bottom:60,right:16,zIndex:9999},children:(0,r.jsx)(c,{settings:e.settings.voice,onSave:r=>e.updateSettings({voice:r}),onClose:()=>{o.current=Date.now(),e.setShowSettings(!1)}})}),"error"===e.voiceState&&e.error&&(0,r.jsx)("div",{style:{position:"fixed",bottom:60,right:16,zIndex:9999},children:(0,r.jsx)(l,{error:e.error,hasRecording:e.hasRecording,onRetry:()=>e.retryTranscription(),onClear:()=>e.clearRecording(),onClose:()=>e.clearRecording()})})]}),document.body):null}e.s(["VoiceControlBar",()=>i],39269),e.s(["VoiceSettingsPopover",()=>c],9083),e.s(["VoiceErrorPopup",()=>l],49996);let m=(0,t.createContext)(null);function v(){let e=(0,t.useContext)(m);if(!e)throw Error("useVoice() must be used inside <VoiceProvider>");return e}function b({children:e}){let{settings:s,updateSettings:i}=function(){let[e,r]=(0,t.useState)(a),[n,s]=(0,t.useState)(!0);return(0,t.useEffect)(()=>{fetch("/api/settings").then(e=>e.json()).then(e=>{r({...a,...e,voice:{...o,...e?.voice}})}).catch(()=>{}).finally(()=>s(!1))},[]),{settings:e,updateSettings:(0,t.useCallback)(async e=>{try{let t=await fetch("/api/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(t.ok){let e=await t.json();return r({...a,...e,voice:{...o,...e?.voice}}),e}}catch{}return null},[]),isLoading:n}}(),d=(0,t.useRef)(new Map),c=(0,t.useRef)(null),l=(0,t.useCallback)((e,r)=>{d.current.set(e,r),c.current=e},[]),v=(0,t.useCallback)(e=>{if(d.current.delete(e),c.current===e){let e=Array.from(d.current.keys());c.current=e.length>0?e[0]:null}},[]),b=(0,t.useRef)(null),[x,h]=(0,t.useState)(null),p=(0,t.useRef)(null),g=(0,t.useRef)("auto"),[f,k]=(0,t.useState)(!1),[y,R]=(0,t.useState)(!1),j=(0,t.useCallback)(e=>{let r=p.current;if(r){if("input"===r.type&&r.element){if(!document.contains(r.element))return;let t=r.element,n=t.selectionStart??t.value.length,o=t.selectionEnd??t.value.length;t.focus(),t.setSelectionRange(n,o),document.execCommand("insertText",!1,e)}else if("terminal"===r.type&&r.terminalId){let t=d.current.get(r.terminalId);if(!t)return;let n="auto"===g.current&&s.voice.autoSubmitTerminal;t.sendInput(e),n&&setTimeout(()=>t.sendInput("\r"),300)}g.current="auto"}},[s.voice.autoSubmitTerminal]),w=(0,t.useCallback)(e=>{b.current?b.current.onTranscriptionComplete(e):j(e)},[j]),S=function({maxRecordingSeconds:e,onTranscriptionComplete:r}){let[n,o]=(0,t.useState)("idle"),[a,s]=(0,t.useState)(0),[i,d]=(0,t.useState)(null),[c,l]=(0,t.useState)(null),u=(0,t.useRef)(null),m=(0,t.useRef)([]),v=(0,t.useRef)(null),b=(0,t.useRef)(null),x=(0,t.useRef)(null),h=(0,t.useRef)(0),p=(0,t.useRef)(!1),g=(0,t.useRef)(r);(0,t.useEffect)(()=>{g.current=r},[r]);let f=(0,t.useCallback)(()=>{v.current||(v.current=setInterval(()=>{s(e=>e+1)},1e3))},[]),k=(0,t.useCallback)(()=>{v.current&&(clearInterval(v.current),v.current=null)},[]);(0,t.useEffect)(()=>{"recording"===n&&a-h.current>=e&&(u.current?.state==="recording"&&u.current.pause(),p.current=!0,k(),o("paused"))},[n,a,e,k]),(0,t.useEffect)(()=>()=>{if(k(),u.current?.state!=="inactive")try{u.current?.stop()}catch{}x.current?.getTracks().forEach(e=>e.stop())},[k]);let y=(0,t.useCallback)(()=>new Promise(e=>{let r=u.current;r&&"inactive"!==r.state?(r.ondataavailable=e=>{e.data.size>0&&m.current.push(e.data)},r.onstop=()=>{e(new Blob(m.current,{type:m.current[0]?.type||"audio/webm"}))},r.stop()):e(new Blob(m.current,{type:m.current[0]?.type||"audio/webm"}))}),[]),R=(0,t.useCallback)(async e=>{let r=new FormData;r.append("audio",e,"recording.webm");let t=await fetch("/api/transcribe",{method:"POST",body:r}),n=await t.json();if(!t.ok)throw Error(n.error||"Transcription failed");return n.text},[]),j=(0,t.useCallback)(()=>{x.current?.getTracks().forEach(e=>e.stop()),x.current=null},[]),w=(0,t.useCallback)(async()=>{if("idle"===n||"disabled"===n)try{d(null),l(null),m.current=[],b.current=null,s(0),h.current=0;let e=await navigator.mediaDevices.getUserMedia({audio:!0});x.current=e;let r=MediaRecorder.isTypeSupported("audio/ogg;codecs=opus")?"audio/ogg;codecs=opus":MediaRecorder.isTypeSupported("audio/webm;codecs=opus")?"audio/webm;codecs=opus":MediaRecorder.isTypeSupported("audio/mp4")?"audio/mp4":"",t=new MediaRecorder(e,r?{mimeType:r}:void 0);u.current=t,t.ondataavailable=e=>{e.data.size>0&&m.current.push(e.data)},t.start(1e3),o("recording"),f()}catch(r){let e=r.message;e.includes("Permission")||e.includes("NotAllowed")?d("Microphone permission denied. Please allow microphone access."):d(e||"Failed to start recording"),o("error"),j()}},[n,f,j]),S=(0,t.useCallback)(()=>{"recording"===n&&(u.current?.state==="recording"&&u.current.pause(),k(),o("paused"))},[n,k]),C=(0,t.useCallback)(()=>{"paused"===n&&(u.current?.state==="paused"&&u.current.resume(),p.current&&(h.current=a,p.current=!1),f(),o("recording"))},[n,a,f]),N=(0,t.useCallback)(()=>{if(k(),u.current?.state!=="inactive")try{u.current?.stop()}catch{}m.current=[],b.current=null,s(0),d(null),l(null),j(),o("idle")},[k,j]),E=(0,t.useCallback)(async()=>{if("recording"===n||"paused"===n){k(),o("transcribing");try{let e=await y();b.current=e,j();let r=await R(e);l(r),s(0),m.current=[],o("idle"),g.current?.(r)}catch(e){d(e.message||"Transcription failed"),o("error")}}},[n,k,y,R,j]),T=(0,t.useCallback)(async()=>{if("error"===n&&b.current){o("transcribing"),d(null);try{let e=await R(b.current);l(e),b.current=null,m.current=[],s(0),o("idle"),g.current?.(e)}catch(e){d(e.message||"Transcription failed"),o("error")}}},[n,R]);return{state:n,elapsedSeconds:a,error:i,transcribedText:c,hasRecording:null!==b.current||m.current.length>0,startRecording:w,pauseRecording:S,resumeRecording:C,clearRecording:N,submitRecording:E,retryTranscription:T}}({maxRecordingSeconds:s.voice.maxRecordingSeconds,onTranscriptionComplete:w}),C=(0,t.useCallback)(e=>{b.current&&b.current.id!==e.id&&(b.current.onRelease?.(),"idle"!==S.state&&"disabled"!==S.state&&S.clearRecording()),b.current=e,h(e.id)},[S]),N=(0,t.useCallback)(e=>{b.current?.id===e.id&&("idle"!==S.state&&"error"!==S.state&&"disabled"!==S.state&&S.clearRecording(),b.current=null,h(null))},[S]);(0,t.useEffect)(()=>{let e=e=>{let r=e.target;if(r.closest("[data-voice-target]")&&("INPUT"===r.tagName||"TEXTAREA"===r.tagName))return void R(!0);if(r.closest("[data-terminal-id]")){let e=r.closest("[data-terminal-id]").dataset.terminalId;e&&d.current.has(e)&&(c.current=e,R(!0))}},r=()=>{setTimeout(()=>{let e=document.activeElement;if(!e)return void R(!1);let r=e.closest("[data-voice-target]")&&("INPUT"===e.tagName||"TEXTAREA"===e.tagName),t=e.closest("[data-terminal-id]");r||t||R(!1)},100)};return document.addEventListener("focusin",e),document.addEventListener("focusout",r),()=>{document.removeEventListener("focusin",e),document.removeEventListener("focusout",r)}},[]);let E=(0,t.useCallback)(()=>{if(b.current)b.current.onRecordStart?.(),S.startRecording();else{let e=document.activeElement;if(e&&("INPUT"===e.tagName||"TEXTAREA"===e.tagName)&&e.closest("[data-voice-target]"))p.current={type:"input",element:e};else if(e?.closest("[data-terminal-id]")){let r=e.closest("[data-terminal-id]").dataset.terminalId||c.current;if(!(r&&d.current.has(r)))return;p.current={type:"terminal",terminalId:r}}else{if(!(c.current&&d.current.has(c.current)))return;p.current={type:"terminal",terminalId:c.current}}S.startRecording()}},[S]),T=(0,t.useCallback)(()=>{g.current="paste",S.submitRecording()},[S]),P=(0,t.useCallback)(()=>{"recording"===S.state?S.pauseRecording():"paused"===S.state&&S.resumeRecording()},[S]);!function({voiceState:e,shortcuts:r,callbacks:o,enabled:a,hasFieldFocus:s,suspended:i}){let d=(0,t.useRef)(o),c=(0,t.useRef)(e),l=(0,t.useRef)(r),u=(0,t.useRef)(s),m=(0,t.useRef)(i);(0,t.useEffect)(()=>{d.current=o,c.current=e,l.current=r,u.current=s,m.current=i}),(0,t.useEffect)(()=>{if(!a)return;let e=e=>{if(m.current)return;let r=c.current,t=l.current,o=d.current;if("idle"===r&&u.current&&n(e,t.startRecording)){e.preventDefault(),e.stopPropagation(),o.onStartRecording();return}if("recording"===r||"paused"===r){if(n(e,t.pauseResume)){e.preventDefault(),e.stopPropagation(),o.onPauseResume();return}if(n(e,t.submitPasteOnly)){e.preventDefault(),e.stopPropagation(),o.onSubmitPasteOnly();return}if(n(e,t.submit)){e.preventDefault(),e.stopPropagation(),o.onSubmit();return}if(n(e,t.clear)){e.preventDefault(),e.stopPropagation(),o.onClear();return}}};return document.addEventListener("keydown",e,!0),()=>document.removeEventListener("keydown",e,!0)},[a])}({voiceState:S.state,shortcuts:s.voice.shortcuts,callbacks:{onStartRecording:E,onPauseResume:P,onSubmit:S.submitRecording,onSubmitPasteOnly:T,onClear:S.clearRecording},enabled:!0,hasFieldFocus:!!b.current||y,suspended:f});let L={voiceState:S.state,elapsedSeconds:S.elapsedSeconds,error:S.error,hasRecording:S.hasRecording,startRecording:S.startRecording,pauseRecording:S.pauseRecording,resumeRecording:S.resumeRecording,clearRecording:S.clearRecording,submitRecording:S.submitRecording,retryTranscription:S.retryTranscription,claimVoiceControl:C,releaseVoiceControl:N,currentClaimantId:x,settings:s,updateSettings:i,registerTerminal:l,unregisterTerminal:v,submitModeRef:g,showSettings:f,setShowSettings:k,hasFieldFocus:y,setHasFieldFocus:R};return(0,r.jsxs)(m.Provider,{value:L,children:[e,(0,r.jsx)(u,{})]})}}]);
@@ -16,6 +16,7 @@
16
16
  "@xterm/xterm": "^6.0.0",
17
17
  "croner": "^10.0.1",
18
18
  "diff": "^8.0.3",
19
+ "ebml": "^3.0.0",
19
20
  "next": "16.1.6",
20
21
  "openai": "^6.23.0",
21
22
  "react": "19.2.3",
@@ -3949,6 +3950,14 @@
3949
3950
  "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
3950
3951
  }
3951
3952
  },
3953
+ "node_modules/buffers": {
3954
+ "version": "0.1.1",
3955
+ "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
3956
+ "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==",
3957
+ "engines": {
3958
+ "node": ">=0.2.0"
3959
+ }
3960
+ },
3952
3961
  "node_modules/call-bind": {
3953
3962
  "version": "1.0.8",
3954
3963
  "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -4401,6 +4410,34 @@
4401
4410
  "node": ">= 0.4"
4402
4411
  }
4403
4412
  },
4413
+ "node_modules/ebml": {
4414
+ "version": "3.0.0",
4415
+ "resolved": "https://registry.npmjs.org/ebml/-/ebml-3.0.0.tgz",
4416
+ "integrity": "sha512-Q6C1u4/TX1nYipT9HNIopp95YyyyI0zs1GXdNRKO7XL7k+oo+ZtDc1CaJjpCdmlLxWsnlKBOXJCXkYU0K/Anlg==",
4417
+ "license": "MIT",
4418
+ "dependencies": {
4419
+ "buffers": "^0.1.1",
4420
+ "debug": "~3.1.0"
4421
+ },
4422
+ "engines": {
4423
+ "node": ">= 6.4"
4424
+ }
4425
+ },
4426
+ "node_modules/ebml/node_modules/debug": {
4427
+ "version": "3.1.0",
4428
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
4429
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
4430
+ "license": "MIT",
4431
+ "dependencies": {
4432
+ "ms": "2.0.0"
4433
+ }
4434
+ },
4435
+ "node_modules/ebml/node_modules/ms": {
4436
+ "version": "2.0.0",
4437
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
4438
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
4439
+ "license": "MIT"
4440
+ },
4404
4441
  "node_modules/electron-to-chromium": {
4405
4442
  "version": "1.5.283",
4406
4443
  "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz",
@@ -18,6 +18,7 @@
18
18
  "@xterm/xterm": "^6.0.0",
19
19
  "croner": "^10.0.1",
20
20
  "diff": "^8.0.3",
21
+ "ebml": "^3.0.0",
21
22
  "next": "16.1.6",
22
23
  "openai": "^6.23.0",
23
24
  "react": "19.2.3",
@@ -11,6 +11,7 @@ import {
11
11
  type LanguageCode,
12
12
  type MediaEncoding,
13
13
  } from '@aws-sdk/client-transcribe-streaming';
14
+ import { remuxWebmToOgg } from '@/lib/webm-to-ogg-opus';
14
15
 
15
16
  let openaiClient: OpenAI | null = null;
16
17
  let transcribeClient: TranscribeStreamingClient | null = null;
@@ -106,8 +107,13 @@ async function transcribeAwsStreaming(audioBuffer: Buffer, ext: string, region:
106
107
  streamBuffer = audioBuffer;
107
108
  mediaEncoding = 'ogg-opus';
108
109
  sampleRate = 48000;
110
+ } else if (ext === 'webm') {
111
+ // Remux WebM/Opus → OGG/Opus (pure JS, no ffmpeg)
112
+ streamBuffer = remuxWebmToOgg(audioBuffer);
113
+ mediaEncoding = 'ogg-opus';
114
+ sampleRate = 48000;
109
115
  } else {
110
- // Convert WebM/MP4 to PCM via ffmpeg
116
+ // MP4/other: convert to PCM via ffmpeg (Safari fallback)
111
117
  const tmpDir = process.env.TMPDIR || '/tmp';
112
118
  const tmpFile = path.join(tmpDir, `transcribe_${Date.now()}.${ext}`);
113
119
  pcmPath = tmpFile.replace(/\.[^.]+$/, '.pcm');
@@ -105,6 +105,7 @@ export function VoiceControlBar({
105
105
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
106
106
  </svg>
107
107
  </button>
108
+
108
109
  </>
109
110
  )}
110
111
 
@@ -123,11 +123,13 @@ export function useVoiceRecorder({ maxRecordingSeconds, onTranscriptionComplete
123
123
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
124
124
  streamRef.current = stream;
125
125
 
126
- const mimeType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
127
- ? 'audio/webm;codecs=opus'
128
- : MediaRecorder.isTypeSupported('audio/mp4')
129
- ? 'audio/mp4'
130
- : '';
126
+ const mimeType = MediaRecorder.isTypeSupported('audio/ogg;codecs=opus')
127
+ ? 'audio/ogg;codecs=opus'
128
+ : MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
129
+ ? 'audio/webm;codecs=opus'
130
+ : MediaRecorder.isTypeSupported('audio/mp4')
131
+ ? 'audio/mp4'
132
+ : '';
131
133
 
132
134
  const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);
133
135
  mediaRecorderRef.current = recorder;
@@ -0,0 +1,254 @@
1
+ /**
2
+ * WebM/Opus → OGG/Opus remuxer (pure JS, no ffmpeg).
3
+ *
4
+ * Extracts Opus packets from a WebM container and wraps them in a valid
5
+ * OGG Opus stream per RFC 7845. Only handles single-track audio-only WebM
6
+ * from browser MediaRecorder — not arbitrary Matroska files.
7
+ */
8
+
9
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
10
+ const { Decoder: EbmlDecoder } = require('ebml');
11
+
12
+ // --- OGG CRC-32 (polynomial 0x04C11DB7, no bit reversal) ---
13
+
14
+ const crcTable = new Uint32Array(256);
15
+ for (let i = 0; i < 256; i++) {
16
+ let r = i << 24;
17
+ for (let j = 0; j < 8; j++) {
18
+ r = r & 0x80000000 ? ((r << 1) ^ 0x04C11DB7) : (r << 1);
19
+ r = r >>> 0; // keep unsigned 32-bit
20
+ }
21
+ crcTable[i] = r;
22
+ }
23
+
24
+ function oggCrc32(data: Uint8Array): number {
25
+ let crc = 0;
26
+ for (let i = 0; i < data.length; i++) {
27
+ crc = ((crc << 8) ^ crcTable[((crc >>> 24) ^ data[i]) & 0xff]) >>> 0;
28
+ }
29
+ return crc;
30
+ }
31
+
32
+ // --- Opus packet duration parsing ---
33
+
34
+ function getOpusPacketDuration48k(packet: Uint8Array): number {
35
+ if (packet.length < 1) return 960; // fallback: 20ms
36
+
37
+ const toc = packet[0];
38
+ const config = (toc >> 3) & 0x1f;
39
+ const frameCountCode = toc & 0x03;
40
+
41
+ // Frame size in 48kHz samples based on config number
42
+ let frameSamples: number;
43
+ if (config <= 11) {
44
+ // SILK: 10ms, 20ms, 40ms, 60ms
45
+ frameSamples = [480, 960, 1920, 2880][config % 4];
46
+ } else if (config <= 15) {
47
+ // Hybrid: 10ms, 20ms
48
+ frameSamples = [480, 960][(config - 12) % 2];
49
+ } else {
50
+ // CELT: 2.5ms, 5ms, 10ms, 20ms
51
+ frameSamples = [120, 240, 480, 960][(config - 16) % 4];
52
+ }
53
+
54
+ // Number of frames per packet
55
+ let frameCount: number;
56
+ switch (frameCountCode) {
57
+ case 0: frameCount = 1; break;
58
+ case 1: frameCount = 2; break;
59
+ case 2: frameCount = 2; break;
60
+ case 3:
61
+ frameCount = packet.length >= 2 ? (packet[1] & 0x3f) : 1;
62
+ break;
63
+ default: frameCount = 1;
64
+ }
65
+
66
+ return frameSamples * frameCount;
67
+ }
68
+
69
+ // --- OGG page writer ---
70
+
71
+ function writeOggPage(
72
+ serialNumber: number,
73
+ sequenceNumber: number,
74
+ granulePosition: bigint,
75
+ flags: number, // 0x02 = BOS, 0x04 = EOS, 0x01 = continued
76
+ packets: Uint8Array[],
77
+ ): Buffer {
78
+ // Build segment table: each packet is split into 255-byte segments + remainder
79
+ const segmentSizes: number[] = [];
80
+ for (const pkt of packets) {
81
+ let remaining = pkt.length;
82
+ while (remaining >= 255) {
83
+ segmentSizes.push(255);
84
+ remaining -= 255;
85
+ }
86
+ segmentSizes.push(remaining); // final segment (0-254), 0 means exactly N*255 bytes
87
+ }
88
+
89
+ const payloadSize = packets.reduce((sum, p) => sum + p.length, 0);
90
+ const headerSize = 27 + segmentSizes.length;
91
+ const pageSize = headerSize + payloadSize;
92
+ const page = Buffer.alloc(pageSize);
93
+
94
+ // Capture pattern
95
+ page.write('OggS', 0);
96
+ // Version
97
+ page[4] = 0;
98
+ // Header type flags
99
+ page[5] = flags;
100
+ // Granule position (64-bit little-endian)
101
+ page.writeBigInt64LE(granulePosition, 6);
102
+ // Serial number
103
+ page.writeUInt32LE(serialNumber, 14);
104
+ // Page sequence number
105
+ page.writeUInt32LE(sequenceNumber, 18);
106
+ // CRC placeholder (filled after)
107
+ page.writeUInt32LE(0, 22);
108
+ // Number of segments
109
+ page[26] = segmentSizes.length;
110
+ // Segment table
111
+ for (let i = 0; i < segmentSizes.length; i++) {
112
+ page[27 + i] = segmentSizes[i];
113
+ }
114
+ // Payload
115
+ let offset = headerSize;
116
+ for (const pkt of packets) {
117
+ Buffer.from(pkt).copy(page, offset);
118
+ offset += pkt.length;
119
+ }
120
+
121
+ // Compute and write CRC
122
+ const crc = oggCrc32(page);
123
+ page.writeUInt32LE(crc, 22);
124
+
125
+ return page;
126
+ }
127
+
128
+ // --- WebM parsing ---
129
+
130
+ interface WebmParseResult {
131
+ opusHead: Buffer;
132
+ codecDelay: number; // nanoseconds
133
+ packets: Uint8Array[];
134
+ }
135
+
136
+ function parseWebmOpus(webmBuffer: Buffer): WebmParseResult {
137
+ const decoder = new EbmlDecoder();
138
+ let opusHead: Buffer | null = null;
139
+ let codecDelay = 0;
140
+ let audioTrack = 1;
141
+ const packets: Uint8Array[] = [];
142
+
143
+ // Track whether we're inside a TrackEntry to associate CodecPrivate with the right track
144
+ let inTrackEntry = false;
145
+ let currentTrackNumber = 0;
146
+ let currentCodecId = '';
147
+
148
+ const events: Array<[string, Record<string, unknown>]> = [];
149
+ decoder.on('data', (chunk: [string, Record<string, unknown>]) => events.push(chunk));
150
+ decoder.write(webmBuffer);
151
+ decoder.end();
152
+
153
+ for (const [type, tag] of events) {
154
+ const name = tag.name as string;
155
+
156
+ if (type === 'start' && name === 'TrackEntry') {
157
+ inTrackEntry = true;
158
+ currentTrackNumber = 0;
159
+ currentCodecId = '';
160
+ }
161
+
162
+ if (type === 'end' && name === 'TrackEntry') {
163
+ if (currentCodecId === 'A_OPUS' && currentTrackNumber > 0) {
164
+ audioTrack = currentTrackNumber;
165
+ }
166
+ inTrackEntry = false;
167
+ }
168
+
169
+ if (type === 'tag' && inTrackEntry) {
170
+ if (name === 'TrackNumber') {
171
+ currentTrackNumber = tag.value as number;
172
+ }
173
+ if (name === 'CodecID') {
174
+ currentCodecId = tag.value as string;
175
+ }
176
+ if (name === 'CodecPrivate') {
177
+ opusHead = Buffer.from(tag.data as Uint8Array);
178
+ }
179
+ if (name === 'CodecDelay') {
180
+ codecDelay = tag.value as number;
181
+ }
182
+ }
183
+
184
+ if (type === 'tag' && name === 'SimpleBlock') {
185
+ const track = tag.track as number;
186
+ const payload = tag.payload as Uint8Array | null;
187
+ if (track === audioTrack && payload && payload.length > 0) {
188
+ packets.push(Uint8Array.from(payload));
189
+ }
190
+ }
191
+ }
192
+
193
+ if (!opusHead) {
194
+ throw new Error('No Opus CodecPrivate (OpusHead) found in WebM');
195
+ }
196
+ if (packets.length === 0) {
197
+ throw new Error('No Opus audio packets found in WebM');
198
+ }
199
+
200
+ return { opusHead, codecDelay, packets };
201
+ }
202
+
203
+ // --- Main remux function ---
204
+
205
+ export function remuxWebmToOgg(webmBuffer: Buffer): Buffer {
206
+ const { opusHead, codecDelay, packets } = parseWebmOpus(webmBuffer);
207
+
208
+ const serialNumber = Math.floor(Math.random() * 0xFFFFFFFF);
209
+ let sequenceNumber = 0;
210
+
211
+ // Pre-skip: convert CodecDelay from nanoseconds to 48kHz samples
212
+ // Also read pre-skip from OpusHead itself (bytes 10-11, little-endian uint16)
213
+ const opusHeadPreSkip = opusHead.readUInt16LE(10);
214
+ const preSkip = codecDelay > 0
215
+ ? Math.round(codecDelay / 1e9 * 48000)
216
+ : opusHeadPreSkip;
217
+
218
+ // If CodecDelay-derived pre-skip differs from OpusHead, patch OpusHead
219
+ const finalOpusHead = Buffer.from(opusHead);
220
+ if (preSkip !== opusHeadPreSkip) {
221
+ finalOpusHead.writeUInt16LE(preSkip, 10);
222
+ }
223
+
224
+ const pages: Buffer[] = [];
225
+
226
+ // Page 0: OpusHead (BOS)
227
+ pages.push(writeOggPage(serialNumber, sequenceNumber++, BigInt(0), 0x02, [finalOpusHead]));
228
+
229
+ // Page 1: OpusTags
230
+ const vendor = Buffer.from('remux');
231
+ const opusTags = Buffer.alloc(8 + 4 + vendor.length + 4);
232
+ opusTags.write('OpusTags', 0);
233
+ opusTags.writeUInt32LE(vendor.length, 8);
234
+ vendor.copy(opusTags, 12);
235
+ opusTags.writeUInt32LE(0, 12 + vendor.length); // zero user comments
236
+ pages.push(writeOggPage(serialNumber, sequenceNumber++, BigInt(0), 0, [opusTags]));
237
+
238
+ // Data pages: one packet per page for simplicity
239
+ let granulePosition = BigInt(preSkip); // starts at pre-skip per RFC 7845
240
+ for (let i = 0; i < packets.length; i++) {
241
+ const duration = getOpusPacketDuration48k(packets[i]);
242
+ granulePosition += BigInt(duration);
243
+ const isLast = i === packets.length - 1;
244
+ pages.push(writeOggPage(
245
+ serialNumber,
246
+ sequenceNumber++,
247
+ granulePosition,
248
+ isLast ? 0x04 : 0, // EOS on last page
249
+ [packets[i]],
250
+ ));
251
+ }
252
+
253
+ return Buffer.concat(pages);
254
+ }