@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/dist/react.cjs CHANGED
@@ -148,6 +148,7 @@ __export(react_exports, {
148
148
  attendanceCsv: () => attendanceCsv,
149
149
  attendanceReport: () => attendanceReport,
150
150
  attendanceTotals: () => attendanceTotals,
151
+ attendeeKey: () => attendeeKey,
151
152
  callInviteAction: () => callInviteAction,
152
153
  callInviteText: () => callInviteText,
153
154
  callStateLabel: () => callStateLabel,
@@ -251,6 +252,7 @@ __export(react_exports, {
251
252
  summarizeMeetingRecordRestrictions: () => summarizeMeetingRecordRestrictions,
252
253
  transcriptDocument: () => transcriptDocument,
253
254
  unavailableFrom: () => unavailableFrom,
255
+ uniqueAttendees: () => uniqueAttendees,
254
256
  useAudioLevel: () => useAudioLevel,
255
257
  useCalls: () => useCalls,
256
258
  useCanModerate: () => useCanModerate,
@@ -778,6 +780,29 @@ function readText(data) {
778
780
  }
779
781
  return "";
780
782
  }
783
+ var SERVER_FAILED_SENTENCE = "The server hit an unexpected problem and nothing was changed.";
784
+ function isRawServerText(value) {
785
+ const text3 = value.trim();
786
+ 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);
787
+ }
788
+ async function refusalFromResponse(response, operation, fallbackRemedy) {
789
+ let body = null;
790
+ try {
791
+ body = await response.json();
792
+ } catch {
793
+ body = null;
794
+ }
795
+ const said = readServerRefusal(body);
796
+ const code = response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport";
797
+ const error = new MeetError(
798
+ code,
799
+ said?.reason ?? SERVER_FAILED_SENTENCE,
800
+ said !== null && said.remedy.length > 0 ? said.remedy : fallbackRemedy
801
+ );
802
+ error.operation = operation;
803
+ error.status = response.status;
804
+ return error;
805
+ }
781
806
  function readServerRefusal(body) {
782
807
  if (typeof body === "string") {
783
808
  return body.trim().length === 0 ? null : { reason: body.trim(), remedy: "" };
@@ -796,7 +821,8 @@ function readServerRefusal(body) {
796
821
  return messages.length === 0 ? null : { reason: messages.join("; "), remedy: "" };
797
822
  }
798
823
  if (typeof detail === "object" && detail !== null) return readServerRefusal(detail);
799
- const reason = typeof row["reason"] === "string" ? row["reason"] : typeof row["message"] === "string" ? row["message"] : typeof detail === "string" ? detail : typeof row["error"] === "string" ? row["error"] : null;
824
+ const humanOrNull = (value) => typeof value === "string" && value.trim().length > 0 && !isRawServerText(value) ? value.trim() : null;
825
+ 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);
800
826
  if (reason === null) return null;
801
827
  const remedy = typeof row["remedy"] === "string" ? row["remedy"] : "";
802
828
  if (remedy.length > 0) return { reason, remedy };
@@ -1850,6 +1876,7 @@ var EMPTY = {
1850
1876
  spotlight: null,
1851
1877
  moderationNotice: null,
1852
1878
  actionNotice: null,
1879
+ privateChatRequest: null,
1853
1880
  effects: DEFAULT_EFFECTS,
1854
1881
  transcript: [],
1855
1882
  notes: [],
@@ -4039,6 +4066,10 @@ function createRoomEngine(options) {
4039
4066
  }
4040
4067
  },
4041
4068
  disconnected(reason) {
4069
+ if (current !== null) {
4070
+ tokens.invalidate(current.roomName);
4071
+ current = null;
4072
+ }
4042
4073
  store.patch({ phase: "left" });
4043
4074
  options.onLeft?.(reason);
4044
4075
  }
@@ -4341,19 +4372,8 @@ function createInRoomClient(options) {
4341
4372
  const token = options.resolveRoomToken();
4342
4373
  return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
4343
4374
  }
4344
- async function refusal2(response, operation, remedy) {
4345
- let body = null;
4346
- try {
4347
- body = await response.json();
4348
- } catch {
4349
- body = null;
4350
- }
4351
- const said = readServerRefusal(body);
4352
- return new MeetError(
4353
- response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
4354
- said?.reason ?? `${operation}: the server answered ${response.status}`,
4355
- said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
4356
- );
4375
+ function refusal2(response, operation, remedy) {
4376
+ return refusalFromResponse(response, operation, remedy);
4357
4377
  }
4358
4378
  return {
4359
4379
  async moderate(request) {
@@ -6313,217 +6333,618 @@ function useGuestRecordAccess(meeting) {
6313
6333
  // src/react/components.tsx
6314
6334
  var import_react4 = require("react");
6315
6335
  var import_design_system = require("@ai-matrx/design-system");
6316
- var import_format2 = require("@ai-matrx/kit/format");
6336
+ var import_format3 = require("@ai-matrx/kit/format");
6317
6337
 
6318
- // src/core/format.ts
6319
- function participantSummary(participants) {
6320
- const names = participants.filter((p) => !p.isLocal).map((p) => p.displayName);
6321
- if (names.length === 0) return "Just you";
6322
- if (names.length === 1) return names[0] ?? "";
6323
- if (names.length === 2) return `${names[0]} and ${names[1]}`;
6324
- return `${names[0]}, ${names[1]} and ${names.length - 2} ${names.length - 2 === 1 ? "other" : "others"}`;
6325
- }
6326
- function callStateLabel(state, outgoing) {
6327
- switch (state) {
6328
- case "dialing":
6329
- case "ringing":
6330
- return outgoing ? "Calling\u2026" : "Incoming call";
6331
- case "connecting":
6332
- return "Connecting\u2026";
6333
- case "active":
6334
- return "In call";
6335
- case "declined":
6336
- return outgoing ? "Declined" : "You declined";
6337
- case "missed":
6338
- return outgoing ? "No answer" : "Missed call";
6339
- case "busy":
6340
- return outgoing ? "On another call" : "You were busy";
6341
- case "cancelled":
6342
- return outgoing ? "Cancelled" : "Cancelled by caller";
6343
- case "ended":
6344
- return "Call ended";
6345
- case "failed":
6346
- return "Call failed";
6347
- default:
6348
- return "Call";
6349
- }
6338
+ // src/core/after.ts
6339
+ var import_format2 = require("@ai-matrx/kit/format");
6340
+ function kindOf(person) {
6341
+ if (person.isAgent) return "assistant";
6342
+ if (person.role === "host") return "host";
6343
+ if (person.role === "cohost") return "cohost";
6344
+ if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
6345
+ return "member";
6350
6346
  }
6351
- function countLabel(count2, singular, plural = `${singular}s`) {
6352
- return `${count2} ${count2 === 1 ? singular : plural}`;
6347
+ function ms(iso) {
6348
+ if (iso === null) return null;
6349
+ const at = new Date(iso).getTime();
6350
+ return Number.isNaN(at) ? null : at;
6353
6351
  }
6354
-
6355
- // src/react/icons.tsx
6356
- var import_jsx_runtime2 = require("react/jsx-runtime");
6357
- function Icon({ children, ...props }) {
6358
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
6359
- "svg",
6360
- {
6361
- width: "20",
6362
- height: "20",
6363
- viewBox: "0 0 24 24",
6364
- fill: "none",
6365
- stroke: "currentColor",
6366
- strokeWidth: "1.75",
6367
- strokeLinecap: "round",
6368
- strokeLinejoin: "round",
6369
- "aria-hidden": "true",
6370
- focusable: "false",
6371
- ...props,
6372
- children
6352
+ function attendeeKey(person, names = {}) {
6353
+ if (person.userId !== null) return `user:${person.userId}`;
6354
+ if (person.isAgent) return `agent:${person.identity}`;
6355
+ const name = displayNameFor(names, person.identity, person.displayName).trim().toLowerCase();
6356
+ return name.length > 0 ? `guest-name:${name}` : `identity:${person.identity}`;
6357
+ }
6358
+ function uniqueAttendees(attendees, names = {}) {
6359
+ const byKey = /* @__PURE__ */ new Map();
6360
+ for (const person of attendees) {
6361
+ const key = attendeeKey(person, names);
6362
+ const kept = byKey.get(key);
6363
+ if (kept === void 0) {
6364
+ byKey.set(key, person);
6365
+ continue;
6373
6366
  }
6374
- );
6375
- }
6376
- function MicIcon(props) {
6377
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6378
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }),
6379
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
6380
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 19v3" })
6381
- ] });
6382
- }
6383
- function MicOffIcon(props) {
6384
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6385
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M2 2l20 20" }),
6386
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12" }),
6387
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 9.34V5a3 3 0 0 0-5.94-.6" }),
6388
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19 10v2a7 7 0 0 1-.11 1.23M5 10v2a7 7 0 0 0 11 5.66" }),
6389
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 19v3" })
6390
- ] });
6367
+ const keptJoin = ms(kept.joinedAt);
6368
+ const join = ms(person.joinedAt);
6369
+ if (join !== null && (keptJoin === null || join < keptJoin)) byKey.set(key, person);
6370
+ }
6371
+ return [...byKey.values()];
6372
+ }
6373
+ var KIND_RANK = {
6374
+ host: 0,
6375
+ cohost: 1,
6376
+ member: 2,
6377
+ "signed-in guest": 3,
6378
+ guest: 4,
6379
+ assistant: 5
6380
+ };
6381
+ function coveredMs(intervals) {
6382
+ const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
6383
+ let total = 0;
6384
+ let from = -Infinity;
6385
+ let to = -Infinity;
6386
+ for (const [start, end] of sorted) {
6387
+ if (start > to) {
6388
+ if (to > from) total += to - from;
6389
+ from = start;
6390
+ to = end;
6391
+ } else if (end > to) {
6392
+ to = end;
6393
+ }
6394
+ }
6395
+ if (to > from) total += to - from;
6396
+ return total;
6391
6397
  }
6392
- function CameraIcon(props) {
6393
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6394
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m23 7-7 5 7 5V7Z" }),
6395
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "1", y: "5", width: "15", height: "14", rx: "2" })
6396
- ] });
6398
+ function attendanceReport(meeting, attendees, names = {}) {
6399
+ const end = ms(meeting.endedAt);
6400
+ const perJoin = attendees.map((person) => {
6401
+ const joined = ms(person.joinedAt);
6402
+ const left = ms(person.leftAt);
6403
+ const until = left ?? end;
6404
+ const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
6405
+ return {
6406
+ id: person.id,
6407
+ identity: person.identity,
6408
+ name: displayNameFor(names, person.identity, person.displayName),
6409
+ userId: person.userId,
6410
+ kind: kindOf(person),
6411
+ joinedAt: person.joinedAt,
6412
+ leftAt: person.leftAt,
6413
+ durationMs: duration,
6414
+ durationToEnd: duration !== null && left === null,
6415
+ admission: person.admissionState,
6416
+ joins: 1
6417
+ };
6418
+ });
6419
+ const groups = /* @__PURE__ */ new Map();
6420
+ attendees.forEach((person, index) => {
6421
+ const key = attendeeKey(person, names);
6422
+ const group = groups.get(key) ?? { rows: [], people: [] };
6423
+ group.rows.push(perJoin[index]);
6424
+ group.people.push(person);
6425
+ groups.set(key, group);
6426
+ });
6427
+ const rows = [...groups.values()].map(({ rows: joins }) => {
6428
+ if (joins.length === 1) return joins[0];
6429
+ const came = joins.filter((row) => row.joinedAt !== null);
6430
+ const first = [...came].sort((a, b) => (ms(a.joinedAt) ?? 0) - (ms(b.joinedAt) ?? 0))[0] ?? joins[0];
6431
+ const kind = joins.reduce(
6432
+ (best, row) => KIND_RANK[row.kind] < KIND_RANK[best] ? row.kind : best,
6433
+ first.kind
6434
+ );
6435
+ const open = came.some((row) => row.leftAt === null);
6436
+ const lastLeave = came.reduce((latest, row) => {
6437
+ const left = ms(row.leftAt);
6438
+ return left !== null && (latest === null || left > latest) ? left : latest;
6439
+ }, null);
6440
+ const intervals = came.flatMap((row) => {
6441
+ const joined = ms(row.joinedAt);
6442
+ return joined !== null && row.durationMs !== null ? [[joined, joined + row.durationMs]] : [];
6443
+ });
6444
+ return {
6445
+ ...first,
6446
+ kind,
6447
+ leftAt: open ? null : lastLeave === null ? null : new Date(lastLeave).toISOString(),
6448
+ durationMs: intervals.length === 0 ? null : coveredMs(intervals),
6449
+ durationToEnd: came.some((row) => row.durationToEnd),
6450
+ joins: came.length === 0 ? joins.length : came.length
6451
+ };
6452
+ });
6453
+ const rank = KIND_RANK;
6454
+ return rows.sort((a, b) => {
6455
+ if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
6456
+ return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
6457
+ });
6397
6458
  }
6398
- function CameraOffIcon(props) {
6399
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6400
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M2 2l20 20" }),
6401
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10.66 5H14a2 2 0 0 1 2 2v3.34l1 1L23 7v10" }),
6402
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 16a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2" })
6403
- ] });
6459
+ function attendanceTotals(rows) {
6460
+ const humans = rows.filter((row) => row.kind !== "assistant");
6461
+ const attended = humans.filter((row) => row.joinedAt !== null);
6462
+ const timed = attended.filter((row) => row.durationMs !== null);
6463
+ return {
6464
+ people: attended.length,
6465
+ guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
6466
+ neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
6467
+ averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
6468
+ };
6404
6469
  }
6405
- function ScreenShareIcon(props) {
6406
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6407
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
6408
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 21h8M12 17v4" }),
6409
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 13V8m0 0-2.5 2.5M12 8l2.5 2.5" })
6410
- ] });
6470
+ function csvCell(value) {
6471
+ if (value === null) return "";
6472
+ const text3 = String(value);
6473
+ return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
6411
6474
  }
6412
- function PhoneIcon(props) {
6413
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }) });
6475
+ var ATTENDANCE_CSV_HEADER = [
6476
+ "Meeting",
6477
+ "Meeting started",
6478
+ "Name",
6479
+ "Attended as",
6480
+ "Joined",
6481
+ "Left",
6482
+ "Minutes in the meeting",
6483
+ "Admission"
6484
+ ];
6485
+ function attendanceCsv(reports) {
6486
+ const lines = [ATTENDANCE_CSV_HEADER.join(",")];
6487
+ for (const { meeting, rows } of reports) {
6488
+ for (const row of rows) {
6489
+ lines.push(
6490
+ [
6491
+ meeting.title,
6492
+ meeting.startedAt ?? meeting.scheduledFor,
6493
+ row.name,
6494
+ row.kind,
6495
+ row.joinedAt,
6496
+ row.leftAt,
6497
+ row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
6498
+ row.admission
6499
+ ].map(csvCell).join(",")
6500
+ );
6501
+ }
6502
+ }
6503
+ return `${lines.join("\r\n")}\r
6504
+ `;
6414
6505
  }
6415
- function PhoneOffIcon(props) {
6416
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6417
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M2 2l20 20" }),
6418
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }),
6419
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" })
6420
- ] });
6506
+ function recordingOffsetMs(at, recording) {
6507
+ if (recording === null || recording.state !== "available") return null;
6508
+ const start = ms(recording.startedAt);
6509
+ const moment = ms(at);
6510
+ if (start === null || moment === null || moment < start) return null;
6511
+ const end = ms(recording.endedAt);
6512
+ if (end !== null && moment > end) return null;
6513
+ return moment - start;
6421
6514
  }
6422
- function HandIcon(props) {
6423
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6424
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 11V6a2 2 0 0 0-4 0v5" }),
6425
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M14 10V4a2 2 0 0 0-4 0v7" }),
6426
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10 10.5V6a2 2 0 0 0-4 0v8" }),
6427
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" })
6428
- ] });
6515
+ function clockPosition(offsetMs) {
6516
+ const total = Math.max(0, Math.floor(offsetMs / 1e3));
6517
+ const h = Math.floor(total / 3600);
6518
+ const m = Math.floor(total % 3600 / 60);
6519
+ const s = total % 60;
6520
+ const ss = String(s).padStart(2, "0");
6521
+ return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
6429
6522
  }
6430
- function ChatIcon(props) {
6431
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z" }) });
6523
+ function searchTranscript(segments, query, names = {}) {
6524
+ const needle = fold(query.trim());
6525
+ if (needle.length === 0) return [];
6526
+ const out = [];
6527
+ for (const segment of segments) {
6528
+ const hay = fold(segment.text);
6529
+ const ranges = [];
6530
+ let from = 0;
6531
+ for (; ; ) {
6532
+ const at = hay.indexOf(needle, from);
6533
+ if (at < 0) break;
6534
+ ranges.push([at, at + needle.length]);
6535
+ from = at + needle.length;
6536
+ }
6537
+ const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
6538
+ if (ranges.length > 0 || speaker.includes(needle)) {
6539
+ out.push({ segmentId: segment.id, ranges });
6540
+ }
6541
+ }
6542
+ return out;
6432
6543
  }
6433
- function PeopleIcon(props) {
6434
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6435
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
6436
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "9", cy: "7", r: "4" }),
6437
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" })
6438
- ] });
6544
+ function fold(value) {
6545
+ return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
6439
6546
  }
6440
- function RecordIcon(props) {
6441
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "8", fill: "currentColor", stroke: "none" }) });
6442
- }
6443
- function SparklesIcon(props) {
6444
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6445
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }),
6446
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" })
6447
- ] });
6448
- }
6449
- function LockIcon(props) {
6450
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6451
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2" }),
6452
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 10V7a4 4 0 0 1 8 0v3" })
6453
- ] });
6454
- }
6455
- function LayoutIcon(props) {
6456
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6457
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
6458
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 9h18M9 21V9" })
6459
- ] });
6547
+ function clockOfDay(iso, options) {
6548
+ const at = new Date(iso);
6549
+ if (Number.isNaN(at.getTime())) return "";
6550
+ return at.toLocaleTimeString(options.locale, {
6551
+ hour: "numeric",
6552
+ minute: "2-digit",
6553
+ second: "2-digit",
6554
+ ...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
6555
+ });
6460
6556
  }
6461
- function SettingsIcon(props) {
6462
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6463
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "3" }),
6464
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" })
6465
- ] });
6557
+ function vttStamp(offsetMs) {
6558
+ const total = Math.max(0, Math.round(offsetMs));
6559
+ const h = Math.floor(total / 36e5);
6560
+ const m = Math.floor(total % 36e5 / 6e4);
6561
+ const s = Math.floor(total % 6e4 / 1e3);
6562
+ const milli = total % 1e3;
6563
+ return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
6466
6564
  }
6467
- function CheckIcon(props) {
6468
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M20 6 9 17l-5-5" }) });
6565
+ function transcriptDocument(bundle, format, options = {}) {
6566
+ const names = options.names ?? bundle.names;
6567
+ const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
6568
+ const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
6569
+ if (format === "vtt") {
6570
+ const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
6571
+ const zero = ms(zeroIso) ?? 0;
6572
+ const cues = ["WEBVTT", ""];
6573
+ lines.forEach((line, index) => {
6574
+ const start = (ms(line.startedAt) ?? zero) - zero;
6575
+ if (start < 0) return;
6576
+ const next = lines[index + 1];
6577
+ const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
6578
+ const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
6579
+ cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
6580
+ cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
6581
+ cues.push("");
6582
+ });
6583
+ return cues.join("\n");
6584
+ }
6585
+ const out = [];
6586
+ const title = bundle.meeting.title;
6587
+ out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
6588
+ out.push("");
6589
+ let last = null;
6590
+ for (const line of lines) {
6591
+ if (line.identity !== last) {
6592
+ if (last !== null) out.push("");
6593
+ const who = speaker(line);
6594
+ const clock = clockOfDay(line.startedAt, options);
6595
+ out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
6596
+ last = line.identity;
6597
+ }
6598
+ out.push(line.text.trim());
6599
+ }
6600
+ if (lines.length === 0) out.push("No transcript was captured for this meeting.");
6601
+ return `${out.join("\n")}
6602
+ `;
6469
6603
  }
6470
- function CloseIcon(props) {
6471
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 6 6 18M6 6l12 12" }) });
6604
+ function bullet(note) {
6605
+ const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
6606
+ return `- ${note.text.trim()}${owner}`;
6472
6607
  }
6473
- function PinIcon(props) {
6474
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6475
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 17v5" }),
6476
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9 2h6l-1 6 3.5 3.5V14H6.5v-2.5L10 8 9 2Z" })
6477
- ] });
6608
+ function notesDocument(bundle, options = {}) {
6609
+ const out = [`# ${bundle.meeting.title}`, ""];
6610
+ if (options.when) out.push(`_${options.when}_`, "");
6611
+ const people = uniqueAttendees(
6612
+ bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null),
6613
+ bundle.names
6614
+ ).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
6615
+ if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
6616
+ out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
6617
+ out.push("## Decisions", "");
6618
+ out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
6619
+ out.push("## Action items", "");
6620
+ out.push(
6621
+ ...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
6622
+ ""
6623
+ );
6624
+ if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
6625
+ return out.join("\n");
6478
6626
  }
6479
- function WifiLowIcon(props) {
6480
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6481
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 20h.01" }),
6482
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8.5 16.5a5 5 0 0 1 7 0" })
6483
- ] });
6627
+ function meetingLength(meeting) {
6628
+ const start = ms(meeting.startedAt);
6629
+ const end = ms(meeting.endedAt);
6630
+ if (start === null || end === null || end < start) return null;
6631
+ return (0, import_format2.formatDurationMs)(end - start, { style: "long" });
6484
6632
  }
6485
- function SmileIcon(props) {
6486
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6487
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
6488
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
6489
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }),
6490
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "15", x2: "15.01", y1: "9", y2: "9" })
6491
- ] });
6633
+ async function loadMeetingChat(api, meetingId, roomToken = null) {
6634
+ const operation = "meet.loadMeetingChat";
6635
+ let response;
6636
+ try {
6637
+ response = await api.request({
6638
+ path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
6639
+ method: "GET",
6640
+ operation,
6641
+ requireAuth: roomToken === null,
6642
+ ...roomToken !== null ? { headers: { [ROOM_TOKEN_HEADER]: roomToken } } : {}
6643
+ });
6644
+ } catch (error) {
6645
+ return {
6646
+ messages: [],
6647
+ refusal: {
6648
+ message: `The chat could not be read (${error.message}).`,
6649
+ remedy: error.remedy ?? "Reload the page."
6650
+ }
6651
+ };
6652
+ }
6653
+ if (!response.ok) {
6654
+ let body = null;
6655
+ try {
6656
+ body = await response.json();
6657
+ } catch {
6658
+ body = null;
6659
+ }
6660
+ const said = readServerRefusal(body);
6661
+ return {
6662
+ messages: [],
6663
+ refusal: {
6664
+ message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
6665
+ remedy: said?.remedy || "Reload the page."
6666
+ }
6667
+ };
6668
+ }
6669
+ const payload = await response.json();
6670
+ const rows = Array.isArray(payload.messages) ? payload.messages : [];
6671
+ return {
6672
+ messages: rows.map(
6673
+ (row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
6674
+ ).filter((message) => message !== null),
6675
+ refusal: null
6676
+ };
6492
6677
  }
6493
- function MoreIcon(props) {
6494
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6495
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "1" }),
6496
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "19", cy: "12", r: "1" }),
6497
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "5", cy: "12", r: "1" })
6498
- ] });
6678
+ function refusalOf(response, operation) {
6679
+ return refusalFromResponse(response, operation, "Nothing was sent. Try again.");
6499
6680
  }
6500
- function SearchIcon(props) {
6501
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6502
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "11", cy: "11", r: "8" }),
6503
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m21 21-4.3-4.3" })
6504
- ] });
6681
+ function asRecipient(row) {
6682
+ const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
6683
+ return {
6684
+ key: String(row["key"] ?? ""),
6685
+ name: String(row["name"] ?? ""),
6686
+ email: typeof row["email"] === "string" ? row["email"] : null,
6687
+ userId: typeof row["user_id"] === "string" ? row["user_id"] : null,
6688
+ sources: sources.filter((s) => s === "invitee" || s === "attendee"),
6689
+ reachable: row["reachable"] !== false
6690
+ };
6505
6691
  }
6506
- function KeyboardIcon(props) {
6507
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6508
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10 8h.01M12 12h.01M14 8h.01M16 12h.01M18 8h.01M6 8h.01M7 16h10M8 12h.01" }),
6509
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" })
6510
- ] });
6692
+ async function loadFollowUpDraft(api, meetingId) {
6693
+ const operation = "meet.loadFollowUpDraft";
6694
+ const response = await api.request({
6695
+ path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
6696
+ method: "GET",
6697
+ requireAuth: true,
6698
+ operation
6699
+ });
6700
+ if (!response.ok) throw await refusalOf(response, operation);
6701
+ const body = await response.json();
6702
+ const last = body["last_sent"];
6703
+ return {
6704
+ subject: String(body["subject"] ?? ""),
6705
+ body: String(body["body"] ?? ""),
6706
+ recipients: (Array.isArray(body["recipients"]) ? body["recipients"] : []).map(
6707
+ (row) => asRecipient(row)
6708
+ ),
6709
+ wrapUpWritten: body["wrap_up_written"] === true,
6710
+ lastSent: last && typeof last["at"] === "string" ? { at: last["at"], count: typeof last["count"] === "number" ? last["count"] : 0 } : null
6711
+ };
6511
6712
  }
6512
- function SpotlightIcon(props) {
6513
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 2 15 8.5 22 9.3 17 14 18.2 21 12 17.6 5.8 21 7 14 2 9.3 9 8.5Z" }) });
6713
+ async function sendFollowUp(api, meetingId, input) {
6714
+ const operation = "meet.sendFollowUp";
6715
+ const response = await api.request({
6716
+ path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
6717
+ method: "POST",
6718
+ requireAuth: true,
6719
+ operation,
6720
+ body: {
6721
+ subject: input.subject,
6722
+ body: input.body,
6723
+ recipient_keys: [...input.recipientKeys],
6724
+ dry_run: input.dryRun === true
6725
+ }
6726
+ });
6727
+ if (!response.ok) throw await refusalOf(response, operation);
6728
+ const body = await response.json();
6729
+ return {
6730
+ dryRun: body["dry_run"] === true,
6731
+ queued: Array.isArray(body["queued"]) ? body["queued"] : [],
6732
+ skipped: Array.isArray(body["skipped"]) ? body["skipped"].map((row) => ({
6733
+ key: String(row["key"] ?? ""),
6734
+ reason: String(row["reason"] ?? "")
6735
+ })) : []
6736
+ };
6514
6737
  }
6515
- function SignalIcon({ bars, ...props }) {
6516
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6517
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4 20v-2", opacity: bars >= 1 ? 1 : 0.3 }),
6518
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10 20v-6", opacity: bars >= 2 ? 1 : 0.3 }),
6519
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 20V9", opacity: bars >= 3 ? 1 : 0.3 }),
6520
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M22 20V4", opacity: bars >= 3 ? 1 : 0.3 })
6521
- ] });
6738
+
6739
+ // src/core/format.ts
6740
+ function participantSummary(participants) {
6741
+ const names = participants.filter((p) => !p.isLocal).map((p) => p.displayName);
6742
+ if (names.length === 0) return "Just you";
6743
+ if (names.length === 1) return names[0] ?? "";
6744
+ if (names.length === 2) return `${names[0]} and ${names[1]}`;
6745
+ return `${names[0]}, ${names[1]} and ${names.length - 2} ${names.length - 2 === 1 ? "other" : "others"}`;
6522
6746
  }
6523
- function SpeakerIcon(props) {
6524
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6525
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M11 5 6 9H2v6h4l5 4V5Z" }),
6526
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
6747
+ function callStateLabel(state, outgoing) {
6748
+ switch (state) {
6749
+ case "dialing":
6750
+ case "ringing":
6751
+ return outgoing ? "Calling\u2026" : "Incoming call";
6752
+ case "connecting":
6753
+ return "Connecting\u2026";
6754
+ case "active":
6755
+ return "In call";
6756
+ case "declined":
6757
+ return outgoing ? "Declined" : "You declined";
6758
+ case "missed":
6759
+ return outgoing ? "No answer" : "Missed call";
6760
+ case "busy":
6761
+ return outgoing ? "On another call" : "You were busy";
6762
+ case "cancelled":
6763
+ return outgoing ? "Cancelled" : "Cancelled by caller";
6764
+ case "ended":
6765
+ return "Call ended";
6766
+ case "failed":
6767
+ return "Call failed";
6768
+ default:
6769
+ return "Call";
6770
+ }
6771
+ }
6772
+ function countLabel(count2, singular, plural = `${singular}s`) {
6773
+ return `${count2} ${count2 === 1 ? singular : plural}`;
6774
+ }
6775
+
6776
+ // src/react/icons.tsx
6777
+ var import_jsx_runtime2 = require("react/jsx-runtime");
6778
+ function Icon({ children, ...props }) {
6779
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
6780
+ "svg",
6781
+ {
6782
+ width: "20",
6783
+ height: "20",
6784
+ viewBox: "0 0 24 24",
6785
+ fill: "none",
6786
+ stroke: "currentColor",
6787
+ strokeWidth: "1.75",
6788
+ strokeLinecap: "round",
6789
+ strokeLinejoin: "round",
6790
+ "aria-hidden": "true",
6791
+ focusable: "false",
6792
+ ...props,
6793
+ children
6794
+ }
6795
+ );
6796
+ }
6797
+ function MicIcon(props) {
6798
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6799
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }),
6800
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
6801
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 19v3" })
6802
+ ] });
6803
+ }
6804
+ function MicOffIcon(props) {
6805
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6806
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M2 2l20 20" }),
6807
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12" }),
6808
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 9.34V5a3 3 0 0 0-5.94-.6" }),
6809
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19 10v2a7 7 0 0 1-.11 1.23M5 10v2a7 7 0 0 0 11 5.66" }),
6810
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 19v3" })
6811
+ ] });
6812
+ }
6813
+ function CameraIcon(props) {
6814
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6815
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m23 7-7 5 7 5V7Z" }),
6816
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "1", y: "5", width: "15", height: "14", rx: "2" })
6817
+ ] });
6818
+ }
6819
+ function CameraOffIcon(props) {
6820
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6821
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M2 2l20 20" }),
6822
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10.66 5H14a2 2 0 0 1 2 2v3.34l1 1L23 7v10" }),
6823
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 16a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2" })
6824
+ ] });
6825
+ }
6826
+ function ScreenShareIcon(props) {
6827
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6828
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
6829
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 21h8M12 17v4" }),
6830
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 13V8m0 0-2.5 2.5M12 8l2.5 2.5" })
6831
+ ] });
6832
+ }
6833
+ function PhoneIcon(props) {
6834
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }) });
6835
+ }
6836
+ function PhoneOffIcon(props) {
6837
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6838
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M2 2l20 20" }),
6839
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }),
6840
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" })
6841
+ ] });
6842
+ }
6843
+ function HandIcon(props) {
6844
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6845
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 11V6a2 2 0 0 0-4 0v5" }),
6846
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M14 10V4a2 2 0 0 0-4 0v7" }),
6847
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10 10.5V6a2 2 0 0 0-4 0v8" }),
6848
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" })
6849
+ ] });
6850
+ }
6851
+ function ChatIcon(props) {
6852
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z" }) });
6853
+ }
6854
+ function PeopleIcon(props) {
6855
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6856
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
6857
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "9", cy: "7", r: "4" }),
6858
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" })
6859
+ ] });
6860
+ }
6861
+ function RecordIcon(props) {
6862
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "8", fill: "currentColor", stroke: "none" }) });
6863
+ }
6864
+ function SparklesIcon(props) {
6865
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6866
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }),
6867
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" })
6868
+ ] });
6869
+ }
6870
+ function LockIcon(props) {
6871
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6872
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2" }),
6873
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 10V7a4 4 0 0 1 8 0v3" })
6874
+ ] });
6875
+ }
6876
+ function LayoutIcon(props) {
6877
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6878
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
6879
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 9h18M9 21V9" })
6880
+ ] });
6881
+ }
6882
+ function SettingsIcon(props) {
6883
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6884
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "3" }),
6885
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" })
6886
+ ] });
6887
+ }
6888
+ function CheckIcon(props) {
6889
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M20 6 9 17l-5-5" }) });
6890
+ }
6891
+ function CloseIcon(props) {
6892
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 6 6 18M6 6l12 12" }) });
6893
+ }
6894
+ function PinIcon(props) {
6895
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6896
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 17v5" }),
6897
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9 2h6l-1 6 3.5 3.5V14H6.5v-2.5L10 8 9 2Z" })
6898
+ ] });
6899
+ }
6900
+ function WifiLowIcon(props) {
6901
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6902
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 20h.01" }),
6903
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8.5 16.5a5 5 0 0 1 7 0" })
6904
+ ] });
6905
+ }
6906
+ function SmileIcon(props) {
6907
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6908
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
6909
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
6910
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }),
6911
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "15", x2: "15.01", y1: "9", y2: "9" })
6912
+ ] });
6913
+ }
6914
+ function MoreIcon(props) {
6915
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6916
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "1" }),
6917
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "19", cy: "12", r: "1" }),
6918
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "5", cy: "12", r: "1" })
6919
+ ] });
6920
+ }
6921
+ function SearchIcon(props) {
6922
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6923
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "11", cy: "11", r: "8" }),
6924
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m21 21-4.3-4.3" })
6925
+ ] });
6926
+ }
6927
+ function KeyboardIcon(props) {
6928
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6929
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10 8h.01M12 12h.01M14 8h.01M16 12h.01M18 8h.01M6 8h.01M7 16h10M8 12h.01" }),
6930
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" })
6931
+ ] });
6932
+ }
6933
+ function SpotlightIcon(props) {
6934
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 2 15 8.5 22 9.3 17 14 18.2 21 12 17.6 5.8 21 7 14 2 9.3 9 8.5Z" }) });
6935
+ }
6936
+ function SignalIcon({ bars, ...props }) {
6937
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6938
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4 20v-2", opacity: bars >= 1 ? 1 : 0.3 }),
6939
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10 20v-6", opacity: bars >= 2 ? 1 : 0.3 }),
6940
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 20V9", opacity: bars >= 3 ? 1 : 0.3 }),
6941
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M22 20V4", opacity: bars >= 3 ? 1 : 0.3 })
6942
+ ] });
6943
+ }
6944
+ function SpeakerIcon(props) {
6945
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
6946
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M11 5 6 9H2v6h4l5 4V5Z" }),
6947
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
6527
6948
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
6528
6949
  ] });
6529
6950
  }
@@ -6553,7 +6974,7 @@ function Avatar({
6553
6974
  url,
6554
6975
  size = 40
6555
6976
  }) {
6556
- const tint = (0, import_format2.avatarPaletteIndex)(name);
6977
+ const tint = (0, import_format3.avatarPaletteIndex)(name);
6557
6978
  if (url != null && url.length > 0) {
6558
6979
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
6559
6980
  "img",
@@ -6578,7 +6999,7 @@ function Avatar({
6578
6999
  background: `var(--mx-meet-avatar-${tint})`
6579
7000
  },
6580
7001
  "aria-hidden": "true",
6581
- children: (0, import_format2.getInitials)(name)
7002
+ children: (0, import_format3.getInitials)(name)
6582
7003
  }
6583
7004
  );
6584
7005
  }
@@ -6763,6 +7184,15 @@ function ControlBar({ onLeave }) {
6763
7184
  const micUnavailable = useLocalMedia().microphone.unavailable;
6764
7185
  const unread = useUnreadChat(panel === "chat");
6765
7186
  const toggle = (next) => setPanel(panel === next ? "none" : next);
7187
+ const privateChatAt = snapshot?.privateChatRequest?.at ?? null;
7188
+ const meetStore = useMeetHost()?.store ?? null;
7189
+ (0, import_react4.useEffect)(() => {
7190
+ const request = meetStore?.snapshot().privateChatRequest ?? null;
7191
+ if (request === null) return;
7192
+ setChatTo(request.to);
7193
+ setPanel("chat");
7194
+ meetStore?.patch({ privateChatRequest: null });
7195
+ }, [privateChatAt, meetStore]);
6766
7196
  useMeetShortcuts({
6767
7197
  mic: () => void room.toggleMicrophone(),
6768
7198
  camera: () => void room.toggleCamera(),
@@ -7002,7 +7432,12 @@ function Popover({
7002
7432
  placement = "up"
7003
7433
  }) {
7004
7434
  const ref = (0, import_react4.useRef)(null);
7005
- const [style, setStyle] = (0, import_react4.useState)({ visibility: "hidden" });
7435
+ const [style, setStyle] = (0, import_react4.useState)({
7436
+ position: "fixed",
7437
+ top: 0,
7438
+ left: 0,
7439
+ visibility: "hidden"
7440
+ });
7006
7441
  (0, import_react4.useLayoutEffect)(() => {
7007
7442
  const element = ref.current;
7008
7443
  const anchor = element?.parentElement ?? null;
@@ -7018,14 +7453,35 @@ function Popover({
7018
7453
  );
7019
7454
  const fitsBelow = rect.bottom + gutter + height <= window.innerHeight;
7020
7455
  const fitsAbove = rect.top - gutter - height >= 0;
7456
+ if (!fitsBelow && !fitsAbove) {
7457
+ const top = Math.max(gutter, window.innerHeight - height - gutter);
7458
+ setStyle({
7459
+ position: "fixed",
7460
+ left,
7461
+ top,
7462
+ maxHeight: window.innerHeight - top - gutter,
7463
+ overflowY: "auto"
7464
+ });
7465
+ return;
7466
+ }
7021
7467
  const down = placement === "down" ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;
7022
7468
  setStyle(
7023
7469
  down ? { position: "fixed", left, top: rect.bottom + gutter } : { position: "fixed", left, bottom: window.innerHeight - rect.top + gutter }
7024
7470
  );
7025
7471
  };
7026
7472
  place();
7473
+ const frame = requestAnimationFrame(place);
7474
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => place());
7475
+ observer?.observe(document.body);
7476
+ observer?.observe(element);
7027
7477
  window.addEventListener("resize", place);
7028
- return () => window.removeEventListener("resize", place);
7478
+ window.addEventListener("scroll", place, true);
7479
+ return () => {
7480
+ cancelAnimationFrame(frame);
7481
+ observer?.disconnect();
7482
+ window.removeEventListener("resize", place);
7483
+ window.removeEventListener("scroll", place, true);
7484
+ };
7029
7485
  }, [placement]);
7030
7486
  (0, import_react4.useEffect)(() => {
7031
7487
  const onKey = (event) => {
@@ -7402,11 +7858,15 @@ function PersonMenu({
7402
7858
  const pinned = snapshot?.pinned === participant.identity;
7403
7859
  const spotlit = snapshot?.spotlight === participant.identity;
7404
7860
  const hasAccount = participant.userId !== null;
7861
+ const [refused, setRefused] = (0, import_react4.useState)(false);
7405
7862
  const act = (request) => {
7863
+ setRefused(false);
7406
7864
  void controls.run(request).then((result) => {
7407
7865
  if (result !== null) onClose();
7866
+ else setRefused(true);
7408
7867
  });
7409
7868
  };
7869
+ const refusal2 = refused && controls.notice?.failed === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system.ErrorBox, { as: "p", className: "mx-meet__error", message: controls.notice.text, operation: "Change this participant", origin: "meet/components", children: controls.notice.text }) : null;
7410
7870
  if (mode === "rename" || mode === "report") {
7411
7871
  const renaming = mode === "rename";
7412
7872
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: renaming ? "Rename" : "Report", onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
@@ -7444,7 +7904,7 @@ function PersonMenu({
7444
7904
  }
7445
7905
  )
7446
7906
  ] }),
7447
- controls.notice?.failed === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__error", children: controls.notice.text }) : null,
7907
+ refusal2,
7448
7908
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu-actions", children: [
7449
7909
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Back" }),
7450
7910
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", className: "mx-meet__send", disabled: text3.trim().length === 0 || controls.busy, children: renaming ? "Rename" : "Report" })
@@ -7457,6 +7917,7 @@ function PersonMenu({
7457
7917
  const removing = mode === "remove";
7458
7918
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: removing ? "Remove" : "Make host", onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu-form", children: [
7459
7919
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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.` }),
7920
+ refusal2,
7460
7921
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu-actions", children: [
7461
7922
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Cancel" }),
7462
7923
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -7487,11 +7948,15 @@ function PersonMenu({
7487
7948
  ]
7488
7949
  }
7489
7950
  ),
7490
- !self && !participant.isAgent && onMessage !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
7951
+ !self && !participant.isAgent && (onMessage !== void 0 || host !== null) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
7491
7952
  MenuItem,
7492
7953
  {
7493
7954
  onSelect: () => {
7494
- onMessage(participant.identity);
7955
+ if (onMessage !== void 0) onMessage(participant.identity);
7956
+ else
7957
+ host?.store.patch({
7958
+ privateChatRequest: { to: participant.identity, at: Date.now() }
7959
+ });
7495
7960
  onClose();
7496
7961
  },
7497
7962
  children: [
@@ -8118,7 +8583,7 @@ function PreJoinBody({
8118
8583
  microphone,
8119
8584
  ...meetingId !== void 0 ? { meetingId } : {},
8120
8585
  ...slug !== void 0 ? { slug } : {}
8121
- }).then(() => onJoined?.()).finally(() => setJoining(false));
8586
+ }).then(() => onJoined?.()).catch(() => void 0).finally(() => setJoining(false));
8122
8587
  },
8123
8588
  children: joining ? "Joining\u2026" : "Join now"
8124
8589
  }
@@ -8254,7 +8719,7 @@ function MeetingRoomBody({
8254
8719
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { className: "mx-meet__title", children: snapshot?.meeting?.title ?? "Meeting" }),
8255
8720
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__meta", children: [
8256
8721
  participantSummary(snapshot?.participants ?? []),
8257
- elapsed > 0 ? ` \xB7 ${(0, import_format2.formatDurationMs)(elapsed)}` : ""
8722
+ elapsed > 0 ? ` \xB7 ${(0, import_format3.formatDurationMs)(elapsed)}` : ""
8258
8723
  ] }),
8259
8724
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RecordingIndicator, {}),
8260
8725
  snapshot?.locked === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__badge", children: [
@@ -8560,7 +9025,10 @@ function MeetingRecordBody({
8560
9025
  const blocks = groupTranscript(bundle.transcript, bundle.names);
8561
9026
  const notice = recordingNotice(bundle.recording);
8562
9027
  const fileId = playableRecordingFileId(bundle.recording);
8563
- const attended = bundle.attendees.filter((person) => person.admissionState !== "denied");
9028
+ const attended = uniqueAttendees(
9029
+ bundle.attendees.filter((person) => person.admissionState !== "denied"),
9030
+ bundle.names
9031
+ );
8564
9032
  const restrictionNotices = summarizeMeetingRecordRestrictions(bundle.restrictions);
8565
9033
  const guestFinishedMeeting = host.identity.userId === null && meeting.endedAt !== null;
8566
9034
  const everyRecordPartRefused = ["attendance", "notes", "transcript", "recording"].every(
@@ -8869,514 +9337,172 @@ function RecordingPlayerBody({ fileId, host }) {
8869
9337
  const failure = thrown;
8870
9338
  setError({
8871
9339
  message: failure.message ?? "The recording could not be loaded.",
8872
- remedy: failure.remedy ?? PLAYBACK_RETRY_REMEDY
8873
- });
8874
- } finally {
8875
- setLoading(false);
8876
- }
8877
- })();
8878
- },
8879
- children: loading ? "Loading the recording\u2026" : "Play the recording"
8880
- }
8881
- )
8882
- ] });
8883
- }
8884
- function clockOf(iso) {
8885
- const at = new Date(iso);
8886
- if (Number.isNaN(at.getTime())) return "";
8887
- return at.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
8888
- }
8889
- function MeetingList({ baseUrl }) {
8890
- const host = useMeetHost();
8891
- const list = useMeetings();
8892
- if (host === null) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(InertNotice, { what: "Your meetings" });
8893
- if (list.failure !== null) {
8894
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system.ErrorBox, { as: "div", className: "mx-meet__empty", error: list.failure, operation: "List your meetings", origin: "meet/components", children: [
8895
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__empty-title", children: "Your meetings could not be listed" }),
8896
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__empty-body", children: list.failure.message }),
8897
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__empty-body", children: list.failure.remedy })
8898
- ] });
8899
- }
8900
- if (list.loading) {
8901
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__empty", role: "status", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__empty-title", children: "Loading your meetings\u2026" }) });
8902
- }
8903
- if (list.meetings.length === 0) {
8904
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__empty", role: "status", children: [
8905
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__empty-title", children: "No meetings yet" }),
8906
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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." })
8907
- ] });
8908
- }
8909
- const origin = baseUrl ?? (typeof window === "undefined" ? "" : window.location.origin);
8910
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "mx-meet-list", children: list.meetings.map((meeting) => {
8911
- const ended = meeting.endedAt !== null;
8912
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "mx-meet-list__row", children: [
8913
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet-list__main", children: [
8914
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet-list__title", children: [
8915
- meeting.title,
8916
- ended ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__badge", children: "Ended" }) : null
8917
- ] }),
8918
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet-list__when", children: meetingWhen(meeting) })
8919
- ] }),
8920
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { className: "mx-meet-list__action", href: meetingLink(origin, meeting.slug), children: ended ? "Open the record" : "Join" })
8921
- ] }, meeting.id);
8922
- }) });
8923
- }
8924
-
8925
- // src/core/invitations.ts
8926
- var CALL_INVITE_KIND = "meet.call-invite";
8927
- var CALL_INVITE_VERSION = 1;
8928
- function callInviteAction(invite) {
8929
- return {
8930
- kind: CALL_INVITE_KIND,
8931
- version: CALL_INVITE_VERSION,
8932
- payload: {
8933
- invite_id: invite.id,
8934
- room_name: invite.roomName,
8935
- mode: invite.mode,
8936
- caller_name: invite.callerName,
8937
- expires_at: invite.expiresAt
8938
- }
8939
- };
8940
- }
8941
- function callInviteText(invite) {
8942
- return invite.mode === "video" ? `${invite.callerName} started a video call` : `${invite.callerName} started an audio call`;
8943
- }
8944
- function createCallInviteHandler(options) {
8945
- const now = options.now ?? (() => Date.now());
8946
- return {
8947
- kind: CALL_INVITE_KIND,
8948
- versions: [CALL_INVITE_VERSION],
8949
- choices: (payload) => {
8950
- if (Date.parse(payload.expires_at) < now()) return [];
8951
- return [
8952
- { id: "join", label: payload.mode === "video" ? "Join video" : "Join call", tone: "primary" },
8953
- { id: "decline", label: "Decline", tone: "neutral" }
8954
- ];
8955
- },
8956
- summarize: (payload) => payload.mode === "video" ? `${payload.caller_name} started a video call` : `${payload.caller_name} started an audio call`,
8957
- execute: async (payload, context) => {
8958
- const settledAt = new Date(now()).toISOString();
8959
- const inviteId = asCallId(payload.invite_id);
8960
- const pending = await options.repository.pendingInvites(asUserId(String(context.actorId)));
8961
- const live = pending.find((invite) => invite.id === inviteId) ?? null;
8962
- if (live === null) {
8963
- return {
8964
- kind: CALL_INVITE_KIND,
8965
- messageId: context.messageId,
8966
- actorId: context.actorId,
8967
- outcome: "unavailable",
8968
- label: "This call has ended",
8969
- settledAt,
8970
- 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."
8971
- };
8972
- }
8973
- if (context.choice === "decline") {
8974
- await options.calls.decline(inviteId);
8975
- return {
8976
- kind: CALL_INVITE_KIND,
8977
- messageId: context.messageId,
8978
- actorId: context.actorId,
8979
- outcome: "declined",
8980
- label: "Declined",
8981
- settledAt
8982
- };
8983
- }
8984
- const accepted = await options.calls.accept(inviteId, live.mode);
8985
- await options.onJoin(accepted);
8986
- return {
8987
- kind: CALL_INVITE_KIND,
8988
- messageId: context.messageId,
8989
- actorId: context.actorId,
8990
- outcome: "applied",
8991
- label: "Joined",
8992
- settledAt
8993
- };
8994
- }
8995
- };
8996
- }
8997
- var MEETING_INVITE_KIND = "meet.meeting-invite";
8998
- var MEETING_INVITE_VERSION = 1;
8999
- function meetingInviteAction(payload) {
9000
- return { kind: MEETING_INVITE_KIND, version: MEETING_INVITE_VERSION, payload };
9001
- }
9002
- function createMeetingInviteHandler(options) {
9003
- const now = options.now ?? (() => Date.now());
9004
- return {
9005
- kind: MEETING_INVITE_KIND,
9006
- versions: [MEETING_INVITE_VERSION],
9007
- // A DURABLE LINK NEVER EXPIRES (R3) — the chip stays useful after the
9008
- // meeting too, because that is when people go looking for the notes.
9009
- choices: () => [{ id: "open", label: "Open meeting", tone: "primary" }],
9010
- summarize: (payload) => payload.scheduled_for === null ? payload.title : `${payload.title} \u2014 ${payload.scheduled_for}`,
9011
- execute: async (payload, context) => {
9012
- if (payload.slug.length === 0) {
9013
- throw new MeetError(
9014
- "invalid-response",
9015
- "meet.meeting-invite: the payload carries no slug",
9016
- "A meeting invitation without a durable slug cannot be opened. Fix the sender; never synthesize a link on the receiving side."
9017
- );
9018
- }
9019
- await options.onOpen(payload);
9020
- return {
9021
- kind: MEETING_INVITE_KIND,
9022
- messageId: context.messageId,
9023
- actorId: context.actorId,
9024
- outcome: "applied",
9025
- label: "Opened",
9026
- settledAt: new Date(now()).toISOString()
9027
- };
9028
- }
9029
- };
9030
- }
9031
-
9032
- // src/core/after.ts
9033
- var import_format4 = require("@ai-matrx/kit/format");
9034
- function kindOf(person) {
9035
- if (person.isAgent) return "assistant";
9036
- if (person.role === "host") return "host";
9037
- if (person.role === "cohost") return "cohost";
9038
- if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
9039
- return "member";
9040
- }
9041
- function ms(iso) {
9042
- if (iso === null) return null;
9043
- const at = new Date(iso).getTime();
9044
- return Number.isNaN(at) ? null : at;
9045
- }
9046
- function attendanceReport(meeting, attendees, names = {}) {
9047
- const end = ms(meeting.endedAt);
9048
- const rows = attendees.map((person) => {
9049
- const joined = ms(person.joinedAt);
9050
- const left = ms(person.leftAt);
9051
- const until = left ?? end;
9052
- const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
9053
- return {
9054
- id: person.id,
9055
- identity: person.identity,
9056
- name: displayNameFor(names, person.identity, person.displayName),
9057
- userId: person.userId,
9058
- kind: kindOf(person),
9059
- joinedAt: person.joinedAt,
9060
- leftAt: person.leftAt,
9061
- durationMs: duration,
9062
- durationToEnd: duration !== null && left === null,
9063
- admission: person.admissionState
9064
- };
9065
- });
9066
- const rank = {
9067
- host: 0,
9068
- cohost: 1,
9069
- member: 2,
9070
- "signed-in guest": 3,
9071
- guest: 4,
9072
- assistant: 5
9073
- };
9074
- return rows.sort((a, b) => {
9075
- if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
9076
- return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
9077
- });
9078
- }
9079
- function attendanceTotals(rows) {
9080
- const humans = rows.filter((row) => row.kind !== "assistant");
9081
- const attended = humans.filter((row) => row.joinedAt !== null);
9082
- const timed = attended.filter((row) => row.durationMs !== null);
9083
- return {
9084
- people: attended.length,
9085
- guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
9086
- neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
9087
- averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
9088
- };
9089
- }
9090
- function csvCell(value) {
9091
- if (value === null) return "";
9092
- const text3 = String(value);
9093
- return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
9094
- }
9095
- var ATTENDANCE_CSV_HEADER = [
9096
- "Meeting",
9097
- "Meeting started",
9098
- "Name",
9099
- "Attended as",
9100
- "Joined",
9101
- "Left",
9102
- "Minutes in the meeting",
9103
- "Admission"
9104
- ];
9105
- function attendanceCsv(reports) {
9106
- const lines = [ATTENDANCE_CSV_HEADER.join(",")];
9107
- for (const { meeting, rows } of reports) {
9108
- for (const row of rows) {
9109
- lines.push(
9110
- [
9111
- meeting.title,
9112
- meeting.startedAt ?? meeting.scheduledFor,
9113
- row.name,
9114
- row.kind,
9115
- row.joinedAt,
9116
- row.leftAt,
9117
- row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
9118
- row.admission
9119
- ].map(csvCell).join(",")
9120
- );
9121
- }
9122
- }
9123
- return `${lines.join("\r\n")}\r
9124
- `;
9125
- }
9126
- function recordingOffsetMs(at, recording) {
9127
- if (recording === null || recording.state !== "available") return null;
9128
- const start = ms(recording.startedAt);
9129
- const moment = ms(at);
9130
- if (start === null || moment === null || moment < start) return null;
9131
- const end = ms(recording.endedAt);
9132
- if (end !== null && moment > end) return null;
9133
- return moment - start;
9134
- }
9135
- function clockPosition(offsetMs) {
9136
- const total = Math.max(0, Math.floor(offsetMs / 1e3));
9137
- const h = Math.floor(total / 3600);
9138
- const m = Math.floor(total % 3600 / 60);
9139
- const s = total % 60;
9140
- const ss = String(s).padStart(2, "0");
9141
- return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
9142
- }
9143
- function searchTranscript(segments, query, names = {}) {
9144
- const needle = fold(query.trim());
9145
- if (needle.length === 0) return [];
9146
- const out = [];
9147
- for (const segment of segments) {
9148
- const hay = fold(segment.text);
9149
- const ranges = [];
9150
- let from = 0;
9151
- for (; ; ) {
9152
- const at = hay.indexOf(needle, from);
9153
- if (at < 0) break;
9154
- ranges.push([at, at + needle.length]);
9155
- from = at + needle.length;
9156
- }
9157
- const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
9158
- if (ranges.length > 0 || speaker.includes(needle)) {
9159
- out.push({ segmentId: segment.id, ranges });
9160
- }
9161
- }
9162
- return out;
9163
- }
9164
- function fold(value) {
9165
- return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
9166
- }
9167
- function clockOfDay(iso, options) {
9168
- const at = new Date(iso);
9169
- if (Number.isNaN(at.getTime())) return "";
9170
- return at.toLocaleTimeString(options.locale, {
9171
- hour: "numeric",
9172
- minute: "2-digit",
9173
- second: "2-digit",
9174
- ...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
9175
- });
9176
- }
9177
- function vttStamp(offsetMs) {
9178
- const total = Math.max(0, Math.round(offsetMs));
9179
- const h = Math.floor(total / 36e5);
9180
- const m = Math.floor(total % 36e5 / 6e4);
9181
- const s = Math.floor(total % 6e4 / 1e3);
9182
- const milli = total % 1e3;
9183
- return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
9184
- }
9185
- function transcriptDocument(bundle, format, options = {}) {
9186
- const names = options.names ?? bundle.names;
9187
- const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
9188
- const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
9189
- if (format === "vtt") {
9190
- const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
9191
- const zero = ms(zeroIso) ?? 0;
9192
- const cues = ["WEBVTT", ""];
9193
- lines.forEach((line, index) => {
9194
- const start = (ms(line.startedAt) ?? zero) - zero;
9195
- if (start < 0) return;
9196
- const next = lines[index + 1];
9197
- const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
9198
- const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
9199
- cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
9200
- cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
9201
- cues.push("");
9202
- });
9203
- return cues.join("\n");
9204
- }
9205
- const out = [];
9206
- const title = bundle.meeting.title;
9207
- out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
9208
- out.push("");
9209
- let last = null;
9210
- for (const line of lines) {
9211
- if (line.identity !== last) {
9212
- if (last !== null) out.push("");
9213
- const who = speaker(line);
9214
- const clock = clockOfDay(line.startedAt, options);
9215
- out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
9216
- last = line.identity;
9217
- }
9218
- out.push(line.text.trim());
9219
- }
9220
- if (lines.length === 0) out.push("No transcript was captured for this meeting.");
9221
- return `${out.join("\n")}
9222
- `;
9223
- }
9224
- function bullet(note) {
9225
- const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
9226
- return `- ${note.text.trim()}${owner}`;
9227
- }
9228
- function notesDocument(bundle, options = {}) {
9229
- const out = [`# ${bundle.meeting.title}`, ""];
9230
- if (options.when) out.push(`_${options.when}_`, "");
9231
- const people = bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
9232
- if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
9233
- out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
9234
- out.push("## Decisions", "");
9235
- out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
9236
- out.push("## Action items", "");
9237
- out.push(
9238
- ...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
9239
- ""
9240
- );
9241
- if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
9242
- return out.join("\n");
9340
+ remedy: failure.remedy ?? PLAYBACK_RETRY_REMEDY
9341
+ });
9342
+ } finally {
9343
+ setLoading(false);
9344
+ }
9345
+ })();
9346
+ },
9347
+ children: loading ? "Loading the recording\u2026" : "Play the recording"
9348
+ }
9349
+ )
9350
+ ] });
9243
9351
  }
9244
- function meetingLength(meeting) {
9245
- const start = ms(meeting.startedAt);
9246
- const end = ms(meeting.endedAt);
9247
- if (start === null || end === null || end < start) return null;
9248
- return (0, import_format4.formatDurationMs)(end - start, { style: "long" });
9352
+ function clockOf(iso) {
9353
+ const at = new Date(iso);
9354
+ if (Number.isNaN(at.getTime())) return "";
9355
+ return at.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
9249
9356
  }
9250
- async function loadMeetingChat(api, meetingId, roomToken = null) {
9251
- const operation = "meet.loadMeetingChat";
9252
- let response;
9253
- try {
9254
- response = await api.request({
9255
- path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
9256
- method: "GET",
9257
- operation,
9258
- requireAuth: roomToken === null,
9259
- ...roomToken !== null ? { headers: { [ROOM_TOKEN_HEADER]: roomToken } } : {}
9260
- });
9261
- } catch (error) {
9262
- return {
9263
- messages: [],
9264
- refusal: {
9265
- message: `The chat could not be read (${error.message}).`,
9266
- remedy: error.remedy ?? "Reload the page."
9267
- }
9268
- };
9357
+ function MeetingList({ baseUrl }) {
9358
+ const host = useMeetHost();
9359
+ const list = useMeetings();
9360
+ if (host === null) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(InertNotice, { what: "Your meetings" });
9361
+ if (list.failure !== null) {
9362
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system.ErrorBox, { as: "div", className: "mx-meet__empty", error: list.failure, operation: "List your meetings", origin: "meet/components", children: [
9363
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__empty-title", children: "Your meetings could not be listed" }),
9364
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__empty-body", children: list.failure.message }),
9365
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__empty-body", children: list.failure.remedy })
9366
+ ] });
9269
9367
  }
9270
- if (!response.ok) {
9271
- let body = null;
9272
- try {
9273
- body = await response.json();
9274
- } catch {
9275
- body = null;
9276
- }
9277
- const said = readServerRefusal(body);
9278
- return {
9279
- messages: [],
9280
- refusal: {
9281
- message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
9282
- remedy: said?.remedy || "Reload the page."
9283
- }
9284
- };
9368
+ if (list.loading) {
9369
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__empty", role: "status", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__empty-title", children: "Loading your meetings\u2026" }) });
9285
9370
  }
9286
- const payload = await response.json();
9287
- const rows = Array.isArray(payload.messages) ? payload.messages : [];
9288
- return {
9289
- messages: rows.map(
9290
- (row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
9291
- ).filter((message) => message !== null),
9292
- refusal: null
9293
- };
9294
- }
9295
- async function refusalOf(response, operation) {
9296
- let body = null;
9297
- try {
9298
- body = await response.json();
9299
- } catch {
9300
- body = null;
9371
+ if (list.meetings.length === 0) {
9372
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__empty", role: "status", children: [
9373
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__empty-title", children: "No meetings yet" }),
9374
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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." })
9375
+ ] });
9301
9376
  }
9302
- const said = readServerRefusal(body);
9303
- return new MeetError(
9304
- response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
9305
- `${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
9306
- said?.remedy || "Nothing was sent. Try again."
9307
- );
9377
+ const origin = baseUrl ?? (typeof window === "undefined" ? "" : window.location.origin);
9378
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "mx-meet-list", children: list.meetings.map((meeting) => {
9379
+ const ended = meeting.endedAt !== null;
9380
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "mx-meet-list__row", children: [
9381
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet-list__main", children: [
9382
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet-list__title", children: [
9383
+ meeting.title,
9384
+ ended ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__badge", children: "Ended" }) : null
9385
+ ] }),
9386
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet-list__when", children: meetingWhen(meeting) })
9387
+ ] }),
9388
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { className: "mx-meet-list__action", href: meetingLink(origin, meeting.slug), children: ended ? "Open the record" : "Join" })
9389
+ ] }, meeting.id);
9390
+ }) });
9308
9391
  }
9309
- function asRecipient(row) {
9310
- const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
9392
+
9393
+ // src/core/invitations.ts
9394
+ var CALL_INVITE_KIND = "meet.call-invite";
9395
+ var CALL_INVITE_VERSION = 1;
9396
+ function callInviteAction(invite) {
9311
9397
  return {
9312
- key: String(row["key"] ?? ""),
9313
- name: String(row["name"] ?? ""),
9314
- email: typeof row["email"] === "string" ? row["email"] : null,
9315
- userId: typeof row["user_id"] === "string" ? row["user_id"] : null,
9316
- sources: sources.filter((s) => s === "invitee" || s === "attendee"),
9317
- reachable: row["reachable"] !== false
9398
+ kind: CALL_INVITE_KIND,
9399
+ version: CALL_INVITE_VERSION,
9400
+ payload: {
9401
+ invite_id: invite.id,
9402
+ room_name: invite.roomName,
9403
+ mode: invite.mode,
9404
+ caller_name: invite.callerName,
9405
+ expires_at: invite.expiresAt
9406
+ }
9318
9407
  };
9319
9408
  }
9320
- async function loadFollowUpDraft(api, meetingId) {
9321
- const operation = "meet.loadFollowUpDraft";
9322
- const response = await api.request({
9323
- path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
9324
- method: "GET",
9325
- requireAuth: true,
9326
- operation
9327
- });
9328
- if (!response.ok) throw await refusalOf(response, operation);
9329
- const body = await response.json();
9330
- const last = body["last_sent"];
9409
+ function callInviteText(invite) {
9410
+ return invite.mode === "video" ? `${invite.callerName} started a video call` : `${invite.callerName} started an audio call`;
9411
+ }
9412
+ function createCallInviteHandler(options) {
9413
+ const now = options.now ?? (() => Date.now());
9331
9414
  return {
9332
- subject: String(body["subject"] ?? ""),
9333
- body: String(body["body"] ?? ""),
9334
- recipients: (Array.isArray(body["recipients"]) ? body["recipients"] : []).map(
9335
- (row) => asRecipient(row)
9336
- ),
9337
- wrapUpWritten: body["wrap_up_written"] === true,
9338
- lastSent: last && typeof last["at"] === "string" ? { at: last["at"], count: typeof last["count"] === "number" ? last["count"] : 0 } : null
9415
+ kind: CALL_INVITE_KIND,
9416
+ versions: [CALL_INVITE_VERSION],
9417
+ choices: (payload) => {
9418
+ if (Date.parse(payload.expires_at) < now()) return [];
9419
+ return [
9420
+ { id: "join", label: payload.mode === "video" ? "Join video" : "Join call", tone: "primary" },
9421
+ { id: "decline", label: "Decline", tone: "neutral" }
9422
+ ];
9423
+ },
9424
+ summarize: (payload) => payload.mode === "video" ? `${payload.caller_name} started a video call` : `${payload.caller_name} started an audio call`,
9425
+ execute: async (payload, context) => {
9426
+ const settledAt = new Date(now()).toISOString();
9427
+ const inviteId = asCallId(payload.invite_id);
9428
+ const pending = await options.repository.pendingInvites(asUserId(String(context.actorId)));
9429
+ const live = pending.find((invite) => invite.id === inviteId) ?? null;
9430
+ if (live === null) {
9431
+ return {
9432
+ kind: CALL_INVITE_KIND,
9433
+ messageId: context.messageId,
9434
+ actorId: context.actorId,
9435
+ outcome: "unavailable",
9436
+ label: "This call has ended",
9437
+ settledAt,
9438
+ 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."
9439
+ };
9440
+ }
9441
+ if (context.choice === "decline") {
9442
+ await options.calls.decline(inviteId);
9443
+ return {
9444
+ kind: CALL_INVITE_KIND,
9445
+ messageId: context.messageId,
9446
+ actorId: context.actorId,
9447
+ outcome: "declined",
9448
+ label: "Declined",
9449
+ settledAt
9450
+ };
9451
+ }
9452
+ const accepted = await options.calls.accept(inviteId, live.mode);
9453
+ await options.onJoin(accepted);
9454
+ return {
9455
+ kind: CALL_INVITE_KIND,
9456
+ messageId: context.messageId,
9457
+ actorId: context.actorId,
9458
+ outcome: "applied",
9459
+ label: "Joined",
9460
+ settledAt
9461
+ };
9462
+ }
9339
9463
  };
9340
9464
  }
9341
- async function sendFollowUp(api, meetingId, input) {
9342
- const operation = "meet.sendFollowUp";
9343
- const response = await api.request({
9344
- path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
9345
- method: "POST",
9346
- requireAuth: true,
9347
- operation,
9348
- body: {
9349
- subject: input.subject,
9350
- body: input.body,
9351
- recipient_keys: [...input.recipientKeys],
9352
- dry_run: input.dryRun === true
9353
- }
9354
- });
9355
- if (!response.ok) throw await refusalOf(response, operation);
9356
- const body = await response.json();
9465
+ var MEETING_INVITE_KIND = "meet.meeting-invite";
9466
+ var MEETING_INVITE_VERSION = 1;
9467
+ function meetingInviteAction(payload) {
9468
+ return { kind: MEETING_INVITE_KIND, version: MEETING_INVITE_VERSION, payload };
9469
+ }
9470
+ function createMeetingInviteHandler(options) {
9471
+ const now = options.now ?? (() => Date.now());
9357
9472
  return {
9358
- dryRun: body["dry_run"] === true,
9359
- queued: Array.isArray(body["queued"]) ? body["queued"] : [],
9360
- skipped: Array.isArray(body["skipped"]) ? body["skipped"].map((row) => ({
9361
- key: String(row["key"] ?? ""),
9362
- reason: String(row["reason"] ?? "")
9363
- })) : []
9473
+ kind: MEETING_INVITE_KIND,
9474
+ versions: [MEETING_INVITE_VERSION],
9475
+ // A DURABLE LINK NEVER EXPIRES (R3) — the chip stays useful after the
9476
+ // meeting too, because that is when people go looking for the notes.
9477
+ choices: () => [{ id: "open", label: "Open meeting", tone: "primary" }],
9478
+ summarize: (payload) => payload.scheduled_for === null ? payload.title : `${payload.title} \u2014 ${payload.scheduled_for}`,
9479
+ execute: async (payload, context) => {
9480
+ if (payload.slug.length === 0) {
9481
+ throw new MeetError(
9482
+ "invalid-response",
9483
+ "meet.meeting-invite: the payload carries no slug",
9484
+ "A meeting invitation without a durable slug cannot be opened. Fix the sender; never synthesize a link on the receiving side."
9485
+ );
9486
+ }
9487
+ await options.onOpen(payload);
9488
+ return {
9489
+ kind: MEETING_INVITE_KIND,
9490
+ messageId: context.messageId,
9491
+ actorId: context.actorId,
9492
+ outcome: "applied",
9493
+ label: "Opened",
9494
+ settledAt: new Date(now()).toISOString()
9495
+ };
9496
+ }
9364
9497
  };
9365
9498
  }
9366
9499
 
9367
9500
  // src/core/management.ts
9368
- async function refusal(response, operation) {
9369
- let body = null;
9370
- try {
9371
- body = await response.json();
9372
- } catch {
9373
- body = null;
9374
- }
9375
- const said = readServerRefusal(body);
9376
- return new MeetError(
9377
- response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
9378
- `${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
9379
- said?.remedy || "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
9501
+ function refusal(response, operation) {
9502
+ return refusalFromResponse(
9503
+ response,
9504
+ operation,
9505
+ "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
9380
9506
  );
9381
9507
  }
9382
9508
  function count(body, key) {