@opengeni/react 7.6.1-canary.37073905159001 → 7.6.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 +2 -19
- package/dist/{browser-viewer-63TXMINB.js → browser-viewer-5X47I6T2.js} +5 -6
- package/dist/{chunk-TKB4SB44.js → chunk-45GE35O7.js} +2 -10
- package/dist/chunk-45GE35O7.js.map +1 -0
- package/dist/{chunk-DOJWJQ3F.js → chunk-5EXGU7UF.js} +3 -3
- package/dist/{chunk-YN7I3STW.js → chunk-6NEZPF53.js} +32 -153
- package/dist/chunk-6NEZPF53.js.map +1 -0
- package/dist/{chunk-I7IWRQLN.js → chunk-BI57CQUV.js} +2 -2
- package/dist/{chunk-6ZSWSH4I.js → chunk-CHPKSLPN.js} +1 -3
- package/dist/chunk-CHPKSLPN.js.map +1 -0
- package/dist/{chunk-Q5QBEZIQ.js → chunk-DV2DBNPQ.js} +29 -48
- package/dist/chunk-DV2DBNPQ.js.map +1 -0
- package/dist/{chunk-RDKBXMWX.js → chunk-FGBCYQOT.js} +74 -120
- package/dist/chunk-FGBCYQOT.js.map +1 -0
- package/dist/{chunk-AAG4R3QN.js → chunk-KGENSZKS.js} +3 -4
- package/dist/chunk-KGENSZKS.js.map +1 -0
- package/dist/{chunk-YQEAA6LT.js → chunk-KZ73RL76.js} +3 -7
- package/dist/chunk-KZ73RL76.js.map +1 -0
- package/dist/{chunk-LFXYGZWK.js → chunk-LZ3KHA55.js} +60 -169
- package/dist/chunk-LZ3KHA55.js.map +1 -0
- package/dist/{chunk-2QUSQVF2.js → chunk-UINDK6C3.js} +3 -3
- package/dist/{chunk-UTRTG6LH.js → chunk-UJRYEWZI.js} +8 -48
- package/dist/chunk-UJRYEWZI.js.map +1 -0
- package/dist/{chunk-XOAMRDDK.js → chunk-VGIKFIO2.js} +3 -6
- package/dist/chunk-VGIKFIO2.js.map +1 -0
- package/dist/{chunk-ELCE5SXV.js → chunk-WZ55DVGK.js} +4 -8
- package/dist/chunk-WZ55DVGK.js.map +1 -0
- package/dist/{chunk-IQUCRAT7.js → chunk-XA6R7DJQ.js} +3 -3
- package/dist/{chunk-TOAH2BKA.js → chunk-YCGBREUV.js} +2 -6
- package/dist/{chunk-TOAH2BKA.js.map → chunk-YCGBREUV.js.map} +1 -1
- package/dist/{chunk-HNFCAQ5R.js → chunk-YRYF2GLG.js} +31 -39
- package/dist/chunk-YRYF2GLG.js.map +1 -0
- package/dist/{chunk-GISEVGS6.js → chunk-YSMMDOOE.js} +2 -10
- package/dist/chunk-YSMMDOOE.js.map +1 -0
- package/dist/{chunk-CWWKTNKK.js → chunk-Z3DISEQN.js} +18 -35
- package/dist/chunk-Z3DISEQN.js.map +1 -0
- package/dist/client.d.ts +1 -4
- package/dist/commands/types.d.ts +0 -2
- package/dist/components/approval-surface.d.ts +0 -15
- package/dist/composer.js +7 -8
- package/dist/{computer-viewer-X2MBRZY4.js → computer-viewer-7GF35SNY.js} +4 -5
- package/dist/connect.js +3 -3
- package/dist/hooks/use-browser-session.d.ts +0 -4
- package/dist/hooks/use-composer.d.ts +0 -2
- package/dist/hooks/use-computer-session.d.ts +0 -2
- package/dist/hooks/use-file-attachments.d.ts +0 -2
- package/dist/hooks/use-session-events.d.ts +1 -1
- package/dist/hooks/use-workspace-sessions.d.ts +5 -15
- package/dist/index.d.ts +2 -2
- package/dist/index.js +21 -28
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +1 -1
- package/dist/interaction.js +5 -6
- package/dist/interaction.js.map +1 -1
- package/dist/{latest-question-CNEZ5KAN.js → latest-question-3BJGKQQ6.js} +3 -3
- package/dist/lib/format.d.ts +3 -27
- package/dist/lib/interaction-errors.d.ts +0 -3
- package/dist/machines.js +4 -5
- package/dist/{model-policy-picker-menu-JSA64TNN.js → model-policy-picker-menu-PUTA5JOB.js} +4 -4
- package/dist/model-policy.js +1 -1
- package/dist/provider.d.ts +1 -5
- package/dist/{queue-surface-implementation-HENWYFWA.js → queue-surface-implementation-2CAQAIHM.js} +2 -3
- package/dist/{queue-surface-implementation-HENWYFWA.js.map → queue-surface-implementation-2CAQAIHM.js.map} +1 -1
- package/dist/realtime.js +2 -2
- package/dist/{sandbox-files-BBC47SLD.js → sandbox-files-AEJTWGT4.js} +3 -4
- package/dist/session-ui.d.ts +1 -1
- package/dist/session-ui.js +12 -13
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +6 -7
- package/dist/usage.js +1 -2
- package/dist/usage.js.map +1 -1
- package/package.json +3 -3
- package/src/client.ts +1 -5
- package/src/commands/registry.ts +15 -18
- package/src/commands/types.ts +0 -2
- package/src/components/approval-surface.tsx +42 -166
- package/src/components/browser-viewer.tsx +9 -38
- package/src/components/composer.tsx +2 -9
- package/src/components/computer-viewer.tsx +30 -160
- package/src/components/generated-video-player.tsx +2 -4
- package/src/components/human-input-form.tsx +5 -18
- package/src/components/message-timeline.tsx +2 -4
- package/src/components/open-geni-chat.tsx +4 -11
- package/src/components/queue-surface-state.tsx +1 -3
- package/src/components/session-chrome.tsx +2 -5
- package/src/components/session-commands-panel.tsx +1 -3
- package/src/components/session-conversation.tsx +2 -4
- package/src/components/session-list.tsx +5 -7
- package/src/components/session-mcp-capability-card.tsx +1 -11
- package/src/hooks/internal.ts +2 -3
- package/src/hooks/use-browser-session.ts +79 -115
- package/src/hooks/use-composer.ts +31 -70
- package/src/hooks/use-computer-session.ts +6 -57
- package/src/hooks/use-file-attachments.ts +3 -16
- package/src/hooks/use-goal.ts +1 -2
- package/src/hooks/use-session-capabilities.ts +2 -3
- package/src/hooks/use-session-events.ts +8 -15
- package/src/hooks/use-slash-commands.ts +11 -6
- package/src/hooks/use-turn-queue.ts +1 -2
- package/src/hooks/use-workspace-sessions.ts +7 -40
- package/src/index.ts +2 -7
- package/src/interaction.ts +0 -1
- package/src/lib/format.ts +9 -73
- package/src/lib/interaction-errors.ts +0 -15
- package/src/model-policy.ts +0 -5
- package/src/provider.tsx +4 -12
- package/src/session-ui.ts +1 -1
- package/src/timeline/projection.ts +1 -12
- package/styles/compiled.css +0 -14
- package/dist/chunk-6ZSWSH4I.js.map +0 -1
- package/dist/chunk-AAG4R3QN.js.map +0 -1
- package/dist/chunk-CWWKTNKK.js.map +0 -1
- package/dist/chunk-ELCE5SXV.js.map +0 -1
- package/dist/chunk-GISEVGS6.js.map +0 -1
- package/dist/chunk-HNFCAQ5R.js.map +0 -1
- package/dist/chunk-LFXYGZWK.js.map +0 -1
- package/dist/chunk-Q5QBEZIQ.js.map +0 -1
- package/dist/chunk-RDKBXMWX.js.map +0 -1
- package/dist/chunk-TKB4SB44.js.map +0 -1
- package/dist/chunk-UGLEYEMP.js +0 -33
- package/dist/chunk-UGLEYEMP.js.map +0 -1
- package/dist/chunk-UTRTG6LH.js.map +0 -1
- package/dist/chunk-XOAMRDDK.js.map +0 -1
- package/dist/chunk-YN7I3STW.js.map +0 -1
- package/dist/chunk-YQEAA6LT.js.map +0 -1
- package/dist/lib/error-message.d.ts +0 -7
- package/src/lib/error-message.ts +0 -37
- /package/dist/{browser-viewer-63TXMINB.js.map → browser-viewer-5X47I6T2.js.map} +0 -0
- /package/dist/{chunk-DOJWJQ3F.js.map → chunk-5EXGU7UF.js.map} +0 -0
- /package/dist/{chunk-I7IWRQLN.js.map → chunk-BI57CQUV.js.map} +0 -0
- /package/dist/{chunk-2QUSQVF2.js.map → chunk-UINDK6C3.js.map} +0 -0
- /package/dist/{chunk-IQUCRAT7.js.map → chunk-XA6R7DJQ.js.map} +0 -0
- /package/dist/{computer-viewer-X2MBRZY4.js.map → computer-viewer-7GF35SNY.js.map} +0 -0
- /package/dist/{latest-question-CNEZ5KAN.js.map → latest-question-3BJGKQQ6.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-JSA64TNN.js.map → model-policy-picker-menu-PUTA5JOB.js.map} +0 -0
- /package/dist/{sandbox-files-BBC47SLD.js.map → sandbox-files-AEJTWGT4.js.map} +0 -0
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
} from "lucide-react";
|
|
26
26
|
import {
|
|
27
27
|
type ClipboardEvent,
|
|
28
|
-
type CompositionEvent,
|
|
29
28
|
type KeyboardEvent,
|
|
30
29
|
type MouseEvent,
|
|
31
30
|
type PointerEvent,
|
|
@@ -291,12 +290,9 @@ export function ComputerViewer({
|
|
|
291
290
|
const rfbStream =
|
|
292
291
|
frames.attachment?.stream.kind === "direct_rfb" ? frames.attachment.stream : null;
|
|
293
292
|
const machineLocked = selectedRegistrySession?.failureCode === "machine_locked";
|
|
294
|
-
const controlUnavailable = computer.controlError !== null;
|
|
295
|
-
const connectionState = controlUnavailable ? "error" : frames.state;
|
|
296
293
|
// RFB has one combined input switch; partial native input stays view-only.
|
|
297
294
|
const rfbInputEnabled =
|
|
298
295
|
!machineLocked &&
|
|
299
|
-
!controlUnavailable &&
|
|
300
296
|
computer.session?.capabilities?.pointerInput === true &&
|
|
301
297
|
computer.session?.capabilities?.keyboardInput === true;
|
|
302
298
|
const rfbCapability = useMemo<DesktopStreamCapability | null>(() => {
|
|
@@ -445,7 +441,6 @@ export function ComputerViewer({
|
|
|
445
441
|
|
|
446
442
|
const perform = useCallback(
|
|
447
443
|
async (action: ComputerAction, frame: ComputerFrame | null): Promise<void> => {
|
|
448
|
-
if (computer.controlError) throw computer.controlError;
|
|
449
444
|
let receipt;
|
|
450
445
|
if (action.type === "pointer") {
|
|
451
446
|
if (!frame) throw new Error("Desktop view is not ready for pointer input.");
|
|
@@ -457,14 +452,9 @@ export function ComputerViewer({
|
|
|
457
452
|
throw new Error(receipt.error?.message ?? "Desktop input did not complete.");
|
|
458
453
|
}
|
|
459
454
|
},
|
|
460
|
-
[act, actFromFrame
|
|
455
|
+
[act, actFromFrame],
|
|
461
456
|
);
|
|
462
457
|
|
|
463
|
-
const reconnect = () => {
|
|
464
|
-
void refreshComputer();
|
|
465
|
-
frames.reconnect();
|
|
466
|
-
};
|
|
467
|
-
|
|
468
458
|
const copyFromRfb = useCallback(
|
|
469
459
|
(event: ClipboardEvent<HTMLDivElement>) => {
|
|
470
460
|
if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
|
|
@@ -596,7 +586,7 @@ export function ComputerViewer({
|
|
|
596
586
|
}}
|
|
597
587
|
onCreate={hideGenericCreate ? undefined : createComputer}
|
|
598
588
|
onRefresh={() => {
|
|
599
|
-
if (
|
|
589
|
+
if (frames.state === "error" && !generationLossSelected && !generationLossFrames) {
|
|
600
590
|
frames.reconnect();
|
|
601
591
|
}
|
|
602
592
|
void Promise.all([refreshRegistry(), refreshComputer()]);
|
|
@@ -654,20 +644,7 @@ export function ComputerViewer({
|
|
|
654
644
|
/>
|
|
655
645
|
{!rfbInputEnabled ? (
|
|
656
646
|
<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">
|
|
657
|
-
|
|
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
|
-
)}
|
|
647
|
+
View only · direct input unavailable
|
|
671
648
|
</div>
|
|
672
649
|
) : null}
|
|
673
650
|
</div>
|
|
@@ -683,19 +660,19 @@ export function ComputerViewer({
|
|
|
683
660
|
observation={computer.observation}
|
|
684
661
|
target={computer.selectedTarget}
|
|
685
662
|
machineLocked={machineLocked}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
connectionError={computer.controlError ?? frames.error ?? computer.error}
|
|
663
|
+
connectionState={frames.state}
|
|
664
|
+
connectionError={frames.error ?? computer.error}
|
|
689
665
|
mutating={computer.mutating}
|
|
690
666
|
backgroundActions={computer.session?.capabilities?.backgroundActions === true}
|
|
691
|
-
backgroundInput={computer.session?.capabilities?.backgroundInput === true}
|
|
692
667
|
clipboardEnabled={computer.session?.capabilities?.clipboard === true}
|
|
693
668
|
pointerInput={computer.session?.capabilities?.pointerInput === true}
|
|
694
669
|
keyboardInput={computer.session?.capabilities?.keyboardInput === true}
|
|
695
670
|
onAction={perform}
|
|
696
671
|
onReadClipboard={computer.readClipboard}
|
|
697
672
|
onReconnect={
|
|
698
|
-
generationLossSelected || generationLossFrames
|
|
673
|
+
generationLossSelected || generationLossFrames
|
|
674
|
+
? () => undefined
|
|
675
|
+
: frames.reconnect
|
|
699
676
|
}
|
|
700
677
|
onError={(cause) => notifyError(cause, "Desktop input failed.")}
|
|
701
678
|
/>
|
|
@@ -703,8 +680,7 @@ export function ComputerViewer({
|
|
|
703
680
|
{showControls ? (
|
|
704
681
|
<ComputerSemanticPanel
|
|
705
682
|
observation={computer.observation}
|
|
706
|
-
mutating={computer.mutating
|
|
707
|
-
controlUnavailable={controlUnavailable}
|
|
683
|
+
mutating={computer.mutating}
|
|
708
684
|
onAction={(action) =>
|
|
709
685
|
void perform(action, null).catch((cause) =>
|
|
710
686
|
notifyError(cause, "Desktop action failed."),
|
|
@@ -716,11 +692,10 @@ export function ComputerViewer({
|
|
|
716
692
|
<ComputerStatusBar
|
|
717
693
|
session={computer.session}
|
|
718
694
|
target={computer.selectedTarget}
|
|
719
|
-
connectionState={
|
|
720
|
-
controlUnavailable={controlUnavailable}
|
|
695
|
+
connectionState={frames.state}
|
|
721
696
|
refreshing={registry.refreshing}
|
|
722
697
|
showControls={showControls}
|
|
723
|
-
controlCount={
|
|
698
|
+
controlCount={semanticNodes(computer.observation).length}
|
|
724
699
|
onToggleControls={() => setShowControls((current) => !current)}
|
|
725
700
|
/>
|
|
726
701
|
</>
|
|
@@ -1050,12 +1025,10 @@ function ComputerViewport(props: {
|
|
|
1050
1025
|
observation: ComputerObservation | null;
|
|
1051
1026
|
target: ComputerTarget | null;
|
|
1052
1027
|
machineLocked: boolean;
|
|
1053
|
-
controlUnavailable: boolean;
|
|
1054
1028
|
connectionState: string;
|
|
1055
1029
|
connectionError: Error | null;
|
|
1056
1030
|
mutating: boolean;
|
|
1057
1031
|
backgroundActions: boolean;
|
|
1058
|
-
backgroundInput: boolean;
|
|
1059
1032
|
clipboardEnabled: boolean;
|
|
1060
1033
|
pointerInput: boolean;
|
|
1061
1034
|
keyboardInput: boolean;
|
|
@@ -1066,7 +1039,6 @@ function ComputerViewport(props: {
|
|
|
1066
1039
|
}) {
|
|
1067
1040
|
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
1068
1041
|
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
|
1069
|
-
const composingRef = useRef(false);
|
|
1070
1042
|
const pointerStartRef = useRef<PointerStart | null>(null);
|
|
1071
1043
|
const clickTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
1072
1044
|
const lastClickRef = useRef<{
|
|
@@ -1104,12 +1076,8 @@ function ComputerViewport(props: {
|
|
|
1104
1076
|
const streamFailed = props.connectionState === "error";
|
|
1105
1077
|
const rawInputEnabled =
|
|
1106
1078
|
!streamFailed &&
|
|
1107
|
-
!props.controlUnavailable &&
|
|
1108
1079
|
!props.machineLocked &&
|
|
1109
|
-
(props.
|
|
1110
|
-
!props.backgroundActions ||
|
|
1111
|
-
props.target?.kind === "screen" ||
|
|
1112
|
-
props.target?.focused === true);
|
|
1080
|
+
(!props.backgroundActions || props.target?.kind === "screen" || props.target?.focused === true);
|
|
1113
1081
|
|
|
1114
1082
|
const clearBufferedInput = useCallback(() => {
|
|
1115
1083
|
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
|
|
@@ -1120,29 +1088,12 @@ function ComputerViewport(props: {
|
|
|
1120
1088
|
pendingTextRef.current = null;
|
|
1121
1089
|
pointerStartRef.current = null;
|
|
1122
1090
|
lastClickRef.current = null;
|
|
1123
|
-
composingRef.current = false;
|
|
1124
1091
|
if (inputRef.current) inputRef.current.value = "";
|
|
1125
1092
|
}, []);
|
|
1126
1093
|
|
|
1127
1094
|
const pointerInputEnabled = rawInputEnabled && props.pointerInput;
|
|
1128
1095
|
const keyboardInputEnabled = rawInputEnabled && props.keyboardInput;
|
|
1129
1096
|
|
|
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
|
-
|
|
1146
1097
|
const paintQueuedFrames = useCallback(() => {
|
|
1147
1098
|
if (decodingFrameRef.current) return;
|
|
1148
1099
|
decodingFrameRef.current = true;
|
|
@@ -1452,13 +1403,6 @@ function ComputerViewport(props: {
|
|
|
1452
1403
|
|
|
1453
1404
|
const keyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
1454
1405
|
if (!keyboardInputEnabled) return;
|
|
1455
|
-
if (
|
|
1456
|
-
composingRef.current ||
|
|
1457
|
-
event.nativeEvent.isComposing ||
|
|
1458
|
-
event.nativeEvent.keyCode === 229
|
|
1459
|
-
) {
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
1406
|
flushPendingWheel();
|
|
1463
1407
|
const command = event.metaKey || event.ctrlKey;
|
|
1464
1408
|
if (
|
|
@@ -1512,8 +1456,7 @@ function ComputerViewport(props: {
|
|
|
1512
1456
|
});
|
|
1513
1457
|
};
|
|
1514
1458
|
|
|
1515
|
-
const input = (value: string
|
|
1516
|
-
if (composingRef.current || nativeComposing) return;
|
|
1459
|
+
const input = (value: string) => {
|
|
1517
1460
|
if (!value || !keyboardInputEnabled) return;
|
|
1518
1461
|
flushPendingWheel();
|
|
1519
1462
|
flushPendingClick();
|
|
@@ -1531,11 +1474,6 @@ function ComputerViewport(props: {
|
|
|
1531
1474
|
if (inputRef.current) inputRef.current.value = "";
|
|
1532
1475
|
};
|
|
1533
1476
|
|
|
1534
|
-
const compositionEnd = (event: CompositionEvent<HTMLTextAreaElement>) => {
|
|
1535
|
-
composingRef.current = false;
|
|
1536
|
-
input(event.currentTarget.value || event.data);
|
|
1537
|
-
};
|
|
1538
|
-
|
|
1539
1477
|
const showCanvas =
|
|
1540
1478
|
props.frame !== null &&
|
|
1541
1479
|
paintedFrame !== null &&
|
|
@@ -1563,13 +1501,7 @@ function ComputerViewport(props: {
|
|
|
1563
1501
|
<textarea
|
|
1564
1502
|
ref={inputRef}
|
|
1565
1503
|
defaultValue=""
|
|
1566
|
-
onInput={(event) =>
|
|
1567
|
-
input(event.currentTarget.value, (event.nativeEvent as InputEvent).isComposing)
|
|
1568
|
-
}
|
|
1569
|
-
onCompositionStart={() => {
|
|
1570
|
-
composingRef.current = true;
|
|
1571
|
-
}}
|
|
1572
|
-
onCompositionEnd={compositionEnd}
|
|
1504
|
+
onInput={(event) => input(event.currentTarget.value)}
|
|
1573
1505
|
onKeyDown={keyDown}
|
|
1574
1506
|
onCopy={copy}
|
|
1575
1507
|
onPaste={paste}
|
|
@@ -1584,7 +1516,6 @@ function ComputerViewport(props: {
|
|
|
1584
1516
|
<ComputerViewportFallback
|
|
1585
1517
|
observation={props.observation}
|
|
1586
1518
|
machineLocked={props.machineLocked}
|
|
1587
|
-
controlUnavailable={props.controlUnavailable}
|
|
1588
1519
|
connectionState={props.connectionState}
|
|
1589
1520
|
error={props.connectionError}
|
|
1590
1521
|
onAction={(action) => enqueue(action, null)}
|
|
@@ -1632,7 +1563,6 @@ function assertExactComputerClipboard(clipboard: ComputerClipboard, expected: st
|
|
|
1632
1563
|
function ComputerViewportFallback(props: {
|
|
1633
1564
|
observation: ComputerObservation | null;
|
|
1634
1565
|
machineLocked: boolean;
|
|
1635
|
-
controlUnavailable: boolean;
|
|
1636
1566
|
connectionState: string;
|
|
1637
1567
|
error: Error | null;
|
|
1638
1568
|
onAction: (action: ComputerAction) => void;
|
|
@@ -1652,7 +1582,7 @@ function ComputerViewportFallback(props: {
|
|
|
1652
1582
|
);
|
|
1653
1583
|
}
|
|
1654
1584
|
const controlFailure = interactionControlFailureFromError(props.error);
|
|
1655
|
-
const interactive =
|
|
1585
|
+
const interactive = semanticNodes(props.observation)
|
|
1656
1586
|
.filter((node) => semanticAction(node) !== null)
|
|
1657
1587
|
.slice(0, 10);
|
|
1658
1588
|
return (
|
|
@@ -1667,11 +1597,9 @@ function ComputerViewportFallback(props: {
|
|
|
1667
1597
|
<p className="text-og-menu font-medium text-og-fg">
|
|
1668
1598
|
{isAttachedChromeGenerationLossError(props.error)
|
|
1669
1599
|
? "Chrome reconnected—open a fresh browser/desktop."
|
|
1670
|
-
: props.
|
|
1671
|
-
? "
|
|
1672
|
-
: props.
|
|
1673
|
-
? "Live view disconnected"
|
|
1674
|
-
: computerConnectionLabel(props.connectionState)}
|
|
1600
|
+
: props.error
|
|
1601
|
+
? "Live view disconnected"
|
|
1602
|
+
: computerConnectionLabel(props.connectionState)}
|
|
1675
1603
|
</p>
|
|
1676
1604
|
</div>
|
|
1677
1605
|
{props.error ? (
|
|
@@ -1681,7 +1609,7 @@ function ComputerViewportFallback(props: {
|
|
|
1681
1609
|
: (controlFailure?.message ?? props.error.message)}
|
|
1682
1610
|
</p>
|
|
1683
1611
|
) : null}
|
|
1684
|
-
{interactive.length > 0
|
|
1612
|
+
{interactive.length > 0 ? (
|
|
1685
1613
|
<div className="mt-3 border-t border-og-border pt-3">
|
|
1686
1614
|
<p className="mb-2 text-og-xs text-og-fg-subtle">App controls remain available</p>
|
|
1687
1615
|
<div className="flex flex-wrap gap-1.5">
|
|
@@ -1718,26 +1646,18 @@ function ComputerViewportFallback(props: {
|
|
|
1718
1646
|
function ComputerSemanticPanel(props: {
|
|
1719
1647
|
observation: ComputerObservation | null;
|
|
1720
1648
|
mutating: boolean;
|
|
1721
|
-
controlUnavailable: boolean;
|
|
1722
1649
|
onAction: (action: ComputerAction) => void;
|
|
1723
1650
|
}) {
|
|
1724
|
-
const nodes =
|
|
1651
|
+
const nodes = semanticNodes(props.observation).slice(0, 100);
|
|
1725
1652
|
return (
|
|
1726
1653
|
<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">
|
|
1727
1654
|
<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">
|
|
1728
1655
|
<KeyboardIcon className="size-3" /> App controls
|
|
1729
1656
|
</div>
|
|
1730
|
-
{
|
|
1657
|
+
{nodes.length === 0 ? (
|
|
1731
1658
|
<p className="px-1 py-2 text-og-control leading-5 text-og-fg-muted">
|
|
1732
|
-
|
|
1659
|
+
This view has no accessibility controls. Use the live image instead.
|
|
1733
1660
|
</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
|
-
)
|
|
1741
1661
|
) : (
|
|
1742
1662
|
<div className="space-y-0.5">
|
|
1743
1663
|
{nodes.map((node) => (
|
|
@@ -1827,7 +1747,6 @@ function ComputerStatusBar(props: {
|
|
|
1827
1747
|
session: ComputerSession | null;
|
|
1828
1748
|
target: ComputerTarget | null;
|
|
1829
1749
|
connectionState: string;
|
|
1830
|
-
controlUnavailable: boolean;
|
|
1831
1750
|
refreshing: boolean;
|
|
1832
1751
|
showControls: boolean;
|
|
1833
1752
|
controlCount: number;
|
|
@@ -1843,22 +1762,14 @@ function ComputerStatusBar(props: {
|
|
|
1843
1762
|
)}
|
|
1844
1763
|
/>
|
|
1845
1764
|
<span>
|
|
1846
|
-
{props.
|
|
1847
|
-
? "Controls unavailable"
|
|
1848
|
-
: props.connectionState === "live"
|
|
1849
|
-
? "Live"
|
|
1850
|
-
: computerConnectionLabel(props.connectionState)}
|
|
1765
|
+
{props.connectionState === "live" ? "Live" : computerConnectionLabel(props.connectionState)}
|
|
1851
1766
|
</span>
|
|
1852
1767
|
<span className="min-w-0 flex-1 truncate">
|
|
1853
|
-
{
|
|
1854
|
-
? "
|
|
1855
|
-
:
|
|
1856
|
-
? "
|
|
1857
|
-
: props.
|
|
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")}
|
|
1768
|
+
{screen
|
|
1769
|
+
? "Full screen · input may move pointer and focus"
|
|
1770
|
+
: props.session?.capabilities?.backgroundActions
|
|
1771
|
+
? "Window · app controls work in the background"
|
|
1772
|
+
: (props.target?.kind ?? "Desktop")}
|
|
1862
1773
|
</span>
|
|
1863
1774
|
{screen ? <MousePointer2Icon className="size-3" aria-hidden /> : null}
|
|
1864
1775
|
{props.refreshing ? <LoaderCircleIcon className="size-3 animate-spin" /> : null}
|
|
@@ -1893,37 +1804,11 @@ function ComputerNotice(props: { icon: ReactNode; text: string; className?: stri
|
|
|
1893
1804
|
);
|
|
1894
1805
|
}
|
|
1895
1806
|
|
|
1896
|
-
|
|
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[] {
|
|
1807
|
+
function semanticNodes(observation: ComputerObservation | null): InteractionSemanticNode[] {
|
|
1913
1808
|
if (observation?.semantic?.kind !== "snapshot") return [];
|
|
1914
1809
|
const result: InteractionSemanticNode[] = [];
|
|
1915
1810
|
const visit = (node: InteractionSemanticNode) => {
|
|
1916
|
-
|
|
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
|
-
}
|
|
1811
|
+
result.push(node);
|
|
1927
1812
|
node.children?.forEach(visit);
|
|
1928
1813
|
};
|
|
1929
1814
|
observation.semantic.roots.forEach(visit);
|
|
@@ -1955,26 +1840,11 @@ function paintCanvas(
|
|
|
1955
1840
|
width: number,
|
|
1956
1841
|
height: number,
|
|
1957
1842
|
): void {
|
|
1958
|
-
const dimensionsChanged = canvas.width !== width || canvas.height !== height;
|
|
1959
1843
|
canvas.width = width;
|
|
1960
1844
|
canvas.height = height;
|
|
1961
1845
|
const context = canvas.getContext("2d", { alpha: false });
|
|
1962
1846
|
if (!context) throw new Error("Desktop canvas is unavailable.");
|
|
1963
1847
|
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`;
|
|
1978
1848
|
}
|
|
1979
1849
|
|
|
1980
1850
|
function loadImage(url: string): Promise<HTMLImageElement> {
|
|
@@ -1,7 +1,6 @@
|
|
|
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";
|
|
5
4
|
import type { VideoArtifactPlaybackLoader } from "../timeline";
|
|
6
5
|
|
|
7
6
|
export type GeneratedVideoPlayerProps = {
|
|
@@ -23,7 +22,6 @@ export function GeneratedVideoPlayer({
|
|
|
23
22
|
className,
|
|
24
23
|
label = "Generated video",
|
|
25
24
|
}: GeneratedVideoPlayerProps) {
|
|
26
|
-
const formatError = useErrorMessage();
|
|
27
25
|
const [retry, setRetry] = useState(0);
|
|
28
26
|
const [state, setState] = useState<SourceState>({ kind: "loading" });
|
|
29
27
|
|
|
@@ -38,12 +36,12 @@ export function GeneratedVideoPlayer({
|
|
|
38
36
|
if (controller.signal.aborted) return;
|
|
39
37
|
setState({
|
|
40
38
|
kind: "error",
|
|
41
|
-
message:
|
|
39
|
+
message: error instanceof Error ? error.message : "Playback is unavailable.",
|
|
42
40
|
});
|
|
43
41
|
},
|
|
44
42
|
);
|
|
45
43
|
return () => controller.abort();
|
|
46
|
-
}, [loadPlaybackSource, receipt.artifact.artifactId, retry
|
|
44
|
+
}, [loadPlaybackSource, receipt.artifact.artifactId, retry]);
|
|
47
45
|
|
|
48
46
|
if (state.kind === "loading") {
|
|
49
47
|
return (
|
|
@@ -8,7 +8,6 @@ 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";
|
|
12
11
|
|
|
13
12
|
export type HumanInputAnswerDraft = {
|
|
14
13
|
values: string[];
|
|
@@ -16,8 +15,6 @@ export type HumanInputAnswerDraft = {
|
|
|
16
15
|
otherSelected: boolean;
|
|
17
16
|
};
|
|
18
17
|
|
|
19
|
-
class SkillReviewVerificationError extends Error {}
|
|
20
|
-
|
|
21
18
|
export type HumanInputFormMessages = {
|
|
22
19
|
title: string;
|
|
23
20
|
description: string;
|
|
@@ -119,7 +116,6 @@ function HumanInputRequestForm({
|
|
|
119
116
|
defaultCollapsed = false,
|
|
120
117
|
className,
|
|
121
118
|
}: HumanInputFormProps) {
|
|
122
|
-
const formatError = useErrorMessage();
|
|
123
119
|
const messages = { ...defaultHumanInputFormMessages, ...messageOverrides };
|
|
124
120
|
const singleQuestion = request.questions.length === 1 ? request.questions[0]! : null;
|
|
125
121
|
const resolvedTitle =
|
|
@@ -188,7 +184,7 @@ function HumanInputRequestForm({
|
|
|
188
184
|
questions.map(async (question) => {
|
|
189
185
|
if (!loadSkillReview)
|
|
190
186
|
throw new Error(
|
|
191
|
-
"This client cannot preview Skill files.
|
|
187
|
+
"This client cannot preview Skill files. Open this request in Opengeni to review it.",
|
|
192
188
|
);
|
|
193
189
|
const reference = question.reference;
|
|
194
190
|
const record = await loadSkillReview(reference);
|
|
@@ -198,9 +194,7 @@ function HumanInputRequestForm({
|
|
|
198
194
|
(record.removalOperationId ?? undefined) !== reference.removalOperationId ||
|
|
199
195
|
!record.files.some((file) => file.path === "SKILL.md")
|
|
200
196
|
) {
|
|
201
|
-
throw new
|
|
202
|
-
"The requested Skill revision could not be verified.",
|
|
203
|
-
);
|
|
197
|
+
throw new Error("The requested Skill revision could not be verified.");
|
|
204
198
|
}
|
|
205
199
|
return [question.id, record] as const;
|
|
206
200
|
}),
|
|
@@ -220,20 +214,13 @@ function HumanInputRequestForm({
|
|
|
220
214
|
loader: loadSkillReview,
|
|
221
215
|
identity: reviewIdentity,
|
|
222
216
|
records: {},
|
|
223
|
-
error:
|
|
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
|
-
),
|
|
217
|
+
error: cause instanceof Error ? cause.message : "Could not load the Skill files.",
|
|
231
218
|
});
|
|
232
219
|
});
|
|
233
220
|
return () => {
|
|
234
221
|
current = false;
|
|
235
222
|
};
|
|
236
|
-
}, [loadSkillReview, reviewIdentity, reviewReload
|
|
223
|
+
}, [loadSkillReview, reviewIdentity, reviewReload]);
|
|
237
224
|
const visibleReviews =
|
|
238
225
|
reviews?.loader === loadSkillReview && reviews?.identity === reviewIdentity ? reviews : null;
|
|
239
226
|
const preview = (question: HumanInputQuestion) => {
|
|
@@ -332,7 +319,7 @@ function HumanInputRequestForm({
|
|
|
332
319
|
await onSubmit(response);
|
|
333
320
|
} catch (cause) {
|
|
334
321
|
if (generation === submissionGeneration.current) {
|
|
335
|
-
setSubmissionError(
|
|
322
|
+
setSubmissionError(cause instanceof Error ? cause.message : String(cause));
|
|
336
323
|
}
|
|
337
324
|
} finally {
|
|
338
325
|
if (generation === submissionGeneration.current) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { RollingActivity } from "../timeline/rolling-activity";
|
|
2
|
-
import { useErrorMessage } from "../lib/error-message";
|
|
3
2
|
import { formatElapsed } from "../timeline/turn-summary";
|
|
4
3
|
import { ActivityNoteTextContext } from "../timeline/activity-rail";
|
|
5
4
|
import { timelineGroupContainsPresentedImage } from "../timeline/presented-image";
|
|
@@ -563,7 +562,6 @@ export function MessageTimeline({
|
|
|
563
562
|
emptyState,
|
|
564
563
|
className,
|
|
565
564
|
}: MessageTimelineProps) {
|
|
566
|
-
const formatError = useErrorMessage();
|
|
567
565
|
const resolvedItems = useMemo(() => {
|
|
568
566
|
const projectedItems = items ?? buildTimeline(events ?? []);
|
|
569
567
|
if (!shouldRenderAuthNeeded) {
|
|
@@ -674,12 +672,12 @@ export function MessageTimeline({
|
|
|
674
672
|
setNewerRetryPending(false);
|
|
675
673
|
setNewerFailure({
|
|
676
674
|
key: newerBoundaryKey,
|
|
677
|
-
message:
|
|
675
|
+
message: reason instanceof Error ? reason.message : String(reason),
|
|
678
676
|
});
|
|
679
677
|
},
|
|
680
678
|
);
|
|
681
679
|
},
|
|
682
|
-
[onLoadNewer, loadingNewer, newerBoundaryKey
|
|
680
|
+
[onLoadNewer, loadingNewer, newerBoundaryKey],
|
|
683
681
|
);
|
|
684
682
|
const previousSourceIdsRef = useRef(new Set<string>());
|
|
685
683
|
const previousSourceBoundaryRef = useRef<string | undefined>(undefined);
|
|
@@ -2,7 +2,6 @@ import type { OpenGeniClient } from "@opengeni/sdk";
|
|
|
2
2
|
import { MenuIcon, SendIcon, XIcon } from "lucide-react";
|
|
3
3
|
import { useCallback, useRef, useState, type CSSProperties, type FormEvent } from "react";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
|
-
import { useErrorMessage } from "../lib/error-message";
|
|
6
5
|
import { useOpenGeni, type ClientOverride } from "../session-context";
|
|
7
6
|
import { SessionConversation, type SessionConversationProps } from "./session-conversation";
|
|
8
7
|
import { SessionList, type SessionListLabels, type SessionListProps } from "./session-list";
|
|
@@ -199,8 +198,7 @@ function NewChat({
|
|
|
199
198
|
}) {
|
|
200
199
|
const [text, setText] = useState("");
|
|
201
200
|
const [sending, setSending] = useState(false);
|
|
202
|
-
const [error, setError] = useState<
|
|
203
|
-
const formatError = useErrorMessage();
|
|
201
|
+
const [error, setError] = useState<string | null>(null);
|
|
204
202
|
// One key per draft, so a retried send returns the same chat.
|
|
205
203
|
const idempotencyKey = useRef(crypto.randomUUID());
|
|
206
204
|
|
|
@@ -216,7 +214,7 @@ function NewChat({
|
|
|
216
214
|
setText("");
|
|
217
215
|
onCreated(id);
|
|
218
216
|
} catch (cause) {
|
|
219
|
-
setError(
|
|
217
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
220
218
|
} finally {
|
|
221
219
|
setSending(false);
|
|
222
220
|
}
|
|
@@ -225,17 +223,12 @@ function NewChat({
|
|
|
225
223
|
return (
|
|
226
224
|
<form
|
|
227
225
|
onSubmit={(event) => void submit(event)}
|
|
228
|
-
className="
|
|
226
|
+
className="flex h-full flex-col justify-end gap-2 p-3"
|
|
229
227
|
data-og-new-chat-composer=""
|
|
230
228
|
>
|
|
231
229
|
{error ? (
|
|
232
230
|
<p role="alert" className="text-og-xs text-og-status-failed">
|
|
233
|
-
{
|
|
234
|
-
error.cause,
|
|
235
|
-
error.cause instanceof Error && error.cause.message === labels.newChatUnavailable
|
|
236
|
-
? labels.newChatUnavailable
|
|
237
|
-
: undefined,
|
|
238
|
-
)}
|
|
231
|
+
{error}
|
|
239
232
|
</p>
|
|
240
233
|
) : null}
|
|
241
234
|
<div className="flex items-end gap-2 rounded-lg border border-og-border bg-og-surface-1 p-2">
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Loader2Icon, RotateCwIcon } from "lucide-react";
|
|
2
2
|
|
|
3
3
|
import type { UseTurnQueueResult } from "../hooks/use-turn-queue";
|
|
4
|
-
import { useErrorMessage } from "../lib/error-message";
|
|
5
4
|
|
|
6
5
|
type QueueStateProps = {
|
|
7
6
|
queue: UseTurnQueueResult;
|
|
@@ -61,7 +60,6 @@ export function QueueErrorAlert({
|
|
|
61
60
|
queue,
|
|
62
61
|
dividerBefore = false,
|
|
63
62
|
}: QueueStateProps & { dividerBefore?: boolean }) {
|
|
64
|
-
const formatError = useErrorMessage();
|
|
65
63
|
return (
|
|
66
64
|
<div className={`${dividerBefore ? "border-t border-border" : ""} p-2`}>
|
|
67
65
|
<div
|
|
@@ -76,7 +74,7 @@ export function QueueErrorAlert({
|
|
|
76
74
|
data-testid="queue-error-message"
|
|
77
75
|
dir="auto"
|
|
78
76
|
>
|
|
79
|
-
{
|
|
77
|
+
{(queue.mutationError ?? queue.error)?.message}
|
|
80
78
|
</span>
|
|
81
79
|
<button
|
|
82
80
|
type="button"
|
|
@@ -73,7 +73,6 @@ import type { ComposerOptimisticMessage, ComposerState } from "../hooks/use-comp
|
|
|
73
73
|
import type { UseGoalResult } from "../hooks/use-goal";
|
|
74
74
|
import type { UseTurnQueueResult } from "../hooks/use-turn-queue";
|
|
75
75
|
import { cn } from "../lib/cn";
|
|
76
|
-
import { useErrorMessage } from "../lib/error-message";
|
|
77
76
|
import { formatClockTime } from "../lib/format";
|
|
78
77
|
import { requestQueueDraftEdit } from "./queue-draft-policy";
|
|
79
78
|
import { QUEUE_ITEM_CONTENT_UNAVAILABLE, queueItemContent } from "./queue-item-content";
|
|
@@ -454,7 +453,6 @@ export function SessionChrome({
|
|
|
454
453
|
defaultActive = null,
|
|
455
454
|
onActiveChange,
|
|
456
455
|
}: SessionChromeProps) {
|
|
457
|
-
const formatError = useErrorMessage();
|
|
458
456
|
const [activityRequested, setActivityOpen] = useState(
|
|
459
457
|
Boolean(defaultActive && ["incoming", "agents", "commands"].includes(defaultActive)),
|
|
460
458
|
);
|
|
@@ -1194,7 +1192,7 @@ export function SessionChrome({
|
|
|
1194
1192
|
</AnimatePresence>
|
|
1195
1193
|
{goal?.mutationError ? (
|
|
1196
1194
|
<p role="alert" className="px-3 pb-2 text-og-xs text-og-danger">
|
|
1197
|
-
Goal action not confirmed. {
|
|
1195
|
+
Goal action not confirmed. {goal.mutationError.message}
|
|
1198
1196
|
</p>
|
|
1199
1197
|
) : null}
|
|
1200
1198
|
<div
|
|
@@ -1669,7 +1667,6 @@ function GoalPanel({
|
|
|
1669
1667
|
elapsed: string | null;
|
|
1670
1668
|
readOnly: boolean;
|
|
1671
1669
|
}) {
|
|
1672
|
-
const formatError = useErrorMessage();
|
|
1673
1670
|
const record = goal.goal;
|
|
1674
1671
|
if (!record) return null;
|
|
1675
1672
|
const canToggle = !readOnly && (record.status === "active" || record.status === "paused");
|
|
@@ -1702,7 +1699,7 @@ function GoalPanel({
|
|
|
1702
1699
|
) : null}
|
|
1703
1700
|
{record.continuation?.lastError ? (
|
|
1704
1701
|
<p className="rounded-og-sm bg-og-status-waiting/10 px-1.5 py-1 text-og-xs leading-4 text-og-status-waiting">
|
|
1705
|
-
{
|
|
1702
|
+
{record.continuation.lastError}
|
|
1706
1703
|
</p>
|
|
1707
1704
|
) : null}
|
|
1708
1705
|
<div className="flex flex-wrap items-center justify-between gap-1.5 pt-0.5">
|