@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/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,9 @@ __export(react_exports, {
|
|
|
143
145
|
asParticipantIdentity: () => asParticipantIdentity,
|
|
144
146
|
asRoomName: () => asRoomName,
|
|
145
147
|
asUserId: () => asUserId,
|
|
148
|
+
attendanceCsv: () => attendanceCsv,
|
|
149
|
+
attendanceReport: () => attendanceReport,
|
|
150
|
+
attendanceTotals: () => attendanceTotals,
|
|
146
151
|
callInviteAction: () => callInviteAction,
|
|
147
152
|
callInviteText: () => callInviteText,
|
|
148
153
|
callStateLabel: () => callStateLabel,
|
|
@@ -151,6 +156,7 @@ __export(react_exports, {
|
|
|
151
156
|
canTransition: () => canTransition,
|
|
152
157
|
captionSegment: () => captionSegment,
|
|
153
158
|
changesPatch: () => changesPatch,
|
|
159
|
+
clockPosition: () => clockPosition,
|
|
154
160
|
computeStage: () => computeStage,
|
|
155
161
|
countLabel: () => countLabel,
|
|
156
162
|
createCallCenter: () => createCallCenter,
|
|
@@ -191,11 +197,14 @@ __export(react_exports, {
|
|
|
191
197
|
learnName: () => learnName,
|
|
192
198
|
learnNames: () => learnNames,
|
|
193
199
|
listenOnlyNotice: () => listenOnlyNotice,
|
|
200
|
+
loadFollowUpDraft: () => loadFollowUpDraft,
|
|
194
201
|
loadGuestMeetingRecord: () => loadGuestMeetingRecord,
|
|
202
|
+
loadMeetingChat: () => loadMeetingChat,
|
|
195
203
|
loadMeetingRecord: () => loadMeetingRecord,
|
|
196
204
|
mapConnectionState: () => mapConnectionState,
|
|
197
205
|
mapQuality: () => mapQuality,
|
|
198
206
|
meetingInviteAction: () => meetingInviteAction,
|
|
207
|
+
meetingLength: () => meetingLength,
|
|
199
208
|
meetingLink: () => meetingLink,
|
|
200
209
|
meetingPath: () => meetingPath,
|
|
201
210
|
meetingSlug: () => meetingSlug,
|
|
@@ -210,16 +219,19 @@ __export(react_exports, {
|
|
|
210
219
|
noteTakerLabel: () => noteTakerLabel,
|
|
211
220
|
noteTakerParticipant: () => noteTakerParticipant,
|
|
212
221
|
noteTakerStatus: () => noteTakerStatus,
|
|
222
|
+
notesDocument: () => notesDocument,
|
|
213
223
|
orderParticipants: () => orderParticipants,
|
|
214
224
|
parseRoomToken: () => parseRoomToken,
|
|
215
225
|
participantSummary: () => participantSummary,
|
|
216
226
|
playableRecordingFileId: () => playableRecordingFileId,
|
|
217
227
|
projectInvitee: () => projectInvitee,
|
|
228
|
+
projectLibraryRecording: () => projectLibraryRecording,
|
|
218
229
|
projectNote: () => projectNote,
|
|
219
230
|
projectOccurrence: () => projectOccurrence,
|
|
220
231
|
projectOverride: () => projectOverride,
|
|
221
232
|
projectParticipant: () => projectParticipant,
|
|
222
233
|
projectRsvpInvitation: () => projectRsvpInvitation,
|
|
234
|
+
projectSearchHit: () => projectSearchHit,
|
|
223
235
|
projectSegment: () => projectSegment,
|
|
224
236
|
projectUpcoming: () => projectUpcoming,
|
|
225
237
|
pushMeetingToGoogleCalendar: () => pushMeetingToGoogleCalendar,
|
|
@@ -227,11 +239,17 @@ __export(react_exports, {
|
|
|
227
239
|
readableTranscript: () => readableTranscript,
|
|
228
240
|
recordingLabel: () => recordingLabel,
|
|
229
241
|
recordingNotice: () => recordingNotice,
|
|
242
|
+
recordingOffsetMs: () => recordingOffsetMs,
|
|
230
243
|
roleOf: () => roleOf,
|
|
231
244
|
roomTopic: () => roomTopic,
|
|
245
|
+
searchTranscript: () => searchTranscript,
|
|
246
|
+
sendFollowUp: () => sendFollowUp,
|
|
232
247
|
setGuestRecordAccess: () => setGuestRecordAccess,
|
|
233
248
|
settingsPatch: () => settingsPatch,
|
|
234
249
|
snapshotParticipant: () => snapshotParticipant,
|
|
250
|
+
snippetRuns: () => snippetRuns,
|
|
251
|
+
summarizeMeetingRecordRestrictions: () => summarizeMeetingRecordRestrictions,
|
|
252
|
+
transcriptDocument: () => transcriptDocument,
|
|
235
253
|
unavailableFrom: () => unavailableFrom,
|
|
236
254
|
useAudioLevel: () => useAudioLevel,
|
|
237
255
|
useCalls: () => useCalls,
|
|
@@ -267,7 +285,8 @@ __export(react_exports, {
|
|
|
267
285
|
useStage: () => useStage,
|
|
268
286
|
useTrack: () => useTrack,
|
|
269
287
|
userTopic: () => userTopic,
|
|
270
|
-
wasRefused: () => wasRefused
|
|
288
|
+
wasRefused: () => wasRefused,
|
|
289
|
+
wrapUpStatus: () => wrapUpStatus
|
|
271
290
|
});
|
|
272
291
|
module.exports = __toCommonJS(react_exports);
|
|
273
292
|
|
|
@@ -469,6 +488,12 @@ var MEET_ROUTES = {
|
|
|
469
488
|
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
470
489
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
471
490
|
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
491
|
+
/**
|
|
492
|
+
* Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,
|
|
493
|
+
* body from the wrap-up, who it can go to); `POST` = send what the host
|
|
494
|
+
* reviewed (`dry_run` renders and queues nothing). Never sent automatically.
|
|
495
|
+
*/
|
|
496
|
+
meetingFollowUp: "/api/v1/meet/meetings/{meeting_id}/follow-up",
|
|
472
497
|
/**
|
|
473
498
|
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
474
499
|
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
@@ -598,10 +623,11 @@ function createTokenClient(options) {
|
|
|
598
623
|
throw error;
|
|
599
624
|
}
|
|
600
625
|
if (response.status === 401 || response.status === 403) {
|
|
626
|
+
const said = await serverSentence(response);
|
|
601
627
|
throw new MeetError(
|
|
602
628
|
"forbidden",
|
|
603
|
-
|
|
604
|
-
"
|
|
629
|
+
said?.message ?? "You can't join this meeting.",
|
|
630
|
+
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again."
|
|
605
631
|
);
|
|
606
632
|
}
|
|
607
633
|
if (response.status === 404) {
|
|
@@ -612,12 +638,7 @@ function createTokenClient(options) {
|
|
|
612
638
|
);
|
|
613
639
|
}
|
|
614
640
|
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
|
-
}
|
|
641
|
+
const reason = (await serverSentence(response))?.message ?? "This meeting was cancelled.";
|
|
621
642
|
throw new MeetError(
|
|
622
643
|
"cancelled",
|
|
623
644
|
reason,
|
|
@@ -625,10 +646,11 @@ function createTokenClient(options) {
|
|
|
625
646
|
);
|
|
626
647
|
}
|
|
627
648
|
if (response.status === 423) {
|
|
649
|
+
const said = await serverSentence(response);
|
|
628
650
|
throw new MeetError(
|
|
629
651
|
"room-locked",
|
|
630
|
-
"
|
|
631
|
-
"The host locked the room. Ask them to unlock
|
|
652
|
+
said?.message ?? "The meeting is locked.",
|
|
653
|
+
said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again."
|
|
632
654
|
);
|
|
633
655
|
}
|
|
634
656
|
if (response.status === 503) {
|
|
@@ -699,6 +721,19 @@ function placeholder(roomName) {
|
|
|
699
721
|
lobby: false
|
|
700
722
|
};
|
|
701
723
|
}
|
|
724
|
+
async function serverSentence(response) {
|
|
725
|
+
try {
|
|
726
|
+
const body = await response.json();
|
|
727
|
+
const source = body !== null && typeof body.detail === "object" && body.detail !== null ? body.detail : body;
|
|
728
|
+
if (source === null) return null;
|
|
729
|
+
const message = typeof source.user_message === "string" && source.user_message.trim() !== "" ? source.user_message : typeof source.message === "string" && source.message.trim() !== "" ? source.message : null;
|
|
730
|
+
if (message === null) return null;
|
|
731
|
+
const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
|
|
732
|
+
return { message, remedy };
|
|
733
|
+
} catch {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
702
737
|
|
|
703
738
|
// src/core/ai.ts
|
|
704
739
|
var MEET_ANSWER_EVENTS = {
|
|
@@ -1142,7 +1177,7 @@ var DEFAULT_RING_TIMEOUT_MS = 45e3;
|
|
|
1142
1177
|
function createCallCenter(options) {
|
|
1143
1178
|
const { manager, repository, identity } = options;
|
|
1144
1179
|
const timers = options.timers ?? {
|
|
1145
|
-
setTimeout: (run,
|
|
1180
|
+
setTimeout: (run, ms2) => setTimeout(run, ms2),
|
|
1146
1181
|
clearTimeout: (handle) => clearTimeout(handle),
|
|
1147
1182
|
now: () => Date.now()
|
|
1148
1183
|
};
|
|
@@ -2080,7 +2115,9 @@ function createMeetingSession(options) {
|
|
|
2080
2115
|
return identity.userId;
|
|
2081
2116
|
}
|
|
2082
2117
|
function knockIdentity() {
|
|
2083
|
-
return asParticipantIdentity(
|
|
2118
|
+
return asParticipantIdentity(
|
|
2119
|
+
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
2120
|
+
);
|
|
2084
2121
|
}
|
|
2085
2122
|
function requireMeeting(operation) {
|
|
2086
2123
|
const meeting = store.snapshot().meeting;
|
|
@@ -2115,8 +2152,9 @@ function createMeetingSession(options) {
|
|
|
2115
2152
|
verdict.admitted ? "admitted" : "denied",
|
|
2116
2153
|
verdict.by === null ? null : verdict.by
|
|
2117
2154
|
);
|
|
2118
|
-
if (
|
|
2119
|
-
options.onAdmitted?.();
|
|
2155
|
+
if (decided === knockIdentity()) {
|
|
2156
|
+
if (verdict.admitted) options.onAdmitted?.();
|
|
2157
|
+
else options.onDenied?.();
|
|
2120
2158
|
}
|
|
2121
2159
|
}
|
|
2122
2160
|
},
|
|
@@ -2453,7 +2491,16 @@ var RPCS = {
|
|
|
2453
2491
|
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
2454
2492
|
meetingOccurrences: "meet_meeting_occurrences",
|
|
2455
2493
|
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
2456
|
-
occurrencesBetween: "meet_occurrences_between"
|
|
2494
|
+
occurrencesBetween: "meet_occurrences_between",
|
|
2495
|
+
/*
|
|
2496
|
+
* ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
|
|
2497
|
+
* scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
|
|
2498
|
+
* ceiling, never the view.
|
|
2499
|
+
*/
|
|
2500
|
+
/** The recordings library. */
|
|
2501
|
+
myRecordings: "meet_my_recordings",
|
|
2502
|
+
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
2503
|
+
search: "meet_search"
|
|
2457
2504
|
};
|
|
2458
2505
|
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
2459
2506
|
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 +3205,117 @@ function createMeetRepository(options) {
|
|
|
3158
3205
|
);
|
|
3159
3206
|
},
|
|
3160
3207
|
async recordings(meetingId) {
|
|
3161
|
-
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: false });
|
|
3208
|
+
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("started_at", { ascending: false });
|
|
3162
3209
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.recordings");
|
|
3163
3210
|
return (response.data ?? []).map(projectRecording);
|
|
3211
|
+
},
|
|
3212
|
+
async myRecordings({ includeArchived = false, limit = 200 } = {}) {
|
|
3213
|
+
const rows = await guard(
|
|
3214
|
+
"meet.myRecordings",
|
|
3215
|
+
() => db.rpc(RPCS.myRecordings, { p_include_archived: includeArchived, p_limit: limit })
|
|
3216
|
+
);
|
|
3217
|
+
return rows.map(projectLibraryRecording);
|
|
3218
|
+
},
|
|
3219
|
+
async searchMeetings(query, limit = 60) {
|
|
3220
|
+
if (query.trim().length === 0) return [];
|
|
3221
|
+
const rows = await guard(
|
|
3222
|
+
"meet.searchMeetings",
|
|
3223
|
+
() => db.rpc(RPCS.search, { p_query: query, p_limit: limit })
|
|
3224
|
+
);
|
|
3225
|
+
return rows.map(projectSearchHit);
|
|
3226
|
+
},
|
|
3227
|
+
async renameRecording(recordingId, title) {
|
|
3228
|
+
const clean = title === null ? null : title.trim().slice(0, 200) || null;
|
|
3229
|
+
const response = await db.from(TABLES.recordings).update({ title: clean }).eq("id", recordingId).select("id");
|
|
3230
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.renameRecording");
|
|
3231
|
+
if ((response.data ?? []).length === 0) {
|
|
3232
|
+
throw normalizeMeetError(
|
|
3233
|
+
{ message: "permission denied: only the meeting's host or an editor can rename it", code: "42501" },
|
|
3234
|
+
"meet.renameRecording"
|
|
3235
|
+
);
|
|
3236
|
+
}
|
|
3237
|
+
},
|
|
3238
|
+
async setRecordingArchived(recordingId, archived) {
|
|
3239
|
+
const response = await db.from(TABLES.recordings).update({ deleted_at: archived ? (/* @__PURE__ */ new Date()).toISOString() : null }).eq("id", recordingId).select("id");
|
|
3240
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.setRecordingArchived");
|
|
3241
|
+
if ((response.data ?? []).length === 0) {
|
|
3242
|
+
throw normalizeMeetError(
|
|
3243
|
+
{ message: "permission denied: only the meeting's host or an editor can archive it", code: "42501" },
|
|
3244
|
+
"meet.setRecordingArchived"
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3164
3247
|
}
|
|
3165
3248
|
};
|
|
3166
3249
|
}
|
|
3250
|
+
var LIBRARY_STATES = [
|
|
3251
|
+
"idle",
|
|
3252
|
+
"starting",
|
|
3253
|
+
"recording",
|
|
3254
|
+
"stopping",
|
|
3255
|
+
"processing",
|
|
3256
|
+
"available",
|
|
3257
|
+
"expired",
|
|
3258
|
+
"failed"
|
|
3259
|
+
];
|
|
3260
|
+
function projectLibraryRecording(row) {
|
|
3261
|
+
const operation = "meet.projectLibraryRecording";
|
|
3262
|
+
const state = text(row, "state");
|
|
3263
|
+
const role = text(row, "my_role");
|
|
3264
|
+
return {
|
|
3265
|
+
id: requireText(row, "recording_id", operation),
|
|
3266
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
3267
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
3268
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
3269
|
+
role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
|
|
3270
|
+
state: LIBRARY_STATES.includes(state) ? state : "failed",
|
|
3271
|
+
title: text(row, "recording_title"),
|
|
3272
|
+
fileId: text(row, "file_id"),
|
|
3273
|
+
startedAt: text(row, "started_at"),
|
|
3274
|
+
endedAt: text(row, "ended_at"),
|
|
3275
|
+
durationMs: num(row, "duration_ms"),
|
|
3276
|
+
sizeBytes: num(row, "size_bytes"),
|
|
3277
|
+
failureReason: text(row, "failure_reason"),
|
|
3278
|
+
archivedAt: text(row, "deleted_at"),
|
|
3279
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation))
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
var SEARCH_SOURCES = [
|
|
3283
|
+
"title",
|
|
3284
|
+
"summary",
|
|
3285
|
+
"decision",
|
|
3286
|
+
"action_item",
|
|
3287
|
+
"note",
|
|
3288
|
+
"transcript"
|
|
3289
|
+
];
|
|
3290
|
+
function projectSearchHit(row) {
|
|
3291
|
+
const operation = "meet.projectSearchHit";
|
|
3292
|
+
const source = text(row, "source");
|
|
3293
|
+
return {
|
|
3294
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
3295
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
3296
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
3297
|
+
meetingAt: text(row, "meeting_at"),
|
|
3298
|
+
source: SEARCH_SOURCES.includes(source) ? source : "note",
|
|
3299
|
+
refId: requireText(row, "ref_id", operation),
|
|
3300
|
+
speaker: text(row, "speaker"),
|
|
3301
|
+
snippet: text(row, "snippet") ?? "",
|
|
3302
|
+
at: text(row, "said_at"),
|
|
3303
|
+
rank: num(row, "rank") ?? 0
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
function snippetRuns(snippet) {
|
|
3307
|
+
const runs = [];
|
|
3308
|
+
const pattern = /⟦([^⟧]*)⟧/g;
|
|
3309
|
+
let last = 0;
|
|
3310
|
+
for (const match of snippet.matchAll(pattern)) {
|
|
3311
|
+
const start = match.index ?? 0;
|
|
3312
|
+
if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
|
|
3313
|
+
runs.push({ text: match[1] ?? "", hit: true });
|
|
3314
|
+
last = start + match[0].length;
|
|
3315
|
+
}
|
|
3316
|
+
if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
|
|
3317
|
+
return runs;
|
|
3318
|
+
}
|
|
3167
3319
|
|
|
3168
3320
|
// src/core/session-identity.ts
|
|
3169
3321
|
var RESOLVING = Object.freeze({
|
|
@@ -4794,6 +4946,11 @@ function MeetRuntime(props) {
|
|
|
4794
4946
|
// publish nor subscribe, so the host saying yes has to mint a new one.
|
|
4795
4947
|
onAdmitted: () => {
|
|
4796
4948
|
void engine.rejoin();
|
|
4949
|
+
},
|
|
4950
|
+
// A "no" is ALSO a mint: the server refuses it with its own sentence, which
|
|
4951
|
+
// turns the waiting room into the pre-join screen showing that sentence.
|
|
4952
|
+
onDenied: () => {
|
|
4953
|
+
void engine.rejoin().catch(() => void 0);
|
|
4797
4954
|
}
|
|
4798
4955
|
});
|
|
4799
4956
|
const resolveRoomToken = () => {
|
|
@@ -6919,22 +7076,27 @@ function MeetingOverlays() {
|
|
|
6919
7076
|
const room = useRoom();
|
|
6920
7077
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
6921
7078
|
/* @__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
|
-
|
|
7079
|
+
notice !== null && notice.kind !== "removed" ? (
|
|
7080
|
+
// A host's request ("the host is asking you to unmute"), not an error:
|
|
7081
|
+
// an assertive status, so it is announced at once without being filed
|
|
7082
|
+
// as a failure (design-system error-box census).
|
|
7083
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__notice-bar", role: "status", "aria-live": "assertive", children: [
|
|
7084
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: notice.text }),
|
|
7085
|
+
notice.kind === "ask-unmute" && room.local?.micEnabled !== true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7086
|
+
"button",
|
|
7087
|
+
{
|
|
7088
|
+
type: "button",
|
|
7089
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
7090
|
+
onClick: () => {
|
|
7091
|
+
dismiss();
|
|
7092
|
+
void room.toggleMicrophone();
|
|
7093
|
+
},
|
|
7094
|
+
children: "Unmute"
|
|
7095
|
+
}
|
|
7096
|
+
) : null,
|
|
7097
|
+
/* @__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 }) })
|
|
7098
|
+
] })
|
|
7099
|
+
) : null
|
|
6938
7100
|
] });
|
|
6939
7101
|
}
|
|
6940
7102
|
function ShortcutsSheet({ onClose }) {
|
|
@@ -8867,6 +9029,341 @@ function createMeetingInviteHandler(options) {
|
|
|
8867
9029
|
};
|
|
8868
9030
|
}
|
|
8869
9031
|
|
|
9032
|
+
// src/core/after.ts
|
|
9033
|
+
var import_format4 = require("@ai-matrx/kit/format");
|
|
9034
|
+
function kindOf(person) {
|
|
9035
|
+
if (person.isAgent) return "assistant";
|
|
9036
|
+
if (person.role === "host") return "host";
|
|
9037
|
+
if (person.role === "cohost") return "cohost";
|
|
9038
|
+
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
9039
|
+
return "member";
|
|
9040
|
+
}
|
|
9041
|
+
function ms(iso) {
|
|
9042
|
+
if (iso === null) return null;
|
|
9043
|
+
const at = new Date(iso).getTime();
|
|
9044
|
+
return Number.isNaN(at) ? null : at;
|
|
9045
|
+
}
|
|
9046
|
+
function attendanceReport(meeting, attendees, names = {}) {
|
|
9047
|
+
const end = ms(meeting.endedAt);
|
|
9048
|
+
const rows = attendees.map((person) => {
|
|
9049
|
+
const joined = ms(person.joinedAt);
|
|
9050
|
+
const left = ms(person.leftAt);
|
|
9051
|
+
const until = left ?? end;
|
|
9052
|
+
const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
|
|
9053
|
+
return {
|
|
9054
|
+
id: person.id,
|
|
9055
|
+
identity: person.identity,
|
|
9056
|
+
name: displayNameFor(names, person.identity, person.displayName),
|
|
9057
|
+
userId: person.userId,
|
|
9058
|
+
kind: kindOf(person),
|
|
9059
|
+
joinedAt: person.joinedAt,
|
|
9060
|
+
leftAt: person.leftAt,
|
|
9061
|
+
durationMs: duration,
|
|
9062
|
+
durationToEnd: duration !== null && left === null,
|
|
9063
|
+
admission: person.admissionState
|
|
9064
|
+
};
|
|
9065
|
+
});
|
|
9066
|
+
const rank = {
|
|
9067
|
+
host: 0,
|
|
9068
|
+
cohost: 1,
|
|
9069
|
+
member: 2,
|
|
9070
|
+
"signed-in guest": 3,
|
|
9071
|
+
guest: 4,
|
|
9072
|
+
assistant: 5
|
|
9073
|
+
};
|
|
9074
|
+
return rows.sort((a, b) => {
|
|
9075
|
+
if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
|
|
9076
|
+
return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
|
|
9077
|
+
});
|
|
9078
|
+
}
|
|
9079
|
+
function attendanceTotals(rows) {
|
|
9080
|
+
const humans = rows.filter((row) => row.kind !== "assistant");
|
|
9081
|
+
const attended = humans.filter((row) => row.joinedAt !== null);
|
|
9082
|
+
const timed = attended.filter((row) => row.durationMs !== null);
|
|
9083
|
+
return {
|
|
9084
|
+
people: attended.length,
|
|
9085
|
+
guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
|
|
9086
|
+
neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
|
|
9087
|
+
averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
|
|
9088
|
+
};
|
|
9089
|
+
}
|
|
9090
|
+
function csvCell(value) {
|
|
9091
|
+
if (value === null) return "";
|
|
9092
|
+
const text3 = String(value);
|
|
9093
|
+
return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
|
|
9094
|
+
}
|
|
9095
|
+
var ATTENDANCE_CSV_HEADER = [
|
|
9096
|
+
"Meeting",
|
|
9097
|
+
"Meeting started",
|
|
9098
|
+
"Name",
|
|
9099
|
+
"Attended as",
|
|
9100
|
+
"Joined",
|
|
9101
|
+
"Left",
|
|
9102
|
+
"Minutes in the meeting",
|
|
9103
|
+
"Admission"
|
|
9104
|
+
];
|
|
9105
|
+
function attendanceCsv(reports) {
|
|
9106
|
+
const lines = [ATTENDANCE_CSV_HEADER.join(",")];
|
|
9107
|
+
for (const { meeting, rows } of reports) {
|
|
9108
|
+
for (const row of rows) {
|
|
9109
|
+
lines.push(
|
|
9110
|
+
[
|
|
9111
|
+
meeting.title,
|
|
9112
|
+
meeting.startedAt ?? meeting.scheduledFor,
|
|
9113
|
+
row.name,
|
|
9114
|
+
row.kind,
|
|
9115
|
+
row.joinedAt,
|
|
9116
|
+
row.leftAt,
|
|
9117
|
+
row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
|
|
9118
|
+
row.admission
|
|
9119
|
+
].map(csvCell).join(",")
|
|
9120
|
+
);
|
|
9121
|
+
}
|
|
9122
|
+
}
|
|
9123
|
+
return `${lines.join("\r\n")}\r
|
|
9124
|
+
`;
|
|
9125
|
+
}
|
|
9126
|
+
function recordingOffsetMs(at, recording) {
|
|
9127
|
+
if (recording === null || recording.state !== "available") return null;
|
|
9128
|
+
const start = ms(recording.startedAt);
|
|
9129
|
+
const moment = ms(at);
|
|
9130
|
+
if (start === null || moment === null || moment < start) return null;
|
|
9131
|
+
const end = ms(recording.endedAt);
|
|
9132
|
+
if (end !== null && moment > end) return null;
|
|
9133
|
+
return moment - start;
|
|
9134
|
+
}
|
|
9135
|
+
function clockPosition(offsetMs) {
|
|
9136
|
+
const total = Math.max(0, Math.floor(offsetMs / 1e3));
|
|
9137
|
+
const h = Math.floor(total / 3600);
|
|
9138
|
+
const m = Math.floor(total % 3600 / 60);
|
|
9139
|
+
const s = total % 60;
|
|
9140
|
+
const ss = String(s).padStart(2, "0");
|
|
9141
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
9142
|
+
}
|
|
9143
|
+
function searchTranscript(segments, query, names = {}) {
|
|
9144
|
+
const needle = fold(query.trim());
|
|
9145
|
+
if (needle.length === 0) return [];
|
|
9146
|
+
const out = [];
|
|
9147
|
+
for (const segment of segments) {
|
|
9148
|
+
const hay = fold(segment.text);
|
|
9149
|
+
const ranges = [];
|
|
9150
|
+
let from = 0;
|
|
9151
|
+
for (; ; ) {
|
|
9152
|
+
const at = hay.indexOf(needle, from);
|
|
9153
|
+
if (at < 0) break;
|
|
9154
|
+
ranges.push([at, at + needle.length]);
|
|
9155
|
+
from = at + needle.length;
|
|
9156
|
+
}
|
|
9157
|
+
const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
|
|
9158
|
+
if (ranges.length > 0 || speaker.includes(needle)) {
|
|
9159
|
+
out.push({ segmentId: segment.id, ranges });
|
|
9160
|
+
}
|
|
9161
|
+
}
|
|
9162
|
+
return out;
|
|
9163
|
+
}
|
|
9164
|
+
function fold(value) {
|
|
9165
|
+
return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
|
|
9166
|
+
}
|
|
9167
|
+
function clockOfDay(iso, options) {
|
|
9168
|
+
const at = new Date(iso);
|
|
9169
|
+
if (Number.isNaN(at.getTime())) return "";
|
|
9170
|
+
return at.toLocaleTimeString(options.locale, {
|
|
9171
|
+
hour: "numeric",
|
|
9172
|
+
minute: "2-digit",
|
|
9173
|
+
second: "2-digit",
|
|
9174
|
+
...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
|
|
9175
|
+
});
|
|
9176
|
+
}
|
|
9177
|
+
function vttStamp(offsetMs) {
|
|
9178
|
+
const total = Math.max(0, Math.round(offsetMs));
|
|
9179
|
+
const h = Math.floor(total / 36e5);
|
|
9180
|
+
const m = Math.floor(total % 36e5 / 6e4);
|
|
9181
|
+
const s = Math.floor(total % 6e4 / 1e3);
|
|
9182
|
+
const milli = total % 1e3;
|
|
9183
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
|
|
9184
|
+
}
|
|
9185
|
+
function transcriptDocument(bundle, format, options = {}) {
|
|
9186
|
+
const names = options.names ?? bundle.names;
|
|
9187
|
+
const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
|
|
9188
|
+
const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
|
|
9189
|
+
if (format === "vtt") {
|
|
9190
|
+
const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
|
|
9191
|
+
const zero = ms(zeroIso) ?? 0;
|
|
9192
|
+
const cues = ["WEBVTT", ""];
|
|
9193
|
+
lines.forEach((line, index) => {
|
|
9194
|
+
const start = (ms(line.startedAt) ?? zero) - zero;
|
|
9195
|
+
if (start < 0) return;
|
|
9196
|
+
const next = lines[index + 1];
|
|
9197
|
+
const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
|
|
9198
|
+
const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
|
|
9199
|
+
cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
|
|
9200
|
+
cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
|
|
9201
|
+
cues.push("");
|
|
9202
|
+
});
|
|
9203
|
+
return cues.join("\n");
|
|
9204
|
+
}
|
|
9205
|
+
const out = [];
|
|
9206
|
+
const title = bundle.meeting.title;
|
|
9207
|
+
out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
|
|
9208
|
+
out.push("");
|
|
9209
|
+
let last = null;
|
|
9210
|
+
for (const line of lines) {
|
|
9211
|
+
if (line.identity !== last) {
|
|
9212
|
+
if (last !== null) out.push("");
|
|
9213
|
+
const who = speaker(line);
|
|
9214
|
+
const clock = clockOfDay(line.startedAt, options);
|
|
9215
|
+
out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
|
|
9216
|
+
last = line.identity;
|
|
9217
|
+
}
|
|
9218
|
+
out.push(line.text.trim());
|
|
9219
|
+
}
|
|
9220
|
+
if (lines.length === 0) out.push("No transcript was captured for this meeting.");
|
|
9221
|
+
return `${out.join("\n")}
|
|
9222
|
+
`;
|
|
9223
|
+
}
|
|
9224
|
+
function bullet(note) {
|
|
9225
|
+
const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
|
|
9226
|
+
return `- ${note.text.trim()}${owner}`;
|
|
9227
|
+
}
|
|
9228
|
+
function notesDocument(bundle, options = {}) {
|
|
9229
|
+
const out = [`# ${bundle.meeting.title}`, ""];
|
|
9230
|
+
if (options.when) out.push(`_${options.when}_`, "");
|
|
9231
|
+
const people = bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
|
|
9232
|
+
if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
|
|
9233
|
+
out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
|
|
9234
|
+
out.push("## Decisions", "");
|
|
9235
|
+
out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
|
|
9236
|
+
out.push("## Action items", "");
|
|
9237
|
+
out.push(
|
|
9238
|
+
...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
|
|
9239
|
+
""
|
|
9240
|
+
);
|
|
9241
|
+
if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
|
|
9242
|
+
return out.join("\n");
|
|
9243
|
+
}
|
|
9244
|
+
function meetingLength(meeting) {
|
|
9245
|
+
const start = ms(meeting.startedAt);
|
|
9246
|
+
const end = ms(meeting.endedAt);
|
|
9247
|
+
if (start === null || end === null || end < start) return null;
|
|
9248
|
+
return (0, import_format4.formatDurationMs)(end - start, { style: "long" });
|
|
9249
|
+
}
|
|
9250
|
+
async function loadMeetingChat(api, meetingId, roomToken = null) {
|
|
9251
|
+
const operation = "meet.loadMeetingChat";
|
|
9252
|
+
let response;
|
|
9253
|
+
try {
|
|
9254
|
+
response = await api.request({
|
|
9255
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
9256
|
+
method: "GET",
|
|
9257
|
+
operation,
|
|
9258
|
+
requireAuth: roomToken === null,
|
|
9259
|
+
...roomToken !== null ? { headers: { [ROOM_TOKEN_HEADER]: roomToken } } : {}
|
|
9260
|
+
});
|
|
9261
|
+
} catch (error) {
|
|
9262
|
+
return {
|
|
9263
|
+
messages: [],
|
|
9264
|
+
refusal: {
|
|
9265
|
+
message: `The chat could not be read (${error.message}).`,
|
|
9266
|
+
remedy: error.remedy ?? "Reload the page."
|
|
9267
|
+
}
|
|
9268
|
+
};
|
|
9269
|
+
}
|
|
9270
|
+
if (!response.ok) {
|
|
9271
|
+
let body = null;
|
|
9272
|
+
try {
|
|
9273
|
+
body = await response.json();
|
|
9274
|
+
} catch {
|
|
9275
|
+
body = null;
|
|
9276
|
+
}
|
|
9277
|
+
const said = readServerRefusal(body);
|
|
9278
|
+
return {
|
|
9279
|
+
messages: [],
|
|
9280
|
+
refusal: {
|
|
9281
|
+
message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
|
|
9282
|
+
remedy: said?.remedy || "Reload the page."
|
|
9283
|
+
}
|
|
9284
|
+
};
|
|
9285
|
+
}
|
|
9286
|
+
const payload = await response.json();
|
|
9287
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
9288
|
+
return {
|
|
9289
|
+
messages: rows.map(
|
|
9290
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
9291
|
+
).filter((message) => message !== null),
|
|
9292
|
+
refusal: null
|
|
9293
|
+
};
|
|
9294
|
+
}
|
|
9295
|
+
async function refusalOf(response, operation) {
|
|
9296
|
+
let body = null;
|
|
9297
|
+
try {
|
|
9298
|
+
body = await response.json();
|
|
9299
|
+
} catch {
|
|
9300
|
+
body = null;
|
|
9301
|
+
}
|
|
9302
|
+
const said = readServerRefusal(body);
|
|
9303
|
+
return new MeetError(
|
|
9304
|
+
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
9305
|
+
`${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
|
|
9306
|
+
said?.remedy || "Nothing was sent. Try again."
|
|
9307
|
+
);
|
|
9308
|
+
}
|
|
9309
|
+
function asRecipient(row) {
|
|
9310
|
+
const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
|
|
9311
|
+
return {
|
|
9312
|
+
key: String(row["key"] ?? ""),
|
|
9313
|
+
name: String(row["name"] ?? ""),
|
|
9314
|
+
email: typeof row["email"] === "string" ? row["email"] : null,
|
|
9315
|
+
userId: typeof row["user_id"] === "string" ? row["user_id"] : null,
|
|
9316
|
+
sources: sources.filter((s) => s === "invitee" || s === "attendee"),
|
|
9317
|
+
reachable: row["reachable"] !== false
|
|
9318
|
+
};
|
|
9319
|
+
}
|
|
9320
|
+
async function loadFollowUpDraft(api, meetingId) {
|
|
9321
|
+
const operation = "meet.loadFollowUpDraft";
|
|
9322
|
+
const response = await api.request({
|
|
9323
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
9324
|
+
method: "GET",
|
|
9325
|
+
requireAuth: true,
|
|
9326
|
+
operation
|
|
9327
|
+
});
|
|
9328
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
9329
|
+
const body = await response.json();
|
|
9330
|
+
const last = body["last_sent"];
|
|
9331
|
+
return {
|
|
9332
|
+
subject: String(body["subject"] ?? ""),
|
|
9333
|
+
body: String(body["body"] ?? ""),
|
|
9334
|
+
recipients: (Array.isArray(body["recipients"]) ? body["recipients"] : []).map(
|
|
9335
|
+
(row) => asRecipient(row)
|
|
9336
|
+
),
|
|
9337
|
+
wrapUpWritten: body["wrap_up_written"] === true,
|
|
9338
|
+
lastSent: last && typeof last["at"] === "string" ? { at: last["at"], count: typeof last["count"] === "number" ? last["count"] : 0 } : null
|
|
9339
|
+
};
|
|
9340
|
+
}
|
|
9341
|
+
async function sendFollowUp(api, meetingId, input) {
|
|
9342
|
+
const operation = "meet.sendFollowUp";
|
|
9343
|
+
const response = await api.request({
|
|
9344
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
9345
|
+
method: "POST",
|
|
9346
|
+
requireAuth: true,
|
|
9347
|
+
operation,
|
|
9348
|
+
body: {
|
|
9349
|
+
subject: input.subject,
|
|
9350
|
+
body: input.body,
|
|
9351
|
+
recipient_keys: [...input.recipientKeys],
|
|
9352
|
+
dry_run: input.dryRun === true
|
|
9353
|
+
}
|
|
9354
|
+
});
|
|
9355
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
9356
|
+
const body = await response.json();
|
|
9357
|
+
return {
|
|
9358
|
+
dryRun: body["dry_run"] === true,
|
|
9359
|
+
queued: Array.isArray(body["queued"]) ? body["queued"] : [],
|
|
9360
|
+
skipped: Array.isArray(body["skipped"]) ? body["skipped"].map((row) => ({
|
|
9361
|
+
key: String(row["key"] ?? ""),
|
|
9362
|
+
reason: String(row["reason"] ?? "")
|
|
9363
|
+
})) : []
|
|
9364
|
+
};
|
|
9365
|
+
}
|
|
9366
|
+
|
|
8870
9367
|
// src/core/management.ts
|
|
8871
9368
|
async function refusal(response, operation) {
|
|
8872
9369
|
let body = null;
|