UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

235 lines (234 loc) 10.1 kB
//#region src/meeting-bot/page-script-source.ts function pageGlobalSource(name) { if (!/^[$A-Z_a-z][$\w]*$/u.test(name)) throw new Error(`Invalid meeting page global: ${name}`); return `window.${name}`; } function createMeetingTranscriptSource(params) { const transcriptMaxLines = params.transcriptMaxLines ?? 500; const meetingGlobal = pageGlobalSource(params.globals.meeting); const captionsGlobal = pageGlobalSource(params.globals.captions); const captionArchiveGlobal = pageGlobalSource(params.globals.captionArchive); return `() => { ${params.pageIdentitySource} const expectedIdentity = ${JSON.stringify(params.expectedIdentity)}; const expectedSessionId = ${JSON.stringify(params.meetingSessionId)}; const currentIdentity = meetingIdentity(location.href); const state = ${meetingGlobal}; const activeCaptions = ${captionsGlobal}; const archivedCaptions = ${captionArchiveGlobal}?.[expectedSessionId]; const captions = activeCaptions && (!activeCaptions.sessionId || activeCaptions.sessionId === expectedSessionId) ? activeCaptions : archivedCaptions; // A same-session finalized buffer belongs to the departed call even if ${params.platformDisplayName} // immediately navigated this tab into another meeting before transcript pickup. const useFinalizedCaptions = Boolean( captions?.finalized === true && captions?.identity === expectedIdentity && (!captions?.sessionId || captions.sessionId === expectedSessionId) ); const effectiveIdentity = useFinalizedCaptions ? captions.identity : currentIdentity || state?.identity || captions?.identity; if (!expectedIdentity || effectiveIdentity !== expectedIdentity) { return JSON.stringify({ urlMatched: false, droppedLines: 0, lines: [] }); } if (!useFinalizedCaptions && state?.sessionId && state.sessionId !== expectedSessionId) { return JSON.stringify({ urlMatched: true, sessionMatched: false, droppedLines: 0, lines: [] }); } if (captions?.sessionId && captions.sessionId !== expectedSessionId) { return JSON.stringify({ urlMatched: true, sessionMatched: false, droppedLines: 0, lines: [] }); } if (${JSON.stringify(params.finalize)} && Array.isArray(captions?.visible) && captions.visible.length > 0) { if (captions.settleTimer !== undefined) clearTimeout(captions.settleTimer); captions.settleTimer = undefined; captions.lines = Array.isArray(captions.lines) ? captions.lines : []; captions.lines.push(...captions.visible.map((entry) => ({ at: entry.at, speaker: entry.speaker, text: entry.text, }))); captions.visible = []; const excess = captions.lines.length - ${transcriptMaxLines}; if (excess > 0) { captions.lines.splice(0, excess); captions.droppedLines = (captions.droppedLines || 0) + excess; } } if (${JSON.stringify(params.finalize)} && captions) { if (captions.settleTimer !== undefined) clearTimeout(captions.settleTimer); captions.settleTimer = undefined; captions.observer?.disconnect?.(); captions.observer = undefined; captions.observerInstalled = false; captions.identity = expectedIdentity; captions.finalized = true; captions.finalizedAt = Date.now(); } const allLines = [ ...(Array.isArray(captions?.lines) ? captions.lines : []), ...(${JSON.stringify(params.finalize)} || !Array.isArray(captions?.visible) ? [] : captions.visible), ]; const visibleOverflow = Math.max(0, allLines.length - ${transcriptMaxLines}); const lines = allLines.slice(-${transcriptMaxLines}); const result = { urlMatched: true, sessionMatched: true, epoch: typeof captions?.epoch === "string" ? captions.epoch : undefined, droppedLines: (Number.isFinite(captions?.droppedLines) ? Math.max(0, Math.trunc(captions.droppedLines)) : 0) + visibleOverflow, lines: lines.map((line) => ({ at: typeof line?.at === "string" ? line.at : undefined, speaker: typeof line?.speaker === "string" ? line.speaker : undefined, text: typeof line?.text === "string" ? line.text : "", })).filter((line) => line.text), }; return JSON.stringify(result); }`; } function createMeetingOwnedAudioLeaveSource(params) { const audioOutputsGlobal = pageGlobalSource(params.audioOutputsGlobal); return `const retireOwnedAudioBridges = () => { const entries = Array.isArray(${audioOutputsGlobal}) ? ${audioOutputsGlobal} : []; const retained = []; const activeSessionId = expectedSessionId || state?.sessionId; for (const entry of entries) { const ownedByActiveSession = Boolean( !entry?.sessionId || (activeSessionId && entry.sessionId === activeSessionId) ); if (!ownedByActiveSession) { retained.push(entry); continue; } const mediaSourceUrl = (element) => String(element?.currentSrc || element?.src || ""); const sources = Array.isArray(entry?.sources) ? entry.sources : entry?.source ? [{ element: entry.source, muted: Boolean(entry.sourceMuted), stream: entry.stream, url: entry.sourceUrl }] : []; for (const source of sources) { const element = source?.element; const sourceMatches = source?.stream || element?.srcObject ? element?.srcObject === source?.stream : Boolean(source?.url && mediaSourceUrl(element) === source.url); const sourceIsEmpty = Boolean(element && !element.srcObject && !mediaSourceUrl(element)); if (!element) continue; if (sourceIsEmpty) { element.muted = true; continue; } if (!sourceMatches) continue; const detachedLiveSource = Boolean( element.isConnected === false && element.srcObject?.getAudioTracks?.().some((track) => track.readyState === "live") ); if (detachedLiveSource) { element.muted = true; element.pause?.(); element.srcObject = null; } else { element.muted = Boolean(source.muted); } } entry?.bridge?.pause?.(); if (entry?.bridge) entry.bridge.srcObject = null; entry?.bridge?.remove?.(); } if (retained.length > 0) ${audioOutputsGlobal} = retained; else delete ${audioOutputsGlobal}; };`; } function createMeetingLeaveSource(params) { const documentSetupSource = params.documentSetupSource ? `${params.documentSetupSource}\n ` : ""; const meetingStateSource = params.meetingStateSource ? ` ${params.meetingStateSource}\n` : ""; const meetingGlobal = pageGlobalSource(params.platform.globals.meeting); return `() => { ${params.pageIdentitySource} ${documentSetupSource}const selectors = ${params.selectors}; const expectedIdentity = ${JSON.stringify(params.expectedIdentity)}; const expectedSessionId = ${JSON.stringify(params.meetingSessionId)}; const leaveInitiated = ${JSON.stringify(params.leaveInitiated)}; const currentIdentity = meetingIdentity(location.href); const state = ${meetingGlobal}; const enforceSessionOwnership = Boolean(expectedSessionId); if (enforceSessionOwnership && state?.sessionId && state.sessionId !== expectedSessionId) { return JSON.stringify({ departed: false, sessionConflict: true, sessionMatched: false, urlMatched: true }); } ${params.sessionMatchSource} const retainedLeaveOwnership = Boolean(!sessionMatched && leaveInitiated); if (!sessionMatched && !retainedLeaveOwnership) { return JSON.stringify({ departed: false, sessionMatched: false, urlMatched: true }); } ${createMeetingOwnedAudioLeaveSource({ audioOutputsGlobal: params.platform.globals.audioOutputs })} ${params.controlSource} const preservedCallMatches = Boolean( expectedIdentity && !currentIdentity && state?.identity === expectedIdentity && state?.inCallControl === leave && state?.inCallUrl === location.href && leave && leave.isConnected !== false ); const pendingLeaveMatches = Boolean( expectedIdentity && state?.identity === expectedIdentity && state?.leavePending === true && state?.inCallUrl === location.href && Date.now() - state?.leavePendingAt < 10_000 ); const rerenderPendingMatches = Boolean( expectedIdentity && !currentIdentity && state?.identity === expectedIdentity && state?.inCallControl?.isConnected === false && state?.inCallUrl === location.href && Date.now() - state?.verifiedAt < 5_000 && !leave ); const meetingIdentityMatches = Boolean( currentUrlMatches || preservedCallMatches || pendingLeaveMatches || rerenderPendingMatches ); // ${params.platform.displayName} can replace the document between our Leave click and its post-call marker. // Retain request ownership only while no identity or live-call control contradicts it. const initiatedLeaveTransitionMatches = Boolean( leaveInitiated && !currentIdentity && !leave && (!state?.identity || state.identity === expectedIdentity) ); if (${params.departedMarkerSource} && (meetingIdentityMatches || initiatedLeaveTransitionMatches)) { retireOwnedAudioBridges(); if (sessionMatched) delete ${meetingGlobal}; return JSON.stringify({ departed: true, sessionMatched: true, urlMatched: true }); } if (!meetingIdentityMatches && !initiatedLeaveTransitionMatches) { return JSON.stringify({ departed: false, urlMatched: false }); } if (!sessionMatched) { return JSON.stringify({ departed: false, urlMatched: true }); } if (confirmation) { confirmation.click(); return JSON.stringify({ departed: false, leaveAction: "confirm", urlMatched: true }); } if (leave) { ${meetingGlobal} = { ...state, identity: expectedIdentity, ${meetingStateSource} inCallControl: leave, inCallUrl: location.href, leavePending: true, leavePendingAt: Date.now(), }; leave.click(); return JSON.stringify({ departed: false, leaveAction: "leave", urlMatched: true }); } return JSON.stringify({ departed: false, urlMatched: true }); }`; } //#endregion export { createMeetingLeaveSource, createMeetingTranscriptSource };