@opengeni/react 7.6.1 → 7.7.0-canary.37090756480001
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 +19 -2
- package/dist/{browser-viewer-5X47I6T2.js → browser-viewer-63TXMINB.js} +6 -5
- package/dist/{chunk-UINDK6C3.js → chunk-2QUSQVF2.js} +3 -3
- package/dist/{chunk-CHPKSLPN.js → chunk-6ZSWSH4I.js} +3 -1
- package/dist/chunk-6ZSWSH4I.js.map +1 -0
- package/dist/{chunk-KGENSZKS.js → chunk-AAG4R3QN.js} +4 -3
- package/dist/chunk-AAG4R3QN.js.map +1 -0
- package/dist/{chunk-Z3DISEQN.js → chunk-CWWKTNKK.js} +35 -18
- package/dist/chunk-CWWKTNKK.js.map +1 -0
- package/dist/{chunk-5EXGU7UF.js → chunk-DOJWJQ3F.js} +3 -3
- package/dist/{chunk-WZ55DVGK.js → chunk-ELCE5SXV.js} +8 -4
- package/dist/chunk-ELCE5SXV.js.map +1 -0
- package/dist/{chunk-YSMMDOOE.js → chunk-GISEVGS6.js} +10 -2
- package/dist/chunk-GISEVGS6.js.map +1 -0
- package/dist/{chunk-YRYF2GLG.js → chunk-HNFCAQ5R.js} +39 -31
- package/dist/chunk-HNFCAQ5R.js.map +1 -0
- package/dist/{chunk-BI57CQUV.js → chunk-I7IWRQLN.js} +2 -2
- package/dist/{chunk-XA6R7DJQ.js → chunk-IQUCRAT7.js} +3 -3
- package/dist/{chunk-DV2DBNPQ.js → chunk-Q5QBEZIQ.js} +48 -29
- package/dist/chunk-Q5QBEZIQ.js.map +1 -0
- package/dist/{chunk-FGBCYQOT.js → chunk-RDKBXMWX.js} +120 -74
- package/dist/chunk-RDKBXMWX.js.map +1 -0
- package/dist/{chunk-45GE35O7.js → chunk-TKB4SB44.js} +10 -2
- package/dist/chunk-TKB4SB44.js.map +1 -0
- package/dist/{chunk-YCGBREUV.js → chunk-TOAH2BKA.js} +6 -2
- package/dist/{chunk-YCGBREUV.js.map → chunk-TOAH2BKA.js.map} +1 -1
- package/dist/chunk-UGLEYEMP.js +33 -0
- package/dist/chunk-UGLEYEMP.js.map +1 -0
- package/dist/{chunk-UJRYEWZI.js → chunk-UTRTG6LH.js} +48 -8
- package/dist/chunk-UTRTG6LH.js.map +1 -0
- package/dist/{chunk-VGIKFIO2.js → chunk-XOAMRDDK.js} +6 -3
- package/dist/chunk-XOAMRDDK.js.map +1 -0
- package/dist/{chunk-6NEZPF53.js → chunk-YN7I3STW.js} +153 -32
- package/dist/chunk-YN7I3STW.js.map +1 -0
- package/dist/{chunk-KZ73RL76.js → chunk-YQEAA6LT.js} +7 -3
- package/dist/chunk-YQEAA6LT.js.map +1 -0
- package/dist/{chunk-LZ3KHA55.js → chunk-ZYALTAEQ.js} +329 -339
- package/dist/chunk-ZYALTAEQ.js.map +1 -0
- package/dist/client.d.ts +4 -1
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/approval-surface.d.ts +15 -0
- package/dist/composer.js +8 -7
- package/dist/{computer-viewer-7GF35SNY.js → computer-viewer-X2MBRZY4.js} +5 -4
- package/dist/connect.js +3 -3
- package/dist/hooks/use-browser-session.d.ts +4 -0
- package/dist/hooks/use-composer.d.ts +2 -0
- package/dist/hooks/use-computer-session.d.ts +2 -0
- package/dist/hooks/use-file-attachments.d.ts +2 -0
- package/dist/hooks/use-session-events.d.ts +1 -1
- package/dist/hooks/use-workspace-sessions.d.ts +15 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +28 -21
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +1 -1
- package/dist/interaction.js +6 -5
- package/dist/interaction.js.map +1 -1
- package/dist/{latest-question-3BJGKQQ6.js → latest-question-CNEZ5KAN.js} +3 -3
- package/dist/lib/error-message.d.ts +7 -0
- package/dist/lib/format.d.ts +27 -3
- package/dist/lib/interaction-errors.d.ts +3 -0
- package/dist/machines.js +5 -4
- package/dist/{model-policy-picker-menu-PUTA5JOB.js → model-policy-picker-menu-JSA64TNN.js} +4 -4
- package/dist/model-policy.js +1 -1
- package/dist/provider.d.ts +5 -1
- package/dist/{queue-surface-implementation-2CAQAIHM.js → queue-surface-implementation-HENWYFWA.js} +3 -2
- package/dist/{queue-surface-implementation-2CAQAIHM.js.map → queue-surface-implementation-HENWYFWA.js.map} +1 -1
- package/dist/realtime.js +2 -2
- package/dist/{sandbox-files-AEJTWGT4.js → sandbox-files-BBC47SLD.js} +4 -3
- package/dist/session-ui.d.ts +1 -1
- package/dist/session-ui.js +13 -12
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +7 -6
- package/dist/usage.js +2 -1
- package/dist/usage.js.map +1 -1
- package/package.json +3 -3
- package/src/client.ts +5 -1
- package/src/commands/registry.ts +18 -15
- package/src/commands/types.ts +2 -0
- package/src/components/approval-surface.tsx +166 -42
- package/src/components/browser-viewer.tsx +38 -9
- package/src/components/composer.tsx +9 -2
- package/src/components/computer-viewer.tsx +160 -30
- package/src/components/generated-video-player.tsx +4 -2
- package/src/components/human-input-form.tsx +18 -5
- package/src/components/message-timeline.tsx +120 -168
- package/src/components/open-geni-chat.tsx +11 -4
- package/src/components/queue-surface-state.tsx +3 -1
- package/src/components/session-chrome.tsx +5 -2
- package/src/components/session-commands-panel.tsx +3 -1
- package/src/components/session-conversation.tsx +4 -2
- package/src/components/session-list.tsx +7 -5
- package/src/components/session-mcp-capability-card.tsx +11 -1
- package/src/hooks/internal.ts +3 -2
- package/src/hooks/use-browser-session.ts +115 -79
- package/src/hooks/use-composer.ts +70 -31
- package/src/hooks/use-computer-session.ts +57 -6
- package/src/hooks/use-file-attachments.ts +16 -3
- package/src/hooks/use-goal.ts +2 -1
- package/src/hooks/use-session-capabilities.ts +3 -2
- package/src/hooks/use-session-events.ts +15 -8
- package/src/hooks/use-slash-commands.ts +6 -11
- package/src/hooks/use-turn-queue.ts +2 -1
- package/src/hooks/use-workspace-sessions.ts +40 -7
- package/src/index.ts +7 -2
- package/src/interaction.ts +1 -0
- package/src/lib/error-message.ts +37 -0
- package/src/lib/format.ts +73 -9
- package/src/lib/interaction-errors.ts +15 -0
- package/src/model-policy.ts +5 -0
- package/src/provider.tsx +12 -4
- package/src/session-ui.ts +1 -1
- package/src/timeline/projection.ts +12 -1
- package/styles/compiled.css +14 -30
- package/dist/chunk-45GE35O7.js.map +0 -1
- package/dist/chunk-6NEZPF53.js.map +0 -1
- package/dist/chunk-CHPKSLPN.js.map +0 -1
- package/dist/chunk-DV2DBNPQ.js.map +0 -1
- package/dist/chunk-FGBCYQOT.js.map +0 -1
- package/dist/chunk-KGENSZKS.js.map +0 -1
- package/dist/chunk-KZ73RL76.js.map +0 -1
- package/dist/chunk-LZ3KHA55.js.map +0 -1
- package/dist/chunk-UJRYEWZI.js.map +0 -1
- package/dist/chunk-VGIKFIO2.js.map +0 -1
- package/dist/chunk-WZ55DVGK.js.map +0 -1
- package/dist/chunk-YRYF2GLG.js.map +0 -1
- package/dist/chunk-YSMMDOOE.js.map +0 -1
- package/dist/chunk-Z3DISEQN.js.map +0 -1
- package/dist/components/timeline-pill-geometry.d.ts +0 -8
- package/dist/components/timeline-pill-placement.d.ts +0 -24
- package/dist/timeline-pill-geometry-Z23V243M.js +0 -110
- package/dist/timeline-pill-geometry-Z23V243M.js.map +0 -1
- package/src/components/timeline-pill-geometry.ts +0 -169
- package/src/components/timeline-pill-placement.ts +0 -99
- /package/dist/{browser-viewer-5X47I6T2.js.map → browser-viewer-63TXMINB.js.map} +0 -0
- /package/dist/{chunk-UINDK6C3.js.map → chunk-2QUSQVF2.js.map} +0 -0
- /package/dist/{chunk-5EXGU7UF.js.map → chunk-DOJWJQ3F.js.map} +0 -0
- /package/dist/{chunk-BI57CQUV.js.map → chunk-I7IWRQLN.js.map} +0 -0
- /package/dist/{chunk-XA6R7DJQ.js.map → chunk-IQUCRAT7.js.map} +0 -0
- /package/dist/{computer-viewer-7GF35SNY.js.map → computer-viewer-X2MBRZY4.js.map} +0 -0
- /package/dist/{latest-question-3BJGKQQ6.js.map → latest-question-CNEZ5KAN.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-PUTA5JOB.js.map → model-policy-picker-menu-JSA64TNN.js.map} +0 -0
- /package/dist/{sandbox-files-AEJTWGT4.js.map → sandbox-files-BBC47SLD.js.map} +0 -0
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "lucide-react";
|
|
26
26
|
import {
|
|
27
27
|
type ClipboardEvent,
|
|
28
|
+
type CompositionEvent,
|
|
28
29
|
type KeyboardEvent,
|
|
29
30
|
type MouseEvent,
|
|
30
31
|
type PointerEvent,
|
|
@@ -290,9 +291,12 @@ export function ComputerViewer({
|
|
|
290
291
|
const rfbStream =
|
|
291
292
|
frames.attachment?.stream.kind === "direct_rfb" ? frames.attachment.stream : null;
|
|
292
293
|
const machineLocked = selectedRegistrySession?.failureCode === "machine_locked";
|
|
294
|
+
const controlUnavailable = computer.controlError !== null;
|
|
295
|
+
const connectionState = controlUnavailable ? "error" : frames.state;
|
|
293
296
|
// RFB has one combined input switch; partial native input stays view-only.
|
|
294
297
|
const rfbInputEnabled =
|
|
295
298
|
!machineLocked &&
|
|
299
|
+
!controlUnavailable &&
|
|
296
300
|
computer.session?.capabilities?.pointerInput === true &&
|
|
297
301
|
computer.session?.capabilities?.keyboardInput === true;
|
|
298
302
|
const rfbCapability = useMemo<DesktopStreamCapability | null>(() => {
|
|
@@ -441,6 +445,7 @@ export function ComputerViewer({
|
|
|
441
445
|
|
|
442
446
|
const perform = useCallback(
|
|
443
447
|
async (action: ComputerAction, frame: ComputerFrame | null): Promise<void> => {
|
|
448
|
+
if (computer.controlError) throw computer.controlError;
|
|
444
449
|
let receipt;
|
|
445
450
|
if (action.type === "pointer") {
|
|
446
451
|
if (!frame) throw new Error("Desktop view is not ready for pointer input.");
|
|
@@ -452,9 +457,14 @@ export function ComputerViewer({
|
|
|
452
457
|
throw new Error(receipt.error?.message ?? "Desktop input did not complete.");
|
|
453
458
|
}
|
|
454
459
|
},
|
|
455
|
-
[act, actFromFrame],
|
|
460
|
+
[act, actFromFrame, computer.controlError],
|
|
456
461
|
);
|
|
457
462
|
|
|
463
|
+
const reconnect = () => {
|
|
464
|
+
void refreshComputer();
|
|
465
|
+
frames.reconnect();
|
|
466
|
+
};
|
|
467
|
+
|
|
458
468
|
const copyFromRfb = useCallback(
|
|
459
469
|
(event: ClipboardEvent<HTMLDivElement>) => {
|
|
460
470
|
if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
|
|
@@ -586,7 +596,7 @@ export function ComputerViewer({
|
|
|
586
596
|
}}
|
|
587
597
|
onCreate={hideGenericCreate ? undefined : createComputer}
|
|
588
598
|
onRefresh={() => {
|
|
589
|
-
if (
|
|
599
|
+
if (connectionState === "error" && !generationLossSelected && !generationLossFrames) {
|
|
590
600
|
frames.reconnect();
|
|
591
601
|
}
|
|
592
602
|
void Promise.all([refreshRegistry(), refreshComputer()]);
|
|
@@ -644,7 +654,20 @@ export function ComputerViewer({
|
|
|
644
654
|
/>
|
|
645
655
|
{!rfbInputEnabled ? (
|
|
646
656
|
<div className="pointer-events-none absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full border border-white/10 bg-black/70 px-3 py-1 text-[11px] text-white/80 backdrop-blur">
|
|
647
|
-
|
|
657
|
+
{controlUnavailable ? (
|
|
658
|
+
<>
|
|
659
|
+
Desktop controls unavailable{" "}
|
|
660
|
+
<button
|
|
661
|
+
type="button"
|
|
662
|
+
onClick={reconnect}
|
|
663
|
+
className="pointer-events-auto ml-2 font-medium underline"
|
|
664
|
+
>
|
|
665
|
+
Reconnect
|
|
666
|
+
</button>
|
|
667
|
+
</>
|
|
668
|
+
) : (
|
|
669
|
+
"View only · direct input unavailable"
|
|
670
|
+
)}
|
|
648
671
|
</div>
|
|
649
672
|
) : null}
|
|
650
673
|
</div>
|
|
@@ -660,19 +683,19 @@ export function ComputerViewer({
|
|
|
660
683
|
observation={computer.observation}
|
|
661
684
|
target={computer.selectedTarget}
|
|
662
685
|
machineLocked={machineLocked}
|
|
663
|
-
|
|
664
|
-
|
|
686
|
+
controlUnavailable={controlUnavailable}
|
|
687
|
+
connectionState={connectionState}
|
|
688
|
+
connectionError={computer.controlError ?? frames.error ?? computer.error}
|
|
665
689
|
mutating={computer.mutating}
|
|
666
690
|
backgroundActions={computer.session?.capabilities?.backgroundActions === true}
|
|
691
|
+
backgroundInput={computer.session?.capabilities?.backgroundInput === true}
|
|
667
692
|
clipboardEnabled={computer.session?.capabilities?.clipboard === true}
|
|
668
693
|
pointerInput={computer.session?.capabilities?.pointerInput === true}
|
|
669
694
|
keyboardInput={computer.session?.capabilities?.keyboardInput === true}
|
|
670
695
|
onAction={perform}
|
|
671
696
|
onReadClipboard={computer.readClipboard}
|
|
672
697
|
onReconnect={
|
|
673
|
-
generationLossSelected || generationLossFrames
|
|
674
|
-
? () => undefined
|
|
675
|
-
: frames.reconnect
|
|
698
|
+
generationLossSelected || generationLossFrames ? () => undefined : reconnect
|
|
676
699
|
}
|
|
677
700
|
onError={(cause) => notifyError(cause, "Desktop input failed.")}
|
|
678
701
|
/>
|
|
@@ -680,7 +703,8 @@ export function ComputerViewer({
|
|
|
680
703
|
{showControls ? (
|
|
681
704
|
<ComputerSemanticPanel
|
|
682
705
|
observation={computer.observation}
|
|
683
|
-
mutating={computer.mutating}
|
|
706
|
+
mutating={computer.mutating || controlUnavailable}
|
|
707
|
+
controlUnavailable={controlUnavailable}
|
|
684
708
|
onAction={(action) =>
|
|
685
709
|
void perform(action, null).catch((cause) =>
|
|
686
710
|
notifyError(cause, "Desktop action failed."),
|
|
@@ -692,10 +716,11 @@ export function ComputerViewer({
|
|
|
692
716
|
<ComputerStatusBar
|
|
693
717
|
session={computer.session}
|
|
694
718
|
target={computer.selectedTarget}
|
|
695
|
-
connectionState={
|
|
719
|
+
connectionState={connectionState}
|
|
720
|
+
controlUnavailable={controlUnavailable}
|
|
696
721
|
refreshing={registry.refreshing}
|
|
697
722
|
showControls={showControls}
|
|
698
|
-
controlCount={
|
|
723
|
+
controlCount={semanticControls(computer.observation).length}
|
|
699
724
|
onToggleControls={() => setShowControls((current) => !current)}
|
|
700
725
|
/>
|
|
701
726
|
</>
|
|
@@ -1025,10 +1050,12 @@ function ComputerViewport(props: {
|
|
|
1025
1050
|
observation: ComputerObservation | null;
|
|
1026
1051
|
target: ComputerTarget | null;
|
|
1027
1052
|
machineLocked: boolean;
|
|
1053
|
+
controlUnavailable: boolean;
|
|
1028
1054
|
connectionState: string;
|
|
1029
1055
|
connectionError: Error | null;
|
|
1030
1056
|
mutating: boolean;
|
|
1031
1057
|
backgroundActions: boolean;
|
|
1058
|
+
backgroundInput: boolean;
|
|
1032
1059
|
clipboardEnabled: boolean;
|
|
1033
1060
|
pointerInput: boolean;
|
|
1034
1061
|
keyboardInput: boolean;
|
|
@@ -1039,6 +1066,7 @@ function ComputerViewport(props: {
|
|
|
1039
1066
|
}) {
|
|
1040
1067
|
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
1041
1068
|
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
|
1069
|
+
const composingRef = useRef(false);
|
|
1042
1070
|
const pointerStartRef = useRef<PointerStart | null>(null);
|
|
1043
1071
|
const clickTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
1044
1072
|
const lastClickRef = useRef<{
|
|
@@ -1076,8 +1104,12 @@ function ComputerViewport(props: {
|
|
|
1076
1104
|
const streamFailed = props.connectionState === "error";
|
|
1077
1105
|
const rawInputEnabled =
|
|
1078
1106
|
!streamFailed &&
|
|
1107
|
+
!props.controlUnavailable &&
|
|
1079
1108
|
!props.machineLocked &&
|
|
1080
|
-
(
|
|
1109
|
+
(props.backgroundInput ||
|
|
1110
|
+
!props.backgroundActions ||
|
|
1111
|
+
props.target?.kind === "screen" ||
|
|
1112
|
+
props.target?.focused === true);
|
|
1081
1113
|
|
|
1082
1114
|
const clearBufferedInput = useCallback(() => {
|
|
1083
1115
|
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
|
|
@@ -1088,12 +1120,29 @@ function ComputerViewport(props: {
|
|
|
1088
1120
|
pendingTextRef.current = null;
|
|
1089
1121
|
pointerStartRef.current = null;
|
|
1090
1122
|
lastClickRef.current = null;
|
|
1123
|
+
composingRef.current = false;
|
|
1091
1124
|
if (inputRef.current) inputRef.current.value = "";
|
|
1092
1125
|
}, []);
|
|
1093
1126
|
|
|
1094
1127
|
const pointerInputEnabled = rawInputEnabled && props.pointerInput;
|
|
1095
1128
|
const keyboardInputEnabled = rawInputEnabled && props.keyboardInput;
|
|
1096
1129
|
|
|
1130
|
+
useLayoutEffect(() => {
|
|
1131
|
+
const canvas = canvasRef.current;
|
|
1132
|
+
const viewport = canvas?.parentElement;
|
|
1133
|
+
if (!canvas || !viewport) return;
|
|
1134
|
+
const fit = () => fitComputerCanvas(canvas);
|
|
1135
|
+
fit();
|
|
1136
|
+
if (typeof ResizeObserver === "undefined") {
|
|
1137
|
+
window.addEventListener("resize", fit);
|
|
1138
|
+
return () => window.removeEventListener("resize", fit);
|
|
1139
|
+
}
|
|
1140
|
+
// The canvas is absolute, so fitting it cannot resize the observed dock.
|
|
1141
|
+
const observer = new ResizeObserver(fit);
|
|
1142
|
+
observer.observe(viewport);
|
|
1143
|
+
return () => observer.disconnect();
|
|
1144
|
+
}, []);
|
|
1145
|
+
|
|
1097
1146
|
const paintQueuedFrames = useCallback(() => {
|
|
1098
1147
|
if (decodingFrameRef.current) return;
|
|
1099
1148
|
decodingFrameRef.current = true;
|
|
@@ -1403,6 +1452,13 @@ function ComputerViewport(props: {
|
|
|
1403
1452
|
|
|
1404
1453
|
const keyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
1405
1454
|
if (!keyboardInputEnabled) return;
|
|
1455
|
+
if (
|
|
1456
|
+
composingRef.current ||
|
|
1457
|
+
event.nativeEvent.isComposing ||
|
|
1458
|
+
event.nativeEvent.keyCode === 229
|
|
1459
|
+
) {
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1406
1462
|
flushPendingWheel();
|
|
1407
1463
|
const command = event.metaKey || event.ctrlKey;
|
|
1408
1464
|
if (
|
|
@@ -1456,7 +1512,8 @@ function ComputerViewport(props: {
|
|
|
1456
1512
|
});
|
|
1457
1513
|
};
|
|
1458
1514
|
|
|
1459
|
-
const input = (value: string) => {
|
|
1515
|
+
const input = (value: string, nativeComposing = false) => {
|
|
1516
|
+
if (composingRef.current || nativeComposing) return;
|
|
1460
1517
|
if (!value || !keyboardInputEnabled) return;
|
|
1461
1518
|
flushPendingWheel();
|
|
1462
1519
|
flushPendingClick();
|
|
@@ -1474,6 +1531,11 @@ function ComputerViewport(props: {
|
|
|
1474
1531
|
if (inputRef.current) inputRef.current.value = "";
|
|
1475
1532
|
};
|
|
1476
1533
|
|
|
1534
|
+
const compositionEnd = (event: CompositionEvent<HTMLTextAreaElement>) => {
|
|
1535
|
+
composingRef.current = false;
|
|
1536
|
+
input(event.currentTarget.value || event.data);
|
|
1537
|
+
};
|
|
1538
|
+
|
|
1477
1539
|
const showCanvas =
|
|
1478
1540
|
props.frame !== null &&
|
|
1479
1541
|
paintedFrame !== null &&
|
|
@@ -1501,7 +1563,13 @@ function ComputerViewport(props: {
|
|
|
1501
1563
|
<textarea
|
|
1502
1564
|
ref={inputRef}
|
|
1503
1565
|
defaultValue=""
|
|
1504
|
-
onInput={(event) =>
|
|
1566
|
+
onInput={(event) =>
|
|
1567
|
+
input(event.currentTarget.value, (event.nativeEvent as InputEvent).isComposing)
|
|
1568
|
+
}
|
|
1569
|
+
onCompositionStart={() => {
|
|
1570
|
+
composingRef.current = true;
|
|
1571
|
+
}}
|
|
1572
|
+
onCompositionEnd={compositionEnd}
|
|
1505
1573
|
onKeyDown={keyDown}
|
|
1506
1574
|
onCopy={copy}
|
|
1507
1575
|
onPaste={paste}
|
|
@@ -1516,6 +1584,7 @@ function ComputerViewport(props: {
|
|
|
1516
1584
|
<ComputerViewportFallback
|
|
1517
1585
|
observation={props.observation}
|
|
1518
1586
|
machineLocked={props.machineLocked}
|
|
1587
|
+
controlUnavailable={props.controlUnavailable}
|
|
1519
1588
|
connectionState={props.connectionState}
|
|
1520
1589
|
error={props.connectionError}
|
|
1521
1590
|
onAction={(action) => enqueue(action, null)}
|
|
@@ -1563,6 +1632,7 @@ function assertExactComputerClipboard(clipboard: ComputerClipboard, expected: st
|
|
|
1563
1632
|
function ComputerViewportFallback(props: {
|
|
1564
1633
|
observation: ComputerObservation | null;
|
|
1565
1634
|
machineLocked: boolean;
|
|
1635
|
+
controlUnavailable: boolean;
|
|
1566
1636
|
connectionState: string;
|
|
1567
1637
|
error: Error | null;
|
|
1568
1638
|
onAction: (action: ComputerAction) => void;
|
|
@@ -1582,7 +1652,7 @@ function ComputerViewportFallback(props: {
|
|
|
1582
1652
|
);
|
|
1583
1653
|
}
|
|
1584
1654
|
const controlFailure = interactionControlFailureFromError(props.error);
|
|
1585
|
-
const interactive =
|
|
1655
|
+
const interactive = semanticControls(props.observation)
|
|
1586
1656
|
.filter((node) => semanticAction(node) !== null)
|
|
1587
1657
|
.slice(0, 10);
|
|
1588
1658
|
return (
|
|
@@ -1597,9 +1667,11 @@ function ComputerViewportFallback(props: {
|
|
|
1597
1667
|
<p className="text-og-menu font-medium text-og-fg">
|
|
1598
1668
|
{isAttachedChromeGenerationLossError(props.error)
|
|
1599
1669
|
? "Chrome reconnected—open a fresh browser/desktop."
|
|
1600
|
-
: props.
|
|
1601
|
-
? "
|
|
1602
|
-
:
|
|
1670
|
+
: props.controlUnavailable
|
|
1671
|
+
? "Desktop controls unavailable"
|
|
1672
|
+
: props.error
|
|
1673
|
+
? "Live view disconnected"
|
|
1674
|
+
: computerConnectionLabel(props.connectionState)}
|
|
1603
1675
|
</p>
|
|
1604
1676
|
</div>
|
|
1605
1677
|
{props.error ? (
|
|
@@ -1609,7 +1681,7 @@ function ComputerViewportFallback(props: {
|
|
|
1609
1681
|
: (controlFailure?.message ?? props.error.message)}
|
|
1610
1682
|
</p>
|
|
1611
1683
|
) : null}
|
|
1612
|
-
{interactive.length > 0 ? (
|
|
1684
|
+
{interactive.length > 0 && !props.controlUnavailable ? (
|
|
1613
1685
|
<div className="mt-3 border-t border-og-border pt-3">
|
|
1614
1686
|
<p className="mb-2 text-og-xs text-og-fg-subtle">App controls remain available</p>
|
|
1615
1687
|
<div className="flex flex-wrap gap-1.5">
|
|
@@ -1646,18 +1718,26 @@ function ComputerViewportFallback(props: {
|
|
|
1646
1718
|
function ComputerSemanticPanel(props: {
|
|
1647
1719
|
observation: ComputerObservation | null;
|
|
1648
1720
|
mutating: boolean;
|
|
1721
|
+
controlUnavailable: boolean;
|
|
1649
1722
|
onAction: (action: ComputerAction) => void;
|
|
1650
1723
|
}) {
|
|
1651
|
-
const nodes =
|
|
1724
|
+
const nodes = semanticControls(props.observation).slice(0, 100);
|
|
1652
1725
|
return (
|
|
1653
1726
|
<aside className="box-border max-h-[40%] w-full shrink-0 overflow-y-auto border-t border-og-border bg-og-surface-1 p-2 @xl/computer-viewer:max-h-none @xl/computer-viewer:w-64 @xl/computer-viewer:border-t-0 @xl/computer-viewer:border-l">
|
|
1654
1727
|
<div className="mb-2 flex items-center gap-1.5 px-1 text-og-xs font-medium uppercase tracking-[0.1em] text-og-fg-subtle">
|
|
1655
1728
|
<KeyboardIcon className="size-3" /> App controls
|
|
1656
1729
|
</div>
|
|
1657
|
-
{
|
|
1730
|
+
{props.controlUnavailable ? (
|
|
1658
1731
|
<p className="px-1 py-2 text-og-control leading-5 text-og-fg-muted">
|
|
1659
|
-
|
|
1732
|
+
Reconnect to use app controls.
|
|
1660
1733
|
</p>
|
|
1734
|
+
) : null}
|
|
1735
|
+
{nodes.length === 0 ? (
|
|
1736
|
+
props.controlUnavailable ? null : (
|
|
1737
|
+
<p className="px-1 py-2 text-og-control leading-5 text-og-fg-muted">
|
|
1738
|
+
This view has no accessibility controls. Use the live image instead.
|
|
1739
|
+
</p>
|
|
1740
|
+
)
|
|
1661
1741
|
) : (
|
|
1662
1742
|
<div className="space-y-0.5">
|
|
1663
1743
|
{nodes.map((node) => (
|
|
@@ -1747,6 +1827,7 @@ function ComputerStatusBar(props: {
|
|
|
1747
1827
|
session: ComputerSession | null;
|
|
1748
1828
|
target: ComputerTarget | null;
|
|
1749
1829
|
connectionState: string;
|
|
1830
|
+
controlUnavailable: boolean;
|
|
1750
1831
|
refreshing: boolean;
|
|
1751
1832
|
showControls: boolean;
|
|
1752
1833
|
controlCount: number;
|
|
@@ -1762,14 +1843,22 @@ function ComputerStatusBar(props: {
|
|
|
1762
1843
|
)}
|
|
1763
1844
|
/>
|
|
1764
1845
|
<span>
|
|
1765
|
-
{props.
|
|
1846
|
+
{props.controlUnavailable
|
|
1847
|
+
? "Controls unavailable"
|
|
1848
|
+
: props.connectionState === "live"
|
|
1849
|
+
? "Live"
|
|
1850
|
+
: computerConnectionLabel(props.connectionState)}
|
|
1766
1851
|
</span>
|
|
1767
1852
|
<span className="min-w-0 flex-1 truncate">
|
|
1768
|
-
{
|
|
1769
|
-
? "
|
|
1770
|
-
:
|
|
1771
|
-
? "
|
|
1772
|
-
:
|
|
1853
|
+
{props.controlUnavailable
|
|
1854
|
+
? "Reconnect to use desktop input"
|
|
1855
|
+
: screen
|
|
1856
|
+
? "Full screen · input may move pointer and focus"
|
|
1857
|
+
: props.session?.capabilities?.backgroundInput
|
|
1858
|
+
? "Window · clicks and typing stay in the background"
|
|
1859
|
+
: props.session?.capabilities?.backgroundActions
|
|
1860
|
+
? "Window · app controls work in the background"
|
|
1861
|
+
: (props.target?.kind ?? "Desktop")}
|
|
1773
1862
|
</span>
|
|
1774
1863
|
{screen ? <MousePointer2Icon className="size-3" aria-hidden /> : null}
|
|
1775
1864
|
{props.refreshing ? <LoaderCircleIcon className="size-3 animate-spin" /> : null}
|
|
@@ -1804,11 +1893,37 @@ function ComputerNotice(props: { icon: ReactNode; text: string; className?: stri
|
|
|
1804
1893
|
);
|
|
1805
1894
|
}
|
|
1806
1895
|
|
|
1807
|
-
|
|
1896
|
+
const structuralRoles = new Set([
|
|
1897
|
+
"frame",
|
|
1898
|
+
"panel",
|
|
1899
|
+
"section",
|
|
1900
|
+
"document",
|
|
1901
|
+
"document web",
|
|
1902
|
+
"static",
|
|
1903
|
+
"static text",
|
|
1904
|
+
"heading",
|
|
1905
|
+
"paragraph",
|
|
1906
|
+
"notification",
|
|
1907
|
+
"separator",
|
|
1908
|
+
"tool bar",
|
|
1909
|
+
"page tab list",
|
|
1910
|
+
]);
|
|
1911
|
+
|
|
1912
|
+
function semanticControls(observation: ComputerObservation | null): InteractionSemanticNode[] {
|
|
1808
1913
|
if (observation?.semantic?.kind !== "snapshot") return [];
|
|
1809
1914
|
const result: InteractionSemanticNode[] = [];
|
|
1810
1915
|
const visit = (node: InteractionSemanticNode) => {
|
|
1811
|
-
|
|
1916
|
+
const role = node.role.trim().toLowerCase();
|
|
1917
|
+
const label = node.name?.trim() || node.identifier?.trim();
|
|
1918
|
+
if (
|
|
1919
|
+
node.actions.includes("set_value") ||
|
|
1920
|
+
(label &&
|
|
1921
|
+
label.toLowerCase() !== role &&
|
|
1922
|
+
!structuralRoles.has(role) &&
|
|
1923
|
+
semanticAction(node) !== null)
|
|
1924
|
+
) {
|
|
1925
|
+
result.push(node);
|
|
1926
|
+
}
|
|
1812
1927
|
node.children?.forEach(visit);
|
|
1813
1928
|
};
|
|
1814
1929
|
observation.semantic.roots.forEach(visit);
|
|
@@ -1840,11 +1955,26 @@ function paintCanvas(
|
|
|
1840
1955
|
width: number,
|
|
1841
1956
|
height: number,
|
|
1842
1957
|
): void {
|
|
1958
|
+
const dimensionsChanged = canvas.width !== width || canvas.height !== height;
|
|
1843
1959
|
canvas.width = width;
|
|
1844
1960
|
canvas.height = height;
|
|
1845
1961
|
const context = canvas.getContext("2d", { alpha: false });
|
|
1846
1962
|
if (!context) throw new Error("Desktop canvas is unavailable.");
|
|
1847
1963
|
context.drawImage(image, 0, 0, width, height);
|
|
1964
|
+
if (dimensionsChanged) fitComputerCanvas(canvas);
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
function fitComputerCanvas(canvas: HTMLCanvasElement): void {
|
|
1968
|
+
const viewport = canvas.parentElement;
|
|
1969
|
+
if (!viewport || canvas.width <= 0 || canvas.height <= 0) return;
|
|
1970
|
+
const scale = Math.min(
|
|
1971
|
+
viewport.clientWidth / canvas.width,
|
|
1972
|
+
viewport.clientHeight / canvas.height,
|
|
1973
|
+
);
|
|
1974
|
+
// Hidden docks retain their previous fit until their content box is visible.
|
|
1975
|
+
if (!Number.isFinite(scale) || scale <= 0) return;
|
|
1976
|
+
canvas.style.width = `${canvas.width * scale}px`;
|
|
1977
|
+
canvas.style.height = `${canvas.height * scale}px`;
|
|
1848
1978
|
}
|
|
1849
1979
|
|
|
1850
1980
|
function loadImage(url: string): Promise<HTMLImageElement> {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { GeneratedVideoReceipt, VideoArtifactPlaybackSource } from "@opengeni/sdk";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
|
+
import { useErrorMessage } from "../lib/error-message";
|
|
4
5
|
import type { VideoArtifactPlaybackLoader } from "../timeline";
|
|
5
6
|
|
|
6
7
|
export type GeneratedVideoPlayerProps = {
|
|
@@ -22,6 +23,7 @@ export function GeneratedVideoPlayer({
|
|
|
22
23
|
className,
|
|
23
24
|
label = "Generated video",
|
|
24
25
|
}: GeneratedVideoPlayerProps) {
|
|
26
|
+
const formatError = useErrorMessage();
|
|
25
27
|
const [retry, setRetry] = useState(0);
|
|
26
28
|
const [state, setState] = useState<SourceState>({ kind: "loading" });
|
|
27
29
|
|
|
@@ -36,12 +38,12 @@ export function GeneratedVideoPlayer({
|
|
|
36
38
|
if (controller.signal.aborted) return;
|
|
37
39
|
setState({
|
|
38
40
|
kind: "error",
|
|
39
|
-
message: error
|
|
41
|
+
message: formatError(error),
|
|
40
42
|
});
|
|
41
43
|
},
|
|
42
44
|
);
|
|
43
45
|
return () => controller.abort();
|
|
44
|
-
}, [loadPlaybackSource, receipt.artifact.artifactId, retry]);
|
|
46
|
+
}, [loadPlaybackSource, receipt.artifact.artifactId, retry, formatError]);
|
|
45
47
|
|
|
46
48
|
if (state.kind === "loading") {
|
|
47
49
|
return (
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
import { ChevronDownIcon, ChevronUpIcon, MessageCircleQuestionIcon } from "lucide-react";
|
|
9
9
|
import { useEffect, useId, useRef, useState, type FormEvent, type ReactNode } from "react";
|
|
10
10
|
import { cn } from "../lib/cn";
|
|
11
|
+
import { useErrorMessage } from "../lib/error-message";
|
|
11
12
|
|
|
12
13
|
export type HumanInputAnswerDraft = {
|
|
13
14
|
values: string[];
|
|
@@ -15,6 +16,8 @@ export type HumanInputAnswerDraft = {
|
|
|
15
16
|
otherSelected: boolean;
|
|
16
17
|
};
|
|
17
18
|
|
|
19
|
+
class SkillReviewVerificationError extends Error {}
|
|
20
|
+
|
|
18
21
|
export type HumanInputFormMessages = {
|
|
19
22
|
title: string;
|
|
20
23
|
description: string;
|
|
@@ -116,6 +119,7 @@ function HumanInputRequestForm({
|
|
|
116
119
|
defaultCollapsed = false,
|
|
117
120
|
className,
|
|
118
121
|
}: HumanInputFormProps) {
|
|
122
|
+
const formatError = useErrorMessage();
|
|
119
123
|
const messages = { ...defaultHumanInputFormMessages, ...messageOverrides };
|
|
120
124
|
const singleQuestion = request.questions.length === 1 ? request.questions[0]! : null;
|
|
121
125
|
const resolvedTitle =
|
|
@@ -184,7 +188,7 @@ function HumanInputRequestForm({
|
|
|
184
188
|
questions.map(async (question) => {
|
|
185
189
|
if (!loadSkillReview)
|
|
186
190
|
throw new Error(
|
|
187
|
-
"This client cannot preview Skill files.
|
|
191
|
+
"This client cannot preview Skill files. Ask your administrator for a review-capable client.",
|
|
188
192
|
);
|
|
189
193
|
const reference = question.reference;
|
|
190
194
|
const record = await loadSkillReview(reference);
|
|
@@ -194,7 +198,9 @@ function HumanInputRequestForm({
|
|
|
194
198
|
(record.removalOperationId ?? undefined) !== reference.removalOperationId ||
|
|
195
199
|
!record.files.some((file) => file.path === "SKILL.md")
|
|
196
200
|
) {
|
|
197
|
-
throw new
|
|
201
|
+
throw new SkillReviewVerificationError(
|
|
202
|
+
"The requested Skill revision could not be verified.",
|
|
203
|
+
);
|
|
198
204
|
}
|
|
199
205
|
return [question.id, record] as const;
|
|
200
206
|
}),
|
|
@@ -214,13 +220,20 @@ function HumanInputRequestForm({
|
|
|
214
220
|
loader: loadSkillReview,
|
|
215
221
|
identity: reviewIdentity,
|
|
216
222
|
records: {},
|
|
217
|
-
error:
|
|
223
|
+
error: formatError(
|
|
224
|
+
cause,
|
|
225
|
+
cause instanceof SkillReviewVerificationError
|
|
226
|
+
? cause.message
|
|
227
|
+
: !loadSkillReview
|
|
228
|
+
? "This client cannot preview Skill files. Ask your administrator for a review-capable client."
|
|
229
|
+
: undefined,
|
|
230
|
+
),
|
|
218
231
|
});
|
|
219
232
|
});
|
|
220
233
|
return () => {
|
|
221
234
|
current = false;
|
|
222
235
|
};
|
|
223
|
-
}, [loadSkillReview, reviewIdentity, reviewReload]);
|
|
236
|
+
}, [loadSkillReview, reviewIdentity, reviewReload, formatError]);
|
|
224
237
|
const visibleReviews =
|
|
225
238
|
reviews?.loader === loadSkillReview && reviews?.identity === reviewIdentity ? reviews : null;
|
|
226
239
|
const preview = (question: HumanInputQuestion) => {
|
|
@@ -319,7 +332,7 @@ function HumanInputRequestForm({
|
|
|
319
332
|
await onSubmit(response);
|
|
320
333
|
} catch (cause) {
|
|
321
334
|
if (generation === submissionGeneration.current) {
|
|
322
|
-
setSubmissionError(
|
|
335
|
+
setSubmissionError(formatError(cause));
|
|
323
336
|
}
|
|
324
337
|
} finally {
|
|
325
338
|
if (generation === submissionGeneration.current) {
|