@ai-matrx/meet 0.7.7 → 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 +37 -0
- package/dist/index.cjs +129 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -50
- package/dist/index.d.ts +79 -50
- package/dist/index.js +129 -50
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +827 -701
- 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 +803 -677
- package/dist/react.js.map +1 -1
- package/dist/styles.css +18 -0
- package/package.json +4 -4
package/dist/react.js
CHANGED
|
@@ -502,6 +502,29 @@ function readText(data) {
|
|
|
502
502
|
}
|
|
503
503
|
return "";
|
|
504
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
|
+
}
|
|
505
528
|
function readServerRefusal(body) {
|
|
506
529
|
if (typeof body === "string") {
|
|
507
530
|
return body.trim().length === 0 ? null : { reason: body.trim(), remedy: "" };
|
|
@@ -520,7 +543,8 @@ function readServerRefusal(body) {
|
|
|
520
543
|
return messages.length === 0 ? null : { reason: messages.join("; "), remedy: "" };
|
|
521
544
|
}
|
|
522
545
|
if (typeof detail === "object" && detail !== null) return readServerRefusal(detail);
|
|
523
|
-
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);
|
|
524
548
|
if (reason === null) return null;
|
|
525
549
|
const remedy = typeof row["remedy"] === "string" ? row["remedy"] : "";
|
|
526
550
|
if (remedy.length > 0) return { reason, remedy };
|
|
@@ -1574,6 +1598,7 @@ var EMPTY = {
|
|
|
1574
1598
|
spotlight: null,
|
|
1575
1599
|
moderationNotice: null,
|
|
1576
1600
|
actionNotice: null,
|
|
1601
|
+
privateChatRequest: null,
|
|
1577
1602
|
effects: DEFAULT_EFFECTS,
|
|
1578
1603
|
transcript: [],
|
|
1579
1604
|
notes: [],
|
|
@@ -3763,6 +3788,10 @@ function createRoomEngine(options) {
|
|
|
3763
3788
|
}
|
|
3764
3789
|
},
|
|
3765
3790
|
disconnected(reason) {
|
|
3791
|
+
if (current !== null) {
|
|
3792
|
+
tokens.invalidate(current.roomName);
|
|
3793
|
+
current = null;
|
|
3794
|
+
}
|
|
3766
3795
|
store.patch({ phase: "left" });
|
|
3767
3796
|
options.onLeft?.(reason);
|
|
3768
3797
|
}
|
|
@@ -4065,19 +4094,8 @@ function createInRoomClient(options) {
|
|
|
4065
4094
|
const token = options.resolveRoomToken();
|
|
4066
4095
|
return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
|
|
4067
4096
|
}
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
try {
|
|
4071
|
-
body = await response.json();
|
|
4072
|
-
} catch {
|
|
4073
|
-
body = null;
|
|
4074
|
-
}
|
|
4075
|
-
const said = readServerRefusal(body);
|
|
4076
|
-
return new MeetError(
|
|
4077
|
-
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
4078
|
-
said?.reason ?? `${operation}: the server answered ${response.status}`,
|
|
4079
|
-
said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
|
|
4080
|
-
);
|
|
4097
|
+
function refusal2(response, operation, remedy) {
|
|
4098
|
+
return refusalFromResponse(response, operation, remedy);
|
|
4081
4099
|
}
|
|
4082
4100
|
return {
|
|
4083
4101
|
async moderate(request) {
|
|
@@ -6037,195 +6055,596 @@ function useGuestRecordAccess(meeting) {
|
|
|
6037
6055
|
// src/react/components.tsx
|
|
6038
6056
|
import { useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
|
|
6039
6057
|
import { ErrorBox } from "@ai-matrx/design-system";
|
|
6040
|
-
import { avatarPaletteIndex, formatDurationMs as
|
|
6058
|
+
import { avatarPaletteIndex, formatDurationMs as formatDurationMs3, getInitials } from "@ai-matrx/kit/format";
|
|
6041
6059
|
|
|
6042
|
-
// src/core/
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
if (
|
|
6046
|
-
if (
|
|
6047
|
-
if (
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
function callStateLabel(state, outgoing) {
|
|
6051
|
-
switch (state) {
|
|
6052
|
-
case "dialing":
|
|
6053
|
-
case "ringing":
|
|
6054
|
-
return outgoing ? "Calling\u2026" : "Incoming call";
|
|
6055
|
-
case "connecting":
|
|
6056
|
-
return "Connecting\u2026";
|
|
6057
|
-
case "active":
|
|
6058
|
-
return "In call";
|
|
6059
|
-
case "declined":
|
|
6060
|
-
return outgoing ? "Declined" : "You declined";
|
|
6061
|
-
case "missed":
|
|
6062
|
-
return outgoing ? "No answer" : "Missed call";
|
|
6063
|
-
case "busy":
|
|
6064
|
-
return outgoing ? "On another call" : "You were busy";
|
|
6065
|
-
case "cancelled":
|
|
6066
|
-
return outgoing ? "Cancelled" : "Cancelled by caller";
|
|
6067
|
-
case "ended":
|
|
6068
|
-
return "Call ended";
|
|
6069
|
-
case "failed":
|
|
6070
|
-
return "Call failed";
|
|
6071
|
-
default:
|
|
6072
|
-
return "Call";
|
|
6073
|
-
}
|
|
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";
|
|
6074
6068
|
}
|
|
6075
|
-
function
|
|
6076
|
-
|
|
6069
|
+
function ms(iso) {
|
|
6070
|
+
if (iso === null) return null;
|
|
6071
|
+
const at = new Date(iso).getTime();
|
|
6072
|
+
return Number.isNaN(at) ? null : at;
|
|
6077
6073
|
}
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
return
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
strokeLinejoin: "round",
|
|
6093
|
-
"aria-hidden": "true",
|
|
6094
|
-
focusable: "false",
|
|
6095
|
-
...props,
|
|
6096
|
-
children
|
|
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;
|
|
6097
6088
|
}
|
|
6098
|
-
|
|
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;
|
|
6099
6119
|
}
|
|
6100
|
-
function
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
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
|
+
});
|
|
6106
6180
|
}
|
|
6107
|
-
function
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
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
|
+
};
|
|
6115
6191
|
}
|
|
6116
|
-
function
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
] });
|
|
6192
|
+
function csvCell(value) {
|
|
6193
|
+
if (value === null) return "";
|
|
6194
|
+
const text3 = String(value);
|
|
6195
|
+
return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
|
|
6121
6196
|
}
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
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
|
+
`;
|
|
6128
6227
|
}
|
|
6129
|
-
function
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
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;
|
|
6135
6236
|
}
|
|
6136
|
-
function
|
|
6137
|
-
|
|
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}`;
|
|
6138
6244
|
}
|
|
6139
|
-
function
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
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;
|
|
6145
6265
|
}
|
|
6146
|
-
function
|
|
6147
|
-
return
|
|
6148
|
-
/* @__PURE__ */ jsx2("path", { d: "M18 11V6a2 2 0 0 0-4 0v5" }),
|
|
6149
|
-
/* @__PURE__ */ jsx2("path", { d: "M14 10V4a2 2 0 0 0-4 0v7" }),
|
|
6150
|
-
/* @__PURE__ */ jsx2("path", { d: "M10 10.5V6a2 2 0 0 0-4 0v8" }),
|
|
6151
|
-
/* @__PURE__ */ jsx2("path", { d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2a8 8 0 0 1-8-8v-1a2 2 0 1 1 4 0" })
|
|
6152
|
-
] });
|
|
6266
|
+
function fold(value) {
|
|
6267
|
+
return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
|
|
6153
6268
|
}
|
|
6154
|
-
function
|
|
6155
|
-
|
|
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
|
+
});
|
|
6156
6278
|
}
|
|
6157
|
-
function
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
}
|
|
6164
|
-
function RecordIcon(props) {
|
|
6165
|
-
return /* @__PURE__ */ jsx2(Icon, { ...props, children: /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "8", fill: "currentColor", stroke: "none" }) });
|
|
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")}`;
|
|
6166
6286
|
}
|
|
6167
|
-
function
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
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
|
+
`;
|
|
6172
6325
|
}
|
|
6173
|
-
function
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
/* @__PURE__ */ jsx2("path", { d: "M8 10V7a4 4 0 0 1 8 0v3" })
|
|
6177
|
-
] });
|
|
6326
|
+
function bullet(note) {
|
|
6327
|
+
const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
|
|
6328
|
+
return `- ${note.text.trim()}${owner}`;
|
|
6178
6329
|
}
|
|
6179
|
-
function
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
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");
|
|
6184
6348
|
}
|
|
6185
|
-
function
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
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" });
|
|
6190
6354
|
}
|
|
6191
|
-
function
|
|
6192
|
-
|
|
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
|
+
};
|
|
6193
6399
|
}
|
|
6194
|
-
function
|
|
6195
|
-
return
|
|
6400
|
+
function refusalOf(response, operation) {
|
|
6401
|
+
return refusalFromResponse(response, operation, "Nothing was sent. Try again.");
|
|
6196
6402
|
}
|
|
6197
|
-
function
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
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
|
+
};
|
|
6202
6413
|
}
|
|
6203
|
-
function
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
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
|
+
};
|
|
6208
6434
|
}
|
|
6209
|
-
function
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
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
|
+
};
|
|
6216
6459
|
}
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
]
|
|
6460
|
+
|
|
6461
|
+
// src/core/format.ts
|
|
6462
|
+
function participantSummary(participants) {
|
|
6463
|
+
const names = participants.filter((p) => !p.isLocal).map((p) => p.displayName);
|
|
6464
|
+
if (names.length === 0) return "Just you";
|
|
6465
|
+
if (names.length === 1) return names[0] ?? "";
|
|
6466
|
+
if (names.length === 2) return `${names[0]} and ${names[1]}`;
|
|
6467
|
+
return `${names[0]}, ${names[1]} and ${names.length - 2} ${names.length - 2 === 1 ? "other" : "others"}`;
|
|
6223
6468
|
}
|
|
6224
|
-
function
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6469
|
+
function callStateLabel(state, outgoing) {
|
|
6470
|
+
switch (state) {
|
|
6471
|
+
case "dialing":
|
|
6472
|
+
case "ringing":
|
|
6473
|
+
return outgoing ? "Calling\u2026" : "Incoming call";
|
|
6474
|
+
case "connecting":
|
|
6475
|
+
return "Connecting\u2026";
|
|
6476
|
+
case "active":
|
|
6477
|
+
return "In call";
|
|
6478
|
+
case "declined":
|
|
6479
|
+
return outgoing ? "Declined" : "You declined";
|
|
6480
|
+
case "missed":
|
|
6481
|
+
return outgoing ? "No answer" : "Missed call";
|
|
6482
|
+
case "busy":
|
|
6483
|
+
return outgoing ? "On another call" : "You were busy";
|
|
6484
|
+
case "cancelled":
|
|
6485
|
+
return outgoing ? "Cancelled" : "Cancelled by caller";
|
|
6486
|
+
case "ended":
|
|
6487
|
+
return "Call ended";
|
|
6488
|
+
case "failed":
|
|
6489
|
+
return "Call failed";
|
|
6490
|
+
default:
|
|
6491
|
+
return "Call";
|
|
6492
|
+
}
|
|
6493
|
+
}
|
|
6494
|
+
function countLabel(count2, singular, plural = `${singular}s`) {
|
|
6495
|
+
return `${count2} ${count2 === 1 ? singular : plural}`;
|
|
6496
|
+
}
|
|
6497
|
+
|
|
6498
|
+
// src/react/icons.tsx
|
|
6499
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
6500
|
+
function Icon({ children, ...props }) {
|
|
6501
|
+
return /* @__PURE__ */ jsx2(
|
|
6502
|
+
"svg",
|
|
6503
|
+
{
|
|
6504
|
+
width: "20",
|
|
6505
|
+
height: "20",
|
|
6506
|
+
viewBox: "0 0 24 24",
|
|
6507
|
+
fill: "none",
|
|
6508
|
+
stroke: "currentColor",
|
|
6509
|
+
strokeWidth: "1.75",
|
|
6510
|
+
strokeLinecap: "round",
|
|
6511
|
+
strokeLinejoin: "round",
|
|
6512
|
+
"aria-hidden": "true",
|
|
6513
|
+
focusable: "false",
|
|
6514
|
+
...props,
|
|
6515
|
+
children
|
|
6516
|
+
}
|
|
6517
|
+
);
|
|
6518
|
+
}
|
|
6519
|
+
function MicIcon(props) {
|
|
6520
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6521
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }),
|
|
6522
|
+
/* @__PURE__ */ jsx2("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
|
|
6523
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 19v3" })
|
|
6524
|
+
] });
|
|
6525
|
+
}
|
|
6526
|
+
function MicOffIcon(props) {
|
|
6527
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6528
|
+
/* @__PURE__ */ jsx2("path", { d: "M2 2l20 20" }),
|
|
6529
|
+
/* @__PURE__ */ jsx2("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12" }),
|
|
6530
|
+
/* @__PURE__ */ jsx2("path", { d: "M15 9.34V5a3 3 0 0 0-5.94-.6" }),
|
|
6531
|
+
/* @__PURE__ */ jsx2("path", { d: "M19 10v2a7 7 0 0 1-.11 1.23M5 10v2a7 7 0 0 0 11 5.66" }),
|
|
6532
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 19v3" })
|
|
6533
|
+
] });
|
|
6534
|
+
}
|
|
6535
|
+
function CameraIcon(props) {
|
|
6536
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6537
|
+
/* @__PURE__ */ jsx2("path", { d: "m23 7-7 5 7 5V7Z" }),
|
|
6538
|
+
/* @__PURE__ */ jsx2("rect", { x: "1", y: "5", width: "15", height: "14", rx: "2" })
|
|
6539
|
+
] });
|
|
6540
|
+
}
|
|
6541
|
+
function CameraOffIcon(props) {
|
|
6542
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6543
|
+
/* @__PURE__ */ jsx2("path", { d: "M2 2l20 20" }),
|
|
6544
|
+
/* @__PURE__ */ jsx2("path", { d: "M10.66 5H14a2 2 0 0 1 2 2v3.34l1 1L23 7v10" }),
|
|
6545
|
+
/* @__PURE__ */ jsx2("path", { d: "M16 16a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2" })
|
|
6546
|
+
] });
|
|
6547
|
+
}
|
|
6548
|
+
function ScreenShareIcon(props) {
|
|
6549
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6550
|
+
/* @__PURE__ */ jsx2("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
|
|
6551
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 21h8M12 17v4" }),
|
|
6552
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 13V8m0 0-2.5 2.5M12 8l2.5 2.5" })
|
|
6553
|
+
] });
|
|
6554
|
+
}
|
|
6555
|
+
function PhoneIcon(props) {
|
|
6556
|
+
return /* @__PURE__ */ jsx2(Icon, { ...props, children: /* @__PURE__ */ jsx2("path", { d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6A19.79 19.79 0 0 1 2.12 4.18 2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.9.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92Z" }) });
|
|
6557
|
+
}
|
|
6558
|
+
function PhoneOffIcon(props) {
|
|
6559
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6560
|
+
/* @__PURE__ */ jsx2("path", { d: "M2 2l20 20" }),
|
|
6561
|
+
/* @__PURE__ */ jsx2("path", { d: "M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45c.9.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67" }),
|
|
6562
|
+
/* @__PURE__ */ jsx2("path", { d: "M5.53 5.53A19.79 19.79 0 0 1 2.12 4.18 2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91" })
|
|
6563
|
+
] });
|
|
6564
|
+
}
|
|
6565
|
+
function HandIcon(props) {
|
|
6566
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6567
|
+
/* @__PURE__ */ jsx2("path", { d: "M18 11V6a2 2 0 0 0-4 0v5" }),
|
|
6568
|
+
/* @__PURE__ */ jsx2("path", { d: "M14 10V4a2 2 0 0 0-4 0v7" }),
|
|
6569
|
+
/* @__PURE__ */ jsx2("path", { d: "M10 10.5V6a2 2 0 0 0-4 0v8" }),
|
|
6570
|
+
/* @__PURE__ */ jsx2("path", { d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2a8 8 0 0 1-8-8v-1a2 2 0 1 1 4 0" })
|
|
6571
|
+
] });
|
|
6572
|
+
}
|
|
6573
|
+
function ChatIcon(props) {
|
|
6574
|
+
return /* @__PURE__ */ jsx2(Icon, { ...props, children: /* @__PURE__ */ jsx2("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z" }) });
|
|
6575
|
+
}
|
|
6576
|
+
function PeopleIcon(props) {
|
|
6577
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6578
|
+
/* @__PURE__ */ jsx2("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
|
|
6579
|
+
/* @__PURE__ */ jsx2("circle", { cx: "9", cy: "7", r: "4" }),
|
|
6580
|
+
/* @__PURE__ */ jsx2("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" })
|
|
6581
|
+
] });
|
|
6582
|
+
}
|
|
6583
|
+
function RecordIcon(props) {
|
|
6584
|
+
return /* @__PURE__ */ jsx2(Icon, { ...props, children: /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "8", fill: "currentColor", stroke: "none" }) });
|
|
6585
|
+
}
|
|
6586
|
+
function SparklesIcon(props) {
|
|
6587
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6588
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 3l1.9 4.6L18.5 9.5l-4.6 1.9L12 16l-1.9-4.6L5.5 9.5l4.6-1.9L12 3Z" }),
|
|
6589
|
+
/* @__PURE__ */ jsx2("path", { d: "M19 15l.9 2.1 2.1.9-2.1.9-.9 2.1-.9-2.1-2.1-.9 2.1-.9L19 15Z" })
|
|
6590
|
+
] });
|
|
6591
|
+
}
|
|
6592
|
+
function LockIcon(props) {
|
|
6593
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6594
|
+
/* @__PURE__ */ jsx2("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2" }),
|
|
6595
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 10V7a4 4 0 0 1 8 0v3" })
|
|
6596
|
+
] });
|
|
6597
|
+
}
|
|
6598
|
+
function LayoutIcon(props) {
|
|
6599
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6600
|
+
/* @__PURE__ */ jsx2("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
|
|
6601
|
+
/* @__PURE__ */ jsx2("path", { d: "M3 9h18M9 21V9" })
|
|
6602
|
+
] });
|
|
6603
|
+
}
|
|
6604
|
+
function SettingsIcon(props) {
|
|
6605
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6606
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "3" }),
|
|
6607
|
+
/* @__PURE__ */ jsx2("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" })
|
|
6608
|
+
] });
|
|
6609
|
+
}
|
|
6610
|
+
function CheckIcon(props) {
|
|
6611
|
+
return /* @__PURE__ */ jsx2(Icon, { ...props, children: /* @__PURE__ */ jsx2("path", { d: "M20 6 9 17l-5-5" }) });
|
|
6612
|
+
}
|
|
6613
|
+
function CloseIcon(props) {
|
|
6614
|
+
return /* @__PURE__ */ jsx2(Icon, { ...props, children: /* @__PURE__ */ jsx2("path", { d: "M18 6 6 18M6 6l12 12" }) });
|
|
6615
|
+
}
|
|
6616
|
+
function PinIcon(props) {
|
|
6617
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6618
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 17v5" }),
|
|
6619
|
+
/* @__PURE__ */ jsx2("path", { d: "M9 2h6l-1 6 3.5 3.5V14H6.5v-2.5L10 8 9 2Z" })
|
|
6620
|
+
] });
|
|
6621
|
+
}
|
|
6622
|
+
function WifiLowIcon(props) {
|
|
6623
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6624
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 20h.01" }),
|
|
6625
|
+
/* @__PURE__ */ jsx2("path", { d: "M8.5 16.5a5 5 0 0 1 7 0" })
|
|
6626
|
+
] });
|
|
6627
|
+
}
|
|
6628
|
+
function SmileIcon(props) {
|
|
6629
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6630
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "10" }),
|
|
6631
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
|
|
6632
|
+
/* @__PURE__ */ jsx2("line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }),
|
|
6633
|
+
/* @__PURE__ */ jsx2("line", { x1: "15", x2: "15.01", y1: "9", y2: "9" })
|
|
6634
|
+
] });
|
|
6635
|
+
}
|
|
6636
|
+
function MoreIcon(props) {
|
|
6637
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6638
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "1" }),
|
|
6639
|
+
/* @__PURE__ */ jsx2("circle", { cx: "19", cy: "12", r: "1" }),
|
|
6640
|
+
/* @__PURE__ */ jsx2("circle", { cx: "5", cy: "12", r: "1" })
|
|
6641
|
+
] });
|
|
6642
|
+
}
|
|
6643
|
+
function SearchIcon(props) {
|
|
6644
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6645
|
+
/* @__PURE__ */ jsx2("circle", { cx: "11", cy: "11", r: "8" }),
|
|
6646
|
+
/* @__PURE__ */ jsx2("path", { d: "m21 21-4.3-4.3" })
|
|
6647
|
+
] });
|
|
6229
6648
|
}
|
|
6230
6649
|
function KeyboardIcon(props) {
|
|
6231
6650
|
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
@@ -6487,6 +6906,15 @@ function ControlBar({ onLeave }) {
|
|
|
6487
6906
|
const micUnavailable = useLocalMedia().microphone.unavailable;
|
|
6488
6907
|
const unread = useUnreadChat(panel === "chat");
|
|
6489
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]);
|
|
6490
6918
|
useMeetShortcuts({
|
|
6491
6919
|
mic: () => void room.toggleMicrophone(),
|
|
6492
6920
|
camera: () => void room.toggleCamera(),
|
|
@@ -6726,7 +7154,12 @@ function Popover({
|
|
|
6726
7154
|
placement = "up"
|
|
6727
7155
|
}) {
|
|
6728
7156
|
const ref = useRef3(null);
|
|
6729
|
-
const [style, setStyle] = useState3({
|
|
7157
|
+
const [style, setStyle] = useState3({
|
|
7158
|
+
position: "fixed",
|
|
7159
|
+
top: 0,
|
|
7160
|
+
left: 0,
|
|
7161
|
+
visibility: "hidden"
|
|
7162
|
+
});
|
|
6730
7163
|
useLayoutEffect(() => {
|
|
6731
7164
|
const element = ref.current;
|
|
6732
7165
|
const anchor = element?.parentElement ?? null;
|
|
@@ -6742,14 +7175,35 @@ function Popover({
|
|
|
6742
7175
|
);
|
|
6743
7176
|
const fitsBelow = rect.bottom + gutter + height <= window.innerHeight;
|
|
6744
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
|
+
}
|
|
6745
7189
|
const down = placement === "down" ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;
|
|
6746
7190
|
setStyle(
|
|
6747
7191
|
down ? { position: "fixed", left, top: rect.bottom + gutter } : { position: "fixed", left, bottom: window.innerHeight - rect.top + gutter }
|
|
6748
7192
|
);
|
|
6749
7193
|
};
|
|
6750
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);
|
|
6751
7199
|
window.addEventListener("resize", place);
|
|
6752
|
-
|
|
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
|
+
};
|
|
6753
7207
|
}, [placement]);
|
|
6754
7208
|
useEffect3(() => {
|
|
6755
7209
|
const onKey = (event) => {
|
|
@@ -7126,11 +7580,15 @@ function PersonMenu({
|
|
|
7126
7580
|
const pinned = snapshot?.pinned === participant.identity;
|
|
7127
7581
|
const spotlit = snapshot?.spotlight === participant.identity;
|
|
7128
7582
|
const hasAccount = participant.userId !== null;
|
|
7583
|
+
const [refused, setRefused] = useState3(false);
|
|
7129
7584
|
const act = (request) => {
|
|
7585
|
+
setRefused(false);
|
|
7130
7586
|
void controls.run(request).then((result) => {
|
|
7131
7587
|
if (result !== null) onClose();
|
|
7588
|
+
else setRefused(true);
|
|
7132
7589
|
});
|
|
7133
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;
|
|
7134
7592
|
if (mode === "rename" || mode === "report") {
|
|
7135
7593
|
const renaming = mode === "rename";
|
|
7136
7594
|
return /* @__PURE__ */ jsx3(Popover, { label: renaming ? "Rename" : "Report", onClose, placement, children: /* @__PURE__ */ jsxs2(
|
|
@@ -7168,7 +7626,7 @@ function PersonMenu({
|
|
|
7168
7626
|
}
|
|
7169
7627
|
)
|
|
7170
7628
|
] }),
|
|
7171
|
-
|
|
7629
|
+
refusal2,
|
|
7172
7630
|
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-actions", children: [
|
|
7173
7631
|
/* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Back" }),
|
|
7174
7632
|
/* @__PURE__ */ jsx3("button", { type: "submit", className: "mx-meet__send", disabled: text3.trim().length === 0 || controls.busy, children: renaming ? "Rename" : "Report" })
|
|
@@ -7181,6 +7639,7 @@ function PersonMenu({
|
|
|
7181
7639
|
const removing = mode === "remove";
|
|
7182
7640
|
return /* @__PURE__ */ jsx3(Popover, { label: removing ? "Remove" : "Make host", onClose, placement, children: /* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-form", children: [
|
|
7183
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,
|
|
7184
7643
|
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-actions", children: [
|
|
7185
7644
|
/* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Cancel" }),
|
|
7186
7645
|
/* @__PURE__ */ jsx3(
|
|
@@ -7211,11 +7670,15 @@ function PersonMenu({
|
|
|
7211
7670
|
]
|
|
7212
7671
|
}
|
|
7213
7672
|
),
|
|
7214
|
-
!self && !participant.isAgent && onMessage !== void 0 ? /* @__PURE__ */ jsxs2(
|
|
7673
|
+
!self && !participant.isAgent && (onMessage !== void 0 || host !== null) ? /* @__PURE__ */ jsxs2(
|
|
7215
7674
|
MenuItem,
|
|
7216
7675
|
{
|
|
7217
7676
|
onSelect: () => {
|
|
7218
|
-
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
|
+
});
|
|
7219
7682
|
onClose();
|
|
7220
7683
|
},
|
|
7221
7684
|
children: [
|
|
@@ -7842,7 +8305,7 @@ function PreJoinBody({
|
|
|
7842
8305
|
microphone,
|
|
7843
8306
|
...meetingId !== void 0 ? { meetingId } : {},
|
|
7844
8307
|
...slug !== void 0 ? { slug } : {}
|
|
7845
|
-
}).then(() => onJoined?.()).finally(() => setJoining(false));
|
|
8308
|
+
}).then(() => onJoined?.()).catch(() => void 0).finally(() => setJoining(false));
|
|
7846
8309
|
},
|
|
7847
8310
|
children: joining ? "Joining\u2026" : "Join now"
|
|
7848
8311
|
}
|
|
@@ -7978,7 +8441,7 @@ function MeetingRoomBody({
|
|
|
7978
8441
|
/* @__PURE__ */ jsx3("h1", { className: "mx-meet__title", children: snapshot?.meeting?.title ?? "Meeting" }),
|
|
7979
8442
|
/* @__PURE__ */ jsxs2("span", { className: "mx-meet__meta", children: [
|
|
7980
8443
|
participantSummary(snapshot?.participants ?? []),
|
|
7981
|
-
elapsed > 0 ? ` \xB7 ${
|
|
8444
|
+
elapsed > 0 ? ` \xB7 ${formatDurationMs3(elapsed)}` : ""
|
|
7982
8445
|
] }),
|
|
7983
8446
|
/* @__PURE__ */ jsx3(RecordingIndicator, {}),
|
|
7984
8447
|
snapshot?.locked === true ? /* @__PURE__ */ jsxs2("span", { className: "mx-meet__badge", children: [
|
|
@@ -8284,7 +8747,10 @@ function MeetingRecordBody({
|
|
|
8284
8747
|
const blocks = groupTranscript(bundle.transcript, bundle.names);
|
|
8285
8748
|
const notice = recordingNotice(bundle.recording);
|
|
8286
8749
|
const fileId = playableRecordingFileId(bundle.recording);
|
|
8287
|
-
const attended =
|
|
8750
|
+
const attended = uniqueAttendees(
|
|
8751
|
+
bundle.attendees.filter((person) => person.admissionState !== "denied"),
|
|
8752
|
+
bundle.names
|
|
8753
|
+
);
|
|
8288
8754
|
const restrictionNotices = summarizeMeetingRecordRestrictions(bundle.restrictions);
|
|
8289
8755
|
const guestFinishedMeeting = host.identity.userId === null && meeting.endedAt !== null;
|
|
8290
8756
|
const everyRecordPartRefused = ["attendance", "notes", "transcript", "recording"].every(
|
|
@@ -8593,514 +9059,172 @@ function RecordingPlayerBody({ fileId, host }) {
|
|
|
8593
9059
|
const failure = thrown;
|
|
8594
9060
|
setError({
|
|
8595
9061
|
message: failure.message ?? "The recording could not be loaded.",
|
|
8596
|
-
remedy: failure.remedy ?? PLAYBACK_RETRY_REMEDY
|
|
8597
|
-
});
|
|
8598
|
-
} finally {
|
|
8599
|
-
setLoading(false);
|
|
8600
|
-
}
|
|
8601
|
-
})();
|
|
8602
|
-
},
|
|
8603
|
-
children: loading ? "Loading the recording\u2026" : "Play the recording"
|
|
8604
|
-
}
|
|
8605
|
-
)
|
|
8606
|
-
] });
|
|
8607
|
-
}
|
|
8608
|
-
function clockOf(iso) {
|
|
8609
|
-
const at = new Date(iso);
|
|
8610
|
-
if (Number.isNaN(at.getTime())) return "";
|
|
8611
|
-
return at.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
|
|
8612
|
-
}
|
|
8613
|
-
function MeetingList({ baseUrl }) {
|
|
8614
|
-
const host = useMeetHost();
|
|
8615
|
-
const list = useMeetings();
|
|
8616
|
-
if (host === null) return /* @__PURE__ */ jsx3(InertNotice, { what: "Your meetings" });
|
|
8617
|
-
if (list.failure !== null) {
|
|
8618
|
-
return /* @__PURE__ */ jsxs2(ErrorBox, { as: "div", className: "mx-meet__empty", error: list.failure, operation: "List your meetings", origin: "meet/components", children: [
|
|
8619
|
-
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-title", children: "Your meetings could not be listed" }),
|
|
8620
|
-
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-body", children: list.failure.message }),
|
|
8621
|
-
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-body", children: list.failure.remedy })
|
|
8622
|
-
] });
|
|
8623
|
-
}
|
|
8624
|
-
if (list.loading) {
|
|
8625
|
-
return /* @__PURE__ */ jsx3("div", { className: "mx-meet__empty", role: "status", children: /* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-title", children: "Loading your meetings\u2026" }) });
|
|
8626
|
-
}
|
|
8627
|
-
if (list.meetings.length === 0) {
|
|
8628
|
-
return /* @__PURE__ */ jsxs2("div", { className: "mx-meet__empty", role: "status", children: [
|
|
8629
|
-
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-title", children: "No meetings yet" }),
|
|
8630
|
-
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-body", children: "Create one above. Its link works before, during and after the meeting \u2014 afterwards it opens the record." })
|
|
8631
|
-
] });
|
|
8632
|
-
}
|
|
8633
|
-
const origin = baseUrl ?? (typeof window === "undefined" ? "" : window.location.origin);
|
|
8634
|
-
return /* @__PURE__ */ jsx3("ul", { className: "mx-meet-list", children: list.meetings.map((meeting) => {
|
|
8635
|
-
const ended = meeting.endedAt !== null;
|
|
8636
|
-
return /* @__PURE__ */ jsxs2("li", { className: "mx-meet-list__row", children: [
|
|
8637
|
-
/* @__PURE__ */ jsxs2("div", { className: "mx-meet-list__main", children: [
|
|
8638
|
-
/* @__PURE__ */ jsxs2("span", { className: "mx-meet-list__title", children: [
|
|
8639
|
-
meeting.title,
|
|
8640
|
-
ended ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__badge", children: "Ended" }) : null
|
|
8641
|
-
] }),
|
|
8642
|
-
/* @__PURE__ */ jsx3("span", { className: "mx-meet-list__when", children: meetingWhen(meeting) })
|
|
8643
|
-
] }),
|
|
8644
|
-
/* @__PURE__ */ jsx3("a", { className: "mx-meet-list__action", href: meetingLink(origin, meeting.slug), children: ended ? "Open the record" : "Join" })
|
|
8645
|
-
] }, meeting.id);
|
|
8646
|
-
}) });
|
|
8647
|
-
}
|
|
8648
|
-
|
|
8649
|
-
// src/core/invitations.ts
|
|
8650
|
-
var CALL_INVITE_KIND = "meet.call-invite";
|
|
8651
|
-
var CALL_INVITE_VERSION = 1;
|
|
8652
|
-
function callInviteAction(invite) {
|
|
8653
|
-
return {
|
|
8654
|
-
kind: CALL_INVITE_KIND,
|
|
8655
|
-
version: CALL_INVITE_VERSION,
|
|
8656
|
-
payload: {
|
|
8657
|
-
invite_id: invite.id,
|
|
8658
|
-
room_name: invite.roomName,
|
|
8659
|
-
mode: invite.mode,
|
|
8660
|
-
caller_name: invite.callerName,
|
|
8661
|
-
expires_at: invite.expiresAt
|
|
8662
|
-
}
|
|
8663
|
-
};
|
|
8664
|
-
}
|
|
8665
|
-
function callInviteText(invite) {
|
|
8666
|
-
return invite.mode === "video" ? `${invite.callerName} started a video call` : `${invite.callerName} started an audio call`;
|
|
8667
|
-
}
|
|
8668
|
-
function createCallInviteHandler(options) {
|
|
8669
|
-
const now = options.now ?? (() => Date.now());
|
|
8670
|
-
return {
|
|
8671
|
-
kind: CALL_INVITE_KIND,
|
|
8672
|
-
versions: [CALL_INVITE_VERSION],
|
|
8673
|
-
choices: (payload) => {
|
|
8674
|
-
if (Date.parse(payload.expires_at) < now()) return [];
|
|
8675
|
-
return [
|
|
8676
|
-
{ id: "join", label: payload.mode === "video" ? "Join video" : "Join call", tone: "primary" },
|
|
8677
|
-
{ id: "decline", label: "Decline", tone: "neutral" }
|
|
8678
|
-
];
|
|
8679
|
-
},
|
|
8680
|
-
summarize: (payload) => payload.mode === "video" ? `${payload.caller_name} started a video call` : `${payload.caller_name} started an audio call`,
|
|
8681
|
-
execute: async (payload, context) => {
|
|
8682
|
-
const settledAt = new Date(now()).toISOString();
|
|
8683
|
-
const inviteId = asCallId(payload.invite_id);
|
|
8684
|
-
const pending = await options.repository.pendingInvites(asUserId(String(context.actorId)));
|
|
8685
|
-
const live = pending.find((invite) => invite.id === inviteId) ?? null;
|
|
8686
|
-
if (live === null) {
|
|
8687
|
-
return {
|
|
8688
|
-
kind: CALL_INVITE_KIND,
|
|
8689
|
-
messageId: context.messageId,
|
|
8690
|
-
actorId: context.actorId,
|
|
8691
|
-
outcome: "unavailable",
|
|
8692
|
-
label: "This call has ended",
|
|
8693
|
-
settledAt,
|
|
8694
|
-
detail: "The invite is no longer ringing \u2014 it was answered elsewhere, cancelled, or it timed out. The chips are replaced by the outcome rather than left clickable."
|
|
8695
|
-
};
|
|
8696
|
-
}
|
|
8697
|
-
if (context.choice === "decline") {
|
|
8698
|
-
await options.calls.decline(inviteId);
|
|
8699
|
-
return {
|
|
8700
|
-
kind: CALL_INVITE_KIND,
|
|
8701
|
-
messageId: context.messageId,
|
|
8702
|
-
actorId: context.actorId,
|
|
8703
|
-
outcome: "declined",
|
|
8704
|
-
label: "Declined",
|
|
8705
|
-
settledAt
|
|
8706
|
-
};
|
|
8707
|
-
}
|
|
8708
|
-
const accepted = await options.calls.accept(inviteId, live.mode);
|
|
8709
|
-
await options.onJoin(accepted);
|
|
8710
|
-
return {
|
|
8711
|
-
kind: CALL_INVITE_KIND,
|
|
8712
|
-
messageId: context.messageId,
|
|
8713
|
-
actorId: context.actorId,
|
|
8714
|
-
outcome: "applied",
|
|
8715
|
-
label: "Joined",
|
|
8716
|
-
settledAt
|
|
8717
|
-
};
|
|
8718
|
-
}
|
|
8719
|
-
};
|
|
8720
|
-
}
|
|
8721
|
-
var MEETING_INVITE_KIND = "meet.meeting-invite";
|
|
8722
|
-
var MEETING_INVITE_VERSION = 1;
|
|
8723
|
-
function meetingInviteAction(payload) {
|
|
8724
|
-
return { kind: MEETING_INVITE_KIND, version: MEETING_INVITE_VERSION, payload };
|
|
8725
|
-
}
|
|
8726
|
-
function createMeetingInviteHandler(options) {
|
|
8727
|
-
const now = options.now ?? (() => Date.now());
|
|
8728
|
-
return {
|
|
8729
|
-
kind: MEETING_INVITE_KIND,
|
|
8730
|
-
versions: [MEETING_INVITE_VERSION],
|
|
8731
|
-
// A DURABLE LINK NEVER EXPIRES (R3) — the chip stays useful after the
|
|
8732
|
-
// meeting too, because that is when people go looking for the notes.
|
|
8733
|
-
choices: () => [{ id: "open", label: "Open meeting", tone: "primary" }],
|
|
8734
|
-
summarize: (payload) => payload.scheduled_for === null ? payload.title : `${payload.title} \u2014 ${payload.scheduled_for}`,
|
|
8735
|
-
execute: async (payload, context) => {
|
|
8736
|
-
if (payload.slug.length === 0) {
|
|
8737
|
-
throw new MeetError(
|
|
8738
|
-
"invalid-response",
|
|
8739
|
-
"meet.meeting-invite: the payload carries no slug",
|
|
8740
|
-
"A meeting invitation without a durable slug cannot be opened. Fix the sender; never synthesize a link on the receiving side."
|
|
8741
|
-
);
|
|
8742
|
-
}
|
|
8743
|
-
await options.onOpen(payload);
|
|
8744
|
-
return {
|
|
8745
|
-
kind: MEETING_INVITE_KIND,
|
|
8746
|
-
messageId: context.messageId,
|
|
8747
|
-
actorId: context.actorId,
|
|
8748
|
-
outcome: "applied",
|
|
8749
|
-
label: "Opened",
|
|
8750
|
-
settledAt: new Date(now()).toISOString()
|
|
8751
|
-
};
|
|
8752
|
-
}
|
|
8753
|
-
};
|
|
8754
|
-
}
|
|
8755
|
-
|
|
8756
|
-
// src/core/after.ts
|
|
8757
|
-
import { formatDurationMs as formatDurationMs3 } from "@ai-matrx/kit/format";
|
|
8758
|
-
function kindOf(person) {
|
|
8759
|
-
if (person.isAgent) return "assistant";
|
|
8760
|
-
if (person.role === "host") return "host";
|
|
8761
|
-
if (person.role === "cohost") return "cohost";
|
|
8762
|
-
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
8763
|
-
return "member";
|
|
8764
|
-
}
|
|
8765
|
-
function ms(iso) {
|
|
8766
|
-
if (iso === null) return null;
|
|
8767
|
-
const at = new Date(iso).getTime();
|
|
8768
|
-
return Number.isNaN(at) ? null : at;
|
|
8769
|
-
}
|
|
8770
|
-
function attendanceReport(meeting, attendees, names = {}) {
|
|
8771
|
-
const end = ms(meeting.endedAt);
|
|
8772
|
-
const rows = attendees.map((person) => {
|
|
8773
|
-
const joined = ms(person.joinedAt);
|
|
8774
|
-
const left = ms(person.leftAt);
|
|
8775
|
-
const until = left ?? end;
|
|
8776
|
-
const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
|
|
8777
|
-
return {
|
|
8778
|
-
id: person.id,
|
|
8779
|
-
identity: person.identity,
|
|
8780
|
-
name: displayNameFor(names, person.identity, person.displayName),
|
|
8781
|
-
userId: person.userId,
|
|
8782
|
-
kind: kindOf(person),
|
|
8783
|
-
joinedAt: person.joinedAt,
|
|
8784
|
-
leftAt: person.leftAt,
|
|
8785
|
-
durationMs: duration,
|
|
8786
|
-
durationToEnd: duration !== null && left === null,
|
|
8787
|
-
admission: person.admissionState
|
|
8788
|
-
};
|
|
8789
|
-
});
|
|
8790
|
-
const rank = {
|
|
8791
|
-
host: 0,
|
|
8792
|
-
cohost: 1,
|
|
8793
|
-
member: 2,
|
|
8794
|
-
"signed-in guest": 3,
|
|
8795
|
-
guest: 4,
|
|
8796
|
-
assistant: 5
|
|
8797
|
-
};
|
|
8798
|
-
return rows.sort((a, b) => {
|
|
8799
|
-
if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
|
|
8800
|
-
return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
|
|
8801
|
-
});
|
|
8802
|
-
}
|
|
8803
|
-
function attendanceTotals(rows) {
|
|
8804
|
-
const humans = rows.filter((row) => row.kind !== "assistant");
|
|
8805
|
-
const attended = humans.filter((row) => row.joinedAt !== null);
|
|
8806
|
-
const timed = attended.filter((row) => row.durationMs !== null);
|
|
8807
|
-
return {
|
|
8808
|
-
people: attended.length,
|
|
8809
|
-
guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
|
|
8810
|
-
neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
|
|
8811
|
-
averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
|
|
8812
|
-
};
|
|
8813
|
-
}
|
|
8814
|
-
function csvCell(value) {
|
|
8815
|
-
if (value === null) return "";
|
|
8816
|
-
const text3 = String(value);
|
|
8817
|
-
return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
|
|
8818
|
-
}
|
|
8819
|
-
var ATTENDANCE_CSV_HEADER = [
|
|
8820
|
-
"Meeting",
|
|
8821
|
-
"Meeting started",
|
|
8822
|
-
"Name",
|
|
8823
|
-
"Attended as",
|
|
8824
|
-
"Joined",
|
|
8825
|
-
"Left",
|
|
8826
|
-
"Minutes in the meeting",
|
|
8827
|
-
"Admission"
|
|
8828
|
-
];
|
|
8829
|
-
function attendanceCsv(reports) {
|
|
8830
|
-
const lines = [ATTENDANCE_CSV_HEADER.join(",")];
|
|
8831
|
-
for (const { meeting, rows } of reports) {
|
|
8832
|
-
for (const row of rows) {
|
|
8833
|
-
lines.push(
|
|
8834
|
-
[
|
|
8835
|
-
meeting.title,
|
|
8836
|
-
meeting.startedAt ?? meeting.scheduledFor,
|
|
8837
|
-
row.name,
|
|
8838
|
-
row.kind,
|
|
8839
|
-
row.joinedAt,
|
|
8840
|
-
row.leftAt,
|
|
8841
|
-
row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
|
|
8842
|
-
row.admission
|
|
8843
|
-
].map(csvCell).join(",")
|
|
8844
|
-
);
|
|
8845
|
-
}
|
|
8846
|
-
}
|
|
8847
|
-
return `${lines.join("\r\n")}\r
|
|
8848
|
-
`;
|
|
8849
|
-
}
|
|
8850
|
-
function recordingOffsetMs(at, recording) {
|
|
8851
|
-
if (recording === null || recording.state !== "available") return null;
|
|
8852
|
-
const start = ms(recording.startedAt);
|
|
8853
|
-
const moment = ms(at);
|
|
8854
|
-
if (start === null || moment === null || moment < start) return null;
|
|
8855
|
-
const end = ms(recording.endedAt);
|
|
8856
|
-
if (end !== null && moment > end) return null;
|
|
8857
|
-
return moment - start;
|
|
8858
|
-
}
|
|
8859
|
-
function clockPosition(offsetMs) {
|
|
8860
|
-
const total = Math.max(0, Math.floor(offsetMs / 1e3));
|
|
8861
|
-
const h = Math.floor(total / 3600);
|
|
8862
|
-
const m = Math.floor(total % 3600 / 60);
|
|
8863
|
-
const s = total % 60;
|
|
8864
|
-
const ss = String(s).padStart(2, "0");
|
|
8865
|
-
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
8866
|
-
}
|
|
8867
|
-
function searchTranscript(segments, query, names = {}) {
|
|
8868
|
-
const needle = fold(query.trim());
|
|
8869
|
-
if (needle.length === 0) return [];
|
|
8870
|
-
const out = [];
|
|
8871
|
-
for (const segment of segments) {
|
|
8872
|
-
const hay = fold(segment.text);
|
|
8873
|
-
const ranges = [];
|
|
8874
|
-
let from = 0;
|
|
8875
|
-
for (; ; ) {
|
|
8876
|
-
const at = hay.indexOf(needle, from);
|
|
8877
|
-
if (at < 0) break;
|
|
8878
|
-
ranges.push([at, at + needle.length]);
|
|
8879
|
-
from = at + needle.length;
|
|
8880
|
-
}
|
|
8881
|
-
const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
|
|
8882
|
-
if (ranges.length > 0 || speaker.includes(needle)) {
|
|
8883
|
-
out.push({ segmentId: segment.id, ranges });
|
|
8884
|
-
}
|
|
8885
|
-
}
|
|
8886
|
-
return out;
|
|
8887
|
-
}
|
|
8888
|
-
function fold(value) {
|
|
8889
|
-
return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
|
|
8890
|
-
}
|
|
8891
|
-
function clockOfDay(iso, options) {
|
|
8892
|
-
const at = new Date(iso);
|
|
8893
|
-
if (Number.isNaN(at.getTime())) return "";
|
|
8894
|
-
return at.toLocaleTimeString(options.locale, {
|
|
8895
|
-
hour: "numeric",
|
|
8896
|
-
minute: "2-digit",
|
|
8897
|
-
second: "2-digit",
|
|
8898
|
-
...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
|
|
8899
|
-
});
|
|
8900
|
-
}
|
|
8901
|
-
function vttStamp(offsetMs) {
|
|
8902
|
-
const total = Math.max(0, Math.round(offsetMs));
|
|
8903
|
-
const h = Math.floor(total / 36e5);
|
|
8904
|
-
const m = Math.floor(total % 36e5 / 6e4);
|
|
8905
|
-
const s = Math.floor(total % 6e4 / 1e3);
|
|
8906
|
-
const milli = total % 1e3;
|
|
8907
|
-
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
|
|
8908
|
-
}
|
|
8909
|
-
function transcriptDocument(bundle, format, options = {}) {
|
|
8910
|
-
const names = options.names ?? bundle.names;
|
|
8911
|
-
const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
|
|
8912
|
-
const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
|
|
8913
|
-
if (format === "vtt") {
|
|
8914
|
-
const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
|
|
8915
|
-
const zero = ms(zeroIso) ?? 0;
|
|
8916
|
-
const cues = ["WEBVTT", ""];
|
|
8917
|
-
lines.forEach((line, index) => {
|
|
8918
|
-
const start = (ms(line.startedAt) ?? zero) - zero;
|
|
8919
|
-
if (start < 0) return;
|
|
8920
|
-
const next = lines[index + 1];
|
|
8921
|
-
const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
|
|
8922
|
-
const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
|
|
8923
|
-
cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
|
|
8924
|
-
cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
|
|
8925
|
-
cues.push("");
|
|
8926
|
-
});
|
|
8927
|
-
return cues.join("\n");
|
|
8928
|
-
}
|
|
8929
|
-
const out = [];
|
|
8930
|
-
const title = bundle.meeting.title;
|
|
8931
|
-
out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
|
|
8932
|
-
out.push("");
|
|
8933
|
-
let last = null;
|
|
8934
|
-
for (const line of lines) {
|
|
8935
|
-
if (line.identity !== last) {
|
|
8936
|
-
if (last !== null) out.push("");
|
|
8937
|
-
const who = speaker(line);
|
|
8938
|
-
const clock = clockOfDay(line.startedAt, options);
|
|
8939
|
-
out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
|
|
8940
|
-
last = line.identity;
|
|
8941
|
-
}
|
|
8942
|
-
out.push(line.text.trim());
|
|
8943
|
-
}
|
|
8944
|
-
if (lines.length === 0) out.push("No transcript was captured for this meeting.");
|
|
8945
|
-
return `${out.join("\n")}
|
|
8946
|
-
`;
|
|
8947
|
-
}
|
|
8948
|
-
function bullet(note) {
|
|
8949
|
-
const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
|
|
8950
|
-
return `- ${note.text.trim()}${owner}`;
|
|
8951
|
-
}
|
|
8952
|
-
function notesDocument(bundle, options = {}) {
|
|
8953
|
-
const out = [`# ${bundle.meeting.title}`, ""];
|
|
8954
|
-
if (options.when) out.push(`_${options.when}_`, "");
|
|
8955
|
-
const people = bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
|
|
8956
|
-
if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
|
|
8957
|
-
out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
|
|
8958
|
-
out.push("## Decisions", "");
|
|
8959
|
-
out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
|
|
8960
|
-
out.push("## Action items", "");
|
|
8961
|
-
out.push(
|
|
8962
|
-
...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
|
|
8963
|
-
""
|
|
8964
|
-
);
|
|
8965
|
-
if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
|
|
8966
|
-
return out.join("\n");
|
|
9062
|
+
remedy: failure.remedy ?? PLAYBACK_RETRY_REMEDY
|
|
9063
|
+
});
|
|
9064
|
+
} finally {
|
|
9065
|
+
setLoading(false);
|
|
9066
|
+
}
|
|
9067
|
+
})();
|
|
9068
|
+
},
|
|
9069
|
+
children: loading ? "Loading the recording\u2026" : "Play the recording"
|
|
9070
|
+
}
|
|
9071
|
+
)
|
|
9072
|
+
] });
|
|
8967
9073
|
}
|
|
8968
|
-
function
|
|
8969
|
-
const
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
return formatDurationMs3(end - start, { style: "long" });
|
|
9074
|
+
function clockOf(iso) {
|
|
9075
|
+
const at = new Date(iso);
|
|
9076
|
+
if (Number.isNaN(at.getTime())) return "";
|
|
9077
|
+
return at.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
|
|
8973
9078
|
}
|
|
8974
|
-
|
|
8975
|
-
const
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
});
|
|
8985
|
-
} catch (error) {
|
|
8986
|
-
return {
|
|
8987
|
-
messages: [],
|
|
8988
|
-
refusal: {
|
|
8989
|
-
message: `The chat could not be read (${error.message}).`,
|
|
8990
|
-
remedy: error.remedy ?? "Reload the page."
|
|
8991
|
-
}
|
|
8992
|
-
};
|
|
9079
|
+
function MeetingList({ baseUrl }) {
|
|
9080
|
+
const host = useMeetHost();
|
|
9081
|
+
const list = useMeetings();
|
|
9082
|
+
if (host === null) return /* @__PURE__ */ jsx3(InertNotice, { what: "Your meetings" });
|
|
9083
|
+
if (list.failure !== null) {
|
|
9084
|
+
return /* @__PURE__ */ jsxs2(ErrorBox, { as: "div", className: "mx-meet__empty", error: list.failure, operation: "List your meetings", origin: "meet/components", children: [
|
|
9085
|
+
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-title", children: "Your meetings could not be listed" }),
|
|
9086
|
+
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-body", children: list.failure.message }),
|
|
9087
|
+
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-body", children: list.failure.remedy })
|
|
9088
|
+
] });
|
|
8993
9089
|
}
|
|
8994
|
-
if (
|
|
8995
|
-
|
|
8996
|
-
try {
|
|
8997
|
-
body = await response.json();
|
|
8998
|
-
} catch {
|
|
8999
|
-
body = null;
|
|
9000
|
-
}
|
|
9001
|
-
const said = readServerRefusal(body);
|
|
9002
|
-
return {
|
|
9003
|
-
messages: [],
|
|
9004
|
-
refusal: {
|
|
9005
|
-
message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
|
|
9006
|
-
remedy: said?.remedy || "Reload the page."
|
|
9007
|
-
}
|
|
9008
|
-
};
|
|
9090
|
+
if (list.loading) {
|
|
9091
|
+
return /* @__PURE__ */ jsx3("div", { className: "mx-meet__empty", role: "status", children: /* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-title", children: "Loading your meetings\u2026" }) });
|
|
9009
9092
|
}
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
).filter((message) => message !== null),
|
|
9016
|
-
refusal: null
|
|
9017
|
-
};
|
|
9018
|
-
}
|
|
9019
|
-
async function refusalOf(response, operation) {
|
|
9020
|
-
let body = null;
|
|
9021
|
-
try {
|
|
9022
|
-
body = await response.json();
|
|
9023
|
-
} catch {
|
|
9024
|
-
body = null;
|
|
9093
|
+
if (list.meetings.length === 0) {
|
|
9094
|
+
return /* @__PURE__ */ jsxs2("div", { className: "mx-meet__empty", role: "status", children: [
|
|
9095
|
+
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-title", children: "No meetings yet" }),
|
|
9096
|
+
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-body", children: "Create one above. Its link works before, during and after the meeting \u2014 afterwards it opens the record." })
|
|
9097
|
+
] });
|
|
9025
9098
|
}
|
|
9026
|
-
const
|
|
9027
|
-
return
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9099
|
+
const origin = baseUrl ?? (typeof window === "undefined" ? "" : window.location.origin);
|
|
9100
|
+
return /* @__PURE__ */ jsx3("ul", { className: "mx-meet-list", children: list.meetings.map((meeting) => {
|
|
9101
|
+
const ended = meeting.endedAt !== null;
|
|
9102
|
+
return /* @__PURE__ */ jsxs2("li", { className: "mx-meet-list__row", children: [
|
|
9103
|
+
/* @__PURE__ */ jsxs2("div", { className: "mx-meet-list__main", children: [
|
|
9104
|
+
/* @__PURE__ */ jsxs2("span", { className: "mx-meet-list__title", children: [
|
|
9105
|
+
meeting.title,
|
|
9106
|
+
ended ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__badge", children: "Ended" }) : null
|
|
9107
|
+
] }),
|
|
9108
|
+
/* @__PURE__ */ jsx3("span", { className: "mx-meet-list__when", children: meetingWhen(meeting) })
|
|
9109
|
+
] }),
|
|
9110
|
+
/* @__PURE__ */ jsx3("a", { className: "mx-meet-list__action", href: meetingLink(origin, meeting.slug), children: ended ? "Open the record" : "Join" })
|
|
9111
|
+
] }, meeting.id);
|
|
9112
|
+
}) });
|
|
9032
9113
|
}
|
|
9033
|
-
|
|
9034
|
-
|
|
9114
|
+
|
|
9115
|
+
// src/core/invitations.ts
|
|
9116
|
+
var CALL_INVITE_KIND = "meet.call-invite";
|
|
9117
|
+
var CALL_INVITE_VERSION = 1;
|
|
9118
|
+
function callInviteAction(invite) {
|
|
9035
9119
|
return {
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9120
|
+
kind: CALL_INVITE_KIND,
|
|
9121
|
+
version: CALL_INVITE_VERSION,
|
|
9122
|
+
payload: {
|
|
9123
|
+
invite_id: invite.id,
|
|
9124
|
+
room_name: invite.roomName,
|
|
9125
|
+
mode: invite.mode,
|
|
9126
|
+
caller_name: invite.callerName,
|
|
9127
|
+
expires_at: invite.expiresAt
|
|
9128
|
+
}
|
|
9042
9129
|
};
|
|
9043
9130
|
}
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
requireAuth: true,
|
|
9050
|
-
operation
|
|
9051
|
-
});
|
|
9052
|
-
if (!response.ok) throw await refusalOf(response, operation);
|
|
9053
|
-
const body = await response.json();
|
|
9054
|
-
const last = body["last_sent"];
|
|
9131
|
+
function callInviteText(invite) {
|
|
9132
|
+
return invite.mode === "video" ? `${invite.callerName} started a video call` : `${invite.callerName} started an audio call`;
|
|
9133
|
+
}
|
|
9134
|
+
function createCallInviteHandler(options) {
|
|
9135
|
+
const now = options.now ?? (() => Date.now());
|
|
9055
9136
|
return {
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
(
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
|
|
9137
|
+
kind: CALL_INVITE_KIND,
|
|
9138
|
+
versions: [CALL_INVITE_VERSION],
|
|
9139
|
+
choices: (payload) => {
|
|
9140
|
+
if (Date.parse(payload.expires_at) < now()) return [];
|
|
9141
|
+
return [
|
|
9142
|
+
{ id: "join", label: payload.mode === "video" ? "Join video" : "Join call", tone: "primary" },
|
|
9143
|
+
{ id: "decline", label: "Decline", tone: "neutral" }
|
|
9144
|
+
];
|
|
9145
|
+
},
|
|
9146
|
+
summarize: (payload) => payload.mode === "video" ? `${payload.caller_name} started a video call` : `${payload.caller_name} started an audio call`,
|
|
9147
|
+
execute: async (payload, context) => {
|
|
9148
|
+
const settledAt = new Date(now()).toISOString();
|
|
9149
|
+
const inviteId = asCallId(payload.invite_id);
|
|
9150
|
+
const pending = await options.repository.pendingInvites(asUserId(String(context.actorId)));
|
|
9151
|
+
const live = pending.find((invite) => invite.id === inviteId) ?? null;
|
|
9152
|
+
if (live === null) {
|
|
9153
|
+
return {
|
|
9154
|
+
kind: CALL_INVITE_KIND,
|
|
9155
|
+
messageId: context.messageId,
|
|
9156
|
+
actorId: context.actorId,
|
|
9157
|
+
outcome: "unavailable",
|
|
9158
|
+
label: "This call has ended",
|
|
9159
|
+
settledAt,
|
|
9160
|
+
detail: "The invite is no longer ringing \u2014 it was answered elsewhere, cancelled, or it timed out. The chips are replaced by the outcome rather than left clickable."
|
|
9161
|
+
};
|
|
9162
|
+
}
|
|
9163
|
+
if (context.choice === "decline") {
|
|
9164
|
+
await options.calls.decline(inviteId);
|
|
9165
|
+
return {
|
|
9166
|
+
kind: CALL_INVITE_KIND,
|
|
9167
|
+
messageId: context.messageId,
|
|
9168
|
+
actorId: context.actorId,
|
|
9169
|
+
outcome: "declined",
|
|
9170
|
+
label: "Declined",
|
|
9171
|
+
settledAt
|
|
9172
|
+
};
|
|
9173
|
+
}
|
|
9174
|
+
const accepted = await options.calls.accept(inviteId, live.mode);
|
|
9175
|
+
await options.onJoin(accepted);
|
|
9176
|
+
return {
|
|
9177
|
+
kind: CALL_INVITE_KIND,
|
|
9178
|
+
messageId: context.messageId,
|
|
9179
|
+
actorId: context.actorId,
|
|
9180
|
+
outcome: "applied",
|
|
9181
|
+
label: "Joined",
|
|
9182
|
+
settledAt
|
|
9183
|
+
};
|
|
9184
|
+
}
|
|
9063
9185
|
};
|
|
9064
9186
|
}
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
body: {
|
|
9073
|
-
subject: input.subject,
|
|
9074
|
-
body: input.body,
|
|
9075
|
-
recipient_keys: [...input.recipientKeys],
|
|
9076
|
-
dry_run: input.dryRun === true
|
|
9077
|
-
}
|
|
9078
|
-
});
|
|
9079
|
-
if (!response.ok) throw await refusalOf(response, operation);
|
|
9080
|
-
const body = await response.json();
|
|
9187
|
+
var MEETING_INVITE_KIND = "meet.meeting-invite";
|
|
9188
|
+
var MEETING_INVITE_VERSION = 1;
|
|
9189
|
+
function meetingInviteAction(payload) {
|
|
9190
|
+
return { kind: MEETING_INVITE_KIND, version: MEETING_INVITE_VERSION, payload };
|
|
9191
|
+
}
|
|
9192
|
+
function createMeetingInviteHandler(options) {
|
|
9193
|
+
const now = options.now ?? (() => Date.now());
|
|
9081
9194
|
return {
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9195
|
+
kind: MEETING_INVITE_KIND,
|
|
9196
|
+
versions: [MEETING_INVITE_VERSION],
|
|
9197
|
+
// A DURABLE LINK NEVER EXPIRES (R3) — the chip stays useful after the
|
|
9198
|
+
// meeting too, because that is when people go looking for the notes.
|
|
9199
|
+
choices: () => [{ id: "open", label: "Open meeting", tone: "primary" }],
|
|
9200
|
+
summarize: (payload) => payload.scheduled_for === null ? payload.title : `${payload.title} \u2014 ${payload.scheduled_for}`,
|
|
9201
|
+
execute: async (payload, context) => {
|
|
9202
|
+
if (payload.slug.length === 0) {
|
|
9203
|
+
throw new MeetError(
|
|
9204
|
+
"invalid-response",
|
|
9205
|
+
"meet.meeting-invite: the payload carries no slug",
|
|
9206
|
+
"A meeting invitation without a durable slug cannot be opened. Fix the sender; never synthesize a link on the receiving side."
|
|
9207
|
+
);
|
|
9208
|
+
}
|
|
9209
|
+
await options.onOpen(payload);
|
|
9210
|
+
return {
|
|
9211
|
+
kind: MEETING_INVITE_KIND,
|
|
9212
|
+
messageId: context.messageId,
|
|
9213
|
+
actorId: context.actorId,
|
|
9214
|
+
outcome: "applied",
|
|
9215
|
+
label: "Opened",
|
|
9216
|
+
settledAt: new Date(now()).toISOString()
|
|
9217
|
+
};
|
|
9218
|
+
}
|
|
9088
9219
|
};
|
|
9089
9220
|
}
|
|
9090
9221
|
|
|
9091
9222
|
// src/core/management.ts
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
body = null;
|
|
9098
|
-
}
|
|
9099
|
-
const said = readServerRefusal(body);
|
|
9100
|
-
return new MeetError(
|
|
9101
|
-
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
9102
|
-
`${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
|
|
9103
|
-
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."
|
|
9104
9228
|
);
|
|
9105
9229
|
}
|
|
9106
9230
|
function count(body, key) {
|
|
@@ -9255,6 +9379,7 @@ export {
|
|
|
9255
9379
|
attendanceCsv,
|
|
9256
9380
|
attendanceReport,
|
|
9257
9381
|
attendanceTotals,
|
|
9382
|
+
attendeeKey,
|
|
9258
9383
|
callInviteAction,
|
|
9259
9384
|
callInviteText,
|
|
9260
9385
|
callStateLabel,
|
|
@@ -9358,6 +9483,7 @@ export {
|
|
|
9358
9483
|
summarizeMeetingRecordRestrictions,
|
|
9359
9484
|
transcriptDocument,
|
|
9360
9485
|
unavailableFrom,
|
|
9486
|
+
uniqueAttendees,
|
|
9361
9487
|
useAudioLevel,
|
|
9362
9488
|
useCalls,
|
|
9363
9489
|
useCanModerate,
|