@ai-matrx/meet 0.7.7 → 0.7.9
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 +51 -0
- package/dist/index.cjs +145 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -50
- package/dist/index.d.ts +80 -50
- package/dist/index.js +145 -56
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +842 -706
- 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 +818 -682
- package/dist/react.js.map +1 -1
- package/dist/styles.css +18 -0
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -43,6 +43,20 @@ var MeetError = class extends Error {
|
|
|
43
43
|
return this.code === "permission-denied" || this.code === "device-not-found" || this.code === "device-in-use" || this.code === "insecure-context";
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
|
+
var SERVER_FAILED_SENTENCE = "The server hit an unexpected problem and nothing was changed.";
|
|
47
|
+
function isRawServerText(value) {
|
|
48
|
+
const text3 = value.trim();
|
|
49
|
+
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);
|
|
50
|
+
}
|
|
51
|
+
function humanDatabaseSentence(message) {
|
|
52
|
+
const text3 = message.trim();
|
|
53
|
+
if (text3.length === 0 || isRawServerText(text3)) return null;
|
|
54
|
+
if (/permission denied for|violates|relation "|does not exist|syntax error|column "|jwt|pgrst|duplicate key|null value in/i.test(
|
|
55
|
+
text3
|
|
56
|
+
))
|
|
57
|
+
return null;
|
|
58
|
+
return text3;
|
|
59
|
+
}
|
|
46
60
|
var FORBIDDEN_CODES = /* @__PURE__ */ new Set(["42501", "PGRST301", "PGRST302"]);
|
|
47
61
|
var NOT_FOUND_CODES = /* @__PURE__ */ new Set(["PGRST116", "PGRST205"]);
|
|
48
62
|
var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505", "40001", "55000"]);
|
|
@@ -70,8 +84,8 @@ function normalizeMeetError(error, operation) {
|
|
|
70
84
|
if (code !== void 0 && FORBIDDEN_CODES.has(code)) {
|
|
71
85
|
return new MeetError(
|
|
72
86
|
"forbidden",
|
|
73
|
-
|
|
74
|
-
"
|
|
87
|
+
humanDatabaseSentence(message) ?? "You don't have access to do that in this meeting.",
|
|
88
|
+
"Ask the meeting's host to do it, or to give you access.",
|
|
75
89
|
error
|
|
76
90
|
);
|
|
77
91
|
}
|
|
@@ -86,15 +100,16 @@ function normalizeMeetError(error, operation) {
|
|
|
86
100
|
if (code !== void 0 && CONFLICT_CODES.has(code)) {
|
|
87
101
|
return new MeetError(
|
|
88
102
|
"conflict",
|
|
89
|
-
|
|
90
|
-
"
|
|
103
|
+
humanDatabaseSentence(message) ?? "This meeting changed a moment ago, so nothing was saved.",
|
|
104
|
+
"Reload to see the latest, then try again.",
|
|
91
105
|
error
|
|
92
106
|
);
|
|
93
107
|
}
|
|
108
|
+
const hint = error?.hint;
|
|
94
109
|
return new MeetError(
|
|
95
110
|
"transport",
|
|
96
|
-
|
|
97
|
-
|
|
111
|
+
/failed to fetch|networkerror|load failed|network request failed/i.test(message) ? "The server could not be reached." : humanDatabaseSentence(message) ?? SERVER_FAILED_SENTENCE,
|
|
112
|
+
typeof hint === "string" && humanDatabaseSentence(hint) !== null ? hint.trim() : "Nothing was changed \u2014 retry in a moment; if it keeps happening, reload the page.",
|
|
98
113
|
error
|
|
99
114
|
);
|
|
100
115
|
}
|
|
@@ -502,6 +517,24 @@ function readText(data) {
|
|
|
502
517
|
}
|
|
503
518
|
return "";
|
|
504
519
|
}
|
|
520
|
+
async function refusalFromResponse(response, operation, fallbackRemedy) {
|
|
521
|
+
let body = null;
|
|
522
|
+
try {
|
|
523
|
+
body = await response.json();
|
|
524
|
+
} catch {
|
|
525
|
+
body = null;
|
|
526
|
+
}
|
|
527
|
+
const said = readServerRefusal(body);
|
|
528
|
+
const code = response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport";
|
|
529
|
+
const error = new MeetError(
|
|
530
|
+
code,
|
|
531
|
+
said?.reason ?? SERVER_FAILED_SENTENCE,
|
|
532
|
+
said !== null && said.remedy.length > 0 ? said.remedy : fallbackRemedy
|
|
533
|
+
);
|
|
534
|
+
error.operation = operation;
|
|
535
|
+
error.status = response.status;
|
|
536
|
+
return error;
|
|
537
|
+
}
|
|
505
538
|
function readServerRefusal(body) {
|
|
506
539
|
if (typeof body === "string") {
|
|
507
540
|
return body.trim().length === 0 ? null : { reason: body.trim(), remedy: "" };
|
|
@@ -520,7 +553,8 @@ function readServerRefusal(body) {
|
|
|
520
553
|
return messages.length === 0 ? null : { reason: messages.join("; "), remedy: "" };
|
|
521
554
|
}
|
|
522
555
|
if (typeof detail === "object" && detail !== null) return readServerRefusal(detail);
|
|
523
|
-
const
|
|
556
|
+
const humanOrNull = (value) => typeof value === "string" && value.trim().length > 0 && !isRawServerText(value) ? value.trim() : null;
|
|
557
|
+
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
558
|
if (reason === null) return null;
|
|
525
559
|
const remedy = typeof row["remedy"] === "string" ? row["remedy"] : "";
|
|
526
560
|
if (remedy.length > 0) return { reason, remedy };
|
|
@@ -1574,6 +1608,7 @@ var EMPTY = {
|
|
|
1574
1608
|
spotlight: null,
|
|
1575
1609
|
moderationNotice: null,
|
|
1576
1610
|
actionNotice: null,
|
|
1611
|
+
privateChatRequest: null,
|
|
1577
1612
|
effects: DEFAULT_EFFECTS,
|
|
1578
1613
|
transcript: [],
|
|
1579
1614
|
notes: [],
|
|
@@ -3763,6 +3798,10 @@ function createRoomEngine(options) {
|
|
|
3763
3798
|
}
|
|
3764
3799
|
},
|
|
3765
3800
|
disconnected(reason) {
|
|
3801
|
+
if (current !== null) {
|
|
3802
|
+
tokens.invalidate(current.roomName);
|
|
3803
|
+
current = null;
|
|
3804
|
+
}
|
|
3766
3805
|
store.patch({ phase: "left" });
|
|
3767
3806
|
options.onLeft?.(reason);
|
|
3768
3807
|
}
|
|
@@ -4065,19 +4104,8 @@ function createInRoomClient(options) {
|
|
|
4065
4104
|
const token = options.resolveRoomToken();
|
|
4066
4105
|
return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
|
|
4067
4106
|
}
|
|
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
|
-
);
|
|
4107
|
+
function refusal2(response, operation, remedy) {
|
|
4108
|
+
return refusalFromResponse(response, operation, remedy);
|
|
4081
4109
|
}
|
|
4082
4110
|
return {
|
|
4083
4111
|
async moderate(request) {
|
|
@@ -6037,195 +6065,596 @@ function useGuestRecordAccess(meeting) {
|
|
|
6037
6065
|
// src/react/components.tsx
|
|
6038
6066
|
import { useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
|
|
6039
6067
|
import { ErrorBox } from "@ai-matrx/design-system";
|
|
6040
|
-
import { avatarPaletteIndex, formatDurationMs as
|
|
6068
|
+
import { avatarPaletteIndex, formatDurationMs as formatDurationMs3, getInitials } from "@ai-matrx/kit/format";
|
|
6041
6069
|
|
|
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
|
-
}
|
|
6070
|
+
// src/core/after.ts
|
|
6071
|
+
import { formatDurationMs as formatDurationMs2 } from "@ai-matrx/kit/format";
|
|
6072
|
+
function kindOf(person) {
|
|
6073
|
+
if (person.isAgent) return "assistant";
|
|
6074
|
+
if (person.role === "host") return "host";
|
|
6075
|
+
if (person.role === "cohost") return "cohost";
|
|
6076
|
+
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
6077
|
+
return "member";
|
|
6074
6078
|
}
|
|
6075
|
-
function
|
|
6076
|
-
|
|
6079
|
+
function ms(iso) {
|
|
6080
|
+
if (iso === null) return null;
|
|
6081
|
+
const at = new Date(iso).getTime();
|
|
6082
|
+
return Number.isNaN(at) ? null : at;
|
|
6077
6083
|
}
|
|
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
|
|
6084
|
+
function attendeeKey(person, names = {}) {
|
|
6085
|
+
if (person.userId !== null) return `user:${person.userId}`;
|
|
6086
|
+
if (person.isAgent) return `agent:${person.identity}`;
|
|
6087
|
+
const name = displayNameFor(names, person.identity, person.displayName).trim().toLowerCase();
|
|
6088
|
+
return name.length > 0 ? `guest-name:${name}` : `identity:${person.identity}`;
|
|
6089
|
+
}
|
|
6090
|
+
function uniqueAttendees(attendees, names = {}) {
|
|
6091
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
6092
|
+
for (const person of attendees) {
|
|
6093
|
+
const key = attendeeKey(person, names);
|
|
6094
|
+
const kept = byKey.get(key);
|
|
6095
|
+
if (kept === void 0) {
|
|
6096
|
+
byKey.set(key, person);
|
|
6097
|
+
continue;
|
|
6097
6098
|
}
|
|
6098
|
-
|
|
6099
|
+
const keptJoin = ms(kept.joinedAt);
|
|
6100
|
+
const join = ms(person.joinedAt);
|
|
6101
|
+
if (join !== null && (keptJoin === null || join < keptJoin)) byKey.set(key, person);
|
|
6102
|
+
}
|
|
6103
|
+
return [...byKey.values()];
|
|
6104
|
+
}
|
|
6105
|
+
var KIND_RANK = {
|
|
6106
|
+
host: 0,
|
|
6107
|
+
cohost: 1,
|
|
6108
|
+
member: 2,
|
|
6109
|
+
"signed-in guest": 3,
|
|
6110
|
+
guest: 4,
|
|
6111
|
+
assistant: 5
|
|
6112
|
+
};
|
|
6113
|
+
function coveredMs(intervals) {
|
|
6114
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
6115
|
+
let total = 0;
|
|
6116
|
+
let from = -Infinity;
|
|
6117
|
+
let to = -Infinity;
|
|
6118
|
+
for (const [start, end] of sorted) {
|
|
6119
|
+
if (start > to) {
|
|
6120
|
+
if (to > from) total += to - from;
|
|
6121
|
+
from = start;
|
|
6122
|
+
to = end;
|
|
6123
|
+
} else if (end > to) {
|
|
6124
|
+
to = end;
|
|
6125
|
+
}
|
|
6126
|
+
}
|
|
6127
|
+
if (to > from) total += to - from;
|
|
6128
|
+
return total;
|
|
6099
6129
|
}
|
|
6100
|
-
function
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6130
|
+
function attendanceReport(meeting, attendees, names = {}) {
|
|
6131
|
+
const end = ms(meeting.endedAt);
|
|
6132
|
+
const perJoin = attendees.map((person) => {
|
|
6133
|
+
const joined = ms(person.joinedAt);
|
|
6134
|
+
const left = ms(person.leftAt);
|
|
6135
|
+
const until = left ?? end;
|
|
6136
|
+
const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
|
|
6137
|
+
return {
|
|
6138
|
+
id: person.id,
|
|
6139
|
+
identity: person.identity,
|
|
6140
|
+
name: displayNameFor(names, person.identity, person.displayName),
|
|
6141
|
+
userId: person.userId,
|
|
6142
|
+
kind: kindOf(person),
|
|
6143
|
+
joinedAt: person.joinedAt,
|
|
6144
|
+
leftAt: person.leftAt,
|
|
6145
|
+
durationMs: duration,
|
|
6146
|
+
durationToEnd: duration !== null && left === null,
|
|
6147
|
+
admission: person.admissionState,
|
|
6148
|
+
joins: 1
|
|
6149
|
+
};
|
|
6150
|
+
});
|
|
6151
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6152
|
+
attendees.forEach((person, index) => {
|
|
6153
|
+
const key = attendeeKey(person, names);
|
|
6154
|
+
const group = groups.get(key) ?? { rows: [], people: [] };
|
|
6155
|
+
group.rows.push(perJoin[index]);
|
|
6156
|
+
group.people.push(person);
|
|
6157
|
+
groups.set(key, group);
|
|
6158
|
+
});
|
|
6159
|
+
const rows = [...groups.values()].map(({ rows: joins }) => {
|
|
6160
|
+
if (joins.length === 1) return joins[0];
|
|
6161
|
+
const came = joins.filter((row) => row.joinedAt !== null);
|
|
6162
|
+
const first = [...came].sort((a, b) => (ms(a.joinedAt) ?? 0) - (ms(b.joinedAt) ?? 0))[0] ?? joins[0];
|
|
6163
|
+
const kind = joins.reduce(
|
|
6164
|
+
(best, row) => KIND_RANK[row.kind] < KIND_RANK[best] ? row.kind : best,
|
|
6165
|
+
first.kind
|
|
6166
|
+
);
|
|
6167
|
+
const open = came.some((row) => row.leftAt === null);
|
|
6168
|
+
const lastLeave = came.reduce((latest, row) => {
|
|
6169
|
+
const left = ms(row.leftAt);
|
|
6170
|
+
return left !== null && (latest === null || left > latest) ? left : latest;
|
|
6171
|
+
}, null);
|
|
6172
|
+
const intervals = came.flatMap((row) => {
|
|
6173
|
+
const joined = ms(row.joinedAt);
|
|
6174
|
+
return joined !== null && row.durationMs !== null ? [[joined, joined + row.durationMs]] : [];
|
|
6175
|
+
});
|
|
6176
|
+
return {
|
|
6177
|
+
...first,
|
|
6178
|
+
kind,
|
|
6179
|
+
leftAt: open ? null : lastLeave === null ? null : new Date(lastLeave).toISOString(),
|
|
6180
|
+
durationMs: intervals.length === 0 ? null : coveredMs(intervals),
|
|
6181
|
+
durationToEnd: came.some((row) => row.durationToEnd),
|
|
6182
|
+
joins: came.length === 0 ? joins.length : came.length
|
|
6183
|
+
};
|
|
6184
|
+
});
|
|
6185
|
+
const rank = KIND_RANK;
|
|
6186
|
+
return rows.sort((a, b) => {
|
|
6187
|
+
if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
|
|
6188
|
+
return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
|
|
6189
|
+
});
|
|
6106
6190
|
}
|
|
6107
|
-
function
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6191
|
+
function attendanceTotals(rows) {
|
|
6192
|
+
const humans = rows.filter((row) => row.kind !== "assistant");
|
|
6193
|
+
const attended = humans.filter((row) => row.joinedAt !== null);
|
|
6194
|
+
const timed = attended.filter((row) => row.durationMs !== null);
|
|
6195
|
+
return {
|
|
6196
|
+
people: attended.length,
|
|
6197
|
+
guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
|
|
6198
|
+
neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
|
|
6199
|
+
averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
|
|
6200
|
+
};
|
|
6115
6201
|
}
|
|
6116
|
-
function
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
] });
|
|
6202
|
+
function csvCell(value) {
|
|
6203
|
+
if (value === null) return "";
|
|
6204
|
+
const text3 = String(value);
|
|
6205
|
+
return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
|
|
6121
6206
|
}
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6207
|
+
var ATTENDANCE_CSV_HEADER = [
|
|
6208
|
+
"Meeting",
|
|
6209
|
+
"Meeting started",
|
|
6210
|
+
"Name",
|
|
6211
|
+
"Attended as",
|
|
6212
|
+
"Joined",
|
|
6213
|
+
"Left",
|
|
6214
|
+
"Minutes in the meeting",
|
|
6215
|
+
"Admission"
|
|
6216
|
+
];
|
|
6217
|
+
function attendanceCsv(reports) {
|
|
6218
|
+
const lines = [ATTENDANCE_CSV_HEADER.join(",")];
|
|
6219
|
+
for (const { meeting, rows } of reports) {
|
|
6220
|
+
for (const row of rows) {
|
|
6221
|
+
lines.push(
|
|
6222
|
+
[
|
|
6223
|
+
meeting.title,
|
|
6224
|
+
meeting.startedAt ?? meeting.scheduledFor,
|
|
6225
|
+
row.name,
|
|
6226
|
+
row.kind,
|
|
6227
|
+
row.joinedAt,
|
|
6228
|
+
row.leftAt,
|
|
6229
|
+
row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
|
|
6230
|
+
row.admission
|
|
6231
|
+
].map(csvCell).join(",")
|
|
6232
|
+
);
|
|
6233
|
+
}
|
|
6234
|
+
}
|
|
6235
|
+
return `${lines.join("\r\n")}\r
|
|
6236
|
+
`;
|
|
6128
6237
|
}
|
|
6129
|
-
function
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6238
|
+
function recordingOffsetMs(at, recording) {
|
|
6239
|
+
if (recording === null || recording.state !== "available") return null;
|
|
6240
|
+
const start = ms(recording.startedAt);
|
|
6241
|
+
const moment = ms(at);
|
|
6242
|
+
if (start === null || moment === null || moment < start) return null;
|
|
6243
|
+
const end = ms(recording.endedAt);
|
|
6244
|
+
if (end !== null && moment > end) return null;
|
|
6245
|
+
return moment - start;
|
|
6135
6246
|
}
|
|
6136
|
-
function
|
|
6137
|
-
|
|
6247
|
+
function clockPosition(offsetMs) {
|
|
6248
|
+
const total = Math.max(0, Math.floor(offsetMs / 1e3));
|
|
6249
|
+
const h = Math.floor(total / 3600);
|
|
6250
|
+
const m = Math.floor(total % 3600 / 60);
|
|
6251
|
+
const s = total % 60;
|
|
6252
|
+
const ss = String(s).padStart(2, "0");
|
|
6253
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
6138
6254
|
}
|
|
6139
|
-
function
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6255
|
+
function searchTranscript(segments, query, names = {}) {
|
|
6256
|
+
const needle = fold(query.trim());
|
|
6257
|
+
if (needle.length === 0) return [];
|
|
6258
|
+
const out = [];
|
|
6259
|
+
for (const segment of segments) {
|
|
6260
|
+
const hay = fold(segment.text);
|
|
6261
|
+
const ranges = [];
|
|
6262
|
+
let from = 0;
|
|
6263
|
+
for (; ; ) {
|
|
6264
|
+
const at = hay.indexOf(needle, from);
|
|
6265
|
+
if (at < 0) break;
|
|
6266
|
+
ranges.push([at, at + needle.length]);
|
|
6267
|
+
from = at + needle.length;
|
|
6268
|
+
}
|
|
6269
|
+
const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
|
|
6270
|
+
if (ranges.length > 0 || speaker.includes(needle)) {
|
|
6271
|
+
out.push({ segmentId: segment.id, ranges });
|
|
6272
|
+
}
|
|
6273
|
+
}
|
|
6274
|
+
return out;
|
|
6145
6275
|
}
|
|
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
|
-
] });
|
|
6276
|
+
function fold(value) {
|
|
6277
|
+
return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
|
|
6153
6278
|
}
|
|
6154
|
-
function
|
|
6155
|
-
|
|
6279
|
+
function clockOfDay(iso, options) {
|
|
6280
|
+
const at = new Date(iso);
|
|
6281
|
+
if (Number.isNaN(at.getTime())) return "";
|
|
6282
|
+
return at.toLocaleTimeString(options.locale, {
|
|
6283
|
+
hour: "numeric",
|
|
6284
|
+
minute: "2-digit",
|
|
6285
|
+
second: "2-digit",
|
|
6286
|
+
...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
|
|
6287
|
+
});
|
|
6156
6288
|
}
|
|
6157
|
-
function
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6289
|
+
function vttStamp(offsetMs) {
|
|
6290
|
+
const total = Math.max(0, Math.round(offsetMs));
|
|
6291
|
+
const h = Math.floor(total / 36e5);
|
|
6292
|
+
const m = Math.floor(total % 36e5 / 6e4);
|
|
6293
|
+
const s = Math.floor(total % 6e4 / 1e3);
|
|
6294
|
+
const milli = total % 1e3;
|
|
6295
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
|
|
6163
6296
|
}
|
|
6164
|
-
function
|
|
6165
|
-
|
|
6297
|
+
function transcriptDocument(bundle, format, options = {}) {
|
|
6298
|
+
const names = options.names ?? bundle.names;
|
|
6299
|
+
const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
|
|
6300
|
+
const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
|
|
6301
|
+
if (format === "vtt") {
|
|
6302
|
+
const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
|
|
6303
|
+
const zero = ms(zeroIso) ?? 0;
|
|
6304
|
+
const cues = ["WEBVTT", ""];
|
|
6305
|
+
lines.forEach((line, index) => {
|
|
6306
|
+
const start = (ms(line.startedAt) ?? zero) - zero;
|
|
6307
|
+
if (start < 0) return;
|
|
6308
|
+
const next = lines[index + 1];
|
|
6309
|
+
const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
|
|
6310
|
+
const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
|
|
6311
|
+
cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
|
|
6312
|
+
cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
|
|
6313
|
+
cues.push("");
|
|
6314
|
+
});
|
|
6315
|
+
return cues.join("\n");
|
|
6316
|
+
}
|
|
6317
|
+
const out = [];
|
|
6318
|
+
const title = bundle.meeting.title;
|
|
6319
|
+
out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
|
|
6320
|
+
out.push("");
|
|
6321
|
+
let last = null;
|
|
6322
|
+
for (const line of lines) {
|
|
6323
|
+
if (line.identity !== last) {
|
|
6324
|
+
if (last !== null) out.push("");
|
|
6325
|
+
const who = speaker(line);
|
|
6326
|
+
const clock = clockOfDay(line.startedAt, options);
|
|
6327
|
+
out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
|
|
6328
|
+
last = line.identity;
|
|
6329
|
+
}
|
|
6330
|
+
out.push(line.text.trim());
|
|
6331
|
+
}
|
|
6332
|
+
if (lines.length === 0) out.push("No transcript was captured for this meeting.");
|
|
6333
|
+
return `${out.join("\n")}
|
|
6334
|
+
`;
|
|
6166
6335
|
}
|
|
6167
|
-
function
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
/* @__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" })
|
|
6171
|
-
] });
|
|
6336
|
+
function bullet(note) {
|
|
6337
|
+
const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
|
|
6338
|
+
return `- ${note.text.trim()}${owner}`;
|
|
6172
6339
|
}
|
|
6173
|
-
function
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6340
|
+
function notesDocument(bundle, options = {}) {
|
|
6341
|
+
const out = [`# ${bundle.meeting.title}`, ""];
|
|
6342
|
+
if (options.when) out.push(`_${options.when}_`, "");
|
|
6343
|
+
const people = uniqueAttendees(
|
|
6344
|
+
bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null),
|
|
6345
|
+
bundle.names
|
|
6346
|
+
).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
|
|
6347
|
+
if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
|
|
6348
|
+
out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
|
|
6349
|
+
out.push("## Decisions", "");
|
|
6350
|
+
out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
|
|
6351
|
+
out.push("## Action items", "");
|
|
6352
|
+
out.push(
|
|
6353
|
+
...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
|
|
6354
|
+
""
|
|
6355
|
+
);
|
|
6356
|
+
if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
|
|
6357
|
+
return out.join("\n");
|
|
6178
6358
|
}
|
|
6179
|
-
function
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6359
|
+
function meetingLength(meeting) {
|
|
6360
|
+
const start = ms(meeting.startedAt);
|
|
6361
|
+
const end = ms(meeting.endedAt);
|
|
6362
|
+
if (start === null || end === null || end < start) return null;
|
|
6363
|
+
return formatDurationMs2(end - start, { style: "long" });
|
|
6184
6364
|
}
|
|
6185
|
-
function
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6365
|
+
async function loadMeetingChat(api, meetingId, roomToken = null) {
|
|
6366
|
+
const operation = "meet.loadMeetingChat";
|
|
6367
|
+
let response;
|
|
6368
|
+
try {
|
|
6369
|
+
response = await api.request({
|
|
6370
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
6371
|
+
method: "GET",
|
|
6372
|
+
operation,
|
|
6373
|
+
requireAuth: roomToken === null,
|
|
6374
|
+
...roomToken !== null ? { headers: { [ROOM_TOKEN_HEADER]: roomToken } } : {}
|
|
6375
|
+
});
|
|
6376
|
+
} catch (error) {
|
|
6377
|
+
return {
|
|
6378
|
+
messages: [],
|
|
6379
|
+
refusal: {
|
|
6380
|
+
message: `The chat could not be read (${error.message}).`,
|
|
6381
|
+
remedy: error.remedy ?? "Reload the page."
|
|
6382
|
+
}
|
|
6383
|
+
};
|
|
6384
|
+
}
|
|
6385
|
+
if (!response.ok) {
|
|
6386
|
+
let body = null;
|
|
6387
|
+
try {
|
|
6388
|
+
body = await response.json();
|
|
6389
|
+
} catch {
|
|
6390
|
+
body = null;
|
|
6391
|
+
}
|
|
6392
|
+
const said = readServerRefusal(body);
|
|
6393
|
+
return {
|
|
6394
|
+
messages: [],
|
|
6395
|
+
refusal: {
|
|
6396
|
+
message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
|
|
6397
|
+
remedy: said?.remedy || "Reload the page."
|
|
6398
|
+
}
|
|
6399
|
+
};
|
|
6400
|
+
}
|
|
6401
|
+
const payload = await response.json();
|
|
6402
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
6403
|
+
return {
|
|
6404
|
+
messages: rows.map(
|
|
6405
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
6406
|
+
).filter((message) => message !== null),
|
|
6407
|
+
refusal: null
|
|
6408
|
+
};
|
|
6190
6409
|
}
|
|
6191
|
-
function
|
|
6192
|
-
return
|
|
6410
|
+
function refusalOf(response, operation) {
|
|
6411
|
+
return refusalFromResponse(response, operation, "Nothing was sent. Try again.");
|
|
6193
6412
|
}
|
|
6194
|
-
function
|
|
6195
|
-
|
|
6413
|
+
function asRecipient(row) {
|
|
6414
|
+
const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
|
|
6415
|
+
return {
|
|
6416
|
+
key: String(row["key"] ?? ""),
|
|
6417
|
+
name: String(row["name"] ?? ""),
|
|
6418
|
+
email: typeof row["email"] === "string" ? row["email"] : null,
|
|
6419
|
+
userId: typeof row["user_id"] === "string" ? row["user_id"] : null,
|
|
6420
|
+
sources: sources.filter((s) => s === "invitee" || s === "attendee"),
|
|
6421
|
+
reachable: row["reachable"] !== false
|
|
6422
|
+
};
|
|
6196
6423
|
}
|
|
6197
|
-
function
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6424
|
+
async function loadFollowUpDraft(api, meetingId) {
|
|
6425
|
+
const operation = "meet.loadFollowUpDraft";
|
|
6426
|
+
const response = await api.request({
|
|
6427
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
6428
|
+
method: "GET",
|
|
6429
|
+
requireAuth: true,
|
|
6430
|
+
operation
|
|
6431
|
+
});
|
|
6432
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
6433
|
+
const body = await response.json();
|
|
6434
|
+
const last = body["last_sent"];
|
|
6435
|
+
return {
|
|
6436
|
+
subject: String(body["subject"] ?? ""),
|
|
6437
|
+
body: String(body["body"] ?? ""),
|
|
6438
|
+
recipients: (Array.isArray(body["recipients"]) ? body["recipients"] : []).map(
|
|
6439
|
+
(row) => asRecipient(row)
|
|
6440
|
+
),
|
|
6441
|
+
wrapUpWritten: body["wrap_up_written"] === true,
|
|
6442
|
+
lastSent: last && typeof last["at"] === "string" ? { at: last["at"], count: typeof last["count"] === "number" ? last["count"] : 0 } : null
|
|
6443
|
+
};
|
|
6202
6444
|
}
|
|
6203
|
-
function
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6445
|
+
async function sendFollowUp(api, meetingId, input) {
|
|
6446
|
+
const operation = "meet.sendFollowUp";
|
|
6447
|
+
const response = await api.request({
|
|
6448
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
6449
|
+
method: "POST",
|
|
6450
|
+
requireAuth: true,
|
|
6451
|
+
operation,
|
|
6452
|
+
body: {
|
|
6453
|
+
subject: input.subject,
|
|
6454
|
+
body: input.body,
|
|
6455
|
+
recipient_keys: [...input.recipientKeys],
|
|
6456
|
+
dry_run: input.dryRun === true
|
|
6457
|
+
}
|
|
6458
|
+
});
|
|
6459
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
6460
|
+
const body = await response.json();
|
|
6461
|
+
return {
|
|
6462
|
+
dryRun: body["dry_run"] === true,
|
|
6463
|
+
queued: Array.isArray(body["queued"]) ? body["queued"] : [],
|
|
6464
|
+
skipped: Array.isArray(body["skipped"]) ? body["skipped"].map((row) => ({
|
|
6465
|
+
key: String(row["key"] ?? ""),
|
|
6466
|
+
reason: String(row["reason"] ?? "")
|
|
6467
|
+
})) : []
|
|
6468
|
+
};
|
|
6208
6469
|
}
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
] }
|
|
6470
|
+
|
|
6471
|
+
// src/core/format.ts
|
|
6472
|
+
function participantSummary(participants) {
|
|
6473
|
+
const names = participants.filter((p) => !p.isLocal).map((p) => p.displayName);
|
|
6474
|
+
if (names.length === 0) return "Just you";
|
|
6475
|
+
if (names.length === 1) return names[0] ?? "";
|
|
6476
|
+
if (names.length === 2) return `${names[0]} and ${names[1]}`;
|
|
6477
|
+
return `${names[0]}, ${names[1]} and ${names.length - 2} ${names.length - 2 === 1 ? "other" : "others"}`;
|
|
6216
6478
|
}
|
|
6217
|
-
function
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6479
|
+
function callStateLabel(state, outgoing) {
|
|
6480
|
+
switch (state) {
|
|
6481
|
+
case "dialing":
|
|
6482
|
+
case "ringing":
|
|
6483
|
+
return outgoing ? "Calling\u2026" : "Incoming call";
|
|
6484
|
+
case "connecting":
|
|
6485
|
+
return "Connecting\u2026";
|
|
6486
|
+
case "active":
|
|
6487
|
+
return "In call";
|
|
6488
|
+
case "declined":
|
|
6489
|
+
return outgoing ? "Declined" : "You declined";
|
|
6490
|
+
case "missed":
|
|
6491
|
+
return outgoing ? "No answer" : "Missed call";
|
|
6492
|
+
case "busy":
|
|
6493
|
+
return outgoing ? "On another call" : "You were busy";
|
|
6494
|
+
case "cancelled":
|
|
6495
|
+
return outgoing ? "Cancelled" : "Cancelled by caller";
|
|
6496
|
+
case "ended":
|
|
6497
|
+
return "Call ended";
|
|
6498
|
+
case "failed":
|
|
6499
|
+
return "Call failed";
|
|
6500
|
+
default:
|
|
6501
|
+
return "Call";
|
|
6502
|
+
}
|
|
6223
6503
|
}
|
|
6224
|
-
function
|
|
6225
|
-
return
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6504
|
+
function countLabel(count2, singular, plural = `${singular}s`) {
|
|
6505
|
+
return `${count2} ${count2 === 1 ? singular : plural}`;
|
|
6506
|
+
}
|
|
6507
|
+
|
|
6508
|
+
// src/react/icons.tsx
|
|
6509
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
6510
|
+
function Icon({ children, ...props }) {
|
|
6511
|
+
return /* @__PURE__ */ jsx2(
|
|
6512
|
+
"svg",
|
|
6513
|
+
{
|
|
6514
|
+
width: "20",
|
|
6515
|
+
height: "20",
|
|
6516
|
+
viewBox: "0 0 24 24",
|
|
6517
|
+
fill: "none",
|
|
6518
|
+
stroke: "currentColor",
|
|
6519
|
+
strokeWidth: "1.75",
|
|
6520
|
+
strokeLinecap: "round",
|
|
6521
|
+
strokeLinejoin: "round",
|
|
6522
|
+
"aria-hidden": "true",
|
|
6523
|
+
focusable: "false",
|
|
6524
|
+
...props,
|
|
6525
|
+
children
|
|
6526
|
+
}
|
|
6527
|
+
);
|
|
6528
|
+
}
|
|
6529
|
+
function MicIcon(props) {
|
|
6530
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6531
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }),
|
|
6532
|
+
/* @__PURE__ */ jsx2("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
|
|
6533
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 19v3" })
|
|
6534
|
+
] });
|
|
6535
|
+
}
|
|
6536
|
+
function MicOffIcon(props) {
|
|
6537
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6538
|
+
/* @__PURE__ */ jsx2("path", { d: "M2 2l20 20" }),
|
|
6539
|
+
/* @__PURE__ */ jsx2("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12" }),
|
|
6540
|
+
/* @__PURE__ */ jsx2("path", { d: "M15 9.34V5a3 3 0 0 0-5.94-.6" }),
|
|
6541
|
+
/* @__PURE__ */ jsx2("path", { d: "M19 10v2a7 7 0 0 1-.11 1.23M5 10v2a7 7 0 0 0 11 5.66" }),
|
|
6542
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 19v3" })
|
|
6543
|
+
] });
|
|
6544
|
+
}
|
|
6545
|
+
function CameraIcon(props) {
|
|
6546
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6547
|
+
/* @__PURE__ */ jsx2("path", { d: "m23 7-7 5 7 5V7Z" }),
|
|
6548
|
+
/* @__PURE__ */ jsx2("rect", { x: "1", y: "5", width: "15", height: "14", rx: "2" })
|
|
6549
|
+
] });
|
|
6550
|
+
}
|
|
6551
|
+
function CameraOffIcon(props) {
|
|
6552
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6553
|
+
/* @__PURE__ */ jsx2("path", { d: "M2 2l20 20" }),
|
|
6554
|
+
/* @__PURE__ */ jsx2("path", { d: "M10.66 5H14a2 2 0 0 1 2 2v3.34l1 1L23 7v10" }),
|
|
6555
|
+
/* @__PURE__ */ jsx2("path", { d: "M16 16a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2" })
|
|
6556
|
+
] });
|
|
6557
|
+
}
|
|
6558
|
+
function ScreenShareIcon(props) {
|
|
6559
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6560
|
+
/* @__PURE__ */ jsx2("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
|
|
6561
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 21h8M12 17v4" }),
|
|
6562
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 13V8m0 0-2.5 2.5M12 8l2.5 2.5" })
|
|
6563
|
+
] });
|
|
6564
|
+
}
|
|
6565
|
+
function PhoneIcon(props) {
|
|
6566
|
+
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" }) });
|
|
6567
|
+
}
|
|
6568
|
+
function PhoneOffIcon(props) {
|
|
6569
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6570
|
+
/* @__PURE__ */ jsx2("path", { d: "M2 2l20 20" }),
|
|
6571
|
+
/* @__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" }),
|
|
6572
|
+
/* @__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" })
|
|
6573
|
+
] });
|
|
6574
|
+
}
|
|
6575
|
+
function HandIcon(props) {
|
|
6576
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6577
|
+
/* @__PURE__ */ jsx2("path", { d: "M18 11V6a2 2 0 0 0-4 0v5" }),
|
|
6578
|
+
/* @__PURE__ */ jsx2("path", { d: "M14 10V4a2 2 0 0 0-4 0v7" }),
|
|
6579
|
+
/* @__PURE__ */ jsx2("path", { d: "M10 10.5V6a2 2 0 0 0-4 0v8" }),
|
|
6580
|
+
/* @__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" })
|
|
6581
|
+
] });
|
|
6582
|
+
}
|
|
6583
|
+
function ChatIcon(props) {
|
|
6584
|
+
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" }) });
|
|
6585
|
+
}
|
|
6586
|
+
function PeopleIcon(props) {
|
|
6587
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6588
|
+
/* @__PURE__ */ jsx2("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
|
|
6589
|
+
/* @__PURE__ */ jsx2("circle", { cx: "9", cy: "7", r: "4" }),
|
|
6590
|
+
/* @__PURE__ */ jsx2("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" })
|
|
6591
|
+
] });
|
|
6592
|
+
}
|
|
6593
|
+
function RecordIcon(props) {
|
|
6594
|
+
return /* @__PURE__ */ jsx2(Icon, { ...props, children: /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "8", fill: "currentColor", stroke: "none" }) });
|
|
6595
|
+
}
|
|
6596
|
+
function SparklesIcon(props) {
|
|
6597
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6598
|
+
/* @__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" }),
|
|
6599
|
+
/* @__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" })
|
|
6600
|
+
] });
|
|
6601
|
+
}
|
|
6602
|
+
function LockIcon(props) {
|
|
6603
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6604
|
+
/* @__PURE__ */ jsx2("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2" }),
|
|
6605
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 10V7a4 4 0 0 1 8 0v3" })
|
|
6606
|
+
] });
|
|
6607
|
+
}
|
|
6608
|
+
function LayoutIcon(props) {
|
|
6609
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6610
|
+
/* @__PURE__ */ jsx2("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
|
|
6611
|
+
/* @__PURE__ */ jsx2("path", { d: "M3 9h18M9 21V9" })
|
|
6612
|
+
] });
|
|
6613
|
+
}
|
|
6614
|
+
function SettingsIcon(props) {
|
|
6615
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6616
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "3" }),
|
|
6617
|
+
/* @__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" })
|
|
6618
|
+
] });
|
|
6619
|
+
}
|
|
6620
|
+
function CheckIcon(props) {
|
|
6621
|
+
return /* @__PURE__ */ jsx2(Icon, { ...props, children: /* @__PURE__ */ jsx2("path", { d: "M20 6 9 17l-5-5" }) });
|
|
6622
|
+
}
|
|
6623
|
+
function CloseIcon(props) {
|
|
6624
|
+
return /* @__PURE__ */ jsx2(Icon, { ...props, children: /* @__PURE__ */ jsx2("path", { d: "M18 6 6 18M6 6l12 12" }) });
|
|
6625
|
+
}
|
|
6626
|
+
function PinIcon(props) {
|
|
6627
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6628
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 17v5" }),
|
|
6629
|
+
/* @__PURE__ */ jsx2("path", { d: "M9 2h6l-1 6 3.5 3.5V14H6.5v-2.5L10 8 9 2Z" })
|
|
6630
|
+
] });
|
|
6631
|
+
}
|
|
6632
|
+
function WifiLowIcon(props) {
|
|
6633
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6634
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 20h.01" }),
|
|
6635
|
+
/* @__PURE__ */ jsx2("path", { d: "M8.5 16.5a5 5 0 0 1 7 0" })
|
|
6636
|
+
] });
|
|
6637
|
+
}
|
|
6638
|
+
function SmileIcon(props) {
|
|
6639
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6640
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "10" }),
|
|
6641
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
|
|
6642
|
+
/* @__PURE__ */ jsx2("line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }),
|
|
6643
|
+
/* @__PURE__ */ jsx2("line", { x1: "15", x2: "15.01", y1: "9", y2: "9" })
|
|
6644
|
+
] });
|
|
6645
|
+
}
|
|
6646
|
+
function MoreIcon(props) {
|
|
6647
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6648
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "1" }),
|
|
6649
|
+
/* @__PURE__ */ jsx2("circle", { cx: "19", cy: "12", r: "1" }),
|
|
6650
|
+
/* @__PURE__ */ jsx2("circle", { cx: "5", cy: "12", r: "1" })
|
|
6651
|
+
] });
|
|
6652
|
+
}
|
|
6653
|
+
function SearchIcon(props) {
|
|
6654
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6655
|
+
/* @__PURE__ */ jsx2("circle", { cx: "11", cy: "11", r: "8" }),
|
|
6656
|
+
/* @__PURE__ */ jsx2("path", { d: "m21 21-4.3-4.3" })
|
|
6657
|
+
] });
|
|
6229
6658
|
}
|
|
6230
6659
|
function KeyboardIcon(props) {
|
|
6231
6660
|
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
@@ -6487,6 +6916,15 @@ function ControlBar({ onLeave }) {
|
|
|
6487
6916
|
const micUnavailable = useLocalMedia().microphone.unavailable;
|
|
6488
6917
|
const unread = useUnreadChat(panel === "chat");
|
|
6489
6918
|
const toggle = (next) => setPanel(panel === next ? "none" : next);
|
|
6919
|
+
const privateChatAt = snapshot?.privateChatRequest?.at ?? null;
|
|
6920
|
+
const meetStore = useMeetHost()?.store ?? null;
|
|
6921
|
+
useEffect3(() => {
|
|
6922
|
+
const request = meetStore?.snapshot().privateChatRequest ?? null;
|
|
6923
|
+
if (request === null) return;
|
|
6924
|
+
setChatTo(request.to);
|
|
6925
|
+
setPanel("chat");
|
|
6926
|
+
meetStore?.patch({ privateChatRequest: null });
|
|
6927
|
+
}, [privateChatAt, meetStore]);
|
|
6490
6928
|
useMeetShortcuts({
|
|
6491
6929
|
mic: () => void room.toggleMicrophone(),
|
|
6492
6930
|
camera: () => void room.toggleCamera(),
|
|
@@ -6726,7 +7164,12 @@ function Popover({
|
|
|
6726
7164
|
placement = "up"
|
|
6727
7165
|
}) {
|
|
6728
7166
|
const ref = useRef3(null);
|
|
6729
|
-
const [style, setStyle] = useState3({
|
|
7167
|
+
const [style, setStyle] = useState3({
|
|
7168
|
+
position: "fixed",
|
|
7169
|
+
top: 0,
|
|
7170
|
+
left: 0,
|
|
7171
|
+
visibility: "hidden"
|
|
7172
|
+
});
|
|
6730
7173
|
useLayoutEffect(() => {
|
|
6731
7174
|
const element = ref.current;
|
|
6732
7175
|
const anchor = element?.parentElement ?? null;
|
|
@@ -6742,14 +7185,35 @@ function Popover({
|
|
|
6742
7185
|
);
|
|
6743
7186
|
const fitsBelow = rect.bottom + gutter + height <= window.innerHeight;
|
|
6744
7187
|
const fitsAbove = rect.top - gutter - height >= 0;
|
|
7188
|
+
if (!fitsBelow && !fitsAbove) {
|
|
7189
|
+
const top = Math.max(gutter, window.innerHeight - height - gutter);
|
|
7190
|
+
setStyle({
|
|
7191
|
+
position: "fixed",
|
|
7192
|
+
left,
|
|
7193
|
+
top,
|
|
7194
|
+
maxHeight: window.innerHeight - top - gutter,
|
|
7195
|
+
overflowY: "auto"
|
|
7196
|
+
});
|
|
7197
|
+
return;
|
|
7198
|
+
}
|
|
6745
7199
|
const down = placement === "down" ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;
|
|
6746
7200
|
setStyle(
|
|
6747
7201
|
down ? { position: "fixed", left, top: rect.bottom + gutter } : { position: "fixed", left, bottom: window.innerHeight - rect.top + gutter }
|
|
6748
7202
|
);
|
|
6749
7203
|
};
|
|
6750
7204
|
place();
|
|
7205
|
+
const frame = requestAnimationFrame(place);
|
|
7206
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => place());
|
|
7207
|
+
observer?.observe(document.body);
|
|
7208
|
+
observer?.observe(element);
|
|
6751
7209
|
window.addEventListener("resize", place);
|
|
6752
|
-
|
|
7210
|
+
window.addEventListener("scroll", place, true);
|
|
7211
|
+
return () => {
|
|
7212
|
+
cancelAnimationFrame(frame);
|
|
7213
|
+
observer?.disconnect();
|
|
7214
|
+
window.removeEventListener("resize", place);
|
|
7215
|
+
window.removeEventListener("scroll", place, true);
|
|
7216
|
+
};
|
|
6753
7217
|
}, [placement]);
|
|
6754
7218
|
useEffect3(() => {
|
|
6755
7219
|
const onKey = (event) => {
|
|
@@ -7126,11 +7590,15 @@ function PersonMenu({
|
|
|
7126
7590
|
const pinned = snapshot?.pinned === participant.identity;
|
|
7127
7591
|
const spotlit = snapshot?.spotlight === participant.identity;
|
|
7128
7592
|
const hasAccount = participant.userId !== null;
|
|
7593
|
+
const [refused, setRefused] = useState3(false);
|
|
7129
7594
|
const act = (request) => {
|
|
7595
|
+
setRefused(false);
|
|
7130
7596
|
void controls.run(request).then((result) => {
|
|
7131
7597
|
if (result !== null) onClose();
|
|
7598
|
+
else setRefused(true);
|
|
7132
7599
|
});
|
|
7133
7600
|
};
|
|
7601
|
+
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
7602
|
if (mode === "rename" || mode === "report") {
|
|
7135
7603
|
const renaming = mode === "rename";
|
|
7136
7604
|
return /* @__PURE__ */ jsx3(Popover, { label: renaming ? "Rename" : "Report", onClose, placement, children: /* @__PURE__ */ jsxs2(
|
|
@@ -7168,7 +7636,7 @@ function PersonMenu({
|
|
|
7168
7636
|
}
|
|
7169
7637
|
)
|
|
7170
7638
|
] }),
|
|
7171
|
-
|
|
7639
|
+
refusal2,
|
|
7172
7640
|
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-actions", children: [
|
|
7173
7641
|
/* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Back" }),
|
|
7174
7642
|
/* @__PURE__ */ jsx3("button", { type: "submit", className: "mx-meet__send", disabled: text3.trim().length === 0 || controls.busy, children: renaming ? "Rename" : "Report" })
|
|
@@ -7181,6 +7649,7 @@ function PersonMenu({
|
|
|
7181
7649
|
const removing = mode === "remove";
|
|
7182
7650
|
return /* @__PURE__ */ jsx3(Popover, { label: removing ? "Remove" : "Make host", onClose, placement, children: /* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-form", children: [
|
|
7183
7651
|
/* @__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.` }),
|
|
7652
|
+
refusal2,
|
|
7184
7653
|
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-actions", children: [
|
|
7185
7654
|
/* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Cancel" }),
|
|
7186
7655
|
/* @__PURE__ */ jsx3(
|
|
@@ -7211,11 +7680,15 @@ function PersonMenu({
|
|
|
7211
7680
|
]
|
|
7212
7681
|
}
|
|
7213
7682
|
),
|
|
7214
|
-
!self && !participant.isAgent && onMessage !== void 0 ? /* @__PURE__ */ jsxs2(
|
|
7683
|
+
!self && !participant.isAgent && (onMessage !== void 0 || host !== null) ? /* @__PURE__ */ jsxs2(
|
|
7215
7684
|
MenuItem,
|
|
7216
7685
|
{
|
|
7217
7686
|
onSelect: () => {
|
|
7218
|
-
onMessage(participant.identity);
|
|
7687
|
+
if (onMessage !== void 0) onMessage(participant.identity);
|
|
7688
|
+
else
|
|
7689
|
+
host?.store.patch({
|
|
7690
|
+
privateChatRequest: { to: participant.identity, at: Date.now() }
|
|
7691
|
+
});
|
|
7219
7692
|
onClose();
|
|
7220
7693
|
},
|
|
7221
7694
|
children: [
|
|
@@ -7842,7 +8315,7 @@ function PreJoinBody({
|
|
|
7842
8315
|
microphone,
|
|
7843
8316
|
...meetingId !== void 0 ? { meetingId } : {},
|
|
7844
8317
|
...slug !== void 0 ? { slug } : {}
|
|
7845
|
-
}).then(() => onJoined?.()).finally(() => setJoining(false));
|
|
8318
|
+
}).then(() => onJoined?.()).catch(() => void 0).finally(() => setJoining(false));
|
|
7846
8319
|
},
|
|
7847
8320
|
children: joining ? "Joining\u2026" : "Join now"
|
|
7848
8321
|
}
|
|
@@ -7978,7 +8451,7 @@ function MeetingRoomBody({
|
|
|
7978
8451
|
/* @__PURE__ */ jsx3("h1", { className: "mx-meet__title", children: snapshot?.meeting?.title ?? "Meeting" }),
|
|
7979
8452
|
/* @__PURE__ */ jsxs2("span", { className: "mx-meet__meta", children: [
|
|
7980
8453
|
participantSummary(snapshot?.participants ?? []),
|
|
7981
|
-
elapsed > 0 ? ` \xB7 ${
|
|
8454
|
+
elapsed > 0 ? ` \xB7 ${formatDurationMs3(elapsed)}` : ""
|
|
7982
8455
|
] }),
|
|
7983
8456
|
/* @__PURE__ */ jsx3(RecordingIndicator, {}),
|
|
7984
8457
|
snapshot?.locked === true ? /* @__PURE__ */ jsxs2("span", { className: "mx-meet__badge", children: [
|
|
@@ -8284,7 +8757,10 @@ function MeetingRecordBody({
|
|
|
8284
8757
|
const blocks = groupTranscript(bundle.transcript, bundle.names);
|
|
8285
8758
|
const notice = recordingNotice(bundle.recording);
|
|
8286
8759
|
const fileId = playableRecordingFileId(bundle.recording);
|
|
8287
|
-
const attended =
|
|
8760
|
+
const attended = uniqueAttendees(
|
|
8761
|
+
bundle.attendees.filter((person) => person.admissionState !== "denied"),
|
|
8762
|
+
bundle.names
|
|
8763
|
+
);
|
|
8288
8764
|
const restrictionNotices = summarizeMeetingRecordRestrictions(bundle.restrictions);
|
|
8289
8765
|
const guestFinishedMeeting = host.identity.userId === null && meeting.endedAt !== null;
|
|
8290
8766
|
const everyRecordPartRefused = ["attendance", "notes", "transcript", "recording"].every(
|
|
@@ -8593,514 +9069,172 @@ function RecordingPlayerBody({ fileId, host }) {
|
|
|
8593
9069
|
const failure = thrown;
|
|
8594
9070
|
setError({
|
|
8595
9071
|
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");
|
|
9072
|
+
remedy: failure.remedy ?? PLAYBACK_RETRY_REMEDY
|
|
9073
|
+
});
|
|
9074
|
+
} finally {
|
|
9075
|
+
setLoading(false);
|
|
9076
|
+
}
|
|
9077
|
+
})();
|
|
9078
|
+
},
|
|
9079
|
+
children: loading ? "Loading the recording\u2026" : "Play the recording"
|
|
9080
|
+
}
|
|
9081
|
+
)
|
|
9082
|
+
] });
|
|
8967
9083
|
}
|
|
8968
|
-
function
|
|
8969
|
-
const
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
return formatDurationMs3(end - start, { style: "long" });
|
|
9084
|
+
function clockOf(iso) {
|
|
9085
|
+
const at = new Date(iso);
|
|
9086
|
+
if (Number.isNaN(at.getTime())) return "";
|
|
9087
|
+
return at.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
|
|
8973
9088
|
}
|
|
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
|
-
};
|
|
9089
|
+
function MeetingList({ baseUrl }) {
|
|
9090
|
+
const host = useMeetHost();
|
|
9091
|
+
const list = useMeetings();
|
|
9092
|
+
if (host === null) return /* @__PURE__ */ jsx3(InertNotice, { what: "Your meetings" });
|
|
9093
|
+
if (list.failure !== null) {
|
|
9094
|
+
return /* @__PURE__ */ jsxs2(ErrorBox, { as: "div", className: "mx-meet__empty", error: list.failure, operation: "List your meetings", origin: "meet/components", children: [
|
|
9095
|
+
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-title", children: "Your meetings could not be listed" }),
|
|
9096
|
+
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-body", children: list.failure.message }),
|
|
9097
|
+
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-body", children: list.failure.remedy })
|
|
9098
|
+
] });
|
|
8993
9099
|
}
|
|
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
|
-
};
|
|
9100
|
+
if (list.loading) {
|
|
9101
|
+
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
9102
|
}
|
|
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;
|
|
9103
|
+
if (list.meetings.length === 0) {
|
|
9104
|
+
return /* @__PURE__ */ jsxs2("div", { className: "mx-meet__empty", role: "status", children: [
|
|
9105
|
+
/* @__PURE__ */ jsx3("p", { className: "mx-meet__empty-title", children: "No meetings yet" }),
|
|
9106
|
+
/* @__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." })
|
|
9107
|
+
] });
|
|
9025
9108
|
}
|
|
9026
|
-
const
|
|
9027
|
-
return
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9109
|
+
const origin = baseUrl ?? (typeof window === "undefined" ? "" : window.location.origin);
|
|
9110
|
+
return /* @__PURE__ */ jsx3("ul", { className: "mx-meet-list", children: list.meetings.map((meeting) => {
|
|
9111
|
+
const ended = meeting.endedAt !== null;
|
|
9112
|
+
return /* @__PURE__ */ jsxs2("li", { className: "mx-meet-list__row", children: [
|
|
9113
|
+
/* @__PURE__ */ jsxs2("div", { className: "mx-meet-list__main", children: [
|
|
9114
|
+
/* @__PURE__ */ jsxs2("span", { className: "mx-meet-list__title", children: [
|
|
9115
|
+
meeting.title,
|
|
9116
|
+
ended ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__badge", children: "Ended" }) : null
|
|
9117
|
+
] }),
|
|
9118
|
+
/* @__PURE__ */ jsx3("span", { className: "mx-meet-list__when", children: meetingWhen(meeting) })
|
|
9119
|
+
] }),
|
|
9120
|
+
/* @__PURE__ */ jsx3("a", { className: "mx-meet-list__action", href: meetingLink(origin, meeting.slug), children: ended ? "Open the record" : "Join" })
|
|
9121
|
+
] }, meeting.id);
|
|
9122
|
+
}) });
|
|
9032
9123
|
}
|
|
9033
|
-
|
|
9034
|
-
|
|
9124
|
+
|
|
9125
|
+
// src/core/invitations.ts
|
|
9126
|
+
var CALL_INVITE_KIND = "meet.call-invite";
|
|
9127
|
+
var CALL_INVITE_VERSION = 1;
|
|
9128
|
+
function callInviteAction(invite) {
|
|
9035
9129
|
return {
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9130
|
+
kind: CALL_INVITE_KIND,
|
|
9131
|
+
version: CALL_INVITE_VERSION,
|
|
9132
|
+
payload: {
|
|
9133
|
+
invite_id: invite.id,
|
|
9134
|
+
room_name: invite.roomName,
|
|
9135
|
+
mode: invite.mode,
|
|
9136
|
+
caller_name: invite.callerName,
|
|
9137
|
+
expires_at: invite.expiresAt
|
|
9138
|
+
}
|
|
9042
9139
|
};
|
|
9043
9140
|
}
|
|
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"];
|
|
9141
|
+
function callInviteText(invite) {
|
|
9142
|
+
return invite.mode === "video" ? `${invite.callerName} started a video call` : `${invite.callerName} started an audio call`;
|
|
9143
|
+
}
|
|
9144
|
+
function createCallInviteHandler(options) {
|
|
9145
|
+
const now = options.now ?? (() => Date.now());
|
|
9055
9146
|
return {
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
(
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
|
|
9147
|
+
kind: CALL_INVITE_KIND,
|
|
9148
|
+
versions: [CALL_INVITE_VERSION],
|
|
9149
|
+
choices: (payload) => {
|
|
9150
|
+
if (Date.parse(payload.expires_at) < now()) return [];
|
|
9151
|
+
return [
|
|
9152
|
+
{ id: "join", label: payload.mode === "video" ? "Join video" : "Join call", tone: "primary" },
|
|
9153
|
+
{ id: "decline", label: "Decline", tone: "neutral" }
|
|
9154
|
+
];
|
|
9155
|
+
},
|
|
9156
|
+
summarize: (payload) => payload.mode === "video" ? `${payload.caller_name} started a video call` : `${payload.caller_name} started an audio call`,
|
|
9157
|
+
execute: async (payload, context) => {
|
|
9158
|
+
const settledAt = new Date(now()).toISOString();
|
|
9159
|
+
const inviteId = asCallId(payload.invite_id);
|
|
9160
|
+
const pending = await options.repository.pendingInvites(asUserId(String(context.actorId)));
|
|
9161
|
+
const live = pending.find((invite) => invite.id === inviteId) ?? null;
|
|
9162
|
+
if (live === null) {
|
|
9163
|
+
return {
|
|
9164
|
+
kind: CALL_INVITE_KIND,
|
|
9165
|
+
messageId: context.messageId,
|
|
9166
|
+
actorId: context.actorId,
|
|
9167
|
+
outcome: "unavailable",
|
|
9168
|
+
label: "This call has ended",
|
|
9169
|
+
settledAt,
|
|
9170
|
+
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."
|
|
9171
|
+
};
|
|
9172
|
+
}
|
|
9173
|
+
if (context.choice === "decline") {
|
|
9174
|
+
await options.calls.decline(inviteId);
|
|
9175
|
+
return {
|
|
9176
|
+
kind: CALL_INVITE_KIND,
|
|
9177
|
+
messageId: context.messageId,
|
|
9178
|
+
actorId: context.actorId,
|
|
9179
|
+
outcome: "declined",
|
|
9180
|
+
label: "Declined",
|
|
9181
|
+
settledAt
|
|
9182
|
+
};
|
|
9183
|
+
}
|
|
9184
|
+
const accepted = await options.calls.accept(inviteId, live.mode);
|
|
9185
|
+
await options.onJoin(accepted);
|
|
9186
|
+
return {
|
|
9187
|
+
kind: CALL_INVITE_KIND,
|
|
9188
|
+
messageId: context.messageId,
|
|
9189
|
+
actorId: context.actorId,
|
|
9190
|
+
outcome: "applied",
|
|
9191
|
+
label: "Joined",
|
|
9192
|
+
settledAt
|
|
9193
|
+
};
|
|
9194
|
+
}
|
|
9063
9195
|
};
|
|
9064
9196
|
}
|
|
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();
|
|
9197
|
+
var MEETING_INVITE_KIND = "meet.meeting-invite";
|
|
9198
|
+
var MEETING_INVITE_VERSION = 1;
|
|
9199
|
+
function meetingInviteAction(payload) {
|
|
9200
|
+
return { kind: MEETING_INVITE_KIND, version: MEETING_INVITE_VERSION, payload };
|
|
9201
|
+
}
|
|
9202
|
+
function createMeetingInviteHandler(options) {
|
|
9203
|
+
const now = options.now ?? (() => Date.now());
|
|
9081
9204
|
return {
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9205
|
+
kind: MEETING_INVITE_KIND,
|
|
9206
|
+
versions: [MEETING_INVITE_VERSION],
|
|
9207
|
+
// A DURABLE LINK NEVER EXPIRES (R3) — the chip stays useful after the
|
|
9208
|
+
// meeting too, because that is when people go looking for the notes.
|
|
9209
|
+
choices: () => [{ id: "open", label: "Open meeting", tone: "primary" }],
|
|
9210
|
+
summarize: (payload) => payload.scheduled_for === null ? payload.title : `${payload.title} \u2014 ${payload.scheduled_for}`,
|
|
9211
|
+
execute: async (payload, context) => {
|
|
9212
|
+
if (payload.slug.length === 0) {
|
|
9213
|
+
throw new MeetError(
|
|
9214
|
+
"invalid-response",
|
|
9215
|
+
"meet.meeting-invite: the payload carries no slug",
|
|
9216
|
+
"A meeting invitation without a durable slug cannot be opened. Fix the sender; never synthesize a link on the receiving side."
|
|
9217
|
+
);
|
|
9218
|
+
}
|
|
9219
|
+
await options.onOpen(payload);
|
|
9220
|
+
return {
|
|
9221
|
+
kind: MEETING_INVITE_KIND,
|
|
9222
|
+
messageId: context.messageId,
|
|
9223
|
+
actorId: context.actorId,
|
|
9224
|
+
outcome: "applied",
|
|
9225
|
+
label: "Opened",
|
|
9226
|
+
settledAt: new Date(now()).toISOString()
|
|
9227
|
+
};
|
|
9228
|
+
}
|
|
9088
9229
|
};
|
|
9089
9230
|
}
|
|
9090
9231
|
|
|
9091
9232
|
// 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."
|
|
9233
|
+
function refusal(response, operation) {
|
|
9234
|
+
return refusalFromResponse(
|
|
9235
|
+
response,
|
|
9236
|
+
operation,
|
|
9237
|
+
"The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
|
|
9104
9238
|
);
|
|
9105
9239
|
}
|
|
9106
9240
|
function count(body, key) {
|
|
@@ -9255,6 +9389,7 @@ export {
|
|
|
9255
9389
|
attendanceCsv,
|
|
9256
9390
|
attendanceReport,
|
|
9257
9391
|
attendanceTotals,
|
|
9392
|
+
attendeeKey,
|
|
9258
9393
|
callInviteAction,
|
|
9259
9394
|
callInviteText,
|
|
9260
9395
|
callStateLabel,
|
|
@@ -9358,6 +9493,7 @@ export {
|
|
|
9358
9493
|
summarizeMeetingRecordRestrictions,
|
|
9359
9494
|
transcriptDocument,
|
|
9360
9495
|
unavailableFrom,
|
|
9496
|
+
uniqueAttendees,
|
|
9361
9497
|
useAudioLevel,
|
|
9362
9498
|
useCalls,
|
|
9363
9499
|
useCanModerate,
|