@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.
Files changed (325) hide show
  1. package/README.md +138 -2
  2. package/dist/artifacts-document.d.ts +3 -0
  3. package/dist/artifacts-document.js +17 -0
  4. package/dist/artifacts-document.js.map +1 -0
  5. package/dist/artifacts-presentation.d.ts +3 -0
  6. package/dist/artifacts-presentation.js +17 -0
  7. package/dist/artifacts-presentation.js.map +1 -0
  8. package/dist/artifacts-spreadsheet.d.ts +3 -0
  9. package/dist/artifacts-spreadsheet.js +17 -0
  10. package/dist/artifacts-spreadsheet.js.map +1 -0
  11. package/dist/artifacts.d.ts +2 -0
  12. package/dist/artifacts.js +199 -0
  13. package/dist/artifacts.js.map +1 -0
  14. package/dist/{chunk-NJ7M2B6Z.js → chunk-5UQPNLIO.js} +16 -8
  15. package/dist/{chunk-NJ7M2B6Z.js.map → chunk-5UQPNLIO.js.map} +1 -1
  16. package/dist/{chunk-LHLYWG5R.js → chunk-6KLX2RH5.js} +461 -38
  17. package/dist/chunk-6KLX2RH5.js.map +1 -0
  18. package/dist/chunk-7WCLAEFV.js +2828 -0
  19. package/dist/chunk-7WCLAEFV.js.map +1 -0
  20. package/dist/{chunk-I4MPFDRP.js → chunk-B6WPZSTT.js} +104 -104
  21. package/dist/{chunk-I4MPFDRP.js.map → chunk-B6WPZSTT.js.map} +1 -1
  22. package/dist/{chunk-EKZH6IDG.js → chunk-C5ZPSSS5.js} +111 -1
  23. package/dist/chunk-C5ZPSSS5.js.map +1 -0
  24. package/dist/{chunk-OL6QVXGF.js → chunk-HA46CYQK.js} +152 -8
  25. package/dist/chunk-HA46CYQK.js.map +1 -0
  26. package/dist/chunk-HUIKIDM5.js +2148 -0
  27. package/dist/chunk-HUIKIDM5.js.map +1 -0
  28. package/dist/{chunk-FWXGSNXT.js → chunk-IGC6A2ZI.js} +585 -34
  29. package/dist/chunk-IGC6A2ZI.js.map +1 -0
  30. package/dist/chunk-JVYQRRC4.js +53 -0
  31. package/dist/chunk-JVYQRRC4.js.map +1 -0
  32. package/dist/chunk-KCGXPP5V.js +157 -0
  33. package/dist/chunk-KCGXPP5V.js.map +1 -0
  34. package/dist/chunk-LLOLUHVG.js +5937 -0
  35. package/dist/chunk-LLOLUHVG.js.map +1 -0
  36. package/dist/{chunk-WYA65Y3F.js → chunk-MKPIAGVL.js} +13 -3
  37. package/dist/chunk-MKPIAGVL.js.map +1 -0
  38. package/dist/chunk-PEI6RANI.js +3234 -0
  39. package/dist/chunk-PEI6RANI.js.map +1 -0
  40. package/dist/chunk-R3AZOAB5.js +933 -0
  41. package/dist/chunk-R3AZOAB5.js.map +1 -0
  42. package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
  43. package/dist/chunk-WSK7G5LE.js.map +1 -0
  44. package/dist/{chunk-23EJ676W.js → chunk-XBZWOM2K.js} +9 -4
  45. package/dist/chunk-XBZWOM2K.js.map +1 -0
  46. package/dist/{chunk-XCNTXWBB.js → chunk-Y7OEDUJ5.js} +856 -338
  47. package/dist/chunk-Y7OEDUJ5.js.map +1 -0
  48. package/dist/client.d.ts +10 -2
  49. package/dist/clipboard.d.ts +1 -0
  50. package/dist/clipboard.js +7 -0
  51. package/dist/clipboard.js.map +1 -0
  52. package/dist/commands/index.d.ts +3 -3
  53. package/dist/commands/registry.d.ts +1 -1
  54. package/dist/commands/types.d.ts +1 -1
  55. package/dist/components/approval-surface.d.ts +2 -2
  56. package/dist/components/artifacts/artifact-surface.d.ts +18 -0
  57. package/dist/components/artifacts/document-editor.d.ts +172 -0
  58. package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
  59. package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
  60. package/dist/components/artifacts/editable-document.d.ts +24 -0
  61. package/dist/components/artifacts/editable-presentation.d.ts +24 -0
  62. package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
  63. package/dist/components/artifacts/index.d.ts +9 -0
  64. package/dist/components/artifacts/presentation-editor.d.ts +297 -0
  65. package/dist/components/artifacts/published-html-artifact-frame.d.ts +14 -0
  66. package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
  67. package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
  68. package/dist/components/browser-input.d.ts +7 -0
  69. package/dist/components/browser-viewer.d.ts +30 -0
  70. package/dist/components/chat-composer.d.ts +8 -8
  71. package/dist/components/code-editor.d.ts +25 -3
  72. package/dist/components/command-palette.d.ts +2 -2
  73. package/dist/components/composer-transcription-control.d.ts +2 -2
  74. package/dist/components/composer.d.ts +17 -17
  75. package/dist/components/computer-viewer.d.ts +24 -0
  76. package/dist/components/copy-button.d.ts +2 -2
  77. package/dist/components/desktop-viewer.d.ts +50 -22
  78. package/dist/components/diff-view.d.ts +1 -1
  79. package/dist/components/enrollment-consent.d.ts +1 -1
  80. package/dist/components/enrollment-device-flow.d.ts +1 -1
  81. package/dist/components/file-browser.d.ts +2 -2
  82. package/dist/components/fleet-tile.d.ts +1 -1
  83. package/dist/components/generated-video-player.d.ts +10 -0
  84. package/dist/components/human-input-form.d.ts +1 -1
  85. package/dist/components/human-input-surface.d.ts +2 -2
  86. package/dist/components/interaction-intervention-banner.d.ts +11 -0
  87. package/dist/components/interactive-workbench-viewers.d.ts +2 -0
  88. package/dist/components/machine-card.d.ts +2 -2
  89. package/dist/components/machine-dock-bar.d.ts +3 -3
  90. package/dist/components/machine-health-pill.d.ts +2 -2
  91. package/dist/components/machine-input-display.d.ts +1 -1
  92. package/dist/components/machine-metrics.d.ts +2 -2
  93. package/dist/components/machine-status-pill.d.ts +4 -4
  94. package/dist/components/machines/health.d.ts +1 -1
  95. package/dist/components/machines/machine-detail.d.ts +3 -3
  96. package/dist/components/machines/metric-history-chart.d.ts +1 -1
  97. package/dist/components/machines/metric-sparkline.d.ts +2 -2
  98. package/dist/components/machines/series.d.ts +2 -2
  99. package/dist/components/machines-dashboard.d.ts +2 -2
  100. package/dist/components/markdown.d.ts +1 -1
  101. package/dist/components/message-timeline.d.ts +12 -5
  102. package/dist/components/model-picker.d.ts +2 -2
  103. package/dist/components/model-policy-picker.d.ts +7 -7
  104. package/dist/components/pierre-diff.d.ts +1 -1
  105. package/dist/components/pierre-file.d.ts +1 -1
  106. package/dist/components/queue-draft-policy.d.ts +1 -1
  107. package/dist/components/queue-surface-implementation.d.ts +3 -3
  108. package/dist/components/queue-surface-state.d.ts +4 -4
  109. package/dist/components/queue-surface.d.ts +5 -5
  110. package/dist/components/sandbox-files.d.ts +3 -3
  111. package/dist/components/sandbox-terminal.d.ts +24 -4
  112. package/dist/components/sandbox-workspace.d.ts +28 -9
  113. package/dist/components/session-chrome.d.ts +4 -4
  114. package/dist/components/session-status.d.ts +2 -2
  115. package/dist/components/timeline-annotation-selection.d.ts +9 -0
  116. package/dist/components/timeline-annotations-dialog.d.ts +16 -0
  117. package/dist/components/timeline-annotations.d.ts +13 -0
  118. package/dist/components/tooltip.d.ts +4 -4
  119. package/dist/components/user-message-body.d.ts +2 -2
  120. package/dist/components/workbench-changes.d.ts +2 -2
  121. package/dist/components/workspace-dock.d.ts +1 -1
  122. package/dist/composer.d.ts +11 -11
  123. package/dist/composer.js +7 -7
  124. package/dist/hooks/internal.d.ts +1 -1
  125. package/dist/hooks/use-attached-browsers.d.ts +21 -0
  126. package/dist/hooks/use-auth-runs.d.ts +32 -0
  127. package/dist/hooks/use-available-models.d.ts +2 -2
  128. package/dist/hooks/use-billing-usage.d.ts +1 -1
  129. package/dist/hooks/use-browser-downloads.d.ts +25 -0
  130. package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
  131. package/dist/hooks/use-browser-identities.d.ts +30 -0
  132. package/dist/hooks/use-browser-session.d.ts +29 -0
  133. package/dist/hooks/use-browser-sessions.d.ts +32 -0
  134. package/dist/hooks/use-channels.d.ts +26 -0
  135. package/dist/hooks/use-codex-accounts.d.ts +2 -2
  136. package/dist/hooks/use-composer.d.ts +36 -9
  137. package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
  138. package/dist/hooks/use-computer-session.d.ts +29 -0
  139. package/dist/hooks/use-computer-sessions.d.ts +28 -0
  140. package/dist/hooks/use-desktop-stream.d.ts +5 -1
  141. package/dist/hooks/use-environments.d.ts +1 -1
  142. package/dist/hooks/use-file-attachments.d.ts +1 -1
  143. package/dist/hooks/use-goal.d.ts +2 -2
  144. package/dist/hooks/use-human-input.d.ts +2 -2
  145. package/dist/hooks/use-interaction-interventions.d.ts +27 -0
  146. package/dist/hooks/use-interaction-invalidation.d.ts +19 -0
  147. package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
  148. package/dist/hooks/use-machine-chip.d.ts +2 -2
  149. package/dist/hooks/use-machines.d.ts +4 -4
  150. package/dist/hooks/use-network-routes.d.ts +25 -0
  151. package/dist/hooks/use-packs.d.ts +7 -2
  152. package/dist/hooks/use-rigs.d.ts +1 -1
  153. package/dist/hooks/use-sandbox-files.d.ts +6 -4
  154. package/dist/hooks/use-sandbox-git.d.ts +6 -5
  155. package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
  156. package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
  157. package/dist/hooks/use-session-capabilities.d.ts +5 -4
  158. package/dist/hooks/use-session-control.d.ts +1 -1
  159. package/dist/hooks/use-session-events.d.ts +2 -2
  160. package/dist/hooks/use-session-lineage.d.ts +1 -1
  161. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  162. package/dist/hooks/use-session.d.ts +2 -2
  163. package/dist/hooks/use-site-auth-connections.d.ts +25 -0
  164. package/dist/hooks/use-slash-commands.d.ts +1 -1
  165. package/dist/hooks/use-supergrok-accounts.d.ts +26 -0
  166. package/dist/hooks/use-terminal-stream.d.ts +11 -2
  167. package/dist/hooks/use-turn-queue.d.ts +2 -2
  168. package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
  169. package/dist/hooks/use-voice-input.d.ts +1 -1
  170. package/dist/hooks/use-workspace-capture.d.ts +1 -1
  171. package/dist/hooks/use-workspace-edit.d.ts +1 -1
  172. package/dist/hooks/use-workspace-sessions.d.ts +1 -1
  173. package/dist/hooks/use-workspaces.d.ts +1 -1
  174. package/dist/index.d.ts +162 -155
  175. package/dist/index.js +1822 -1703
  176. package/dist/index.js.map +1 -1
  177. package/dist/interaction.d.ts +33 -0
  178. package/dist/interaction.js +550 -0
  179. package/dist/interaction.js.map +1 -0
  180. package/dist/interactive-workbench-viewers-TOHBXJEE.js +15 -0
  181. package/dist/interactive-workbench-viewers-TOHBXJEE.js.map +1 -0
  182. package/dist/lib/host-platform.d.ts +5 -0
  183. package/dist/lib/relay-wire.d.ts +24 -1
  184. package/dist/lib/sandbox-liveness.d.ts +9 -0
  185. package/dist/lib/terminal-capability.d.ts +5 -0
  186. package/dist/lib/xterm-theme.d.ts +1 -1
  187. package/dist/machines.d.ts +30 -30
  188. package/dist/machines.js +2 -2
  189. package/dist/model-policy.d.ts +1 -1
  190. package/dist/model-policy.js +1 -1
  191. package/dist/provider.d.ts +6 -5
  192. package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
  193. package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
  194. package/dist/realtime/dropdown-menu.d.ts +8 -8
  195. package/dist/realtime/realtime-control.d.ts +8 -8
  196. package/dist/realtime.d.ts +4 -4
  197. package/dist/realtime.js +38 -11
  198. package/dist/realtime.js.map +1 -1
  199. package/dist/session-context.d.ts +28 -2
  200. package/dist/session-ui.d.ts +14 -14
  201. package/dist/session-ui.js +5 -4
  202. package/dist/session.d.ts +28 -28
  203. package/dist/session.js +10 -10
  204. package/dist/timeline/activity-rail.d.ts +5 -3
  205. package/dist/timeline/disclosure-context.d.ts +1 -1
  206. package/dist/timeline/fleet-decision-projection.d.ts +1 -1
  207. package/dist/timeline/fleet-decision-row.d.ts +3 -3
  208. package/dist/timeline/index.d.ts +19 -19
  209. package/dist/timeline/parsers.d.ts +4 -1
  210. package/dist/timeline/projection.d.ts +3 -3
  211. package/dist/timeline/registry.d.ts +8 -2
  212. package/dist/timeline/screenshot-lightbox.d.ts +2 -2
  213. package/dist/timeline/shared.d.ts +21 -10
  214. package/dist/timeline/tool-diff.d.ts +2 -2
  215. package/dist/timeline/tool-renderers.d.ts +1 -1
  216. package/dist/timeline/turn-summary.d.ts +3 -3
  217. package/dist/timeline/types.d.ts +44 -3
  218. package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
  219. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
  220. package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
  221. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
  222. package/package.json +36 -7
  223. package/src/artifacts-document.ts +31 -0
  224. package/src/artifacts-presentation.ts +36 -0
  225. package/src/artifacts-spreadsheet.ts +21 -0
  226. package/src/artifacts.ts +89 -0
  227. package/src/client.ts +157 -1
  228. package/src/clipboard.ts +2 -0
  229. package/src/components/artifacts/artifact-surface.tsx +93 -0
  230. package/src/components/artifacts/document-editor.tsx +2255 -0
  231. package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
  232. package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
  233. package/src/components/artifacts/editable-document.tsx +657 -0
  234. package/src/components/artifacts/editable-presentation.tsx +1020 -0
  235. package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
  236. package/src/components/artifacts/index.ts +104 -0
  237. package/src/components/artifacts/presentation-editor.tsx +3148 -0
  238. package/src/components/artifacts/published-html-artifact-frame.tsx +38 -0
  239. package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
  240. package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
  241. package/src/components/browser-input.ts +65 -0
  242. package/src/components/browser-viewer.tsx +2702 -0
  243. package/src/components/chat-composer.tsx +13 -0
  244. package/src/components/code-editor.tsx +89 -30
  245. package/src/components/composer-transcription-control.tsx +10 -6
  246. package/src/components/computer-viewer.tsx +1636 -0
  247. package/src/components/desktop-viewer.tsx +215 -74
  248. package/src/components/generated-video-player.tsx +89 -0
  249. package/src/components/interaction-intervention-banner.tsx +99 -0
  250. package/src/components/interactive-workbench-viewers.ts +2 -0
  251. package/src/components/machine-input-display.ts +3 -0
  252. package/src/components/machines/machine-detail.tsx +1 -1
  253. package/src/components/message-timeline.tsx +289 -32
  254. package/src/components/model-policy-picker.tsx +15 -0
  255. package/src/components/queue-surface-implementation.tsx +116 -60
  256. package/src/components/sandbox-files.tsx +2 -2
  257. package/src/components/sandbox-terminal.tsx +128 -46
  258. package/src/components/sandbox-workspace.tsx +256 -90
  259. package/src/components/session-chrome.tsx +53 -13
  260. package/src/components/timeline-annotation-selection.tsx +221 -0
  261. package/src/components/timeline-annotations-dialog.tsx +213 -0
  262. package/src/components/timeline-annotations.tsx +84 -0
  263. package/src/components/workbench-changes.tsx +188 -31
  264. package/src/components/workspace-dock.tsx +59 -5
  265. package/src/hooks/use-attached-browsers.ts +154 -0
  266. package/src/hooks/use-auth-runs.ts +278 -0
  267. package/src/hooks/use-browser-downloads.ts +218 -0
  268. package/src/hooks/use-browser-frame-stream.ts +425 -0
  269. package/src/hooks/use-browser-identities.ts +266 -0
  270. package/src/hooks/use-browser-session.ts +543 -0
  271. package/src/hooks/use-browser-sessions.ts +286 -0
  272. package/src/hooks/use-channels.ts +99 -0
  273. package/src/hooks/use-composer.ts +769 -24
  274. package/src/hooks/use-computer-frame-stream.ts +434 -0
  275. package/src/hooks/use-computer-session.ts +462 -0
  276. package/src/hooks/use-computer-sessions.ts +251 -0
  277. package/src/hooks/use-desktop-stream.ts +39 -7
  278. package/src/hooks/use-interaction-interventions.ts +242 -0
  279. package/src/hooks/use-interaction-invalidation.ts +66 -0
  280. package/src/hooks/use-machine-chip.ts +15 -3
  281. package/src/hooks/use-machines.ts +33 -3
  282. package/src/hooks/use-network-routes.ts +214 -0
  283. package/src/hooks/use-packs.ts +51 -0
  284. package/src/hooks/use-sandbox-files.ts +98 -55
  285. package/src/hooks/use-sandbox-git.ts +93 -38
  286. package/src/hooks/use-sandbox-terminal.ts +3 -2
  287. package/src/hooks/use-session-capabilities.ts +59 -46
  288. package/src/hooks/use-site-auth-connections.ts +226 -0
  289. package/src/hooks/use-supergrok-accounts.ts +81 -0
  290. package/src/hooks/use-terminal-stream.ts +359 -47
  291. package/src/hooks/use-video-artifact-playback.ts +28 -0
  292. package/src/hooks/use-workspace-edit.ts +2 -1
  293. package/src/index.ts +18 -0
  294. package/src/interaction.ts +101 -0
  295. package/src/lib/host-platform.ts +10 -0
  296. package/src/lib/relay-wire.ts +47 -4
  297. package/src/lib/sandbox-liveness.ts +11 -0
  298. package/src/lib/terminal-capability.ts +10 -0
  299. package/src/model-policy.ts +19 -4
  300. package/src/provider.tsx +133 -1
  301. package/src/realtime/realtime-control.tsx +34 -6
  302. package/src/session-context.ts +156 -1
  303. package/src/timeline/activity-rail.tsx +25 -3
  304. package/src/timeline/index.ts +3 -0
  305. package/src/timeline/parsers.ts +14 -1
  306. package/src/timeline/projection.ts +269 -14
  307. package/src/timeline/registry.ts +13 -1
  308. package/src/timeline/screenshot-lightbox.tsx +19 -9
  309. package/src/timeline/shared.tsx +56 -8
  310. package/src/timeline/tool-renderers.tsx +303 -50
  311. package/src/timeline/types.ts +57 -3
  312. package/styles/compiled.css +312 -21
  313. package/styles/index.css +15 -0
  314. package/styles/responsive.css +13 -1
  315. package/dist/chunk-23EJ676W.js.map +0 -1
  316. package/dist/chunk-EKZH6IDG.js.map +0 -1
  317. package/dist/chunk-FWXGSNXT.js.map +0 -1
  318. package/dist/chunk-LHLYWG5R.js.map +0 -1
  319. package/dist/chunk-O4VXOBGF.js.map +0 -1
  320. package/dist/chunk-OL6QVXGF.js.map +0 -1
  321. package/dist/chunk-WYA65Y3F.js.map +0 -1
  322. package/dist/chunk-XCNTXWBB.js.map +0 -1
  323. package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
  324. package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
  325. 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: "Changing direction…",
314
- detail: steering.text,
315
- tone: "accent",
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.phase === "submitting" ? (
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
- }, [agentsSignal, elapsed, goalState, incoming, queuedTurns, record, steering]);
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 phase={steering.phase} text={steering.text} />
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({ phase, text }: { phase: "submitting" | "accepted"; text: string }) {
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
- <p className="truncate text-og-xs font-medium leading-4 text-og-fg">{text}</p>
782
- <p className="mt-0.5 text-[10px] leading-4 text-og-fg-muted">
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
- : "Direction accepted. The agent will continue from it."}
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;