@opengeni/react 0.45.0 → 0.46.1

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.
@@ -1113,9 +1113,31 @@ var VOICE_RECORDING_TIMESLICE_MILLISECONDS = 5e3;
1113
1113
  var VOICE_RECORDING_OWNER_HEARTBEAT_MILLISECONDS = 5e3;
1114
1114
  var VOICE_RECORDING_OWNER_STALE_MILLISECONDS = 3e4;
1115
1115
  var VOICE_RECORDING_CLIENT_MAX_DURATION_SECONDS = 600;
1116
+ var VOICE_RECORDING_RESUMABLE_CLIENT_MAX_DURATION_SECONDS = 8 * 60 * 60;
1117
+ var VOICE_RECORDING_RECOVERY_STATUS_POLL_MILLISECONDS = 2e3;
1118
+ var VOICE_RECORDING_RECOVERY_MAX_MUTATION_DELAY_MILLISECONDS = 3e4;
1119
+ var TRANSCRIPTION_RECORDING_RECOVERY_RETRY_AFTER_MILLISECONDS = 5e3;
1116
1120
  var MIME_PREFERENCES = ["audio/webm;codecs=opus", "audio/mp4", "audio/ogg;codecs=opus"];
1117
1121
  var createDefaultVoiceRecordingId = () => crypto.randomUUID();
1118
1122
  var currentDate = () => /* @__PURE__ */ new Date();
1123
+ function transcriptionRecoveryMutationDelayMilliseconds(retryAfterMilliseconds, attempt, random = Math.random) {
1124
+ const hint = typeof retryAfterMilliseconds === "number" && Number.isInteger(retryAfterMilliseconds) && retryAfterMilliseconds > 0 ? retryAfterMilliseconds : TRANSCRIPTION_RECORDING_RECOVERY_RETRY_AFTER_MILLISECONDS;
1125
+ const boundedHint = Math.max(
1126
+ 500,
1127
+ Math.min(hint, VOICE_RECORDING_RECOVERY_MAX_MUTATION_DELAY_MILLISECONDS)
1128
+ );
1129
+ const exponent = Math.max(0, Math.min(Math.floor(attempt), 6));
1130
+ const exponential = Math.min(
1131
+ VOICE_RECORDING_RECOVERY_MAX_MUTATION_DELAY_MILLISECONDS,
1132
+ boundedHint * 2 ** exponent
1133
+ );
1134
+ const sampledJitter = random();
1135
+ const jitterRatio = Number.isFinite(sampledJitter) ? Math.max(0, Math.min(1, sampledJitter)) : 0;
1136
+ return Math.min(
1137
+ VOICE_RECORDING_RECOVERY_MAX_MUTATION_DELAY_MILLISECONDS,
1138
+ Math.ceil(exponential * (1 + jitterRatio * 0.2))
1139
+ );
1140
+ }
1119
1141
  function useVoiceInput({
1120
1142
  client,
1121
1143
  workspaceId,
@@ -1337,8 +1359,10 @@ function useVoiceInput({
1337
1359
  const finalizePersistedRecording = useCallback2(
1338
1360
  async (generation) => {
1339
1361
  const manifest = manifestRef.current;
1340
- const maxSizeBytes = capability?.maxSizeBytes;
1341
- if (!manifest || !client || !maxSizeBytes || manifest.workspaceId !== workspaceId) return;
1362
+ const resumable = capability?.resumable && isResumableVoiceInputClient(client) ? capability.resumable : null;
1363
+ const maxSizeBytes = resumable?.maxSizeBytes ?? capability?.maxSizeBytes;
1364
+ if (!manifest || !client || !capability || !maxSizeBytes || manifest.workspaceId !== workspaceId)
1365
+ return;
1342
1366
  const controller = new AbortController();
1343
1367
  controllerRef.current?.abort();
1344
1368
  controllerRef.current = controller;
@@ -1378,16 +1402,12 @@ function useVoiceInput({
1378
1402
  await preserveForRetry(retained, "invalid_audio", generation);
1379
1403
  return;
1380
1404
  }
1381
- const audio = new Blob(
1382
- chunks.sort((left, right) => left.chunkNumber - right.chunkNumber).map((chunk) => chunk.audio),
1383
- { type: transcribing.mimeType }
1384
- );
1385
- if (!active()) return;
1386
- if (audio.size > maxSizeBytes) throw { code: "too_large" };
1387
- const response = await client.transcribeAudio(workspaceId, {
1388
- audio,
1389
- mimeType: audio.type,
1390
- durationSeconds: transcribing.totalDurationMilliseconds / 1e3,
1405
+ const response = await transcribePersistedRecording({
1406
+ client,
1407
+ workspaceId,
1408
+ capability,
1409
+ manifest: transcribing,
1410
+ chunks,
1391
1411
  signal: controller.signal
1392
1412
  });
1393
1413
  if (!active()) return;
@@ -1428,6 +1448,12 @@ function useVoiceInput({
1428
1448
  }
1429
1449
  if (!active()) return;
1430
1450
  rememberManifest(handedOff);
1451
+ if (resumable && isResumableVoiceInputClient(client)) {
1452
+ await client.discardTranscriptionRecording(workspaceId, handedOff.recordingId, {
1453
+ signal: controller.signal
1454
+ }).catch(() => void 0);
1455
+ }
1456
+ if (!active()) return;
1431
1457
  await store.discard(handedOff.recordingId, ownerIdRef.current ?? void 0).catch(() => void 0);
1432
1458
  if (!active()) return;
1433
1459
  clearVisibleRecording();
@@ -1452,7 +1478,7 @@ function useVoiceInput({
1452
1478
  }
1453
1479
  },
1454
1480
  [
1455
- capability?.maxSizeBytes,
1481
+ capability,
1456
1482
  clearVisibleRecording,
1457
1483
  client,
1458
1484
  ensureStore,
@@ -1568,7 +1594,8 @@ function useVoiceInput({
1568
1594
  });
1569
1595
  if (!startAttemptIsCurrent()) return;
1570
1596
  rememberManifest(result.manifest);
1571
- if (result.manifest.totalBytes > capability.maxSizeBytes) {
1597
+ const maxSizeBytes = capability.resumable && isResumableVoiceInputClient(client) ? capability.resumable.maxSizeBytes : capability.maxSizeBytes;
1598
+ if (result.manifest.totalBytes > maxSizeBytes) {
1572
1599
  attemptCaptureLimitError = "too_large";
1573
1600
  if (attemptOwnsSharedCapture()) captureLimitErrorRef.current = "too_large";
1574
1601
  if (recorder.state !== "inactive") recorder.stop();
@@ -1627,7 +1654,10 @@ function useVoiceInput({
1627
1654
  setStatus("recording");
1628
1655
  timerRef.current = setTimeout(
1629
1656
  stop,
1630
- Math.min(capability.maxDurationSeconds, VOICE_RECORDING_CLIENT_MAX_DURATION_SECONDS) * 1e3
1657
+ Math.min(
1658
+ capability.resumable && isResumableVoiceInputClient(client) ? capability.resumable.maxDurationSeconds : capability.maxDurationSeconds,
1659
+ capability.resumable && isResumableVoiceInputClient(client) ? VOICE_RECORDING_RESUMABLE_CLIENT_MAX_DURATION_SECONDS : VOICE_RECORDING_CLIENT_MAX_DURATION_SECONDS
1660
+ ) * 1e3
1631
1661
  );
1632
1662
  return true;
1633
1663
  } catch (reason) {
@@ -1728,6 +1758,10 @@ function useVoiceInput({
1728
1758
  return;
1729
1759
  }
1730
1760
  if (generation !== generationRef.current) return;
1761
+ if (capability?.resumable && isResumableVoiceInputClient(client)) {
1762
+ await client.discardTranscriptionRecording(workspaceId, handedOff.recordingId).catch(() => void 0);
1763
+ }
1764
+ if (generation !== generationRef.current) return;
1731
1765
  await store.discard(handedOff.recordingId, ownerIdRef.current ?? void 0).catch(() => void 0);
1732
1766
  if (generation !== generationRef.current) return;
1733
1767
  clearVisibleRecording();
@@ -1737,6 +1771,8 @@ function useVoiceInput({
1737
1771
  await loadNextRecoverable(generation);
1738
1772
  }, [
1739
1773
  clearVisibleRecording,
1774
+ capability?.resumable,
1775
+ client,
1740
1776
  ensureStore,
1741
1777
  focusInput,
1742
1778
  loadNextRecoverable,
@@ -1760,6 +1796,10 @@ function useVoiceInput({
1760
1796
  await captureSettled.catch(() => void 0);
1761
1797
  if (generation !== generationRef.current) return;
1762
1798
  if (manifest) {
1799
+ if (capability?.resumable && isResumableVoiceInputClient(client)) {
1800
+ await client.discardTranscriptionRecording(workspaceId, manifest.recordingId).catch(() => void 0);
1801
+ }
1802
+ if (generation !== generationRef.current) return;
1763
1803
  try {
1764
1804
  await (await ensureStore()).discard(manifest.recordingId, ownerIdRef.current ?? void 0);
1765
1805
  } catch {
@@ -1781,10 +1821,13 @@ function useVoiceInput({
1781
1821
  }, [
1782
1822
  clearCaptureRuntime,
1783
1823
  clearVisibleRecording,
1824
+ capability?.resumable,
1825
+ client,
1784
1826
  ensureStore,
1785
1827
  focusInput,
1786
1828
  loadNextRecoverable,
1787
- rememberManifest
1829
+ rememberManifest,
1830
+ workspaceId
1788
1831
  ]);
1789
1832
  const cancel = useCallback2(() => {
1790
1833
  if (status === "recording" || status === "requesting-permission") {
@@ -1924,6 +1967,184 @@ function useVoiceInput({
1924
1967
  discard
1925
1968
  };
1926
1969
  }
1970
+ async function transcribePersistedRecording(input) {
1971
+ const client = input.client;
1972
+ const resumable = input.capability.resumable;
1973
+ const ordered = [...input.chunks].sort((left, right) => left.chunkNumber - right.chunkNumber);
1974
+ if (!resumable || !isResumableVoiceInputClient(client)) {
1975
+ const audio = new Blob(
1976
+ ordered.map((chunk) => chunk.audio),
1977
+ { type: input.manifest.mimeType }
1978
+ );
1979
+ if (audio.size > input.capability.maxSizeBytes) throw { code: "too_large" };
1980
+ return await client.transcribeAudio(input.workspaceId, {
1981
+ audio,
1982
+ mimeType: audio.type,
1983
+ durationSeconds: input.manifest.totalDurationMilliseconds / 1e3,
1984
+ signal: input.signal
1985
+ });
1986
+ }
1987
+ if (input.manifest.totalBytes > resumable.maxSizeBytes || input.manifest.totalDurationMilliseconds > resumable.maxDurationSeconds * 1e3) {
1988
+ throw { code: "too_large" };
1989
+ }
1990
+ if (ordered.some((chunk, index) => chunk.chunkNumber !== index) || ordered.length !== input.manifest.chunkCount) {
1991
+ throw { code: "invalid_audio" };
1992
+ }
1993
+ const finalizeInput = {
1994
+ chunkCount: input.manifest.chunkCount,
1995
+ totalBytes: input.manifest.totalBytes,
1996
+ totalDurationMilliseconds: input.manifest.totalDurationMilliseconds,
1997
+ signal: input.signal
1998
+ };
1999
+ let remote = await client.createTranscriptionRecording(input.workspaceId, {
2000
+ recordingId: input.manifest.recordingId,
2001
+ mimeType: input.manifest.mimeType,
2002
+ signal: input.signal
2003
+ });
2004
+ for (const chunk of ordered) {
2005
+ if (chunk.chunkNumber < remote.recording.nextChunkNumber) continue;
2006
+ if (chunk.chunkNumber !== remote.recording.nextChunkNumber) {
2007
+ throw { code: "invalid_audio" };
2008
+ }
2009
+ if (chunk.byteLength > resumable.maxChunkSizeBytes) throw { code: "too_large" };
2010
+ const uploaded = await client.uploadTranscriptionRecordingChunk(
2011
+ input.workspaceId,
2012
+ input.manifest.recordingId,
2013
+ chunk.chunkNumber,
2014
+ {
2015
+ audio: chunk.audio,
2016
+ mimeType: input.manifest.mimeType,
2017
+ sha256: chunk.sha256,
2018
+ startMilliseconds: chunk.startMilliseconds,
2019
+ durationMilliseconds: chunk.durationMilliseconds,
2020
+ signal: input.signal
2021
+ }
2022
+ );
2023
+ remote = { recording: uploaded.recording, segments: remote.segments };
2024
+ }
2025
+ if (remote.recording.nextChunkNumber !== ordered.length) {
2026
+ throw { code: "invalid_audio" };
2027
+ }
2028
+ remote = await client.finalizeTranscriptionRecording(
2029
+ input.workspaceId,
2030
+ input.manifest.recordingId,
2031
+ finalizeInput
2032
+ );
2033
+ let recoveryMutationAttempt = 0;
2034
+ let recoveryMutationDueAt = 0;
2035
+ const scheduleRecoveryMutation = (response) => {
2036
+ if (response.recording.state !== "segmenting" && response.recording.state !== "transcribing") {
2037
+ return;
2038
+ }
2039
+ recoveryMutationDueAt = Date.now() + transcriptionRecoveryMutationDelayMilliseconds(
2040
+ response.retryAfterMilliseconds,
2041
+ recoveryMutationAttempt
2042
+ );
2043
+ recoveryMutationAttempt += 1;
2044
+ };
2045
+ scheduleRecoveryMutation(remote);
2046
+ for (; ; ) {
2047
+ if (input.signal.aborted) throw new DOMException("Aborted", "AbortError");
2048
+ switch (remote.recording.state) {
2049
+ case "complete":
2050
+ if (remote.recording.transcriptText === null) throw { code: "invalid_audio" };
2051
+ return {
2052
+ text: remote.recording.transcriptText,
2053
+ languages: remote.recording.languages
2054
+ };
2055
+ case "ready":
2056
+ remote = await client.processNextTranscriptionRecordingSegment(
2057
+ input.workspaceId,
2058
+ input.manifest.recordingId,
2059
+ { signal: input.signal }
2060
+ );
2061
+ scheduleRecoveryMutation(remote);
2062
+ break;
2063
+ case "failed": {
2064
+ if (!remote.recording.retryable) {
2065
+ throw { code: remote.recording.errorCode ?? "unknown" };
2066
+ }
2067
+ const retried = remote.recording.segmentCount === 0 ? await client.finalizeTranscriptionRecording(
2068
+ input.workspaceId,
2069
+ input.manifest.recordingId,
2070
+ finalizeInput
2071
+ ) : await client.processNextTranscriptionRecordingSegment(
2072
+ input.workspaceId,
2073
+ input.manifest.recordingId,
2074
+ { signal: input.signal }
2075
+ );
2076
+ if (retried.recording.state === "failed") {
2077
+ throw { code: retried.recording.errorCode ?? "unknown" };
2078
+ }
2079
+ remote = retried;
2080
+ scheduleRecoveryMutation(remote);
2081
+ break;
2082
+ }
2083
+ case "discarded":
2084
+ throw { code: "invalid_audio" };
2085
+ case "uploading":
2086
+ remote = await client.finalizeTranscriptionRecording(
2087
+ input.workspaceId,
2088
+ input.manifest.recordingId,
2089
+ finalizeInput
2090
+ );
2091
+ scheduleRecoveryMutation(remote);
2092
+ break;
2093
+ case "segmenting":
2094
+ case "transcribing":
2095
+ if (Date.now() < recoveryMutationDueAt) {
2096
+ await abortableDelay(
2097
+ Math.min(
2098
+ recoveryMutationDueAt - Date.now(),
2099
+ VOICE_RECORDING_RECOVERY_STATUS_POLL_MILLISECONDS
2100
+ ),
2101
+ input.signal
2102
+ );
2103
+ remote = await client.getTranscriptionRecording(
2104
+ input.workspaceId,
2105
+ input.manifest.recordingId,
2106
+ { signal: input.signal }
2107
+ );
2108
+ break;
2109
+ }
2110
+ if (remote.recording.state === "segmenting") {
2111
+ remote = await client.finalizeTranscriptionRecording(
2112
+ input.workspaceId,
2113
+ input.manifest.recordingId,
2114
+ finalizeInput
2115
+ );
2116
+ } else {
2117
+ remote = await client.processNextTranscriptionRecordingSegment(
2118
+ input.workspaceId,
2119
+ input.manifest.recordingId,
2120
+ { signal: input.signal }
2121
+ );
2122
+ }
2123
+ scheduleRecoveryMutation(remote);
2124
+ break;
2125
+ }
2126
+ }
2127
+ }
2128
+ function isResumableVoiceInputClient(client) {
2129
+ return Boolean(
2130
+ client && client.createTranscriptionRecording && client.getTranscriptionRecording && client.uploadTranscriptionRecordingChunk && client.finalizeTranscriptionRecording && client.processNextTranscriptionRecordingSegment && client.discardTranscriptionRecording
2131
+ );
2132
+ }
2133
+ async function abortableDelay(milliseconds, signal) {
2134
+ if (signal.aborted) throw new DOMException("Aborted", "AbortError");
2135
+ await new Promise((resolve, reject) => {
2136
+ const timer = setTimeout(() => {
2137
+ signal.removeEventListener("abort", onAbort);
2138
+ resolve();
2139
+ }, milliseconds);
2140
+ const onAbort = () => {
2141
+ clearTimeout(timer);
2142
+ signal.removeEventListener("abort", onAbort);
2143
+ reject(new DOMException("Aborted", "AbortError"));
2144
+ };
2145
+ signal.addEventListener("abort", onAbort, { once: true });
2146
+ });
2147
+ }
1927
2148
  function chooseMimeType(accepted) {
1928
2149
  const normalized = accepted.map((value) => value.trim().toLowerCase());
1929
2150
  return MIME_PREFERENCES.find((mimeType) => {
@@ -2499,7 +2720,7 @@ function ModelPicker({
2499
2720
  disabled: disabled === true,
2500
2721
  "aria-label": label,
2501
2722
  className: cn(
2502
- "h-8 max-w-[180px] cursor-pointer appearance-none truncate rounded-og-md bg-transparent",
2723
+ "h-8 max-w-[180px] cursor-pointer appearance-none truncate rounded-og-md bg-transparent pointer-coarse:h-11",
2503
2724
  "py-0 pl-2 pr-6 text-[13px] text-og-fg-muted",
2504
2725
  "transition-colors duration-150 hover:bg-og-surface-2 hover:text-og-fg",
2505
2726
  "focus:outline-none focus-visible:outline-none",
@@ -3163,7 +3384,7 @@ var Input = forwardRef(function ComposerInput({ rows = 1, placeholder, className
3163
3384
  "aria-controls": paletteOpen ? controller.listboxId : void 0,
3164
3385
  "aria-activedescendant": paletteOpen ? `${controller.listboxId}-option-${controller.palette.highlight}` : void 0,
3165
3386
  className: cn(
3166
- "block w-full resize-none bg-transparent px-3.5 pt-3 pb-1 text-og-composer md:px-4 md:text-og-composer-wide",
3387
+ "block w-full resize-none bg-transparent px-3.5 pt-3 pb-1 text-og-composer md:px-4 md:text-og-composer-wide pointer-coarse:min-h-11",
3167
3388
  "text-og-fg placeholder:text-og-fg-subtle focus:outline-none focus-visible:outline-none",
3168
3389
  "disabled:cursor-not-allowed disabled:opacity-60",
3169
3390
  className
@@ -3273,7 +3494,7 @@ var AttachButton = forwardRef(
3273
3494
  className: cn(
3274
3495
  "inline-flex size-8 items-center justify-center rounded-og-md",
3275
3496
  "text-og-fg-muted transition-colors duration-150 hover:bg-og-surface-2 hover:text-og-fg",
3276
- "disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:size-9",
3497
+ "disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:size-11",
3277
3498
  className
3278
3499
  ),
3279
3500
  children: children ?? /* @__PURE__ */ jsx3(PaperclipIcon, { className: "size-4" })
@@ -3322,7 +3543,7 @@ var PauseButton = forwardRef(
3322
3543
  disabled: busy,
3323
3544
  "aria-label": ariaLabel ?? controller.messages.pauseAriaLabel,
3324
3545
  className: cn(
3325
- "inline-flex size-8 items-center justify-center rounded-og-md border border-og-border pointer-coarse:size-9",
3546
+ "inline-flex size-8 items-center justify-center rounded-og-md border border-og-border pointer-coarse:size-11",
3326
3547
  "bg-og-surface-2 text-og-fg-muted transition-colors duration-150",
3327
3548
  "hover:border-og-status-waiting/50 hover:text-og-status-waiting",
3328
3549
  "disabled:opacity-50",
@@ -3348,7 +3569,7 @@ var SendButton = forwardRef(
3348
3569
  "data-og-tip": tip,
3349
3570
  "aria-label": ariaLabel ?? (controller.paused ? controller.messages.sendAndResumeAriaLabel : controller.messages.sendMessageAriaLabel),
3350
3571
  className: cn(
3351
- "inline-flex size-8 items-center justify-center rounded-og-md pointer-coarse:size-9",
3572
+ "inline-flex size-8 items-center justify-center rounded-og-md pointer-coarse:size-11",
3352
3573
  "bg-og-accent text-og-accent-fg shadow-og-sm",
3353
3574
  "transition-[background-color,transform,opacity] duration-150 ease-og-spring",
3354
3575
  "hover:bg-og-accent-strong active:scale-95",
@@ -4162,4 +4383,4 @@ export {
4162
4383
  Status,
4163
4384
  ComposerTranscriptionControl
4164
4385
  };
4165
- //# sourceMappingURL=chunk-L3EWO3UK.js.map
4386
+ //# sourceMappingURL=chunk-3PB7MIT6.js.map