@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.
- package/dist/web/.next/BUILD_ID +1 -1
- package/dist/web/.next/build-manifest.json +2 -2
- package/dist/web/.next/server/app/_global-error.html +2 -2
- package/dist/web/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/_not-found.html +1 -1
- package/dist/web/.next/server/app/_not-found.rsc +10 -10
- package/dist/web/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
- package/dist/web/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/dist/web/.next/server/app/_not-found.segments/_index.segment.rsc +5 -5
- package/dist/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/dist/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/dist/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dist/web/.next/server/app/api/cli-assets/assistant/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/fix/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/import/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/store/preview/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/store/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/sync/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/updates/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/dashboard/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/file/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/git-status/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/kanban/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/kanban/stream/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/reorder/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/scheduler/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/search/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/sly-actions/invalidate/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/sly-actions/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/transcribe/route.js +3 -3
- package/dist/web/.next/server/app/api/transcribe/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/version-check/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/page.js.nft.json +1 -1
- package/dist/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/project/[id]/page.js.nft.json +1 -1
- package/dist/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/chunks/{[root-of-the-server]__934c9bb4._.js → [root-of-the-server]__0f9b71fd._.js} +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__1dec5018._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__e00fb85f._.js → [root-of-the-server]__2605d761._.js} +1 -1
- package/dist/web/.next/server/chunks/{[root-of-the-server]__41df0777._.js → [root-of-the-server]__53ef96b9._.js} +2 -2
- package/dist/web/.next/server/chunks/{[root-of-the-server]__1cdc76ef._.js → [root-of-the-server]__8ce673c9._.js} +2 -2
- package/dist/web/.next/server/chunks/[root-of-the-server]__a43db24d._.js +19 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__104b2da3._.js → [root-of-the-server]__a4d171b5._.js} +3 -3
- package/dist/web/.next/server/chunks/{[root-of-the-server]__3dc5531f._.js → [root-of-the-server]__c7ac3578._.js} +1 -1
- package/dist/web/.next/server/chunks/ssr/src_contexts_VoiceContext_tsx_cfba7292._.js +1 -1
- package/dist/web/.next/server/pages/404.html +1 -1
- package/dist/web/.next/server/pages/500.html +2 -2
- package/dist/web/.next/static/chunks/3df3846316317676.css +1 -0
- package/dist/web/.next/static/chunks/{0452f599128364c9.js → 8fb2a99c64580de7.js} +1 -1
- package/dist/web/.next/static/chunks/{2a22edae0b1198eb.js → b8e0c1aeea4a14bc.js} +1 -1
- package/dist/web/package-lock.json +37 -0
- package/dist/web/package.json +1 -0
- package/dist/web/src/app/api/transcribe/route.ts +7 -1
- package/dist/web/src/components/VoiceControlBar.tsx +1 -0
- package/dist/web/src/hooks/useVoiceRecorder.ts +7 -5
- package/dist/web/src/lib/webm-to-ogg-opus.ts +254 -0
- package/dist/web/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/templates/kanban-seed.json +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__3998d59e._.js +0 -19
- package/dist/web/.next/server/chunks/[root-of-the-server]__403750fd._.js +0 -3
- package/dist/web/.next/static/chunks/f3d7065d54a0b9ac.css +0 -1
- /package/dist/web/.next/static/{wK5osty7Iax92ZOE_Skb7 → eGpcv9LfmamGIm1II8SKL}/_buildManifest.js +0 -0
- /package/dist/web/.next/static/{wK5osty7Iax92ZOE_Skb7 → eGpcv9LfmamGIm1II8SKL}/_clientMiddlewareManifest.json +0 -0
- /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",
|
package/dist/web/package.json
CHANGED
|
@@ -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
|
-
//
|
|
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');
|
|
@@ -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/
|
|
127
|
-
? 'audio/
|
|
128
|
-
: MediaRecorder.isTypeSupported('audio/
|
|
129
|
-
? 'audio/
|
|
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
|
+
}
|