@opengeni/react 0.46.1 → 0.48.0
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/README.md +68 -12
- package/dist/{chunk-U3CSCMWA.js → chunk-FK6VG4LL.js} +9 -13
- package/dist/chunk-FK6VG4LL.js.map +1 -0
- package/dist/{chunk-HFO4ERGQ.js → chunk-FWXGSNXT.js} +2 -2
- package/dist/{chunk-SRFUT2ZU.js → chunk-I4MPFDRP.js} +119 -63
- package/dist/chunk-I4MPFDRP.js.map +1 -0
- package/dist/{chunk-EB67J347.js → chunk-KZ73RL76.js} +111 -31
- package/dist/chunk-KZ73RL76.js.map +1 -0
- package/dist/{chunk-LWR4MXSS.js → chunk-LHLYWG5R.js} +10 -1
- package/dist/chunk-LHLYWG5R.js.map +1 -0
- package/dist/{chunk-3PB7MIT6.js → chunk-NJ7M2B6Z.js} +207 -67
- package/dist/chunk-NJ7M2B6Z.js.map +1 -0
- package/dist/{chunk-VCBFUSUZ.js → chunk-O4VXOBGF.js} +3 -3
- package/dist/{chunk-VCBFUSUZ.js.map → chunk-O4VXOBGF.js.map} +1 -1
- package/dist/{chunk-6EDV6YFE.js → chunk-OL6QVXGF.js} +121 -29
- package/dist/chunk-OL6QVXGF.js.map +1 -0
- package/dist/chunk-VZTQ73XT.js +86 -0
- package/dist/chunk-VZTQ73XT.js.map +1 -0
- package/dist/{chunk-FGZUCXZF.js → chunk-WYA65Y3F.js} +16 -16
- package/dist/chunk-WYA65Y3F.js.map +1 -0
- package/dist/{chunk-LGVMUIRV.js → chunk-XCNTXWBB.js} +264 -115
- package/dist/chunk-XCNTXWBB.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/components/chat-composer.d.ts +7 -2
- package/dist/components/composer-transcription-control.d.ts +2 -0
- package/dist/components/composer.d.ts +14 -0
- package/dist/components/machines/machine-detail.d.ts +3 -1
- package/dist/components/machines-dashboard.d.ts +3 -3
- package/dist/components/message-timeline.d.ts +12 -2
- package/dist/components/sandbox-files.d.ts +2 -2
- package/dist/components/sandbox-terminal.d.ts +29 -0
- package/dist/components/sandbox-workspace.d.ts +5 -1
- package/dist/components/workspace-dock.d.ts +3 -1
- package/dist/composer.d.ts +2 -2
- package/dist/composer.js +6 -6
- package/dist/hooks/internal.d.ts +6 -0
- package/dist/hooks/use-codex-accounts.d.ts +1 -1
- package/dist/hooks/use-environments.d.ts +10 -4
- package/dist/hooks/use-machines.d.ts +7 -0
- package/dist/hooks/use-sandbox-files.d.ts +19 -0
- package/dist/hooks/use-sandbox-git.d.ts +7 -0
- package/dist/hooks/use-transcription.d.ts +2 -2
- package/dist/hooks/use-voice-input.d.ts +5 -3
- package/dist/index.d.ts +6 -5
- package/dist/index.js +904 -538
- package/dist/index.js.map +1 -1
- package/dist/lib/composer-responsive-context.d.ts +9 -0
- package/dist/lib/use-portal-token-style.d.ts +14 -3
- package/dist/machines.js +2 -2
- package/dist/{queue-surface-implementation-J4XJFKRW.js → queue-surface-implementation-U5JFG7HT.js} +19 -18
- package/dist/queue-surface-implementation-U5JFG7HT.js.map +1 -0
- package/dist/realtime/realtime-control.d.ts +9 -0
- package/dist/realtime.js +27 -30
- package/dist/realtime.js.map +1 -1
- package/dist/session-ui.js +5 -5
- package/dist/session.js +4 -4
- package/dist/timeline/activity-rail.d.ts +3 -2
- package/dist/timeline/index.d.ts +2 -2
- package/dist/timeline/parsers.d.ts +3 -3
- package/dist/timeline/registry.d.ts +3 -0
- package/dist/timeline/screenshot-lightbox.d.ts +1 -1
- package/dist/timeline/types.d.ts +4 -0
- package/dist/voice-recording-store.d.ts +8 -2
- package/package.json +20 -4
- package/src/client.ts +3 -0
- package/src/components/chat-composer.tsx +12 -2
- package/src/components/code-editor.tsx +1 -1
- package/src/components/command-palette.tsx +1 -1
- package/src/components/composer-transcription-control.tsx +41 -16
- package/src/components/composer.tsx +49 -23
- package/src/components/copy-button.tsx +1 -1
- package/src/components/desktop-viewer.tsx +2 -2
- package/src/components/file-browser.tsx +39 -17
- package/src/components/human-input-form.tsx +2 -2
- package/src/components/machine-card.tsx +1 -1
- package/src/components/machines/machine-detail.tsx +39 -15
- package/src/components/machines-dashboard.tsx +7 -6
- package/src/components/message-timeline.tsx +39 -6
- package/src/components/model-picker.tsx +2 -2
- package/src/components/model-policy-picker.tsx +16 -15
- package/src/components/queue-surface-implementation.tsx +20 -16
- package/src/components/queue-surface-state.tsx +2 -2
- package/src/components/sandbox-files.tsx +140 -60
- package/src/components/sandbox-terminal.tsx +24 -5
- package/src/components/sandbox-workspace.tsx +180 -142
- package/src/components/session-chrome.tsx +14 -14
- package/src/components/tooltip.tsx +13 -16
- package/src/components/user-message-body.tsx +1 -1
- package/src/components/workbench-changes.tsx +4 -4
- package/src/components/workspace-dock.tsx +13 -5
- package/src/composer.ts +3 -0
- package/src/hooks/internal.ts +132 -38
- package/src/hooks/use-codex-accounts.ts +4 -4
- package/src/hooks/use-environments.ts +24 -5
- package/src/hooks/use-file-attachments.ts +70 -32
- package/src/hooks/use-goal.ts +17 -5
- package/src/hooks/use-machines.ts +47 -1
- package/src/hooks/use-sandbox-files.ts +310 -59
- package/src/hooks/use-sandbox-git.ts +148 -41
- package/src/hooks/use-session-capabilities.ts +35 -24
- package/src/hooks/use-session-events.ts +18 -4
- package/src/hooks/use-transcription.ts +4 -14
- package/src/hooks/use-turn-queue.ts +17 -5
- package/src/hooks/use-voice-input.ts +163 -19
- package/src/hooks/use-windowed-sections.ts +9 -7
- package/src/index.ts +11 -6
- package/src/lib/composer-responsive-context.ts +15 -0
- package/src/lib/use-portal-token-style.ts +60 -15
- package/src/provider.tsx +52 -7
- package/src/realtime/dropdown-menu.tsx +15 -19
- package/src/realtime/realtime-control.tsx +16 -5
- package/src/timeline/activity-rail.tsx +14 -5
- package/src/timeline/index.ts +1 -1
- package/src/timeline/parsers.ts +33 -20
- package/src/timeline/projection.ts +23 -0
- package/src/timeline/registry.ts +7 -0
- package/src/timeline/screenshot-lightbox.tsx +15 -4
- package/src/timeline/shared.tsx +5 -5
- package/src/timeline/tool-renderers.tsx +166 -22
- package/src/timeline/turn-summary.tsx +2 -2
- package/src/timeline/types.ts +4 -0
- package/src/voice-recording-store.ts +15 -0
- package/styles/compiled.css +5065 -0
- package/styles/compiled.d.ts +2 -0
- package/styles/effective-tokens.css +27 -0
- package/styles/index.css +3 -0
- package/styles/responsive.css +196 -0
- package/styles/responsive.d.ts +2 -0
- package/styles/tokens.css +50 -17
- package/dist/chunk-3PB7MIT6.js.map +0 -1
- package/dist/chunk-6EDV6YFE.js.map +0 -1
- package/dist/chunk-EB67J347.js.map +0 -1
- package/dist/chunk-FGZUCXZF.js.map +0 -1
- package/dist/chunk-JB5725BI.js +0 -48
- package/dist/chunk-JB5725BI.js.map +0 -1
- package/dist/chunk-LGVMUIRV.js.map +0 -1
- package/dist/chunk-LWR4MXSS.js.map +0 -1
- package/dist/chunk-SRFUT2ZU.js.map +0 -1
- package/dist/chunk-U3CSCMWA.js.map +0 -1
- package/dist/queue-surface-implementation-J4XJFKRW.js.map +0 -1
- /package/dist/{chunk-HFO4ERGQ.js.map → chunk-FWXGSNXT.js.map} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { OpenGeniApiError, type ClientVoiceInputConfig, type OpenGeniClient } from "@opengeni/sdk";
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import {
|
|
4
4
|
IndexedDbVoiceRecordingStore,
|
|
@@ -21,6 +21,7 @@ export type VoiceInputStatus =
|
|
|
21
21
|
| "recording"
|
|
22
22
|
| "saving"
|
|
23
23
|
| "transcribing"
|
|
24
|
+
| "retrying"
|
|
24
25
|
| "recovered"
|
|
25
26
|
| "transcript-ready"
|
|
26
27
|
| "error";
|
|
@@ -39,6 +40,8 @@ export type UseVoiceInputOptions = {
|
|
|
39
40
|
createRecordingId?: (() => string) | undefined;
|
|
40
41
|
createOwnerId?: (() => string) | undefined;
|
|
41
42
|
now?: (() => Date) | undefined;
|
|
43
|
+
/** Test/embed seam. Production uses bounded exponential recovery backoff. */
|
|
44
|
+
automaticRetryDelayMilliseconds?: number | undefined;
|
|
42
45
|
};
|
|
43
46
|
|
|
44
47
|
type ResumableVoiceInputClient = Pick<
|
|
@@ -86,7 +89,6 @@ export const VOICE_RECORDING_RESUMABLE_CLIENT_MAX_DURATION_SECONDS = 8 * 60 * 60
|
|
|
86
89
|
export const VOICE_RECORDING_RECOVERY_STATUS_POLL_MILLISECONDS = 2_000;
|
|
87
90
|
export const VOICE_RECORDING_RECOVERY_MAX_MUTATION_DELAY_MILLISECONDS = 30_000;
|
|
88
91
|
const TRANSCRIPTION_RECORDING_RECOVERY_RETRY_AFTER_MILLISECONDS = 5_000;
|
|
89
|
-
|
|
90
92
|
const MIME_PREFERENCES = ["audio/webm;codecs=opus", "audio/mp4", "audio/ogg;codecs=opus"];
|
|
91
93
|
const createDefaultVoiceRecordingId = () => crypto.randomUUID();
|
|
92
94
|
const currentDate = () => new Date();
|
|
@@ -132,6 +134,7 @@ export function useVoiceInput({
|
|
|
132
134
|
createRecordingId = createDefaultVoiceRecordingId,
|
|
133
135
|
createOwnerId,
|
|
134
136
|
now = currentDate,
|
|
137
|
+
automaticRetryDelayMilliseconds,
|
|
135
138
|
}: UseVoiceInputOptions): UseVoiceInputResult {
|
|
136
139
|
const [status, setStatus] = useState<VoiceInputStatus>("idle");
|
|
137
140
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -156,6 +159,8 @@ export function useVoiceInput({
|
|
|
156
159
|
const recorderRef = useRef<MediaRecorder | null>(null);
|
|
157
160
|
const streamRef = useRef<MediaStream | null>(null);
|
|
158
161
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
162
|
+
const automaticRetryTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
163
|
+
const automaticRetryAttemptRef = useRef(0);
|
|
159
164
|
const ownerHeartbeatTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
160
165
|
const controllerRef = useRef<AbortController | null>(null);
|
|
161
166
|
const valueRef = useRef(value);
|
|
@@ -211,6 +216,7 @@ export function useVoiceInput({
|
|
|
211
216
|
setRecordingId(null);
|
|
212
217
|
setDurationSeconds(0);
|
|
213
218
|
setLocallySaved(false);
|
|
219
|
+
automaticRetryAttemptRef.current = 0;
|
|
214
220
|
}, [stopOwnerHeartbeat]);
|
|
215
221
|
|
|
216
222
|
const ensureStore = useCallback(async (): Promise<VoiceRecordingStore> => {
|
|
@@ -348,7 +354,9 @@ export function useVoiceInput({
|
|
|
348
354
|
setStatus(
|
|
349
355
|
claimed.finalizationState === "transcript-ready" && claimed.transcriptText !== null
|
|
350
356
|
? "transcript-ready"
|
|
351
|
-
: "
|
|
357
|
+
: claimed.recoveryMode === "automatic"
|
|
358
|
+
? "retrying"
|
|
359
|
+
: "recovered",
|
|
352
360
|
);
|
|
353
361
|
setError(null);
|
|
354
362
|
return;
|
|
@@ -415,7 +423,12 @@ export function useVoiceInput({
|
|
|
415
423
|
if (chunks.length === 0) {
|
|
416
424
|
const retained = await store.updateManifest(
|
|
417
425
|
transcribing.recordingId,
|
|
418
|
-
{
|
|
426
|
+
{
|
|
427
|
+
uploadState: "retrying",
|
|
428
|
+
transcriptionState: "retrying",
|
|
429
|
+
recoveryMode: "manual",
|
|
430
|
+
handoffMode: "explicit",
|
|
431
|
+
},
|
|
419
432
|
readNow().toISOString(),
|
|
420
433
|
ownerIdRef.current ?? undefined,
|
|
421
434
|
);
|
|
@@ -447,6 +460,14 @@ export function useVoiceInput({
|
|
|
447
460
|
if (!active()) return;
|
|
448
461
|
rememberManifest(ready);
|
|
449
462
|
|
|
463
|
+
automaticRetryAttemptRef.current = 0;
|
|
464
|
+
if (ready.handoffMode === "explicit") {
|
|
465
|
+
setStatus("transcript-ready");
|
|
466
|
+
setError("handoff_uncertain");
|
|
467
|
+
focusInput();
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
450
471
|
const next = appendFinalTranscript(valueRef.current, response.text);
|
|
451
472
|
if (next !== valueRef.current) {
|
|
452
473
|
valueRef.current = next;
|
|
@@ -490,16 +511,22 @@ export function useVoiceInput({
|
|
|
490
511
|
await loadNextRecoverable(generation);
|
|
491
512
|
} catch (reason) {
|
|
492
513
|
if (!active()) return;
|
|
514
|
+
const code = controller.signal.aborted ? null : errorCode(reason);
|
|
515
|
+
const retryable = !controller.signal.aborted && isRetryableVoiceInputError(reason);
|
|
493
516
|
const retained = await updateManifestBestEffort(transcribing, {
|
|
494
517
|
uploadState: "retrying",
|
|
495
518
|
transcriptionState: "retrying",
|
|
519
|
+
recoveryMode: retryable ? "automatic" : "manual",
|
|
520
|
+
handoffMode: "explicit",
|
|
496
521
|
});
|
|
497
522
|
if (!active()) return;
|
|
498
|
-
|
|
499
|
-
retained
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
523
|
+
if (retryable) {
|
|
524
|
+
rememberManifest(retained);
|
|
525
|
+
setStatus("retrying");
|
|
526
|
+
setError(code);
|
|
527
|
+
} else {
|
|
528
|
+
await preserveForRetry(retained, code, generation);
|
|
529
|
+
}
|
|
503
530
|
} finally {
|
|
504
531
|
if (controllerRef.current === controller) controllerRef.current = null;
|
|
505
532
|
}
|
|
@@ -519,6 +546,61 @@ export function useVoiceInput({
|
|
|
519
546
|
workspaceId,
|
|
520
547
|
],
|
|
521
548
|
);
|
|
549
|
+
// Live session renders must not postpone a durable retry deadline. Execute the
|
|
550
|
+
// latest finalizer without making its callback identity part of the timer key.
|
|
551
|
+
const finalizePersistedRecordingRef = useRef(finalizePersistedRecording);
|
|
552
|
+
finalizePersistedRecordingRef.current = finalizePersistedRecording;
|
|
553
|
+
const clientAvailable = client !== null && client !== undefined;
|
|
554
|
+
|
|
555
|
+
useEffect(() => {
|
|
556
|
+
if (
|
|
557
|
+
status !== "retrying" ||
|
|
558
|
+
!manifestRef.current ||
|
|
559
|
+
manifestRef.current.recoveryMode !== "automatic" ||
|
|
560
|
+
manifestRef.current.workspaceId !== workspaceId ||
|
|
561
|
+
!clientAvailable ||
|
|
562
|
+
disabled ||
|
|
563
|
+
!enabled
|
|
564
|
+
) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const generation = generationRef.current;
|
|
568
|
+
let started = false;
|
|
569
|
+
const run = () => {
|
|
570
|
+
if (
|
|
571
|
+
started ||
|
|
572
|
+
generation !== generationRef.current ||
|
|
573
|
+
statusRef.current !== "retrying" ||
|
|
574
|
+
workspaceIdRef.current !== workspaceId
|
|
575
|
+
) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
started = true;
|
|
579
|
+
if (automaticRetryTimerRef.current) clearTimeout(automaticRetryTimerRef.current);
|
|
580
|
+
automaticRetryTimerRef.current = null;
|
|
581
|
+
automaticRetryAttemptRef.current += 1;
|
|
582
|
+
void finalizePersistedRecordingRef.current(generation);
|
|
583
|
+
};
|
|
584
|
+
const onOnline = () => run();
|
|
585
|
+
globalThis.addEventListener?.("online", onOnline);
|
|
586
|
+
if (typeof navigator !== "undefined" && navigator.onLine === false) {
|
|
587
|
+
return () => globalThis.removeEventListener?.("online", onOnline);
|
|
588
|
+
}
|
|
589
|
+
const delay = Math.max(
|
|
590
|
+
0,
|
|
591
|
+
automaticRetryDelayMilliseconds ??
|
|
592
|
+
transcriptionRecoveryMutationDelayMilliseconds(
|
|
593
|
+
TRANSCRIPTION_RECORDING_RECOVERY_RETRY_AFTER_MILLISECONDS,
|
|
594
|
+
automaticRetryAttemptRef.current,
|
|
595
|
+
),
|
|
596
|
+
);
|
|
597
|
+
automaticRetryTimerRef.current = setTimeout(run, delay);
|
|
598
|
+
return () => {
|
|
599
|
+
globalThis.removeEventListener?.("online", onOnline);
|
|
600
|
+
if (automaticRetryTimerRef.current) clearTimeout(automaticRetryTimerRef.current);
|
|
601
|
+
automaticRetryTimerRef.current = null;
|
|
602
|
+
};
|
|
603
|
+
}, [automaticRetryDelayMilliseconds, clientAvailable, disabled, enabled, status, workspaceId]);
|
|
522
604
|
|
|
523
605
|
const stop = useCallback(() => {
|
|
524
606
|
const recorder = recorderRef.current;
|
|
@@ -540,6 +622,7 @@ export function useVoiceInput({
|
|
|
540
622
|
status === "recording" ||
|
|
541
623
|
status === "saving" ||
|
|
542
624
|
status === "transcribing" ||
|
|
625
|
+
status === "retrying" ||
|
|
543
626
|
!navigator.mediaDevices?.getUserMedia ||
|
|
544
627
|
typeof MediaRecorder === "undefined"
|
|
545
628
|
) {
|
|
@@ -716,11 +799,19 @@ export function useVoiceInput({
|
|
|
716
799
|
}
|
|
717
800
|
rememberManifest(stopped);
|
|
718
801
|
if (attemptPersistenceError) {
|
|
719
|
-
await
|
|
802
|
+
const retained = await updateManifestBestEffort(stopped, {
|
|
803
|
+
recoveryMode: "manual",
|
|
804
|
+
handoffMode: "explicit",
|
|
805
|
+
});
|
|
806
|
+
await preserveForRetry(retained, "storage_unavailable", generation);
|
|
720
807
|
return;
|
|
721
808
|
}
|
|
722
809
|
if (attemptCaptureLimitError) {
|
|
723
|
-
await
|
|
810
|
+
const retained = await updateManifestBestEffort(stopped, {
|
|
811
|
+
recoveryMode: "manual",
|
|
812
|
+
handoffMode: "explicit",
|
|
813
|
+
});
|
|
814
|
+
await preserveForRetry(retained, attemptCaptureLimitError, generation);
|
|
724
815
|
return;
|
|
725
816
|
}
|
|
726
817
|
await finalizePersistedRecording(generation);
|
|
@@ -826,9 +917,29 @@ export function useVoiceInput({
|
|
|
826
917
|
}
|
|
827
918
|
const generation = ++generationRef.current;
|
|
828
919
|
controllerRef.current?.abort();
|
|
920
|
+
if (automaticRetryTimerRef.current) clearTimeout(automaticRetryTimerRef.current);
|
|
921
|
+
automaticRetryTimerRef.current = null;
|
|
922
|
+
automaticRetryAttemptRef.current = 0;
|
|
829
923
|
setError(null);
|
|
830
|
-
|
|
831
|
-
|
|
924
|
+
const manifest = manifestRef.current;
|
|
925
|
+
void updateManifestBestEffort(manifest, {
|
|
926
|
+
recoveryMode: "automatic",
|
|
927
|
+
handoffMode: "append",
|
|
928
|
+
}).then((retained) => {
|
|
929
|
+
if (generation !== generationRef.current) return;
|
|
930
|
+
rememberManifest(retained);
|
|
931
|
+
void finalizePersistedRecording(generation);
|
|
932
|
+
});
|
|
933
|
+
}, [
|
|
934
|
+
client,
|
|
935
|
+
disabled,
|
|
936
|
+
enabled,
|
|
937
|
+
finalizePersistedRecording,
|
|
938
|
+
rememberManifest,
|
|
939
|
+
status,
|
|
940
|
+
updateManifestBestEffort,
|
|
941
|
+
workspaceId,
|
|
942
|
+
]);
|
|
832
943
|
|
|
833
944
|
const insertSavedTranscript = useCallback(async (): Promise<void> => {
|
|
834
945
|
const manifest = manifestRef.current;
|
|
@@ -951,10 +1062,12 @@ export function useVoiceInput({
|
|
|
951
1062
|
void discard();
|
|
952
1063
|
return;
|
|
953
1064
|
}
|
|
954
|
-
if (status === "saving" || status === "transcribing") {
|
|
1065
|
+
if (status === "saving" || status === "transcribing" || status === "retrying") {
|
|
955
1066
|
const generation = ++generationRef.current;
|
|
956
1067
|
controllerRef.current?.abort();
|
|
957
1068
|
controllerRef.current = null;
|
|
1069
|
+
if (automaticRetryTimerRef.current) clearTimeout(automaticRetryTimerRef.current);
|
|
1070
|
+
automaticRetryTimerRef.current = null;
|
|
958
1071
|
const captureSettled = status === "saving" ? captureSettledRef.current : Promise.resolve();
|
|
959
1072
|
void captureSettled.then(async () => {
|
|
960
1073
|
const manifest = manifestRef.current;
|
|
@@ -963,6 +1076,8 @@ export function useVoiceInput({
|
|
|
963
1076
|
captureState: "stopped",
|
|
964
1077
|
uploadState: "retrying",
|
|
965
1078
|
transcriptionState: "retrying",
|
|
1079
|
+
recoveryMode: "manual",
|
|
1080
|
+
handoffMode: "explicit",
|
|
966
1081
|
});
|
|
967
1082
|
await preserveForRetry(retained, null, generation);
|
|
968
1083
|
});
|
|
@@ -996,7 +1111,12 @@ export function useVoiceInput({
|
|
|
996
1111
|
{
|
|
997
1112
|
...(current.captureState === "capturing" ? { captureState: "stopped" as const } : {}),
|
|
998
1113
|
...(wasProcessing
|
|
999
|
-
? {
|
|
1114
|
+
? {
|
|
1115
|
+
uploadState: "retrying" as const,
|
|
1116
|
+
transcriptionState: "retrying" as const,
|
|
1117
|
+
recoveryMode: "automatic" as const,
|
|
1118
|
+
handoffMode: "explicit" as const,
|
|
1119
|
+
}
|
|
1000
1120
|
: {}),
|
|
1001
1121
|
ownerId: null,
|
|
1002
1122
|
ownerHeartbeatAt: null,
|
|
@@ -1028,7 +1148,8 @@ export function useVoiceInput({
|
|
|
1028
1148
|
(status === "requesting-permission" ||
|
|
1029
1149
|
status === "recording" ||
|
|
1030
1150
|
status === "saving" ||
|
|
1031
|
-
status === "transcribing"
|
|
1151
|
+
status === "transcribing" ||
|
|
1152
|
+
status === "retrying")
|
|
1032
1153
|
) {
|
|
1033
1154
|
event.preventDefault();
|
|
1034
1155
|
cancel();
|
|
@@ -1043,6 +1164,8 @@ export function useVoiceInput({
|
|
|
1043
1164
|
generationRef.current += 1;
|
|
1044
1165
|
controllerRef.current?.abort();
|
|
1045
1166
|
controllerRef.current = null;
|
|
1167
|
+
if (automaticRetryTimerRef.current) clearTimeout(automaticRetryTimerRef.current);
|
|
1168
|
+
automaticRetryTimerRef.current = null;
|
|
1046
1169
|
const manifest = manifestRef.current;
|
|
1047
1170
|
const ownerReady = ownerIdPromiseRef.current;
|
|
1048
1171
|
stopOwnerHeartbeat();
|
|
@@ -1061,7 +1184,9 @@ export function useVoiceInput({
|
|
|
1061
1184
|
const store = storeRef.current;
|
|
1062
1185
|
if (store && manifest && ownerId) {
|
|
1063
1186
|
const wasProcessing =
|
|
1064
|
-
statusRef.current === "saving" ||
|
|
1187
|
+
statusRef.current === "saving" ||
|
|
1188
|
+
statusRef.current === "transcribing" ||
|
|
1189
|
+
statusRef.current === "retrying";
|
|
1065
1190
|
await store
|
|
1066
1191
|
.updateManifest(
|
|
1067
1192
|
manifest.recordingId,
|
|
@@ -1073,6 +1198,8 @@ export function useVoiceInput({
|
|
|
1073
1198
|
? {
|
|
1074
1199
|
uploadState: "retrying" as const,
|
|
1075
1200
|
transcriptionState: "retrying" as const,
|
|
1201
|
+
recoveryMode: "automatic" as const,
|
|
1202
|
+
handoffMode: "explicit" as const,
|
|
1076
1203
|
}
|
|
1077
1204
|
: {}),
|
|
1078
1205
|
ownerId: null,
|
|
@@ -1232,7 +1359,7 @@ async function transcribePersistedRecording(input: {
|
|
|
1232
1359
|
break;
|
|
1233
1360
|
case "failed": {
|
|
1234
1361
|
if (!remote.recording.retryable) {
|
|
1235
|
-
throw { code: remote.recording.errorCode ?? "unknown" };
|
|
1362
|
+
throw { code: remote.recording.errorCode ?? "unknown", retryable: false };
|
|
1236
1363
|
}
|
|
1237
1364
|
const retried =
|
|
1238
1365
|
remote.recording.segmentCount === 0
|
|
@@ -1247,7 +1374,10 @@ async function transcribePersistedRecording(input: {
|
|
|
1247
1374
|
{ signal: input.signal },
|
|
1248
1375
|
);
|
|
1249
1376
|
if (retried.recording.state === "failed") {
|
|
1250
|
-
throw {
|
|
1377
|
+
throw {
|
|
1378
|
+
code: retried.recording.errorCode ?? "unknown",
|
|
1379
|
+
retryable: retried.recording.retryable,
|
|
1380
|
+
};
|
|
1251
1381
|
}
|
|
1252
1382
|
remote = retried;
|
|
1253
1383
|
scheduleRecoveryMutation(remote);
|
|
@@ -1350,6 +1480,9 @@ function chooseMimeType(accepted: string[]): string | undefined {
|
|
|
1350
1480
|
}
|
|
1351
1481
|
|
|
1352
1482
|
function errorCode(error: unknown): string {
|
|
1483
|
+
if (error instanceof OpenGeniApiError && typeof error.details?.transcriptionCode === "string") {
|
|
1484
|
+
return error.details.transcriptionCode;
|
|
1485
|
+
}
|
|
1353
1486
|
if (error && typeof error === "object" && "code" in error && typeof error.code === "string") {
|
|
1354
1487
|
return error.code;
|
|
1355
1488
|
}
|
|
@@ -1357,3 +1490,14 @@ function errorCode(error: unknown): string {
|
|
|
1357
1490
|
if (error instanceof DOMException && error.name === "NotSupportedError") return "not_supported";
|
|
1358
1491
|
return "unknown";
|
|
1359
1492
|
}
|
|
1493
|
+
|
|
1494
|
+
function isRetryableVoiceInputError(error: unknown): boolean {
|
|
1495
|
+
if (error instanceof OpenGeniApiError) {
|
|
1496
|
+
return error.retryable && error.status !== 409;
|
|
1497
|
+
}
|
|
1498
|
+
if (error instanceof TypeError) return true;
|
|
1499
|
+
if (error && typeof error === "object" && "retryable" in error) {
|
|
1500
|
+
return error.retryable === true;
|
|
1501
|
+
}
|
|
1502
|
+
return false;
|
|
1503
|
+
}
|
|
@@ -108,16 +108,18 @@ export function useWindowedSections(opts: {
|
|
|
108
108
|
Array.from({ length: count }, (_, i) => estimateHeight(i)),
|
|
109
109
|
);
|
|
110
110
|
|
|
111
|
-
// Re-seed
|
|
112
|
-
//
|
|
111
|
+
// Re-seed when the estimate function changes as well as when the count changes.
|
|
112
|
+
// A replacement diff can contain the same number of files but radically
|
|
113
|
+
// different section sizes; retaining measurements by index would then place
|
|
114
|
+
// every unvisited section at an offset belonging to the previous diff.
|
|
113
115
|
useEffect(() => {
|
|
114
116
|
setHeights((prev) => {
|
|
115
|
-
|
|
116
|
-
return
|
|
117
|
+
const next = Array.from({ length: count }, (_, i) => estimateHeight(i));
|
|
118
|
+
return prev.length === next.length && prev.every((height, index) => height === next[index])
|
|
119
|
+
? prev
|
|
120
|
+
: next;
|
|
117
121
|
});
|
|
118
|
-
|
|
119
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
120
|
-
}, [count]);
|
|
122
|
+
}, [count, estimateHeight]);
|
|
121
123
|
|
|
122
124
|
const heightsRef = useRef(heights);
|
|
123
125
|
heightsRef.current = heights;
|
package/src/index.ts
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
// @opengeni/react — hooks + styled components on @opengeni/sdk.
|
|
5
5
|
//
|
|
6
|
-
// Import the styles once
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// Import the ready-to-use styles once:
|
|
7
|
+
// import "@opengeni/react/compiled.css";
|
|
8
|
+
// Tailwind v4 hosts may instead use the additive styles.css + @source bridge;
|
|
9
|
+
// container-responsive composers additionally import responsive.css.
|
|
9
10
|
|
|
10
11
|
export type {
|
|
11
12
|
EmbeddedFileAttachmentClientLike,
|
|
@@ -22,6 +23,7 @@ export { OpenGeniProvider } from "./provider";
|
|
|
22
23
|
export type { OpenGeniProviderProps } from "./provider";
|
|
23
24
|
export { useOpenGeni, useOpenGeniClient } from "./session-context";
|
|
24
25
|
export type { ClientOverride, OpenGeniContextValue } from "./session-context";
|
|
26
|
+
export { usePageLiveActivity } from "./hooks/internal";
|
|
25
27
|
|
|
26
28
|
// Hooks
|
|
27
29
|
export { useSession, isTitleEvent } from "./hooks/use-session";
|
|
@@ -79,7 +81,9 @@ export type {
|
|
|
79
81
|
VoiceRecordingChunk,
|
|
80
82
|
VoiceRecordingChunkUploadState,
|
|
81
83
|
VoiceRecordingFinalizationState,
|
|
84
|
+
VoiceRecordingHandoffMode,
|
|
82
85
|
VoiceRecordingManifest,
|
|
86
|
+
VoiceRecordingRecoveryMode,
|
|
83
87
|
VoiceRecordingStore,
|
|
84
88
|
VoiceRecordingTranscriptionState,
|
|
85
89
|
VoiceRecordingUploadState,
|
|
@@ -229,12 +233,14 @@ export type {
|
|
|
229
233
|
CapturedFileUnavailableReason,
|
|
230
234
|
FileTreeNode,
|
|
231
235
|
FileTreeStatus,
|
|
236
|
+
SandboxFilesGitSummary,
|
|
232
237
|
SandboxWriteFileOptions,
|
|
233
238
|
UseSandboxFilesOptions,
|
|
234
239
|
UseSandboxFilesResult,
|
|
235
240
|
} from "./hooks/use-sandbox-files";
|
|
236
241
|
export { useSandboxGit } from "./hooks/use-sandbox-git";
|
|
237
242
|
export type {
|
|
243
|
+
SandboxGitComparison,
|
|
238
244
|
SandboxGitFileDiff,
|
|
239
245
|
UseSandboxGitOptions,
|
|
240
246
|
UseSandboxGitResult,
|
|
@@ -364,7 +370,7 @@ export type {
|
|
|
364
370
|
TurnSummaryProps,
|
|
365
371
|
} from "./timeline";
|
|
366
372
|
|
|
367
|
-
// Pure provider-shape parsers (exec banner, V4A diff,
|
|
373
|
+
// Pure provider-shape parsers (exec banner, V4A diff, tool arguments, …)
|
|
368
374
|
export {
|
|
369
375
|
applyPatchOps,
|
|
370
376
|
applyPatchOpsFromToolItem,
|
|
@@ -376,7 +382,6 @@ export {
|
|
|
376
382
|
parseExecBannerSessionId,
|
|
377
383
|
parseFreeformApplyPatch,
|
|
378
384
|
parseToolArgs,
|
|
379
|
-
redactSecrets,
|
|
380
385
|
sandboxCommandExitCode,
|
|
381
386
|
stripExecBanner,
|
|
382
387
|
tailPeek,
|
|
@@ -423,7 +428,7 @@ export type {
|
|
|
423
428
|
ComposerTranscriptionMessages,
|
|
424
429
|
} from "./components/composer-transcription-control";
|
|
425
430
|
export { defaultChatComposerMessages } from "./components/composer";
|
|
426
|
-
export type { ChatComposerMessages } from "./components/composer";
|
|
431
|
+
export type { ChatComposerMessages, ResponsiveBasis } from "./components/composer";
|
|
427
432
|
export { ModelPicker } from "./components/model-picker";
|
|
428
433
|
export type { ModelPickerProps } from "./components/model-picker";
|
|
429
434
|
export {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext, useContext, type RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
/** The measurement surface used by responsive composer chrome. */
|
|
4
|
+
export type ResponsiveBasis = "viewport" | "container";
|
|
5
|
+
|
|
6
|
+
export type ComposerResponsiveContextValue = {
|
|
7
|
+
responsiveBasis: ResponsiveBasis;
|
|
8
|
+
rootRef: RefObject<HTMLDivElement | null>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const ComposerResponsiveContext = createContext<ComposerResponsiveContextValue | null>(null);
|
|
12
|
+
|
|
13
|
+
export function useComposerResponsiveContext(): ComposerResponsiveContextValue | null {
|
|
14
|
+
return useContext(ComposerResponsiveContext);
|
|
15
|
+
}
|
|
@@ -1,6 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type CSSProperties,
|
|
3
|
+
type RefCallback,
|
|
4
|
+
type RefObject,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { useComposerResponsiveContext } from "./composer-responsive-context";
|
|
2
11
|
|
|
3
|
-
export type PortalTokenStyle = CSSProperties &
|
|
12
|
+
export type PortalTokenStyle = Pick<CSSProperties, "colorScheme"> &
|
|
13
|
+
Partial<Record<`--${string}`, string>>;
|
|
14
|
+
|
|
15
|
+
export const PORTAL_SOURCE_INLINE_SIZE = "--og-portal-source-inline-size";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A callback ref that publishes the currently mounted source element through
|
|
19
|
+
* React state. Unlike a bare mutable ref, late mounts and replacements cause
|
|
20
|
+
* consumers to render and restart their observers without polling.
|
|
21
|
+
*/
|
|
22
|
+
export function usePortalTokenSource<T extends HTMLElement>(): {
|
|
23
|
+
source: T | null;
|
|
24
|
+
currentRef: RefObject<T | null>;
|
|
25
|
+
ref: RefCallback<T>;
|
|
26
|
+
} {
|
|
27
|
+
const currentRef = useRef<T | null>(null);
|
|
28
|
+
const [source, setSource] = useState<T | null>(null);
|
|
29
|
+
const ref = useCallback<RefCallback<T>>((node) => {
|
|
30
|
+
currentRef.current = node;
|
|
31
|
+
setSource((current) => (current === node ? current : node));
|
|
32
|
+
}, []);
|
|
33
|
+
return { source, currentRef, ref };
|
|
34
|
+
}
|
|
4
35
|
|
|
5
36
|
/**
|
|
6
37
|
* Copy the effective SDK tokens across a portal boundary. A locally themed or
|
|
@@ -8,11 +39,13 @@ export type PortalTokenStyle = CSSProperties & Record<`--${string}`, string>;
|
|
|
8
39
|
* `<body>`. Ancestor theme/class/style mutations and OS color-scheme changes
|
|
9
40
|
* keep the copied values live.
|
|
10
41
|
*/
|
|
11
|
-
export function usePortalTokenStyle(
|
|
42
|
+
export function usePortalTokenStyle(source: HTMLElement | null): PortalTokenStyle {
|
|
43
|
+
const responsiveContext = useComposerResponsiveContext();
|
|
12
44
|
const [style, setStyle] = useState<PortalTokenStyle>({});
|
|
13
45
|
useEffect(() => {
|
|
14
|
-
|
|
15
|
-
|
|
46
|
+
if (!source || typeof window === "undefined") return;
|
|
47
|
+
const layoutSource =
|
|
48
|
+
responsiveContext?.responsiveBasis === "container" ? responsiveContext.rootRef.current : null;
|
|
16
49
|
let signature = "";
|
|
17
50
|
const sync = () => {
|
|
18
51
|
const computed = getComputedStyle(source);
|
|
@@ -22,6 +55,12 @@ export function usePortalTokenStyle(sourceRef: RefObject<HTMLElement | null>): P
|
|
|
22
55
|
if (!property.startsWith("--og-")) continue;
|
|
23
56
|
next[property as `--${string}`] = computed.getPropertyValue(property);
|
|
24
57
|
}
|
|
58
|
+
if (layoutSource) {
|
|
59
|
+
const inlineSize = layoutSource.clientWidth || layoutSource.getBoundingClientRect().width;
|
|
60
|
+
if (inlineSize > 0) {
|
|
61
|
+
next[PORTAL_SOURCE_INLINE_SIZE] = `${Math.round(inlineSize * 1000) / 1000}px`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
25
64
|
const nextSignature = JSON.stringify(next);
|
|
26
65
|
if (nextSignature !== signature) {
|
|
27
66
|
signature = nextSignature;
|
|
@@ -29,23 +68,29 @@ export function usePortalTokenStyle(sourceRef: RefObject<HTMLElement | null>): P
|
|
|
29
68
|
}
|
|
30
69
|
};
|
|
31
70
|
const observers: MutationObserver[] = [];
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
71
|
+
if (typeof MutationObserver !== "undefined") {
|
|
72
|
+
let ancestor: HTMLElement | null = source;
|
|
73
|
+
while (ancestor) {
|
|
74
|
+
const observer = new MutationObserver(sync);
|
|
75
|
+
observer.observe(ancestor, {
|
|
76
|
+
attributes: true,
|
|
77
|
+
attributeFilter: ["class", "data-og-density", "data-og-theme", "style"],
|
|
78
|
+
});
|
|
79
|
+
observers.push(observer);
|
|
80
|
+
ancestor = ancestor.parentElement;
|
|
81
|
+
}
|
|
41
82
|
}
|
|
83
|
+
const resizeObserver =
|
|
84
|
+
layoutSource && typeof ResizeObserver !== "undefined" ? new ResizeObserver(sync) : null;
|
|
85
|
+
if (layoutSource) resizeObserver?.observe(layoutSource);
|
|
42
86
|
const colorScheme = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
43
87
|
colorScheme?.addEventListener?.("change", sync);
|
|
44
88
|
sync();
|
|
45
89
|
return () => {
|
|
46
90
|
for (const observer of observers) observer.disconnect();
|
|
91
|
+
resizeObserver?.disconnect();
|
|
47
92
|
colorScheme?.removeEventListener?.("change", sync);
|
|
48
93
|
};
|
|
49
|
-
}, [
|
|
94
|
+
}, [responsiveContext?.responsiveBasis, responsiveContext?.rootRef, source]);
|
|
50
95
|
return style;
|
|
51
96
|
}
|