@opengeni/react 0.46.1 → 0.52.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.
- package/README.md +158 -14
- package/dist/artifacts-document.d.ts +3 -0
- package/dist/artifacts-document.js +17 -0
- package/dist/artifacts-document.js.map +1 -0
- package/dist/artifacts-presentation.d.ts +3 -0
- package/dist/artifacts-presentation.js +17 -0
- package/dist/artifacts-presentation.js.map +1 -0
- package/dist/artifacts-spreadsheet.d.ts +3 -0
- package/dist/artifacts-spreadsheet.js +17 -0
- package/dist/artifacts-spreadsheet.js.map +1 -0
- package/dist/artifacts.d.ts +2 -0
- package/dist/artifacts.js +170 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/chunk-6BBWWOOU.js +82 -0
- package/dist/chunk-6BBWWOOU.js.map +1 -0
- package/dist/{chunk-HFO4ERGQ.js → chunk-724BW4TW.js} +586 -35
- package/dist/chunk-724BW4TW.js.map +1 -0
- package/dist/chunk-7I2KB3OK.js +3895 -0
- package/dist/chunk-7I2KB3OK.js.map +1 -0
- package/dist/chunk-7WCLAEFV.js +2828 -0
- package/dist/chunk-7WCLAEFV.js.map +1 -0
- package/dist/{chunk-U3CSCMWA.js → chunk-FK6VG4LL.js} +9 -13
- package/dist/chunk-FK6VG4LL.js.map +1 -0
- package/dist/{chunk-SRFUT2ZU.js → chunk-FWS3KC3Z.js} +120 -64
- package/dist/chunk-FWS3KC3Z.js.map +1 -0
- package/dist/{chunk-EKZH6IDG.js → chunk-HDBG7XGO.js} +62 -1
- package/dist/chunk-HDBG7XGO.js.map +1 -0
- package/dist/chunk-HUIKIDM5.js +2148 -0
- package/dist/chunk-HUIKIDM5.js.map +1 -0
- package/dist/{chunk-LGVMUIRV.js → chunk-JAGDUCZQ.js} +1053 -390
- package/dist/chunk-JAGDUCZQ.js.map +1 -0
- package/dist/{chunk-LWR4MXSS.js → chunk-JR3QFY6B.js} +420 -35
- package/dist/chunk-JR3QFY6B.js.map +1 -0
- package/dist/chunk-JVYQRRC4.js +53 -0
- package/dist/chunk-JVYQRRC4.js.map +1 -0
- package/dist/chunk-KCGXPP5V.js +157 -0
- package/dist/chunk-KCGXPP5V.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-PEI6RANI.js +3234 -0
- package/dist/chunk-PEI6RANI.js.map +1 -0
- package/dist/{chunk-3PB7MIT6.js → chunk-ROIRTM37.js} +220 -72
- package/dist/chunk-ROIRTM37.js.map +1 -0
- package/dist/chunk-VZTQ73XT.js +86 -0
- package/dist/chunk-VZTQ73XT.js.map +1 -0
- package/dist/{chunk-VCBFUSUZ.js → chunk-WSK7G5LE.js} +72 -4
- package/dist/chunk-WSK7G5LE.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-6EDV6YFE.js → chunk-X4S56Y2H.js} +147 -35
- package/dist/chunk-X4S56Y2H.js.map +1 -0
- package/dist/client.d.ts +7 -1
- package/dist/clipboard.d.ts +1 -0
- package/dist/clipboard.js +7 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/types.d.ts +1 -1
- package/dist/components/approval-surface.d.ts +2 -2
- package/dist/components/artifacts/artifact-surface.d.ts +18 -0
- package/dist/components/artifacts/document-editor.d.ts +172 -0
- package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
- package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
- package/dist/components/artifacts/editable-document.d.ts +24 -0
- package/dist/components/artifacts/editable-presentation.d.ts +24 -0
- package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
- package/dist/components/artifacts/index.d.ts +8 -0
- package/dist/components/artifacts/presentation-editor.d.ts +297 -0
- package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
- package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
- package/dist/components/browser-viewer.d.ts +29 -0
- package/dist/components/chat-composer.d.ts +13 -8
- package/dist/components/code-editor.d.ts +25 -3
- package/dist/components/command-palette.d.ts +2 -2
- package/dist/components/composer-transcription-control.d.ts +4 -2
- package/dist/components/composer.d.ts +29 -15
- package/dist/components/computer-viewer.d.ts +23 -0
- package/dist/components/copy-button.d.ts +2 -2
- package/dist/components/desktop-viewer.d.ts +25 -21
- package/dist/components/diff-view.d.ts +1 -1
- package/dist/components/enrollment-consent.d.ts +1 -1
- package/dist/components/enrollment-device-flow.d.ts +1 -1
- package/dist/components/file-browser.d.ts +2 -2
- package/dist/components/fleet-tile.d.ts +1 -1
- package/dist/components/generated-video-player.d.ts +10 -0
- package/dist/components/human-input-form.d.ts +1 -1
- package/dist/components/human-input-surface.d.ts +2 -2
- package/dist/components/interactive-workbench-viewers.d.ts +2 -0
- package/dist/components/machine-card.d.ts +2 -2
- package/dist/components/machine-dock-bar.d.ts +3 -3
- package/dist/components/machine-health-pill.d.ts +2 -2
- package/dist/components/machine-input-display.d.ts +1 -1
- package/dist/components/machine-metrics.d.ts +2 -2
- package/dist/components/machine-status-pill.d.ts +4 -4
- package/dist/components/machines/health.d.ts +1 -1
- package/dist/components/machines/machine-detail.d.ts +5 -3
- package/dist/components/machines/metric-history-chart.d.ts +1 -1
- package/dist/components/machines/metric-sparkline.d.ts +2 -2
- package/dist/components/machines/series.d.ts +2 -2
- package/dist/components/machines-dashboard.d.ts +5 -5
- package/dist/components/markdown.d.ts +1 -1
- package/dist/components/message-timeline.d.ts +22 -5
- package/dist/components/model-picker.d.ts +2 -2
- package/dist/components/model-policy-picker.d.ts +7 -7
- package/dist/components/pierre-diff.d.ts +1 -1
- package/dist/components/pierre-file.d.ts +1 -1
- package/dist/components/queue-draft-policy.d.ts +1 -1
- package/dist/components/queue-surface-implementation.d.ts +3 -3
- package/dist/components/queue-surface-state.d.ts +4 -4
- package/dist/components/queue-surface.d.ts +5 -5
- package/dist/components/sandbox-files.d.ts +5 -5
- package/dist/components/sandbox-terminal.d.ts +50 -4
- package/dist/components/sandbox-workspace.d.ts +30 -10
- package/dist/components/session-chrome.d.ts +4 -4
- package/dist/components/session-status.d.ts +2 -2
- package/dist/components/timeline-annotation-selection.d.ts +9 -0
- package/dist/components/timeline-annotations-dialog.d.ts +16 -0
- package/dist/components/timeline-annotations.d.ts +13 -0
- package/dist/components/tooltip.d.ts +4 -4
- package/dist/components/user-message-body.d.ts +2 -2
- package/dist/components/workbench-changes.d.ts +2 -2
- package/dist/components/workspace-dock.d.ts +3 -1
- package/dist/composer.d.ts +11 -11
- package/dist/composer.js +7 -7
- package/dist/hooks/internal.d.ts +7 -1
- package/dist/hooks/use-attached-browsers.d.ts +21 -0
- package/dist/hooks/use-available-models.d.ts +2 -2
- package/dist/hooks/use-billing-usage.d.ts +1 -1
- package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
- package/dist/hooks/use-browser-identities.d.ts +26 -0
- package/dist/hooks/use-browser-session.d.ts +28 -0
- package/dist/hooks/use-browser-sessions.d.ts +32 -0
- package/dist/hooks/use-codex-accounts.d.ts +3 -3
- package/dist/hooks/use-composer.d.ts +36 -9
- package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
- package/dist/hooks/use-computer-session.d.ts +28 -0
- package/dist/hooks/use-computer-sessions.d.ts +28 -0
- package/dist/hooks/use-desktop-stream.d.ts +1 -1
- package/dist/hooks/use-environments.d.ts +11 -5
- package/dist/hooks/use-file-attachments.d.ts +1 -1
- package/dist/hooks/use-goal.d.ts +2 -2
- package/dist/hooks/use-human-input.d.ts +2 -2
- package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
- package/dist/hooks/use-machine-chip.d.ts +2 -2
- package/dist/hooks/use-machines.d.ts +10 -3
- package/dist/hooks/use-packs.d.ts +7 -2
- package/dist/hooks/use-rigs.d.ts +1 -1
- package/dist/hooks/use-sandbox-files.d.ts +25 -4
- package/dist/hooks/use-sandbox-git.d.ts +13 -5
- package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
- package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
- package/dist/hooks/use-session-capabilities.d.ts +5 -4
- package/dist/hooks/use-session-control.d.ts +1 -1
- package/dist/hooks/use-session-events.d.ts +2 -2
- package/dist/hooks/use-session-lineage.d.ts +1 -1
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-session.d.ts +2 -2
- package/dist/hooks/use-slash-commands.d.ts +1 -1
- package/dist/hooks/use-terminal-stream.d.ts +8 -2
- package/dist/hooks/use-transcription.d.ts +2 -2
- package/dist/hooks/use-turn-queue.d.ts +2 -2
- package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
- package/dist/hooks/use-voice-input.d.ts +6 -4
- package/dist/hooks/use-workspace-capture.d.ts +1 -1
- package/dist/hooks/use-workspace-edit.d.ts +1 -1
- package/dist/hooks/use-workspace-sessions.d.ts +1 -1
- package/dist/hooks/use-workspaces.d.ts +1 -1
- package/dist/index.d.ts +158 -154
- package/dist/index.js +1718 -908
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +23 -0
- package/dist/interaction.js +36 -0
- package/dist/interaction.js.map +1 -0
- package/dist/interactive-workbench-viewers-KHNOHWQE.js +13 -0
- package/dist/interactive-workbench-viewers-KHNOHWQE.js.map +1 -0
- package/dist/lib/composer-responsive-context.d.ts +9 -0
- package/dist/lib/relay-wire.d.ts +4 -0
- package/dist/lib/sandbox-liveness.d.ts +9 -0
- package/dist/lib/terminal-capability.d.ts +5 -0
- package/dist/lib/use-portal-token-style.d.ts +14 -3
- package/dist/lib/xterm-theme.d.ts +1 -1
- package/dist/machines.d.ts +30 -30
- package/dist/machines.js +3 -3
- package/dist/provider.d.ts +4 -4
- package/dist/{queue-surface-implementation-J4XJFKRW.js → queue-surface-implementation-2KVJTYXH.js} +117 -64
- package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
- package/dist/realtime/dropdown-menu.d.ts +8 -8
- package/dist/realtime/realtime-control.d.ts +15 -6
- package/dist/realtime.d.ts +4 -4
- package/dist/realtime.js +30 -33
- package/dist/realtime.js.map +1 -1
- package/dist/session-context.d.ts +19 -1
- package/dist/session-ui.d.ts +14 -14
- package/dist/session-ui.js +6 -5
- package/dist/session.d.ts +28 -28
- package/dist/session.js +5 -5
- package/dist/timeline/activity-rail.d.ts +6 -3
- package/dist/timeline/disclosure-context.d.ts +1 -1
- package/dist/timeline/fleet-decision-projection.d.ts +1 -1
- package/dist/timeline/fleet-decision-row.d.ts +3 -3
- package/dist/timeline/index.d.ts +19 -19
- package/dist/timeline/parsers.d.ts +6 -3
- package/dist/timeline/projection.d.ts +3 -3
- package/dist/timeline/registry.d.ts +10 -1
- package/dist/timeline/screenshot-lightbox.d.ts +2 -2
- package/dist/timeline/shared.d.ts +21 -10
- package/dist/timeline/tool-diff.d.ts +2 -2
- package/dist/timeline/tool-renderers.d.ts +1 -1
- package/dist/timeline/turn-summary.d.ts +3 -3
- package/dist/timeline/types.d.ts +42 -2
- package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
- package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
- package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
- package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
- package/dist/voice-recording-store.d.ts +8 -2
- package/package.json +52 -7
- package/src/artifacts-document.ts +31 -0
- package/src/artifacts-presentation.ts +36 -0
- package/src/artifacts-spreadsheet.ts +21 -0
- package/src/artifacts.ts +86 -0
- package/src/client.ts +90 -1
- package/src/clipboard.ts +2 -0
- package/src/components/artifacts/artifact-surface.tsx +93 -0
- package/src/components/artifacts/document-editor.tsx +2255 -0
- package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
- package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
- package/src/components/artifacts/editable-document.tsx +657 -0
- package/src/components/artifacts/editable-presentation.tsx +1020 -0
- package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
- package/src/components/artifacts/index.ts +99 -0
- package/src/components/artifacts/presentation-editor.tsx +3148 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
- package/src/components/browser-viewer.tsx +1592 -0
- package/src/components/chat-composer.tsx +25 -2
- package/src/components/code-editor.tsx +90 -31
- package/src/components/command-palette.tsx +1 -1
- package/src/components/composer-transcription-control.tsx +51 -22
- package/src/components/composer.tsx +49 -23
- package/src/components/computer-viewer.tsx +1139 -0
- package/src/components/copy-button.tsx +1 -1
- package/src/components/desktop-viewer.tsx +103 -75
- package/src/components/file-browser.tsx +39 -17
- package/src/components/generated-video-player.tsx +89 -0
- package/src/components/human-input-form.tsx +2 -2
- package/src/components/interactive-workbench-viewers.ts +2 -0
- package/src/components/machine-card.tsx +1 -1
- package/src/components/machine-input-display.ts +3 -0
- package/src/components/machines/machine-detail.tsx +39 -15
- package/src/components/machines-dashboard.tsx +7 -6
- package/src/components/message-timeline.tsx +327 -37
- package/src/components/model-picker.tsx +2 -2
- package/src/components/model-policy-picker.tsx +16 -15
- package/src/components/queue-surface-implementation.tsx +135 -75
- package/src/components/queue-surface-state.tsx +2 -2
- package/src/components/sandbox-files.tsx +142 -62
- package/src/components/sandbox-terminal.tsx +119 -35
- package/src/components/sandbox-workspace.tsx +394 -228
- package/src/components/session-chrome.tsx +67 -27
- package/src/components/timeline-annotation-selection.tsx +221 -0
- package/src/components/timeline-annotations-dialog.tsx +213 -0
- package/src/components/timeline-annotations.tsx +84 -0
- package/src/components/tooltip.tsx +13 -16
- package/src/components/user-message-body.tsx +1 -1
- package/src/components/workbench-changes.tsx +192 -35
- 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-attached-browsers.ts +152 -0
- package/src/hooks/use-browser-frame-stream.ts +316 -0
- package/src/hooks/use-browser-identities.ts +225 -0
- package/src/hooks/use-browser-session.ts +476 -0
- package/src/hooks/use-browser-sessions.ts +281 -0
- package/src/hooks/use-codex-accounts.ts +4 -4
- package/src/hooks/use-composer.ts +769 -24
- package/src/hooks/use-computer-frame-stream.ts +311 -0
- package/src/hooks/use-computer-session.ts +442 -0
- package/src/hooks/use-computer-sessions.ts +245 -0
- 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-machine-chip.ts +5 -3
- package/src/hooks/use-machines.ts +79 -3
- package/src/hooks/use-packs.ts +51 -0
- package/src/hooks/use-sandbox-files.ts +382 -88
- package/src/hooks/use-sandbox-git.ts +208 -46
- package/src/hooks/use-sandbox-terminal.ts +3 -2
- package/src/hooks/use-session-capabilities.ts +94 -70
- package/src/hooks/use-session-events.ts +18 -4
- package/src/hooks/use-terminal-stream.ts +102 -23
- package/src/hooks/use-transcription.ts +4 -14
- package/src/hooks/use-turn-queue.ts +17 -5
- package/src/hooks/use-video-artifact-playback.ts +28 -0
- package/src/hooks/use-voice-input.ts +163 -19
- package/src/hooks/use-windowed-sections.ts +9 -7
- package/src/hooks/use-workspace-edit.ts +2 -1
- package/src/index.ts +21 -6
- package/src/interaction.ts +71 -0
- package/src/lib/composer-responsive-context.ts +15 -0
- package/src/lib/relay-wire.ts +4 -0
- package/src/lib/sandbox-liveness.ts +11 -0
- package/src/lib/terminal-capability.ts +10 -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/session-context.ts +90 -0
- package/src/timeline/activity-rail.tsx +31 -6
- package/src/timeline/index.ts +4 -1
- package/src/timeline/parsers.ts +46 -20
- package/src/timeline/projection.ts +233 -13
- package/src/timeline/registry.ts +19 -0
- package/src/timeline/screenshot-lightbox.tsx +30 -9
- package/src/timeline/shared.tsx +59 -11
- package/src/timeline/tool-renderers.tsx +421 -24
- package/src/timeline/turn-summary.tsx +2 -2
- package/src/timeline/types.ts +58 -2
- package/src/voice-recording-store.ts +15 -0
- package/styles/compiled.css +5298 -0
- package/styles/compiled.d.ts +2 -0
- package/styles/effective-tokens.css +27 -0
- package/styles/index.css +18 -0
- package/styles/responsive.css +208 -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-EKZH6IDG.js.map +0 -1
- package/dist/chunk-FGZUCXZF.js.map +0 -1
- package/dist/chunk-HFO4ERGQ.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/chunk-VCBFUSUZ.js.map +0 -1
- package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
- package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
- package/dist/queue-surface-implementation-J4XJFKRW.js.map +0 -1
package/src/hooks/internal.ts
CHANGED
|
@@ -2,6 +2,51 @@ import type { SessionEvent } from "@opengeni/sdk";
|
|
|
2
2
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
3
|
import type { EmbeddedSessionEventClientLike } from "../client";
|
|
4
4
|
|
|
5
|
+
const HIDDEN_STREAM_GRACE_MS = 2_000;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Keep live browser work active while the page is visible, then suspend it
|
|
9
|
+
* after a short hidden-tab grace period. Returning to the page re-enables the
|
|
10
|
+
* caller, which must reconcile durable state before resuming live delivery.
|
|
11
|
+
*/
|
|
12
|
+
export function usePageLiveActivity(hiddenGraceMs = HIDDEN_STREAM_GRACE_MS): boolean {
|
|
13
|
+
// Keep the server and initial browser render identical; the effect applies
|
|
14
|
+
// visibility after mount and preserves the intentional hidden-tab grace.
|
|
15
|
+
const [active, setActive] = useState(true);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (typeof document === "undefined") return;
|
|
19
|
+
let hiddenTimer: ReturnType<typeof setTimeout> | null = null;
|
|
20
|
+
const cancelHiddenTimer = () => {
|
|
21
|
+
if (hiddenTimer !== null) {
|
|
22
|
+
clearTimeout(hiddenTimer);
|
|
23
|
+
hiddenTimer = null;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const sync = () => {
|
|
27
|
+
cancelHiddenTimer();
|
|
28
|
+
if (document.visibilityState !== "hidden") {
|
|
29
|
+
setActive(true);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
hiddenTimer = setTimeout(() => {
|
|
33
|
+
hiddenTimer = null;
|
|
34
|
+
if (document.visibilityState === "hidden") setActive(false);
|
|
35
|
+
}, hiddenGraceMs);
|
|
36
|
+
};
|
|
37
|
+
document.addEventListener("visibilitychange", sync);
|
|
38
|
+
window.addEventListener("pageshow", sync);
|
|
39
|
+
sync();
|
|
40
|
+
return () => {
|
|
41
|
+
cancelHiddenTimer();
|
|
42
|
+
document.removeEventListener("visibilitychange", sync);
|
|
43
|
+
window.removeEventListener("pageshow", sync);
|
|
44
|
+
};
|
|
45
|
+
}, [hiddenGraceMs]);
|
|
46
|
+
|
|
47
|
+
return active;
|
|
48
|
+
}
|
|
49
|
+
|
|
5
50
|
export type AsyncListState<T> = {
|
|
6
51
|
data: T | null;
|
|
7
52
|
loading: boolean;
|
|
@@ -19,6 +64,7 @@ export function usePolledValue<T>(
|
|
|
19
64
|
): AsyncListState<T> {
|
|
20
65
|
const enabled = options.enabled ?? true;
|
|
21
66
|
const pollIntervalMs = options.pollIntervalMs;
|
|
67
|
+
const pageLive = usePageLiveActivity();
|
|
22
68
|
const [data, setData] = useState<T | null>(null);
|
|
23
69
|
const [loading, setLoading] = useState(enabled);
|
|
24
70
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -28,6 +74,8 @@ export function usePolledValue<T>(
|
|
|
28
74
|
activeLoadRef.current = load;
|
|
29
75
|
}, [load]);
|
|
30
76
|
const requestAbortRef = useRef<AbortController | null>(null);
|
|
77
|
+
const inFlightRef = useRef<{ load: typeof load; promise: Promise<void> } | null>(null);
|
|
78
|
+
const trailingRefreshRef = useRef<{ load: typeof load; promise: Promise<void> } | null>(null);
|
|
31
79
|
const [stateIdentity, setStateIdentity] = useState<{ load: typeof load }>(() => ({ load }));
|
|
32
80
|
|
|
33
81
|
// A new loader identity means a new query (different session/workspace/...):
|
|
@@ -40,68 +88,107 @@ export function usePolledValue<T>(
|
|
|
40
88
|
}
|
|
41
89
|
}, [load, stateIdentity.load]);
|
|
42
90
|
|
|
43
|
-
const run = useCallback(
|
|
91
|
+
const run = useCallback((): Promise<void> => {
|
|
44
92
|
// A callback retained by a completed mutation from the previous query must
|
|
45
93
|
// not supersede or settle the current query's request.
|
|
46
|
-
if (activeLoadRef.current !== load) return;
|
|
94
|
+
if (activeLoadRef.current !== load) return Promise.resolve();
|
|
95
|
+
const existing = inFlightRef.current;
|
|
96
|
+
if (existing?.load === load) return existing.promise;
|
|
47
97
|
const ticket = ++generation.current;
|
|
48
|
-
requestAbortRef.current?.abort();
|
|
49
98
|
const requestAbort = new AbortController();
|
|
50
99
|
requestAbortRef.current = requestAbort;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
100
|
+
let promise!: Promise<void>;
|
|
101
|
+
promise = (async () => {
|
|
102
|
+
try {
|
|
103
|
+
const result = await load(requestAbort.signal);
|
|
104
|
+
if (
|
|
105
|
+
ticket === generation.current &&
|
|
106
|
+
activeLoadRef.current === load &&
|
|
107
|
+
!requestAbort.signal.aborted
|
|
108
|
+
) {
|
|
109
|
+
setData(result);
|
|
110
|
+
setError(null);
|
|
111
|
+
setLoading(false);
|
|
112
|
+
}
|
|
113
|
+
} catch (cause) {
|
|
114
|
+
if (
|
|
115
|
+
ticket === generation.current &&
|
|
116
|
+
activeLoadRef.current === load &&
|
|
117
|
+
!requestAbort.signal.aborted
|
|
118
|
+
) {
|
|
119
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
120
|
+
setLoading(false);
|
|
121
|
+
}
|
|
122
|
+
} finally {
|
|
123
|
+
if (requestAbortRef.current === requestAbort) requestAbortRef.current = null;
|
|
124
|
+
if (inFlightRef.current?.promise === promise) inFlightRef.current = null;
|
|
74
125
|
}
|
|
75
|
-
}
|
|
126
|
+
})();
|
|
127
|
+
inFlightRef.current = { load, promise };
|
|
128
|
+
return promise;
|
|
76
129
|
}, [load]);
|
|
77
130
|
|
|
131
|
+
const refresh = useCallback((): Promise<void> => {
|
|
132
|
+
const existing = inFlightRef.current;
|
|
133
|
+
if (existing?.load !== load) return run();
|
|
134
|
+
const trailing = trailingRefreshRef.current;
|
|
135
|
+
if (trailing?.load === load) return trailing.promise;
|
|
136
|
+
let promise!: Promise<void>;
|
|
137
|
+
promise = existing.promise.then(async () => {
|
|
138
|
+
if (trailingRefreshRef.current?.promise === promise) {
|
|
139
|
+
trailingRefreshRef.current = null;
|
|
140
|
+
}
|
|
141
|
+
if (activeLoadRef.current === load) await run();
|
|
142
|
+
});
|
|
143
|
+
trailingRefreshRef.current = { load, promise };
|
|
144
|
+
return promise;
|
|
145
|
+
}, [load, run]);
|
|
146
|
+
|
|
78
147
|
useEffect(() => {
|
|
79
|
-
if (!enabled) {
|
|
148
|
+
if (!enabled || !pageLive) {
|
|
80
149
|
setLoading(false);
|
|
150
|
+
generation.current += 1;
|
|
151
|
+
requestAbortRef.current?.abort();
|
|
152
|
+
inFlightRef.current = null;
|
|
153
|
+
trailingRefreshRef.current = null;
|
|
81
154
|
return;
|
|
82
155
|
}
|
|
83
156
|
setLoading(true);
|
|
84
|
-
|
|
157
|
+
let cancelled = false;
|
|
158
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
159
|
+
const schedule = () => {
|
|
160
|
+
if (cancelled || pollIntervalMs === undefined || pollIntervalMs <= 0) return;
|
|
161
|
+
timer = setTimeout(() => {
|
|
162
|
+
timer = null;
|
|
163
|
+
void run().finally(schedule);
|
|
164
|
+
}, pollIntervalMs);
|
|
165
|
+
};
|
|
166
|
+
void run().finally(schedule);
|
|
85
167
|
if (pollIntervalMs === undefined || pollIntervalMs <= 0) {
|
|
86
168
|
return () => {
|
|
169
|
+
cancelled = true;
|
|
87
170
|
generation.current += 1;
|
|
88
171
|
requestAbortRef.current?.abort();
|
|
172
|
+
inFlightRef.current = null;
|
|
173
|
+
trailingRefreshRef.current = null;
|
|
89
174
|
};
|
|
90
175
|
}
|
|
91
|
-
const timer = setInterval(() => void run(), pollIntervalMs);
|
|
92
176
|
return () => {
|
|
93
|
-
|
|
177
|
+
cancelled = true;
|
|
178
|
+
if (timer !== null) clearTimeout(timer);
|
|
94
179
|
generation.current += 1;
|
|
95
180
|
requestAbortRef.current?.abort();
|
|
181
|
+
inFlightRef.current = null;
|
|
182
|
+
trailingRefreshRef.current = null;
|
|
96
183
|
};
|
|
97
|
-
}, [run, enabled, pollIntervalMs]);
|
|
184
|
+
}, [run, enabled, pageLive, pollIntervalMs]);
|
|
98
185
|
|
|
99
186
|
const identityMatches = stateIdentity.load === load;
|
|
100
187
|
return {
|
|
101
188
|
data: identityMatches ? data : null,
|
|
102
189
|
loading: identityMatches ? loading : enabled,
|
|
103
190
|
error: identityMatches ? error : null,
|
|
104
|
-
refresh
|
|
191
|
+
refresh,
|
|
105
192
|
};
|
|
106
193
|
}
|
|
107
194
|
|
|
@@ -224,6 +311,8 @@ export function useSessionEventTrigger(
|
|
|
224
311
|
reconcileBeforeLive?: (() => void | Promise<void>) | undefined,
|
|
225
312
|
): void {
|
|
226
313
|
const enabled = options.enabled ?? true;
|
|
314
|
+
const pageLive = usePageLiveActivity();
|
|
315
|
+
const liveEnabled = enabled && pageLive;
|
|
227
316
|
const events = options.events;
|
|
228
317
|
const sharedFeed = events !== undefined;
|
|
229
318
|
const matchRef = useRef(match);
|
|
@@ -240,7 +329,7 @@ export function useSessionEventTrigger(
|
|
|
240
329
|
|
|
241
330
|
// Shared-log mode: scan only the unseen tail on every append.
|
|
242
331
|
useEffect(() => {
|
|
243
|
-
if (!sharedFeed || !
|
|
332
|
+
if (!sharedFeed || !liveEnabled || !sessionId) {
|
|
244
333
|
return;
|
|
245
334
|
}
|
|
246
335
|
const feedKey = `${workspaceId}\u0000${sessionId}`;
|
|
@@ -285,11 +374,11 @@ export function useSessionEventTrigger(
|
|
|
285
374
|
onEventRef.current(event);
|
|
286
375
|
}
|
|
287
376
|
}
|
|
288
|
-
}, [sharedFeed,
|
|
377
|
+
}, [sharedFeed, liveEnabled, events, workspaceId, sessionId]);
|
|
289
378
|
|
|
290
379
|
// Self-stream mode: tail from the session's current lastSequence.
|
|
291
380
|
useEffect(() => {
|
|
292
|
-
if (sharedFeed || !
|
|
381
|
+
if (sharedFeed || !liveEnabled || !sessionId) {
|
|
293
382
|
return;
|
|
294
383
|
}
|
|
295
384
|
const controller = new AbortController();
|
|
@@ -302,7 +391,12 @@ export function useSessionEventTrigger(
|
|
|
302
391
|
const stream = client.streamEvents(workspaceId, sessionId, {
|
|
303
392
|
after: session.lastSequence,
|
|
304
393
|
signal: controller.signal,
|
|
305
|
-
|
|
394
|
+
onOpen: () => {
|
|
395
|
+
// Start reconciliation without delaying consumption of live SSE.
|
|
396
|
+
void Promise.resolve()
|
|
397
|
+
.then(() => reconcileBeforeLiveRef.current?.())
|
|
398
|
+
.catch(() => undefined);
|
|
399
|
+
},
|
|
306
400
|
});
|
|
307
401
|
for await (const event of stream) {
|
|
308
402
|
if (matchRef.current(event)) {
|
|
@@ -317,7 +411,7 @@ export function useSessionEventTrigger(
|
|
|
317
411
|
return () => {
|
|
318
412
|
controller.abort();
|
|
319
413
|
};
|
|
320
|
-
}, [sharedFeed,
|
|
414
|
+
}, [sharedFeed, liveEnabled, client, workspaceId, sessionId]);
|
|
321
415
|
}
|
|
322
416
|
|
|
323
417
|
/** Debounce rapid event bursts into one trailing call (default 150ms). */
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { AttachedBrowserDevice } from "@opengeni/sdk/interaction";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import type { EmbeddedBrowserInteractionClientLike } from "../client";
|
|
4
|
+
import {
|
|
5
|
+
type EmbeddedBrowserInteractionClientOverride,
|
|
6
|
+
useEmbeddedBrowserInteraction,
|
|
7
|
+
} from "../session-context";
|
|
8
|
+
import { usePageLiveActivity } from "./internal";
|
|
9
|
+
|
|
10
|
+
export type UseAttachedBrowsersOptions = EmbeddedBrowserInteractionClientOverride & {
|
|
11
|
+
enabled?: boolean | undefined;
|
|
12
|
+
includeDisconnected?: boolean | undefined;
|
|
13
|
+
pollIntervalMs?: number | undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type UseAttachedBrowsersResult = {
|
|
17
|
+
revision: number;
|
|
18
|
+
devices: AttachedBrowserDevice[];
|
|
19
|
+
loading: boolean;
|
|
20
|
+
refreshing: boolean;
|
|
21
|
+
error: Error | null;
|
|
22
|
+
refresh: () => Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Workspace-wide inventory of Chrome profiles currently connected through an
|
|
26
|
+
* enrolled OpenGeni machine. BrowserSessions remain the execution authority. */
|
|
27
|
+
export function useAttachedBrowsers(
|
|
28
|
+
options: UseAttachedBrowsersOptions = {},
|
|
29
|
+
): UseAttachedBrowsersResult {
|
|
30
|
+
const { client, workspaceId } = useEmbeddedBrowserInteraction(options);
|
|
31
|
+
const enabled = options.enabled ?? true;
|
|
32
|
+
const includeDisconnected = options.includeDisconnected ?? false;
|
|
33
|
+
const pageLive = usePageLiveActivity();
|
|
34
|
+
const pollIntervalMs = Math.max(1_000, options.pollIntervalMs ?? 3_000);
|
|
35
|
+
const [state, setState] = useState(() => emptyState(workspaceId, enabled));
|
|
36
|
+
const requestRef = useRef<{ id: number; controller: AbortController | null }>({
|
|
37
|
+
id: 0,
|
|
38
|
+
controller: null,
|
|
39
|
+
});
|
|
40
|
+
const mountedRef = useRef(true);
|
|
41
|
+
const visible = state.workspaceId === workspaceId ? state : emptyState(workspaceId, enabled);
|
|
42
|
+
|
|
43
|
+
const cancelLoad = useCallback(() => {
|
|
44
|
+
const id = requestRef.current.id + 1;
|
|
45
|
+
requestRef.current.controller?.abort();
|
|
46
|
+
requestRef.current = { id, controller: null };
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const load = useCallback(
|
|
50
|
+
async (foreground: boolean): Promise<void> => {
|
|
51
|
+
if (!enabled) return;
|
|
52
|
+
const id = requestRef.current.id + 1;
|
|
53
|
+
requestRef.current.controller?.abort();
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
requestRef.current = { id, controller };
|
|
56
|
+
setState((current) => ({
|
|
57
|
+
...(current.workspaceId === workspaceId ? current : emptyState(workspaceId, true)),
|
|
58
|
+
loading: foreground,
|
|
59
|
+
refreshing: !foreground,
|
|
60
|
+
error: foreground || current.workspaceId !== workspaceId ? null : current.error,
|
|
61
|
+
}));
|
|
62
|
+
try {
|
|
63
|
+
const response = await client.listAttachedBrowsers(workspaceId, {
|
|
64
|
+
includeDisconnected,
|
|
65
|
+
signal: controller.signal,
|
|
66
|
+
});
|
|
67
|
+
if (!mountedRef.current || requestRef.current.id !== id) return;
|
|
68
|
+
setState({
|
|
69
|
+
workspaceId,
|
|
70
|
+
revision: response.revision,
|
|
71
|
+
devices: sortDevices(response.devices),
|
|
72
|
+
loading: false,
|
|
73
|
+
refreshing: false,
|
|
74
|
+
error: null,
|
|
75
|
+
});
|
|
76
|
+
} catch (cause) {
|
|
77
|
+
if (controller.signal.aborted || !mountedRef.current || requestRef.current.id !== id)
|
|
78
|
+
return;
|
|
79
|
+
setState((current) => ({
|
|
80
|
+
...(current.workspaceId === workspaceId ? current : emptyState(workspaceId, true)),
|
|
81
|
+
loading: false,
|
|
82
|
+
refreshing: false,
|
|
83
|
+
error: cause instanceof Error ? cause : new Error(String(cause)),
|
|
84
|
+
}));
|
|
85
|
+
} finally {
|
|
86
|
+
if (requestRef.current.id === id) requestRef.current = { id, controller: null };
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
[client, enabled, includeDisconnected, workspaceId],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const refresh = useCallback(async () => await load(false), [load]);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
mountedRef.current = true;
|
|
96
|
+
if (!enabled) {
|
|
97
|
+
cancelLoad();
|
|
98
|
+
setState(emptyState(workspaceId, false));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
void load(true);
|
|
102
|
+
return () => {
|
|
103
|
+
mountedRef.current = false;
|
|
104
|
+
cancelLoad();
|
|
105
|
+
};
|
|
106
|
+
}, [cancelLoad, enabled, load, workspaceId]);
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!enabled || !pageLive) return;
|
|
110
|
+
const timer = setInterval(() => {
|
|
111
|
+
if (!requestRef.current.controller) void load(false);
|
|
112
|
+
}, pollIntervalMs);
|
|
113
|
+
return () => clearInterval(timer);
|
|
114
|
+
}, [enabled, load, pageLive, pollIntervalMs]);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
revision: visible.revision,
|
|
118
|
+
devices: visible.devices,
|
|
119
|
+
loading: visible.loading,
|
|
120
|
+
refreshing: visible.refreshing,
|
|
121
|
+
error: visible.error,
|
|
122
|
+
refresh,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function emptyState(workspaceId: string, loading: boolean) {
|
|
127
|
+
return {
|
|
128
|
+
workspaceId,
|
|
129
|
+
revision: 0,
|
|
130
|
+
devices: [] as AttachedBrowserDevice[],
|
|
131
|
+
loading,
|
|
132
|
+
refreshing: false,
|
|
133
|
+
error: null as Error | null,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function sortDevices(devices: readonly AttachedBrowserDevice[]): AttachedBrowserDevice[] {
|
|
138
|
+
return [...devices].sort(
|
|
139
|
+
(left, right) =>
|
|
140
|
+
Number(right.state === "connected") - Number(left.state === "connected") ||
|
|
141
|
+
timestamp(right.lastSeenAt) - timestamp(left.lastSeenAt) ||
|
|
142
|
+
left.name.localeCompare(right.name),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function timestamp(value: string): number {
|
|
147
|
+
const parsed = Date.parse(value);
|
|
148
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Exported for narrow proxy implementations and tests. */
|
|
152
|
+
export type AttachedBrowsersClient = EmbeddedBrowserInteractionClientLike;
|