@ai-matrx/meet 0.7.6 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/dist/index.cjs +671 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +654 -319
- package/dist/index.d.ts +654 -319
- package/dist/index.js +671 -44
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +692 -69
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +691 -68
- package/dist/react.js.map +1 -1
- package/dist/styles.css +18 -0
- package/package.json +4 -4
package/dist/react.js
CHANGED
|
@@ -212,6 +212,12 @@ var MEET_ROUTES = {
|
|
|
212
212
|
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
213
213
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
214
214
|
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
215
|
+
/**
|
|
216
|
+
* Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,
|
|
217
|
+
* body from the wrap-up, who it can go to); `POST` = send what the host
|
|
218
|
+
* reviewed (`dry_run` renders and queues nothing). Never sent automatically.
|
|
219
|
+
*/
|
|
220
|
+
meetingFollowUp: "/api/v1/meet/meetings/{meeting_id}/follow-up",
|
|
215
221
|
/**
|
|
216
222
|
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
217
223
|
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
@@ -341,10 +347,11 @@ function createTokenClient(options) {
|
|
|
341
347
|
throw error;
|
|
342
348
|
}
|
|
343
349
|
if (response.status === 401 || response.status === 403) {
|
|
350
|
+
const said = await serverSentence(response);
|
|
344
351
|
throw new MeetError(
|
|
345
352
|
"forbidden",
|
|
346
|
-
|
|
347
|
-
"
|
|
353
|
+
said?.message ?? "You can't join this meeting.",
|
|
354
|
+
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again."
|
|
348
355
|
);
|
|
349
356
|
}
|
|
350
357
|
if (response.status === 404) {
|
|
@@ -355,12 +362,7 @@ function createTokenClient(options) {
|
|
|
355
362
|
);
|
|
356
363
|
}
|
|
357
364
|
if (response.status === 410) {
|
|
358
|
-
|
|
359
|
-
try {
|
|
360
|
-
const body = await response.json();
|
|
361
|
-
if (typeof body?.detail?.message === "string") reason = body.detail.message;
|
|
362
|
-
} catch {
|
|
363
|
-
}
|
|
365
|
+
const reason = (await serverSentence(response))?.message ?? "This meeting was cancelled.";
|
|
364
366
|
throw new MeetError(
|
|
365
367
|
"cancelled",
|
|
366
368
|
reason,
|
|
@@ -368,10 +370,11 @@ function createTokenClient(options) {
|
|
|
368
370
|
);
|
|
369
371
|
}
|
|
370
372
|
if (response.status === 423) {
|
|
373
|
+
const said = await serverSentence(response);
|
|
371
374
|
throw new MeetError(
|
|
372
375
|
"room-locked",
|
|
373
|
-
"
|
|
374
|
-
"The host locked the room. Ask them to unlock
|
|
376
|
+
said?.message ?? "The meeting is locked.",
|
|
377
|
+
said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again."
|
|
375
378
|
);
|
|
376
379
|
}
|
|
377
380
|
if (response.status === 503) {
|
|
@@ -442,6 +445,19 @@ function placeholder(roomName) {
|
|
|
442
445
|
lobby: false
|
|
443
446
|
};
|
|
444
447
|
}
|
|
448
|
+
async function serverSentence(response) {
|
|
449
|
+
try {
|
|
450
|
+
const body = await response.json();
|
|
451
|
+
const source = body !== null && typeof body.detail === "object" && body.detail !== null ? body.detail : body;
|
|
452
|
+
if (source === null) return null;
|
|
453
|
+
const message = typeof source.user_message === "string" && source.user_message.trim() !== "" ? source.user_message : typeof source.message === "string" && source.message.trim() !== "" ? source.message : null;
|
|
454
|
+
if (message === null) return null;
|
|
455
|
+
const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
|
|
456
|
+
return { message, remedy };
|
|
457
|
+
} catch {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
445
461
|
|
|
446
462
|
// src/core/ai.ts
|
|
447
463
|
var MEET_ANSWER_EVENTS = {
|
|
@@ -486,6 +502,29 @@ function readText(data) {
|
|
|
486
502
|
}
|
|
487
503
|
return "";
|
|
488
504
|
}
|
|
505
|
+
var SERVER_FAILED_SENTENCE = "The server hit an unexpected problem and nothing was changed.";
|
|
506
|
+
function isRawServerText(value) {
|
|
507
|
+
const text3 = value.trim();
|
|
508
|
+
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);
|
|
509
|
+
}
|
|
510
|
+
async function refusalFromResponse(response, operation, fallbackRemedy) {
|
|
511
|
+
let body = null;
|
|
512
|
+
try {
|
|
513
|
+
body = await response.json();
|
|
514
|
+
} catch {
|
|
515
|
+
body = null;
|
|
516
|
+
}
|
|
517
|
+
const said = readServerRefusal(body);
|
|
518
|
+
const code = response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport";
|
|
519
|
+
const error = new MeetError(
|
|
520
|
+
code,
|
|
521
|
+
said?.reason ?? SERVER_FAILED_SENTENCE,
|
|
522
|
+
said !== null && said.remedy.length > 0 ? said.remedy : fallbackRemedy
|
|
523
|
+
);
|
|
524
|
+
error.operation = operation;
|
|
525
|
+
error.status = response.status;
|
|
526
|
+
return error;
|
|
527
|
+
}
|
|
489
528
|
function readServerRefusal(body) {
|
|
490
529
|
if (typeof body === "string") {
|
|
491
530
|
return body.trim().length === 0 ? null : { reason: body.trim(), remedy: "" };
|
|
@@ -504,7 +543,8 @@ function readServerRefusal(body) {
|
|
|
504
543
|
return messages.length === 0 ? null : { reason: messages.join("; "), remedy: "" };
|
|
505
544
|
}
|
|
506
545
|
if (typeof detail === "object" && detail !== null) return readServerRefusal(detail);
|
|
507
|
-
const
|
|
546
|
+
const humanOrNull = (value) => typeof value === "string" && value.trim().length > 0 && !isRawServerText(value) ? value.trim() : null;
|
|
547
|
+
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);
|
|
508
548
|
if (reason === null) return null;
|
|
509
549
|
const remedy = typeof row["remedy"] === "string" ? row["remedy"] : "";
|
|
510
550
|
if (remedy.length > 0) return { reason, remedy };
|
|
@@ -885,7 +925,7 @@ var DEFAULT_RING_TIMEOUT_MS = 45e3;
|
|
|
885
925
|
function createCallCenter(options) {
|
|
886
926
|
const { manager, repository, identity } = options;
|
|
887
927
|
const timers = options.timers ?? {
|
|
888
|
-
setTimeout: (run,
|
|
928
|
+
setTimeout: (run, ms2) => setTimeout(run, ms2),
|
|
889
929
|
clearTimeout: (handle) => clearTimeout(handle),
|
|
890
930
|
now: () => Date.now()
|
|
891
931
|
};
|
|
@@ -1558,6 +1598,7 @@ var EMPTY = {
|
|
|
1558
1598
|
spotlight: null,
|
|
1559
1599
|
moderationNotice: null,
|
|
1560
1600
|
actionNotice: null,
|
|
1601
|
+
privateChatRequest: null,
|
|
1561
1602
|
effects: DEFAULT_EFFECTS,
|
|
1562
1603
|
transcript: [],
|
|
1563
1604
|
notes: [],
|
|
@@ -1823,7 +1864,9 @@ function createMeetingSession(options) {
|
|
|
1823
1864
|
return identity.userId;
|
|
1824
1865
|
}
|
|
1825
1866
|
function knockIdentity() {
|
|
1826
|
-
return asParticipantIdentity(
|
|
1867
|
+
return asParticipantIdentity(
|
|
1868
|
+
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
1869
|
+
);
|
|
1827
1870
|
}
|
|
1828
1871
|
function requireMeeting(operation) {
|
|
1829
1872
|
const meeting = store.snapshot().meeting;
|
|
@@ -1858,8 +1901,9 @@ function createMeetingSession(options) {
|
|
|
1858
1901
|
verdict.admitted ? "admitted" : "denied",
|
|
1859
1902
|
verdict.by === null ? null : verdict.by
|
|
1860
1903
|
);
|
|
1861
|
-
if (
|
|
1862
|
-
options.onAdmitted?.();
|
|
1904
|
+
if (decided === knockIdentity()) {
|
|
1905
|
+
if (verdict.admitted) options.onAdmitted?.();
|
|
1906
|
+
else options.onDenied?.();
|
|
1863
1907
|
}
|
|
1864
1908
|
}
|
|
1865
1909
|
},
|
|
@@ -2196,7 +2240,16 @@ var RPCS = {
|
|
|
2196
2240
|
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
2197
2241
|
meetingOccurrences: "meet_meeting_occurrences",
|
|
2198
2242
|
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
2199
|
-
occurrencesBetween: "meet_occurrences_between"
|
|
2243
|
+
occurrencesBetween: "meet_occurrences_between",
|
|
2244
|
+
/*
|
|
2245
|
+
* ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
|
|
2246
|
+
* scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
|
|
2247
|
+
* ceiling, never the view.
|
|
2248
|
+
*/
|
|
2249
|
+
/** The recordings library. */
|
|
2250
|
+
myRecordings: "meet_my_recordings",
|
|
2251
|
+
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
2252
|
+
search: "meet_search"
|
|
2200
2253
|
};
|
|
2201
2254
|
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
2202
2255
|
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";
|
|
@@ -2901,12 +2954,117 @@ function createMeetRepository(options) {
|
|
|
2901
2954
|
);
|
|
2902
2955
|
},
|
|
2903
2956
|
async recordings(meetingId) {
|
|
2904
|
-
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: false });
|
|
2957
|
+
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("started_at", { ascending: false });
|
|
2905
2958
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.recordings");
|
|
2906
2959
|
return (response.data ?? []).map(projectRecording);
|
|
2960
|
+
},
|
|
2961
|
+
async myRecordings({ includeArchived = false, limit = 200 } = {}) {
|
|
2962
|
+
const rows = await guard(
|
|
2963
|
+
"meet.myRecordings",
|
|
2964
|
+
() => db.rpc(RPCS.myRecordings, { p_include_archived: includeArchived, p_limit: limit })
|
|
2965
|
+
);
|
|
2966
|
+
return rows.map(projectLibraryRecording);
|
|
2967
|
+
},
|
|
2968
|
+
async searchMeetings(query, limit = 60) {
|
|
2969
|
+
if (query.trim().length === 0) return [];
|
|
2970
|
+
const rows = await guard(
|
|
2971
|
+
"meet.searchMeetings",
|
|
2972
|
+
() => db.rpc(RPCS.search, { p_query: query, p_limit: limit })
|
|
2973
|
+
);
|
|
2974
|
+
return rows.map(projectSearchHit);
|
|
2975
|
+
},
|
|
2976
|
+
async renameRecording(recordingId, title) {
|
|
2977
|
+
const clean = title === null ? null : title.trim().slice(0, 200) || null;
|
|
2978
|
+
const response = await db.from(TABLES.recordings).update({ title: clean }).eq("id", recordingId).select("id");
|
|
2979
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.renameRecording");
|
|
2980
|
+
if ((response.data ?? []).length === 0) {
|
|
2981
|
+
throw normalizeMeetError(
|
|
2982
|
+
{ message: "permission denied: only the meeting's host or an editor can rename it", code: "42501" },
|
|
2983
|
+
"meet.renameRecording"
|
|
2984
|
+
);
|
|
2985
|
+
}
|
|
2986
|
+
},
|
|
2987
|
+
async setRecordingArchived(recordingId, archived) {
|
|
2988
|
+
const response = await db.from(TABLES.recordings).update({ deleted_at: archived ? (/* @__PURE__ */ new Date()).toISOString() : null }).eq("id", recordingId).select("id");
|
|
2989
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.setRecordingArchived");
|
|
2990
|
+
if ((response.data ?? []).length === 0) {
|
|
2991
|
+
throw normalizeMeetError(
|
|
2992
|
+
{ message: "permission denied: only the meeting's host or an editor can archive it", code: "42501" },
|
|
2993
|
+
"meet.setRecordingArchived"
|
|
2994
|
+
);
|
|
2995
|
+
}
|
|
2907
2996
|
}
|
|
2908
2997
|
};
|
|
2909
2998
|
}
|
|
2999
|
+
var LIBRARY_STATES = [
|
|
3000
|
+
"idle",
|
|
3001
|
+
"starting",
|
|
3002
|
+
"recording",
|
|
3003
|
+
"stopping",
|
|
3004
|
+
"processing",
|
|
3005
|
+
"available",
|
|
3006
|
+
"expired",
|
|
3007
|
+
"failed"
|
|
3008
|
+
];
|
|
3009
|
+
function projectLibraryRecording(row) {
|
|
3010
|
+
const operation = "meet.projectLibraryRecording";
|
|
3011
|
+
const state = text(row, "state");
|
|
3012
|
+
const role = text(row, "my_role");
|
|
3013
|
+
return {
|
|
3014
|
+
id: requireText(row, "recording_id", operation),
|
|
3015
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
3016
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
3017
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
3018
|
+
role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
|
|
3019
|
+
state: LIBRARY_STATES.includes(state) ? state : "failed",
|
|
3020
|
+
title: text(row, "recording_title"),
|
|
3021
|
+
fileId: text(row, "file_id"),
|
|
3022
|
+
startedAt: text(row, "started_at"),
|
|
3023
|
+
endedAt: text(row, "ended_at"),
|
|
3024
|
+
durationMs: num(row, "duration_ms"),
|
|
3025
|
+
sizeBytes: num(row, "size_bytes"),
|
|
3026
|
+
failureReason: text(row, "failure_reason"),
|
|
3027
|
+
archivedAt: text(row, "deleted_at"),
|
|
3028
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation))
|
|
3029
|
+
};
|
|
3030
|
+
}
|
|
3031
|
+
var SEARCH_SOURCES = [
|
|
3032
|
+
"title",
|
|
3033
|
+
"summary",
|
|
3034
|
+
"decision",
|
|
3035
|
+
"action_item",
|
|
3036
|
+
"note",
|
|
3037
|
+
"transcript"
|
|
3038
|
+
];
|
|
3039
|
+
function projectSearchHit(row) {
|
|
3040
|
+
const operation = "meet.projectSearchHit";
|
|
3041
|
+
const source = text(row, "source");
|
|
3042
|
+
return {
|
|
3043
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
3044
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
3045
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
3046
|
+
meetingAt: text(row, "meeting_at"),
|
|
3047
|
+
source: SEARCH_SOURCES.includes(source) ? source : "note",
|
|
3048
|
+
refId: requireText(row, "ref_id", operation),
|
|
3049
|
+
speaker: text(row, "speaker"),
|
|
3050
|
+
snippet: text(row, "snippet") ?? "",
|
|
3051
|
+
at: text(row, "said_at"),
|
|
3052
|
+
rank: num(row, "rank") ?? 0
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
function snippetRuns(snippet) {
|
|
3056
|
+
const runs = [];
|
|
3057
|
+
const pattern = /⟦([^⟧]*)⟧/g;
|
|
3058
|
+
let last = 0;
|
|
3059
|
+
for (const match of snippet.matchAll(pattern)) {
|
|
3060
|
+
const start = match.index ?? 0;
|
|
3061
|
+
if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
|
|
3062
|
+
runs.push({ text: match[1] ?? "", hit: true });
|
|
3063
|
+
last = start + match[0].length;
|
|
3064
|
+
}
|
|
3065
|
+
if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
|
|
3066
|
+
return runs;
|
|
3067
|
+
}
|
|
2910
3068
|
|
|
2911
3069
|
// src/core/session-identity.ts
|
|
2912
3070
|
var RESOLVING = Object.freeze({
|
|
@@ -3630,6 +3788,10 @@ function createRoomEngine(options) {
|
|
|
3630
3788
|
}
|
|
3631
3789
|
},
|
|
3632
3790
|
disconnected(reason) {
|
|
3791
|
+
if (current !== null) {
|
|
3792
|
+
tokens.invalidate(current.roomName);
|
|
3793
|
+
current = null;
|
|
3794
|
+
}
|
|
3633
3795
|
store.patch({ phase: "left" });
|
|
3634
3796
|
options.onLeft?.(reason);
|
|
3635
3797
|
}
|
|
@@ -3932,19 +4094,8 @@ function createInRoomClient(options) {
|
|
|
3932
4094
|
const token = options.resolveRoomToken();
|
|
3933
4095
|
return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
|
|
3934
4096
|
}
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
try {
|
|
3938
|
-
body = await response.json();
|
|
3939
|
-
} catch {
|
|
3940
|
-
body = null;
|
|
3941
|
-
}
|
|
3942
|
-
const said = readServerRefusal(body);
|
|
3943
|
-
return new MeetError(
|
|
3944
|
-
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
3945
|
-
said?.reason ?? `${operation}: the server answered ${response.status}`,
|
|
3946
|
-
said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
|
|
3947
|
-
);
|
|
4097
|
+
function refusal2(response, operation, remedy) {
|
|
4098
|
+
return refusalFromResponse(response, operation, remedy);
|
|
3948
4099
|
}
|
|
3949
4100
|
return {
|
|
3950
4101
|
async moderate(request) {
|
|
@@ -4537,6 +4688,11 @@ function MeetRuntime(props) {
|
|
|
4537
4688
|
// publish nor subscribe, so the host saying yes has to mint a new one.
|
|
4538
4689
|
onAdmitted: () => {
|
|
4539
4690
|
void engine.rejoin();
|
|
4691
|
+
},
|
|
4692
|
+
// A "no" is ALSO a mint: the server refuses it with its own sentence, which
|
|
4693
|
+
// turns the waiting room into the pre-join screen showing that sentence.
|
|
4694
|
+
onDenied: () => {
|
|
4695
|
+
void engine.rejoin().catch(() => void 0);
|
|
4540
4696
|
}
|
|
4541
4697
|
});
|
|
4542
4698
|
const resolveRoomToken = () => {
|
|
@@ -5899,7 +6055,408 @@ function useGuestRecordAccess(meeting) {
|
|
|
5899
6055
|
// src/react/components.tsx
|
|
5900
6056
|
import { useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
|
|
5901
6057
|
import { ErrorBox } from "@ai-matrx/design-system";
|
|
5902
|
-
import { avatarPaletteIndex, formatDurationMs as
|
|
6058
|
+
import { avatarPaletteIndex, formatDurationMs as formatDurationMs3, getInitials } from "@ai-matrx/kit/format";
|
|
6059
|
+
|
|
6060
|
+
// src/core/after.ts
|
|
6061
|
+
import { formatDurationMs as formatDurationMs2 } from "@ai-matrx/kit/format";
|
|
6062
|
+
function kindOf(person) {
|
|
6063
|
+
if (person.isAgent) return "assistant";
|
|
6064
|
+
if (person.role === "host") return "host";
|
|
6065
|
+
if (person.role === "cohost") return "cohost";
|
|
6066
|
+
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
6067
|
+
return "member";
|
|
6068
|
+
}
|
|
6069
|
+
function ms(iso) {
|
|
6070
|
+
if (iso === null) return null;
|
|
6071
|
+
const at = new Date(iso).getTime();
|
|
6072
|
+
return Number.isNaN(at) ? null : at;
|
|
6073
|
+
}
|
|
6074
|
+
function attendeeKey(person, names = {}) {
|
|
6075
|
+
if (person.userId !== null) return `user:${person.userId}`;
|
|
6076
|
+
if (person.isAgent) return `agent:${person.identity}`;
|
|
6077
|
+
const name = displayNameFor(names, person.identity, person.displayName).trim().toLowerCase();
|
|
6078
|
+
return name.length > 0 ? `guest-name:${name}` : `identity:${person.identity}`;
|
|
6079
|
+
}
|
|
6080
|
+
function uniqueAttendees(attendees, names = {}) {
|
|
6081
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
6082
|
+
for (const person of attendees) {
|
|
6083
|
+
const key = attendeeKey(person, names);
|
|
6084
|
+
const kept = byKey.get(key);
|
|
6085
|
+
if (kept === void 0) {
|
|
6086
|
+
byKey.set(key, person);
|
|
6087
|
+
continue;
|
|
6088
|
+
}
|
|
6089
|
+
const keptJoin = ms(kept.joinedAt);
|
|
6090
|
+
const join = ms(person.joinedAt);
|
|
6091
|
+
if (join !== null && (keptJoin === null || join < keptJoin)) byKey.set(key, person);
|
|
6092
|
+
}
|
|
6093
|
+
return [...byKey.values()];
|
|
6094
|
+
}
|
|
6095
|
+
var KIND_RANK = {
|
|
6096
|
+
host: 0,
|
|
6097
|
+
cohost: 1,
|
|
6098
|
+
member: 2,
|
|
6099
|
+
"signed-in guest": 3,
|
|
6100
|
+
guest: 4,
|
|
6101
|
+
assistant: 5
|
|
6102
|
+
};
|
|
6103
|
+
function coveredMs(intervals) {
|
|
6104
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
6105
|
+
let total = 0;
|
|
6106
|
+
let from = -Infinity;
|
|
6107
|
+
let to = -Infinity;
|
|
6108
|
+
for (const [start, end] of sorted) {
|
|
6109
|
+
if (start > to) {
|
|
6110
|
+
if (to > from) total += to - from;
|
|
6111
|
+
from = start;
|
|
6112
|
+
to = end;
|
|
6113
|
+
} else if (end > to) {
|
|
6114
|
+
to = end;
|
|
6115
|
+
}
|
|
6116
|
+
}
|
|
6117
|
+
if (to > from) total += to - from;
|
|
6118
|
+
return total;
|
|
6119
|
+
}
|
|
6120
|
+
function attendanceReport(meeting, attendees, names = {}) {
|
|
6121
|
+
const end = ms(meeting.endedAt);
|
|
6122
|
+
const perJoin = attendees.map((person) => {
|
|
6123
|
+
const joined = ms(person.joinedAt);
|
|
6124
|
+
const left = ms(person.leftAt);
|
|
6125
|
+
const until = left ?? end;
|
|
6126
|
+
const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
|
|
6127
|
+
return {
|
|
6128
|
+
id: person.id,
|
|
6129
|
+
identity: person.identity,
|
|
6130
|
+
name: displayNameFor(names, person.identity, person.displayName),
|
|
6131
|
+
userId: person.userId,
|
|
6132
|
+
kind: kindOf(person),
|
|
6133
|
+
joinedAt: person.joinedAt,
|
|
6134
|
+
leftAt: person.leftAt,
|
|
6135
|
+
durationMs: duration,
|
|
6136
|
+
durationToEnd: duration !== null && left === null,
|
|
6137
|
+
admission: person.admissionState,
|
|
6138
|
+
joins: 1
|
|
6139
|
+
};
|
|
6140
|
+
});
|
|
6141
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6142
|
+
attendees.forEach((person, index) => {
|
|
6143
|
+
const key = attendeeKey(person, names);
|
|
6144
|
+
const group = groups.get(key) ?? { rows: [], people: [] };
|
|
6145
|
+
group.rows.push(perJoin[index]);
|
|
6146
|
+
group.people.push(person);
|
|
6147
|
+
groups.set(key, group);
|
|
6148
|
+
});
|
|
6149
|
+
const rows = [...groups.values()].map(({ rows: joins }) => {
|
|
6150
|
+
if (joins.length === 1) return joins[0];
|
|
6151
|
+
const came = joins.filter((row) => row.joinedAt !== null);
|
|
6152
|
+
const first = [...came].sort((a, b) => (ms(a.joinedAt) ?? 0) - (ms(b.joinedAt) ?? 0))[0] ?? joins[0];
|
|
6153
|
+
const kind = joins.reduce(
|
|
6154
|
+
(best, row) => KIND_RANK[row.kind] < KIND_RANK[best] ? row.kind : best,
|
|
6155
|
+
first.kind
|
|
6156
|
+
);
|
|
6157
|
+
const open = came.some((row) => row.leftAt === null);
|
|
6158
|
+
const lastLeave = came.reduce((latest, row) => {
|
|
6159
|
+
const left = ms(row.leftAt);
|
|
6160
|
+
return left !== null && (latest === null || left > latest) ? left : latest;
|
|
6161
|
+
}, null);
|
|
6162
|
+
const intervals = came.flatMap((row) => {
|
|
6163
|
+
const joined = ms(row.joinedAt);
|
|
6164
|
+
return joined !== null && row.durationMs !== null ? [[joined, joined + row.durationMs]] : [];
|
|
6165
|
+
});
|
|
6166
|
+
return {
|
|
6167
|
+
...first,
|
|
6168
|
+
kind,
|
|
6169
|
+
leftAt: open ? null : lastLeave === null ? null : new Date(lastLeave).toISOString(),
|
|
6170
|
+
durationMs: intervals.length === 0 ? null : coveredMs(intervals),
|
|
6171
|
+
durationToEnd: came.some((row) => row.durationToEnd),
|
|
6172
|
+
joins: came.length === 0 ? joins.length : came.length
|
|
6173
|
+
};
|
|
6174
|
+
});
|
|
6175
|
+
const rank = KIND_RANK;
|
|
6176
|
+
return rows.sort((a, b) => {
|
|
6177
|
+
if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
|
|
6178
|
+
return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
|
|
6179
|
+
});
|
|
6180
|
+
}
|
|
6181
|
+
function attendanceTotals(rows) {
|
|
6182
|
+
const humans = rows.filter((row) => row.kind !== "assistant");
|
|
6183
|
+
const attended = humans.filter((row) => row.joinedAt !== null);
|
|
6184
|
+
const timed = attended.filter((row) => row.durationMs !== null);
|
|
6185
|
+
return {
|
|
6186
|
+
people: attended.length,
|
|
6187
|
+
guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
|
|
6188
|
+
neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
|
|
6189
|
+
averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
|
|
6190
|
+
};
|
|
6191
|
+
}
|
|
6192
|
+
function csvCell(value) {
|
|
6193
|
+
if (value === null) return "";
|
|
6194
|
+
const text3 = String(value);
|
|
6195
|
+
return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
|
|
6196
|
+
}
|
|
6197
|
+
var ATTENDANCE_CSV_HEADER = [
|
|
6198
|
+
"Meeting",
|
|
6199
|
+
"Meeting started",
|
|
6200
|
+
"Name",
|
|
6201
|
+
"Attended as",
|
|
6202
|
+
"Joined",
|
|
6203
|
+
"Left",
|
|
6204
|
+
"Minutes in the meeting",
|
|
6205
|
+
"Admission"
|
|
6206
|
+
];
|
|
6207
|
+
function attendanceCsv(reports) {
|
|
6208
|
+
const lines = [ATTENDANCE_CSV_HEADER.join(",")];
|
|
6209
|
+
for (const { meeting, rows } of reports) {
|
|
6210
|
+
for (const row of rows) {
|
|
6211
|
+
lines.push(
|
|
6212
|
+
[
|
|
6213
|
+
meeting.title,
|
|
6214
|
+
meeting.startedAt ?? meeting.scheduledFor,
|
|
6215
|
+
row.name,
|
|
6216
|
+
row.kind,
|
|
6217
|
+
row.joinedAt,
|
|
6218
|
+
row.leftAt,
|
|
6219
|
+
row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
|
|
6220
|
+
row.admission
|
|
6221
|
+
].map(csvCell).join(",")
|
|
6222
|
+
);
|
|
6223
|
+
}
|
|
6224
|
+
}
|
|
6225
|
+
return `${lines.join("\r\n")}\r
|
|
6226
|
+
`;
|
|
6227
|
+
}
|
|
6228
|
+
function recordingOffsetMs(at, recording) {
|
|
6229
|
+
if (recording === null || recording.state !== "available") return null;
|
|
6230
|
+
const start = ms(recording.startedAt);
|
|
6231
|
+
const moment = ms(at);
|
|
6232
|
+
if (start === null || moment === null || moment < start) return null;
|
|
6233
|
+
const end = ms(recording.endedAt);
|
|
6234
|
+
if (end !== null && moment > end) return null;
|
|
6235
|
+
return moment - start;
|
|
6236
|
+
}
|
|
6237
|
+
function clockPosition(offsetMs) {
|
|
6238
|
+
const total = Math.max(0, Math.floor(offsetMs / 1e3));
|
|
6239
|
+
const h = Math.floor(total / 3600);
|
|
6240
|
+
const m = Math.floor(total % 3600 / 60);
|
|
6241
|
+
const s = total % 60;
|
|
6242
|
+
const ss = String(s).padStart(2, "0");
|
|
6243
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
6244
|
+
}
|
|
6245
|
+
function searchTranscript(segments, query, names = {}) {
|
|
6246
|
+
const needle = fold(query.trim());
|
|
6247
|
+
if (needle.length === 0) return [];
|
|
6248
|
+
const out = [];
|
|
6249
|
+
for (const segment of segments) {
|
|
6250
|
+
const hay = fold(segment.text);
|
|
6251
|
+
const ranges = [];
|
|
6252
|
+
let from = 0;
|
|
6253
|
+
for (; ; ) {
|
|
6254
|
+
const at = hay.indexOf(needle, from);
|
|
6255
|
+
if (at < 0) break;
|
|
6256
|
+
ranges.push([at, at + needle.length]);
|
|
6257
|
+
from = at + needle.length;
|
|
6258
|
+
}
|
|
6259
|
+
const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
|
|
6260
|
+
if (ranges.length > 0 || speaker.includes(needle)) {
|
|
6261
|
+
out.push({ segmentId: segment.id, ranges });
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
return out;
|
|
6265
|
+
}
|
|
6266
|
+
function fold(value) {
|
|
6267
|
+
return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
|
|
6268
|
+
}
|
|
6269
|
+
function clockOfDay(iso, options) {
|
|
6270
|
+
const at = new Date(iso);
|
|
6271
|
+
if (Number.isNaN(at.getTime())) return "";
|
|
6272
|
+
return at.toLocaleTimeString(options.locale, {
|
|
6273
|
+
hour: "numeric",
|
|
6274
|
+
minute: "2-digit",
|
|
6275
|
+
second: "2-digit",
|
|
6276
|
+
...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
|
|
6277
|
+
});
|
|
6278
|
+
}
|
|
6279
|
+
function vttStamp(offsetMs) {
|
|
6280
|
+
const total = Math.max(0, Math.round(offsetMs));
|
|
6281
|
+
const h = Math.floor(total / 36e5);
|
|
6282
|
+
const m = Math.floor(total % 36e5 / 6e4);
|
|
6283
|
+
const s = Math.floor(total % 6e4 / 1e3);
|
|
6284
|
+
const milli = total % 1e3;
|
|
6285
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
|
|
6286
|
+
}
|
|
6287
|
+
function transcriptDocument(bundle, format, options = {}) {
|
|
6288
|
+
const names = options.names ?? bundle.names;
|
|
6289
|
+
const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
|
|
6290
|
+
const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
|
|
6291
|
+
if (format === "vtt") {
|
|
6292
|
+
const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
|
|
6293
|
+
const zero = ms(zeroIso) ?? 0;
|
|
6294
|
+
const cues = ["WEBVTT", ""];
|
|
6295
|
+
lines.forEach((line, index) => {
|
|
6296
|
+
const start = (ms(line.startedAt) ?? zero) - zero;
|
|
6297
|
+
if (start < 0) return;
|
|
6298
|
+
const next = lines[index + 1];
|
|
6299
|
+
const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
|
|
6300
|
+
const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
|
|
6301
|
+
cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
|
|
6302
|
+
cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
|
|
6303
|
+
cues.push("");
|
|
6304
|
+
});
|
|
6305
|
+
return cues.join("\n");
|
|
6306
|
+
}
|
|
6307
|
+
const out = [];
|
|
6308
|
+
const title = bundle.meeting.title;
|
|
6309
|
+
out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
|
|
6310
|
+
out.push("");
|
|
6311
|
+
let last = null;
|
|
6312
|
+
for (const line of lines) {
|
|
6313
|
+
if (line.identity !== last) {
|
|
6314
|
+
if (last !== null) out.push("");
|
|
6315
|
+
const who = speaker(line);
|
|
6316
|
+
const clock = clockOfDay(line.startedAt, options);
|
|
6317
|
+
out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
|
|
6318
|
+
last = line.identity;
|
|
6319
|
+
}
|
|
6320
|
+
out.push(line.text.trim());
|
|
6321
|
+
}
|
|
6322
|
+
if (lines.length === 0) out.push("No transcript was captured for this meeting.");
|
|
6323
|
+
return `${out.join("\n")}
|
|
6324
|
+
`;
|
|
6325
|
+
}
|
|
6326
|
+
function bullet(note) {
|
|
6327
|
+
const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
|
|
6328
|
+
return `- ${note.text.trim()}${owner}`;
|
|
6329
|
+
}
|
|
6330
|
+
function notesDocument(bundle, options = {}) {
|
|
6331
|
+
const out = [`# ${bundle.meeting.title}`, ""];
|
|
6332
|
+
if (options.when) out.push(`_${options.when}_`, "");
|
|
6333
|
+
const people = uniqueAttendees(
|
|
6334
|
+
bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null),
|
|
6335
|
+
bundle.names
|
|
6336
|
+
).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
|
|
6337
|
+
if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
|
|
6338
|
+
out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
|
|
6339
|
+
out.push("## Decisions", "");
|
|
6340
|
+
out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
|
|
6341
|
+
out.push("## Action items", "");
|
|
6342
|
+
out.push(
|
|
6343
|
+
...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
|
|
6344
|
+
""
|
|
6345
|
+
);
|
|
6346
|
+
if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
|
|
6347
|
+
return out.join("\n");
|
|
6348
|
+
}
|
|
6349
|
+
function meetingLength(meeting) {
|
|
6350
|
+
const start = ms(meeting.startedAt);
|
|
6351
|
+
const end = ms(meeting.endedAt);
|
|
6352
|
+
if (start === null || end === null || end < start) return null;
|
|
6353
|
+
return formatDurationMs2(end - start, { style: "long" });
|
|
6354
|
+
}
|
|
6355
|
+
async function loadMeetingChat(api, meetingId, roomToken = null) {
|
|
6356
|
+
const operation = "meet.loadMeetingChat";
|
|
6357
|
+
let response;
|
|
6358
|
+
try {
|
|
6359
|
+
response = await api.request({
|
|
6360
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
6361
|
+
method: "GET",
|
|
6362
|
+
operation,
|
|
6363
|
+
requireAuth: roomToken === null,
|
|
6364
|
+
...roomToken !== null ? { headers: { [ROOM_TOKEN_HEADER]: roomToken } } : {}
|
|
6365
|
+
});
|
|
6366
|
+
} catch (error) {
|
|
6367
|
+
return {
|
|
6368
|
+
messages: [],
|
|
6369
|
+
refusal: {
|
|
6370
|
+
message: `The chat could not be read (${error.message}).`,
|
|
6371
|
+
remedy: error.remedy ?? "Reload the page."
|
|
6372
|
+
}
|
|
6373
|
+
};
|
|
6374
|
+
}
|
|
6375
|
+
if (!response.ok) {
|
|
6376
|
+
let body = null;
|
|
6377
|
+
try {
|
|
6378
|
+
body = await response.json();
|
|
6379
|
+
} catch {
|
|
6380
|
+
body = null;
|
|
6381
|
+
}
|
|
6382
|
+
const said = readServerRefusal(body);
|
|
6383
|
+
return {
|
|
6384
|
+
messages: [],
|
|
6385
|
+
refusal: {
|
|
6386
|
+
message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
|
|
6387
|
+
remedy: said?.remedy || "Reload the page."
|
|
6388
|
+
}
|
|
6389
|
+
};
|
|
6390
|
+
}
|
|
6391
|
+
const payload = await response.json();
|
|
6392
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
6393
|
+
return {
|
|
6394
|
+
messages: rows.map(
|
|
6395
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
6396
|
+
).filter((message) => message !== null),
|
|
6397
|
+
refusal: null
|
|
6398
|
+
};
|
|
6399
|
+
}
|
|
6400
|
+
function refusalOf(response, operation) {
|
|
6401
|
+
return refusalFromResponse(response, operation, "Nothing was sent. Try again.");
|
|
6402
|
+
}
|
|
6403
|
+
function asRecipient(row) {
|
|
6404
|
+
const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
|
|
6405
|
+
return {
|
|
6406
|
+
key: String(row["key"] ?? ""),
|
|
6407
|
+
name: String(row["name"] ?? ""),
|
|
6408
|
+
email: typeof row["email"] === "string" ? row["email"] : null,
|
|
6409
|
+
userId: typeof row["user_id"] === "string" ? row["user_id"] : null,
|
|
6410
|
+
sources: sources.filter((s) => s === "invitee" || s === "attendee"),
|
|
6411
|
+
reachable: row["reachable"] !== false
|
|
6412
|
+
};
|
|
6413
|
+
}
|
|
6414
|
+
async function loadFollowUpDraft(api, meetingId) {
|
|
6415
|
+
const operation = "meet.loadFollowUpDraft";
|
|
6416
|
+
const response = await api.request({
|
|
6417
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
6418
|
+
method: "GET",
|
|
6419
|
+
requireAuth: true,
|
|
6420
|
+
operation
|
|
6421
|
+
});
|
|
6422
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
6423
|
+
const body = await response.json();
|
|
6424
|
+
const last = body["last_sent"];
|
|
6425
|
+
return {
|
|
6426
|
+
subject: String(body["subject"] ?? ""),
|
|
6427
|
+
body: String(body["body"] ?? ""),
|
|
6428
|
+
recipients: (Array.isArray(body["recipients"]) ? body["recipients"] : []).map(
|
|
6429
|
+
(row) => asRecipient(row)
|
|
6430
|
+
),
|
|
6431
|
+
wrapUpWritten: body["wrap_up_written"] === true,
|
|
6432
|
+
lastSent: last && typeof last["at"] === "string" ? { at: last["at"], count: typeof last["count"] === "number" ? last["count"] : 0 } : null
|
|
6433
|
+
};
|
|
6434
|
+
}
|
|
6435
|
+
async function sendFollowUp(api, meetingId, input) {
|
|
6436
|
+
const operation = "meet.sendFollowUp";
|
|
6437
|
+
const response = await api.request({
|
|
6438
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
6439
|
+
method: "POST",
|
|
6440
|
+
requireAuth: true,
|
|
6441
|
+
operation,
|
|
6442
|
+
body: {
|
|
6443
|
+
subject: input.subject,
|
|
6444
|
+
body: input.body,
|
|
6445
|
+
recipient_keys: [...input.recipientKeys],
|
|
6446
|
+
dry_run: input.dryRun === true
|
|
6447
|
+
}
|
|
6448
|
+
});
|
|
6449
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
6450
|
+
const body = await response.json();
|
|
6451
|
+
return {
|
|
6452
|
+
dryRun: body["dry_run"] === true,
|
|
6453
|
+
queued: Array.isArray(body["queued"]) ? body["queued"] : [],
|
|
6454
|
+
skipped: Array.isArray(body["skipped"]) ? body["skipped"].map((row) => ({
|
|
6455
|
+
key: String(row["key"] ?? ""),
|
|
6456
|
+
reason: String(row["reason"] ?? "")
|
|
6457
|
+
})) : []
|
|
6458
|
+
};
|
|
6459
|
+
}
|
|
5903
6460
|
|
|
5904
6461
|
// src/core/format.ts
|
|
5905
6462
|
function participantSummary(participants) {
|
|
@@ -6349,6 +6906,15 @@ function ControlBar({ onLeave }) {
|
|
|
6349
6906
|
const micUnavailable = useLocalMedia().microphone.unavailable;
|
|
6350
6907
|
const unread = useUnreadChat(panel === "chat");
|
|
6351
6908
|
const toggle = (next) => setPanel(panel === next ? "none" : next);
|
|
6909
|
+
const privateChatAt = snapshot?.privateChatRequest?.at ?? null;
|
|
6910
|
+
const meetStore = useMeetHost()?.store ?? null;
|
|
6911
|
+
useEffect3(() => {
|
|
6912
|
+
const request = meetStore?.snapshot().privateChatRequest ?? null;
|
|
6913
|
+
if (request === null) return;
|
|
6914
|
+
setChatTo(request.to);
|
|
6915
|
+
setPanel("chat");
|
|
6916
|
+
meetStore?.patch({ privateChatRequest: null });
|
|
6917
|
+
}, [privateChatAt, meetStore]);
|
|
6352
6918
|
useMeetShortcuts({
|
|
6353
6919
|
mic: () => void room.toggleMicrophone(),
|
|
6354
6920
|
camera: () => void room.toggleCamera(),
|
|
@@ -6588,7 +7154,12 @@ function Popover({
|
|
|
6588
7154
|
placement = "up"
|
|
6589
7155
|
}) {
|
|
6590
7156
|
const ref = useRef3(null);
|
|
6591
|
-
const [style, setStyle] = useState3({
|
|
7157
|
+
const [style, setStyle] = useState3({
|
|
7158
|
+
position: "fixed",
|
|
7159
|
+
top: 0,
|
|
7160
|
+
left: 0,
|
|
7161
|
+
visibility: "hidden"
|
|
7162
|
+
});
|
|
6592
7163
|
useLayoutEffect(() => {
|
|
6593
7164
|
const element = ref.current;
|
|
6594
7165
|
const anchor = element?.parentElement ?? null;
|
|
@@ -6604,14 +7175,35 @@ function Popover({
|
|
|
6604
7175
|
);
|
|
6605
7176
|
const fitsBelow = rect.bottom + gutter + height <= window.innerHeight;
|
|
6606
7177
|
const fitsAbove = rect.top - gutter - height >= 0;
|
|
7178
|
+
if (!fitsBelow && !fitsAbove) {
|
|
7179
|
+
const top = Math.max(gutter, window.innerHeight - height - gutter);
|
|
7180
|
+
setStyle({
|
|
7181
|
+
position: "fixed",
|
|
7182
|
+
left,
|
|
7183
|
+
top,
|
|
7184
|
+
maxHeight: window.innerHeight - top - gutter,
|
|
7185
|
+
overflowY: "auto"
|
|
7186
|
+
});
|
|
7187
|
+
return;
|
|
7188
|
+
}
|
|
6607
7189
|
const down = placement === "down" ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;
|
|
6608
7190
|
setStyle(
|
|
6609
7191
|
down ? { position: "fixed", left, top: rect.bottom + gutter } : { position: "fixed", left, bottom: window.innerHeight - rect.top + gutter }
|
|
6610
7192
|
);
|
|
6611
7193
|
};
|
|
6612
7194
|
place();
|
|
7195
|
+
const frame = requestAnimationFrame(place);
|
|
7196
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => place());
|
|
7197
|
+
observer?.observe(document.body);
|
|
7198
|
+
observer?.observe(element);
|
|
6613
7199
|
window.addEventListener("resize", place);
|
|
6614
|
-
|
|
7200
|
+
window.addEventListener("scroll", place, true);
|
|
7201
|
+
return () => {
|
|
7202
|
+
cancelAnimationFrame(frame);
|
|
7203
|
+
observer?.disconnect();
|
|
7204
|
+
window.removeEventListener("resize", place);
|
|
7205
|
+
window.removeEventListener("scroll", place, true);
|
|
7206
|
+
};
|
|
6615
7207
|
}, [placement]);
|
|
6616
7208
|
useEffect3(() => {
|
|
6617
7209
|
const onKey = (event) => {
|
|
@@ -6662,22 +7254,27 @@ function MeetingOverlays() {
|
|
|
6662
7254
|
const room = useRoom();
|
|
6663
7255
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
6664
7256
|
/* @__PURE__ */ jsx3(ReactionOverlay, { reactions }),
|
|
6665
|
-
notice !== null && notice.kind !== "removed" ?
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
7257
|
+
notice !== null && notice.kind !== "removed" ? (
|
|
7258
|
+
// A host's request ("the host is asking you to unmute"), not an error:
|
|
7259
|
+
// an assertive status, so it is announced at once without being filed
|
|
7260
|
+
// as a failure (design-system error-box census).
|
|
7261
|
+
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__notice-bar", role: "status", "aria-live": "assertive", children: [
|
|
7262
|
+
/* @__PURE__ */ jsx3("span", { children: notice.text }),
|
|
7263
|
+
notice.kind === "ask-unmute" && room.local?.micEnabled !== true ? /* @__PURE__ */ jsx3(
|
|
7264
|
+
"button",
|
|
7265
|
+
{
|
|
7266
|
+
type: "button",
|
|
7267
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
7268
|
+
onClick: () => {
|
|
7269
|
+
dismiss();
|
|
7270
|
+
void room.toggleMicrophone();
|
|
7271
|
+
},
|
|
7272
|
+
children: "Unmute"
|
|
7273
|
+
}
|
|
7274
|
+
) : null,
|
|
7275
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ jsx3(CloseIcon, { width: 14, height: 14 }) })
|
|
7276
|
+
] })
|
|
7277
|
+
) : null
|
|
6681
7278
|
] });
|
|
6682
7279
|
}
|
|
6683
7280
|
function ShortcutsSheet({ onClose }) {
|
|
@@ -6983,11 +7580,15 @@ function PersonMenu({
|
|
|
6983
7580
|
const pinned = snapshot?.pinned === participant.identity;
|
|
6984
7581
|
const spotlit = snapshot?.spotlight === participant.identity;
|
|
6985
7582
|
const hasAccount = participant.userId !== null;
|
|
7583
|
+
const [refused, setRefused] = useState3(false);
|
|
6986
7584
|
const act = (request) => {
|
|
7585
|
+
setRefused(false);
|
|
6987
7586
|
void controls.run(request).then((result) => {
|
|
6988
7587
|
if (result !== null) onClose();
|
|
7588
|
+
else setRefused(true);
|
|
6989
7589
|
});
|
|
6990
7590
|
};
|
|
7591
|
+
const refusal2 = refused && controls.notice?.failed === true ? /* @__PURE__ */ jsx3(ErrorBox, { as: "p", className: "mx-meet__error", message: controls.notice.text, operation: "Change this participant", origin: "meet/components", children: controls.notice.text }) : null;
|
|
6991
7592
|
if (mode === "rename" || mode === "report") {
|
|
6992
7593
|
const renaming = mode === "rename";
|
|
6993
7594
|
return /* @__PURE__ */ jsx3(Popover, { label: renaming ? "Rename" : "Report", onClose, placement, children: /* @__PURE__ */ jsxs2(
|
|
@@ -7025,7 +7626,7 @@ function PersonMenu({
|
|
|
7025
7626
|
}
|
|
7026
7627
|
)
|
|
7027
7628
|
] }),
|
|
7028
|
-
|
|
7629
|
+
refusal2,
|
|
7029
7630
|
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-actions", children: [
|
|
7030
7631
|
/* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Back" }),
|
|
7031
7632
|
/* @__PURE__ */ jsx3("button", { type: "submit", className: "mx-meet__send", disabled: text3.trim().length === 0 || controls.busy, children: renaming ? "Rename" : "Report" })
|
|
@@ -7038,6 +7639,7 @@ function PersonMenu({
|
|
|
7038
7639
|
const removing = mode === "remove";
|
|
7039
7640
|
return /* @__PURE__ */ jsx3(Popover, { label: removing ? "Remove" : "Make host", onClose, placement, children: /* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-form", children: [
|
|
7040
7641
|
/* @__PURE__ */ jsx3("p", { className: "mx-meet__confirm-text", children: removing ? `Remove ${participant.displayName}? They leave immediately and cannot rejoin this meeting.` : `Make ${participant.displayName} the host? They get every host control and own this meeting. You stay on as a co-host.` }),
|
|
7642
|
+
refusal2,
|
|
7041
7643
|
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-actions", children: [
|
|
7042
7644
|
/* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Cancel" }),
|
|
7043
7645
|
/* @__PURE__ */ jsx3(
|
|
@@ -7068,11 +7670,15 @@ function PersonMenu({
|
|
|
7068
7670
|
]
|
|
7069
7671
|
}
|
|
7070
7672
|
),
|
|
7071
|
-
!self && !participant.isAgent && onMessage !== void 0 ? /* @__PURE__ */ jsxs2(
|
|
7673
|
+
!self && !participant.isAgent && (onMessage !== void 0 || host !== null) ? /* @__PURE__ */ jsxs2(
|
|
7072
7674
|
MenuItem,
|
|
7073
7675
|
{
|
|
7074
7676
|
onSelect: () => {
|
|
7075
|
-
onMessage(participant.identity);
|
|
7677
|
+
if (onMessage !== void 0) onMessage(participant.identity);
|
|
7678
|
+
else
|
|
7679
|
+
host?.store.patch({
|
|
7680
|
+
privateChatRequest: { to: participant.identity, at: Date.now() }
|
|
7681
|
+
});
|
|
7076
7682
|
onClose();
|
|
7077
7683
|
},
|
|
7078
7684
|
children: [
|
|
@@ -7699,7 +8305,7 @@ function PreJoinBody({
|
|
|
7699
8305
|
microphone,
|
|
7700
8306
|
...meetingId !== void 0 ? { meetingId } : {},
|
|
7701
8307
|
...slug !== void 0 ? { slug } : {}
|
|
7702
|
-
}).then(() => onJoined?.()).finally(() => setJoining(false));
|
|
8308
|
+
}).then(() => onJoined?.()).catch(() => void 0).finally(() => setJoining(false));
|
|
7703
8309
|
},
|
|
7704
8310
|
children: joining ? "Joining\u2026" : "Join now"
|
|
7705
8311
|
}
|
|
@@ -7835,7 +8441,7 @@ function MeetingRoomBody({
|
|
|
7835
8441
|
/* @__PURE__ */ jsx3("h1", { className: "mx-meet__title", children: snapshot?.meeting?.title ?? "Meeting" }),
|
|
7836
8442
|
/* @__PURE__ */ jsxs2("span", { className: "mx-meet__meta", children: [
|
|
7837
8443
|
participantSummary(snapshot?.participants ?? []),
|
|
7838
|
-
elapsed > 0 ? ` \xB7 ${
|
|
8444
|
+
elapsed > 0 ? ` \xB7 ${formatDurationMs3(elapsed)}` : ""
|
|
7839
8445
|
] }),
|
|
7840
8446
|
/* @__PURE__ */ jsx3(RecordingIndicator, {}),
|
|
7841
8447
|
snapshot?.locked === true ? /* @__PURE__ */ jsxs2("span", { className: "mx-meet__badge", children: [
|
|
@@ -8141,7 +8747,10 @@ function MeetingRecordBody({
|
|
|
8141
8747
|
const blocks = groupTranscript(bundle.transcript, bundle.names);
|
|
8142
8748
|
const notice = recordingNotice(bundle.recording);
|
|
8143
8749
|
const fileId = playableRecordingFileId(bundle.recording);
|
|
8144
|
-
const attended =
|
|
8750
|
+
const attended = uniqueAttendees(
|
|
8751
|
+
bundle.attendees.filter((person) => person.admissionState !== "denied"),
|
|
8752
|
+
bundle.names
|
|
8753
|
+
);
|
|
8145
8754
|
const restrictionNotices = summarizeMeetingRecordRestrictions(bundle.restrictions);
|
|
8146
8755
|
const guestFinishedMeeting = host.identity.userId === null && meeting.endedAt !== null;
|
|
8147
8756
|
const everyRecordPartRefused = ["attendance", "notes", "transcript", "recording"].every(
|
|
@@ -8611,18 +9220,11 @@ function createMeetingInviteHandler(options) {
|
|
|
8611
9220
|
}
|
|
8612
9221
|
|
|
8613
9222
|
// src/core/management.ts
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
body = null;
|
|
8620
|
-
}
|
|
8621
|
-
const said = readServerRefusal(body);
|
|
8622
|
-
return new MeetError(
|
|
8623
|
-
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
8624
|
-
`${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
|
|
8625
|
-
said?.remedy || "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
|
|
9223
|
+
function refusal(response, operation) {
|
|
9224
|
+
return refusalFromResponse(
|
|
9225
|
+
response,
|
|
9226
|
+
operation,
|
|
9227
|
+
"The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
|
|
8626
9228
|
);
|
|
8627
9229
|
}
|
|
8628
9230
|
function count(body, key) {
|
|
@@ -8661,6 +9263,7 @@ async function pushMeetingToGoogleCalendar(api, meetingId, target) {
|
|
|
8661
9263
|
return await response.json();
|
|
8662
9264
|
}
|
|
8663
9265
|
export {
|
|
9266
|
+
ATTENDANCE_CSV_HEADER,
|
|
8664
9267
|
AiPanel,
|
|
8665
9268
|
AttendanceNotice,
|
|
8666
9269
|
Avatar,
|
|
@@ -8764,6 +9367,7 @@ export {
|
|
|
8764
9367
|
TRANSCRIPTION_TOPIC,
|
|
8765
9368
|
WifiLowIcon,
|
|
8766
9369
|
absenceSentence,
|
|
9370
|
+
activeWrapUpSummary,
|
|
8767
9371
|
announceMeeting,
|
|
8768
9372
|
applyRoomEvent,
|
|
8769
9373
|
asCallId,
|
|
@@ -8772,6 +9376,10 @@ export {
|
|
|
8772
9376
|
asParticipantIdentity,
|
|
8773
9377
|
asRoomName,
|
|
8774
9378
|
asUserId,
|
|
9379
|
+
attendanceCsv,
|
|
9380
|
+
attendanceReport,
|
|
9381
|
+
attendanceTotals,
|
|
9382
|
+
attendeeKey,
|
|
8775
9383
|
callInviteAction,
|
|
8776
9384
|
callInviteText,
|
|
8777
9385
|
callStateLabel,
|
|
@@ -8780,6 +9388,7 @@ export {
|
|
|
8780
9388
|
canTransition,
|
|
8781
9389
|
captionSegment,
|
|
8782
9390
|
changesPatch,
|
|
9391
|
+
clockPosition,
|
|
8783
9392
|
computeStage,
|
|
8784
9393
|
countLabel,
|
|
8785
9394
|
createCallCenter,
|
|
@@ -8820,11 +9429,14 @@ export {
|
|
|
8820
9429
|
learnName,
|
|
8821
9430
|
learnNames,
|
|
8822
9431
|
listenOnlyNotice,
|
|
9432
|
+
loadFollowUpDraft,
|
|
8823
9433
|
loadGuestMeetingRecord,
|
|
9434
|
+
loadMeetingChat,
|
|
8824
9435
|
loadMeetingRecord,
|
|
8825
9436
|
mapConnectionState,
|
|
8826
9437
|
mapQuality,
|
|
8827
9438
|
meetingInviteAction,
|
|
9439
|
+
meetingLength,
|
|
8828
9440
|
meetingLink,
|
|
8829
9441
|
meetingPath,
|
|
8830
9442
|
meetingSlug,
|
|
@@ -8839,16 +9451,19 @@ export {
|
|
|
8839
9451
|
noteTakerLabel,
|
|
8840
9452
|
noteTakerParticipant,
|
|
8841
9453
|
noteTakerStatus,
|
|
9454
|
+
notesDocument,
|
|
8842
9455
|
orderParticipants,
|
|
8843
9456
|
parseRoomToken,
|
|
8844
9457
|
participantSummary,
|
|
8845
9458
|
playableRecordingFileId,
|
|
8846
9459
|
projectInvitee,
|
|
9460
|
+
projectLibraryRecording,
|
|
8847
9461
|
projectNote,
|
|
8848
9462
|
projectOccurrence,
|
|
8849
9463
|
projectOverride,
|
|
8850
9464
|
projectParticipant,
|
|
8851
9465
|
projectRsvpInvitation,
|
|
9466
|
+
projectSearchHit,
|
|
8852
9467
|
projectSegment,
|
|
8853
9468
|
projectUpcoming,
|
|
8854
9469
|
pushMeetingToGoogleCalendar,
|
|
@@ -8856,12 +9471,19 @@ export {
|
|
|
8856
9471
|
readableTranscript,
|
|
8857
9472
|
recordingLabel,
|
|
8858
9473
|
recordingNotice,
|
|
9474
|
+
recordingOffsetMs,
|
|
8859
9475
|
roleOf,
|
|
8860
9476
|
roomTopic,
|
|
9477
|
+
searchTranscript,
|
|
9478
|
+
sendFollowUp,
|
|
8861
9479
|
setGuestRecordAccess,
|
|
8862
9480
|
settingsPatch,
|
|
8863
9481
|
snapshotParticipant,
|
|
9482
|
+
snippetRuns,
|
|
9483
|
+
summarizeMeetingRecordRestrictions,
|
|
9484
|
+
transcriptDocument,
|
|
8864
9485
|
unavailableFrom,
|
|
9486
|
+
uniqueAttendees,
|
|
8865
9487
|
useAudioLevel,
|
|
8866
9488
|
useCalls,
|
|
8867
9489
|
useCanModerate,
|
|
@@ -8896,6 +9518,7 @@ export {
|
|
|
8896
9518
|
useStage,
|
|
8897
9519
|
useTrack,
|
|
8898
9520
|
userTopic,
|
|
8899
|
-
wasRefused
|
|
9521
|
+
wasRefused,
|
|
9522
|
+
wrapUpStatus
|
|
8900
9523
|
};
|
|
8901
9524
|
//# sourceMappingURL=react.js.map
|