@ai-matrx/meet 0.7.6 → 0.7.7
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 +28 -0
- package/dist/index.cjs +567 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +538 -232
- package/dist/index.d.ts +538 -232
- package/dist/index.js +567 -19
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +530 -33
- 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 +530 -33
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
|
+
ATTENDANCE_CSV_HEADER: () => ATTENDANCE_CSV_HEADER,
|
|
33
34
|
CALL_INVITE_KIND: () => CALL_INVITE_KIND,
|
|
34
35
|
CALL_INVITE_VERSION: () => CALL_INVITE_VERSION,
|
|
35
36
|
CONSENT_NOTICE_ATTRIBUTE: () => CONSENT_NOTICE_ATTRIBUTE,
|
|
@@ -71,6 +72,7 @@ __export(src_exports, {
|
|
|
71
72
|
TRANSCRIPTION_ATTRIBUTES: () => TRANSCRIPTION_ATTRIBUTES,
|
|
72
73
|
TRANSCRIPTION_TOPIC: () => TRANSCRIPTION_TOPIC,
|
|
73
74
|
absenceSentence: () => absenceSentence,
|
|
75
|
+
activeWrapUpSummary: () => activeWrapUpSummary,
|
|
74
76
|
announceMeeting: () => announceMeeting,
|
|
75
77
|
applyRoomEvent: () => applyRoomEvent,
|
|
76
78
|
asCallId: () => asCallId,
|
|
@@ -79,6 +81,9 @@ __export(src_exports, {
|
|
|
79
81
|
asParticipantIdentity: () => asParticipantIdentity,
|
|
80
82
|
asRoomName: () => asRoomName,
|
|
81
83
|
asUserId: () => asUserId,
|
|
84
|
+
attendanceCsv: () => attendanceCsv,
|
|
85
|
+
attendanceReport: () => attendanceReport,
|
|
86
|
+
attendanceTotals: () => attendanceTotals,
|
|
82
87
|
callInviteAction: () => callInviteAction,
|
|
83
88
|
callInviteText: () => callInviteText,
|
|
84
89
|
callStateLabel: () => callStateLabel,
|
|
@@ -87,6 +92,7 @@ __export(src_exports, {
|
|
|
87
92
|
canTransition: () => canTransition,
|
|
88
93
|
captionSegment: () => captionSegment,
|
|
89
94
|
changesPatch: () => changesPatch,
|
|
95
|
+
clockPosition: () => clockPosition,
|
|
90
96
|
computeStage: () => computeStage,
|
|
91
97
|
countLabel: () => countLabel,
|
|
92
98
|
createCallCenter: () => createCallCenter,
|
|
@@ -127,11 +133,14 @@ __export(src_exports, {
|
|
|
127
133
|
learnName: () => learnName,
|
|
128
134
|
learnNames: () => learnNames,
|
|
129
135
|
listenOnlyNotice: () => listenOnlyNotice,
|
|
136
|
+
loadFollowUpDraft: () => loadFollowUpDraft,
|
|
130
137
|
loadGuestMeetingRecord: () => loadGuestMeetingRecord,
|
|
138
|
+
loadMeetingChat: () => loadMeetingChat,
|
|
131
139
|
loadMeetingRecord: () => loadMeetingRecord,
|
|
132
140
|
mapConnectionState: () => mapConnectionState,
|
|
133
141
|
mapQuality: () => mapQuality,
|
|
134
142
|
meetingInviteAction: () => meetingInviteAction,
|
|
143
|
+
meetingLength: () => meetingLength,
|
|
135
144
|
meetingLink: () => meetingLink,
|
|
136
145
|
meetingPath: () => meetingPath,
|
|
137
146
|
meetingSlug: () => meetingSlug,
|
|
@@ -146,16 +155,19 @@ __export(src_exports, {
|
|
|
146
155
|
noteTakerLabel: () => noteTakerLabel,
|
|
147
156
|
noteTakerParticipant: () => noteTakerParticipant,
|
|
148
157
|
noteTakerStatus: () => noteTakerStatus,
|
|
158
|
+
notesDocument: () => notesDocument,
|
|
149
159
|
orderParticipants: () => orderParticipants,
|
|
150
160
|
parseRoomToken: () => parseRoomToken,
|
|
151
161
|
participantSummary: () => participantSummary,
|
|
152
162
|
playableRecordingFileId: () => playableRecordingFileId,
|
|
153
163
|
projectInvitee: () => projectInvitee,
|
|
164
|
+
projectLibraryRecording: () => projectLibraryRecording,
|
|
154
165
|
projectNote: () => projectNote,
|
|
155
166
|
projectOccurrence: () => projectOccurrence,
|
|
156
167
|
projectOverride: () => projectOverride,
|
|
157
168
|
projectParticipant: () => projectParticipant,
|
|
158
169
|
projectRsvpInvitation: () => projectRsvpInvitation,
|
|
170
|
+
projectSearchHit: () => projectSearchHit,
|
|
159
171
|
projectSegment: () => projectSegment,
|
|
160
172
|
projectUpcoming: () => projectUpcoming,
|
|
161
173
|
pushMeetingToGoogleCalendar: () => pushMeetingToGoogleCalendar,
|
|
@@ -163,14 +175,21 @@ __export(src_exports, {
|
|
|
163
175
|
readableTranscript: () => readableTranscript,
|
|
164
176
|
recordingLabel: () => recordingLabel,
|
|
165
177
|
recordingNotice: () => recordingNotice,
|
|
178
|
+
recordingOffsetMs: () => recordingOffsetMs,
|
|
166
179
|
roleOf: () => roleOf,
|
|
167
180
|
roomTopic: () => roomTopic,
|
|
181
|
+
searchTranscript: () => searchTranscript,
|
|
182
|
+
sendFollowUp: () => sendFollowUp,
|
|
168
183
|
setGuestRecordAccess: () => setGuestRecordAccess,
|
|
169
184
|
settingsPatch: () => settingsPatch,
|
|
170
185
|
snapshotParticipant: () => snapshotParticipant,
|
|
186
|
+
snippetRuns: () => snippetRuns,
|
|
187
|
+
summarizeMeetingRecordRestrictions: () => summarizeMeetingRecordRestrictions,
|
|
188
|
+
transcriptDocument: () => transcriptDocument,
|
|
171
189
|
unavailableFrom: () => unavailableFrom,
|
|
172
190
|
userTopic: () => userTopic,
|
|
173
|
-
wasRefused: () => wasRefused
|
|
191
|
+
wasRefused: () => wasRefused,
|
|
192
|
+
wrapUpStatus: () => wrapUpStatus
|
|
174
193
|
});
|
|
175
194
|
module.exports = __toCommonJS(src_exports);
|
|
176
195
|
|
|
@@ -368,6 +387,12 @@ var MEET_ROUTES = {
|
|
|
368
387
|
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
369
388
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
370
389
|
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
390
|
+
/**
|
|
391
|
+
* Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,
|
|
392
|
+
* body from the wrap-up, who it can go to); `POST` = send what the host
|
|
393
|
+
* reviewed (`dry_run` renders and queues nothing). Never sent automatically.
|
|
394
|
+
*/
|
|
395
|
+
meetingFollowUp: "/api/v1/meet/meetings/{meeting_id}/follow-up",
|
|
371
396
|
/**
|
|
372
397
|
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
373
398
|
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
@@ -497,10 +522,11 @@ function createTokenClient(options) {
|
|
|
497
522
|
throw error;
|
|
498
523
|
}
|
|
499
524
|
if (response.status === 401 || response.status === 403) {
|
|
525
|
+
const said = await serverSentence(response);
|
|
500
526
|
throw new MeetError(
|
|
501
527
|
"forbidden",
|
|
502
|
-
|
|
503
|
-
"
|
|
528
|
+
said?.message ?? "You can't join this meeting.",
|
|
529
|
+
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again."
|
|
504
530
|
);
|
|
505
531
|
}
|
|
506
532
|
if (response.status === 404) {
|
|
@@ -511,12 +537,7 @@ function createTokenClient(options) {
|
|
|
511
537
|
);
|
|
512
538
|
}
|
|
513
539
|
if (response.status === 410) {
|
|
514
|
-
|
|
515
|
-
try {
|
|
516
|
-
const body = await response.json();
|
|
517
|
-
if (typeof body?.detail?.message === "string") reason = body.detail.message;
|
|
518
|
-
} catch {
|
|
519
|
-
}
|
|
540
|
+
const reason = (await serverSentence(response))?.message ?? "This meeting was cancelled.";
|
|
520
541
|
throw new MeetError(
|
|
521
542
|
"cancelled",
|
|
522
543
|
reason,
|
|
@@ -524,10 +545,11 @@ function createTokenClient(options) {
|
|
|
524
545
|
);
|
|
525
546
|
}
|
|
526
547
|
if (response.status === 423) {
|
|
548
|
+
const said = await serverSentence(response);
|
|
527
549
|
throw new MeetError(
|
|
528
550
|
"room-locked",
|
|
529
|
-
"
|
|
530
|
-
"The host locked the room. Ask them to unlock
|
|
551
|
+
said?.message ?? "The meeting is locked.",
|
|
552
|
+
said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again."
|
|
531
553
|
);
|
|
532
554
|
}
|
|
533
555
|
if (response.status === 503) {
|
|
@@ -598,6 +620,19 @@ function placeholder(roomName) {
|
|
|
598
620
|
lobby: false
|
|
599
621
|
};
|
|
600
622
|
}
|
|
623
|
+
async function serverSentence(response) {
|
|
624
|
+
try {
|
|
625
|
+
const body = await response.json();
|
|
626
|
+
const source = body !== null && typeof body.detail === "object" && body.detail !== null ? body.detail : body;
|
|
627
|
+
if (source === null) return null;
|
|
628
|
+
const message = typeof source.user_message === "string" && source.user_message.trim() !== "" ? source.user_message : typeof source.message === "string" && source.message.trim() !== "" ? source.message : null;
|
|
629
|
+
if (message === null) return null;
|
|
630
|
+
const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
|
|
631
|
+
return { message, remedy };
|
|
632
|
+
} catch {
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
601
636
|
|
|
602
637
|
// src/core/ai.ts
|
|
603
638
|
var MEET_ANSWER_EVENTS = {
|
|
@@ -1066,7 +1101,7 @@ var DEFAULT_RING_TIMEOUT_MS = 45e3;
|
|
|
1066
1101
|
function createCallCenter(options) {
|
|
1067
1102
|
const { manager, repository, identity } = options;
|
|
1068
1103
|
const timers = options.timers ?? {
|
|
1069
|
-
setTimeout: (run,
|
|
1104
|
+
setTimeout: (run, ms2) => setTimeout(run, ms2),
|
|
1070
1105
|
clearTimeout: (handle) => clearTimeout(handle),
|
|
1071
1106
|
now: () => Date.now()
|
|
1072
1107
|
};
|
|
@@ -1350,7 +1385,16 @@ var RPCS = {
|
|
|
1350
1385
|
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
1351
1386
|
meetingOccurrences: "meet_meeting_occurrences",
|
|
1352
1387
|
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
1353
|
-
occurrencesBetween: "meet_occurrences_between"
|
|
1388
|
+
occurrencesBetween: "meet_occurrences_between",
|
|
1389
|
+
/*
|
|
1390
|
+
* ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
|
|
1391
|
+
* scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
|
|
1392
|
+
* ceiling, never the view.
|
|
1393
|
+
*/
|
|
1394
|
+
/** The recordings library. */
|
|
1395
|
+
myRecordings: "meet_my_recordings",
|
|
1396
|
+
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
1397
|
+
search: "meet_search"
|
|
1354
1398
|
};
|
|
1355
1399
|
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
1356
1400
|
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";
|
|
@@ -2055,12 +2099,117 @@ function createMeetRepository(options) {
|
|
|
2055
2099
|
);
|
|
2056
2100
|
},
|
|
2057
2101
|
async recordings(meetingId) {
|
|
2058
|
-
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: false });
|
|
2102
|
+
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("started_at", { ascending: false });
|
|
2059
2103
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.recordings");
|
|
2060
2104
|
return (response.data ?? []).map(projectRecording);
|
|
2105
|
+
},
|
|
2106
|
+
async myRecordings({ includeArchived = false, limit = 200 } = {}) {
|
|
2107
|
+
const rows = await guard(
|
|
2108
|
+
"meet.myRecordings",
|
|
2109
|
+
() => db.rpc(RPCS.myRecordings, { p_include_archived: includeArchived, p_limit: limit })
|
|
2110
|
+
);
|
|
2111
|
+
return rows.map(projectLibraryRecording);
|
|
2112
|
+
},
|
|
2113
|
+
async searchMeetings(query, limit = 60) {
|
|
2114
|
+
if (query.trim().length === 0) return [];
|
|
2115
|
+
const rows = await guard(
|
|
2116
|
+
"meet.searchMeetings",
|
|
2117
|
+
() => db.rpc(RPCS.search, { p_query: query, p_limit: limit })
|
|
2118
|
+
);
|
|
2119
|
+
return rows.map(projectSearchHit);
|
|
2120
|
+
},
|
|
2121
|
+
async renameRecording(recordingId, title) {
|
|
2122
|
+
const clean = title === null ? null : title.trim().slice(0, 200) || null;
|
|
2123
|
+
const response = await db.from(TABLES.recordings).update({ title: clean }).eq("id", recordingId).select("id");
|
|
2124
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.renameRecording");
|
|
2125
|
+
if ((response.data ?? []).length === 0) {
|
|
2126
|
+
throw normalizeMeetError(
|
|
2127
|
+
{ message: "permission denied: only the meeting's host or an editor can rename it", code: "42501" },
|
|
2128
|
+
"meet.renameRecording"
|
|
2129
|
+
);
|
|
2130
|
+
}
|
|
2131
|
+
},
|
|
2132
|
+
async setRecordingArchived(recordingId, archived) {
|
|
2133
|
+
const response = await db.from(TABLES.recordings).update({ deleted_at: archived ? (/* @__PURE__ */ new Date()).toISOString() : null }).eq("id", recordingId).select("id");
|
|
2134
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.setRecordingArchived");
|
|
2135
|
+
if ((response.data ?? []).length === 0) {
|
|
2136
|
+
throw normalizeMeetError(
|
|
2137
|
+
{ message: "permission denied: only the meeting's host or an editor can archive it", code: "42501" },
|
|
2138
|
+
"meet.setRecordingArchived"
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2061
2141
|
}
|
|
2062
2142
|
};
|
|
2063
2143
|
}
|
|
2144
|
+
var LIBRARY_STATES = [
|
|
2145
|
+
"idle",
|
|
2146
|
+
"starting",
|
|
2147
|
+
"recording",
|
|
2148
|
+
"stopping",
|
|
2149
|
+
"processing",
|
|
2150
|
+
"available",
|
|
2151
|
+
"expired",
|
|
2152
|
+
"failed"
|
|
2153
|
+
];
|
|
2154
|
+
function projectLibraryRecording(row) {
|
|
2155
|
+
const operation = "meet.projectLibraryRecording";
|
|
2156
|
+
const state = text(row, "state");
|
|
2157
|
+
const role = text(row, "my_role");
|
|
2158
|
+
return {
|
|
2159
|
+
id: requireText(row, "recording_id", operation),
|
|
2160
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2161
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2162
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2163
|
+
role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
|
|
2164
|
+
state: LIBRARY_STATES.includes(state) ? state : "failed",
|
|
2165
|
+
title: text(row, "recording_title"),
|
|
2166
|
+
fileId: text(row, "file_id"),
|
|
2167
|
+
startedAt: text(row, "started_at"),
|
|
2168
|
+
endedAt: text(row, "ended_at"),
|
|
2169
|
+
durationMs: num(row, "duration_ms"),
|
|
2170
|
+
sizeBytes: num(row, "size_bytes"),
|
|
2171
|
+
failureReason: text(row, "failure_reason"),
|
|
2172
|
+
archivedAt: text(row, "deleted_at"),
|
|
2173
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation))
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
var SEARCH_SOURCES = [
|
|
2177
|
+
"title",
|
|
2178
|
+
"summary",
|
|
2179
|
+
"decision",
|
|
2180
|
+
"action_item",
|
|
2181
|
+
"note",
|
|
2182
|
+
"transcript"
|
|
2183
|
+
];
|
|
2184
|
+
function projectSearchHit(row) {
|
|
2185
|
+
const operation = "meet.projectSearchHit";
|
|
2186
|
+
const source = text(row, "source");
|
|
2187
|
+
return {
|
|
2188
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2189
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2190
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2191
|
+
meetingAt: text(row, "meeting_at"),
|
|
2192
|
+
source: SEARCH_SOURCES.includes(source) ? source : "note",
|
|
2193
|
+
refId: requireText(row, "ref_id", operation),
|
|
2194
|
+
speaker: text(row, "speaker"),
|
|
2195
|
+
snippet: text(row, "snippet") ?? "",
|
|
2196
|
+
at: text(row, "said_at"),
|
|
2197
|
+
rank: num(row, "rank") ?? 0
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
function snippetRuns(snippet) {
|
|
2201
|
+
const runs = [];
|
|
2202
|
+
const pattern = /⟦([^⟧]*)⟧/g;
|
|
2203
|
+
let last = 0;
|
|
2204
|
+
for (const match of snippet.matchAll(pattern)) {
|
|
2205
|
+
const start = match.index ?? 0;
|
|
2206
|
+
if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
|
|
2207
|
+
runs.push({ text: match[1] ?? "", hit: true });
|
|
2208
|
+
last = start + match[0].length;
|
|
2209
|
+
}
|
|
2210
|
+
if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
|
|
2211
|
+
return runs;
|
|
2212
|
+
}
|
|
2064
2213
|
|
|
2065
2214
|
// src/core/room-events.ts
|
|
2066
2215
|
var RECORDING_STATES = [
|
|
@@ -3494,7 +3643,9 @@ function createMeetingSession(options) {
|
|
|
3494
3643
|
return identity.userId;
|
|
3495
3644
|
}
|
|
3496
3645
|
function knockIdentity() {
|
|
3497
|
-
return asParticipantIdentity(
|
|
3646
|
+
return asParticipantIdentity(
|
|
3647
|
+
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
3648
|
+
);
|
|
3498
3649
|
}
|
|
3499
3650
|
function requireMeeting(operation) {
|
|
3500
3651
|
const meeting = store.snapshot().meeting;
|
|
@@ -3529,8 +3680,9 @@ function createMeetingSession(options) {
|
|
|
3529
3680
|
verdict.admitted ? "admitted" : "denied",
|
|
3530
3681
|
verdict.by === null ? null : verdict.by
|
|
3531
3682
|
);
|
|
3532
|
-
if (
|
|
3533
|
-
options.onAdmitted?.();
|
|
3683
|
+
if (decided === knockIdentity()) {
|
|
3684
|
+
if (verdict.admitted) options.onAdmitted?.();
|
|
3685
|
+
else options.onDenied?.();
|
|
3534
3686
|
}
|
|
3535
3687
|
}
|
|
3536
3688
|
},
|
|
@@ -3902,6 +4054,341 @@ function fileDownloadPath(fileId) {
|
|
|
3902
4054
|
return `/files/${encodeURIComponent(fileId)}/download?inline=1`;
|
|
3903
4055
|
}
|
|
3904
4056
|
|
|
4057
|
+
// src/core/after.ts
|
|
4058
|
+
var import_format = require("@ai-matrx/kit/format");
|
|
4059
|
+
function kindOf(person) {
|
|
4060
|
+
if (person.isAgent) return "assistant";
|
|
4061
|
+
if (person.role === "host") return "host";
|
|
4062
|
+
if (person.role === "cohost") return "cohost";
|
|
4063
|
+
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
4064
|
+
return "member";
|
|
4065
|
+
}
|
|
4066
|
+
function ms(iso) {
|
|
4067
|
+
if (iso === null) return null;
|
|
4068
|
+
const at = new Date(iso).getTime();
|
|
4069
|
+
return Number.isNaN(at) ? null : at;
|
|
4070
|
+
}
|
|
4071
|
+
function attendanceReport(meeting, attendees, names = {}) {
|
|
4072
|
+
const end = ms(meeting.endedAt);
|
|
4073
|
+
const rows = attendees.map((person) => {
|
|
4074
|
+
const joined = ms(person.joinedAt);
|
|
4075
|
+
const left = ms(person.leftAt);
|
|
4076
|
+
const until = left ?? end;
|
|
4077
|
+
const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
|
|
4078
|
+
return {
|
|
4079
|
+
id: person.id,
|
|
4080
|
+
identity: person.identity,
|
|
4081
|
+
name: displayNameFor(names, person.identity, person.displayName),
|
|
4082
|
+
userId: person.userId,
|
|
4083
|
+
kind: kindOf(person),
|
|
4084
|
+
joinedAt: person.joinedAt,
|
|
4085
|
+
leftAt: person.leftAt,
|
|
4086
|
+
durationMs: duration,
|
|
4087
|
+
durationToEnd: duration !== null && left === null,
|
|
4088
|
+
admission: person.admissionState
|
|
4089
|
+
};
|
|
4090
|
+
});
|
|
4091
|
+
const rank = {
|
|
4092
|
+
host: 0,
|
|
4093
|
+
cohost: 1,
|
|
4094
|
+
member: 2,
|
|
4095
|
+
"signed-in guest": 3,
|
|
4096
|
+
guest: 4,
|
|
4097
|
+
assistant: 5
|
|
4098
|
+
};
|
|
4099
|
+
return rows.sort((a, b) => {
|
|
4100
|
+
if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
|
|
4101
|
+
return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
|
|
4102
|
+
});
|
|
4103
|
+
}
|
|
4104
|
+
function attendanceTotals(rows) {
|
|
4105
|
+
const humans = rows.filter((row) => row.kind !== "assistant");
|
|
4106
|
+
const attended = humans.filter((row) => row.joinedAt !== null);
|
|
4107
|
+
const timed = attended.filter((row) => row.durationMs !== null);
|
|
4108
|
+
return {
|
|
4109
|
+
people: attended.length,
|
|
4110
|
+
guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
|
|
4111
|
+
neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
|
|
4112
|
+
averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
|
|
4113
|
+
};
|
|
4114
|
+
}
|
|
4115
|
+
function csvCell(value) {
|
|
4116
|
+
if (value === null) return "";
|
|
4117
|
+
const text3 = String(value);
|
|
4118
|
+
return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
|
|
4119
|
+
}
|
|
4120
|
+
var ATTENDANCE_CSV_HEADER = [
|
|
4121
|
+
"Meeting",
|
|
4122
|
+
"Meeting started",
|
|
4123
|
+
"Name",
|
|
4124
|
+
"Attended as",
|
|
4125
|
+
"Joined",
|
|
4126
|
+
"Left",
|
|
4127
|
+
"Minutes in the meeting",
|
|
4128
|
+
"Admission"
|
|
4129
|
+
];
|
|
4130
|
+
function attendanceCsv(reports) {
|
|
4131
|
+
const lines = [ATTENDANCE_CSV_HEADER.join(",")];
|
|
4132
|
+
for (const { meeting, rows } of reports) {
|
|
4133
|
+
for (const row of rows) {
|
|
4134
|
+
lines.push(
|
|
4135
|
+
[
|
|
4136
|
+
meeting.title,
|
|
4137
|
+
meeting.startedAt ?? meeting.scheduledFor,
|
|
4138
|
+
row.name,
|
|
4139
|
+
row.kind,
|
|
4140
|
+
row.joinedAt,
|
|
4141
|
+
row.leftAt,
|
|
4142
|
+
row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
|
|
4143
|
+
row.admission
|
|
4144
|
+
].map(csvCell).join(",")
|
|
4145
|
+
);
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
return `${lines.join("\r\n")}\r
|
|
4149
|
+
`;
|
|
4150
|
+
}
|
|
4151
|
+
function recordingOffsetMs(at, recording) {
|
|
4152
|
+
if (recording === null || recording.state !== "available") return null;
|
|
4153
|
+
const start = ms(recording.startedAt);
|
|
4154
|
+
const moment = ms(at);
|
|
4155
|
+
if (start === null || moment === null || moment < start) return null;
|
|
4156
|
+
const end = ms(recording.endedAt);
|
|
4157
|
+
if (end !== null && moment > end) return null;
|
|
4158
|
+
return moment - start;
|
|
4159
|
+
}
|
|
4160
|
+
function clockPosition(offsetMs) {
|
|
4161
|
+
const total = Math.max(0, Math.floor(offsetMs / 1e3));
|
|
4162
|
+
const h = Math.floor(total / 3600);
|
|
4163
|
+
const m = Math.floor(total % 3600 / 60);
|
|
4164
|
+
const s = total % 60;
|
|
4165
|
+
const ss = String(s).padStart(2, "0");
|
|
4166
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
4167
|
+
}
|
|
4168
|
+
function searchTranscript(segments, query, names = {}) {
|
|
4169
|
+
const needle = fold(query.trim());
|
|
4170
|
+
if (needle.length === 0) return [];
|
|
4171
|
+
const out = [];
|
|
4172
|
+
for (const segment of segments) {
|
|
4173
|
+
const hay = fold(segment.text);
|
|
4174
|
+
const ranges = [];
|
|
4175
|
+
let from = 0;
|
|
4176
|
+
for (; ; ) {
|
|
4177
|
+
const at = hay.indexOf(needle, from);
|
|
4178
|
+
if (at < 0) break;
|
|
4179
|
+
ranges.push([at, at + needle.length]);
|
|
4180
|
+
from = at + needle.length;
|
|
4181
|
+
}
|
|
4182
|
+
const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
|
|
4183
|
+
if (ranges.length > 0 || speaker.includes(needle)) {
|
|
4184
|
+
out.push({ segmentId: segment.id, ranges });
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
return out;
|
|
4188
|
+
}
|
|
4189
|
+
function fold(value) {
|
|
4190
|
+
return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
|
|
4191
|
+
}
|
|
4192
|
+
function clockOfDay(iso, options) {
|
|
4193
|
+
const at = new Date(iso);
|
|
4194
|
+
if (Number.isNaN(at.getTime())) return "";
|
|
4195
|
+
return at.toLocaleTimeString(options.locale, {
|
|
4196
|
+
hour: "numeric",
|
|
4197
|
+
minute: "2-digit",
|
|
4198
|
+
second: "2-digit",
|
|
4199
|
+
...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
|
|
4200
|
+
});
|
|
4201
|
+
}
|
|
4202
|
+
function vttStamp(offsetMs) {
|
|
4203
|
+
const total = Math.max(0, Math.round(offsetMs));
|
|
4204
|
+
const h = Math.floor(total / 36e5);
|
|
4205
|
+
const m = Math.floor(total % 36e5 / 6e4);
|
|
4206
|
+
const s = Math.floor(total % 6e4 / 1e3);
|
|
4207
|
+
const milli = total % 1e3;
|
|
4208
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
|
|
4209
|
+
}
|
|
4210
|
+
function transcriptDocument(bundle, format, options = {}) {
|
|
4211
|
+
const names = options.names ?? bundle.names;
|
|
4212
|
+
const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
|
|
4213
|
+
const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
|
|
4214
|
+
if (format === "vtt") {
|
|
4215
|
+
const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
|
|
4216
|
+
const zero = ms(zeroIso) ?? 0;
|
|
4217
|
+
const cues = ["WEBVTT", ""];
|
|
4218
|
+
lines.forEach((line, index) => {
|
|
4219
|
+
const start = (ms(line.startedAt) ?? zero) - zero;
|
|
4220
|
+
if (start < 0) return;
|
|
4221
|
+
const next = lines[index + 1];
|
|
4222
|
+
const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
|
|
4223
|
+
const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
|
|
4224
|
+
cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
|
|
4225
|
+
cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
|
|
4226
|
+
cues.push("");
|
|
4227
|
+
});
|
|
4228
|
+
return cues.join("\n");
|
|
4229
|
+
}
|
|
4230
|
+
const out = [];
|
|
4231
|
+
const title = bundle.meeting.title;
|
|
4232
|
+
out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
|
|
4233
|
+
out.push("");
|
|
4234
|
+
let last = null;
|
|
4235
|
+
for (const line of lines) {
|
|
4236
|
+
if (line.identity !== last) {
|
|
4237
|
+
if (last !== null) out.push("");
|
|
4238
|
+
const who = speaker(line);
|
|
4239
|
+
const clock = clockOfDay(line.startedAt, options);
|
|
4240
|
+
out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
|
|
4241
|
+
last = line.identity;
|
|
4242
|
+
}
|
|
4243
|
+
out.push(line.text.trim());
|
|
4244
|
+
}
|
|
4245
|
+
if (lines.length === 0) out.push("No transcript was captured for this meeting.");
|
|
4246
|
+
return `${out.join("\n")}
|
|
4247
|
+
`;
|
|
4248
|
+
}
|
|
4249
|
+
function bullet(note) {
|
|
4250
|
+
const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
|
|
4251
|
+
return `- ${note.text.trim()}${owner}`;
|
|
4252
|
+
}
|
|
4253
|
+
function notesDocument(bundle, options = {}) {
|
|
4254
|
+
const out = [`# ${bundle.meeting.title}`, ""];
|
|
4255
|
+
if (options.when) out.push(`_${options.when}_`, "");
|
|
4256
|
+
const people = bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
|
|
4257
|
+
if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
|
|
4258
|
+
out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
|
|
4259
|
+
out.push("## Decisions", "");
|
|
4260
|
+
out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
|
|
4261
|
+
out.push("## Action items", "");
|
|
4262
|
+
out.push(
|
|
4263
|
+
...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
|
|
4264
|
+
""
|
|
4265
|
+
);
|
|
4266
|
+
if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
|
|
4267
|
+
return out.join("\n");
|
|
4268
|
+
}
|
|
4269
|
+
function meetingLength(meeting) {
|
|
4270
|
+
const start = ms(meeting.startedAt);
|
|
4271
|
+
const end = ms(meeting.endedAt);
|
|
4272
|
+
if (start === null || end === null || end < start) return null;
|
|
4273
|
+
return (0, import_format.formatDurationMs)(end - start, { style: "long" });
|
|
4274
|
+
}
|
|
4275
|
+
async function loadMeetingChat(api, meetingId, roomToken = null) {
|
|
4276
|
+
const operation = "meet.loadMeetingChat";
|
|
4277
|
+
let response;
|
|
4278
|
+
try {
|
|
4279
|
+
response = await api.request({
|
|
4280
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
4281
|
+
method: "GET",
|
|
4282
|
+
operation,
|
|
4283
|
+
requireAuth: roomToken === null,
|
|
4284
|
+
...roomToken !== null ? { headers: { [ROOM_TOKEN_HEADER]: roomToken } } : {}
|
|
4285
|
+
});
|
|
4286
|
+
} catch (error) {
|
|
4287
|
+
return {
|
|
4288
|
+
messages: [],
|
|
4289
|
+
refusal: {
|
|
4290
|
+
message: `The chat could not be read (${error.message}).`,
|
|
4291
|
+
remedy: error.remedy ?? "Reload the page."
|
|
4292
|
+
}
|
|
4293
|
+
};
|
|
4294
|
+
}
|
|
4295
|
+
if (!response.ok) {
|
|
4296
|
+
let body = null;
|
|
4297
|
+
try {
|
|
4298
|
+
body = await response.json();
|
|
4299
|
+
} catch {
|
|
4300
|
+
body = null;
|
|
4301
|
+
}
|
|
4302
|
+
const said = readServerRefusal(body);
|
|
4303
|
+
return {
|
|
4304
|
+
messages: [],
|
|
4305
|
+
refusal: {
|
|
4306
|
+
message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
|
|
4307
|
+
remedy: said?.remedy || "Reload the page."
|
|
4308
|
+
}
|
|
4309
|
+
};
|
|
4310
|
+
}
|
|
4311
|
+
const payload = await response.json();
|
|
4312
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
4313
|
+
return {
|
|
4314
|
+
messages: rows.map(
|
|
4315
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
4316
|
+
).filter((message) => message !== null),
|
|
4317
|
+
refusal: null
|
|
4318
|
+
};
|
|
4319
|
+
}
|
|
4320
|
+
async function refusalOf(response, operation) {
|
|
4321
|
+
let body = null;
|
|
4322
|
+
try {
|
|
4323
|
+
body = await response.json();
|
|
4324
|
+
} catch {
|
|
4325
|
+
body = null;
|
|
4326
|
+
}
|
|
4327
|
+
const said = readServerRefusal(body);
|
|
4328
|
+
return new MeetError(
|
|
4329
|
+
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
4330
|
+
`${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
|
|
4331
|
+
said?.remedy || "Nothing was sent. Try again."
|
|
4332
|
+
);
|
|
4333
|
+
}
|
|
4334
|
+
function asRecipient(row) {
|
|
4335
|
+
const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
|
|
4336
|
+
return {
|
|
4337
|
+
key: String(row["key"] ?? ""),
|
|
4338
|
+
name: String(row["name"] ?? ""),
|
|
4339
|
+
email: typeof row["email"] === "string" ? row["email"] : null,
|
|
4340
|
+
userId: typeof row["user_id"] === "string" ? row["user_id"] : null,
|
|
4341
|
+
sources: sources.filter((s) => s === "invitee" || s === "attendee"),
|
|
4342
|
+
reachable: row["reachable"] !== false
|
|
4343
|
+
};
|
|
4344
|
+
}
|
|
4345
|
+
async function loadFollowUpDraft(api, meetingId) {
|
|
4346
|
+
const operation = "meet.loadFollowUpDraft";
|
|
4347
|
+
const response = await api.request({
|
|
4348
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
4349
|
+
method: "GET",
|
|
4350
|
+
requireAuth: true,
|
|
4351
|
+
operation
|
|
4352
|
+
});
|
|
4353
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
4354
|
+
const body = await response.json();
|
|
4355
|
+
const last = body["last_sent"];
|
|
4356
|
+
return {
|
|
4357
|
+
subject: String(body["subject"] ?? ""),
|
|
4358
|
+
body: String(body["body"] ?? ""),
|
|
4359
|
+
recipients: (Array.isArray(body["recipients"]) ? body["recipients"] : []).map(
|
|
4360
|
+
(row) => asRecipient(row)
|
|
4361
|
+
),
|
|
4362
|
+
wrapUpWritten: body["wrap_up_written"] === true,
|
|
4363
|
+
lastSent: last && typeof last["at"] === "string" ? { at: last["at"], count: typeof last["count"] === "number" ? last["count"] : 0 } : null
|
|
4364
|
+
};
|
|
4365
|
+
}
|
|
4366
|
+
async function sendFollowUp(api, meetingId, input) {
|
|
4367
|
+
const operation = "meet.sendFollowUp";
|
|
4368
|
+
const response = await api.request({
|
|
4369
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
4370
|
+
method: "POST",
|
|
4371
|
+
requireAuth: true,
|
|
4372
|
+
operation,
|
|
4373
|
+
body: {
|
|
4374
|
+
subject: input.subject,
|
|
4375
|
+
body: input.body,
|
|
4376
|
+
recipient_keys: [...input.recipientKeys],
|
|
4377
|
+
dry_run: input.dryRun === true
|
|
4378
|
+
}
|
|
4379
|
+
});
|
|
4380
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
4381
|
+
const body = await response.json();
|
|
4382
|
+
return {
|
|
4383
|
+
dryRun: body["dry_run"] === true,
|
|
4384
|
+
queued: Array.isArray(body["queued"]) ? body["queued"] : [],
|
|
4385
|
+
skipped: Array.isArray(body["skipped"]) ? body["skipped"].map((row) => ({
|
|
4386
|
+
key: String(row["key"] ?? ""),
|
|
4387
|
+
reason: String(row["reason"] ?? "")
|
|
4388
|
+
})) : []
|
|
4389
|
+
};
|
|
4390
|
+
}
|
|
4391
|
+
|
|
3905
4392
|
// src/core/management.ts
|
|
3906
4393
|
async function refusal(response, operation) {
|
|
3907
4394
|
let body = null;
|
|
@@ -4658,7 +5145,7 @@ function createInRoomClient(options) {
|
|
|
4658
5145
|
}
|
|
4659
5146
|
|
|
4660
5147
|
// src/core/record.ts
|
|
4661
|
-
var
|
|
5148
|
+
var import_format2 = require("@ai-matrx/kit/format");
|
|
4662
5149
|
function activeWrapUpSummary(notes) {
|
|
4663
5150
|
return notes.find((note) => note.role === "summary" && note.supersededBy === null) ?? null;
|
|
4664
5151
|
}
|
|
@@ -4741,6 +5228,36 @@ function wasRefused(bundle, part) {
|
|
|
4741
5228
|
function absenceSentence(bundle, part, absence) {
|
|
4742
5229
|
return wasRefused(bundle, part) ? REFUSED_ABSENCE : absence;
|
|
4743
5230
|
}
|
|
5231
|
+
function noteTakerMetadata(meeting) {
|
|
5232
|
+
const raw = meeting.metadata["note_taker"];
|
|
5233
|
+
return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
5234
|
+
}
|
|
5235
|
+
var WRAP_UP_IN_PROGRESS = "The wrap-up is being written\u2026 The summary, the decisions and the action items are produced from the whole transcript once the meeting ends, which takes a moment longer than the meeting does.";
|
|
5236
|
+
var WRAP_UP_ABSENT = "No wrap-up was written for this meeting. A summary is produced from the transcript when the meeting ends and the note-taker was in the room.";
|
|
5237
|
+
function wrapUpStatus(bundle) {
|
|
5238
|
+
if (bundle.summary !== null) return { state: "written" };
|
|
5239
|
+
if (wasRefused(bundle, "notes")) return { state: "refused", message: REFUSED_ABSENCE };
|
|
5240
|
+
const noteTaker = noteTakerMetadata(bundle.meeting);
|
|
5241
|
+
const intelligenceRaw = noteTaker["intelligence"];
|
|
5242
|
+
const intelligence = typeof intelligenceRaw === "object" && intelligenceRaw !== null && !Array.isArray(intelligenceRaw) ? intelligenceRaw : {};
|
|
5243
|
+
const wrapUpRaw = intelligence["wrap_up"];
|
|
5244
|
+
const wrapUp = typeof wrapUpRaw === "object" && wrapUpRaw !== null && !Array.isArray(wrapUpRaw) ? wrapUpRaw : {};
|
|
5245
|
+
if (wrapUp["state"] === "failed") {
|
|
5246
|
+
const reason = wrapUp["reason"];
|
|
5247
|
+
return {
|
|
5248
|
+
state: "failed",
|
|
5249
|
+
// THE SERVER'S OWN SENTENCE, verbatim. It already names the remedy, and a
|
|
5250
|
+
// house sentence over it would hide the one thing worth reading.
|
|
5251
|
+
message: typeof reason === "string" && reason.trim().length > 0 ? reason.trim() : "The wrap-up pass failed and no summary was written. It is not retried automatically; a host can end the meeting again to re-queue it."
|
|
5252
|
+
};
|
|
5253
|
+
}
|
|
5254
|
+
const queued = typeof wrapUp["run_id"] === "string" || typeof intelligence["wrap_up_run_id"] === "string" || wrapUp["state"] === "queued" || wrapUp["state"] === "running";
|
|
5255
|
+
const attended = noteTaker["state"] === "ended" || noteTaker["state"] === "transcribing" || noteTaker["state"] === "handoff_pending";
|
|
5256
|
+
if (hasEnded(bundle.meeting) && (queued || attended)) {
|
|
5257
|
+
return { state: "in-progress", message: WRAP_UP_IN_PROGRESS, canRefresh: true };
|
|
5258
|
+
}
|
|
5259
|
+
return { state: "absent", message: WRAP_UP_ABSENT };
|
|
5260
|
+
}
|
|
4744
5261
|
function groupTranscript(segments, names = {}) {
|
|
4745
5262
|
const blocks = [];
|
|
4746
5263
|
for (const segment of segments) {
|
|
@@ -4809,7 +5326,7 @@ function meetingWhen(meeting, locale) {
|
|
|
4809
5326
|
const endedAt = new Date(meeting.endedAt);
|
|
4810
5327
|
if (Number.isNaN(endedAt.getTime())) return when2;
|
|
4811
5328
|
const elapsedMs = endedAt.getTime() - startedAt.getTime();
|
|
4812
|
-
return `${when2} \xB7 ${(0,
|
|
5329
|
+
return `${when2} \xB7 ${(0, import_format2.formatDurationMs)(elapsedMs, { style: "long" })}`;
|
|
4813
5330
|
}
|
|
4814
5331
|
var GUEST_RECORD_ACCESS_LEVELS = [
|
|
4815
5332
|
"none",
|
|
@@ -4858,6 +5375,37 @@ var NOT_SHARED = {
|
|
|
4858
5375
|
recording: "The recording of this meeting is not shared with guests."
|
|
4859
5376
|
};
|
|
4860
5377
|
var NOT_SHARED_REMEDY = "The organization that held this meeting decided how much of its record guests can read. If you need more, ask whoever invited you \u2014 they can send it to you, or change the setting for this meeting.";
|
|
5378
|
+
var RESTRICTION_PART_NAME = {
|
|
5379
|
+
attendance: "attendance",
|
|
5380
|
+
notes: "notes and decisions",
|
|
5381
|
+
transcript: "word-for-word transcript",
|
|
5382
|
+
recording: "recording"
|
|
5383
|
+
};
|
|
5384
|
+
function joinPartNames(parts) {
|
|
5385
|
+
const names = parts.map((part) => RESTRICTION_PART_NAME[part]);
|
|
5386
|
+
if (names.length < 2) return names[0] ?? "this part of the record";
|
|
5387
|
+
if (names.length === 2) return `${names[0]} and ${names[1]}`;
|
|
5388
|
+
return `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`;
|
|
5389
|
+
}
|
|
5390
|
+
function summarizeMeetingRecordRestrictions(restrictions) {
|
|
5391
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5392
|
+
for (const restriction of restrictions) {
|
|
5393
|
+
const rows = grouped.get(restriction.remedy) ?? [];
|
|
5394
|
+
rows.push(restriction);
|
|
5395
|
+
grouped.set(restriction.remedy, rows);
|
|
5396
|
+
}
|
|
5397
|
+
return [...grouped.values()].map((rows) => {
|
|
5398
|
+
if (rows.length === 1) {
|
|
5399
|
+
const row = rows[0];
|
|
5400
|
+
return { parts: [row.part], message: row.message, remedy: row.remedy };
|
|
5401
|
+
}
|
|
5402
|
+
return {
|
|
5403
|
+
parts: rows.map((row) => row.part),
|
|
5404
|
+
message: `Some parts of this meeting record are not available to this reader: ${joinPartNames(rows.map((row) => row.part))}.`,
|
|
5405
|
+
remedy: rows[0].remedy
|
|
5406
|
+
};
|
|
5407
|
+
});
|
|
5408
|
+
}
|
|
4861
5409
|
async function loadGuestMeetingRecord(args) {
|
|
4862
5410
|
const { api, repository, meetingId, meeting, roomToken } = args;
|
|
4863
5411
|
if (roomToken === null) {
|