@ai-matrx/meet 0.11.49 → 0.11.51

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.
@@ -1285,6 +1285,22 @@ async function setGuestRecordAccess(args) {
1285
1285
  };
1286
1286
  }
1287
1287
 
1288
+ // src/core/captions-state.ts
1289
+ var CAPTIONS_TIMEOUT_REASON = "Captions are unavailable right now.";
1290
+ function resolveCaptionsState(i) {
1291
+ if (!i.on) return { state: "off", reason: null, remedy: null };
1292
+ if (i.takerState === "transcribing" && i.takerPresent) return { state: "live", reason: null, remedy: null };
1293
+ if (i.takerState === "joining" && !i.timedOut) return { state: "starting", reason: null, remedy: null };
1294
+ if (i.refusal !== null && i.refusal.reason.length > 0) {
1295
+ return { state: "unavailable", reason: i.refusal.reason, remedy: i.refusal.remedy.length > 0 ? i.refusal.remedy : null };
1296
+ }
1297
+ if (i.takerState === "ended" || i.takerState === "handoff_pending") {
1298
+ return { state: "unavailable", reason: i.takerLabel, remedy: null };
1299
+ }
1300
+ if (i.timedOut) return { state: "unavailable", reason: CAPTIONS_TIMEOUT_REASON, remedy: null };
1301
+ return { state: "starting", reason: null, remedy: null };
1302
+ }
1303
+
1288
1304
  // src/react/hooks.ts
1289
1305
  var EMPTY_NAMES = {};
1290
1306
  var NO_RUNTIME_REMEDY = "Render this inside a <MeetProvider> that has a Supabase client, an organization id and an identity (userId or guestName). The provider stays inert until all three are real, and while it is inert this package's components render nothing rather than a dead control.";
@@ -1329,7 +1345,8 @@ function useRoom() {
1329
1345
  local,
1330
1346
  // An observer (role `viewer`) publishes nothing: no Share screen to press and be refused (MD-16).
1331
1347
  sharingAllowed: snapshot === null || canShareUnder(snapshot.livePolicy, effectiveRole(snapshot), effectiveStanding(snapshot)),
1332
- canShareScreen: (engine?.supportsScreenShare() ?? true) && !isObserverRole(snapshot?.grants.role ?? null) && (snapshot === null || canShareUnder(snapshot.livePolicy, effectiveRole(snapshot), effectiveStanding(snapshot))),
1348
+ canShareScreen: (engine?.supportsScreenShare() ?? true) && !isObserverRole(snapshot?.grants.role ?? null) && // The server's word that sharing was taken away arrives before the rules refresh does: the control goes at once.
1349
+ !(snapshot?.share.state === "ended" && snapshot.share.reason === "not_allowed") && (snapshot === null || canShareUnder(snapshot.livePolicy, effectiveRole(snapshot), effectiveStanding(snapshot))),
1333
1350
  otherPresenter: otherPresenterOf(snapshot),
1334
1351
  join: (0, import_react5.useCallback)(
1335
1352
  async (request) => requireEngine(engine, "join").join(request),
@@ -1673,32 +1690,37 @@ function useCaptions() {
1673
1690
  if (host === null || meeting === null) return;
1674
1691
  void host.ai.requestCaptions({ meetingId: meeting.id });
1675
1692
  }, [host, meeting]);
1693
+ const [timedOut, setTimedOut] = (0, import_react5.useState)(false);
1694
+ const timeoutSeconds = snapshot?.captionsStartTimeoutSeconds ?? 20;
1695
+ const live = taker.state === "transcribing" && taker.present;
1696
+ const [attempt, setAttempt] = (0, import_react5.useState)(0);
1697
+ (0, import_react5.useEffect)(() => {
1698
+ setTimedOut(false);
1699
+ if (!on || live) return void 0;
1700
+ const id = setTimeout(() => setTimedOut(true), timeoutSeconds * 1e3);
1701
+ return () => clearTimeout(id);
1702
+ }, [on, live, timeoutSeconds, attempt]);
1676
1703
  const toggle = (0, import_react5.useCallback)(() => {
1677
1704
  if (host === null) return;
1678
1705
  const next = !on;
1679
1706
  host.store.patch({ captionsOn: next, captionsRefusal: null });
1680
1707
  if (next && taker.state !== "joining" && taker.state !== "transcribing") request();
1681
1708
  }, [host, on, taker.state, request]);
1682
- let state = "off";
1683
- let reason = null;
1684
- let remedy = null;
1685
- if (on) {
1686
- if (taker.state === "transcribing" && taker.present) {
1687
- state = "live";
1688
- } else if (taker.state === "joining") {
1689
- state = "starting";
1690
- } else if (refusal !== null && refusal.reason.length > 0) {
1691
- state = "unavailable";
1692
- reason = refusal.reason;
1693
- remedy = refusal.remedy.length > 0 ? refusal.remedy : null;
1694
- } else if (taker.state === "ended" || taker.state === "handoff_pending") {
1695
- state = "unavailable";
1696
- reason = noteTakerLabel(taker);
1697
- } else {
1698
- state = "starting";
1699
- }
1700
- }
1701
- return { on, state, reason, remedy, toggle };
1709
+ const retry = (0, import_react5.useCallback)(() => {
1710
+ if (host === null) return;
1711
+ host.store.patch({ captionsOn: true, captionsRefusal: null });
1712
+ setAttempt((n) => n + 1);
1713
+ request();
1714
+ }, [host, request]);
1715
+ const view = resolveCaptionsState({
1716
+ on,
1717
+ takerState: taker.state,
1718
+ takerPresent: taker.present,
1719
+ refusal,
1720
+ takerLabel: on ? noteTakerLabel(taker) : "",
1721
+ timedOut
1722
+ });
1723
+ return { on, state: view.state, reason: view.reason, remedy: view.remedy, toggle, retry };
1702
1724
  }
1703
1725
  function useEndMeeting() {
1704
1726
  const host = useMeetHost();
@@ -1756,6 +1778,7 @@ function useMediaSupervisor() {
1756
1778
  acceptOffer: (0, import_react5.useCallback)(async () => engine?.acceptDeviceOffer(), [engine]),
1757
1779
  dismissOffer: (0, import_react5.useCallback)(() => engine?.dismissDeviceOffer(), [engine]),
1758
1780
  dismissNotice: (0, import_react5.useCallback)(() => engine?.dismissDeviceNotice(), [engine]),
1781
+ undoNotice: (0, import_react5.useCallback)(async () => engine?.undoDeviceNotice(), [engine]),
1759
1782
  retry: (0, import_react5.useCallback)(
1760
1783
  async (kind) => kind === "microphone" ? engine?.setMicrophoneEnabled(true) : engine?.setCameraEnabled(true),
1761
1784
  [engine]
@@ -2009,19 +2032,19 @@ function useLivePolicy() {
2009
2032
  const host = useMeetHost();
2010
2033
  const pending = consentPending(policy, role, snapshot?.recording === "recording", snapshot?.recordingConsentGiven === true);
2011
2034
  const viewOnly = pending && policy.recordingConsent === "view_only_until_agree";
2012
- const idle = snapshot !== null && snapshot.recording !== "recording" && snapshot.recording !== "starting" && snapshot.recording !== "stopping";
2013
- const given = snapshot?.recordingConsentGiven === true;
2014
2035
  const store = host?.store ?? null;
2015
- (0, import_react5.useEffect)(() => {
2016
- if (idle && given) store?.patch({ recordingConsentGiven: false });
2017
- }, [idle, given, store]);
2018
2036
  return {
2019
2037
  canChat: canChat(policy, role),
2020
2038
  chatMode: policy.chatMode,
2021
2039
  micLocked: isLockedFor(policy, role, "microphone") || viewOnly,
2022
2040
  cameraLocked: isLockedFor(policy, role, "camera") || viewOnly,
2023
2041
  consent: pending ? policy.recordingConsent === "ok_or_leave" ? "ok_or_leave" : "view_only_until_agree" : null,
2024
- agree: () => store?.patch({ recordingConsentGiven: true }),
2042
+ // The person's agreement is recorded by the SERVER (per run); the screen follows at once and
2043
+ // takes it back if the server did not save it.
2044
+ agree: () => {
2045
+ store?.patch({ recordingConsentGiven: true });
2046
+ void host?.meetings.acknowledgeConsent().catch(() => store?.patch({ recordingConsentGiven: false }));
2047
+ },
2025
2048
  minutesLeft: left,
2026
2049
  timeWarning: left !== null && left <= policy.timeLimitWarningMinutes
2027
2050
  };
@@ -4276,7 +4299,7 @@ function SpeakingWhileMutedNudge() {
4276
4299
  }
4277
4300
  function MediaNotices() {
4278
4301
  const media = useLocalMedia();
4279
- const { region, where, enableAudio, acceptOffer, dismissOffer, dismissNotice, retry } = useMediaSupervisor();
4302
+ const { region, where, enableAudio, acceptOffer, dismissOffer, dismissNotice, undoNotice, retry } = useMediaSupervisor();
4280
4303
  const [fixing, setFixing] = (0, import_react10.useState)(null);
4281
4304
  const chips = ["microphone", "camera"].flatMap((kind) => {
4282
4305
  const reason = deviceUnavailableReason(media[kind], region.permission[kind]);
@@ -4341,9 +4364,10 @@ function MediaNotices() {
4341
4364
  {
4342
4365
  className: "mx-meet__media-pill",
4343
4366
  role: "status",
4344
- ...notice.outcome === "lost" ? { "data-meet-notice": "device-lost" } : {},
4367
+ "data-meet-notice": notice.outcome === "lost" ? "device-lost" : "device-switched",
4345
4368
  children: [
4346
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: notice.outcome === "switched" ? `${notice.from || name(notice.kind)} disconnected \xB7 now using ${notice.to || "the default"}` : `${notice.from || name(notice.kind)} disconnected` }),
4369
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: notice.outcome === "switched" ? `${notice.from || name(notice.kind)} disconnected \xB7 switched to ${notice.to || "the default"}` : `${notice.from || name(notice.kind)} disconnected` }),
4370
+ notice.outcome === "switched" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => void undoNotice(), children: "Undo" }) : null,
4347
4371
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismissNotice, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, { width: 14, height: 14 }) })
4348
4372
  ]
4349
4373
  }
@@ -4396,7 +4420,7 @@ function MeetingOverlays() {
4396
4420
  live.minutesLeft,
4397
4421
  " ",
4398
4422
  live.minutesLeft === 1 ? "minute" : "minutes",
4399
- " (time limit)."
4423
+ "."
4400
4424
  ] }) : null,
4401
4425
  notice !== null && notice.kind !== "removed" ? (
4402
4426
  // A host's request ("the host is asking you to unmute"), not an error:
@@ -5455,11 +5479,14 @@ function Captions({ lines = 3 }) {
5455
5479
  const captions = useCaptions();
5456
5480
  if (!captions.on) return null;
5457
5481
  if (captions.state === "unavailable") {
5458
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__captions", role: "status", "data-meet-notice": "captions-unavailable", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__caption", children: [
5459
- "Captions are unavailable. ",
5460
- captions.reason,
5461
- captions.remedy !== null ? ` ${captions.remedy}` : ""
5462
- ] }) });
5482
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__captions", role: "status", "data-meet-notice": "captions-unavailable", children: [
5483
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__caption", children: [
5484
+ (captions.reason ?? "").startsWith("Captions are unavailable") ? "" : "Captions are unavailable. ",
5485
+ captions.reason,
5486
+ captions.remedy !== null ? ` ${captions.remedy}` : ""
5487
+ ] }),
5488
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__caption-retry", onClick: captions.retry, children: "Try again" })
5489
+ ] });
5463
5490
  }
5464
5491
  if (visible.length === 0) {
5465
5492
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__captions", "aria-live": "polite", "aria-label": "Live captions", "data-meet-caption": captions.state, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__caption mx-meet__caption--interim", children: captions.state === "starting" ? "Starting captions. They appear as people speak." : "Captions are on. They appear as people speak." }) });