@ai-matrx/meet 0.7.6 → 0.7.8
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 +65 -0
- package/dist/index.cjs +671 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +654 -319
- package/dist/index.d.ts +654 -319
- package/dist/index.js +671 -44
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +692 -69
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +691 -68
- package/dist/react.js.map +1 -1
- package/dist/styles.css +18 -0
- package/package.json +4 -4
package/dist/react.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
32
32
|
// src/react.ts
|
|
33
33
|
var react_exports = {};
|
|
34
34
|
__export(react_exports, {
|
|
35
|
+
ATTENDANCE_CSV_HEADER: () => ATTENDANCE_CSV_HEADER,
|
|
35
36
|
AiPanel: () => AiPanel,
|
|
36
37
|
AttendanceNotice: () => AttendanceNotice,
|
|
37
38
|
Avatar: () => Avatar,
|
|
@@ -135,6 +136,7 @@ __export(react_exports, {
|
|
|
135
136
|
TRANSCRIPTION_TOPIC: () => TRANSCRIPTION_TOPIC,
|
|
136
137
|
WifiLowIcon: () => WifiLowIcon,
|
|
137
138
|
absenceSentence: () => absenceSentence,
|
|
139
|
+
activeWrapUpSummary: () => activeWrapUpSummary,
|
|
138
140
|
announceMeeting: () => announceMeeting,
|
|
139
141
|
applyRoomEvent: () => applyRoomEvent,
|
|
140
142
|
asCallId: () => asCallId,
|
|
@@ -143,6 +145,10 @@ __export(react_exports, {
|
|
|
143
145
|
asParticipantIdentity: () => asParticipantIdentity,
|
|
144
146
|
asRoomName: () => asRoomName,
|
|
145
147
|
asUserId: () => asUserId,
|
|
148
|
+
attendanceCsv: () => attendanceCsv,
|
|
149
|
+
attendanceReport: () => attendanceReport,
|
|
150
|
+
attendanceTotals: () => attendanceTotals,
|
|
151
|
+
attendeeKey: () => attendeeKey,
|
|
146
152
|
callInviteAction: () => callInviteAction,
|
|
147
153
|
callInviteText: () => callInviteText,
|
|
148
154
|
callStateLabel: () => callStateLabel,
|
|
@@ -151,6 +157,7 @@ __export(react_exports, {
|
|
|
151
157
|
canTransition: () => canTransition,
|
|
152
158
|
captionSegment: () => captionSegment,
|
|
153
159
|
changesPatch: () => changesPatch,
|
|
160
|
+
clockPosition: () => clockPosition,
|
|
154
161
|
computeStage: () => computeStage,
|
|
155
162
|
countLabel: () => countLabel,
|
|
156
163
|
createCallCenter: () => createCallCenter,
|
|
@@ -191,11 +198,14 @@ __export(react_exports, {
|
|
|
191
198
|
learnName: () => learnName,
|
|
192
199
|
learnNames: () => learnNames,
|
|
193
200
|
listenOnlyNotice: () => listenOnlyNotice,
|
|
201
|
+
loadFollowUpDraft: () => loadFollowUpDraft,
|
|
194
202
|
loadGuestMeetingRecord: () => loadGuestMeetingRecord,
|
|
203
|
+
loadMeetingChat: () => loadMeetingChat,
|
|
195
204
|
loadMeetingRecord: () => loadMeetingRecord,
|
|
196
205
|
mapConnectionState: () => mapConnectionState,
|
|
197
206
|
mapQuality: () => mapQuality,
|
|
198
207
|
meetingInviteAction: () => meetingInviteAction,
|
|
208
|
+
meetingLength: () => meetingLength,
|
|
199
209
|
meetingLink: () => meetingLink,
|
|
200
210
|
meetingPath: () => meetingPath,
|
|
201
211
|
meetingSlug: () => meetingSlug,
|
|
@@ -210,16 +220,19 @@ __export(react_exports, {
|
|
|
210
220
|
noteTakerLabel: () => noteTakerLabel,
|
|
211
221
|
noteTakerParticipant: () => noteTakerParticipant,
|
|
212
222
|
noteTakerStatus: () => noteTakerStatus,
|
|
223
|
+
notesDocument: () => notesDocument,
|
|
213
224
|
orderParticipants: () => orderParticipants,
|
|
214
225
|
parseRoomToken: () => parseRoomToken,
|
|
215
226
|
participantSummary: () => participantSummary,
|
|
216
227
|
playableRecordingFileId: () => playableRecordingFileId,
|
|
217
228
|
projectInvitee: () => projectInvitee,
|
|
229
|
+
projectLibraryRecording: () => projectLibraryRecording,
|
|
218
230
|
projectNote: () => projectNote,
|
|
219
231
|
projectOccurrence: () => projectOccurrence,
|
|
220
232
|
projectOverride: () => projectOverride,
|
|
221
233
|
projectParticipant: () => projectParticipant,
|
|
222
234
|
projectRsvpInvitation: () => projectRsvpInvitation,
|
|
235
|
+
projectSearchHit: () => projectSearchHit,
|
|
223
236
|
projectSegment: () => projectSegment,
|
|
224
237
|
projectUpcoming: () => projectUpcoming,
|
|
225
238
|
pushMeetingToGoogleCalendar: () => pushMeetingToGoogleCalendar,
|
|
@@ -227,12 +240,19 @@ __export(react_exports, {
|
|
|
227
240
|
readableTranscript: () => readableTranscript,
|
|
228
241
|
recordingLabel: () => recordingLabel,
|
|
229
242
|
recordingNotice: () => recordingNotice,
|
|
243
|
+
recordingOffsetMs: () => recordingOffsetMs,
|
|
230
244
|
roleOf: () => roleOf,
|
|
231
245
|
roomTopic: () => roomTopic,
|
|
246
|
+
searchTranscript: () => searchTranscript,
|
|
247
|
+
sendFollowUp: () => sendFollowUp,
|
|
232
248
|
setGuestRecordAccess: () => setGuestRecordAccess,
|
|
233
249
|
settingsPatch: () => settingsPatch,
|
|
234
250
|
snapshotParticipant: () => snapshotParticipant,
|
|
251
|
+
snippetRuns: () => snippetRuns,
|
|
252
|
+
summarizeMeetingRecordRestrictions: () => summarizeMeetingRecordRestrictions,
|
|
253
|
+
transcriptDocument: () => transcriptDocument,
|
|
235
254
|
unavailableFrom: () => unavailableFrom,
|
|
255
|
+
uniqueAttendees: () => uniqueAttendees,
|
|
236
256
|
useAudioLevel: () => useAudioLevel,
|
|
237
257
|
useCalls: () => useCalls,
|
|
238
258
|
useCanModerate: () => useCanModerate,
|
|
@@ -267,7 +287,8 @@ __export(react_exports, {
|
|
|
267
287
|
useStage: () => useStage,
|
|
268
288
|
useTrack: () => useTrack,
|
|
269
289
|
userTopic: () => userTopic,
|
|
270
|
-
wasRefused: () => wasRefused
|
|
290
|
+
wasRefused: () => wasRefused,
|
|
291
|
+
wrapUpStatus: () => wrapUpStatus
|
|
271
292
|
});
|
|
272
293
|
module.exports = __toCommonJS(react_exports);
|
|
273
294
|
|
|
@@ -469,6 +490,12 @@ var MEET_ROUTES = {
|
|
|
469
490
|
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
470
491
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
471
492
|
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
493
|
+
/**
|
|
494
|
+
* Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,
|
|
495
|
+
* body from the wrap-up, who it can go to); `POST` = send what the host
|
|
496
|
+
* reviewed (`dry_run` renders and queues nothing). Never sent automatically.
|
|
497
|
+
*/
|
|
498
|
+
meetingFollowUp: "/api/v1/meet/meetings/{meeting_id}/follow-up",
|
|
472
499
|
/**
|
|
473
500
|
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
474
501
|
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
@@ -598,10 +625,11 @@ function createTokenClient(options) {
|
|
|
598
625
|
throw error;
|
|
599
626
|
}
|
|
600
627
|
if (response.status === 401 || response.status === 403) {
|
|
628
|
+
const said = await serverSentence(response);
|
|
601
629
|
throw new MeetError(
|
|
602
630
|
"forbidden",
|
|
603
|
-
|
|
604
|
-
"
|
|
631
|
+
said?.message ?? "You can't join this meeting.",
|
|
632
|
+
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again."
|
|
605
633
|
);
|
|
606
634
|
}
|
|
607
635
|
if (response.status === 404) {
|
|
@@ -612,12 +640,7 @@ function createTokenClient(options) {
|
|
|
612
640
|
);
|
|
613
641
|
}
|
|
614
642
|
if (response.status === 410) {
|
|
615
|
-
|
|
616
|
-
try {
|
|
617
|
-
const body = await response.json();
|
|
618
|
-
if (typeof body?.detail?.message === "string") reason = body.detail.message;
|
|
619
|
-
} catch {
|
|
620
|
-
}
|
|
643
|
+
const reason = (await serverSentence(response))?.message ?? "This meeting was cancelled.";
|
|
621
644
|
throw new MeetError(
|
|
622
645
|
"cancelled",
|
|
623
646
|
reason,
|
|
@@ -625,10 +648,11 @@ function createTokenClient(options) {
|
|
|
625
648
|
);
|
|
626
649
|
}
|
|
627
650
|
if (response.status === 423) {
|
|
651
|
+
const said = await serverSentence(response);
|
|
628
652
|
throw new MeetError(
|
|
629
653
|
"room-locked",
|
|
630
|
-
"
|
|
631
|
-
"The host locked the room. Ask them to unlock
|
|
654
|
+
said?.message ?? "The meeting is locked.",
|
|
655
|
+
said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again."
|
|
632
656
|
);
|
|
633
657
|
}
|
|
634
658
|
if (response.status === 503) {
|
|
@@ -699,6 +723,19 @@ function placeholder(roomName) {
|
|
|
699
723
|
lobby: false
|
|
700
724
|
};
|
|
701
725
|
}
|
|
726
|
+
async function serverSentence(response) {
|
|
727
|
+
try {
|
|
728
|
+
const body = await response.json();
|
|
729
|
+
const source = body !== null && typeof body.detail === "object" && body.detail !== null ? body.detail : body;
|
|
730
|
+
if (source === null) return null;
|
|
731
|
+
const message = typeof source.user_message === "string" && source.user_message.trim() !== "" ? source.user_message : typeof source.message === "string" && source.message.trim() !== "" ? source.message : null;
|
|
732
|
+
if (message === null) return null;
|
|
733
|
+
const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
|
|
734
|
+
return { message, remedy };
|
|
735
|
+
} catch {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
702
739
|
|
|
703
740
|
// src/core/ai.ts
|
|
704
741
|
var MEET_ANSWER_EVENTS = {
|
|
@@ -743,6 +780,29 @@ function readText(data) {
|
|
|
743
780
|
}
|
|
744
781
|
return "";
|
|
745
782
|
}
|
|
783
|
+
var SERVER_FAILED_SENTENCE = "The server hit an unexpected problem and nothing was changed.";
|
|
784
|
+
function isRawServerText(value) {
|
|
785
|
+
const text3 = value.trim();
|
|
786
|
+
return /^internal server error\b/i.test(text3) || /^[A-Z][A-Za-z0-9]*(Error|Exception)$/.test(text3) || /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(text3);
|
|
787
|
+
}
|
|
788
|
+
async function refusalFromResponse(response, operation, fallbackRemedy) {
|
|
789
|
+
let body = null;
|
|
790
|
+
try {
|
|
791
|
+
body = await response.json();
|
|
792
|
+
} catch {
|
|
793
|
+
body = null;
|
|
794
|
+
}
|
|
795
|
+
const said = readServerRefusal(body);
|
|
796
|
+
const code = response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport";
|
|
797
|
+
const error = new MeetError(
|
|
798
|
+
code,
|
|
799
|
+
said?.reason ?? SERVER_FAILED_SENTENCE,
|
|
800
|
+
said !== null && said.remedy.length > 0 ? said.remedy : fallbackRemedy
|
|
801
|
+
);
|
|
802
|
+
error.operation = operation;
|
|
803
|
+
error.status = response.status;
|
|
804
|
+
return error;
|
|
805
|
+
}
|
|
746
806
|
function readServerRefusal(body) {
|
|
747
807
|
if (typeof body === "string") {
|
|
748
808
|
return body.trim().length === 0 ? null : { reason: body.trim(), remedy: "" };
|
|
@@ -761,7 +821,8 @@ function readServerRefusal(body) {
|
|
|
761
821
|
return messages.length === 0 ? null : { reason: messages.join("; "), remedy: "" };
|
|
762
822
|
}
|
|
763
823
|
if (typeof detail === "object" && detail !== null) return readServerRefusal(detail);
|
|
764
|
-
const
|
|
824
|
+
const humanOrNull = (value) => typeof value === "string" && value.trim().length > 0 && !isRawServerText(value) ? value.trim() : null;
|
|
825
|
+
const reason = humanOrNull(row["reason"]) ?? humanOrNull(row["message"]) ?? humanOrNull(row["user_message"]) ?? (typeof detail === "string" ? humanOrNull(detail) : null) ?? (typeof row["message"] === "string" || typeof row["error"] === "string" || typeof detail === "string" ? SERVER_FAILED_SENTENCE : null);
|
|
765
826
|
if (reason === null) return null;
|
|
766
827
|
const remedy = typeof row["remedy"] === "string" ? row["remedy"] : "";
|
|
767
828
|
if (remedy.length > 0) return { reason, remedy };
|
|
@@ -1142,7 +1203,7 @@ var DEFAULT_RING_TIMEOUT_MS = 45e3;
|
|
|
1142
1203
|
function createCallCenter(options) {
|
|
1143
1204
|
const { manager, repository, identity } = options;
|
|
1144
1205
|
const timers = options.timers ?? {
|
|
1145
|
-
setTimeout: (run,
|
|
1206
|
+
setTimeout: (run, ms2) => setTimeout(run, ms2),
|
|
1146
1207
|
clearTimeout: (handle) => clearTimeout(handle),
|
|
1147
1208
|
now: () => Date.now()
|
|
1148
1209
|
};
|
|
@@ -1815,6 +1876,7 @@ var EMPTY = {
|
|
|
1815
1876
|
spotlight: null,
|
|
1816
1877
|
moderationNotice: null,
|
|
1817
1878
|
actionNotice: null,
|
|
1879
|
+
privateChatRequest: null,
|
|
1818
1880
|
effects: DEFAULT_EFFECTS,
|
|
1819
1881
|
transcript: [],
|
|
1820
1882
|
notes: [],
|
|
@@ -2080,7 +2142,9 @@ function createMeetingSession(options) {
|
|
|
2080
2142
|
return identity.userId;
|
|
2081
2143
|
}
|
|
2082
2144
|
function knockIdentity() {
|
|
2083
|
-
return asParticipantIdentity(
|
|
2145
|
+
return asParticipantIdentity(
|
|
2146
|
+
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
2147
|
+
);
|
|
2084
2148
|
}
|
|
2085
2149
|
function requireMeeting(operation) {
|
|
2086
2150
|
const meeting = store.snapshot().meeting;
|
|
@@ -2115,8 +2179,9 @@ function createMeetingSession(options) {
|
|
|
2115
2179
|
verdict.admitted ? "admitted" : "denied",
|
|
2116
2180
|
verdict.by === null ? null : verdict.by
|
|
2117
2181
|
);
|
|
2118
|
-
if (
|
|
2119
|
-
options.onAdmitted?.();
|
|
2182
|
+
if (decided === knockIdentity()) {
|
|
2183
|
+
if (verdict.admitted) options.onAdmitted?.();
|
|
2184
|
+
else options.onDenied?.();
|
|
2120
2185
|
}
|
|
2121
2186
|
}
|
|
2122
2187
|
},
|
|
@@ -2453,7 +2518,16 @@ var RPCS = {
|
|
|
2453
2518
|
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
2454
2519
|
meetingOccurrences: "meet_meeting_occurrences",
|
|
2455
2520
|
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
2456
|
-
occurrencesBetween: "meet_occurrences_between"
|
|
2521
|
+
occurrencesBetween: "meet_occurrences_between",
|
|
2522
|
+
/*
|
|
2523
|
+
* ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
|
|
2524
|
+
* scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
|
|
2525
|
+
* ceiling, never the view.
|
|
2526
|
+
*/
|
|
2527
|
+
/** The recordings library. */
|
|
2528
|
+
myRecordings: "meet_my_recordings",
|
|
2529
|
+
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
2530
|
+
search: "meet_search"
|
|
2457
2531
|
};
|
|
2458
2532
|
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
2459
2533
|
var MEETING_COLUMNS = "id,organization_id,room_name,slug,title,kind,host_user_id,scheduled_for,scheduled_duration_minutes,started_at,ended_at,locked,lobby_enabled,recording_policy,ai_enabled,metadata,time_zone,agenda,recurrence_rule,join_before_host,cancelled_at,cancellation_reason,deleted_at,calendar_sequence,version";
|
|
@@ -3158,12 +3232,117 @@ function createMeetRepository(options) {
|
|
|
3158
3232
|
);
|
|
3159
3233
|
},
|
|
3160
3234
|
async recordings(meetingId) {
|
|
3161
|
-
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: false });
|
|
3235
|
+
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("started_at", { ascending: false });
|
|
3162
3236
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.recordings");
|
|
3163
3237
|
return (response.data ?? []).map(projectRecording);
|
|
3238
|
+
},
|
|
3239
|
+
async myRecordings({ includeArchived = false, limit = 200 } = {}) {
|
|
3240
|
+
const rows = await guard(
|
|
3241
|
+
"meet.myRecordings",
|
|
3242
|
+
() => db.rpc(RPCS.myRecordings, { p_include_archived: includeArchived, p_limit: limit })
|
|
3243
|
+
);
|
|
3244
|
+
return rows.map(projectLibraryRecording);
|
|
3245
|
+
},
|
|
3246
|
+
async searchMeetings(query, limit = 60) {
|
|
3247
|
+
if (query.trim().length === 0) return [];
|
|
3248
|
+
const rows = await guard(
|
|
3249
|
+
"meet.searchMeetings",
|
|
3250
|
+
() => db.rpc(RPCS.search, { p_query: query, p_limit: limit })
|
|
3251
|
+
);
|
|
3252
|
+
return rows.map(projectSearchHit);
|
|
3253
|
+
},
|
|
3254
|
+
async renameRecording(recordingId, title) {
|
|
3255
|
+
const clean = title === null ? null : title.trim().slice(0, 200) || null;
|
|
3256
|
+
const response = await db.from(TABLES.recordings).update({ title: clean }).eq("id", recordingId).select("id");
|
|
3257
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.renameRecording");
|
|
3258
|
+
if ((response.data ?? []).length === 0) {
|
|
3259
|
+
throw normalizeMeetError(
|
|
3260
|
+
{ message: "permission denied: only the meeting's host or an editor can rename it", code: "42501" },
|
|
3261
|
+
"meet.renameRecording"
|
|
3262
|
+
);
|
|
3263
|
+
}
|
|
3264
|
+
},
|
|
3265
|
+
async setRecordingArchived(recordingId, archived) {
|
|
3266
|
+
const response = await db.from(TABLES.recordings).update({ deleted_at: archived ? (/* @__PURE__ */ new Date()).toISOString() : null }).eq("id", recordingId).select("id");
|
|
3267
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.setRecordingArchived");
|
|
3268
|
+
if ((response.data ?? []).length === 0) {
|
|
3269
|
+
throw normalizeMeetError(
|
|
3270
|
+
{ message: "permission denied: only the meeting's host or an editor can archive it", code: "42501" },
|
|
3271
|
+
"meet.setRecordingArchived"
|
|
3272
|
+
);
|
|
3273
|
+
}
|
|
3164
3274
|
}
|
|
3165
3275
|
};
|
|
3166
3276
|
}
|
|
3277
|
+
var LIBRARY_STATES = [
|
|
3278
|
+
"idle",
|
|
3279
|
+
"starting",
|
|
3280
|
+
"recording",
|
|
3281
|
+
"stopping",
|
|
3282
|
+
"processing",
|
|
3283
|
+
"available",
|
|
3284
|
+
"expired",
|
|
3285
|
+
"failed"
|
|
3286
|
+
];
|
|
3287
|
+
function projectLibraryRecording(row) {
|
|
3288
|
+
const operation = "meet.projectLibraryRecording";
|
|
3289
|
+
const state = text(row, "state");
|
|
3290
|
+
const role = text(row, "my_role");
|
|
3291
|
+
return {
|
|
3292
|
+
id: requireText(row, "recording_id", operation),
|
|
3293
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
3294
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
3295
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
3296
|
+
role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
|
|
3297
|
+
state: LIBRARY_STATES.includes(state) ? state : "failed",
|
|
3298
|
+
title: text(row, "recording_title"),
|
|
3299
|
+
fileId: text(row, "file_id"),
|
|
3300
|
+
startedAt: text(row, "started_at"),
|
|
3301
|
+
endedAt: text(row, "ended_at"),
|
|
3302
|
+
durationMs: num(row, "duration_ms"),
|
|
3303
|
+
sizeBytes: num(row, "size_bytes"),
|
|
3304
|
+
failureReason: text(row, "failure_reason"),
|
|
3305
|
+
archivedAt: text(row, "deleted_at"),
|
|
3306
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation))
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
var SEARCH_SOURCES = [
|
|
3310
|
+
"title",
|
|
3311
|
+
"summary",
|
|
3312
|
+
"decision",
|
|
3313
|
+
"action_item",
|
|
3314
|
+
"note",
|
|
3315
|
+
"transcript"
|
|
3316
|
+
];
|
|
3317
|
+
function projectSearchHit(row) {
|
|
3318
|
+
const operation = "meet.projectSearchHit";
|
|
3319
|
+
const source = text(row, "source");
|
|
3320
|
+
return {
|
|
3321
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
3322
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
3323
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
3324
|
+
meetingAt: text(row, "meeting_at"),
|
|
3325
|
+
source: SEARCH_SOURCES.includes(source) ? source : "note",
|
|
3326
|
+
refId: requireText(row, "ref_id", operation),
|
|
3327
|
+
speaker: text(row, "speaker"),
|
|
3328
|
+
snippet: text(row, "snippet") ?? "",
|
|
3329
|
+
at: text(row, "said_at"),
|
|
3330
|
+
rank: num(row, "rank") ?? 0
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3333
|
+
function snippetRuns(snippet) {
|
|
3334
|
+
const runs = [];
|
|
3335
|
+
const pattern = /⟦([^⟧]*)⟧/g;
|
|
3336
|
+
let last = 0;
|
|
3337
|
+
for (const match of snippet.matchAll(pattern)) {
|
|
3338
|
+
const start = match.index ?? 0;
|
|
3339
|
+
if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
|
|
3340
|
+
runs.push({ text: match[1] ?? "", hit: true });
|
|
3341
|
+
last = start + match[0].length;
|
|
3342
|
+
}
|
|
3343
|
+
if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
|
|
3344
|
+
return runs;
|
|
3345
|
+
}
|
|
3167
3346
|
|
|
3168
3347
|
// src/core/session-identity.ts
|
|
3169
3348
|
var RESOLVING = Object.freeze({
|
|
@@ -3887,6 +4066,10 @@ function createRoomEngine(options) {
|
|
|
3887
4066
|
}
|
|
3888
4067
|
},
|
|
3889
4068
|
disconnected(reason) {
|
|
4069
|
+
if (current !== null) {
|
|
4070
|
+
tokens.invalidate(current.roomName);
|
|
4071
|
+
current = null;
|
|
4072
|
+
}
|
|
3890
4073
|
store.patch({ phase: "left" });
|
|
3891
4074
|
options.onLeft?.(reason);
|
|
3892
4075
|
}
|
|
@@ -4189,19 +4372,8 @@ function createInRoomClient(options) {
|
|
|
4189
4372
|
const token = options.resolveRoomToken();
|
|
4190
4373
|
return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
|
|
4191
4374
|
}
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
try {
|
|
4195
|
-
body = await response.json();
|
|
4196
|
-
} catch {
|
|
4197
|
-
body = null;
|
|
4198
|
-
}
|
|
4199
|
-
const said = readServerRefusal(body);
|
|
4200
|
-
return new MeetError(
|
|
4201
|
-
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
4202
|
-
said?.reason ?? `${operation}: the server answered ${response.status}`,
|
|
4203
|
-
said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
|
|
4204
|
-
);
|
|
4375
|
+
function refusal2(response, operation, remedy) {
|
|
4376
|
+
return refusalFromResponse(response, operation, remedy);
|
|
4205
4377
|
}
|
|
4206
4378
|
return {
|
|
4207
4379
|
async moderate(request) {
|
|
@@ -4794,6 +4966,11 @@ function MeetRuntime(props) {
|
|
|
4794
4966
|
// publish nor subscribe, so the host saying yes has to mint a new one.
|
|
4795
4967
|
onAdmitted: () => {
|
|
4796
4968
|
void engine.rejoin();
|
|
4969
|
+
},
|
|
4970
|
+
// A "no" is ALSO a mint: the server refuses it with its own sentence, which
|
|
4971
|
+
// turns the waiting room into the pre-join screen showing that sentence.
|
|
4972
|
+
onDenied: () => {
|
|
4973
|
+
void engine.rejoin().catch(() => void 0);
|
|
4797
4974
|
}
|
|
4798
4975
|
});
|
|
4799
4976
|
const resolveRoomToken = () => {
|
|
@@ -6156,7 +6333,408 @@ function useGuestRecordAccess(meeting) {
|
|
|
6156
6333
|
// src/react/components.tsx
|
|
6157
6334
|
var import_react4 = require("react");
|
|
6158
6335
|
var import_design_system = require("@ai-matrx/design-system");
|
|
6336
|
+
var import_format3 = require("@ai-matrx/kit/format");
|
|
6337
|
+
|
|
6338
|
+
// src/core/after.ts
|
|
6159
6339
|
var import_format2 = require("@ai-matrx/kit/format");
|
|
6340
|
+
function kindOf(person) {
|
|
6341
|
+
if (person.isAgent) return "assistant";
|
|
6342
|
+
if (person.role === "host") return "host";
|
|
6343
|
+
if (person.role === "cohost") return "cohost";
|
|
6344
|
+
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
6345
|
+
return "member";
|
|
6346
|
+
}
|
|
6347
|
+
function ms(iso) {
|
|
6348
|
+
if (iso === null) return null;
|
|
6349
|
+
const at = new Date(iso).getTime();
|
|
6350
|
+
return Number.isNaN(at) ? null : at;
|
|
6351
|
+
}
|
|
6352
|
+
function attendeeKey(person, names = {}) {
|
|
6353
|
+
if (person.userId !== null) return `user:${person.userId}`;
|
|
6354
|
+
if (person.isAgent) return `agent:${person.identity}`;
|
|
6355
|
+
const name = displayNameFor(names, person.identity, person.displayName).trim().toLowerCase();
|
|
6356
|
+
return name.length > 0 ? `guest-name:${name}` : `identity:${person.identity}`;
|
|
6357
|
+
}
|
|
6358
|
+
function uniqueAttendees(attendees, names = {}) {
|
|
6359
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
6360
|
+
for (const person of attendees) {
|
|
6361
|
+
const key = attendeeKey(person, names);
|
|
6362
|
+
const kept = byKey.get(key);
|
|
6363
|
+
if (kept === void 0) {
|
|
6364
|
+
byKey.set(key, person);
|
|
6365
|
+
continue;
|
|
6366
|
+
}
|
|
6367
|
+
const keptJoin = ms(kept.joinedAt);
|
|
6368
|
+
const join = ms(person.joinedAt);
|
|
6369
|
+
if (join !== null && (keptJoin === null || join < keptJoin)) byKey.set(key, person);
|
|
6370
|
+
}
|
|
6371
|
+
return [...byKey.values()];
|
|
6372
|
+
}
|
|
6373
|
+
var KIND_RANK = {
|
|
6374
|
+
host: 0,
|
|
6375
|
+
cohost: 1,
|
|
6376
|
+
member: 2,
|
|
6377
|
+
"signed-in guest": 3,
|
|
6378
|
+
guest: 4,
|
|
6379
|
+
assistant: 5
|
|
6380
|
+
};
|
|
6381
|
+
function coveredMs(intervals) {
|
|
6382
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
6383
|
+
let total = 0;
|
|
6384
|
+
let from = -Infinity;
|
|
6385
|
+
let to = -Infinity;
|
|
6386
|
+
for (const [start, end] of sorted) {
|
|
6387
|
+
if (start > to) {
|
|
6388
|
+
if (to > from) total += to - from;
|
|
6389
|
+
from = start;
|
|
6390
|
+
to = end;
|
|
6391
|
+
} else if (end > to) {
|
|
6392
|
+
to = end;
|
|
6393
|
+
}
|
|
6394
|
+
}
|
|
6395
|
+
if (to > from) total += to - from;
|
|
6396
|
+
return total;
|
|
6397
|
+
}
|
|
6398
|
+
function attendanceReport(meeting, attendees, names = {}) {
|
|
6399
|
+
const end = ms(meeting.endedAt);
|
|
6400
|
+
const perJoin = attendees.map((person) => {
|
|
6401
|
+
const joined = ms(person.joinedAt);
|
|
6402
|
+
const left = ms(person.leftAt);
|
|
6403
|
+
const until = left ?? end;
|
|
6404
|
+
const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
|
|
6405
|
+
return {
|
|
6406
|
+
id: person.id,
|
|
6407
|
+
identity: person.identity,
|
|
6408
|
+
name: displayNameFor(names, person.identity, person.displayName),
|
|
6409
|
+
userId: person.userId,
|
|
6410
|
+
kind: kindOf(person),
|
|
6411
|
+
joinedAt: person.joinedAt,
|
|
6412
|
+
leftAt: person.leftAt,
|
|
6413
|
+
durationMs: duration,
|
|
6414
|
+
durationToEnd: duration !== null && left === null,
|
|
6415
|
+
admission: person.admissionState,
|
|
6416
|
+
joins: 1
|
|
6417
|
+
};
|
|
6418
|
+
});
|
|
6419
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6420
|
+
attendees.forEach((person, index) => {
|
|
6421
|
+
const key = attendeeKey(person, names);
|
|
6422
|
+
const group = groups.get(key) ?? { rows: [], people: [] };
|
|
6423
|
+
group.rows.push(perJoin[index]);
|
|
6424
|
+
group.people.push(person);
|
|
6425
|
+
groups.set(key, group);
|
|
6426
|
+
});
|
|
6427
|
+
const rows = [...groups.values()].map(({ rows: joins }) => {
|
|
6428
|
+
if (joins.length === 1) return joins[0];
|
|
6429
|
+
const came = joins.filter((row) => row.joinedAt !== null);
|
|
6430
|
+
const first = [...came].sort((a, b) => (ms(a.joinedAt) ?? 0) - (ms(b.joinedAt) ?? 0))[0] ?? joins[0];
|
|
6431
|
+
const kind = joins.reduce(
|
|
6432
|
+
(best, row) => KIND_RANK[row.kind] < KIND_RANK[best] ? row.kind : best,
|
|
6433
|
+
first.kind
|
|
6434
|
+
);
|
|
6435
|
+
const open = came.some((row) => row.leftAt === null);
|
|
6436
|
+
const lastLeave = came.reduce((latest, row) => {
|
|
6437
|
+
const left = ms(row.leftAt);
|
|
6438
|
+
return left !== null && (latest === null || left > latest) ? left : latest;
|
|
6439
|
+
}, null);
|
|
6440
|
+
const intervals = came.flatMap((row) => {
|
|
6441
|
+
const joined = ms(row.joinedAt);
|
|
6442
|
+
return joined !== null && row.durationMs !== null ? [[joined, joined + row.durationMs]] : [];
|
|
6443
|
+
});
|
|
6444
|
+
return {
|
|
6445
|
+
...first,
|
|
6446
|
+
kind,
|
|
6447
|
+
leftAt: open ? null : lastLeave === null ? null : new Date(lastLeave).toISOString(),
|
|
6448
|
+
durationMs: intervals.length === 0 ? null : coveredMs(intervals),
|
|
6449
|
+
durationToEnd: came.some((row) => row.durationToEnd),
|
|
6450
|
+
joins: came.length === 0 ? joins.length : came.length
|
|
6451
|
+
};
|
|
6452
|
+
});
|
|
6453
|
+
const rank = KIND_RANK;
|
|
6454
|
+
return rows.sort((a, b) => {
|
|
6455
|
+
if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
|
|
6456
|
+
return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
|
|
6457
|
+
});
|
|
6458
|
+
}
|
|
6459
|
+
function attendanceTotals(rows) {
|
|
6460
|
+
const humans = rows.filter((row) => row.kind !== "assistant");
|
|
6461
|
+
const attended = humans.filter((row) => row.joinedAt !== null);
|
|
6462
|
+
const timed = attended.filter((row) => row.durationMs !== null);
|
|
6463
|
+
return {
|
|
6464
|
+
people: attended.length,
|
|
6465
|
+
guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
|
|
6466
|
+
neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
|
|
6467
|
+
averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
|
|
6468
|
+
};
|
|
6469
|
+
}
|
|
6470
|
+
function csvCell(value) {
|
|
6471
|
+
if (value === null) return "";
|
|
6472
|
+
const text3 = String(value);
|
|
6473
|
+
return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
|
|
6474
|
+
}
|
|
6475
|
+
var ATTENDANCE_CSV_HEADER = [
|
|
6476
|
+
"Meeting",
|
|
6477
|
+
"Meeting started",
|
|
6478
|
+
"Name",
|
|
6479
|
+
"Attended as",
|
|
6480
|
+
"Joined",
|
|
6481
|
+
"Left",
|
|
6482
|
+
"Minutes in the meeting",
|
|
6483
|
+
"Admission"
|
|
6484
|
+
];
|
|
6485
|
+
function attendanceCsv(reports) {
|
|
6486
|
+
const lines = [ATTENDANCE_CSV_HEADER.join(",")];
|
|
6487
|
+
for (const { meeting, rows } of reports) {
|
|
6488
|
+
for (const row of rows) {
|
|
6489
|
+
lines.push(
|
|
6490
|
+
[
|
|
6491
|
+
meeting.title,
|
|
6492
|
+
meeting.startedAt ?? meeting.scheduledFor,
|
|
6493
|
+
row.name,
|
|
6494
|
+
row.kind,
|
|
6495
|
+
row.joinedAt,
|
|
6496
|
+
row.leftAt,
|
|
6497
|
+
row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
|
|
6498
|
+
row.admission
|
|
6499
|
+
].map(csvCell).join(",")
|
|
6500
|
+
);
|
|
6501
|
+
}
|
|
6502
|
+
}
|
|
6503
|
+
return `${lines.join("\r\n")}\r
|
|
6504
|
+
`;
|
|
6505
|
+
}
|
|
6506
|
+
function recordingOffsetMs(at, recording) {
|
|
6507
|
+
if (recording === null || recording.state !== "available") return null;
|
|
6508
|
+
const start = ms(recording.startedAt);
|
|
6509
|
+
const moment = ms(at);
|
|
6510
|
+
if (start === null || moment === null || moment < start) return null;
|
|
6511
|
+
const end = ms(recording.endedAt);
|
|
6512
|
+
if (end !== null && moment > end) return null;
|
|
6513
|
+
return moment - start;
|
|
6514
|
+
}
|
|
6515
|
+
function clockPosition(offsetMs) {
|
|
6516
|
+
const total = Math.max(0, Math.floor(offsetMs / 1e3));
|
|
6517
|
+
const h = Math.floor(total / 3600);
|
|
6518
|
+
const m = Math.floor(total % 3600 / 60);
|
|
6519
|
+
const s = total % 60;
|
|
6520
|
+
const ss = String(s).padStart(2, "0");
|
|
6521
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
6522
|
+
}
|
|
6523
|
+
function searchTranscript(segments, query, names = {}) {
|
|
6524
|
+
const needle = fold(query.trim());
|
|
6525
|
+
if (needle.length === 0) return [];
|
|
6526
|
+
const out = [];
|
|
6527
|
+
for (const segment of segments) {
|
|
6528
|
+
const hay = fold(segment.text);
|
|
6529
|
+
const ranges = [];
|
|
6530
|
+
let from = 0;
|
|
6531
|
+
for (; ; ) {
|
|
6532
|
+
const at = hay.indexOf(needle, from);
|
|
6533
|
+
if (at < 0) break;
|
|
6534
|
+
ranges.push([at, at + needle.length]);
|
|
6535
|
+
from = at + needle.length;
|
|
6536
|
+
}
|
|
6537
|
+
const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
|
|
6538
|
+
if (ranges.length > 0 || speaker.includes(needle)) {
|
|
6539
|
+
out.push({ segmentId: segment.id, ranges });
|
|
6540
|
+
}
|
|
6541
|
+
}
|
|
6542
|
+
return out;
|
|
6543
|
+
}
|
|
6544
|
+
function fold(value) {
|
|
6545
|
+
return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
|
|
6546
|
+
}
|
|
6547
|
+
function clockOfDay(iso, options) {
|
|
6548
|
+
const at = new Date(iso);
|
|
6549
|
+
if (Number.isNaN(at.getTime())) return "";
|
|
6550
|
+
return at.toLocaleTimeString(options.locale, {
|
|
6551
|
+
hour: "numeric",
|
|
6552
|
+
minute: "2-digit",
|
|
6553
|
+
second: "2-digit",
|
|
6554
|
+
...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
|
|
6555
|
+
});
|
|
6556
|
+
}
|
|
6557
|
+
function vttStamp(offsetMs) {
|
|
6558
|
+
const total = Math.max(0, Math.round(offsetMs));
|
|
6559
|
+
const h = Math.floor(total / 36e5);
|
|
6560
|
+
const m = Math.floor(total % 36e5 / 6e4);
|
|
6561
|
+
const s = Math.floor(total % 6e4 / 1e3);
|
|
6562
|
+
const milli = total % 1e3;
|
|
6563
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
|
|
6564
|
+
}
|
|
6565
|
+
function transcriptDocument(bundle, format, options = {}) {
|
|
6566
|
+
const names = options.names ?? bundle.names;
|
|
6567
|
+
const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
|
|
6568
|
+
const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
|
|
6569
|
+
if (format === "vtt") {
|
|
6570
|
+
const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
|
|
6571
|
+
const zero = ms(zeroIso) ?? 0;
|
|
6572
|
+
const cues = ["WEBVTT", ""];
|
|
6573
|
+
lines.forEach((line, index) => {
|
|
6574
|
+
const start = (ms(line.startedAt) ?? zero) - zero;
|
|
6575
|
+
if (start < 0) return;
|
|
6576
|
+
const next = lines[index + 1];
|
|
6577
|
+
const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
|
|
6578
|
+
const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
|
|
6579
|
+
cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
|
|
6580
|
+
cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
|
|
6581
|
+
cues.push("");
|
|
6582
|
+
});
|
|
6583
|
+
return cues.join("\n");
|
|
6584
|
+
}
|
|
6585
|
+
const out = [];
|
|
6586
|
+
const title = bundle.meeting.title;
|
|
6587
|
+
out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
|
|
6588
|
+
out.push("");
|
|
6589
|
+
let last = null;
|
|
6590
|
+
for (const line of lines) {
|
|
6591
|
+
if (line.identity !== last) {
|
|
6592
|
+
if (last !== null) out.push("");
|
|
6593
|
+
const who = speaker(line);
|
|
6594
|
+
const clock = clockOfDay(line.startedAt, options);
|
|
6595
|
+
out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
|
|
6596
|
+
last = line.identity;
|
|
6597
|
+
}
|
|
6598
|
+
out.push(line.text.trim());
|
|
6599
|
+
}
|
|
6600
|
+
if (lines.length === 0) out.push("No transcript was captured for this meeting.");
|
|
6601
|
+
return `${out.join("\n")}
|
|
6602
|
+
`;
|
|
6603
|
+
}
|
|
6604
|
+
function bullet(note) {
|
|
6605
|
+
const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
|
|
6606
|
+
return `- ${note.text.trim()}${owner}`;
|
|
6607
|
+
}
|
|
6608
|
+
function notesDocument(bundle, options = {}) {
|
|
6609
|
+
const out = [`# ${bundle.meeting.title}`, ""];
|
|
6610
|
+
if (options.when) out.push(`_${options.when}_`, "");
|
|
6611
|
+
const people = uniqueAttendees(
|
|
6612
|
+
bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null),
|
|
6613
|
+
bundle.names
|
|
6614
|
+
).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
|
|
6615
|
+
if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
|
|
6616
|
+
out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
|
|
6617
|
+
out.push("## Decisions", "");
|
|
6618
|
+
out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
|
|
6619
|
+
out.push("## Action items", "");
|
|
6620
|
+
out.push(
|
|
6621
|
+
...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
|
|
6622
|
+
""
|
|
6623
|
+
);
|
|
6624
|
+
if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
|
|
6625
|
+
return out.join("\n");
|
|
6626
|
+
}
|
|
6627
|
+
function meetingLength(meeting) {
|
|
6628
|
+
const start = ms(meeting.startedAt);
|
|
6629
|
+
const end = ms(meeting.endedAt);
|
|
6630
|
+
if (start === null || end === null || end < start) return null;
|
|
6631
|
+
return (0, import_format2.formatDurationMs)(end - start, { style: "long" });
|
|
6632
|
+
}
|
|
6633
|
+
async function loadMeetingChat(api, meetingId, roomToken = null) {
|
|
6634
|
+
const operation = "meet.loadMeetingChat";
|
|
6635
|
+
let response;
|
|
6636
|
+
try {
|
|
6637
|
+
response = await api.request({
|
|
6638
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
6639
|
+
method: "GET",
|
|
6640
|
+
operation,
|
|
6641
|
+
requireAuth: roomToken === null,
|
|
6642
|
+
...roomToken !== null ? { headers: { [ROOM_TOKEN_HEADER]: roomToken } } : {}
|
|
6643
|
+
});
|
|
6644
|
+
} catch (error) {
|
|
6645
|
+
return {
|
|
6646
|
+
messages: [],
|
|
6647
|
+
refusal: {
|
|
6648
|
+
message: `The chat could not be read (${error.message}).`,
|
|
6649
|
+
remedy: error.remedy ?? "Reload the page."
|
|
6650
|
+
}
|
|
6651
|
+
};
|
|
6652
|
+
}
|
|
6653
|
+
if (!response.ok) {
|
|
6654
|
+
let body = null;
|
|
6655
|
+
try {
|
|
6656
|
+
body = await response.json();
|
|
6657
|
+
} catch {
|
|
6658
|
+
body = null;
|
|
6659
|
+
}
|
|
6660
|
+
const said = readServerRefusal(body);
|
|
6661
|
+
return {
|
|
6662
|
+
messages: [],
|
|
6663
|
+
refusal: {
|
|
6664
|
+
message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
|
|
6665
|
+
remedy: said?.remedy || "Reload the page."
|
|
6666
|
+
}
|
|
6667
|
+
};
|
|
6668
|
+
}
|
|
6669
|
+
const payload = await response.json();
|
|
6670
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
6671
|
+
return {
|
|
6672
|
+
messages: rows.map(
|
|
6673
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
6674
|
+
).filter((message) => message !== null),
|
|
6675
|
+
refusal: null
|
|
6676
|
+
};
|
|
6677
|
+
}
|
|
6678
|
+
function refusalOf(response, operation) {
|
|
6679
|
+
return refusalFromResponse(response, operation, "Nothing was sent. Try again.");
|
|
6680
|
+
}
|
|
6681
|
+
function asRecipient(row) {
|
|
6682
|
+
const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
|
|
6683
|
+
return {
|
|
6684
|
+
key: String(row["key"] ?? ""),
|
|
6685
|
+
name: String(row["name"] ?? ""),
|
|
6686
|
+
email: typeof row["email"] === "string" ? row["email"] : null,
|
|
6687
|
+
userId: typeof row["user_id"] === "string" ? row["user_id"] : null,
|
|
6688
|
+
sources: sources.filter((s) => s === "invitee" || s === "attendee"),
|
|
6689
|
+
reachable: row["reachable"] !== false
|
|
6690
|
+
};
|
|
6691
|
+
}
|
|
6692
|
+
async function loadFollowUpDraft(api, meetingId) {
|
|
6693
|
+
const operation = "meet.loadFollowUpDraft";
|
|
6694
|
+
const response = await api.request({
|
|
6695
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
6696
|
+
method: "GET",
|
|
6697
|
+
requireAuth: true,
|
|
6698
|
+
operation
|
|
6699
|
+
});
|
|
6700
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
6701
|
+
const body = await response.json();
|
|
6702
|
+
const last = body["last_sent"];
|
|
6703
|
+
return {
|
|
6704
|
+
subject: String(body["subject"] ?? ""),
|
|
6705
|
+
body: String(body["body"] ?? ""),
|
|
6706
|
+
recipients: (Array.isArray(body["recipients"]) ? body["recipients"] : []).map(
|
|
6707
|
+
(row) => asRecipient(row)
|
|
6708
|
+
),
|
|
6709
|
+
wrapUpWritten: body["wrap_up_written"] === true,
|
|
6710
|
+
lastSent: last && typeof last["at"] === "string" ? { at: last["at"], count: typeof last["count"] === "number" ? last["count"] : 0 } : null
|
|
6711
|
+
};
|
|
6712
|
+
}
|
|
6713
|
+
async function sendFollowUp(api, meetingId, input) {
|
|
6714
|
+
const operation = "meet.sendFollowUp";
|
|
6715
|
+
const response = await api.request({
|
|
6716
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
6717
|
+
method: "POST",
|
|
6718
|
+
requireAuth: true,
|
|
6719
|
+
operation,
|
|
6720
|
+
body: {
|
|
6721
|
+
subject: input.subject,
|
|
6722
|
+
body: input.body,
|
|
6723
|
+
recipient_keys: [...input.recipientKeys],
|
|
6724
|
+
dry_run: input.dryRun === true
|
|
6725
|
+
}
|
|
6726
|
+
});
|
|
6727
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
6728
|
+
const body = await response.json();
|
|
6729
|
+
return {
|
|
6730
|
+
dryRun: body["dry_run"] === true,
|
|
6731
|
+
queued: Array.isArray(body["queued"]) ? body["queued"] : [],
|
|
6732
|
+
skipped: Array.isArray(body["skipped"]) ? body["skipped"].map((row) => ({
|
|
6733
|
+
key: String(row["key"] ?? ""),
|
|
6734
|
+
reason: String(row["reason"] ?? "")
|
|
6735
|
+
})) : []
|
|
6736
|
+
};
|
|
6737
|
+
}
|
|
6160
6738
|
|
|
6161
6739
|
// src/core/format.ts
|
|
6162
6740
|
function participantSummary(participants) {
|
|
@@ -6396,7 +6974,7 @@ function Avatar({
|
|
|
6396
6974
|
url,
|
|
6397
6975
|
size = 40
|
|
6398
6976
|
}) {
|
|
6399
|
-
const tint = (0,
|
|
6977
|
+
const tint = (0, import_format3.avatarPaletteIndex)(name);
|
|
6400
6978
|
if (url != null && url.length > 0) {
|
|
6401
6979
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6402
6980
|
"img",
|
|
@@ -6421,7 +6999,7 @@ function Avatar({
|
|
|
6421
6999
|
background: `var(--mx-meet-avatar-${tint})`
|
|
6422
7000
|
},
|
|
6423
7001
|
"aria-hidden": "true",
|
|
6424
|
-
children: (0,
|
|
7002
|
+
children: (0, import_format3.getInitials)(name)
|
|
6425
7003
|
}
|
|
6426
7004
|
);
|
|
6427
7005
|
}
|
|
@@ -6606,6 +7184,15 @@ function ControlBar({ onLeave }) {
|
|
|
6606
7184
|
const micUnavailable = useLocalMedia().microphone.unavailable;
|
|
6607
7185
|
const unread = useUnreadChat(panel === "chat");
|
|
6608
7186
|
const toggle = (next) => setPanel(panel === next ? "none" : next);
|
|
7187
|
+
const privateChatAt = snapshot?.privateChatRequest?.at ?? null;
|
|
7188
|
+
const meetStore = useMeetHost()?.store ?? null;
|
|
7189
|
+
(0, import_react4.useEffect)(() => {
|
|
7190
|
+
const request = meetStore?.snapshot().privateChatRequest ?? null;
|
|
7191
|
+
if (request === null) return;
|
|
7192
|
+
setChatTo(request.to);
|
|
7193
|
+
setPanel("chat");
|
|
7194
|
+
meetStore?.patch({ privateChatRequest: null });
|
|
7195
|
+
}, [privateChatAt, meetStore]);
|
|
6609
7196
|
useMeetShortcuts({
|
|
6610
7197
|
mic: () => void room.toggleMicrophone(),
|
|
6611
7198
|
camera: () => void room.toggleCamera(),
|
|
@@ -6845,7 +7432,12 @@ function Popover({
|
|
|
6845
7432
|
placement = "up"
|
|
6846
7433
|
}) {
|
|
6847
7434
|
const ref = (0, import_react4.useRef)(null);
|
|
6848
|
-
const [style, setStyle] = (0, import_react4.useState)({
|
|
7435
|
+
const [style, setStyle] = (0, import_react4.useState)({
|
|
7436
|
+
position: "fixed",
|
|
7437
|
+
top: 0,
|
|
7438
|
+
left: 0,
|
|
7439
|
+
visibility: "hidden"
|
|
7440
|
+
});
|
|
6849
7441
|
(0, import_react4.useLayoutEffect)(() => {
|
|
6850
7442
|
const element = ref.current;
|
|
6851
7443
|
const anchor = element?.parentElement ?? null;
|
|
@@ -6861,14 +7453,35 @@ function Popover({
|
|
|
6861
7453
|
);
|
|
6862
7454
|
const fitsBelow = rect.bottom + gutter + height <= window.innerHeight;
|
|
6863
7455
|
const fitsAbove = rect.top - gutter - height >= 0;
|
|
7456
|
+
if (!fitsBelow && !fitsAbove) {
|
|
7457
|
+
const top = Math.max(gutter, window.innerHeight - height - gutter);
|
|
7458
|
+
setStyle({
|
|
7459
|
+
position: "fixed",
|
|
7460
|
+
left,
|
|
7461
|
+
top,
|
|
7462
|
+
maxHeight: window.innerHeight - top - gutter,
|
|
7463
|
+
overflowY: "auto"
|
|
7464
|
+
});
|
|
7465
|
+
return;
|
|
7466
|
+
}
|
|
6864
7467
|
const down = placement === "down" ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;
|
|
6865
7468
|
setStyle(
|
|
6866
7469
|
down ? { position: "fixed", left, top: rect.bottom + gutter } : { position: "fixed", left, bottom: window.innerHeight - rect.top + gutter }
|
|
6867
7470
|
);
|
|
6868
7471
|
};
|
|
6869
7472
|
place();
|
|
7473
|
+
const frame = requestAnimationFrame(place);
|
|
7474
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => place());
|
|
7475
|
+
observer?.observe(document.body);
|
|
7476
|
+
observer?.observe(element);
|
|
6870
7477
|
window.addEventListener("resize", place);
|
|
6871
|
-
|
|
7478
|
+
window.addEventListener("scroll", place, true);
|
|
7479
|
+
return () => {
|
|
7480
|
+
cancelAnimationFrame(frame);
|
|
7481
|
+
observer?.disconnect();
|
|
7482
|
+
window.removeEventListener("resize", place);
|
|
7483
|
+
window.removeEventListener("scroll", place, true);
|
|
7484
|
+
};
|
|
6872
7485
|
}, [placement]);
|
|
6873
7486
|
(0, import_react4.useEffect)(() => {
|
|
6874
7487
|
const onKey = (event) => {
|
|
@@ -6919,22 +7532,27 @@ function MeetingOverlays() {
|
|
|
6919
7532
|
const room = useRoom();
|
|
6920
7533
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
6921
7534
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReactionOverlay, { reactions }),
|
|
6922
|
-
notice !== null && notice.kind !== "removed" ?
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
7535
|
+
notice !== null && notice.kind !== "removed" ? (
|
|
7536
|
+
// A host's request ("the host is asking you to unmute"), not an error:
|
|
7537
|
+
// an assertive status, so it is announced at once without being filed
|
|
7538
|
+
// as a failure (design-system error-box census).
|
|
7539
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__notice-bar", role: "status", "aria-live": "assertive", children: [
|
|
7540
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: notice.text }),
|
|
7541
|
+
notice.kind === "ask-unmute" && room.local?.micEnabled !== true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7542
|
+
"button",
|
|
7543
|
+
{
|
|
7544
|
+
type: "button",
|
|
7545
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
7546
|
+
onClick: () => {
|
|
7547
|
+
dismiss();
|
|
7548
|
+
void room.toggleMicrophone();
|
|
7549
|
+
},
|
|
7550
|
+
children: "Unmute"
|
|
7551
|
+
}
|
|
7552
|
+
) : null,
|
|
7553
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, { width: 14, height: 14 }) })
|
|
7554
|
+
] })
|
|
7555
|
+
) : null
|
|
6938
7556
|
] });
|
|
6939
7557
|
}
|
|
6940
7558
|
function ShortcutsSheet({ onClose }) {
|
|
@@ -7240,11 +7858,15 @@ function PersonMenu({
|
|
|
7240
7858
|
const pinned = snapshot?.pinned === participant.identity;
|
|
7241
7859
|
const spotlit = snapshot?.spotlight === participant.identity;
|
|
7242
7860
|
const hasAccount = participant.userId !== null;
|
|
7861
|
+
const [refused, setRefused] = (0, import_react4.useState)(false);
|
|
7243
7862
|
const act = (request) => {
|
|
7863
|
+
setRefused(false);
|
|
7244
7864
|
void controls.run(request).then((result) => {
|
|
7245
7865
|
if (result !== null) onClose();
|
|
7866
|
+
else setRefused(true);
|
|
7246
7867
|
});
|
|
7247
7868
|
};
|
|
7869
|
+
const refusal2 = refused && controls.notice?.failed === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system.ErrorBox, { as: "p", className: "mx-meet__error", message: controls.notice.text, operation: "Change this participant", origin: "meet/components", children: controls.notice.text }) : null;
|
|
7248
7870
|
if (mode === "rename" || mode === "report") {
|
|
7249
7871
|
const renaming = mode === "rename";
|
|
7250
7872
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: renaming ? "Rename" : "Report", onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
@@ -7282,7 +7904,7 @@ function PersonMenu({
|
|
|
7282
7904
|
}
|
|
7283
7905
|
)
|
|
7284
7906
|
] }),
|
|
7285
|
-
|
|
7907
|
+
refusal2,
|
|
7286
7908
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu-actions", children: [
|
|
7287
7909
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Back" }),
|
|
7288
7910
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", className: "mx-meet__send", disabled: text3.trim().length === 0 || controls.busy, children: renaming ? "Rename" : "Report" })
|
|
@@ -7295,6 +7917,7 @@ function PersonMenu({
|
|
|
7295
7917
|
const removing = mode === "remove";
|
|
7296
7918
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: removing ? "Remove" : "Make host", onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu-form", children: [
|
|
7297
7919
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__confirm-text", children: removing ? `Remove ${participant.displayName}? They leave immediately and cannot rejoin this meeting.` : `Make ${participant.displayName} the host? They get every host control and own this meeting. You stay on as a co-host.` }),
|
|
7920
|
+
refusal2,
|
|
7298
7921
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu-actions", children: [
|
|
7299
7922
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Cancel" }),
|
|
7300
7923
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -7325,11 +7948,15 @@ function PersonMenu({
|
|
|
7325
7948
|
]
|
|
7326
7949
|
}
|
|
7327
7950
|
),
|
|
7328
|
-
!self && !participant.isAgent && onMessage !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7951
|
+
!self && !participant.isAgent && (onMessage !== void 0 || host !== null) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7329
7952
|
MenuItem,
|
|
7330
7953
|
{
|
|
7331
7954
|
onSelect: () => {
|
|
7332
|
-
onMessage(participant.identity);
|
|
7955
|
+
if (onMessage !== void 0) onMessage(participant.identity);
|
|
7956
|
+
else
|
|
7957
|
+
host?.store.patch({
|
|
7958
|
+
privateChatRequest: { to: participant.identity, at: Date.now() }
|
|
7959
|
+
});
|
|
7333
7960
|
onClose();
|
|
7334
7961
|
},
|
|
7335
7962
|
children: [
|
|
@@ -7956,7 +8583,7 @@ function PreJoinBody({
|
|
|
7956
8583
|
microphone,
|
|
7957
8584
|
...meetingId !== void 0 ? { meetingId } : {},
|
|
7958
8585
|
...slug !== void 0 ? { slug } : {}
|
|
7959
|
-
}).then(() => onJoined?.()).finally(() => setJoining(false));
|
|
8586
|
+
}).then(() => onJoined?.()).catch(() => void 0).finally(() => setJoining(false));
|
|
7960
8587
|
},
|
|
7961
8588
|
children: joining ? "Joining\u2026" : "Join now"
|
|
7962
8589
|
}
|
|
@@ -8092,7 +8719,7 @@ function MeetingRoomBody({
|
|
|
8092
8719
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { className: "mx-meet__title", children: snapshot?.meeting?.title ?? "Meeting" }),
|
|
8093
8720
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__meta", children: [
|
|
8094
8721
|
participantSummary(snapshot?.participants ?? []),
|
|
8095
|
-
elapsed > 0 ? ` \xB7 ${(0,
|
|
8722
|
+
elapsed > 0 ? ` \xB7 ${(0, import_format3.formatDurationMs)(elapsed)}` : ""
|
|
8096
8723
|
] }),
|
|
8097
8724
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RecordingIndicator, {}),
|
|
8098
8725
|
snapshot?.locked === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__badge", children: [
|
|
@@ -8398,7 +9025,10 @@ function MeetingRecordBody({
|
|
|
8398
9025
|
const blocks = groupTranscript(bundle.transcript, bundle.names);
|
|
8399
9026
|
const notice = recordingNotice(bundle.recording);
|
|
8400
9027
|
const fileId = playableRecordingFileId(bundle.recording);
|
|
8401
|
-
const attended =
|
|
9028
|
+
const attended = uniqueAttendees(
|
|
9029
|
+
bundle.attendees.filter((person) => person.admissionState !== "denied"),
|
|
9030
|
+
bundle.names
|
|
9031
|
+
);
|
|
8402
9032
|
const restrictionNotices = summarizeMeetingRecordRestrictions(bundle.restrictions);
|
|
8403
9033
|
const guestFinishedMeeting = host.identity.userId === null && meeting.endedAt !== null;
|
|
8404
9034
|
const everyRecordPartRefused = ["attendance", "notes", "transcript", "recording"].every(
|
|
@@ -8868,18 +9498,11 @@ function createMeetingInviteHandler(options) {
|
|
|
8868
9498
|
}
|
|
8869
9499
|
|
|
8870
9500
|
// src/core/management.ts
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
body = null;
|
|
8877
|
-
}
|
|
8878
|
-
const said = readServerRefusal(body);
|
|
8879
|
-
return new MeetError(
|
|
8880
|
-
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
8881
|
-
`${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
|
|
8882
|
-
said?.remedy || "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
|
|
9501
|
+
function refusal(response, operation) {
|
|
9502
|
+
return refusalFromResponse(
|
|
9503
|
+
response,
|
|
9504
|
+
operation,
|
|
9505
|
+
"The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
|
|
8883
9506
|
);
|
|
8884
9507
|
}
|
|
8885
9508
|
function count(body, key) {
|