@ai-matrx/meet 0.7.3 → 0.7.6
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/CHANGELOG.md +74 -0
- package/dist/index.cjs +544 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +219 -24
- package/dist/index.d.ts +219 -24
- package/dist/index.js +544 -35
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1886 -162
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +136 -12
- package/dist/react.d.ts +136 -12
- package/dist/react.js +1887 -163
- package/dist/react.js.map +1 -1
- package/dist/styles.css +530 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -191,7 +191,16 @@ var MEET_ROUTES = {
|
|
|
191
191
|
*/
|
|
192
192
|
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
193
193
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
194
|
-
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar"
|
|
194
|
+
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
195
|
+
/**
|
|
196
|
+
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
197
|
+
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
198
|
+
* Decided by the server from the meeting itself; a guest's pass rides
|
|
199
|
+
* `x-meet-room-token`.
|
|
200
|
+
*/
|
|
201
|
+
moderate: "/api/v1/meet/moderate",
|
|
202
|
+
/** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */
|
|
203
|
+
chat: "/api/v1/meet/chat"
|
|
195
204
|
};
|
|
196
205
|
function meetingPath(template, meetingId) {
|
|
197
206
|
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
@@ -1909,7 +1918,19 @@ var ROOM_EVENTS = {
|
|
|
1909
1918
|
* production 2026-09-09 the guest lane showed no indicator at all while the
|
|
1910
1919
|
* host recorded (MRI-C8).
|
|
1911
1920
|
*/
|
|
1912
|
-
recording: "matrx.meet.recording"
|
|
1921
|
+
recording: "matrx.meet.recording",
|
|
1922
|
+
/**
|
|
1923
|
+
* A chat message to everyone was SAVED (Meet wave 2). Payload
|
|
1924
|
+
* `{ message: { id, meeting_id, sender_identity, sender_name, body, created_at } }`.
|
|
1925
|
+
*/
|
|
1926
|
+
chat: "matrx.meet.chat",
|
|
1927
|
+
/**
|
|
1928
|
+
* A host or co-host acted (Meet wave 2). Payload `{ meeting_id, kind, … }`:
|
|
1929
|
+
* `muted {source, by}` · `ask-unmute {by}` · `removed {by}` — addressed to the
|
|
1930
|
+
* person concerned; `lower-hands {identities: [...] | "*"}` · `role {identity,
|
|
1931
|
+
* role}` — to everyone.
|
|
1932
|
+
*/
|
|
1933
|
+
moderation: "matrx.meet.moderation"
|
|
1913
1934
|
};
|
|
1914
1935
|
function isRoomEventTopic(topic) {
|
|
1915
1936
|
return topic.startsWith(ROOM_EVENT_PREFIX);
|
|
@@ -1945,6 +1966,10 @@ function applyRoomEvent(args) {
|
|
|
1945
1966
|
return applyEnded(store, payload);
|
|
1946
1967
|
case ROOM_EVENTS.recording:
|
|
1947
1968
|
return applyRecording(store, payload);
|
|
1969
|
+
case ROOM_EVENTS.chat:
|
|
1970
|
+
return applyChat(store, payload);
|
|
1971
|
+
case ROOM_EVENTS.moderation:
|
|
1972
|
+
return applyModeration(store, payload, now);
|
|
1948
1973
|
default:
|
|
1949
1974
|
return ignored(`${topic} is not known to this version of @ai-matrx/meet`);
|
|
1950
1975
|
}
|
|
@@ -2036,6 +2061,101 @@ function applyEnded(store, payload) {
|
|
|
2036
2061
|
store.patch({ meeting: { ...meeting, endedAt } });
|
|
2037
2062
|
return HANDLED;
|
|
2038
2063
|
}
|
|
2064
|
+
function chatFromRow(row) {
|
|
2065
|
+
const id = text2(row["id"]);
|
|
2066
|
+
const identity = text2(row["sender_identity"]);
|
|
2067
|
+
const body = text2(row["body"]);
|
|
2068
|
+
if (id === null || identity === null || body === null) return null;
|
|
2069
|
+
return {
|
|
2070
|
+
id,
|
|
2071
|
+
identity: asParticipantIdentity(identity),
|
|
2072
|
+
displayName: text2(row["sender_name"]) ?? "Participant",
|
|
2073
|
+
text: body,
|
|
2074
|
+
at: text2(row["created_at"]) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2075
|
+
// `false` only from a server whose chat table has not landed: the room heard
|
|
2076
|
+
// it, nobody will see it later — and the line says so.
|
|
2077
|
+
saved: row["saved"] !== false
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
function applyChat(store, payload) {
|
|
2081
|
+
const row = object(payload["message"]);
|
|
2082
|
+
if (row === null) return ignored("a chat event with no `message` row");
|
|
2083
|
+
const meeting = store.snapshot().meeting;
|
|
2084
|
+
if (meeting !== null && text2(row["meeting_id"]) !== null && row["meeting_id"] !== meeting.id) {
|
|
2085
|
+
return ignored("a chat message for a different meeting");
|
|
2086
|
+
}
|
|
2087
|
+
const message = chatFromRow(row);
|
|
2088
|
+
if (message === null) return ignored("a chat row with no id, sender or body");
|
|
2089
|
+
store.addChat(message);
|
|
2090
|
+
return HANDLED;
|
|
2091
|
+
}
|
|
2092
|
+
function moderationText(kind, payload) {
|
|
2093
|
+
const by = text2(payload["by"]) ?? "The host";
|
|
2094
|
+
switch (kind) {
|
|
2095
|
+
case "muted": {
|
|
2096
|
+
const source = text2(payload["source"]) ?? "microphone";
|
|
2097
|
+
return source === "microphone" ? `${by} muted you.` : source === "camera" ? `${by} turned off your camera.` : `${by} stopped your screen share.`;
|
|
2098
|
+
}
|
|
2099
|
+
case "ask-unmute":
|
|
2100
|
+
return `${by} is asking you to unmute.`;
|
|
2101
|
+
case "removed":
|
|
2102
|
+
return `${by} removed you from this meeting.`;
|
|
2103
|
+
default:
|
|
2104
|
+
return null;
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
function applyModeration(store, payload, now) {
|
|
2108
|
+
const snapshot = store.snapshot();
|
|
2109
|
+
const meetingId = text2(payload["meeting_id"]);
|
|
2110
|
+
if (snapshot.meeting !== null && meetingId !== null && meetingId !== snapshot.meeting.id) {
|
|
2111
|
+
return ignored("a moderation event for a different meeting");
|
|
2112
|
+
}
|
|
2113
|
+
const kind = text2(payload["kind"]);
|
|
2114
|
+
switch (kind) {
|
|
2115
|
+
case "lower-hands": {
|
|
2116
|
+
const raw = payload["identities"];
|
|
2117
|
+
const everyone = raw === "*";
|
|
2118
|
+
const named = new Set(
|
|
2119
|
+
Array.isArray(raw) ? raw.filter((value) => typeof value === "string") : []
|
|
2120
|
+
);
|
|
2121
|
+
for (const participant of snapshot.participants) {
|
|
2122
|
+
if (participant.handRaised && (everyone || named.has(participant.identity))) {
|
|
2123
|
+
store.setHand(participant.identity, false);
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
return HANDLED;
|
|
2127
|
+
}
|
|
2128
|
+
case "role": {
|
|
2129
|
+
const identity = text2(payload["identity"]);
|
|
2130
|
+
const role = text2(payload["role"]);
|
|
2131
|
+
if (identity === null || role === null || !ROLES.includes(role)) {
|
|
2132
|
+
return ignored("a role event with no identity or an unknown role");
|
|
2133
|
+
}
|
|
2134
|
+
if (identity === snapshot.localIdentity) {
|
|
2135
|
+
store.patch({
|
|
2136
|
+
grants: { ...snapshot.grants, role },
|
|
2137
|
+
moderationNotice: {
|
|
2138
|
+
kind: "role",
|
|
2139
|
+
text: role === "host" ? "You are now the host." : role === "cohost" ? "You are now a co-host." : "You are no longer a co-host.",
|
|
2140
|
+
at: now()
|
|
2141
|
+
}
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
return HANDLED;
|
|
2145
|
+
}
|
|
2146
|
+
case "muted":
|
|
2147
|
+
case "ask-unmute":
|
|
2148
|
+
case "removed": {
|
|
2149
|
+
const sentence = moderationText(kind, payload);
|
|
2150
|
+
if (sentence === null) return ignored("a moderation event with no sentence");
|
|
2151
|
+
store.patch({ moderationNotice: { kind, text: sentence, at: now() } });
|
|
2152
|
+
return HANDLED;
|
|
2153
|
+
}
|
|
2154
|
+
default:
|
|
2155
|
+
return ignored(`a moderation kind this version does not know: ${String(kind)}`);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
var ROLES = ["host", "cohost", "participant", "guest"];
|
|
2039
2159
|
|
|
2040
2160
|
// src/core/devices.ts
|
|
2041
2161
|
var KINDS = ["audioinput", "videoinput", "audiooutput"];
|
|
@@ -2368,7 +2488,8 @@ function computeStage(args) {
|
|
|
2368
2488
|
const ordered = orderParticipants(args.participants, args.activeSpeakers);
|
|
2369
2489
|
const sharer = ordered.find((participant) => participant.screenSharing) ?? null;
|
|
2370
2490
|
const pinnedParticipant = args.pinned === null ? null : ordered.find((participant) => participant.identity === args.pinned) ?? null;
|
|
2371
|
-
const
|
|
2491
|
+
const spotlit = args.spotlight == null ? null : ordered.find((participant) => participant.identity === args.spotlight) ?? null;
|
|
2492
|
+
const feature = sharer !== null ? sharer : pinnedParticipant !== null && args.layout !== "grid" ? pinnedParticipant : spotlit !== null ? spotlit : args.layout === "grid" ? null : ordered[0] ?? null;
|
|
2372
2493
|
const rest = feature === null ? ordered : ordered.filter((participant) => participant.identity !== feature.identity);
|
|
2373
2494
|
const pageCount = Math.max(1, Math.ceil(rest.length / pageSize));
|
|
2374
2495
|
const page = Math.min(Math.max(0, args.page ?? 0), pageCount - 1);
|
|
@@ -2394,8 +2515,7 @@ function gridColumns(count2) {
|
|
|
2394
2515
|
var DATA_TOPICS = {
|
|
2395
2516
|
hand: "mx.hand",
|
|
2396
2517
|
reaction: "mx.reaction",
|
|
2397
|
-
chat: "mx.chat"
|
|
2398
|
-
hostAction: "mx.host"
|
|
2518
|
+
chat: "mx.chat"
|
|
2399
2519
|
};
|
|
2400
2520
|
var TRANSCRIPTION_TOPIC = "lk.transcription";
|
|
2401
2521
|
var TRANSCRIPTION_ATTRIBUTES = {
|
|
@@ -2456,11 +2576,48 @@ function snapshotParticipant(participant, isLocal) {
|
|
|
2456
2576
|
screenSharing: participant.isScreenShareEnabled
|
|
2457
2577
|
};
|
|
2458
2578
|
}
|
|
2579
|
+
var DEFAULT_AUDIO_PROCESSING = {
|
|
2580
|
+
noiseSuppression: true,
|
|
2581
|
+
echoCancellation: true,
|
|
2582
|
+
autoGainControl: true
|
|
2583
|
+
};
|
|
2584
|
+
function videoEffectsSupported() {
|
|
2585
|
+
const scope = globalThis;
|
|
2586
|
+
if (typeof scope.OffscreenCanvas === "undefined" || scope.document === void 0) return false;
|
|
2587
|
+
try {
|
|
2588
|
+
return scope.document.createElement("canvas").getContext("webgl2") !== null;
|
|
2589
|
+
} catch {
|
|
2590
|
+
return false;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
async function applyVideoEffect(target, effect) {
|
|
2594
|
+
const { Track } = await import("livekit-client");
|
|
2595
|
+
const track = target.localParticipant.getTrackPublication(Track.Source.Camera)?.track;
|
|
2596
|
+
if (track === void 0) return;
|
|
2597
|
+
try {
|
|
2598
|
+
if (effect.kind === "none") {
|
|
2599
|
+
await track.stopProcessor();
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
const processors = await import("@livekit/track-processors");
|
|
2603
|
+
const processor = effect.kind === "blur" ? processors.BackgroundBlur(effect.radius ?? 12) : processors.VirtualBackground(effect.imageUrl);
|
|
2604
|
+
await track.setProcessor(processor);
|
|
2605
|
+
} catch (error) {
|
|
2606
|
+
throw new MeetError(
|
|
2607
|
+
"media-unsupported",
|
|
2608
|
+
"The background effect could not start on this camera",
|
|
2609
|
+
"Background effects need a browser with WebGL2 (current Chrome, Edge, Firefox or Safari). Your video continues without the effect.",
|
|
2610
|
+
error
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2459
2614
|
var encoder = new TextEncoder();
|
|
2460
2615
|
var decoder = new TextDecoder();
|
|
2461
2616
|
function createLiveKitDriver(handlers, options = {}) {
|
|
2462
2617
|
let room = null;
|
|
2463
2618
|
let projectRoster = null;
|
|
2619
|
+
let audioCapture = DEFAULT_AUDIO_PROCESSING;
|
|
2620
|
+
let videoEffect = { kind: "none" };
|
|
2464
2621
|
async function load() {
|
|
2465
2622
|
if (room !== null) return room;
|
|
2466
2623
|
let module;
|
|
@@ -2489,7 +2646,9 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
2489
2646
|
handlers.participants([local, ...remotes]);
|
|
2490
2647
|
};
|
|
2491
2648
|
projectRoster = roster;
|
|
2492
|
-
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, roster).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.
|
|
2649
|
+
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, roster).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
2650
|
+
handlers.roomMetadata?.(metadata ?? "");
|
|
2651
|
+
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
2493
2652
|
handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
|
|
2494
2653
|
}).on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
|
|
2495
2654
|
handlers.quality(participant.identity, mapQuality(quality));
|
|
@@ -2566,6 +2725,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
2566
2725
|
throw normalizeDeviceError(error, "meet.connect");
|
|
2567
2726
|
}
|
|
2568
2727
|
projectRoster?.();
|
|
2728
|
+
handlers.roomMetadata?.(target.metadata ?? "");
|
|
2569
2729
|
},
|
|
2570
2730
|
async disconnect() {
|
|
2571
2731
|
if (room === null) return;
|
|
@@ -2580,14 +2740,19 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
2580
2740
|
},
|
|
2581
2741
|
async setMicrophoneEnabled(enabled) {
|
|
2582
2742
|
try {
|
|
2583
|
-
await requireRoom("setMicrophoneEnabled").localParticipant.setMicrophoneEnabled(
|
|
2743
|
+
await requireRoom("setMicrophoneEnabled").localParticipant.setMicrophoneEnabled(
|
|
2744
|
+
enabled,
|
|
2745
|
+
audioCapture
|
|
2746
|
+
);
|
|
2584
2747
|
} catch (error) {
|
|
2585
2748
|
throw normalizeDeviceError(error, "meet.setMicrophoneEnabled");
|
|
2586
2749
|
}
|
|
2587
2750
|
},
|
|
2588
2751
|
async setCameraEnabled(enabled) {
|
|
2589
2752
|
try {
|
|
2590
|
-
|
|
2753
|
+
const target = requireRoom("setCameraEnabled");
|
|
2754
|
+
await target.localParticipant.setCameraEnabled(enabled);
|
|
2755
|
+
if (enabled && videoEffect.kind !== "none") await applyVideoEffect(target, videoEffect);
|
|
2591
2756
|
} catch (error) {
|
|
2592
2757
|
throw normalizeDeviceError(error, "meet.setCameraEnabled");
|
|
2593
2758
|
}
|
|
@@ -2599,14 +2764,40 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
2599
2764
|
throw normalizeDeviceError(error, "meet.setScreenShareEnabled");
|
|
2600
2765
|
}
|
|
2601
2766
|
},
|
|
2602
|
-
async publishData(topic, payload) {
|
|
2767
|
+
async publishData(topic, payload, to) {
|
|
2603
2768
|
await requireRoom("publishData").localParticipant.publishData(
|
|
2604
2769
|
// `reliable` is not optional for a control plane: a dropped
|
|
2605
2770
|
// "hand lowered" leaves a hand up on every other screen forever.
|
|
2606
2771
|
encoder.encode(payload),
|
|
2607
|
-
{ reliable: true, topic }
|
|
2772
|
+
to === void 0 || to.length === 0 ? { reliable: true, topic } : { reliable: true, topic, destinationIdentities: [...to] }
|
|
2608
2773
|
);
|
|
2609
2774
|
},
|
|
2775
|
+
async setAudioProcessing(processing) {
|
|
2776
|
+
audioCapture = { ...audioCapture, ...processing };
|
|
2777
|
+
const target = room;
|
|
2778
|
+
if (target === null) return;
|
|
2779
|
+
const { Track } = await import("livekit-client");
|
|
2780
|
+
const publication = target.localParticipant.getTrackPublication(Track.Source.Microphone);
|
|
2781
|
+
const track = publication?.track;
|
|
2782
|
+
if (track === void 0 || publication?.isMuted === true) return;
|
|
2783
|
+
try {
|
|
2784
|
+
await track.restartTrack({
|
|
2785
|
+
...audioCapture,
|
|
2786
|
+
...target.getActiveDevice("audioinput") !== void 0 ? { deviceId: target.getActiveDevice("audioinput") } : {}
|
|
2787
|
+
});
|
|
2788
|
+
} catch (error) {
|
|
2789
|
+
throw normalizeDeviceError(error, "meet.setAudioProcessing");
|
|
2790
|
+
}
|
|
2791
|
+
},
|
|
2792
|
+
async setVideoEffect(effect) {
|
|
2793
|
+
videoEffect = effect;
|
|
2794
|
+
const target = room;
|
|
2795
|
+
if (target === null) return;
|
|
2796
|
+
await applyVideoEffect(target, effect);
|
|
2797
|
+
},
|
|
2798
|
+
supportsVideoEffects() {
|
|
2799
|
+
return videoEffectsSupported();
|
|
2800
|
+
},
|
|
2610
2801
|
attach(identity, kind, element) {
|
|
2611
2802
|
const target = requireRoom("attach");
|
|
2612
2803
|
const source = sources?.[kind];
|
|
@@ -2829,6 +3020,11 @@ function listenOnlyNotice(media) {
|
|
|
2829
3020
|
}
|
|
2830
3021
|
|
|
2831
3022
|
// src/core/store.ts
|
|
3023
|
+
var DEFAULT_EFFECTS = {
|
|
3024
|
+
noiseSuppression: true,
|
|
3025
|
+
background: "none",
|
|
3026
|
+
backgroundImage: null
|
|
3027
|
+
};
|
|
2832
3028
|
var NO_GRANTS = {
|
|
2833
3029
|
role: "participant",
|
|
2834
3030
|
canRecord: false,
|
|
@@ -2851,7 +3047,13 @@ var EMPTY = {
|
|
|
2851
3047
|
recordingSource: "local",
|
|
2852
3048
|
recordingId: null,
|
|
2853
3049
|
chat: [],
|
|
3050
|
+
chatLoaded: false,
|
|
3051
|
+
chatHistoryError: null,
|
|
2854
3052
|
reactions: [],
|
|
3053
|
+
spotlight: null,
|
|
3054
|
+
moderationNotice: null,
|
|
3055
|
+
actionNotice: null,
|
|
3056
|
+
effects: DEFAULT_EFFECTS,
|
|
2855
3057
|
transcript: [],
|
|
2856
3058
|
notes: [],
|
|
2857
3059
|
names: {},
|
|
@@ -2890,6 +3092,7 @@ function createMeetStore(initial = {}) {
|
|
|
2890
3092
|
setParticipants(participants) {
|
|
2891
3093
|
const names = learnNames(state.names, participants);
|
|
2892
3094
|
const hands = new Map(state.participants.map((p) => [p.identity, p.handRaised]));
|
|
3095
|
+
const handTimes = new Map(state.participants.map((p) => [p.identity, p.handRaisedAt ?? null]));
|
|
2893
3096
|
const quality = new Map(state.participants.map((p) => [p.identity, p.quality]));
|
|
2894
3097
|
commit({
|
|
2895
3098
|
...state,
|
|
@@ -2903,6 +3106,7 @@ function createMeetStore(initial = {}) {
|
|
|
2903
3106
|
// name book. `roster-names.test.ts` fails if a token ever gets past it.
|
|
2904
3107
|
displayName: displayNameFor(names, participant.identity, participant.displayName),
|
|
2905
3108
|
handRaised: hands.get(participant.identity) ?? participant.handRaised,
|
|
3109
|
+
handRaisedAt: handTimes.get(participant.identity) ?? participant.handRaisedAt ?? null,
|
|
2906
3110
|
quality: participant.quality === "unknown" ? quality.get(participant.identity) ?? "unknown" : participant.quality
|
|
2907
3111
|
}))
|
|
2908
3112
|
});
|
|
@@ -2925,11 +3129,17 @@ function createMeetStore(initial = {}) {
|
|
|
2925
3129
|
)
|
|
2926
3130
|
});
|
|
2927
3131
|
},
|
|
2928
|
-
setHand(identity, raised) {
|
|
3132
|
+
setHand(identity, raised, at) {
|
|
2929
3133
|
commit({
|
|
2930
3134
|
...state,
|
|
2931
3135
|
participants: state.participants.map(
|
|
2932
|
-
(participant) => participant.identity === identity ? {
|
|
3136
|
+
(participant) => participant.identity === identity ? {
|
|
3137
|
+
...participant,
|
|
3138
|
+
handRaised: raised,
|
|
3139
|
+
// A hand that is ALREADY up keeps its place in the queue when
|
|
3140
|
+
// it is re-announced (to a late joiner): the first time wins.
|
|
3141
|
+
handRaisedAt: raised ? participant.handRaised && participant.handRaisedAt != null ? participant.handRaisedAt : at ?? Date.now() : null
|
|
3142
|
+
} : participant
|
|
2933
3143
|
)
|
|
2934
3144
|
});
|
|
2935
3145
|
},
|
|
@@ -2939,6 +3149,31 @@ function createMeetStore(initial = {}) {
|
|
|
2939
3149
|
const names = learnName(state.names, message.identity, message.displayName);
|
|
2940
3150
|
commit({ ...state, chat: chat.slice(-CHAT_LIMIT), names });
|
|
2941
3151
|
},
|
|
3152
|
+
loadChat(messages) {
|
|
3153
|
+
const known = new Set(state.chat.map((message) => message.id));
|
|
3154
|
+
const merged = [...messages.filter((message) => !known.has(message.id)), ...state.chat].sort(
|
|
3155
|
+
(a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : 0
|
|
3156
|
+
);
|
|
3157
|
+
const names = learnNames(
|
|
3158
|
+
state.names,
|
|
3159
|
+
messages.map((message) => ({ identity: message.identity, displayName: message.displayName }))
|
|
3160
|
+
);
|
|
3161
|
+
commit({ ...state, chat: merged.slice(-CHAT_LIMIT), chatLoaded: true, chatHistoryError: null, names });
|
|
3162
|
+
},
|
|
3163
|
+
settleChat(localId, next) {
|
|
3164
|
+
const index = state.chat.findIndex((message) => message.id === localId);
|
|
3165
|
+
if (index === -1) return;
|
|
3166
|
+
const current = state.chat[index];
|
|
3167
|
+
let replacement;
|
|
3168
|
+
if ("failed" in next && typeof next.failed === "string" && !("id" in next)) {
|
|
3169
|
+
replacement = { ...current, failed: next.failed };
|
|
3170
|
+
} else {
|
|
3171
|
+
replacement = next;
|
|
3172
|
+
}
|
|
3173
|
+
const duplicate = replacement.id !== localId && state.chat.some((message) => message.id === replacement.id);
|
|
3174
|
+
const chat = duplicate ? state.chat.filter((message) => message.id !== localId) : state.chat.map((message, position) => position === index ? replacement : message);
|
|
3175
|
+
commit({ ...state, chat });
|
|
3176
|
+
},
|
|
2942
3177
|
addReaction(reaction) {
|
|
2943
3178
|
commit({ ...state, reactions: [...state.reactions, reaction] });
|
|
2944
3179
|
},
|
|
@@ -3029,7 +3264,7 @@ function createMeetStore(initial = {}) {
|
|
|
3029
3264
|
commit({ ...state, media: { ...state.media, [kind]: next } });
|
|
3030
3265
|
},
|
|
3031
3266
|
reset() {
|
|
3032
|
-
commit({ ...EMPTY });
|
|
3267
|
+
commit({ ...EMPTY, effects: state.effects });
|
|
3033
3268
|
}
|
|
3034
3269
|
};
|
|
3035
3270
|
}
|
|
@@ -3661,7 +3896,37 @@ function createRoomEngine(options) {
|
|
|
3661
3896
|
const handlers = {
|
|
3662
3897
|
participants(list) {
|
|
3663
3898
|
const joinedAt = new Date(now()).toISOString();
|
|
3899
|
+
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
3664
3900
|
store.setParticipants(list.map((entry) => projectParticipant(entry, joinedAt)));
|
|
3901
|
+
const snapshot = store.snapshot();
|
|
3902
|
+
const newcomer = snapshot.participants.some(
|
|
3903
|
+
(participant) => !participant.isLocal && !before.has(participant.identity)
|
|
3904
|
+
);
|
|
3905
|
+
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
3906
|
+
if (newcomer && before.size > 0 && me?.handRaised === true) {
|
|
3907
|
+
void publish(DATA_TOPICS.hand, { raised: true, raisedAt: me.handRaisedAt ?? now() }).catch(
|
|
3908
|
+
() => void 0
|
|
3909
|
+
);
|
|
3910
|
+
}
|
|
3911
|
+
},
|
|
3912
|
+
roomMetadata(metadata) {
|
|
3913
|
+
let spotlight = null;
|
|
3914
|
+
if (metadata.length > 0) {
|
|
3915
|
+
try {
|
|
3916
|
+
const parsed = JSON.parse(metadata);
|
|
3917
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
3918
|
+
const value = parsed.spotlight;
|
|
3919
|
+
if (typeof value === "string" && value.length > 0) spotlight = asParticipantIdentity(value);
|
|
3920
|
+
}
|
|
3921
|
+
} catch {
|
|
3922
|
+
report(
|
|
3923
|
+
"warn",
|
|
3924
|
+
"meet: the room's metadata is not JSON, so no spotlight was read from it",
|
|
3925
|
+
"Only aidream writes room metadata; check services/meet/moderation.py."
|
|
3926
|
+
);
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
if (store.snapshot().spotlight !== spotlight) store.patch({ spotlight });
|
|
3665
3930
|
},
|
|
3666
3931
|
activeSpeakers(identities) {
|
|
3667
3932
|
store.setSpeaking(identities.map(asParticipantIdentity));
|
|
@@ -3765,7 +4030,11 @@ function createRoomEngine(options) {
|
|
|
3765
4030
|
const sender = asParticipantIdentity(from ?? "");
|
|
3766
4031
|
switch (topic) {
|
|
3767
4032
|
case DATA_TOPICS.hand:
|
|
3768
|
-
store.setHand(
|
|
4033
|
+
store.setHand(
|
|
4034
|
+
sender,
|
|
4035
|
+
envelope.raised === true,
|
|
4036
|
+
typeof envelope.raisedAt === "number" ? envelope.raisedAt : now()
|
|
4037
|
+
);
|
|
3769
4038
|
return;
|
|
3770
4039
|
case DATA_TOPICS.reaction:
|
|
3771
4040
|
if (typeof envelope.emoji === "string") {
|
|
@@ -3779,12 +4048,14 @@ function createRoomEngine(options) {
|
|
|
3779
4048
|
return;
|
|
3780
4049
|
case DATA_TOPICS.chat:
|
|
3781
4050
|
if (typeof envelope.text === "string") {
|
|
4051
|
+
const local = store.snapshot().localIdentity;
|
|
3782
4052
|
store.addChat({
|
|
3783
4053
|
id: envelope.id ?? `${sender}-${now()}`,
|
|
3784
4054
|
identity: sender,
|
|
3785
4055
|
displayName: envelope.name ?? sender,
|
|
3786
4056
|
text: envelope.text,
|
|
3787
|
-
at: envelope.at ?? new Date(now()).toISOString()
|
|
4057
|
+
at: envelope.at ?? new Date(now()).toISOString(),
|
|
4058
|
+
...typeof envelope.to === "string" && local !== null ? { to: local } : {}
|
|
3788
4059
|
});
|
|
3789
4060
|
}
|
|
3790
4061
|
return;
|
|
@@ -3830,6 +4101,24 @@ function createRoomEngine(options) {
|
|
|
3830
4101
|
store.patch({ phase: "connected", failure: null });
|
|
3831
4102
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
3832
4103
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
4104
|
+
const effects = options.effectsStorage?.load() ?? store.snapshot().effects;
|
|
4105
|
+
store.patch({ effects });
|
|
4106
|
+
if (!effects.noiseSuppression || effects.background !== "none") {
|
|
4107
|
+
try {
|
|
4108
|
+
await applyEffects(created, effects);
|
|
4109
|
+
} catch {
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
for (const kind of ["audioinput", "videoinput", "audiooutput"]) {
|
|
4113
|
+
const chosen = options.resolveDevice?.(kind) ?? null;
|
|
4114
|
+
if (chosen === null || chosen === "default") continue;
|
|
4115
|
+
try {
|
|
4116
|
+
await created.switchDevice(kind, chosen);
|
|
4117
|
+
} catch (error) {
|
|
4118
|
+
const normalized = normalizeDeviceError(error, `meet.switchDevice(${kind})`);
|
|
4119
|
+
report("warn", normalized.message, normalized.remedy);
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
3833
4122
|
if (request.mode === "video") {
|
|
3834
4123
|
await enableTrack(created, "camera", true);
|
|
3835
4124
|
}
|
|
@@ -3852,9 +4141,30 @@ function createRoomEngine(options) {
|
|
|
3852
4141
|
return state;
|
|
3853
4142
|
}
|
|
3854
4143
|
}
|
|
3855
|
-
async function publish(topic, body) {
|
|
4144
|
+
async function publish(topic, body, to) {
|
|
3856
4145
|
if (driver === null) return;
|
|
3857
|
-
await driver.publishData(topic, JSON.stringify(body));
|
|
4146
|
+
await driver.publishData(topic, JSON.stringify(body), to);
|
|
4147
|
+
}
|
|
4148
|
+
async function applyEffects(target, effects) {
|
|
4149
|
+
try {
|
|
4150
|
+
await target.setAudioProcessing?.({
|
|
4151
|
+
noiseSuppression: effects.noiseSuppression,
|
|
4152
|
+
echoCancellation: true,
|
|
4153
|
+
autoGainControl: true
|
|
4154
|
+
});
|
|
4155
|
+
} catch (error) {
|
|
4156
|
+
report("warn", `meet: noise suppression could not be applied: ${String(error)}`, "The microphone keeps working without it.");
|
|
4157
|
+
}
|
|
4158
|
+
if (target.setVideoEffect === void 0) return;
|
|
4159
|
+
const effect = effects.background === "blur" ? { kind: "blur" } : effects.background === "image" && effects.backgroundImage !== null ? { kind: "image", imageUrl: effects.backgroundImage } : { kind: "none" };
|
|
4160
|
+
if (effect.kind !== "none" && target.supportsVideoEffects?.() === false) return;
|
|
4161
|
+
try {
|
|
4162
|
+
await target.setVideoEffect(effect);
|
|
4163
|
+
} catch (error) {
|
|
4164
|
+
const normalized = normalizeMeetError(error, "meet.setVideoEffect");
|
|
4165
|
+
report("warn", normalized.message, normalized.remedy);
|
|
4166
|
+
throw normalized;
|
|
4167
|
+
}
|
|
3858
4168
|
}
|
|
3859
4169
|
return {
|
|
3860
4170
|
join(request) {
|
|
@@ -3910,7 +4220,7 @@ function createRoomEngine(options) {
|
|
|
3910
4220
|
async raiseHand(raised) {
|
|
3911
4221
|
const local = store.snapshot().localIdentity;
|
|
3912
4222
|
if (local !== null) store.setHand(local, raised);
|
|
3913
|
-
await publish(DATA_TOPICS.hand, { raised });
|
|
4223
|
+
await publish(DATA_TOPICS.hand, { raised, raisedAt: now() });
|
|
3914
4224
|
},
|
|
3915
4225
|
async react(emoji) {
|
|
3916
4226
|
const local = store.snapshot().localIdentity;
|
|
@@ -3920,32 +4230,41 @@ function createRoomEngine(options) {
|
|
|
3920
4230
|
}
|
|
3921
4231
|
await publish(DATA_TOPICS.reaction, { id, emoji });
|
|
3922
4232
|
},
|
|
3923
|
-
async
|
|
4233
|
+
async sendPrivateChat(to, text3) {
|
|
3924
4234
|
const trimmed = text3.trim();
|
|
3925
4235
|
if (trimmed.length === 0) return;
|
|
3926
4236
|
const local = store.snapshot().localIdentity;
|
|
3927
4237
|
const id = `${local ?? "local"}-${now()}`;
|
|
3928
4238
|
const at = new Date(now()).toISOString();
|
|
3929
4239
|
if (local !== null) {
|
|
3930
|
-
store.addChat({
|
|
3931
|
-
id,
|
|
3932
|
-
identity: local,
|
|
3933
|
-
displayName: identity.displayName,
|
|
3934
|
-
text: trimmed,
|
|
3935
|
-
at
|
|
3936
|
-
});
|
|
4240
|
+
store.addChat({ id, identity: local, displayName: identity.displayName, text: trimmed, at, to });
|
|
3937
4241
|
}
|
|
3938
|
-
await publish(
|
|
4242
|
+
await publish(
|
|
4243
|
+
DATA_TOPICS.chat,
|
|
4244
|
+
{ id, text: trimmed, at, name: identity.displayName, to },
|
|
4245
|
+
[to]
|
|
4246
|
+
);
|
|
3939
4247
|
},
|
|
3940
|
-
async
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
4248
|
+
async setEffects(next) {
|
|
4249
|
+
const previous = store.snapshot().effects;
|
|
4250
|
+
const merged = { ...previous, ...next };
|
|
4251
|
+
store.patch({ effects: merged });
|
|
4252
|
+
options.effectsStorage?.save(merged);
|
|
4253
|
+
if (driver === null) return;
|
|
4254
|
+
try {
|
|
4255
|
+
await applyEffects(driver, merged);
|
|
4256
|
+
} catch {
|
|
4257
|
+
const reverted = { ...merged, background: previous.background, backgroundImage: previous.backgroundImage };
|
|
4258
|
+
store.patch({ effects: reverted });
|
|
4259
|
+
options.effectsStorage?.save(reverted);
|
|
3947
4260
|
}
|
|
3948
|
-
|
|
4261
|
+
},
|
|
4262
|
+
supportsVideoEffects() {
|
|
4263
|
+
return driver?.supportsVideoEffects?.() ?? false;
|
|
4264
|
+
},
|
|
4265
|
+
async switchDevice(kind, deviceId) {
|
|
4266
|
+
if (driver === null) return;
|
|
4267
|
+
await driver.switchDevice(kind, deviceId);
|
|
3949
4268
|
},
|
|
3950
4269
|
pin(who) {
|
|
3951
4270
|
store.patch({ pinned: who, layout: who === null ? "grid" : "speaker" });
|
|
@@ -3976,6 +4295,192 @@ function hostUserIds(participants) {
|
|
|
3976
4295
|
return participants.filter((participant) => participant.role === "host" && participant.userId !== null).map((participant) => participant.userId);
|
|
3977
4296
|
}
|
|
3978
4297
|
|
|
4298
|
+
// src/core/effects.ts
|
|
4299
|
+
function coerce(value) {
|
|
4300
|
+
if (typeof value !== "object" || value === null) return DEFAULT_EFFECTS;
|
|
4301
|
+
const row = value;
|
|
4302
|
+
const background = row["background"] === "blur" || row["background"] === "image" ? row["background"] : "none";
|
|
4303
|
+
const backgroundImage = typeof row["backgroundImage"] === "string" ? row["backgroundImage"] : null;
|
|
4304
|
+
return {
|
|
4305
|
+
noiseSuppression: row["noiseSuppression"] !== false,
|
|
4306
|
+
background: background === "image" && backgroundImage === null ? "none" : background,
|
|
4307
|
+
backgroundImage
|
|
4308
|
+
};
|
|
4309
|
+
}
|
|
4310
|
+
function createWebEffectsStorage(key = "mx.meet.effects") {
|
|
4311
|
+
let memory = DEFAULT_EFFECTS;
|
|
4312
|
+
const storage = globalThis.localStorage;
|
|
4313
|
+
return {
|
|
4314
|
+
load() {
|
|
4315
|
+
if (storage === void 0) return memory;
|
|
4316
|
+
try {
|
|
4317
|
+
const raw = storage.getItem(key);
|
|
4318
|
+
return raw === null ? memory : coerce(JSON.parse(raw));
|
|
4319
|
+
} catch {
|
|
4320
|
+
return memory;
|
|
4321
|
+
}
|
|
4322
|
+
},
|
|
4323
|
+
save(effects) {
|
|
4324
|
+
memory = effects;
|
|
4325
|
+
try {
|
|
4326
|
+
storage?.setItem(key, JSON.stringify(effects));
|
|
4327
|
+
} catch {
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
};
|
|
4331
|
+
}
|
|
4332
|
+
|
|
4333
|
+
// src/core/in-room.ts
|
|
4334
|
+
var MODERATION_ACTIONS = [
|
|
4335
|
+
"mute",
|
|
4336
|
+
"stop_video",
|
|
4337
|
+
"stop_screen",
|
|
4338
|
+
"mute_all",
|
|
4339
|
+
"ask_unmute",
|
|
4340
|
+
"ask_all_unmute",
|
|
4341
|
+
"spotlight",
|
|
4342
|
+
"lower_hand",
|
|
4343
|
+
"lower_all_hands",
|
|
4344
|
+
"make_cohost",
|
|
4345
|
+
"revoke_cohost",
|
|
4346
|
+
"transfer_host",
|
|
4347
|
+
"rename",
|
|
4348
|
+
"remove",
|
|
4349
|
+
"report"
|
|
4350
|
+
];
|
|
4351
|
+
function createInRoomClient(options) {
|
|
4352
|
+
const { api, store } = options;
|
|
4353
|
+
const now = options.now ?? (() => Date.now());
|
|
4354
|
+
function meetingId(operation) {
|
|
4355
|
+
const meeting = store.snapshot().meeting;
|
|
4356
|
+
if (meeting === null) {
|
|
4357
|
+
throw new MeetError(
|
|
4358
|
+
"not-found",
|
|
4359
|
+
`${operation}: this room has no meeting record`,
|
|
4360
|
+
"Moderation and saved chat belong to a meeting. An ad-hoc call has neither."
|
|
4361
|
+
);
|
|
4362
|
+
}
|
|
4363
|
+
return meeting.id;
|
|
4364
|
+
}
|
|
4365
|
+
function headers() {
|
|
4366
|
+
const token = options.resolveRoomToken();
|
|
4367
|
+
return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
|
|
4368
|
+
}
|
|
4369
|
+
async function refusal2(response, operation, remedy) {
|
|
4370
|
+
let body = null;
|
|
4371
|
+
try {
|
|
4372
|
+
body = await response.json();
|
|
4373
|
+
} catch {
|
|
4374
|
+
body = null;
|
|
4375
|
+
}
|
|
4376
|
+
const said = readServerRefusal(body);
|
|
4377
|
+
return new MeetError(
|
|
4378
|
+
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
4379
|
+
said?.reason ?? `${operation}: the server answered ${response.status}`,
|
|
4380
|
+
said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
|
|
4381
|
+
);
|
|
4382
|
+
}
|
|
4383
|
+
return {
|
|
4384
|
+
async moderate(request) {
|
|
4385
|
+
const operation = `meet.moderate(${request.action})`;
|
|
4386
|
+
const hdrs = headers();
|
|
4387
|
+
const response = await api.request({
|
|
4388
|
+
path: MEET_ROUTES.moderate,
|
|
4389
|
+
method: "POST",
|
|
4390
|
+
operation,
|
|
4391
|
+
body: {
|
|
4392
|
+
meeting_id: meetingId(operation),
|
|
4393
|
+
action: request.action,
|
|
4394
|
+
...request.identity != null ? { identity: request.identity } : {},
|
|
4395
|
+
...request.name !== void 0 ? { name: request.name } : {},
|
|
4396
|
+
...request.reason !== void 0 ? { reason: request.reason } : {}
|
|
4397
|
+
},
|
|
4398
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4399
|
+
});
|
|
4400
|
+
if (!response.ok) {
|
|
4401
|
+
throw await refusal2(
|
|
4402
|
+
response,
|
|
4403
|
+
operation,
|
|
4404
|
+
"Nothing was changed. The server decides host powers from the meeting itself."
|
|
4405
|
+
);
|
|
4406
|
+
}
|
|
4407
|
+
const payload = await response.json();
|
|
4408
|
+
return {
|
|
4409
|
+
action: request.action,
|
|
4410
|
+
affected: Array.isArray(payload["affected"]) ? payload["affected"].filter((value) => typeof value === "string") : [],
|
|
4411
|
+
message: typeof payload["message"] === "string" ? payload["message"] : "Done."
|
|
4412
|
+
};
|
|
4413
|
+
},
|
|
4414
|
+
async sendChat(text3) {
|
|
4415
|
+
const body = text3.trim();
|
|
4416
|
+
if (body.length === 0) return;
|
|
4417
|
+
const operation = "meet.sendChat";
|
|
4418
|
+
const id = meetingId(operation);
|
|
4419
|
+
const snapshot = store.snapshot();
|
|
4420
|
+
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
4421
|
+
const localId = `pending-${now()}`;
|
|
4422
|
+
const pending = {
|
|
4423
|
+
id: localId,
|
|
4424
|
+
identity: snapshot.localIdentity ?? "local",
|
|
4425
|
+
displayName: me?.displayName ?? "You",
|
|
4426
|
+
text: body,
|
|
4427
|
+
at: new Date(now()).toISOString(),
|
|
4428
|
+
saved: false
|
|
4429
|
+
};
|
|
4430
|
+
store.addChat(pending);
|
|
4431
|
+
const hdrs = headers();
|
|
4432
|
+
let response;
|
|
4433
|
+
try {
|
|
4434
|
+
response = await api.request({
|
|
4435
|
+
path: MEET_ROUTES.chat,
|
|
4436
|
+
method: "POST",
|
|
4437
|
+
operation,
|
|
4438
|
+
body: { meeting_id: id, body },
|
|
4439
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4440
|
+
});
|
|
4441
|
+
} catch (error) {
|
|
4442
|
+
store.settleChat(localId, { failed: "Not sent \u2014 the server could not be reached." });
|
|
4443
|
+
throw error;
|
|
4444
|
+
}
|
|
4445
|
+
if (!response.ok) {
|
|
4446
|
+
const error = await refusal2(response, operation, "Your message was not sent.");
|
|
4447
|
+
store.settleChat(localId, { failed: error.message });
|
|
4448
|
+
throw error;
|
|
4449
|
+
}
|
|
4450
|
+
const saved = chatFromRow(await response.json());
|
|
4451
|
+
if (saved === null) {
|
|
4452
|
+
store.settleChat(localId, { failed: "The server's answer could not be read." });
|
|
4453
|
+
return;
|
|
4454
|
+
}
|
|
4455
|
+
store.settleChat(localId, saved);
|
|
4456
|
+
},
|
|
4457
|
+
async loadChat() {
|
|
4458
|
+
const operation = "meet.loadChat";
|
|
4459
|
+
const id = meetingId(operation);
|
|
4460
|
+
const hdrs = headers();
|
|
4461
|
+
const response = await api.request({
|
|
4462
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(id)}`,
|
|
4463
|
+
method: "GET",
|
|
4464
|
+
operation,
|
|
4465
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4466
|
+
});
|
|
4467
|
+
if (!response.ok) {
|
|
4468
|
+
throw await refusal2(
|
|
4469
|
+
response,
|
|
4470
|
+
operation,
|
|
4471
|
+
"Messages sent before you joined could not be read. New messages still arrive."
|
|
4472
|
+
);
|
|
4473
|
+
}
|
|
4474
|
+
const payload = await response.json();
|
|
4475
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
4476
|
+
const messages = rows.map(
|
|
4477
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
4478
|
+
).filter((message) => message !== null);
|
|
4479
|
+
store.loadChat(messages);
|
|
4480
|
+
}
|
|
4481
|
+
};
|
|
4482
|
+
}
|
|
4483
|
+
|
|
3979
4484
|
// src/core/record.ts
|
|
3980
4485
|
import { formatDurationMs } from "@ai-matrx/kit/format";
|
|
3981
4486
|
function activeWrapUpSummary(notes) {
|
|
@@ -4312,6 +4817,7 @@ export {
|
|
|
4312
4817
|
CALL_INVITE_VERSION,
|
|
4313
4818
|
CONSENT_NOTICE_ATTRIBUTE,
|
|
4314
4819
|
DATA_TOPICS,
|
|
4820
|
+
DEFAULT_EFFECTS,
|
|
4315
4821
|
DEFAULT_PAGE_SIZE,
|
|
4316
4822
|
END_MEETING_NOT_SERVED,
|
|
4317
4823
|
FILE_SESSION_PATH,
|
|
@@ -4329,6 +4835,7 @@ export {
|
|
|
4329
4835
|
MEET_EVENTS,
|
|
4330
4836
|
MEET_ROUTES,
|
|
4331
4837
|
MEET_SCHEMA,
|
|
4838
|
+
MODERATION_ACTIONS,
|
|
4332
4839
|
MeetError,
|
|
4333
4840
|
NOTE_TAKER_IDENTITY_PREFIX,
|
|
4334
4841
|
NOTE_TAKER_METADATA_KEY,
|
|
@@ -4368,6 +4875,7 @@ export {
|
|
|
4368
4875
|
createCallCenter,
|
|
4369
4876
|
createCallInviteHandler,
|
|
4370
4877
|
createDeviceManager,
|
|
4878
|
+
createInRoomClient,
|
|
4371
4879
|
createLiveKitDriver,
|
|
4372
4880
|
createMeetAi,
|
|
4373
4881
|
createMeetApi,
|
|
@@ -4380,6 +4888,7 @@ export {
|
|
|
4380
4888
|
createRoomEngine,
|
|
4381
4889
|
createTokenClient,
|
|
4382
4890
|
createWebDeviceStorage,
|
|
4891
|
+
createWebEffectsStorage,
|
|
4383
4892
|
createWebRoomTokenStorage,
|
|
4384
4893
|
displayNameFor,
|
|
4385
4894
|
fileDownloadPath,
|