@opengeni/react 0.48.0 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -2
- 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 +199 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/{chunk-NJ7M2B6Z.js → chunk-5UQPNLIO.js} +16 -8
- package/dist/{chunk-NJ7M2B6Z.js.map → chunk-5UQPNLIO.js.map} +1 -1
- package/dist/{chunk-LHLYWG5R.js → chunk-6KLX2RH5.js} +461 -38
- package/dist/chunk-6KLX2RH5.js.map +1 -0
- package/dist/chunk-7WCLAEFV.js +2828 -0
- package/dist/chunk-7WCLAEFV.js.map +1 -0
- package/dist/{chunk-I4MPFDRP.js → chunk-B6WPZSTT.js} +104 -104
- package/dist/{chunk-I4MPFDRP.js.map → chunk-B6WPZSTT.js.map} +1 -1
- package/dist/{chunk-EKZH6IDG.js → chunk-C5ZPSSS5.js} +111 -1
- package/dist/chunk-C5ZPSSS5.js.map +1 -0
- package/dist/{chunk-OL6QVXGF.js → chunk-HA46CYQK.js} +152 -8
- package/dist/chunk-HA46CYQK.js.map +1 -0
- package/dist/chunk-HUIKIDM5.js +2148 -0
- package/dist/chunk-HUIKIDM5.js.map +1 -0
- package/dist/{chunk-FWXGSNXT.js → chunk-IGC6A2ZI.js} +585 -34
- package/dist/chunk-IGC6A2ZI.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-LLOLUHVG.js +5937 -0
- package/dist/chunk-LLOLUHVG.js.map +1 -0
- package/dist/{chunk-WYA65Y3F.js → chunk-MKPIAGVL.js} +13 -3
- package/dist/chunk-MKPIAGVL.js.map +1 -0
- package/dist/chunk-PEI6RANI.js +3234 -0
- package/dist/chunk-PEI6RANI.js.map +1 -0
- package/dist/chunk-R3AZOAB5.js +933 -0
- package/dist/chunk-R3AZOAB5.js.map +1 -0
- package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
- package/dist/chunk-WSK7G5LE.js.map +1 -0
- package/dist/{chunk-23EJ676W.js → chunk-XBZWOM2K.js} +9 -4
- package/dist/chunk-XBZWOM2K.js.map +1 -0
- package/dist/{chunk-XCNTXWBB.js → chunk-Y7OEDUJ5.js} +856 -338
- package/dist/chunk-Y7OEDUJ5.js.map +1 -0
- package/dist/client.d.ts +10 -2
- 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 +9 -0
- package/dist/components/artifacts/presentation-editor.d.ts +297 -0
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +14 -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-input.d.ts +7 -0
- package/dist/components/browser-viewer.d.ts +30 -0
- package/dist/components/chat-composer.d.ts +8 -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 +2 -2
- package/dist/components/composer.d.ts +17 -17
- package/dist/components/computer-viewer.d.ts +24 -0
- package/dist/components/copy-button.d.ts +2 -2
- package/dist/components/desktop-viewer.d.ts +50 -22
- 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/interaction-intervention-banner.d.ts +11 -0
- 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 +3 -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 +2 -2
- package/dist/components/markdown.d.ts +1 -1
- package/dist/components/message-timeline.d.ts +12 -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 +3 -3
- package/dist/components/sandbox-terminal.d.ts +24 -4
- package/dist/components/sandbox-workspace.d.ts +28 -9
- 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 +1 -1
- package/dist/composer.d.ts +11 -11
- package/dist/composer.js +7 -7
- package/dist/hooks/internal.d.ts +1 -1
- package/dist/hooks/use-attached-browsers.d.ts +21 -0
- package/dist/hooks/use-auth-runs.d.ts +32 -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-downloads.d.ts +25 -0
- package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
- package/dist/hooks/use-browser-identities.d.ts +30 -0
- package/dist/hooks/use-browser-session.d.ts +29 -0
- package/dist/hooks/use-browser-sessions.d.ts +32 -0
- package/dist/hooks/use-channels.d.ts +26 -0
- package/dist/hooks/use-codex-accounts.d.ts +2 -2
- 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 +29 -0
- package/dist/hooks/use-computer-sessions.d.ts +28 -0
- package/dist/hooks/use-desktop-stream.d.ts +5 -1
- package/dist/hooks/use-environments.d.ts +1 -1
- 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-interaction-interventions.d.ts +27 -0
- package/dist/hooks/use-interaction-invalidation.d.ts +19 -0
- 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 +4 -4
- package/dist/hooks/use-network-routes.d.ts +25 -0
- 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 +6 -4
- package/dist/hooks/use-sandbox-git.d.ts +6 -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-site-auth-connections.d.ts +25 -0
- package/dist/hooks/use-slash-commands.d.ts +1 -1
- package/dist/hooks/use-supergrok-accounts.d.ts +26 -0
- package/dist/hooks/use-terminal-stream.d.ts +11 -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 +1 -1
- 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 +162 -155
- package/dist/index.js +1822 -1703
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +33 -0
- package/dist/interaction.js +550 -0
- package/dist/interaction.js.map +1 -0
- package/dist/interactive-workbench-viewers-TOHBXJEE.js +15 -0
- package/dist/interactive-workbench-viewers-TOHBXJEE.js.map +1 -0
- package/dist/lib/host-platform.d.ts +5 -0
- package/dist/lib/relay-wire.d.ts +24 -1
- package/dist/lib/sandbox-liveness.d.ts +9 -0
- package/dist/lib/terminal-capability.d.ts +5 -0
- package/dist/lib/xterm-theme.d.ts +1 -1
- package/dist/machines.d.ts +30 -30
- package/dist/machines.js +2 -2
- package/dist/model-policy.d.ts +1 -1
- package/dist/model-policy.js +1 -1
- package/dist/provider.d.ts +6 -5
- package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
- 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 +8 -8
- package/dist/realtime.d.ts +4 -4
- package/dist/realtime.js +38 -11
- package/dist/realtime.js.map +1 -1
- package/dist/session-context.d.ts +28 -2
- package/dist/session-ui.d.ts +14 -14
- package/dist/session-ui.js +5 -4
- package/dist/session.d.ts +28 -28
- package/dist/session.js +10 -10
- package/dist/timeline/activity-rail.d.ts +5 -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 +4 -1
- package/dist/timeline/projection.d.ts +3 -3
- package/dist/timeline/registry.d.ts +8 -2
- 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 +44 -3
- 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/package.json +36 -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 +89 -0
- package/src/client.ts +157 -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 +104 -0
- package/src/components/artifacts/presentation-editor.tsx +3148 -0
- package/src/components/artifacts/published-html-artifact-frame.tsx +38 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
- package/src/components/browser-input.ts +65 -0
- package/src/components/browser-viewer.tsx +2702 -0
- package/src/components/chat-composer.tsx +13 -0
- package/src/components/code-editor.tsx +89 -30
- package/src/components/composer-transcription-control.tsx +10 -6
- package/src/components/computer-viewer.tsx +1636 -0
- package/src/components/desktop-viewer.tsx +215 -74
- package/src/components/generated-video-player.tsx +89 -0
- package/src/components/interaction-intervention-banner.tsx +99 -0
- package/src/components/interactive-workbench-viewers.ts +2 -0
- package/src/components/machine-input-display.ts +3 -0
- package/src/components/machines/machine-detail.tsx +1 -1
- package/src/components/message-timeline.tsx +289 -32
- package/src/components/model-policy-picker.tsx +15 -0
- package/src/components/queue-surface-implementation.tsx +116 -60
- package/src/components/sandbox-files.tsx +2 -2
- package/src/components/sandbox-terminal.tsx +128 -46
- package/src/components/sandbox-workspace.tsx +256 -90
- package/src/components/session-chrome.tsx +53 -13
- 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/workbench-changes.tsx +188 -31
- package/src/components/workspace-dock.tsx +59 -5
- package/src/hooks/use-attached-browsers.ts +154 -0
- package/src/hooks/use-auth-runs.ts +278 -0
- package/src/hooks/use-browser-downloads.ts +218 -0
- package/src/hooks/use-browser-frame-stream.ts +425 -0
- package/src/hooks/use-browser-identities.ts +266 -0
- package/src/hooks/use-browser-session.ts +543 -0
- package/src/hooks/use-browser-sessions.ts +286 -0
- package/src/hooks/use-channels.ts +99 -0
- package/src/hooks/use-composer.ts +769 -24
- package/src/hooks/use-computer-frame-stream.ts +434 -0
- package/src/hooks/use-computer-session.ts +462 -0
- package/src/hooks/use-computer-sessions.ts +251 -0
- package/src/hooks/use-desktop-stream.ts +39 -7
- package/src/hooks/use-interaction-interventions.ts +242 -0
- package/src/hooks/use-interaction-invalidation.ts +66 -0
- package/src/hooks/use-machine-chip.ts +15 -3
- package/src/hooks/use-machines.ts +33 -3
- package/src/hooks/use-network-routes.ts +214 -0
- package/src/hooks/use-packs.ts +51 -0
- package/src/hooks/use-sandbox-files.ts +98 -55
- package/src/hooks/use-sandbox-git.ts +93 -38
- package/src/hooks/use-sandbox-terminal.ts +3 -2
- package/src/hooks/use-session-capabilities.ts +59 -46
- package/src/hooks/use-site-auth-connections.ts +226 -0
- package/src/hooks/use-supergrok-accounts.ts +81 -0
- package/src/hooks/use-terminal-stream.ts +359 -47
- package/src/hooks/use-video-artifact-playback.ts +28 -0
- package/src/hooks/use-workspace-edit.ts +2 -1
- package/src/index.ts +18 -0
- package/src/interaction.ts +101 -0
- package/src/lib/host-platform.ts +10 -0
- package/src/lib/relay-wire.ts +47 -4
- package/src/lib/sandbox-liveness.ts +11 -0
- package/src/lib/terminal-capability.ts +10 -0
- package/src/model-policy.ts +19 -4
- package/src/provider.tsx +133 -1
- package/src/realtime/realtime-control.tsx +34 -6
- package/src/session-context.ts +156 -1
- package/src/timeline/activity-rail.tsx +25 -3
- package/src/timeline/index.ts +3 -0
- package/src/timeline/parsers.ts +14 -1
- package/src/timeline/projection.ts +269 -14
- package/src/timeline/registry.ts +13 -1
- package/src/timeline/screenshot-lightbox.tsx +19 -9
- package/src/timeline/shared.tsx +56 -8
- package/src/timeline/tool-renderers.tsx +303 -50
- package/src/timeline/types.ts +57 -3
- package/styles/compiled.css +312 -21
- package/styles/index.css +15 -0
- package/styles/responsive.css +13 -1
- package/dist/chunk-23EJ676W.js.map +0 -1
- package/dist/chunk-EKZH6IDG.js.map +0 -1
- package/dist/chunk-FWXGSNXT.js.map +0 -1
- package/dist/chunk-LHLYWG5R.js.map +0 -1
- package/dist/chunk-O4VXOBGF.js.map +0 -1
- package/dist/chunk-OL6QVXGF.js.map +0 -1
- package/dist/chunk-WYA65Y3F.js.map +0 -1
- package/dist/chunk-XCNTXWBB.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-U5JFG7HT.js.map +0 -1
|
@@ -272,6 +272,14 @@ export function SessionChrome({
|
|
|
272
272
|
queuedTurns: currentTurnId ? turns.filter((turn) => turn.id !== currentTurnId) : turns,
|
|
273
273
|
};
|
|
274
274
|
}, [composerSteering, queue.pendingByTurn, turns]);
|
|
275
|
+
const stoppingKind =
|
|
276
|
+
composer?.stoppingAttempt ??
|
|
277
|
+
(queue.stoppingPreviousAttempt
|
|
278
|
+
? queue.effectiveControl?.state === "paused"
|
|
279
|
+
? "current"
|
|
280
|
+
: "previous"
|
|
281
|
+
: null);
|
|
282
|
+
const stopping = stoppingKind !== null;
|
|
275
283
|
const canMutateQueue = !readOnly && composer !== undefined;
|
|
276
284
|
|
|
277
285
|
const liveGoal =
|
|
@@ -307,14 +315,18 @@ export function SessionChrome({
|
|
|
307
315
|
icon: <InboxIcon className="size-3" />,
|
|
308
316
|
});
|
|
309
317
|
}
|
|
310
|
-
if (steering) {
|
|
318
|
+
if (steering || stopping) {
|
|
311
319
|
rows.push({
|
|
312
320
|
id: "steering",
|
|
313
|
-
label:
|
|
314
|
-
|
|
315
|
-
|
|
321
|
+
label: stopping
|
|
322
|
+
? stoppingKind === "current"
|
|
323
|
+
? "Stopping current work…"
|
|
324
|
+
: "Stopping previous work…"
|
|
325
|
+
: "Changing direction…",
|
|
326
|
+
...(steering?.text ? { detail: steering.text } : {}),
|
|
327
|
+
tone: stopping ? "waiting" : "accent",
|
|
316
328
|
icon:
|
|
317
|
-
steering
|
|
329
|
+
steering?.phase === "submitting" || stopping ? (
|
|
318
330
|
<Loader2Icon className="size-3 animate-og-spin" />
|
|
319
331
|
) : (
|
|
320
332
|
<ZapIcon className="size-3" />
|
|
@@ -379,7 +391,17 @@ export function SessionChrome({
|
|
|
379
391
|
});
|
|
380
392
|
}
|
|
381
393
|
return rows;
|
|
382
|
-
}, [
|
|
394
|
+
}, [
|
|
395
|
+
agentsSignal,
|
|
396
|
+
elapsed,
|
|
397
|
+
goalState,
|
|
398
|
+
incoming,
|
|
399
|
+
queuedTurns,
|
|
400
|
+
record,
|
|
401
|
+
steering,
|
|
402
|
+
stopping,
|
|
403
|
+
stoppingKind,
|
|
404
|
+
]);
|
|
383
405
|
|
|
384
406
|
const [activeUncontrolled, setActiveUncontrolled] = useState<SessionChromeSignalId | null>(
|
|
385
407
|
defaultActive,
|
|
@@ -479,8 +501,12 @@ export function SessionChrome({
|
|
|
479
501
|
const panelBody =
|
|
480
502
|
active === "incoming" ? (
|
|
481
503
|
<IncomingPanel inputs={incoming} onDismiss={onDismissIncoming} />
|
|
482
|
-
) : active === "steering" && steering ? (
|
|
483
|
-
<SteeringPanel
|
|
504
|
+
) : active === "steering" && (steering || stopping) ? (
|
|
505
|
+
<SteeringPanel
|
|
506
|
+
phase={stopping ? "stopping" : (steering?.phase ?? "accepted")}
|
|
507
|
+
stoppingKind={stoppingKind}
|
|
508
|
+
text={steering?.text}
|
|
509
|
+
/>
|
|
484
510
|
) : active === "queue" ? (
|
|
485
511
|
<QueuePanel
|
|
486
512
|
turns={queuedTurns}
|
|
@@ -762,7 +788,15 @@ function IncomingPanel({
|
|
|
762
788
|
);
|
|
763
789
|
}
|
|
764
790
|
|
|
765
|
-
function SteeringPanel({
|
|
791
|
+
function SteeringPanel({
|
|
792
|
+
phase,
|
|
793
|
+
stoppingKind,
|
|
794
|
+
text,
|
|
795
|
+
}: {
|
|
796
|
+
phase: "submitting" | "accepted" | "stopping";
|
|
797
|
+
stoppingKind?: "current" | "previous" | null | undefined;
|
|
798
|
+
text?: string | undefined;
|
|
799
|
+
}) {
|
|
766
800
|
return (
|
|
767
801
|
<div
|
|
768
802
|
className="flex items-start gap-2 rounded-og-sm px-1.5 py-1"
|
|
@@ -771,18 +805,24 @@ function SteeringPanel({ phase, text }: { phase: "submitting" | "accepted"; text
|
|
|
771
805
|
data-og-session-chrome-panel="steering"
|
|
772
806
|
>
|
|
773
807
|
<span className="mt-0.5 inline-flex size-5 shrink-0 items-center justify-center rounded-full bg-og-accent-soft text-og-accent">
|
|
774
|
-
{phase === "submitting" ? (
|
|
808
|
+
{phase === "submitting" || phase === "stopping" ? (
|
|
775
809
|
<Loader2Icon className="size-3 animate-og-spin" aria-hidden="true" />
|
|
776
810
|
) : (
|
|
777
811
|
<ZapIcon className="size-3" aria-hidden="true" />
|
|
778
812
|
)}
|
|
779
813
|
</span>
|
|
780
814
|
<div className="min-w-0">
|
|
781
|
-
|
|
782
|
-
|
|
815
|
+
{text ? (
|
|
816
|
+
<p className="truncate text-og-xs font-medium leading-4 text-og-fg">{text}</p>
|
|
817
|
+
) : null}
|
|
818
|
+
<p className={cn(text && "mt-0.5", "text-[10px] leading-4 text-og-fg-muted")}>
|
|
783
819
|
{phase === "submitting"
|
|
784
820
|
? "Sending your latest direction…"
|
|
785
|
-
:
|
|
821
|
+
: phase === "stopping"
|
|
822
|
+
? stoppingKind === "previous"
|
|
823
|
+
? "Direction saved. Waiting for the previous command to stop safely."
|
|
824
|
+
: "Waiting for the current command to stop safely."
|
|
825
|
+
: "Direction accepted. The agent will continue from it."}
|
|
786
826
|
</p>
|
|
787
827
|
</div>
|
|
788
828
|
</div>
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { DraftTimelineAnnotation } from "@opengeni/sdk";
|
|
2
|
+
import { useEffect, useRef, useState, type RefObject } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import type { TimelineAnnotationSourceDescriptor } from "../timeline";
|
|
5
|
+
|
|
6
|
+
type SelectionCandidate = {
|
|
7
|
+
annotation: DraftTimelineAnnotation;
|
|
8
|
+
left: number;
|
|
9
|
+
top: number;
|
|
10
|
+
keyboard: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const INTERACTIVE_SELECTOR =
|
|
14
|
+
'a,button,input,textarea,select,summary,[role="button"],[contenteditable="true"],[tabindex]:not([tabindex="-1"])';
|
|
15
|
+
const SOURCE_CONTEXT_BYTES = 160;
|
|
16
|
+
const MAX_QUOTE_BYTES = 16 * 1024;
|
|
17
|
+
|
|
18
|
+
function utf8Prefix(value: string, maxBytes: number): string {
|
|
19
|
+
let output = "";
|
|
20
|
+
let bytes = 0;
|
|
21
|
+
for (const character of value) {
|
|
22
|
+
const next = new TextEncoder().encode(character).byteLength;
|
|
23
|
+
if (bytes + next > maxBytes) break;
|
|
24
|
+
output += character;
|
|
25
|
+
bytes += next;
|
|
26
|
+
}
|
|
27
|
+
return output;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function utf8Suffix(value: string, maxBytes: number): string {
|
|
31
|
+
let output = "";
|
|
32
|
+
let bytes = 0;
|
|
33
|
+
for (const character of [...value].reverse()) {
|
|
34
|
+
const next = new TextEncoder().encode(character).byteLength;
|
|
35
|
+
if (bytes + next > maxBytes) break;
|
|
36
|
+
output = character + output;
|
|
37
|
+
bytes += next;
|
|
38
|
+
}
|
|
39
|
+
return output;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function selectionElement(node: Node | null): Element | null {
|
|
43
|
+
return node instanceof Element ? node : (node?.parentElement ?? null);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function containingSource(node: Node | null): HTMLElement | null {
|
|
47
|
+
const element = selectionElement(node);
|
|
48
|
+
const source = element?.closest("[data-og-annotation-source-key]");
|
|
49
|
+
return source instanceof HTMLElement ? source : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isInteractiveSelection(node: Node | null, boundary: HTMLElement): boolean {
|
|
53
|
+
const element = selectionElement(node);
|
|
54
|
+
const interactive = element?.closest(INTERACTIVE_SELECTOR);
|
|
55
|
+
return interactive instanceof Element && boundary.contains(interactive);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function occurrenceOffsets(text: string, quote: string): number[] {
|
|
59
|
+
const offsets: number[] = [];
|
|
60
|
+
let cursor = 0;
|
|
61
|
+
while (cursor <= text.length - quote.length) {
|
|
62
|
+
const offset = text.indexOf(quote, cursor);
|
|
63
|
+
if (offset < 0) break;
|
|
64
|
+
offsets.push(offset);
|
|
65
|
+
cursor = offset + 1;
|
|
66
|
+
}
|
|
67
|
+
return offsets;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function selectedOccurrence(boundary: HTMLElement, range: Range, quote: string): number {
|
|
71
|
+
try {
|
|
72
|
+
const prefix = range.cloneRange();
|
|
73
|
+
prefix.selectNodeContents(boundary);
|
|
74
|
+
prefix.setEnd(range.startContainer, range.startOffset);
|
|
75
|
+
return occurrenceOffsets(prefix.toString(), quote).length;
|
|
76
|
+
} catch {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function annotationId(): string {
|
|
82
|
+
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
83
|
+
return globalThis.crypto.randomUUID();
|
|
84
|
+
}
|
|
85
|
+
return `annotation-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildCandidate(
|
|
89
|
+
root: HTMLElement,
|
|
90
|
+
sources: ReadonlyMap<string, TimelineAnnotationSourceDescriptor>,
|
|
91
|
+
keyboard: boolean,
|
|
92
|
+
): SelectionCandidate | null {
|
|
93
|
+
const selection = document.getSelection();
|
|
94
|
+
if (!selection || selection.rangeCount !== 1 || selection.isCollapsed) return null;
|
|
95
|
+
const range = selection.getRangeAt(0);
|
|
96
|
+
const startBoundary = containingSource(range.startContainer);
|
|
97
|
+
const endBoundary = containingSource(range.endContainer);
|
|
98
|
+
if (!startBoundary || startBoundary !== endBoundary || !root.contains(startBoundary)) return null;
|
|
99
|
+
if (
|
|
100
|
+
isInteractiveSelection(range.startContainer, startBoundary) ||
|
|
101
|
+
isInteractiveSelection(range.endContainer, startBoundary)
|
|
102
|
+
) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const sourceKey = startBoundary.dataset.ogAnnotationSourceKey;
|
|
106
|
+
const source = sourceKey ? sources.get(sourceKey) : undefined;
|
|
107
|
+
const quote = selection.toString();
|
|
108
|
+
if (
|
|
109
|
+
!source ||
|
|
110
|
+
quote.trim().length === 0 ||
|
|
111
|
+
new TextEncoder().encode(quote).byteLength > MAX_QUOTE_BYTES
|
|
112
|
+
) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const sourceOffsets = occurrenceOffsets(source.text, quote);
|
|
116
|
+
if (sourceOffsets.length === 0) return null;
|
|
117
|
+
const occurrence = selectedOccurrence(startBoundary, range, quote);
|
|
118
|
+
const startOffset = sourceOffsets[Math.min(occurrence, sourceOffsets.length - 1)]!;
|
|
119
|
+
const endOffset = startOffset + quote.length;
|
|
120
|
+
const rect = range.getBoundingClientRect();
|
|
121
|
+
if (rect.width === 0 && rect.height === 0) return null;
|
|
122
|
+
return {
|
|
123
|
+
annotation: {
|
|
124
|
+
id: annotationId(),
|
|
125
|
+
quote,
|
|
126
|
+
note: "",
|
|
127
|
+
source: {
|
|
128
|
+
kind: source.kind,
|
|
129
|
+
eventId: source.eventId,
|
|
130
|
+
eventType: source.eventType,
|
|
131
|
+
sequence: source.sequence,
|
|
132
|
+
turnId: source.turnId,
|
|
133
|
+
startOffset,
|
|
134
|
+
endOffset,
|
|
135
|
+
contextBefore: utf8Suffix(source.text.slice(0, startOffset), SOURCE_CONTEXT_BYTES),
|
|
136
|
+
contextAfter: utf8Prefix(source.text.slice(endOffset), SOURCE_CONTEXT_BYTES),
|
|
137
|
+
...(source.label ? { label: source.label } : {}),
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
left: Math.min(Math.max(12, rect.left + rect.width / 2), window.innerWidth - 12),
|
|
141
|
+
top: rect.bottom + 48 <= window.innerHeight ? rect.bottom + 8 : Math.max(12, rect.top - 40),
|
|
142
|
+
keyboard,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function TimelineAnnotationSelection({
|
|
147
|
+
rootRef,
|
|
148
|
+
sources,
|
|
149
|
+
onAnnotate,
|
|
150
|
+
}: {
|
|
151
|
+
rootRef: RefObject<HTMLElement | null>;
|
|
152
|
+
sources: ReadonlyMap<string, TimelineAnnotationSourceDescriptor>;
|
|
153
|
+
onAnnotate?: ((annotation: DraftTimelineAnnotation) => void) | undefined;
|
|
154
|
+
}) {
|
|
155
|
+
const [candidate, setCandidate] = useState<SelectionCandidate | null>(null);
|
|
156
|
+
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
157
|
+
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
const root = rootRef.current;
|
|
160
|
+
if (!root || !onAnnotate) {
|
|
161
|
+
setCandidate(null);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const update = (keyboard: boolean) => {
|
|
165
|
+
setCandidate(buildCandidate(root, sources, keyboard));
|
|
166
|
+
};
|
|
167
|
+
const onPointerUp = () => window.setTimeout(() => update(false), 0);
|
|
168
|
+
const onKeyUp = (event: KeyboardEvent) => {
|
|
169
|
+
if (event.key === "Escape") {
|
|
170
|
+
setCandidate(null);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
window.setTimeout(() => update(true), 0);
|
|
174
|
+
};
|
|
175
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
176
|
+
if (
|
|
177
|
+
candidate &&
|
|
178
|
+
!(event.target instanceof Node && buttonRef.current?.contains(event.target))
|
|
179
|
+
) {
|
|
180
|
+
setCandidate(null);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const onDocumentKeyDown = (event: KeyboardEvent) => {
|
|
184
|
+
if (event.key === "Escape") setCandidate(null);
|
|
185
|
+
};
|
|
186
|
+
root.addEventListener("pointerup", onPointerUp);
|
|
187
|
+
root.addEventListener("keyup", onKeyUp);
|
|
188
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
189
|
+
document.addEventListener("keydown", onDocumentKeyDown);
|
|
190
|
+
return () => {
|
|
191
|
+
root.removeEventListener("pointerup", onPointerUp);
|
|
192
|
+
root.removeEventListener("keyup", onKeyUp);
|
|
193
|
+
document.removeEventListener("pointerdown", onPointerDown);
|
|
194
|
+
document.removeEventListener("keydown", onDocumentKeyDown);
|
|
195
|
+
};
|
|
196
|
+
}, [candidate, onAnnotate, rootRef, sources]);
|
|
197
|
+
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
if (candidate?.keyboard) buttonRef.current?.focus();
|
|
200
|
+
}, [candidate]);
|
|
201
|
+
|
|
202
|
+
if (!candidate || !onAnnotate || typeof document === "undefined") return null;
|
|
203
|
+
return createPortal(
|
|
204
|
+
<button
|
|
205
|
+
ref={buttonRef}
|
|
206
|
+
type="button"
|
|
207
|
+
style={{ left: candidate.left, top: candidate.top }}
|
|
208
|
+
className="og-root fixed z-[80] -translate-x-1/2 rounded-full border border-og-border bg-og-surface-1 px-3 py-1.5 text-og-sm font-medium text-og-fg shadow-lg outline-hidden transition hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-[44px]"
|
|
209
|
+
onClick={() => {
|
|
210
|
+
onAnnotate(candidate.annotation);
|
|
211
|
+
document.getSelection()?.removeAllRanges();
|
|
212
|
+
setCandidate(null);
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
Annotate
|
|
216
|
+
</button>,
|
|
217
|
+
document.body,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export default TimelineAnnotationSelection;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type { TimelineAnnotationSource } from "@opengeni/sdk";
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState, type RefObject } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import type { TimelineAnnotationLike } from "./timeline-annotations";
|
|
5
|
+
|
|
6
|
+
function cssEscapeAttribute(value: string): string {
|
|
7
|
+
if (typeof CSS !== "undefined" && typeof CSS.escape === "function") return CSS.escape(value);
|
|
8
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function revealLoadedSource(source: TimelineAnnotationSource): boolean {
|
|
12
|
+
if (typeof document === "undefined") return false;
|
|
13
|
+
const element = document.querySelector(
|
|
14
|
+
`[data-og-annotation-source-key="${cssEscapeAttribute(source.eventId)}"]`,
|
|
15
|
+
);
|
|
16
|
+
if (!(element instanceof HTMLElement)) return false;
|
|
17
|
+
element.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
18
|
+
element.animate?.(
|
|
19
|
+
[
|
|
20
|
+
{ outline: "2px solid color-mix(in srgb, currentColor 55%, transparent)" },
|
|
21
|
+
{ outline: "2px solid transparent" },
|
|
22
|
+
],
|
|
23
|
+
{ duration: 1600, easing: "ease-out" },
|
|
24
|
+
);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sourceLabel(annotation: TimelineAnnotationLike): string {
|
|
29
|
+
if (annotation.source.label) return annotation.source.label;
|
|
30
|
+
switch (annotation.source.kind) {
|
|
31
|
+
case "user_message":
|
|
32
|
+
return "User message";
|
|
33
|
+
case "assistant_message":
|
|
34
|
+
return "Assistant message";
|
|
35
|
+
case "tool_output":
|
|
36
|
+
return "Tool output";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function TimelineAnnotationsDialog({
|
|
41
|
+
annotations,
|
|
42
|
+
editable,
|
|
43
|
+
focusAnnotationId,
|
|
44
|
+
onFocusConsumed,
|
|
45
|
+
onUpdate,
|
|
46
|
+
onRemove,
|
|
47
|
+
onRevealSource,
|
|
48
|
+
triggerRef,
|
|
49
|
+
countLabel,
|
|
50
|
+
onDismiss,
|
|
51
|
+
}: {
|
|
52
|
+
annotations: readonly TimelineAnnotationLike[];
|
|
53
|
+
editable: boolean;
|
|
54
|
+
focusAnnotationId?: string | null | undefined;
|
|
55
|
+
onFocusConsumed?: (() => void) | undefined;
|
|
56
|
+
onUpdate?: ((id: string, note: string) => void) | undefined;
|
|
57
|
+
onRemove?: ((id: string) => void) | undefined;
|
|
58
|
+
onRevealSource?: ((source: TimelineAnnotationSource) => boolean) | undefined;
|
|
59
|
+
triggerRef: RefObject<HTMLButtonElement | null>;
|
|
60
|
+
countLabel: string;
|
|
61
|
+
onDismiss: (restoreFocus: boolean) => void;
|
|
62
|
+
}) {
|
|
63
|
+
const [unavailableId, setUnavailableId] = useState<string | null>(null);
|
|
64
|
+
const [position, setPosition] = useState({ left: 12, top: 12, above: false });
|
|
65
|
+
const panelRef = useRef<HTMLDivElement | null>(null);
|
|
66
|
+
const noteRefs = useRef(new Map<string, HTMLTextAreaElement>());
|
|
67
|
+
|
|
68
|
+
useLayoutEffect(() => {
|
|
69
|
+
if (!focusAnnotationId) return;
|
|
70
|
+
const note = noteRefs.current.get(focusAnnotationId);
|
|
71
|
+
note?.focus();
|
|
72
|
+
if (note && document.activeElement === note) onFocusConsumed?.();
|
|
73
|
+
}, [focusAnnotationId, onFocusConsumed]);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
const updatePosition = () => {
|
|
77
|
+
const rect = triggerRef.current?.getBoundingClientRect();
|
|
78
|
+
if (!rect) return;
|
|
79
|
+
const panelWidth = Math.min(400, window.innerWidth - 24);
|
|
80
|
+
const left = Math.min(
|
|
81
|
+
Math.max(12, rect.left),
|
|
82
|
+
Math.max(12, window.innerWidth - panelWidth - 12),
|
|
83
|
+
);
|
|
84
|
+
const above = rect.top > Math.min(360, window.innerHeight * 0.55);
|
|
85
|
+
setPosition({ left, top: above ? rect.top - 8 : rect.bottom + 8, above });
|
|
86
|
+
};
|
|
87
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
88
|
+
if (event.key === "Escape") onDismiss(true);
|
|
89
|
+
};
|
|
90
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
91
|
+
if (!(event.target instanceof Node)) return;
|
|
92
|
+
if (panelRef.current?.contains(event.target) || triggerRef.current?.contains(event.target)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
onDismiss(false);
|
|
96
|
+
};
|
|
97
|
+
updatePosition();
|
|
98
|
+
window.addEventListener("resize", updatePosition);
|
|
99
|
+
window.addEventListener("scroll", updatePosition, true);
|
|
100
|
+
document.addEventListener("keydown", onKeyDown);
|
|
101
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
102
|
+
if (!focusAnnotationId) {
|
|
103
|
+
window.setTimeout(() => {
|
|
104
|
+
if (document.activeElement === triggerRef.current) panelRef.current?.focus();
|
|
105
|
+
}, 0);
|
|
106
|
+
}
|
|
107
|
+
return () => {
|
|
108
|
+
window.removeEventListener("resize", updatePosition);
|
|
109
|
+
window.removeEventListener("scroll", updatePosition, true);
|
|
110
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
111
|
+
document.removeEventListener("pointerdown", onPointerDown);
|
|
112
|
+
};
|
|
113
|
+
}, [focusAnnotationId, onDismiss, triggerRef]);
|
|
114
|
+
|
|
115
|
+
if (typeof document === "undefined") return null;
|
|
116
|
+
return createPortal(
|
|
117
|
+
<div
|
|
118
|
+
ref={panelRef}
|
|
119
|
+
role="dialog"
|
|
120
|
+
tabIndex={-1}
|
|
121
|
+
style={{
|
|
122
|
+
left: position.left,
|
|
123
|
+
top: position.top,
|
|
124
|
+
transform: position.above ? "translateY(-100%)" : undefined,
|
|
125
|
+
}}
|
|
126
|
+
className="og-root fixed z-[75] box-border max-h-[min(32rem,70vh)] w-[min(25rem,calc(100vw-1.5rem))] overflow-y-auto rounded-og-lg border border-og-border bg-og-surface-1 p-3 text-og-fg shadow-xl outline-hidden"
|
|
127
|
+
aria-label={editable ? "Review timeline annotations" : "Timeline annotations"}
|
|
128
|
+
>
|
|
129
|
+
<div className="mb-2 flex items-center justify-between gap-3">
|
|
130
|
+
<div>
|
|
131
|
+
<p className="text-og-sm font-semibold">{countLabel}</p>
|
|
132
|
+
<p className="text-og-xs text-og-fg-subtle">
|
|
133
|
+
{editable ? "Add a note for each quoted source." : "Sent with this message."}
|
|
134
|
+
</p>
|
|
135
|
+
</div>
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
className="rounded-md border-0 bg-transparent px-2 py-1 text-og-xs text-og-fg outline-hidden hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-[44px]"
|
|
139
|
+
onClick={() => onDismiss(true)}
|
|
140
|
+
>
|
|
141
|
+
Close
|
|
142
|
+
</button>
|
|
143
|
+
</div>
|
|
144
|
+
<div className="grid gap-2.5">
|
|
145
|
+
{annotations.map((annotation, index) => (
|
|
146
|
+
<section
|
|
147
|
+
key={annotation.id}
|
|
148
|
+
className="rounded-og-md border border-og-border bg-og-surface-2/60 p-2.5"
|
|
149
|
+
aria-label={`Annotation ${index + 1}`}
|
|
150
|
+
>
|
|
151
|
+
<div className="flex items-center justify-between gap-2">
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
className="truncate rounded border-0 bg-transparent text-left text-og-xs font-medium text-og-fg-muted underline-offset-2 outline-hidden hover:text-og-fg hover:underline focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-[44px]"
|
|
155
|
+
onClick={() => {
|
|
156
|
+
const revealed =
|
|
157
|
+
onRevealSource?.(annotation.source) ?? revealLoadedSource(annotation.source);
|
|
158
|
+
setUnavailableId(revealed ? null : annotation.id);
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
{sourceLabel(annotation)} · view source
|
|
162
|
+
</button>
|
|
163
|
+
{editable && onRemove ? (
|
|
164
|
+
<button
|
|
165
|
+
type="button"
|
|
166
|
+
className="shrink-0 rounded border-0 bg-transparent p-1 text-og-fg-subtle outline-hidden hover:bg-og-surface-1 hover:text-og-status-failed focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:size-[44px]"
|
|
167
|
+
aria-label={`Remove annotation ${index + 1}`}
|
|
168
|
+
onClick={() => onRemove(annotation.id)}
|
|
169
|
+
>
|
|
170
|
+
<span aria-hidden="true" className="text-og-md leading-none">
|
|
171
|
+
×
|
|
172
|
+
</span>
|
|
173
|
+
</button>
|
|
174
|
+
) : null}
|
|
175
|
+
</div>
|
|
176
|
+
{unavailableId === annotation.id ? (
|
|
177
|
+
<p role="status" className="mt-1 text-og-xs text-og-status-waiting">
|
|
178
|
+
Source is outside the loaded timeline window.
|
|
179
|
+
</p>
|
|
180
|
+
) : null}
|
|
181
|
+
<blockquote className="mt-2 max-h-28 overflow-auto whitespace-pre-wrap border-l-2 border-og-border pl-2 text-og-sm leading-5 text-og-fg-muted">
|
|
182
|
+
{annotation.quote}
|
|
183
|
+
</blockquote>
|
|
184
|
+
{editable && onUpdate ? (
|
|
185
|
+
<label className="mt-2 block text-og-xs font-medium text-og-fg-muted">
|
|
186
|
+
Note
|
|
187
|
+
<textarea
|
|
188
|
+
ref={(node) => {
|
|
189
|
+
if (node) noteRefs.current.set(annotation.id, node);
|
|
190
|
+
else noteRefs.current.delete(annotation.id);
|
|
191
|
+
}}
|
|
192
|
+
value={annotation.note}
|
|
193
|
+
rows={2}
|
|
194
|
+
maxLength={2048}
|
|
195
|
+
placeholder="What should the agent do with this?"
|
|
196
|
+
className="mt-1 w-full resize-y rounded-og-sm border border-og-border bg-og-surface-1 px-2.5 py-2 text-og-sm leading-5 text-og-fg outline-hidden placeholder:text-og-fg-subtle focus:border-og-accent focus:ring-1 focus:ring-og-accent"
|
|
197
|
+
onInput={(event) => onUpdate(annotation.id, event.currentTarget.value)}
|
|
198
|
+
/>
|
|
199
|
+
</label>
|
|
200
|
+
) : annotation.note ? (
|
|
201
|
+
<p className="mt-2 whitespace-pre-wrap text-og-sm leading-5 text-og-fg">
|
|
202
|
+
{annotation.note}
|
|
203
|
+
</p>
|
|
204
|
+
) : null}
|
|
205
|
+
</section>
|
|
206
|
+
))}
|
|
207
|
+
</div>
|
|
208
|
+
</div>,
|
|
209
|
+
document.body,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export default TimelineAnnotationsDialog;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DraftTimelineAnnotation,
|
|
3
|
+
TimelineAnnotation,
|
|
4
|
+
TimelineAnnotationSource,
|
|
5
|
+
} from "@opengeni/sdk";
|
|
6
|
+
import { Suspense, lazy, useRef, useState } from "react";
|
|
7
|
+
import { cn } from "../lib/cn";
|
|
8
|
+
|
|
9
|
+
export type TimelineAnnotationLike = DraftTimelineAnnotation | TimelineAnnotation;
|
|
10
|
+
|
|
11
|
+
const TimelineAnnotationsDialog = lazy(() => import("./timeline-annotations-dialog"));
|
|
12
|
+
|
|
13
|
+
export function TimelineAnnotationsChip({
|
|
14
|
+
annotations,
|
|
15
|
+
editable = false,
|
|
16
|
+
focusAnnotationId,
|
|
17
|
+
onFocusConsumed,
|
|
18
|
+
onUpdate,
|
|
19
|
+
onRemove,
|
|
20
|
+
onRevealSource,
|
|
21
|
+
className,
|
|
22
|
+
}: {
|
|
23
|
+
annotations: readonly TimelineAnnotationLike[];
|
|
24
|
+
editable?: boolean | undefined;
|
|
25
|
+
focusAnnotationId?: string | null | undefined;
|
|
26
|
+
onFocusConsumed?: (() => void) | undefined;
|
|
27
|
+
onUpdate?: ((id: string, note: string) => void) | undefined;
|
|
28
|
+
onRemove?: ((id: string) => void) | undefined;
|
|
29
|
+
onRevealSource?: ((source: TimelineAnnotationSource) => boolean) | undefined;
|
|
30
|
+
className?: string | undefined;
|
|
31
|
+
}) {
|
|
32
|
+
const [open, setOpen] = useState(false);
|
|
33
|
+
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
34
|
+
|
|
35
|
+
if (annotations.length === 0) return null;
|
|
36
|
+
const focusRequested = Boolean(
|
|
37
|
+
focusAnnotationId && annotations.some((item) => item.id === focusAnnotationId),
|
|
38
|
+
);
|
|
39
|
+
const visible = open || focusRequested;
|
|
40
|
+
const countLabel = `${annotations.length} ${annotations.length === 1 ? "annotation" : "annotations"}`;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
<button
|
|
45
|
+
ref={triggerRef}
|
|
46
|
+
type="button"
|
|
47
|
+
className={cn(
|
|
48
|
+
"inline-flex min-h-8 items-center gap-1.5 rounded-full border border-og-border bg-og-surface-1 px-2.5 py-1 text-og-sm font-medium text-og-fg-muted outline-hidden transition hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-[44px]",
|
|
49
|
+
className,
|
|
50
|
+
)}
|
|
51
|
+
aria-label={`Review ${countLabel}`}
|
|
52
|
+
aria-haspopup="dialog"
|
|
53
|
+
aria-expanded={visible}
|
|
54
|
+
onClick={() => setOpen((current) => !current)}
|
|
55
|
+
>
|
|
56
|
+
{countLabel}
|
|
57
|
+
</button>
|
|
58
|
+
{visible ? (
|
|
59
|
+
<Suspense fallback={null}>
|
|
60
|
+
<TimelineAnnotationsDialog
|
|
61
|
+
annotations={annotations}
|
|
62
|
+
editable={editable}
|
|
63
|
+
focusAnnotationId={focusAnnotationId}
|
|
64
|
+
onFocusConsumed={() => {
|
|
65
|
+
setOpen(true);
|
|
66
|
+
onFocusConsumed?.();
|
|
67
|
+
}}
|
|
68
|
+
onUpdate={onUpdate}
|
|
69
|
+
onRemove={onRemove}
|
|
70
|
+
onRevealSource={onRevealSource}
|
|
71
|
+
triggerRef={triggerRef}
|
|
72
|
+
countLabel={countLabel}
|
|
73
|
+
onDismiss={(restoreFocus) => {
|
|
74
|
+
setOpen(false);
|
|
75
|
+
if (restoreFocus) triggerRef.current?.focus();
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
</Suspense>
|
|
79
|
+
) : null}
|
|
80
|
+
</>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default TimelineAnnotationsChip;
|