@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/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,10 @@ __export(src_exports, {
|
|
|
79
81
|
asParticipantIdentity: () => asParticipantIdentity,
|
|
80
82
|
asRoomName: () => asRoomName,
|
|
81
83
|
asUserId: () => asUserId,
|
|
84
|
+
attendanceCsv: () => attendanceCsv,
|
|
85
|
+
attendanceReport: () => attendanceReport,
|
|
86
|
+
attendanceTotals: () => attendanceTotals,
|
|
87
|
+
attendeeKey: () => attendeeKey,
|
|
82
88
|
callInviteAction: () => callInviteAction,
|
|
83
89
|
callInviteText: () => callInviteText,
|
|
84
90
|
callStateLabel: () => callStateLabel,
|
|
@@ -87,6 +93,7 @@ __export(src_exports, {
|
|
|
87
93
|
canTransition: () => canTransition,
|
|
88
94
|
captionSegment: () => captionSegment,
|
|
89
95
|
changesPatch: () => changesPatch,
|
|
96
|
+
clockPosition: () => clockPosition,
|
|
90
97
|
computeStage: () => computeStage,
|
|
91
98
|
countLabel: () => countLabel,
|
|
92
99
|
createCallCenter: () => createCallCenter,
|
|
@@ -127,11 +134,14 @@ __export(src_exports, {
|
|
|
127
134
|
learnName: () => learnName,
|
|
128
135
|
learnNames: () => learnNames,
|
|
129
136
|
listenOnlyNotice: () => listenOnlyNotice,
|
|
137
|
+
loadFollowUpDraft: () => loadFollowUpDraft,
|
|
130
138
|
loadGuestMeetingRecord: () => loadGuestMeetingRecord,
|
|
139
|
+
loadMeetingChat: () => loadMeetingChat,
|
|
131
140
|
loadMeetingRecord: () => loadMeetingRecord,
|
|
132
141
|
mapConnectionState: () => mapConnectionState,
|
|
133
142
|
mapQuality: () => mapQuality,
|
|
134
143
|
meetingInviteAction: () => meetingInviteAction,
|
|
144
|
+
meetingLength: () => meetingLength,
|
|
135
145
|
meetingLink: () => meetingLink,
|
|
136
146
|
meetingPath: () => meetingPath,
|
|
137
147
|
meetingSlug: () => meetingSlug,
|
|
@@ -146,16 +156,19 @@ __export(src_exports, {
|
|
|
146
156
|
noteTakerLabel: () => noteTakerLabel,
|
|
147
157
|
noteTakerParticipant: () => noteTakerParticipant,
|
|
148
158
|
noteTakerStatus: () => noteTakerStatus,
|
|
159
|
+
notesDocument: () => notesDocument,
|
|
149
160
|
orderParticipants: () => orderParticipants,
|
|
150
161
|
parseRoomToken: () => parseRoomToken,
|
|
151
162
|
participantSummary: () => participantSummary,
|
|
152
163
|
playableRecordingFileId: () => playableRecordingFileId,
|
|
153
164
|
projectInvitee: () => projectInvitee,
|
|
165
|
+
projectLibraryRecording: () => projectLibraryRecording,
|
|
154
166
|
projectNote: () => projectNote,
|
|
155
167
|
projectOccurrence: () => projectOccurrence,
|
|
156
168
|
projectOverride: () => projectOverride,
|
|
157
169
|
projectParticipant: () => projectParticipant,
|
|
158
170
|
projectRsvpInvitation: () => projectRsvpInvitation,
|
|
171
|
+
projectSearchHit: () => projectSearchHit,
|
|
159
172
|
projectSegment: () => projectSegment,
|
|
160
173
|
projectUpcoming: () => projectUpcoming,
|
|
161
174
|
pushMeetingToGoogleCalendar: () => pushMeetingToGoogleCalendar,
|
|
@@ -163,14 +176,22 @@ __export(src_exports, {
|
|
|
163
176
|
readableTranscript: () => readableTranscript,
|
|
164
177
|
recordingLabel: () => recordingLabel,
|
|
165
178
|
recordingNotice: () => recordingNotice,
|
|
179
|
+
recordingOffsetMs: () => recordingOffsetMs,
|
|
166
180
|
roleOf: () => roleOf,
|
|
167
181
|
roomTopic: () => roomTopic,
|
|
182
|
+
searchTranscript: () => searchTranscript,
|
|
183
|
+
sendFollowUp: () => sendFollowUp,
|
|
168
184
|
setGuestRecordAccess: () => setGuestRecordAccess,
|
|
169
185
|
settingsPatch: () => settingsPatch,
|
|
170
186
|
snapshotParticipant: () => snapshotParticipant,
|
|
187
|
+
snippetRuns: () => snippetRuns,
|
|
188
|
+
summarizeMeetingRecordRestrictions: () => summarizeMeetingRecordRestrictions,
|
|
189
|
+
transcriptDocument: () => transcriptDocument,
|
|
171
190
|
unavailableFrom: () => unavailableFrom,
|
|
191
|
+
uniqueAttendees: () => uniqueAttendees,
|
|
172
192
|
userTopic: () => userTopic,
|
|
173
|
-
wasRefused: () => wasRefused
|
|
193
|
+
wasRefused: () => wasRefused,
|
|
194
|
+
wrapUpStatus: () => wrapUpStatus
|
|
174
195
|
});
|
|
175
196
|
module.exports = __toCommonJS(src_exports);
|
|
176
197
|
|
|
@@ -368,6 +389,12 @@ var MEET_ROUTES = {
|
|
|
368
389
|
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
369
390
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
370
391
|
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
392
|
+
/**
|
|
393
|
+
* Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,
|
|
394
|
+
* body from the wrap-up, who it can go to); `POST` = send what the host
|
|
395
|
+
* reviewed (`dry_run` renders and queues nothing). Never sent automatically.
|
|
396
|
+
*/
|
|
397
|
+
meetingFollowUp: "/api/v1/meet/meetings/{meeting_id}/follow-up",
|
|
371
398
|
/**
|
|
372
399
|
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
373
400
|
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
@@ -497,10 +524,11 @@ function createTokenClient(options) {
|
|
|
497
524
|
throw error;
|
|
498
525
|
}
|
|
499
526
|
if (response.status === 401 || response.status === 403) {
|
|
527
|
+
const said = await serverSentence(response);
|
|
500
528
|
throw new MeetError(
|
|
501
529
|
"forbidden",
|
|
502
|
-
|
|
503
|
-
"
|
|
530
|
+
said?.message ?? "You can't join this meeting.",
|
|
531
|
+
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again."
|
|
504
532
|
);
|
|
505
533
|
}
|
|
506
534
|
if (response.status === 404) {
|
|
@@ -511,12 +539,7 @@ function createTokenClient(options) {
|
|
|
511
539
|
);
|
|
512
540
|
}
|
|
513
541
|
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
|
-
}
|
|
542
|
+
const reason = (await serverSentence(response))?.message ?? "This meeting was cancelled.";
|
|
520
543
|
throw new MeetError(
|
|
521
544
|
"cancelled",
|
|
522
545
|
reason,
|
|
@@ -524,10 +547,11 @@ function createTokenClient(options) {
|
|
|
524
547
|
);
|
|
525
548
|
}
|
|
526
549
|
if (response.status === 423) {
|
|
550
|
+
const said = await serverSentence(response);
|
|
527
551
|
throw new MeetError(
|
|
528
552
|
"room-locked",
|
|
529
|
-
"
|
|
530
|
-
"The host locked the room. Ask them to unlock
|
|
553
|
+
said?.message ?? "The meeting is locked.",
|
|
554
|
+
said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again."
|
|
531
555
|
);
|
|
532
556
|
}
|
|
533
557
|
if (response.status === 503) {
|
|
@@ -598,6 +622,19 @@ function placeholder(roomName) {
|
|
|
598
622
|
lobby: false
|
|
599
623
|
};
|
|
600
624
|
}
|
|
625
|
+
async function serverSentence(response) {
|
|
626
|
+
try {
|
|
627
|
+
const body = await response.json();
|
|
628
|
+
const source = body !== null && typeof body.detail === "object" && body.detail !== null ? body.detail : body;
|
|
629
|
+
if (source === null) return null;
|
|
630
|
+
const message = typeof source.user_message === "string" && source.user_message.trim() !== "" ? source.user_message : typeof source.message === "string" && source.message.trim() !== "" ? source.message : null;
|
|
631
|
+
if (message === null) return null;
|
|
632
|
+
const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
|
|
633
|
+
return { message, remedy };
|
|
634
|
+
} catch {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
601
638
|
|
|
602
639
|
// src/core/ai.ts
|
|
603
640
|
var MEET_ANSWER_EVENTS = {
|
|
@@ -642,6 +679,29 @@ function readText(data) {
|
|
|
642
679
|
}
|
|
643
680
|
return "";
|
|
644
681
|
}
|
|
682
|
+
var SERVER_FAILED_SENTENCE = "The server hit an unexpected problem and nothing was changed.";
|
|
683
|
+
function isRawServerText(value) {
|
|
684
|
+
const text3 = value.trim();
|
|
685
|
+
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);
|
|
686
|
+
}
|
|
687
|
+
async function refusalFromResponse(response, operation, fallbackRemedy) {
|
|
688
|
+
let body = null;
|
|
689
|
+
try {
|
|
690
|
+
body = await response.json();
|
|
691
|
+
} catch {
|
|
692
|
+
body = null;
|
|
693
|
+
}
|
|
694
|
+
const said = readServerRefusal(body);
|
|
695
|
+
const code = response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport";
|
|
696
|
+
const error = new MeetError(
|
|
697
|
+
code,
|
|
698
|
+
said?.reason ?? SERVER_FAILED_SENTENCE,
|
|
699
|
+
said !== null && said.remedy.length > 0 ? said.remedy : fallbackRemedy
|
|
700
|
+
);
|
|
701
|
+
error.operation = operation;
|
|
702
|
+
error.status = response.status;
|
|
703
|
+
return error;
|
|
704
|
+
}
|
|
645
705
|
function readServerRefusal(body) {
|
|
646
706
|
if (typeof body === "string") {
|
|
647
707
|
return body.trim().length === 0 ? null : { reason: body.trim(), remedy: "" };
|
|
@@ -660,7 +720,8 @@ function readServerRefusal(body) {
|
|
|
660
720
|
return messages.length === 0 ? null : { reason: messages.join("; "), remedy: "" };
|
|
661
721
|
}
|
|
662
722
|
if (typeof detail === "object" && detail !== null) return readServerRefusal(detail);
|
|
663
|
-
const
|
|
723
|
+
const humanOrNull = (value) => typeof value === "string" && value.trim().length > 0 && !isRawServerText(value) ? value.trim() : null;
|
|
724
|
+
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);
|
|
664
725
|
if (reason === null) return null;
|
|
665
726
|
const remedy = typeof row["remedy"] === "string" ? row["remedy"] : "";
|
|
666
727
|
if (remedy.length > 0) return { reason, remedy };
|
|
@@ -1066,7 +1127,7 @@ var DEFAULT_RING_TIMEOUT_MS = 45e3;
|
|
|
1066
1127
|
function createCallCenter(options) {
|
|
1067
1128
|
const { manager, repository, identity } = options;
|
|
1068
1129
|
const timers = options.timers ?? {
|
|
1069
|
-
setTimeout: (run,
|
|
1130
|
+
setTimeout: (run, ms2) => setTimeout(run, ms2),
|
|
1070
1131
|
clearTimeout: (handle) => clearTimeout(handle),
|
|
1071
1132
|
now: () => Date.now()
|
|
1072
1133
|
};
|
|
@@ -1350,7 +1411,16 @@ var RPCS = {
|
|
|
1350
1411
|
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
1351
1412
|
meetingOccurrences: "meet_meeting_occurrences",
|
|
1352
1413
|
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
1353
|
-
occurrencesBetween: "meet_occurrences_between"
|
|
1414
|
+
occurrencesBetween: "meet_occurrences_between",
|
|
1415
|
+
/*
|
|
1416
|
+
* ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
|
|
1417
|
+
* scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
|
|
1418
|
+
* ceiling, never the view.
|
|
1419
|
+
*/
|
|
1420
|
+
/** The recordings library. */
|
|
1421
|
+
myRecordings: "meet_my_recordings",
|
|
1422
|
+
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
1423
|
+
search: "meet_search"
|
|
1354
1424
|
};
|
|
1355
1425
|
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
1356
1426
|
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 +2125,117 @@ function createMeetRepository(options) {
|
|
|
2055
2125
|
);
|
|
2056
2126
|
},
|
|
2057
2127
|
async recordings(meetingId) {
|
|
2058
|
-
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: false });
|
|
2128
|
+
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("started_at", { ascending: false });
|
|
2059
2129
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.recordings");
|
|
2060
2130
|
return (response.data ?? []).map(projectRecording);
|
|
2131
|
+
},
|
|
2132
|
+
async myRecordings({ includeArchived = false, limit = 200 } = {}) {
|
|
2133
|
+
const rows = await guard(
|
|
2134
|
+
"meet.myRecordings",
|
|
2135
|
+
() => db.rpc(RPCS.myRecordings, { p_include_archived: includeArchived, p_limit: limit })
|
|
2136
|
+
);
|
|
2137
|
+
return rows.map(projectLibraryRecording);
|
|
2138
|
+
},
|
|
2139
|
+
async searchMeetings(query, limit = 60) {
|
|
2140
|
+
if (query.trim().length === 0) return [];
|
|
2141
|
+
const rows = await guard(
|
|
2142
|
+
"meet.searchMeetings",
|
|
2143
|
+
() => db.rpc(RPCS.search, { p_query: query, p_limit: limit })
|
|
2144
|
+
);
|
|
2145
|
+
return rows.map(projectSearchHit);
|
|
2146
|
+
},
|
|
2147
|
+
async renameRecording(recordingId, title) {
|
|
2148
|
+
const clean = title === null ? null : title.trim().slice(0, 200) || null;
|
|
2149
|
+
const response = await db.from(TABLES.recordings).update({ title: clean }).eq("id", recordingId).select("id");
|
|
2150
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.renameRecording");
|
|
2151
|
+
if ((response.data ?? []).length === 0) {
|
|
2152
|
+
throw normalizeMeetError(
|
|
2153
|
+
{ message: "permission denied: only the meeting's host or an editor can rename it", code: "42501" },
|
|
2154
|
+
"meet.renameRecording"
|
|
2155
|
+
);
|
|
2156
|
+
}
|
|
2157
|
+
},
|
|
2158
|
+
async setRecordingArchived(recordingId, archived) {
|
|
2159
|
+
const response = await db.from(TABLES.recordings).update({ deleted_at: archived ? (/* @__PURE__ */ new Date()).toISOString() : null }).eq("id", recordingId).select("id");
|
|
2160
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.setRecordingArchived");
|
|
2161
|
+
if ((response.data ?? []).length === 0) {
|
|
2162
|
+
throw normalizeMeetError(
|
|
2163
|
+
{ message: "permission denied: only the meeting's host or an editor can archive it", code: "42501" },
|
|
2164
|
+
"meet.setRecordingArchived"
|
|
2165
|
+
);
|
|
2166
|
+
}
|
|
2061
2167
|
}
|
|
2062
2168
|
};
|
|
2063
2169
|
}
|
|
2170
|
+
var LIBRARY_STATES = [
|
|
2171
|
+
"idle",
|
|
2172
|
+
"starting",
|
|
2173
|
+
"recording",
|
|
2174
|
+
"stopping",
|
|
2175
|
+
"processing",
|
|
2176
|
+
"available",
|
|
2177
|
+
"expired",
|
|
2178
|
+
"failed"
|
|
2179
|
+
];
|
|
2180
|
+
function projectLibraryRecording(row) {
|
|
2181
|
+
const operation = "meet.projectLibraryRecording";
|
|
2182
|
+
const state = text(row, "state");
|
|
2183
|
+
const role = text(row, "my_role");
|
|
2184
|
+
return {
|
|
2185
|
+
id: requireText(row, "recording_id", operation),
|
|
2186
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2187
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2188
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2189
|
+
role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
|
|
2190
|
+
state: LIBRARY_STATES.includes(state) ? state : "failed",
|
|
2191
|
+
title: text(row, "recording_title"),
|
|
2192
|
+
fileId: text(row, "file_id"),
|
|
2193
|
+
startedAt: text(row, "started_at"),
|
|
2194
|
+
endedAt: text(row, "ended_at"),
|
|
2195
|
+
durationMs: num(row, "duration_ms"),
|
|
2196
|
+
sizeBytes: num(row, "size_bytes"),
|
|
2197
|
+
failureReason: text(row, "failure_reason"),
|
|
2198
|
+
archivedAt: text(row, "deleted_at"),
|
|
2199
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation))
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
var SEARCH_SOURCES = [
|
|
2203
|
+
"title",
|
|
2204
|
+
"summary",
|
|
2205
|
+
"decision",
|
|
2206
|
+
"action_item",
|
|
2207
|
+
"note",
|
|
2208
|
+
"transcript"
|
|
2209
|
+
];
|
|
2210
|
+
function projectSearchHit(row) {
|
|
2211
|
+
const operation = "meet.projectSearchHit";
|
|
2212
|
+
const source = text(row, "source");
|
|
2213
|
+
return {
|
|
2214
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2215
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2216
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2217
|
+
meetingAt: text(row, "meeting_at"),
|
|
2218
|
+
source: SEARCH_SOURCES.includes(source) ? source : "note",
|
|
2219
|
+
refId: requireText(row, "ref_id", operation),
|
|
2220
|
+
speaker: text(row, "speaker"),
|
|
2221
|
+
snippet: text(row, "snippet") ?? "",
|
|
2222
|
+
at: text(row, "said_at"),
|
|
2223
|
+
rank: num(row, "rank") ?? 0
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
function snippetRuns(snippet) {
|
|
2227
|
+
const runs = [];
|
|
2228
|
+
const pattern = /⟦([^⟧]*)⟧/g;
|
|
2229
|
+
let last = 0;
|
|
2230
|
+
for (const match of snippet.matchAll(pattern)) {
|
|
2231
|
+
const start = match.index ?? 0;
|
|
2232
|
+
if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
|
|
2233
|
+
runs.push({ text: match[1] ?? "", hit: true });
|
|
2234
|
+
last = start + match[0].length;
|
|
2235
|
+
}
|
|
2236
|
+
if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
|
|
2237
|
+
return runs;
|
|
2238
|
+
}
|
|
2064
2239
|
|
|
2065
2240
|
// src/core/room-events.ts
|
|
2066
2241
|
var RECORDING_STATES = [
|
|
@@ -3229,6 +3404,7 @@ var EMPTY = {
|
|
|
3229
3404
|
spotlight: null,
|
|
3230
3405
|
moderationNotice: null,
|
|
3231
3406
|
actionNotice: null,
|
|
3407
|
+
privateChatRequest: null,
|
|
3232
3408
|
effects: DEFAULT_EFFECTS,
|
|
3233
3409
|
transcript: [],
|
|
3234
3410
|
notes: [],
|
|
@@ -3494,7 +3670,9 @@ function createMeetingSession(options) {
|
|
|
3494
3670
|
return identity.userId;
|
|
3495
3671
|
}
|
|
3496
3672
|
function knockIdentity() {
|
|
3497
|
-
return asParticipantIdentity(
|
|
3673
|
+
return asParticipantIdentity(
|
|
3674
|
+
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
3675
|
+
);
|
|
3498
3676
|
}
|
|
3499
3677
|
function requireMeeting(operation) {
|
|
3500
3678
|
const meeting = store.snapshot().meeting;
|
|
@@ -3529,8 +3707,9 @@ function createMeetingSession(options) {
|
|
|
3529
3707
|
verdict.admitted ? "admitted" : "denied",
|
|
3530
3708
|
verdict.by === null ? null : verdict.by
|
|
3531
3709
|
);
|
|
3532
|
-
if (
|
|
3533
|
-
options.onAdmitted?.();
|
|
3710
|
+
if (decided === knockIdentity()) {
|
|
3711
|
+
if (verdict.admitted) options.onAdmitted?.();
|
|
3712
|
+
else options.onDenied?.();
|
|
3534
3713
|
}
|
|
3535
3714
|
}
|
|
3536
3715
|
},
|
|
@@ -3902,19 +4081,413 @@ function fileDownloadPath(fileId) {
|
|
|
3902
4081
|
return `/files/${encodeURIComponent(fileId)}/download?inline=1`;
|
|
3903
4082
|
}
|
|
3904
4083
|
|
|
3905
|
-
// src/core/
|
|
3906
|
-
|
|
3907
|
-
|
|
4084
|
+
// src/core/after.ts
|
|
4085
|
+
var import_format = require("@ai-matrx/kit/format");
|
|
4086
|
+
function kindOf(person) {
|
|
4087
|
+
if (person.isAgent) return "assistant";
|
|
4088
|
+
if (person.role === "host") return "host";
|
|
4089
|
+
if (person.role === "cohost") return "cohost";
|
|
4090
|
+
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
4091
|
+
return "member";
|
|
4092
|
+
}
|
|
4093
|
+
function ms(iso) {
|
|
4094
|
+
if (iso === null) return null;
|
|
4095
|
+
const at = new Date(iso).getTime();
|
|
4096
|
+
return Number.isNaN(at) ? null : at;
|
|
4097
|
+
}
|
|
4098
|
+
function attendeeKey(person, names = {}) {
|
|
4099
|
+
if (person.userId !== null) return `user:${person.userId}`;
|
|
4100
|
+
if (person.isAgent) return `agent:${person.identity}`;
|
|
4101
|
+
const name = displayNameFor(names, person.identity, person.displayName).trim().toLowerCase();
|
|
4102
|
+
return name.length > 0 ? `guest-name:${name}` : `identity:${person.identity}`;
|
|
4103
|
+
}
|
|
4104
|
+
function uniqueAttendees(attendees, names = {}) {
|
|
4105
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
4106
|
+
for (const person of attendees) {
|
|
4107
|
+
const key = attendeeKey(person, names);
|
|
4108
|
+
const kept = byKey.get(key);
|
|
4109
|
+
if (kept === void 0) {
|
|
4110
|
+
byKey.set(key, person);
|
|
4111
|
+
continue;
|
|
4112
|
+
}
|
|
4113
|
+
const keptJoin = ms(kept.joinedAt);
|
|
4114
|
+
const join = ms(person.joinedAt);
|
|
4115
|
+
if (join !== null && (keptJoin === null || join < keptJoin)) byKey.set(key, person);
|
|
4116
|
+
}
|
|
4117
|
+
return [...byKey.values()];
|
|
4118
|
+
}
|
|
4119
|
+
var KIND_RANK = {
|
|
4120
|
+
host: 0,
|
|
4121
|
+
cohost: 1,
|
|
4122
|
+
member: 2,
|
|
4123
|
+
"signed-in guest": 3,
|
|
4124
|
+
guest: 4,
|
|
4125
|
+
assistant: 5
|
|
4126
|
+
};
|
|
4127
|
+
function coveredMs(intervals) {
|
|
4128
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
4129
|
+
let total = 0;
|
|
4130
|
+
let from = -Infinity;
|
|
4131
|
+
let to = -Infinity;
|
|
4132
|
+
for (const [start, end] of sorted) {
|
|
4133
|
+
if (start > to) {
|
|
4134
|
+
if (to > from) total += to - from;
|
|
4135
|
+
from = start;
|
|
4136
|
+
to = end;
|
|
4137
|
+
} else if (end > to) {
|
|
4138
|
+
to = end;
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
if (to > from) total += to - from;
|
|
4142
|
+
return total;
|
|
4143
|
+
}
|
|
4144
|
+
function attendanceReport(meeting, attendees, names = {}) {
|
|
4145
|
+
const end = ms(meeting.endedAt);
|
|
4146
|
+
const perJoin = attendees.map((person) => {
|
|
4147
|
+
const joined = ms(person.joinedAt);
|
|
4148
|
+
const left = ms(person.leftAt);
|
|
4149
|
+
const until = left ?? end;
|
|
4150
|
+
const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
|
|
4151
|
+
return {
|
|
4152
|
+
id: person.id,
|
|
4153
|
+
identity: person.identity,
|
|
4154
|
+
name: displayNameFor(names, person.identity, person.displayName),
|
|
4155
|
+
userId: person.userId,
|
|
4156
|
+
kind: kindOf(person),
|
|
4157
|
+
joinedAt: person.joinedAt,
|
|
4158
|
+
leftAt: person.leftAt,
|
|
4159
|
+
durationMs: duration,
|
|
4160
|
+
durationToEnd: duration !== null && left === null,
|
|
4161
|
+
admission: person.admissionState,
|
|
4162
|
+
joins: 1
|
|
4163
|
+
};
|
|
4164
|
+
});
|
|
4165
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4166
|
+
attendees.forEach((person, index) => {
|
|
4167
|
+
const key = attendeeKey(person, names);
|
|
4168
|
+
const group = groups.get(key) ?? { rows: [], people: [] };
|
|
4169
|
+
group.rows.push(perJoin[index]);
|
|
4170
|
+
group.people.push(person);
|
|
4171
|
+
groups.set(key, group);
|
|
4172
|
+
});
|
|
4173
|
+
const rows = [...groups.values()].map(({ rows: joins }) => {
|
|
4174
|
+
if (joins.length === 1) return joins[0];
|
|
4175
|
+
const came = joins.filter((row) => row.joinedAt !== null);
|
|
4176
|
+
const first = [...came].sort((a, b) => (ms(a.joinedAt) ?? 0) - (ms(b.joinedAt) ?? 0))[0] ?? joins[0];
|
|
4177
|
+
const kind = joins.reduce(
|
|
4178
|
+
(best, row) => KIND_RANK[row.kind] < KIND_RANK[best] ? row.kind : best,
|
|
4179
|
+
first.kind
|
|
4180
|
+
);
|
|
4181
|
+
const open = came.some((row) => row.leftAt === null);
|
|
4182
|
+
const lastLeave = came.reduce((latest, row) => {
|
|
4183
|
+
const left = ms(row.leftAt);
|
|
4184
|
+
return left !== null && (latest === null || left > latest) ? left : latest;
|
|
4185
|
+
}, null);
|
|
4186
|
+
const intervals = came.flatMap((row) => {
|
|
4187
|
+
const joined = ms(row.joinedAt);
|
|
4188
|
+
return joined !== null && row.durationMs !== null ? [[joined, joined + row.durationMs]] : [];
|
|
4189
|
+
});
|
|
4190
|
+
return {
|
|
4191
|
+
...first,
|
|
4192
|
+
kind,
|
|
4193
|
+
leftAt: open ? null : lastLeave === null ? null : new Date(lastLeave).toISOString(),
|
|
4194
|
+
durationMs: intervals.length === 0 ? null : coveredMs(intervals),
|
|
4195
|
+
durationToEnd: came.some((row) => row.durationToEnd),
|
|
4196
|
+
joins: came.length === 0 ? joins.length : came.length
|
|
4197
|
+
};
|
|
4198
|
+
});
|
|
4199
|
+
const rank = KIND_RANK;
|
|
4200
|
+
return rows.sort((a, b) => {
|
|
4201
|
+
if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
|
|
4202
|
+
return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
|
|
4203
|
+
});
|
|
4204
|
+
}
|
|
4205
|
+
function attendanceTotals(rows) {
|
|
4206
|
+
const humans = rows.filter((row) => row.kind !== "assistant");
|
|
4207
|
+
const attended = humans.filter((row) => row.joinedAt !== null);
|
|
4208
|
+
const timed = attended.filter((row) => row.durationMs !== null);
|
|
4209
|
+
return {
|
|
4210
|
+
people: attended.length,
|
|
4211
|
+
guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
|
|
4212
|
+
neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
|
|
4213
|
+
averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
|
|
4214
|
+
};
|
|
4215
|
+
}
|
|
4216
|
+
function csvCell(value) {
|
|
4217
|
+
if (value === null) return "";
|
|
4218
|
+
const text3 = String(value);
|
|
4219
|
+
return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
|
|
4220
|
+
}
|
|
4221
|
+
var ATTENDANCE_CSV_HEADER = [
|
|
4222
|
+
"Meeting",
|
|
4223
|
+
"Meeting started",
|
|
4224
|
+
"Name",
|
|
4225
|
+
"Attended as",
|
|
4226
|
+
"Joined",
|
|
4227
|
+
"Left",
|
|
4228
|
+
"Minutes in the meeting",
|
|
4229
|
+
"Admission"
|
|
4230
|
+
];
|
|
4231
|
+
function attendanceCsv(reports) {
|
|
4232
|
+
const lines = [ATTENDANCE_CSV_HEADER.join(",")];
|
|
4233
|
+
for (const { meeting, rows } of reports) {
|
|
4234
|
+
for (const row of rows) {
|
|
4235
|
+
lines.push(
|
|
4236
|
+
[
|
|
4237
|
+
meeting.title,
|
|
4238
|
+
meeting.startedAt ?? meeting.scheduledFor,
|
|
4239
|
+
row.name,
|
|
4240
|
+
row.kind,
|
|
4241
|
+
row.joinedAt,
|
|
4242
|
+
row.leftAt,
|
|
4243
|
+
row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
|
|
4244
|
+
row.admission
|
|
4245
|
+
].map(csvCell).join(",")
|
|
4246
|
+
);
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
return `${lines.join("\r\n")}\r
|
|
4250
|
+
`;
|
|
4251
|
+
}
|
|
4252
|
+
function recordingOffsetMs(at, recording) {
|
|
4253
|
+
if (recording === null || recording.state !== "available") return null;
|
|
4254
|
+
const start = ms(recording.startedAt);
|
|
4255
|
+
const moment = ms(at);
|
|
4256
|
+
if (start === null || moment === null || moment < start) return null;
|
|
4257
|
+
const end = ms(recording.endedAt);
|
|
4258
|
+
if (end !== null && moment > end) return null;
|
|
4259
|
+
return moment - start;
|
|
4260
|
+
}
|
|
4261
|
+
function clockPosition(offsetMs) {
|
|
4262
|
+
const total = Math.max(0, Math.floor(offsetMs / 1e3));
|
|
4263
|
+
const h = Math.floor(total / 3600);
|
|
4264
|
+
const m = Math.floor(total % 3600 / 60);
|
|
4265
|
+
const s = total % 60;
|
|
4266
|
+
const ss = String(s).padStart(2, "0");
|
|
4267
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
4268
|
+
}
|
|
4269
|
+
function searchTranscript(segments, query, names = {}) {
|
|
4270
|
+
const needle = fold(query.trim());
|
|
4271
|
+
if (needle.length === 0) return [];
|
|
4272
|
+
const out = [];
|
|
4273
|
+
for (const segment of segments) {
|
|
4274
|
+
const hay = fold(segment.text);
|
|
4275
|
+
const ranges = [];
|
|
4276
|
+
let from = 0;
|
|
4277
|
+
for (; ; ) {
|
|
4278
|
+
const at = hay.indexOf(needle, from);
|
|
4279
|
+
if (at < 0) break;
|
|
4280
|
+
ranges.push([at, at + needle.length]);
|
|
4281
|
+
from = at + needle.length;
|
|
4282
|
+
}
|
|
4283
|
+
const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
|
|
4284
|
+
if (ranges.length > 0 || speaker.includes(needle)) {
|
|
4285
|
+
out.push({ segmentId: segment.id, ranges });
|
|
4286
|
+
}
|
|
4287
|
+
}
|
|
4288
|
+
return out;
|
|
4289
|
+
}
|
|
4290
|
+
function fold(value) {
|
|
4291
|
+
return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
|
|
4292
|
+
}
|
|
4293
|
+
function clockOfDay(iso, options) {
|
|
4294
|
+
const at = new Date(iso);
|
|
4295
|
+
if (Number.isNaN(at.getTime())) return "";
|
|
4296
|
+
return at.toLocaleTimeString(options.locale, {
|
|
4297
|
+
hour: "numeric",
|
|
4298
|
+
minute: "2-digit",
|
|
4299
|
+
second: "2-digit",
|
|
4300
|
+
...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
|
|
4301
|
+
});
|
|
4302
|
+
}
|
|
4303
|
+
function vttStamp(offsetMs) {
|
|
4304
|
+
const total = Math.max(0, Math.round(offsetMs));
|
|
4305
|
+
const h = Math.floor(total / 36e5);
|
|
4306
|
+
const m = Math.floor(total % 36e5 / 6e4);
|
|
4307
|
+
const s = Math.floor(total % 6e4 / 1e3);
|
|
4308
|
+
const milli = total % 1e3;
|
|
4309
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
|
|
4310
|
+
}
|
|
4311
|
+
function transcriptDocument(bundle, format, options = {}) {
|
|
4312
|
+
const names = options.names ?? bundle.names;
|
|
4313
|
+
const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
|
|
4314
|
+
const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
|
|
4315
|
+
if (format === "vtt") {
|
|
4316
|
+
const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
|
|
4317
|
+
const zero = ms(zeroIso) ?? 0;
|
|
4318
|
+
const cues = ["WEBVTT", ""];
|
|
4319
|
+
lines.forEach((line, index) => {
|
|
4320
|
+
const start = (ms(line.startedAt) ?? zero) - zero;
|
|
4321
|
+
if (start < 0) return;
|
|
4322
|
+
const next = lines[index + 1];
|
|
4323
|
+
const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
|
|
4324
|
+
const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
|
|
4325
|
+
cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
|
|
4326
|
+
cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
|
|
4327
|
+
cues.push("");
|
|
4328
|
+
});
|
|
4329
|
+
return cues.join("\n");
|
|
4330
|
+
}
|
|
4331
|
+
const out = [];
|
|
4332
|
+
const title = bundle.meeting.title;
|
|
4333
|
+
out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
|
|
4334
|
+
out.push("");
|
|
4335
|
+
let last = null;
|
|
4336
|
+
for (const line of lines) {
|
|
4337
|
+
if (line.identity !== last) {
|
|
4338
|
+
if (last !== null) out.push("");
|
|
4339
|
+
const who = speaker(line);
|
|
4340
|
+
const clock = clockOfDay(line.startedAt, options);
|
|
4341
|
+
out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
|
|
4342
|
+
last = line.identity;
|
|
4343
|
+
}
|
|
4344
|
+
out.push(line.text.trim());
|
|
4345
|
+
}
|
|
4346
|
+
if (lines.length === 0) out.push("No transcript was captured for this meeting.");
|
|
4347
|
+
return `${out.join("\n")}
|
|
4348
|
+
`;
|
|
4349
|
+
}
|
|
4350
|
+
function bullet(note) {
|
|
4351
|
+
const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
|
|
4352
|
+
return `- ${note.text.trim()}${owner}`;
|
|
4353
|
+
}
|
|
4354
|
+
function notesDocument(bundle, options = {}) {
|
|
4355
|
+
const out = [`# ${bundle.meeting.title}`, ""];
|
|
4356
|
+
if (options.when) out.push(`_${options.when}_`, "");
|
|
4357
|
+
const people = uniqueAttendees(
|
|
4358
|
+
bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null),
|
|
4359
|
+
bundle.names
|
|
4360
|
+
).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
|
|
4361
|
+
if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
|
|
4362
|
+
out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
|
|
4363
|
+
out.push("## Decisions", "");
|
|
4364
|
+
out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
|
|
4365
|
+
out.push("## Action items", "");
|
|
4366
|
+
out.push(
|
|
4367
|
+
...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
|
|
4368
|
+
""
|
|
4369
|
+
);
|
|
4370
|
+
if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
|
|
4371
|
+
return out.join("\n");
|
|
4372
|
+
}
|
|
4373
|
+
function meetingLength(meeting) {
|
|
4374
|
+
const start = ms(meeting.startedAt);
|
|
4375
|
+
const end = ms(meeting.endedAt);
|
|
4376
|
+
if (start === null || end === null || end < start) return null;
|
|
4377
|
+
return (0, import_format.formatDurationMs)(end - start, { style: "long" });
|
|
4378
|
+
}
|
|
4379
|
+
async function loadMeetingChat(api, meetingId, roomToken = null) {
|
|
4380
|
+
const operation = "meet.loadMeetingChat";
|
|
4381
|
+
let response;
|
|
3908
4382
|
try {
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
4383
|
+
response = await api.request({
|
|
4384
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
4385
|
+
method: "GET",
|
|
4386
|
+
operation,
|
|
4387
|
+
requireAuth: roomToken === null,
|
|
4388
|
+
...roomToken !== null ? { headers: { [ROOM_TOKEN_HEADER]: roomToken } } : {}
|
|
4389
|
+
});
|
|
4390
|
+
} catch (error) {
|
|
4391
|
+
return {
|
|
4392
|
+
messages: [],
|
|
4393
|
+
refusal: {
|
|
4394
|
+
message: `The chat could not be read (${error.message}).`,
|
|
4395
|
+
remedy: error.remedy ?? "Reload the page."
|
|
4396
|
+
}
|
|
4397
|
+
};
|
|
3912
4398
|
}
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
4399
|
+
if (!response.ok) {
|
|
4400
|
+
let body = null;
|
|
4401
|
+
try {
|
|
4402
|
+
body = await response.json();
|
|
4403
|
+
} catch {
|
|
4404
|
+
body = null;
|
|
4405
|
+
}
|
|
4406
|
+
const said = readServerRefusal(body);
|
|
4407
|
+
return {
|
|
4408
|
+
messages: [],
|
|
4409
|
+
refusal: {
|
|
4410
|
+
message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
|
|
4411
|
+
remedy: said?.remedy || "Reload the page."
|
|
4412
|
+
}
|
|
4413
|
+
};
|
|
4414
|
+
}
|
|
4415
|
+
const payload = await response.json();
|
|
4416
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
4417
|
+
return {
|
|
4418
|
+
messages: rows.map(
|
|
4419
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
4420
|
+
).filter((message) => message !== null),
|
|
4421
|
+
refusal: null
|
|
4422
|
+
};
|
|
4423
|
+
}
|
|
4424
|
+
function refusalOf(response, operation) {
|
|
4425
|
+
return refusalFromResponse(response, operation, "Nothing was sent. Try again.");
|
|
4426
|
+
}
|
|
4427
|
+
function asRecipient(row) {
|
|
4428
|
+
const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
|
|
4429
|
+
return {
|
|
4430
|
+
key: String(row["key"] ?? ""),
|
|
4431
|
+
name: String(row["name"] ?? ""),
|
|
4432
|
+
email: typeof row["email"] === "string" ? row["email"] : null,
|
|
4433
|
+
userId: typeof row["user_id"] === "string" ? row["user_id"] : null,
|
|
4434
|
+
sources: sources.filter((s) => s === "invitee" || s === "attendee"),
|
|
4435
|
+
reachable: row["reachable"] !== false
|
|
4436
|
+
};
|
|
4437
|
+
}
|
|
4438
|
+
async function loadFollowUpDraft(api, meetingId) {
|
|
4439
|
+
const operation = "meet.loadFollowUpDraft";
|
|
4440
|
+
const response = await api.request({
|
|
4441
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
4442
|
+
method: "GET",
|
|
4443
|
+
requireAuth: true,
|
|
4444
|
+
operation
|
|
4445
|
+
});
|
|
4446
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
4447
|
+
const body = await response.json();
|
|
4448
|
+
const last = body["last_sent"];
|
|
4449
|
+
return {
|
|
4450
|
+
subject: String(body["subject"] ?? ""),
|
|
4451
|
+
body: String(body["body"] ?? ""),
|
|
4452
|
+
recipients: (Array.isArray(body["recipients"]) ? body["recipients"] : []).map(
|
|
4453
|
+
(row) => asRecipient(row)
|
|
4454
|
+
),
|
|
4455
|
+
wrapUpWritten: body["wrap_up_written"] === true,
|
|
4456
|
+
lastSent: last && typeof last["at"] === "string" ? { at: last["at"], count: typeof last["count"] === "number" ? last["count"] : 0 } : null
|
|
4457
|
+
};
|
|
4458
|
+
}
|
|
4459
|
+
async function sendFollowUp(api, meetingId, input) {
|
|
4460
|
+
const operation = "meet.sendFollowUp";
|
|
4461
|
+
const response = await api.request({
|
|
4462
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
4463
|
+
method: "POST",
|
|
4464
|
+
requireAuth: true,
|
|
4465
|
+
operation,
|
|
4466
|
+
body: {
|
|
4467
|
+
subject: input.subject,
|
|
4468
|
+
body: input.body,
|
|
4469
|
+
recipient_keys: [...input.recipientKeys],
|
|
4470
|
+
dry_run: input.dryRun === true
|
|
4471
|
+
}
|
|
4472
|
+
});
|
|
4473
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
4474
|
+
const body = await response.json();
|
|
4475
|
+
return {
|
|
4476
|
+
dryRun: body["dry_run"] === true,
|
|
4477
|
+
queued: Array.isArray(body["queued"]) ? body["queued"] : [],
|
|
4478
|
+
skipped: Array.isArray(body["skipped"]) ? body["skipped"].map((row) => ({
|
|
4479
|
+
key: String(row["key"] ?? ""),
|
|
4480
|
+
reason: String(row["reason"] ?? "")
|
|
4481
|
+
})) : []
|
|
4482
|
+
};
|
|
4483
|
+
}
|
|
4484
|
+
|
|
4485
|
+
// src/core/management.ts
|
|
4486
|
+
function refusal(response, operation) {
|
|
4487
|
+
return refusalFromResponse(
|
|
4488
|
+
response,
|
|
4489
|
+
operation,
|
|
4490
|
+
"The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
|
|
3918
4491
|
);
|
|
3919
4492
|
}
|
|
3920
4493
|
function count(body, key) {
|
|
@@ -4240,6 +4813,10 @@ function createRoomEngine(options) {
|
|
|
4240
4813
|
}
|
|
4241
4814
|
},
|
|
4242
4815
|
disconnected(reason) {
|
|
4816
|
+
if (current !== null) {
|
|
4817
|
+
tokens.invalidate(current.roomName);
|
|
4818
|
+
current = null;
|
|
4819
|
+
}
|
|
4243
4820
|
store.patch({ phase: "left" });
|
|
4244
4821
|
options.onLeft?.(reason);
|
|
4245
4822
|
}
|
|
@@ -4542,19 +5119,8 @@ function createInRoomClient(options) {
|
|
|
4542
5119
|
const token = options.resolveRoomToken();
|
|
4543
5120
|
return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
|
|
4544
5121
|
}
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
try {
|
|
4548
|
-
body = await response.json();
|
|
4549
|
-
} catch {
|
|
4550
|
-
body = null;
|
|
4551
|
-
}
|
|
4552
|
-
const said = readServerRefusal(body);
|
|
4553
|
-
return new MeetError(
|
|
4554
|
-
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
4555
|
-
said?.reason ?? `${operation}: the server answered ${response.status}`,
|
|
4556
|
-
said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
|
|
4557
|
-
);
|
|
5122
|
+
function refusal2(response, operation, remedy) {
|
|
5123
|
+
return refusalFromResponse(response, operation, remedy);
|
|
4558
5124
|
}
|
|
4559
5125
|
return {
|
|
4560
5126
|
async moderate(request) {
|
|
@@ -4658,7 +5224,7 @@ function createInRoomClient(options) {
|
|
|
4658
5224
|
}
|
|
4659
5225
|
|
|
4660
5226
|
// src/core/record.ts
|
|
4661
|
-
var
|
|
5227
|
+
var import_format2 = require("@ai-matrx/kit/format");
|
|
4662
5228
|
function activeWrapUpSummary(notes) {
|
|
4663
5229
|
return notes.find((note) => note.role === "summary" && note.supersededBy === null) ?? null;
|
|
4664
5230
|
}
|
|
@@ -4741,6 +5307,36 @@ function wasRefused(bundle, part) {
|
|
|
4741
5307
|
function absenceSentence(bundle, part, absence) {
|
|
4742
5308
|
return wasRefused(bundle, part) ? REFUSED_ABSENCE : absence;
|
|
4743
5309
|
}
|
|
5310
|
+
function noteTakerMetadata(meeting) {
|
|
5311
|
+
const raw = meeting.metadata["note_taker"];
|
|
5312
|
+
return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
5313
|
+
}
|
|
5314
|
+
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.";
|
|
5315
|
+
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.";
|
|
5316
|
+
function wrapUpStatus(bundle) {
|
|
5317
|
+
if (bundle.summary !== null) return { state: "written" };
|
|
5318
|
+
if (wasRefused(bundle, "notes")) return { state: "refused", message: REFUSED_ABSENCE };
|
|
5319
|
+
const noteTaker = noteTakerMetadata(bundle.meeting);
|
|
5320
|
+
const intelligenceRaw = noteTaker["intelligence"];
|
|
5321
|
+
const intelligence = typeof intelligenceRaw === "object" && intelligenceRaw !== null && !Array.isArray(intelligenceRaw) ? intelligenceRaw : {};
|
|
5322
|
+
const wrapUpRaw = intelligence["wrap_up"];
|
|
5323
|
+
const wrapUp = typeof wrapUpRaw === "object" && wrapUpRaw !== null && !Array.isArray(wrapUpRaw) ? wrapUpRaw : {};
|
|
5324
|
+
if (wrapUp["state"] === "failed") {
|
|
5325
|
+
const reason = wrapUp["reason"];
|
|
5326
|
+
return {
|
|
5327
|
+
state: "failed",
|
|
5328
|
+
// THE SERVER'S OWN SENTENCE, verbatim. It already names the remedy, and a
|
|
5329
|
+
// house sentence over it would hide the one thing worth reading.
|
|
5330
|
+
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."
|
|
5331
|
+
};
|
|
5332
|
+
}
|
|
5333
|
+
const queued = typeof wrapUp["run_id"] === "string" || typeof intelligence["wrap_up_run_id"] === "string" || wrapUp["state"] === "queued" || wrapUp["state"] === "running";
|
|
5334
|
+
const attended = noteTaker["state"] === "ended" || noteTaker["state"] === "transcribing" || noteTaker["state"] === "handoff_pending";
|
|
5335
|
+
if (hasEnded(bundle.meeting) && (queued || attended)) {
|
|
5336
|
+
return { state: "in-progress", message: WRAP_UP_IN_PROGRESS, canRefresh: true };
|
|
5337
|
+
}
|
|
5338
|
+
return { state: "absent", message: WRAP_UP_ABSENT };
|
|
5339
|
+
}
|
|
4744
5340
|
function groupTranscript(segments, names = {}) {
|
|
4745
5341
|
const blocks = [];
|
|
4746
5342
|
for (const segment of segments) {
|
|
@@ -4809,7 +5405,7 @@ function meetingWhen(meeting, locale) {
|
|
|
4809
5405
|
const endedAt = new Date(meeting.endedAt);
|
|
4810
5406
|
if (Number.isNaN(endedAt.getTime())) return when2;
|
|
4811
5407
|
const elapsedMs = endedAt.getTime() - startedAt.getTime();
|
|
4812
|
-
return `${when2} \xB7 ${(0,
|
|
5408
|
+
return `${when2} \xB7 ${(0, import_format2.formatDurationMs)(elapsedMs, { style: "long" })}`;
|
|
4813
5409
|
}
|
|
4814
5410
|
var GUEST_RECORD_ACCESS_LEVELS = [
|
|
4815
5411
|
"none",
|
|
@@ -4858,6 +5454,37 @@ var NOT_SHARED = {
|
|
|
4858
5454
|
recording: "The recording of this meeting is not shared with guests."
|
|
4859
5455
|
};
|
|
4860
5456
|
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.";
|
|
5457
|
+
var RESTRICTION_PART_NAME = {
|
|
5458
|
+
attendance: "attendance",
|
|
5459
|
+
notes: "notes and decisions",
|
|
5460
|
+
transcript: "word-for-word transcript",
|
|
5461
|
+
recording: "recording"
|
|
5462
|
+
};
|
|
5463
|
+
function joinPartNames(parts) {
|
|
5464
|
+
const names = parts.map((part) => RESTRICTION_PART_NAME[part]);
|
|
5465
|
+
if (names.length < 2) return names[0] ?? "this part of the record";
|
|
5466
|
+
if (names.length === 2) return `${names[0]} and ${names[1]}`;
|
|
5467
|
+
return `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`;
|
|
5468
|
+
}
|
|
5469
|
+
function summarizeMeetingRecordRestrictions(restrictions) {
|
|
5470
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5471
|
+
for (const restriction of restrictions) {
|
|
5472
|
+
const rows = grouped.get(restriction.remedy) ?? [];
|
|
5473
|
+
rows.push(restriction);
|
|
5474
|
+
grouped.set(restriction.remedy, rows);
|
|
5475
|
+
}
|
|
5476
|
+
return [...grouped.values()].map((rows) => {
|
|
5477
|
+
if (rows.length === 1) {
|
|
5478
|
+
const row = rows[0];
|
|
5479
|
+
return { parts: [row.part], message: row.message, remedy: row.remedy };
|
|
5480
|
+
}
|
|
5481
|
+
return {
|
|
5482
|
+
parts: rows.map((row) => row.part),
|
|
5483
|
+
message: `Some parts of this meeting record are not available to this reader: ${joinPartNames(rows.map((row) => row.part))}.`,
|
|
5484
|
+
remedy: rows[0].remedy
|
|
5485
|
+
};
|
|
5486
|
+
});
|
|
5487
|
+
}
|
|
4861
5488
|
async function loadGuestMeetingRecord(args) {
|
|
4862
5489
|
const { api, repository, meetingId, meeting, roomToken } = args;
|
|
4863
5490
|
if (roomToken === null) {
|