@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.
@@ -1190,6 +1190,22 @@ async function setGuestRecordAccess(args) {
1190
1190
  };
1191
1191
  }
1192
1192
 
1193
+ // src/core/captions-state.ts
1194
+ var CAPTIONS_TIMEOUT_REASON = "Captions are unavailable right now.";
1195
+ function resolveCaptionsState(i) {
1196
+ if (!i.on) return { state: "off", reason: null, remedy: null };
1197
+ if (i.takerState === "transcribing" && i.takerPresent) return { state: "live", reason: null, remedy: null };
1198
+ if (i.takerState === "joining" && !i.timedOut) return { state: "starting", reason: null, remedy: null };
1199
+ if (i.refusal !== null && i.refusal.reason.length > 0) {
1200
+ return { state: "unavailable", reason: i.refusal.reason, remedy: i.refusal.remedy.length > 0 ? i.refusal.remedy : null };
1201
+ }
1202
+ if (i.takerState === "ended" || i.takerState === "handoff_pending") {
1203
+ return { state: "unavailable", reason: i.takerLabel, remedy: null };
1204
+ }
1205
+ if (i.timedOut) return { state: "unavailable", reason: CAPTIONS_TIMEOUT_REASON, remedy: null };
1206
+ return { state: "starting", reason: null, remedy: null };
1207
+ }
1208
+
1193
1209
  // src/react/hooks.ts
1194
1210
  var EMPTY_NAMES = {};
1195
1211
  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.";
@@ -1234,7 +1250,8 @@ function useRoom() {
1234
1250
  local,
1235
1251
  // An observer (role `viewer`) publishes nothing: no Share screen to press and be refused (MD-16).
1236
1252
  sharingAllowed: snapshot === null || canShareUnder(snapshot.livePolicy, effectiveRole(snapshot), effectiveStanding(snapshot)),
1237
- canShareScreen: (engine?.supportsScreenShare() ?? true) && !isObserverRole(snapshot?.grants.role ?? null) && (snapshot === null || canShareUnder(snapshot.livePolicy, effectiveRole(snapshot), effectiveStanding(snapshot))),
1253
+ 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.
1254
+ !(snapshot?.share.state === "ended" && snapshot.share.reason === "not_allowed") && (snapshot === null || canShareUnder(snapshot.livePolicy, effectiveRole(snapshot), effectiveStanding(snapshot))),
1238
1255
  otherPresenter: otherPresenterOf(snapshot),
1239
1256
  join: useCallback2(
1240
1257
  async (request) => requireEngine(engine, "join").join(request),
@@ -1578,32 +1595,37 @@ function useCaptions() {
1578
1595
  if (host === null || meeting === null) return;
1579
1596
  void host.ai.requestCaptions({ meetingId: meeting.id });
1580
1597
  }, [host, meeting]);
1598
+ const [timedOut, setTimedOut] = useState3(false);
1599
+ const timeoutSeconds = snapshot?.captionsStartTimeoutSeconds ?? 20;
1600
+ const live = taker.state === "transcribing" && taker.present;
1601
+ const [attempt, setAttempt] = useState3(0);
1602
+ useEffect3(() => {
1603
+ setTimedOut(false);
1604
+ if (!on || live) return void 0;
1605
+ const id = setTimeout(() => setTimedOut(true), timeoutSeconds * 1e3);
1606
+ return () => clearTimeout(id);
1607
+ }, [on, live, timeoutSeconds, attempt]);
1581
1608
  const toggle = useCallback2(() => {
1582
1609
  if (host === null) return;
1583
1610
  const next = !on;
1584
1611
  host.store.patch({ captionsOn: next, captionsRefusal: null });
1585
1612
  if (next && taker.state !== "joining" && taker.state !== "transcribing") request();
1586
1613
  }, [host, on, taker.state, request]);
1587
- let state = "off";
1588
- let reason = null;
1589
- let remedy = null;
1590
- if (on) {
1591
- if (taker.state === "transcribing" && taker.present) {
1592
- state = "live";
1593
- } else if (taker.state === "joining") {
1594
- state = "starting";
1595
- } else if (refusal !== null && refusal.reason.length > 0) {
1596
- state = "unavailable";
1597
- reason = refusal.reason;
1598
- remedy = refusal.remedy.length > 0 ? refusal.remedy : null;
1599
- } else if (taker.state === "ended" || taker.state === "handoff_pending") {
1600
- state = "unavailable";
1601
- reason = noteTakerLabel(taker);
1602
- } else {
1603
- state = "starting";
1604
- }
1605
- }
1606
- return { on, state, reason, remedy, toggle };
1614
+ const retry = useCallback2(() => {
1615
+ if (host === null) return;
1616
+ host.store.patch({ captionsOn: true, captionsRefusal: null });
1617
+ setAttempt((n) => n + 1);
1618
+ request();
1619
+ }, [host, request]);
1620
+ const view = resolveCaptionsState({
1621
+ on,
1622
+ takerState: taker.state,
1623
+ takerPresent: taker.present,
1624
+ refusal,
1625
+ takerLabel: on ? noteTakerLabel(taker) : "",
1626
+ timedOut
1627
+ });
1628
+ return { on, state: view.state, reason: view.reason, remedy: view.remedy, toggle, retry };
1607
1629
  }
1608
1630
  function useEndMeeting() {
1609
1631
  const host = useMeetHost();
@@ -1661,6 +1683,7 @@ function useMediaSupervisor() {
1661
1683
  acceptOffer: useCallback2(async () => engine?.acceptDeviceOffer(), [engine]),
1662
1684
  dismissOffer: useCallback2(() => engine?.dismissDeviceOffer(), [engine]),
1663
1685
  dismissNotice: useCallback2(() => engine?.dismissDeviceNotice(), [engine]),
1686
+ undoNotice: useCallback2(async () => engine?.undoDeviceNotice(), [engine]),
1664
1687
  retry: useCallback2(
1665
1688
  async (kind) => kind === "microphone" ? engine?.setMicrophoneEnabled(true) : engine?.setCameraEnabled(true),
1666
1689
  [engine]
@@ -1914,19 +1937,19 @@ function useLivePolicy() {
1914
1937
  const host = useMeetHost();
1915
1938
  const pending = consentPending(policy, role, snapshot?.recording === "recording", snapshot?.recordingConsentGiven === true);
1916
1939
  const viewOnly = pending && policy.recordingConsent === "view_only_until_agree";
1917
- const idle = snapshot !== null && snapshot.recording !== "recording" && snapshot.recording !== "starting" && snapshot.recording !== "stopping";
1918
- const given = snapshot?.recordingConsentGiven === true;
1919
1940
  const store = host?.store ?? null;
1920
- useEffect3(() => {
1921
- if (idle && given) store?.patch({ recordingConsentGiven: false });
1922
- }, [idle, given, store]);
1923
1941
  return {
1924
1942
  canChat: canChat(policy, role),
1925
1943
  chatMode: policy.chatMode,
1926
1944
  micLocked: isLockedFor(policy, role, "microphone") || viewOnly,
1927
1945
  cameraLocked: isLockedFor(policy, role, "camera") || viewOnly,
1928
1946
  consent: pending ? policy.recordingConsent === "ok_or_leave" ? "ok_or_leave" : "view_only_until_agree" : null,
1929
- agree: () => store?.patch({ recordingConsentGiven: true }),
1947
+ // The person's agreement is recorded by the SERVER (per run); the screen follows at once and
1948
+ // takes it back if the server did not save it.
1949
+ agree: () => {
1950
+ store?.patch({ recordingConsentGiven: true });
1951
+ void host?.meetings.acknowledgeConsent().catch(() => store?.patch({ recordingConsentGiven: false }));
1952
+ },
1930
1953
  minutesLeft: left,
1931
1954
  timeWarning: left !== null && left <= policy.timeLimitWarningMinutes
1932
1955
  };
@@ -4181,7 +4204,7 @@ function SpeakingWhileMutedNudge() {
4181
4204
  }
4182
4205
  function MediaNotices() {
4183
4206
  const media = useLocalMedia();
4184
- const { region, where, enableAudio, acceptOffer, dismissOffer, dismissNotice, retry } = useMediaSupervisor();
4207
+ const { region, where, enableAudio, acceptOffer, dismissOffer, dismissNotice, undoNotice, retry } = useMediaSupervisor();
4185
4208
  const [fixing, setFixing] = useState5(null);
4186
4209
  const chips = ["microphone", "camera"].flatMap((kind) => {
4187
4210
  const reason = deviceUnavailableReason(media[kind], region.permission[kind]);
@@ -4246,9 +4269,10 @@ function MediaNotices() {
4246
4269
  {
4247
4270
  className: "mx-meet__media-pill",
4248
4271
  role: "status",
4249
- ...notice.outcome === "lost" ? { "data-meet-notice": "device-lost" } : {},
4272
+ "data-meet-notice": notice.outcome === "lost" ? "device-lost" : "device-switched",
4250
4273
  children: [
4251
- /* @__PURE__ */ jsx6("span", { children: notice.outcome === "switched" ? `${notice.from || name(notice.kind)} disconnected \xB7 now using ${notice.to || "the default"}` : `${notice.from || name(notice.kind)} disconnected` }),
4274
+ /* @__PURE__ */ jsx6("span", { children: notice.outcome === "switched" ? `${notice.from || name(notice.kind)} disconnected \xB7 switched to ${notice.to || "the default"}` : `${notice.from || name(notice.kind)} disconnected` }),
4275
+ notice.outcome === "switched" ? /* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__link", onClick: () => void undoNotice(), children: "Undo" }) : null,
4252
4276
  /* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismissNotice, children: /* @__PURE__ */ jsx6(CloseIcon, { width: 14, height: 14 }) })
4253
4277
  ]
4254
4278
  }
@@ -4301,7 +4325,7 @@ function MeetingOverlays() {
4301
4325
  live.minutesLeft,
4302
4326
  " ",
4303
4327
  live.minutesLeft === 1 ? "minute" : "minutes",
4304
- " (time limit)."
4328
+ "."
4305
4329
  ] }) : null,
4306
4330
  notice !== null && notice.kind !== "removed" ? (
4307
4331
  // A host's request ("the host is asking you to unmute"), not an error:
@@ -5360,11 +5384,14 @@ function Captions({ lines = 3 }) {
5360
5384
  const captions = useCaptions();
5361
5385
  if (!captions.on) return null;
5362
5386
  if (captions.state === "unavailable") {
5363
- return /* @__PURE__ */ jsx6("div", { className: "mx-meet__captions", role: "status", "data-meet-notice": "captions-unavailable", children: /* @__PURE__ */ jsxs4("p", { className: "mx-meet__caption", children: [
5364
- "Captions are unavailable. ",
5365
- captions.reason,
5366
- captions.remedy !== null ? ` ${captions.remedy}` : ""
5367
- ] }) });
5387
+ return /* @__PURE__ */ jsxs4("div", { className: "mx-meet__captions", role: "status", "data-meet-notice": "captions-unavailable", children: [
5388
+ /* @__PURE__ */ jsxs4("p", { className: "mx-meet__caption", children: [
5389
+ (captions.reason ?? "").startsWith("Captions are unavailable") ? "" : "Captions are unavailable. ",
5390
+ captions.reason,
5391
+ captions.remedy !== null ? ` ${captions.remedy}` : ""
5392
+ ] }),
5393
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__caption-retry", onClick: captions.retry, children: "Try again" })
5394
+ ] });
5368
5395
  }
5369
5396
  if (visible.length === 0) {
5370
5397
  return /* @__PURE__ */ jsx6("div", { className: "mx-meet__captions", "aria-live": "polite", "aria-label": "Live captions", "data-meet-caption": captions.state, children: /* @__PURE__ */ jsx6("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." }) });