@chatpanel/events 0.222.0 → 0.226.0
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/capture-plan.js +1 -0
- package/meeting-apps.js +1 -0
- package/note-transcript.js +10 -0
- package/package.json +7 -1
- package/voice-channels.js +1 -1
- package/voice-scribe.js +1 -1
package/capture-plan.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const P=Object.freeze(["online","in-person","hybrid"]),O=Object.freeze(["off","standard","strong"]),k=Object.freeze(["app","system","tab","picker","device"]),p=Object.freeze({scenario:Object.freeze(["online","in-person","hybrid"]),noise:Object.freeze(["auto","off","standard","strong"]),echo:Object.freeze(["auto","on","off"]),gain:Object.freeze(["auto","on","off"]),playback:Object.freeze(["auto",...k])}),E=Object.freeze({scenario:"online",noise:"auto",echo:"auto",gain:"auto",playback:"auto",app:null}),R=Object.freeze({echoCancellation:!1,noiseSuppression:!1,autoGainControl:!1}),b=Object.freeze({echo:!0,noise:Object.freeze(["standard"]),gain:!0}),z=Object.freeze({macos:Object.freeze({playback:Object.freeze(["app","system","picker"]),mic:b,micMode:"user"}),windows:Object.freeze({playback:Object.freeze(["app","system"]),mic:b,micMode:null}),linux:Object.freeze({playback:Object.freeze(["app","system","device"]),mic:b,micMode:null}),ios:Object.freeze({playback:Object.freeze([]),mic:Object.freeze({echo:!0,noise:Object.freeze(["standard"]),gain:!0}),micMode:"user"}),android:Object.freeze({playback:Object.freeze([]),mic:Object.freeze({echo:!0,noise:Object.freeze(["standard"]),gain:!0}),micMode:null}),browser:Object.freeze({playback:Object.freeze(["tab","picker"]),mic:b,micMode:null}),terminal:Object.freeze({playback:Object.freeze(["device"]),mic:Object.freeze({echo:!1,noise:Object.freeze([]),gain:!1}),micMode:null})}),_=Object.freeze(Object.keys(z));function M(n,o={}){const a=z[n];if(!a)throw new Error(`unknown capture platform: ${n}`);const e=o&&typeof o=="object"?o:{};return{playback:Array.isArray(e.playback)?e.playback.filter(s=>k.includes(s)):[...a.playback],mic:{...a.mic,...e.mic&&typeof e.mic=="object"?e.mic:{}},micMode:e.micMode!==void 0?e.micMode:a.micMode}}const u=(n,o,a)=>o.includes(n)?n:a;function v(n){const o=n&&typeof n=="object"?n:{},a=E;return{scenario:u(o.scenario,p.scenario,a.scenario),noise:u(o.noise,p.noise,a.noise),echo:u(o.echo,p.echo,a.echo),gain:u(o.gain,p.gain,a.gain),playback:u(o.playback,p.playback,a.playback),app:typeof o.app=="string"&&o.app.trim()?o.app.trim().slice(0,80):null}}const g={"playback-unsupported":["info","That way of hearing the call is not available here, so another one is used."],"far-end-through-speakers":["warn","The call’s audio cannot be captured on its own here, so the microphone listens to the whole room — keep the call on speaker."],"far-end-unheard":["warn","The call’s audio cannot be captured on its own here, and with headphones only your side will be transcribed. Switch to the speaker to include everyone."],"strong-drops-room-voices":["info","Strong noise suppression keeps only the nearest voice, so standard is used for a room."],"noise-unsupported":["info","That level of noise suppression is not available here, so the nearest one is used."],"echo-unavailable":["warn","Echo cancellation is not available here — use headphones, or the call may be transcribed twice."],"echo-cancels-far-end":["warn","Echo cancellation is on while the call is heard through the room, so the other side may be cut out."],"mic-mode-isolation":["warn","The microphone is set to Voice Isolation, which removes everyone but the nearest voice. Switch it to Standard or Wide Spectrum for a room."]},c=n=>({code:n,level:g[n][0],text:g[n][1]});function x(n,o,a){const e=a.playback;return n!=="auto"?e.includes(n)?{source:n,fell:!1}:{source:w(o,e),fell:!0}:{source:w(o,e),fell:!1}}function w(n,o){return n&&o.includes("app")?"app":o.find(a=>a!=="app")||null}function T(n,o){if(n==="off")return"off";const a=O.indexOf(n);for(let e=a;e>0;e--)if(o.includes(O[e]))return O[e];return"off"}function I({prefs:n,caps:o,facts:a}={}){const e=v(n),s=o&&typeof o=="object"?o:M("browser"),f=s.mic||{echo:!1,noise:[],gain:!1},d=a?.headphones===!0?!0:a?.headphones===!1?!1:null,t=[];let r=null;if(e.scenario!=="in-person"){const{source:y,fell:S}=x(e.playback,e.app,{playback:Array.isArray(s.playback)?s.playback:[]});S&&t.push(c("playback-unsupported")),y&&(r={source:y,app:y==="app"?e.app:null})}let l=e.scenario==="online"?"self":"room",m=!r&&e.scenario==="hybrid";!r&&e.scenario==="online"&&(d===!0?t.push(c("far-end-unheard")):(l="room",m=!0,t.push(c("far-end-through-speakers"))));let i;e.echo!=="auto"?i=e.echo==="on":m||!r?i=!1:i=l==="room"||d!==!0,i&&m&&e.echo==="on"&&t.push(c("echo-cancels-far-end")),i&&!f.echo&&(i=!1,t.push(c("echo-unavailable")));let h=e.noise==="auto"?"standard":e.noise;h==="strong"&&l==="room"&&(h="standard",t.push(c("strong-drops-room-voices")));const j=T(h,Array.isArray(f.noise)?f.noise:[]);j!==h&&e.noise!=="auto"&&t.push(c("noise-unsupported"));const A=e.gain==="off"?!1:!!f.gain;l==="room"&&a?.micMode==="voice-isolation"&&t.push(c("mic-mode-isolation"));const C=!!r&&d!==!0;return{scenario:e.scenario,scribe:{mic:l,playback:!!r,echoGuard:C},mic:{echo:i,noise:j,gain:A},playback:r,notices:t}}function L(n){const o=n&&typeof n=="object"?n:{};return{channelCount:1,echoCancellation:!!o.echo,noiseSuppression:!!o.noise&&o.noise!=="off",autoGainControl:!!o.gain}}export{p as CAPTURE_PREFS,E as DEFAULT_CAPTURE_PREFS,O as NOISE_LEVELS,_ as PLATFORMS,R as PLAYBACK_AUDIO_CONSTRAINTS,k as PLAYBACK_SOURCES,P as SCENARIOS,M as captureCaps,L as micAudioConstraints,I as planCapture,v as readCapturePrefs};
|
package/meeting-apps.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const l=Object.freeze([{id:"zoom",label:"Zoom",macos:["us.zoom.xos"],windows:["Zoom.exe"]},{id:"teams",label:"Microsoft Teams",macos:["com.microsoft.teams2","com.microsoft.teams"],windows:["ms-teams.exe","Teams.exe"]},{id:"webex",label:"Webex",macos:["Cisco-Systems.Spark"],windows:["CiscoCollabHost.exe"]},{id:"slack",label:"Slack",macos:["com.tinyspeck.slackmacgap"],windows:["slack.exe"]},{id:"facetime",label:"FaceTime",macos:["com.apple.FaceTime"],windows:[]}].map(e=>Object.freeze({...e,macos:Object.freeze(e.macos),windows:Object.freeze(e.windows)})));function u(e,c="macos"){const a=String(e??"").trim(),t=l.find(o=>o.id===a);return t?c==="macos"?[...t.macos]:[...t.windows]:c==="macos"&&/^[A-Za-z0-9][\w.-]{0,119}$/.test(a)&&a.includes(".")?[a]:[]}const r=(e,c)=>!!e&&!!c&&(e===c||e.startsWith(`${c}.`)),f=e=>!e||/^com\.apple\./.test(e)&&!l.some(c=>c.macos.some(a=>r(e,a))),d=e=>String(e??"").replace(/[\u0000-\u001f]/g,"").trim().slice(0,80);function g(e,{platform:c="macos",self:a=null}={}){if(c!=="macos")return[];const t=new Map;for(const o of Array.isArray(e)?e:[]){const s=d(o?.bundleID);if(f(s)||a&&r(s,a))continue;const n=l.find(m=>m.macos.some(w=>r(s,w))),p=n?n.id:[...t.keys()].find(m=>!l.some(w=>w.id===m)&&r(s,m))||s,i=t.get(p)||{id:p,label:n?n.label:d(o?.name)||s,bundles:n?[...n.macos]:[s],meeting:!!n,playing:!1,mic:!1};i.playing=i.playing||!!o?.output,i.mic=i.mic||!!o?.input,t.set(p,i)}return[...t.values()].filter(o=>o.meeting||o.playing).sort((o,s)=>s.meeting-o.meeting||s.playing-o.playing||o.label.localeCompare(s.label))}export{l as MEETING_APPS,u as appBundles,r as bundleMatches,g as rankAudioApps};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const g=n=>String(n||"Speaker").replace(/[*_[\]`\n\r]/g,"").trim().slice(0,60)||"Speaker";function x(n,r,s=null){const e=String(n?.text??""),t=Math.min(Math.max(Number(n?.end??n?.start??e.length)||0,0),e.length),d=Math.min(Math.max(Number(n?.start??t)||0,0),t),i=String(r?.text??"").replace(/\s+/g," ").trim();if(!i)return{text:e,caret:t,last:s};const l=!!s&&s.sid===r.sid&&s.at===t&&d===t;let a;if(l)a=` ${i}`;else{const o=e.slice(0,t);a=`${o.trim()?/\n\n$/.test(o)?"":/\n$/.test(o)?`
|
|
2
|
+
`:`
|
|
3
|
+
|
|
4
|
+
`:""}**${g(r.speaker)}:** ${i}`}const c=e.slice(t),h=l||!c||c.startsWith(`
|
|
5
|
+
|
|
6
|
+
`)?"":c.startsWith(`
|
|
7
|
+
`)?`
|
|
8
|
+
`:`
|
|
9
|
+
|
|
10
|
+
`,m=t+a.length;return{text:e.slice(0,t)+a+h+c,caret:m,last:{sid:r.sid,at:m}}}export{x as transcriptInsert};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/events",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.226.0",
|
|
4
4
|
"description": "The canonical ChatPanel event-log and capability contracts — typed durable facts, clock-free deterministic linearization, schema upcasting, and the invariants the replay harness asserts. Pure, dependency-free ESM shared by the ChatPanel extension, gateway and bridge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -216,6 +216,9 @@
|
|
|
216
216
|
"./voice-speaker.js": "./voice-speaker.js",
|
|
217
217
|
"./voice-channels.js": "./voice-channels.js",
|
|
218
218
|
"./voice-scribe.js": "./voice-scribe.js",
|
|
219
|
+
"./capture-plan.js": "./capture-plan.js",
|
|
220
|
+
"./meeting-apps.js": "./meeting-apps.js",
|
|
221
|
+
"./note-transcript.js": "./note-transcript.js",
|
|
219
222
|
"./voice-loop.js": "./voice-loop.js",
|
|
220
223
|
"./voice-vad.js": "./voice-vad.js",
|
|
221
224
|
"./voice-sample.js": "./voice-sample.js",
|
|
@@ -460,6 +463,9 @@
|
|
|
460
463
|
"voice-speaker.js",
|
|
461
464
|
"voice-channels.js",
|
|
462
465
|
"voice-scribe.js",
|
|
466
|
+
"capture-plan.js",
|
|
467
|
+
"meeting-apps.js",
|
|
468
|
+
"note-transcript.js",
|
|
463
469
|
"voice-loop.js",
|
|
464
470
|
"voice-vad.js",
|
|
465
471
|
"voice-sample.js",
|
package/voice-channels.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{PINNED_SELF as
|
|
1
|
+
import{PINNED_SELF as B}from"./voice-speaker.js";const $=Object.freeze(["mic","playback"]),A=B,_="Speaker",M="In room",W=Object.freeze(["self","room"]),z=6e3,F=4e3,v=new Set(["thank you","thanks","you","uh","um","hmm","hm","mm","amen","bye","okay","ok","yeah","oh","the","that","so","and","i","a","me","we","yes","no","you you","thank you thank you"]);function J(n,o=null){if(o!=null&&o>2e3)return!1;const r=String(n||"").toLowerCase().replace(/[^\p{L}\p{N}\s]/gu,"").replace(/\s+/g," ").trim();return!r||v.has(r)}const j=15e3,T=4,U=.7,R=n=>String(n||"").toLowerCase().replace(/[^\p{L}\p{N}\s']/gu," ").split(/\s+/).filter(Boolean);function q(n,o){const r=new Map;for(const i of o)r.set(i,(r.get(i)||0)+1);let m=0;for(const i of n){const k=r.get(i)||0;k>0&&(m++,r.set(i,k-1))}return n.length?m/n.length:0}const G=n=>String(n||"").split(/\s+/).filter(Boolean).slice(0,2).map(o=>o[0].toUpperCase()).join("")||"?",K=n=>{if(!n)return _;const o=typeof n=="string"?n:n.label||n.id;return String(o||"").trim()||_};function V(n,o,{mic:r="self",playback:m=!0}={}){if(n==="mic"&&r!=="room")return A;const i=K(o);return n!=="mic"||!m?i:/^Speaker\b/.test(i)?i.replace(/^Speaker\b/,M):`${M} · ${i}`}const b=n=>String(n||"").replace(/\s+/g," ").trim();function Q({now:n=Date.now,mic:o="self",playback:r=!0,seed:m=[],echoGuard:i=!1}={}){if(!W.includes(o))throw new Error(`unknown mic mode: ${o}`);const k={mic:o,playback:r},s=[],x={mic:"",playback:""},D=!!i&&!!r;let S=[],E=0;const w=e=>(S=S.filter(t=>e-t.t<=j),S.flatMap(t=>t.words)),N=(e,t)=>e.length>=T&&q(e,t)>=U;for(const e of Array.isArray(m)?m:[]){if(!e||!e.text)continue;const t=e.speaker===A||o==="room"&&!r?"mic":"playback";s.push({sid:`s:${++E}`,t:Number(e.t)||0,channel:t,speaker:String(e.speaker||_),text:b(e.text),open:!1,seed:!0})}const H=e=>{for(let t=s.length-1;t>=0;t--)if(s[t].channel===e)return s[t];return null};return{final(e,t,{speaker:a,endOfTurn:u,durationMs:p=null}={}){if(!$.includes(e))throw new Error(`unknown channel: ${e}`);const c=n(),f=b(t);if(!f||(x[e]="",J(f,p)))return null;if(D){const I=R(f);if(e==="mic"){if(N(I,w(c)))return null}else{S.push({t:c,words:I});const P=w(c);for(let g=s.length-1;g>=0&&c-s[g].t<=j;g--){const O=s[g];O.channel==="mic"&&!O.seed&&N(R(O.text),P)&&s.splice(g,1)}}}const d=V(e,a,k),l=s[s.length-1],h=p!=null?c-p-l?.t:null,y=h!=null?h<=F:c-(l?.t||0)<=z;if(l&&!l.seed&&l.channel===e&&l.speaker===d&&(l.open||y))return l.text=`${l.text} ${f}`,l.t=c,l.open=u===!1,{...l,joined:!0};const L={sid:`a:${++E}`,t:c,channel:e,speaker:d,text:f,open:u===!1};return s.push(L),{...L,joined:!1}},interim(e,t){$.includes(e)&&(x[e]=b(t))},interims:()=>({...x}),segments:()=>s.map(({sid:e,t,channel:a,speaker:u,text:p})=>({sid:e,t,channel:a,speaker:u,text:p})),delta:(e=0,t=40)=>s.filter(a=>a.t>=e).slice(-t).map(({sid:a,t:u,speaker:p,text:c})=>({sid:a,t:u,speaker:p,text:c})),speakers(){const e={mic:[],playback:[]};for(const t of s)e[t.channel].includes(t.speaker)||e[t.channel].push(t.speaker);return e},last:e=>{const t=H(e);return t?{...t}:null},record({id:e,title:t,startedAt:a,status:u="live",endReason:p=null,participants:c}={}){const f=u==="ended",d=this.speakers();return{id:e,schemaVersion:1,platform:"audio",meetingKey:`${r?"audio":"room"}:${a}`,title:t||(r?"Audio transcript":"Room transcript"),url:"",status:u,endReason:f?p||"ended":null,startedAt:a,endedAt:f?n():null,participants:c||[...d.mic,...d.playback].map(h=>({name:h,initials:G(h)})),segments:s.map(({t:h,speaker:y,text:C})=>({t:h,speaker:y,text:C})),chat:[]}}}}export{$ as CHANNELS,U as ECHO_MATCH,T as ECHO_MIN_WORDS,j as ECHO_WINDOW_MS,z as JOIN_GAP_MS,F as JOIN_PAUSE_MS,A as MIC_LABEL,W as MIC_MODES,_ as OTHER_LABEL,M as ROOM_PREFIX,V as channelSpeaker,Q as createChannelTranscript,J as isNoiseFinal};
|
package/voice-scribe.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createChannelTranscript as
|
|
1
|
+
import{createChannelTranscript as W}from"./voice-channels.js";const X=4e3,Y=3e4,F=3e3,Z={setTimeout:(r,n)=>setTimeout(r,n),clearTimeout:r=>clearTimeout(r),setInterval:(r,n)=>setInterval(r,n),clearInterval:r=>clearInterval(r)},G=()=>Math.random().toString(36).slice(2,8),U=r=>{const n=new Date(r);return`${n.getHours()}:${String(n.getMinutes()).padStart(2,"0")}`};function et({mic:r="self",playback:n=!0,resume:o=null,live:v=!1,echoGuard:A=!1,openEar:y,capturePlayback:R,persist:w,onUpdate:m,onEnd:P,onNotice:C,onLine:_,now:f=Date.now,newId:j=G,timers:b=Z,flushDebounceMs:D=X,heartbeatMs:L=Y}={}){if(typeof y!="function")throw new Error("createAudioScribe: openEar is required");if(typeof w!="function")throw new Error("createAudioScribe: persist is required");if(n&&typeof R!="function")throw new Error("createAudioScribe: capturePlayback is required when playback is on");return{async start(){let i=null;if(n)try{i=await R({onEnded:()=>I("ended")})}catch(t){return t?.code!=="cancelled"&&C?.({level:"error",text:t?.message||"Could not capture what is playing"}),null}const E=o?.startedAt||f(),$=o?.meetingId||`mtg_${i?"audio":"room"}_${E.toString(36)}_${j()}`,l=W({now:f,mic:r,playback:!!i,seed:o?.segments||[],echoGuard:A});let h=!0,H=!1,q=o?.segments?.length?Math.max(...o.segments.map(t=>Number(t.t)||0))+1:0,z=0,T=null,O=null,x=!1;const g={mic:null,playback:null};let p=[],k=null;const J=t=>l.segments().some(s=>s.sid===t);function M(t=!1){const s=t?p:p.filter(e=>f()-e.at>=F);p=t?[]:p.filter(e=>f()-e.at<F);for(const e of s)J(e.line.sid)&&_?.(e.line);k&&(b.clearTimeout(k),k=null),p.length&&(k=b.setTimeout(()=>{k=null,M()},Math.max(0,F-(f()-p[0].at))))}function K(t,s,e){if(!_)return;const a={sid:s.sid,t:s.t,channel:t,speaker:s.speaker,text:e};if(t==="mic"&&A&&i){p.push({at:f(),line:a}),k||M();return}_(a)}const u={mic:"starting",playback:i?"starting":"off",lang:null},Q=()=>{if(o?.title)return o.title;const t=U(E);return i?i.surface==="screen"?`System audio · ${t}`:i.surface==="tab"?`Tab audio · ${t}`:`Audio · ${t}`:`Room · ${t}`};async function S(t="live",s){if(x)return;const e=l.record({id:$,startedAt:E,status:t,endReason:s,title:Q()}),a=l.delta(q);let c=null;try{c=await w(e,a)}catch{return}if(c?.limit){x=!0,I("limit");return}if(c?.ok===!1)return;z=f();const N=e.segments[e.segments.length-1];N&&(q=N.t)}function V(){if(h){if(f()-z>=D){S("live");return}T||(T=b.setTimeout(()=>{T=null,h&&S("live")},D))}}const B=(t,s)=>y(t,{...s,on:{onStart:()=>{u[t]="listening",m?.(d())},onInterim:e=>{l.interim(t,e),m?.(d())},onFinal:(e,a)=>{const c=l.final(t,e,a||{});c&&(K(t,c,String(e).replace(/\s+/g," ").trim()),V(),m?.(d()))},onStatus:e=>{e?.state==="language"&&e.lang?u.lang=e.lang:e?.state&&e.state!=="ready"&&(u[t]=e.state==="diarize"?"loading speaker model":e.state),m?.(d())},onError:({message:e,fatal:a}={})=>{u[t]=a?"failed":"listening",C?.({level:"error",text:`${t==="mic"?"Microphone":"Playback"}: ${e||"failed"}`});const c=t==="mic"?u.playback:u.mic;a&&(c==="failed"||c==="off")&&I("ended"),m?.(d())},onEnd:()=>{h&&u[t]!=="failed"&&(u[t]="stopped"),m?.(d())}}});g.mic=B("mic",{diarize:r==="room",interims:!!v,stream:null}),g.mic.start(),i&&(g.playback=B("playback",{diarize:!0,interims:!!v,stream:i.stream}),g.playback.start()),O=b.setInterval(()=>{h&&S("live")},L);function d(){return{meetingId:$,startedAt:E,mic:r,surface:i?i.surface:"mic",status:{...u},audio:{mic:g.mic?.stats?.()||null,playback:g.playback?.stats?.()||null},speakers:l.speakers(),interims:l.interims(),lines:l.segments().length,last:l.last("playback")||l.last("mic")}}function I(t="user"){if(H)return;H=!0,h=!1,b.clearInterval(O),T&&(b.clearTimeout(T),T=null),M(!0);for(const a of Object.values(g))try{a?.stop()}catch{}try{i?.stop()}catch{}(x?Promise.resolve():S("ended",t==="user"?"user":t==="interrupted"?"interrupted":"ended")).finally(()=>P?.({meetingId:$,reason:t}))}return m?.(d()),{meetingId:$,isRunning:()=>h,snapshot:d,stop:I,flush:()=>S("live")}}}}function rt(r,{now:n=Date.now()}={}){if(!r?.id)return null;const{id:o,title:v,tags:A,...y}=r;return{id:`meeting:${o}`,title:v||"Audio transcript",...A?{tags:A}:{},createdAt:r.startedAt||n,updatedAt:r.endedAt||n,body:{...y,segments:Array.isArray(y.segments)?y.segments:[]},meta:{origin:"capture",readOnly:!1,live:r.status!=="ended"}}}export{X as FLUSH_DEBOUNCE_MS,Y as HEARTBEAT_MS,F as LINE_SETTLE_MS,et as createAudioScribe,rt as meetingRecordFromCapture};
|