@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.js
CHANGED
|
@@ -192,6 +192,12 @@ var MEET_ROUTES = {
|
|
|
192
192
|
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
193
193
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
194
194
|
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
195
|
+
/**
|
|
196
|
+
* Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,
|
|
197
|
+
* body from the wrap-up, who it can go to); `POST` = send what the host
|
|
198
|
+
* reviewed (`dry_run` renders and queues nothing). Never sent automatically.
|
|
199
|
+
*/
|
|
200
|
+
meetingFollowUp: "/api/v1/meet/meetings/{meeting_id}/follow-up",
|
|
195
201
|
/**
|
|
196
202
|
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
197
203
|
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
@@ -321,10 +327,11 @@ function createTokenClient(options) {
|
|
|
321
327
|
throw error;
|
|
322
328
|
}
|
|
323
329
|
if (response.status === 401 || response.status === 403) {
|
|
330
|
+
const said = await serverSentence(response);
|
|
324
331
|
throw new MeetError(
|
|
325
332
|
"forbidden",
|
|
326
|
-
|
|
327
|
-
"
|
|
333
|
+
said?.message ?? "You can't join this meeting.",
|
|
334
|
+
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again."
|
|
328
335
|
);
|
|
329
336
|
}
|
|
330
337
|
if (response.status === 404) {
|
|
@@ -335,12 +342,7 @@ function createTokenClient(options) {
|
|
|
335
342
|
);
|
|
336
343
|
}
|
|
337
344
|
if (response.status === 410) {
|
|
338
|
-
|
|
339
|
-
try {
|
|
340
|
-
const body = await response.json();
|
|
341
|
-
if (typeof body?.detail?.message === "string") reason = body.detail.message;
|
|
342
|
-
} catch {
|
|
343
|
-
}
|
|
345
|
+
const reason = (await serverSentence(response))?.message ?? "This meeting was cancelled.";
|
|
344
346
|
throw new MeetError(
|
|
345
347
|
"cancelled",
|
|
346
348
|
reason,
|
|
@@ -348,10 +350,11 @@ function createTokenClient(options) {
|
|
|
348
350
|
);
|
|
349
351
|
}
|
|
350
352
|
if (response.status === 423) {
|
|
353
|
+
const said = await serverSentence(response);
|
|
351
354
|
throw new MeetError(
|
|
352
355
|
"room-locked",
|
|
353
|
-
"
|
|
354
|
-
"The host locked the room. Ask them to unlock
|
|
356
|
+
said?.message ?? "The meeting is locked.",
|
|
357
|
+
said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again."
|
|
355
358
|
);
|
|
356
359
|
}
|
|
357
360
|
if (response.status === 503) {
|
|
@@ -422,6 +425,19 @@ function placeholder(roomName) {
|
|
|
422
425
|
lobby: false
|
|
423
426
|
};
|
|
424
427
|
}
|
|
428
|
+
async function serverSentence(response) {
|
|
429
|
+
try {
|
|
430
|
+
const body = await response.json();
|
|
431
|
+
const source = body !== null && typeof body.detail === "object" && body.detail !== null ? body.detail : body;
|
|
432
|
+
if (source === null) return null;
|
|
433
|
+
const message = typeof source.user_message === "string" && source.user_message.trim() !== "" ? source.user_message : typeof source.message === "string" && source.message.trim() !== "" ? source.message : null;
|
|
434
|
+
if (message === null) return null;
|
|
435
|
+
const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
|
|
436
|
+
return { message, remedy };
|
|
437
|
+
} catch {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
425
441
|
|
|
426
442
|
// src/core/ai.ts
|
|
427
443
|
var MEET_ANSWER_EVENTS = {
|
|
@@ -466,6 +482,29 @@ function readText(data) {
|
|
|
466
482
|
}
|
|
467
483
|
return "";
|
|
468
484
|
}
|
|
485
|
+
var SERVER_FAILED_SENTENCE = "The server hit an unexpected problem and nothing was changed.";
|
|
486
|
+
function isRawServerText(value) {
|
|
487
|
+
const text3 = value.trim();
|
|
488
|
+
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);
|
|
489
|
+
}
|
|
490
|
+
async function refusalFromResponse(response, operation, fallbackRemedy) {
|
|
491
|
+
let body = null;
|
|
492
|
+
try {
|
|
493
|
+
body = await response.json();
|
|
494
|
+
} catch {
|
|
495
|
+
body = null;
|
|
496
|
+
}
|
|
497
|
+
const said = readServerRefusal(body);
|
|
498
|
+
const code = response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport";
|
|
499
|
+
const error = new MeetError(
|
|
500
|
+
code,
|
|
501
|
+
said?.reason ?? SERVER_FAILED_SENTENCE,
|
|
502
|
+
said !== null && said.remedy.length > 0 ? said.remedy : fallbackRemedy
|
|
503
|
+
);
|
|
504
|
+
error.operation = operation;
|
|
505
|
+
error.status = response.status;
|
|
506
|
+
return error;
|
|
507
|
+
}
|
|
469
508
|
function readServerRefusal(body) {
|
|
470
509
|
if (typeof body === "string") {
|
|
471
510
|
return body.trim().length === 0 ? null : { reason: body.trim(), remedy: "" };
|
|
@@ -484,7 +523,8 @@ function readServerRefusal(body) {
|
|
|
484
523
|
return messages.length === 0 ? null : { reason: messages.join("; "), remedy: "" };
|
|
485
524
|
}
|
|
486
525
|
if (typeof detail === "object" && detail !== null) return readServerRefusal(detail);
|
|
487
|
-
const
|
|
526
|
+
const humanOrNull = (value) => typeof value === "string" && value.trim().length > 0 && !isRawServerText(value) ? value.trim() : null;
|
|
527
|
+
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);
|
|
488
528
|
if (reason === null) return null;
|
|
489
529
|
const remedy = typeof row["remedy"] === "string" ? row["remedy"] : "";
|
|
490
530
|
if (remedy.length > 0) return { reason, remedy };
|
|
@@ -890,7 +930,7 @@ var DEFAULT_RING_TIMEOUT_MS = 45e3;
|
|
|
890
930
|
function createCallCenter(options) {
|
|
891
931
|
const { manager, repository, identity } = options;
|
|
892
932
|
const timers = options.timers ?? {
|
|
893
|
-
setTimeout: (run,
|
|
933
|
+
setTimeout: (run, ms2) => setTimeout(run, ms2),
|
|
894
934
|
clearTimeout: (handle) => clearTimeout(handle),
|
|
895
935
|
now: () => Date.now()
|
|
896
936
|
};
|
|
@@ -1174,7 +1214,16 @@ var RPCS = {
|
|
|
1174
1214
|
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
1175
1215
|
meetingOccurrences: "meet_meeting_occurrences",
|
|
1176
1216
|
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
1177
|
-
occurrencesBetween: "meet_occurrences_between"
|
|
1217
|
+
occurrencesBetween: "meet_occurrences_between",
|
|
1218
|
+
/*
|
|
1219
|
+
* ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
|
|
1220
|
+
* scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
|
|
1221
|
+
* ceiling, never the view.
|
|
1222
|
+
*/
|
|
1223
|
+
/** The recordings library. */
|
|
1224
|
+
myRecordings: "meet_my_recordings",
|
|
1225
|
+
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
1226
|
+
search: "meet_search"
|
|
1178
1227
|
};
|
|
1179
1228
|
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
1180
1229
|
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";
|
|
@@ -1879,12 +1928,117 @@ function createMeetRepository(options) {
|
|
|
1879
1928
|
);
|
|
1880
1929
|
},
|
|
1881
1930
|
async recordings(meetingId) {
|
|
1882
|
-
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: false });
|
|
1931
|
+
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("started_at", { ascending: false });
|
|
1883
1932
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.recordings");
|
|
1884
1933
|
return (response.data ?? []).map(projectRecording);
|
|
1934
|
+
},
|
|
1935
|
+
async myRecordings({ includeArchived = false, limit = 200 } = {}) {
|
|
1936
|
+
const rows = await guard(
|
|
1937
|
+
"meet.myRecordings",
|
|
1938
|
+
() => db.rpc(RPCS.myRecordings, { p_include_archived: includeArchived, p_limit: limit })
|
|
1939
|
+
);
|
|
1940
|
+
return rows.map(projectLibraryRecording);
|
|
1941
|
+
},
|
|
1942
|
+
async searchMeetings(query, limit = 60) {
|
|
1943
|
+
if (query.trim().length === 0) return [];
|
|
1944
|
+
const rows = await guard(
|
|
1945
|
+
"meet.searchMeetings",
|
|
1946
|
+
() => db.rpc(RPCS.search, { p_query: query, p_limit: limit })
|
|
1947
|
+
);
|
|
1948
|
+
return rows.map(projectSearchHit);
|
|
1949
|
+
},
|
|
1950
|
+
async renameRecording(recordingId, title) {
|
|
1951
|
+
const clean = title === null ? null : title.trim().slice(0, 200) || null;
|
|
1952
|
+
const response = await db.from(TABLES.recordings).update({ title: clean }).eq("id", recordingId).select("id");
|
|
1953
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.renameRecording");
|
|
1954
|
+
if ((response.data ?? []).length === 0) {
|
|
1955
|
+
throw normalizeMeetError(
|
|
1956
|
+
{ message: "permission denied: only the meeting's host or an editor can rename it", code: "42501" },
|
|
1957
|
+
"meet.renameRecording"
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
},
|
|
1961
|
+
async setRecordingArchived(recordingId, archived) {
|
|
1962
|
+
const response = await db.from(TABLES.recordings).update({ deleted_at: archived ? (/* @__PURE__ */ new Date()).toISOString() : null }).eq("id", recordingId).select("id");
|
|
1963
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.setRecordingArchived");
|
|
1964
|
+
if ((response.data ?? []).length === 0) {
|
|
1965
|
+
throw normalizeMeetError(
|
|
1966
|
+
{ message: "permission denied: only the meeting's host or an editor can archive it", code: "42501" },
|
|
1967
|
+
"meet.setRecordingArchived"
|
|
1968
|
+
);
|
|
1969
|
+
}
|
|
1885
1970
|
}
|
|
1886
1971
|
};
|
|
1887
1972
|
}
|
|
1973
|
+
var LIBRARY_STATES = [
|
|
1974
|
+
"idle",
|
|
1975
|
+
"starting",
|
|
1976
|
+
"recording",
|
|
1977
|
+
"stopping",
|
|
1978
|
+
"processing",
|
|
1979
|
+
"available",
|
|
1980
|
+
"expired",
|
|
1981
|
+
"failed"
|
|
1982
|
+
];
|
|
1983
|
+
function projectLibraryRecording(row) {
|
|
1984
|
+
const operation = "meet.projectLibraryRecording";
|
|
1985
|
+
const state = text(row, "state");
|
|
1986
|
+
const role = text(row, "my_role");
|
|
1987
|
+
return {
|
|
1988
|
+
id: requireText(row, "recording_id", operation),
|
|
1989
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
1990
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
1991
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
1992
|
+
role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
|
|
1993
|
+
state: LIBRARY_STATES.includes(state) ? state : "failed",
|
|
1994
|
+
title: text(row, "recording_title"),
|
|
1995
|
+
fileId: text(row, "file_id"),
|
|
1996
|
+
startedAt: text(row, "started_at"),
|
|
1997
|
+
endedAt: text(row, "ended_at"),
|
|
1998
|
+
durationMs: num(row, "duration_ms"),
|
|
1999
|
+
sizeBytes: num(row, "size_bytes"),
|
|
2000
|
+
failureReason: text(row, "failure_reason"),
|
|
2001
|
+
archivedAt: text(row, "deleted_at"),
|
|
2002
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation))
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
var SEARCH_SOURCES = [
|
|
2006
|
+
"title",
|
|
2007
|
+
"summary",
|
|
2008
|
+
"decision",
|
|
2009
|
+
"action_item",
|
|
2010
|
+
"note",
|
|
2011
|
+
"transcript"
|
|
2012
|
+
];
|
|
2013
|
+
function projectSearchHit(row) {
|
|
2014
|
+
const operation = "meet.projectSearchHit";
|
|
2015
|
+
const source = text(row, "source");
|
|
2016
|
+
return {
|
|
2017
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2018
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2019
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2020
|
+
meetingAt: text(row, "meeting_at"),
|
|
2021
|
+
source: SEARCH_SOURCES.includes(source) ? source : "note",
|
|
2022
|
+
refId: requireText(row, "ref_id", operation),
|
|
2023
|
+
speaker: text(row, "speaker"),
|
|
2024
|
+
snippet: text(row, "snippet") ?? "",
|
|
2025
|
+
at: text(row, "said_at"),
|
|
2026
|
+
rank: num(row, "rank") ?? 0
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
function snippetRuns(snippet) {
|
|
2030
|
+
const runs = [];
|
|
2031
|
+
const pattern = /⟦([^⟧]*)⟧/g;
|
|
2032
|
+
let last = 0;
|
|
2033
|
+
for (const match of snippet.matchAll(pattern)) {
|
|
2034
|
+
const start = match.index ?? 0;
|
|
2035
|
+
if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
|
|
2036
|
+
runs.push({ text: match[1] ?? "", hit: true });
|
|
2037
|
+
last = start + match[0].length;
|
|
2038
|
+
}
|
|
2039
|
+
if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
|
|
2040
|
+
return runs;
|
|
2041
|
+
}
|
|
1888
2042
|
|
|
1889
2043
|
// src/core/room-events.ts
|
|
1890
2044
|
var RECORDING_STATES = [
|
|
@@ -3053,6 +3207,7 @@ var EMPTY = {
|
|
|
3053
3207
|
spotlight: null,
|
|
3054
3208
|
moderationNotice: null,
|
|
3055
3209
|
actionNotice: null,
|
|
3210
|
+
privateChatRequest: null,
|
|
3056
3211
|
effects: DEFAULT_EFFECTS,
|
|
3057
3212
|
transcript: [],
|
|
3058
3213
|
notes: [],
|
|
@@ -3318,7 +3473,9 @@ function createMeetingSession(options) {
|
|
|
3318
3473
|
return identity.userId;
|
|
3319
3474
|
}
|
|
3320
3475
|
function knockIdentity() {
|
|
3321
|
-
return asParticipantIdentity(
|
|
3476
|
+
return asParticipantIdentity(
|
|
3477
|
+
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
3478
|
+
);
|
|
3322
3479
|
}
|
|
3323
3480
|
function requireMeeting(operation) {
|
|
3324
3481
|
const meeting = store.snapshot().meeting;
|
|
@@ -3353,8 +3510,9 @@ function createMeetingSession(options) {
|
|
|
3353
3510
|
verdict.admitted ? "admitted" : "denied",
|
|
3354
3511
|
verdict.by === null ? null : verdict.by
|
|
3355
3512
|
);
|
|
3356
|
-
if (
|
|
3357
|
-
options.onAdmitted?.();
|
|
3513
|
+
if (decided === knockIdentity()) {
|
|
3514
|
+
if (verdict.admitted) options.onAdmitted?.();
|
|
3515
|
+
else options.onDenied?.();
|
|
3358
3516
|
}
|
|
3359
3517
|
}
|
|
3360
3518
|
},
|
|
@@ -3726,19 +3884,413 @@ function fileDownloadPath(fileId) {
|
|
|
3726
3884
|
return `/files/${encodeURIComponent(fileId)}/download?inline=1`;
|
|
3727
3885
|
}
|
|
3728
3886
|
|
|
3729
|
-
// src/core/
|
|
3730
|
-
|
|
3731
|
-
|
|
3887
|
+
// src/core/after.ts
|
|
3888
|
+
import { formatDurationMs } from "@ai-matrx/kit/format";
|
|
3889
|
+
function kindOf(person) {
|
|
3890
|
+
if (person.isAgent) return "assistant";
|
|
3891
|
+
if (person.role === "host") return "host";
|
|
3892
|
+
if (person.role === "cohost") return "cohost";
|
|
3893
|
+
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
3894
|
+
return "member";
|
|
3895
|
+
}
|
|
3896
|
+
function ms(iso) {
|
|
3897
|
+
if (iso === null) return null;
|
|
3898
|
+
const at = new Date(iso).getTime();
|
|
3899
|
+
return Number.isNaN(at) ? null : at;
|
|
3900
|
+
}
|
|
3901
|
+
function attendeeKey(person, names = {}) {
|
|
3902
|
+
if (person.userId !== null) return `user:${person.userId}`;
|
|
3903
|
+
if (person.isAgent) return `agent:${person.identity}`;
|
|
3904
|
+
const name = displayNameFor(names, person.identity, person.displayName).trim().toLowerCase();
|
|
3905
|
+
return name.length > 0 ? `guest-name:${name}` : `identity:${person.identity}`;
|
|
3906
|
+
}
|
|
3907
|
+
function uniqueAttendees(attendees, names = {}) {
|
|
3908
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
3909
|
+
for (const person of attendees) {
|
|
3910
|
+
const key = attendeeKey(person, names);
|
|
3911
|
+
const kept = byKey.get(key);
|
|
3912
|
+
if (kept === void 0) {
|
|
3913
|
+
byKey.set(key, person);
|
|
3914
|
+
continue;
|
|
3915
|
+
}
|
|
3916
|
+
const keptJoin = ms(kept.joinedAt);
|
|
3917
|
+
const join = ms(person.joinedAt);
|
|
3918
|
+
if (join !== null && (keptJoin === null || join < keptJoin)) byKey.set(key, person);
|
|
3919
|
+
}
|
|
3920
|
+
return [...byKey.values()];
|
|
3921
|
+
}
|
|
3922
|
+
var KIND_RANK = {
|
|
3923
|
+
host: 0,
|
|
3924
|
+
cohost: 1,
|
|
3925
|
+
member: 2,
|
|
3926
|
+
"signed-in guest": 3,
|
|
3927
|
+
guest: 4,
|
|
3928
|
+
assistant: 5
|
|
3929
|
+
};
|
|
3930
|
+
function coveredMs(intervals) {
|
|
3931
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
3932
|
+
let total = 0;
|
|
3933
|
+
let from = -Infinity;
|
|
3934
|
+
let to = -Infinity;
|
|
3935
|
+
for (const [start, end] of sorted) {
|
|
3936
|
+
if (start > to) {
|
|
3937
|
+
if (to > from) total += to - from;
|
|
3938
|
+
from = start;
|
|
3939
|
+
to = end;
|
|
3940
|
+
} else if (end > to) {
|
|
3941
|
+
to = end;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
if (to > from) total += to - from;
|
|
3945
|
+
return total;
|
|
3946
|
+
}
|
|
3947
|
+
function attendanceReport(meeting, attendees, names = {}) {
|
|
3948
|
+
const end = ms(meeting.endedAt);
|
|
3949
|
+
const perJoin = attendees.map((person) => {
|
|
3950
|
+
const joined = ms(person.joinedAt);
|
|
3951
|
+
const left = ms(person.leftAt);
|
|
3952
|
+
const until = left ?? end;
|
|
3953
|
+
const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
|
|
3954
|
+
return {
|
|
3955
|
+
id: person.id,
|
|
3956
|
+
identity: person.identity,
|
|
3957
|
+
name: displayNameFor(names, person.identity, person.displayName),
|
|
3958
|
+
userId: person.userId,
|
|
3959
|
+
kind: kindOf(person),
|
|
3960
|
+
joinedAt: person.joinedAt,
|
|
3961
|
+
leftAt: person.leftAt,
|
|
3962
|
+
durationMs: duration,
|
|
3963
|
+
durationToEnd: duration !== null && left === null,
|
|
3964
|
+
admission: person.admissionState,
|
|
3965
|
+
joins: 1
|
|
3966
|
+
};
|
|
3967
|
+
});
|
|
3968
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3969
|
+
attendees.forEach((person, index) => {
|
|
3970
|
+
const key = attendeeKey(person, names);
|
|
3971
|
+
const group = groups.get(key) ?? { rows: [], people: [] };
|
|
3972
|
+
group.rows.push(perJoin[index]);
|
|
3973
|
+
group.people.push(person);
|
|
3974
|
+
groups.set(key, group);
|
|
3975
|
+
});
|
|
3976
|
+
const rows = [...groups.values()].map(({ rows: joins }) => {
|
|
3977
|
+
if (joins.length === 1) return joins[0];
|
|
3978
|
+
const came = joins.filter((row) => row.joinedAt !== null);
|
|
3979
|
+
const first = [...came].sort((a, b) => (ms(a.joinedAt) ?? 0) - (ms(b.joinedAt) ?? 0))[0] ?? joins[0];
|
|
3980
|
+
const kind = joins.reduce(
|
|
3981
|
+
(best, row) => KIND_RANK[row.kind] < KIND_RANK[best] ? row.kind : best,
|
|
3982
|
+
first.kind
|
|
3983
|
+
);
|
|
3984
|
+
const open = came.some((row) => row.leftAt === null);
|
|
3985
|
+
const lastLeave = came.reduce((latest, row) => {
|
|
3986
|
+
const left = ms(row.leftAt);
|
|
3987
|
+
return left !== null && (latest === null || left > latest) ? left : latest;
|
|
3988
|
+
}, null);
|
|
3989
|
+
const intervals = came.flatMap((row) => {
|
|
3990
|
+
const joined = ms(row.joinedAt);
|
|
3991
|
+
return joined !== null && row.durationMs !== null ? [[joined, joined + row.durationMs]] : [];
|
|
3992
|
+
});
|
|
3993
|
+
return {
|
|
3994
|
+
...first,
|
|
3995
|
+
kind,
|
|
3996
|
+
leftAt: open ? null : lastLeave === null ? null : new Date(lastLeave).toISOString(),
|
|
3997
|
+
durationMs: intervals.length === 0 ? null : coveredMs(intervals),
|
|
3998
|
+
durationToEnd: came.some((row) => row.durationToEnd),
|
|
3999
|
+
joins: came.length === 0 ? joins.length : came.length
|
|
4000
|
+
};
|
|
4001
|
+
});
|
|
4002
|
+
const rank = KIND_RANK;
|
|
4003
|
+
return rows.sort((a, b) => {
|
|
4004
|
+
if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
|
|
4005
|
+
return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
|
|
4006
|
+
});
|
|
4007
|
+
}
|
|
4008
|
+
function attendanceTotals(rows) {
|
|
4009
|
+
const humans = rows.filter((row) => row.kind !== "assistant");
|
|
4010
|
+
const attended = humans.filter((row) => row.joinedAt !== null);
|
|
4011
|
+
const timed = attended.filter((row) => row.durationMs !== null);
|
|
4012
|
+
return {
|
|
4013
|
+
people: attended.length,
|
|
4014
|
+
guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
|
|
4015
|
+
neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
|
|
4016
|
+
averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
|
|
4017
|
+
};
|
|
4018
|
+
}
|
|
4019
|
+
function csvCell(value) {
|
|
4020
|
+
if (value === null) return "";
|
|
4021
|
+
const text3 = String(value);
|
|
4022
|
+
return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
|
|
4023
|
+
}
|
|
4024
|
+
var ATTENDANCE_CSV_HEADER = [
|
|
4025
|
+
"Meeting",
|
|
4026
|
+
"Meeting started",
|
|
4027
|
+
"Name",
|
|
4028
|
+
"Attended as",
|
|
4029
|
+
"Joined",
|
|
4030
|
+
"Left",
|
|
4031
|
+
"Minutes in the meeting",
|
|
4032
|
+
"Admission"
|
|
4033
|
+
];
|
|
4034
|
+
function attendanceCsv(reports) {
|
|
4035
|
+
const lines = [ATTENDANCE_CSV_HEADER.join(",")];
|
|
4036
|
+
for (const { meeting, rows } of reports) {
|
|
4037
|
+
for (const row of rows) {
|
|
4038
|
+
lines.push(
|
|
4039
|
+
[
|
|
4040
|
+
meeting.title,
|
|
4041
|
+
meeting.startedAt ?? meeting.scheduledFor,
|
|
4042
|
+
row.name,
|
|
4043
|
+
row.kind,
|
|
4044
|
+
row.joinedAt,
|
|
4045
|
+
row.leftAt,
|
|
4046
|
+
row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
|
|
4047
|
+
row.admission
|
|
4048
|
+
].map(csvCell).join(",")
|
|
4049
|
+
);
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
return `${lines.join("\r\n")}\r
|
|
4053
|
+
`;
|
|
4054
|
+
}
|
|
4055
|
+
function recordingOffsetMs(at, recording) {
|
|
4056
|
+
if (recording === null || recording.state !== "available") return null;
|
|
4057
|
+
const start = ms(recording.startedAt);
|
|
4058
|
+
const moment = ms(at);
|
|
4059
|
+
if (start === null || moment === null || moment < start) return null;
|
|
4060
|
+
const end = ms(recording.endedAt);
|
|
4061
|
+
if (end !== null && moment > end) return null;
|
|
4062
|
+
return moment - start;
|
|
4063
|
+
}
|
|
4064
|
+
function clockPosition(offsetMs) {
|
|
4065
|
+
const total = Math.max(0, Math.floor(offsetMs / 1e3));
|
|
4066
|
+
const h = Math.floor(total / 3600);
|
|
4067
|
+
const m = Math.floor(total % 3600 / 60);
|
|
4068
|
+
const s = total % 60;
|
|
4069
|
+
const ss = String(s).padStart(2, "0");
|
|
4070
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
4071
|
+
}
|
|
4072
|
+
function searchTranscript(segments, query, names = {}) {
|
|
4073
|
+
const needle = fold(query.trim());
|
|
4074
|
+
if (needle.length === 0) return [];
|
|
4075
|
+
const out = [];
|
|
4076
|
+
for (const segment of segments) {
|
|
4077
|
+
const hay = fold(segment.text);
|
|
4078
|
+
const ranges = [];
|
|
4079
|
+
let from = 0;
|
|
4080
|
+
for (; ; ) {
|
|
4081
|
+
const at = hay.indexOf(needle, from);
|
|
4082
|
+
if (at < 0) break;
|
|
4083
|
+
ranges.push([at, at + needle.length]);
|
|
4084
|
+
from = at + needle.length;
|
|
4085
|
+
}
|
|
4086
|
+
const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
|
|
4087
|
+
if (ranges.length > 0 || speaker.includes(needle)) {
|
|
4088
|
+
out.push({ segmentId: segment.id, ranges });
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
return out;
|
|
4092
|
+
}
|
|
4093
|
+
function fold(value) {
|
|
4094
|
+
return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
|
|
4095
|
+
}
|
|
4096
|
+
function clockOfDay(iso, options) {
|
|
4097
|
+
const at = new Date(iso);
|
|
4098
|
+
if (Number.isNaN(at.getTime())) return "";
|
|
4099
|
+
return at.toLocaleTimeString(options.locale, {
|
|
4100
|
+
hour: "numeric",
|
|
4101
|
+
minute: "2-digit",
|
|
4102
|
+
second: "2-digit",
|
|
4103
|
+
...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
|
|
4104
|
+
});
|
|
4105
|
+
}
|
|
4106
|
+
function vttStamp(offsetMs) {
|
|
4107
|
+
const total = Math.max(0, Math.round(offsetMs));
|
|
4108
|
+
const h = Math.floor(total / 36e5);
|
|
4109
|
+
const m = Math.floor(total % 36e5 / 6e4);
|
|
4110
|
+
const s = Math.floor(total % 6e4 / 1e3);
|
|
4111
|
+
const milli = total % 1e3;
|
|
4112
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
|
|
4113
|
+
}
|
|
4114
|
+
function transcriptDocument(bundle, format, options = {}) {
|
|
4115
|
+
const names = options.names ?? bundle.names;
|
|
4116
|
+
const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
|
|
4117
|
+
const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
|
|
4118
|
+
if (format === "vtt") {
|
|
4119
|
+
const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
|
|
4120
|
+
const zero = ms(zeroIso) ?? 0;
|
|
4121
|
+
const cues = ["WEBVTT", ""];
|
|
4122
|
+
lines.forEach((line, index) => {
|
|
4123
|
+
const start = (ms(line.startedAt) ?? zero) - zero;
|
|
4124
|
+
if (start < 0) return;
|
|
4125
|
+
const next = lines[index + 1];
|
|
4126
|
+
const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
|
|
4127
|
+
const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
|
|
4128
|
+
cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
|
|
4129
|
+
cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
|
|
4130
|
+
cues.push("");
|
|
4131
|
+
});
|
|
4132
|
+
return cues.join("\n");
|
|
4133
|
+
}
|
|
4134
|
+
const out = [];
|
|
4135
|
+
const title = bundle.meeting.title;
|
|
4136
|
+
out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
|
|
4137
|
+
out.push("");
|
|
4138
|
+
let last = null;
|
|
4139
|
+
for (const line of lines) {
|
|
4140
|
+
if (line.identity !== last) {
|
|
4141
|
+
if (last !== null) out.push("");
|
|
4142
|
+
const who = speaker(line);
|
|
4143
|
+
const clock = clockOfDay(line.startedAt, options);
|
|
4144
|
+
out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
|
|
4145
|
+
last = line.identity;
|
|
4146
|
+
}
|
|
4147
|
+
out.push(line.text.trim());
|
|
4148
|
+
}
|
|
4149
|
+
if (lines.length === 0) out.push("No transcript was captured for this meeting.");
|
|
4150
|
+
return `${out.join("\n")}
|
|
4151
|
+
`;
|
|
4152
|
+
}
|
|
4153
|
+
function bullet(note) {
|
|
4154
|
+
const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
|
|
4155
|
+
return `- ${note.text.trim()}${owner}`;
|
|
4156
|
+
}
|
|
4157
|
+
function notesDocument(bundle, options = {}) {
|
|
4158
|
+
const out = [`# ${bundle.meeting.title}`, ""];
|
|
4159
|
+
if (options.when) out.push(`_${options.when}_`, "");
|
|
4160
|
+
const people = uniqueAttendees(
|
|
4161
|
+
bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null),
|
|
4162
|
+
bundle.names
|
|
4163
|
+
).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
|
|
4164
|
+
if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
|
|
4165
|
+
out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
|
|
4166
|
+
out.push("## Decisions", "");
|
|
4167
|
+
out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
|
|
4168
|
+
out.push("## Action items", "");
|
|
4169
|
+
out.push(
|
|
4170
|
+
...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
|
|
4171
|
+
""
|
|
4172
|
+
);
|
|
4173
|
+
if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
|
|
4174
|
+
return out.join("\n");
|
|
4175
|
+
}
|
|
4176
|
+
function meetingLength(meeting) {
|
|
4177
|
+
const start = ms(meeting.startedAt);
|
|
4178
|
+
const end = ms(meeting.endedAt);
|
|
4179
|
+
if (start === null || end === null || end < start) return null;
|
|
4180
|
+
return formatDurationMs(end - start, { style: "long" });
|
|
4181
|
+
}
|
|
4182
|
+
async function loadMeetingChat(api, meetingId, roomToken = null) {
|
|
4183
|
+
const operation = "meet.loadMeetingChat";
|
|
4184
|
+
let response;
|
|
3732
4185
|
try {
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
4186
|
+
response = await api.request({
|
|
4187
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
4188
|
+
method: "GET",
|
|
4189
|
+
operation,
|
|
4190
|
+
requireAuth: roomToken === null,
|
|
4191
|
+
...roomToken !== null ? { headers: { [ROOM_TOKEN_HEADER]: roomToken } } : {}
|
|
4192
|
+
});
|
|
4193
|
+
} catch (error) {
|
|
4194
|
+
return {
|
|
4195
|
+
messages: [],
|
|
4196
|
+
refusal: {
|
|
4197
|
+
message: `The chat could not be read (${error.message}).`,
|
|
4198
|
+
remedy: error.remedy ?? "Reload the page."
|
|
4199
|
+
}
|
|
4200
|
+
};
|
|
3736
4201
|
}
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
4202
|
+
if (!response.ok) {
|
|
4203
|
+
let body = null;
|
|
4204
|
+
try {
|
|
4205
|
+
body = await response.json();
|
|
4206
|
+
} catch {
|
|
4207
|
+
body = null;
|
|
4208
|
+
}
|
|
4209
|
+
const said = readServerRefusal(body);
|
|
4210
|
+
return {
|
|
4211
|
+
messages: [],
|
|
4212
|
+
refusal: {
|
|
4213
|
+
message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
|
|
4214
|
+
remedy: said?.remedy || "Reload the page."
|
|
4215
|
+
}
|
|
4216
|
+
};
|
|
4217
|
+
}
|
|
4218
|
+
const payload = await response.json();
|
|
4219
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
4220
|
+
return {
|
|
4221
|
+
messages: rows.map(
|
|
4222
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
4223
|
+
).filter((message) => message !== null),
|
|
4224
|
+
refusal: null
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
4227
|
+
function refusalOf(response, operation) {
|
|
4228
|
+
return refusalFromResponse(response, operation, "Nothing was sent. Try again.");
|
|
4229
|
+
}
|
|
4230
|
+
function asRecipient(row) {
|
|
4231
|
+
const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
|
|
4232
|
+
return {
|
|
4233
|
+
key: String(row["key"] ?? ""),
|
|
4234
|
+
name: String(row["name"] ?? ""),
|
|
4235
|
+
email: typeof row["email"] === "string" ? row["email"] : null,
|
|
4236
|
+
userId: typeof row["user_id"] === "string" ? row["user_id"] : null,
|
|
4237
|
+
sources: sources.filter((s) => s === "invitee" || s === "attendee"),
|
|
4238
|
+
reachable: row["reachable"] !== false
|
|
4239
|
+
};
|
|
4240
|
+
}
|
|
4241
|
+
async function loadFollowUpDraft(api, meetingId) {
|
|
4242
|
+
const operation = "meet.loadFollowUpDraft";
|
|
4243
|
+
const response = await api.request({
|
|
4244
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
4245
|
+
method: "GET",
|
|
4246
|
+
requireAuth: true,
|
|
4247
|
+
operation
|
|
4248
|
+
});
|
|
4249
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
4250
|
+
const body = await response.json();
|
|
4251
|
+
const last = body["last_sent"];
|
|
4252
|
+
return {
|
|
4253
|
+
subject: String(body["subject"] ?? ""),
|
|
4254
|
+
body: String(body["body"] ?? ""),
|
|
4255
|
+
recipients: (Array.isArray(body["recipients"]) ? body["recipients"] : []).map(
|
|
4256
|
+
(row) => asRecipient(row)
|
|
4257
|
+
),
|
|
4258
|
+
wrapUpWritten: body["wrap_up_written"] === true,
|
|
4259
|
+
lastSent: last && typeof last["at"] === "string" ? { at: last["at"], count: typeof last["count"] === "number" ? last["count"] : 0 } : null
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4262
|
+
async function sendFollowUp(api, meetingId, input) {
|
|
4263
|
+
const operation = "meet.sendFollowUp";
|
|
4264
|
+
const response = await api.request({
|
|
4265
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
4266
|
+
method: "POST",
|
|
4267
|
+
requireAuth: true,
|
|
4268
|
+
operation,
|
|
4269
|
+
body: {
|
|
4270
|
+
subject: input.subject,
|
|
4271
|
+
body: input.body,
|
|
4272
|
+
recipient_keys: [...input.recipientKeys],
|
|
4273
|
+
dry_run: input.dryRun === true
|
|
4274
|
+
}
|
|
4275
|
+
});
|
|
4276
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
4277
|
+
const body = await response.json();
|
|
4278
|
+
return {
|
|
4279
|
+
dryRun: body["dry_run"] === true,
|
|
4280
|
+
queued: Array.isArray(body["queued"]) ? body["queued"] : [],
|
|
4281
|
+
skipped: Array.isArray(body["skipped"]) ? body["skipped"].map((row) => ({
|
|
4282
|
+
key: String(row["key"] ?? ""),
|
|
4283
|
+
reason: String(row["reason"] ?? "")
|
|
4284
|
+
})) : []
|
|
4285
|
+
};
|
|
4286
|
+
}
|
|
4287
|
+
|
|
4288
|
+
// src/core/management.ts
|
|
4289
|
+
function refusal(response, operation) {
|
|
4290
|
+
return refusalFromResponse(
|
|
4291
|
+
response,
|
|
4292
|
+
operation,
|
|
4293
|
+
"The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
|
|
3742
4294
|
);
|
|
3743
4295
|
}
|
|
3744
4296
|
function count(body, key) {
|
|
@@ -4064,6 +4616,10 @@ function createRoomEngine(options) {
|
|
|
4064
4616
|
}
|
|
4065
4617
|
},
|
|
4066
4618
|
disconnected(reason) {
|
|
4619
|
+
if (current !== null) {
|
|
4620
|
+
tokens.invalidate(current.roomName);
|
|
4621
|
+
current = null;
|
|
4622
|
+
}
|
|
4067
4623
|
store.patch({ phase: "left" });
|
|
4068
4624
|
options.onLeft?.(reason);
|
|
4069
4625
|
}
|
|
@@ -4366,19 +4922,8 @@ function createInRoomClient(options) {
|
|
|
4366
4922
|
const token = options.resolveRoomToken();
|
|
4367
4923
|
return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
|
|
4368
4924
|
}
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
try {
|
|
4372
|
-
body = await response.json();
|
|
4373
|
-
} catch {
|
|
4374
|
-
body = null;
|
|
4375
|
-
}
|
|
4376
|
-
const said = readServerRefusal(body);
|
|
4377
|
-
return new MeetError(
|
|
4378
|
-
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
4379
|
-
said?.reason ?? `${operation}: the server answered ${response.status}`,
|
|
4380
|
-
said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
|
|
4381
|
-
);
|
|
4925
|
+
function refusal2(response, operation, remedy) {
|
|
4926
|
+
return refusalFromResponse(response, operation, remedy);
|
|
4382
4927
|
}
|
|
4383
4928
|
return {
|
|
4384
4929
|
async moderate(request) {
|
|
@@ -4482,7 +5027,7 @@ function createInRoomClient(options) {
|
|
|
4482
5027
|
}
|
|
4483
5028
|
|
|
4484
5029
|
// src/core/record.ts
|
|
4485
|
-
import { formatDurationMs } from "@ai-matrx/kit/format";
|
|
5030
|
+
import { formatDurationMs as formatDurationMs2 } from "@ai-matrx/kit/format";
|
|
4486
5031
|
function activeWrapUpSummary(notes) {
|
|
4487
5032
|
return notes.find((note) => note.role === "summary" && note.supersededBy === null) ?? null;
|
|
4488
5033
|
}
|
|
@@ -4565,6 +5110,36 @@ function wasRefused(bundle, part) {
|
|
|
4565
5110
|
function absenceSentence(bundle, part, absence) {
|
|
4566
5111
|
return wasRefused(bundle, part) ? REFUSED_ABSENCE : absence;
|
|
4567
5112
|
}
|
|
5113
|
+
function noteTakerMetadata(meeting) {
|
|
5114
|
+
const raw = meeting.metadata["note_taker"];
|
|
5115
|
+
return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
5116
|
+
}
|
|
5117
|
+
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.";
|
|
5118
|
+
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.";
|
|
5119
|
+
function wrapUpStatus(bundle) {
|
|
5120
|
+
if (bundle.summary !== null) return { state: "written" };
|
|
5121
|
+
if (wasRefused(bundle, "notes")) return { state: "refused", message: REFUSED_ABSENCE };
|
|
5122
|
+
const noteTaker = noteTakerMetadata(bundle.meeting);
|
|
5123
|
+
const intelligenceRaw = noteTaker["intelligence"];
|
|
5124
|
+
const intelligence = typeof intelligenceRaw === "object" && intelligenceRaw !== null && !Array.isArray(intelligenceRaw) ? intelligenceRaw : {};
|
|
5125
|
+
const wrapUpRaw = intelligence["wrap_up"];
|
|
5126
|
+
const wrapUp = typeof wrapUpRaw === "object" && wrapUpRaw !== null && !Array.isArray(wrapUpRaw) ? wrapUpRaw : {};
|
|
5127
|
+
if (wrapUp["state"] === "failed") {
|
|
5128
|
+
const reason = wrapUp["reason"];
|
|
5129
|
+
return {
|
|
5130
|
+
state: "failed",
|
|
5131
|
+
// THE SERVER'S OWN SENTENCE, verbatim. It already names the remedy, and a
|
|
5132
|
+
// house sentence over it would hide the one thing worth reading.
|
|
5133
|
+
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."
|
|
5134
|
+
};
|
|
5135
|
+
}
|
|
5136
|
+
const queued = typeof wrapUp["run_id"] === "string" || typeof intelligence["wrap_up_run_id"] === "string" || wrapUp["state"] === "queued" || wrapUp["state"] === "running";
|
|
5137
|
+
const attended = noteTaker["state"] === "ended" || noteTaker["state"] === "transcribing" || noteTaker["state"] === "handoff_pending";
|
|
5138
|
+
if (hasEnded(bundle.meeting) && (queued || attended)) {
|
|
5139
|
+
return { state: "in-progress", message: WRAP_UP_IN_PROGRESS, canRefresh: true };
|
|
5140
|
+
}
|
|
5141
|
+
return { state: "absent", message: WRAP_UP_ABSENT };
|
|
5142
|
+
}
|
|
4568
5143
|
function groupTranscript(segments, names = {}) {
|
|
4569
5144
|
const blocks = [];
|
|
4570
5145
|
for (const segment of segments) {
|
|
@@ -4633,7 +5208,7 @@ function meetingWhen(meeting, locale) {
|
|
|
4633
5208
|
const endedAt = new Date(meeting.endedAt);
|
|
4634
5209
|
if (Number.isNaN(endedAt.getTime())) return when2;
|
|
4635
5210
|
const elapsedMs = endedAt.getTime() - startedAt.getTime();
|
|
4636
|
-
return `${when2} \xB7 ${
|
|
5211
|
+
return `${when2} \xB7 ${formatDurationMs2(elapsedMs, { style: "long" })}`;
|
|
4637
5212
|
}
|
|
4638
5213
|
var GUEST_RECORD_ACCESS_LEVELS = [
|
|
4639
5214
|
"none",
|
|
@@ -4682,6 +5257,37 @@ var NOT_SHARED = {
|
|
|
4682
5257
|
recording: "The recording of this meeting is not shared with guests."
|
|
4683
5258
|
};
|
|
4684
5259
|
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.";
|
|
5260
|
+
var RESTRICTION_PART_NAME = {
|
|
5261
|
+
attendance: "attendance",
|
|
5262
|
+
notes: "notes and decisions",
|
|
5263
|
+
transcript: "word-for-word transcript",
|
|
5264
|
+
recording: "recording"
|
|
5265
|
+
};
|
|
5266
|
+
function joinPartNames(parts) {
|
|
5267
|
+
const names = parts.map((part) => RESTRICTION_PART_NAME[part]);
|
|
5268
|
+
if (names.length < 2) return names[0] ?? "this part of the record";
|
|
5269
|
+
if (names.length === 2) return `${names[0]} and ${names[1]}`;
|
|
5270
|
+
return `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`;
|
|
5271
|
+
}
|
|
5272
|
+
function summarizeMeetingRecordRestrictions(restrictions) {
|
|
5273
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5274
|
+
for (const restriction of restrictions) {
|
|
5275
|
+
const rows = grouped.get(restriction.remedy) ?? [];
|
|
5276
|
+
rows.push(restriction);
|
|
5277
|
+
grouped.set(restriction.remedy, rows);
|
|
5278
|
+
}
|
|
5279
|
+
return [...grouped.values()].map((rows) => {
|
|
5280
|
+
if (rows.length === 1) {
|
|
5281
|
+
const row = rows[0];
|
|
5282
|
+
return { parts: [row.part], message: row.message, remedy: row.remedy };
|
|
5283
|
+
}
|
|
5284
|
+
return {
|
|
5285
|
+
parts: rows.map((row) => row.part),
|
|
5286
|
+
message: `Some parts of this meeting record are not available to this reader: ${joinPartNames(rows.map((row) => row.part))}.`,
|
|
5287
|
+
remedy: rows[0].remedy
|
|
5288
|
+
};
|
|
5289
|
+
});
|
|
5290
|
+
}
|
|
4685
5291
|
async function loadGuestMeetingRecord(args) {
|
|
4686
5292
|
const { api, repository, meetingId, meeting, roomToken } = args;
|
|
4687
5293
|
if (roomToken === null) {
|
|
@@ -4813,6 +5419,7 @@ async function setGuestRecordAccess(args) {
|
|
|
4813
5419
|
}
|
|
4814
5420
|
var GUEST_RECORD_NOT_SHARED = NOT_SHARED;
|
|
4815
5421
|
export {
|
|
5422
|
+
ATTENDANCE_CSV_HEADER,
|
|
4816
5423
|
CALL_INVITE_KIND,
|
|
4817
5424
|
CALL_INVITE_VERSION,
|
|
4818
5425
|
CONSENT_NOTICE_ATTRIBUTE,
|
|
@@ -4854,6 +5461,7 @@ export {
|
|
|
4854
5461
|
TRANSCRIPTION_ATTRIBUTES,
|
|
4855
5462
|
TRANSCRIPTION_TOPIC,
|
|
4856
5463
|
absenceSentence,
|
|
5464
|
+
activeWrapUpSummary,
|
|
4857
5465
|
announceMeeting,
|
|
4858
5466
|
applyRoomEvent,
|
|
4859
5467
|
asCallId,
|
|
@@ -4862,6 +5470,10 @@ export {
|
|
|
4862
5470
|
asParticipantIdentity,
|
|
4863
5471
|
asRoomName,
|
|
4864
5472
|
asUserId,
|
|
5473
|
+
attendanceCsv,
|
|
5474
|
+
attendanceReport,
|
|
5475
|
+
attendanceTotals,
|
|
5476
|
+
attendeeKey,
|
|
4865
5477
|
callInviteAction,
|
|
4866
5478
|
callInviteText,
|
|
4867
5479
|
callStateLabel,
|
|
@@ -4870,6 +5482,7 @@ export {
|
|
|
4870
5482
|
canTransition,
|
|
4871
5483
|
captionSegment,
|
|
4872
5484
|
changesPatch,
|
|
5485
|
+
clockPosition,
|
|
4873
5486
|
computeStage,
|
|
4874
5487
|
countLabel,
|
|
4875
5488
|
createCallCenter,
|
|
@@ -4910,11 +5523,14 @@ export {
|
|
|
4910
5523
|
learnName,
|
|
4911
5524
|
learnNames,
|
|
4912
5525
|
listenOnlyNotice,
|
|
5526
|
+
loadFollowUpDraft,
|
|
4913
5527
|
loadGuestMeetingRecord,
|
|
5528
|
+
loadMeetingChat,
|
|
4914
5529
|
loadMeetingRecord,
|
|
4915
5530
|
mapConnectionState,
|
|
4916
5531
|
mapQuality,
|
|
4917
5532
|
meetingInviteAction,
|
|
5533
|
+
meetingLength,
|
|
4918
5534
|
meetingLink,
|
|
4919
5535
|
meetingPath,
|
|
4920
5536
|
meetingSlug,
|
|
@@ -4929,16 +5545,19 @@ export {
|
|
|
4929
5545
|
noteTakerLabel,
|
|
4930
5546
|
noteTakerParticipant,
|
|
4931
5547
|
noteTakerStatus,
|
|
5548
|
+
notesDocument,
|
|
4932
5549
|
orderParticipants,
|
|
4933
5550
|
parseRoomToken,
|
|
4934
5551
|
participantSummary,
|
|
4935
5552
|
playableRecordingFileId,
|
|
4936
5553
|
projectInvitee,
|
|
5554
|
+
projectLibraryRecording,
|
|
4937
5555
|
projectNote,
|
|
4938
5556
|
projectOccurrence,
|
|
4939
5557
|
projectOverride,
|
|
4940
5558
|
projectParticipant,
|
|
4941
5559
|
projectRsvpInvitation,
|
|
5560
|
+
projectSearchHit,
|
|
4942
5561
|
projectSegment,
|
|
4943
5562
|
projectUpcoming,
|
|
4944
5563
|
pushMeetingToGoogleCalendar,
|
|
@@ -4946,13 +5565,21 @@ export {
|
|
|
4946
5565
|
readableTranscript,
|
|
4947
5566
|
recordingLabel,
|
|
4948
5567
|
recordingNotice,
|
|
5568
|
+
recordingOffsetMs,
|
|
4949
5569
|
roleOf,
|
|
4950
5570
|
roomTopic,
|
|
5571
|
+
searchTranscript,
|
|
5572
|
+
sendFollowUp,
|
|
4951
5573
|
setGuestRecordAccess,
|
|
4952
5574
|
settingsPatch,
|
|
4953
5575
|
snapshotParticipant,
|
|
5576
|
+
snippetRuns,
|
|
5577
|
+
summarizeMeetingRecordRestrictions,
|
|
5578
|
+
transcriptDocument,
|
|
4954
5579
|
unavailableFrom,
|
|
5580
|
+
uniqueAttendees,
|
|
4955
5581
|
userTopic,
|
|
4956
|
-
wasRefused
|
|
5582
|
+
wasRefused,
|
|
5583
|
+
wrapUpStatus
|
|
4957
5584
|
};
|
|
4958
5585
|
//# sourceMappingURL=index.js.map
|