@adecore/agents-react 0.31.12 → 0.33.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 (409) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/AccountDot.js +57 -6
  3. package/dist/agents/AgentIcon.js +88 -12
  4. package/dist/agents/ModelOptionControl.js +153 -18
  5. package/dist/agents/ProviderLogo.js +49 -4
  6. package/dist/agents/account-limits.js +17 -14
  7. package/dist/agents/accounts.js +99 -67
  8. package/dist/agents/agent-marks.js +5 -3
  9. package/dist/agents/model-name.js +42 -31
  10. package/dist/agents/model-options.js +8 -8
  11. package/dist/agents/model-requirement.js +17 -14
  12. package/dist/agents/option-words.d.ts +7 -0
  13. package/dist/agents/option-words.js +35 -0
  14. package/dist/agents/provider-paths.js +6 -6
  15. package/dist/agents/status-look.js +32 -12
  16. package/dist/chat/account-choice.d.ts +13 -0
  17. package/dist/chat/account-choice.js +90 -20
  18. package/dist/chat/actions.js +126 -59
  19. package/dist/chat/activity.js +5 -5
  20. package/dist/chat/attachments.js +74 -53
  21. package/dist/chat/bookmarks.js +58 -48
  22. package/dist/chat/chat-client.js +344 -263
  23. package/dist/chat/chat-references.js +3 -3
  24. package/dist/chat/composer-effort.d.ts +5 -0
  25. package/dist/chat/composer-effort.js +26 -0
  26. package/dist/chat/composer-send.js +13 -13
  27. package/dist/chat/drafts.js +115 -79
  28. package/dist/chat/forks.js +81 -24
  29. package/dist/chat/guards.js +15 -43
  30. package/dist/chat/index.d.ts +3 -1
  31. package/dist/chat/index.js +3 -2
  32. package/dist/chat/logic/ansi.js +170 -167
  33. package/dist/chat/logic/answers.js +2 -2
  34. package/dist/chat/logic/bookmarks.js +19 -19
  35. package/dist/chat/logic/command-looks.js +138 -96
  36. package/dist/chat/logic/context-usage.js +13 -9
  37. package/dist/chat/logic/continue-after-stop.js +15 -19
  38. package/dist/chat/logic/effort.d.ts +14 -0
  39. package/dist/chat/logic/effort.js +34 -0
  40. package/dist/chat/logic/embedded-code.js +387 -326
  41. package/dist/chat/logic/fork.js +134 -110
  42. package/dist/chat/logic/generated-image.js +54 -23
  43. package/dist/chat/logic/goal.js +28 -20
  44. package/dist/chat/logic/handback.js +6 -6
  45. package/dist/chat/logic/json.js +2 -2
  46. package/dist/chat/logic/limit.js +46 -30
  47. package/dist/chat/logic/picker-entries.d.ts +5 -3
  48. package/dist/chat/logic/picker-entries.js +83 -43
  49. package/dist/chat/logic/resume-compaction.d.ts +14 -1
  50. package/dist/chat/logic/resume-compaction.js +93 -24
  51. package/dist/chat/logic/scrubber.js +188 -136
  52. package/dist/chat/logic/thought.js +13 -16
  53. package/dist/chat/logic/thread-cards.js +60 -48
  54. package/dist/chat/logic/timeline-copy.js +30 -38
  55. package/dist/chat/logic/timeline-target.js +92 -63
  56. package/dist/chat/logic/timeline.js +499 -355
  57. package/dist/chat/logic/tool-catalog.js +54 -34
  58. package/dist/chat/logic/tools.js +106 -82
  59. package/dist/chat/logic/visual-height.js +18 -18
  60. package/dist/chat/logic/visual-viewport.js +6 -3
  61. package/dist/chat/logic/welcome.js +9 -9
  62. package/dist/chat/logic/workflow.d.ts +10 -0
  63. package/dist/chat/logic/workflow.js +130 -32
  64. package/dist/chat/mentions.js +149 -109
  65. package/dist/chat/persisted-json.js +51 -54
  66. package/dist/chat/preferences.d.ts +4 -0
  67. package/dist/chat/preferences.js +162 -84
  68. package/dist/chat/prompt-hold.js +9 -9
  69. package/dist/chat/quote.js +21 -30
  70. package/dist/chat/reading-width.js +12 -10
  71. package/dist/chat/recent-messages.js +34 -21
  72. package/dist/chat/resume-compaction-dismissals.js +25 -5
  73. package/dist/chat/runtime-modes.js +3 -3
  74. package/dist/chat/send-key.js +11 -7
  75. package/dist/chat/stash.js +89 -69
  76. package/dist/chat/subagent-conversation.js +181 -139
  77. package/dist/chat/subagent-list.js +65 -67
  78. package/dist/chat/subagent-support.js +12 -7
  79. package/dist/chat/subagent-view.js +132 -44
  80. package/dist/chat/thumbnails.js +88 -60
  81. package/dist/chat/timeline-flash.js +24 -12
  82. package/dist/chat/timeline-scroll.js +116 -86
  83. package/dist/chat/ui/AccountPill.js +126 -15
  84. package/dist/chat/ui/AnsiOutput.js +62 -6
  85. package/dist/chat/ui/BookmarkMarker.js +231 -41
  86. package/dist/chat/ui/BookmarkSubmenu.js +165 -18
  87. package/dist/chat/ui/ChatActivity.js +649 -70
  88. package/dist/chat/ui/ChatReferenceChip.js +104 -9
  89. package/dist/chat/ui/CodeBlock.js +132 -58
  90. package/dist/chat/ui/Composer.js +866 -132
  91. package/dist/chat/ui/ComposerInput.js +309 -120
  92. package/dist/chat/ui/DiffPool.js +75 -14
  93. package/dist/chat/ui/EditDiff.js +160 -21
  94. package/dist/chat/ui/FadingWords.js +32 -3
  95. package/dist/chat/ui/GoalDock.js +235 -42
  96. package/dist/chat/ui/ImageView.js +617 -61
  97. package/dist/chat/ui/LimitState.js +446 -72
  98. package/dist/chat/ui/Markdown.js +991 -173
  99. package/dist/chat/ui/MermaidBlock.js +285 -79
  100. package/dist/chat/ui/MessageActions.js +251 -39
  101. package/dist/chat/ui/ModelMarks.js +55 -5
  102. package/dist/chat/ui/Pickers.js +664 -113
  103. package/dist/chat/ui/PromptComposer.js +281 -51
  104. package/dist/chat/ui/QuoteButton.js +151 -57
  105. package/dist/chat/ui/ResumeCompactionDock.d.ts +6 -2
  106. package/dist/chat/ui/ResumeCompactionDock.js +141 -6
  107. package/dist/chat/ui/RowTime.js +59 -5
  108. package/dist/chat/ui/RunSettings.js +1744 -154
  109. package/dist/chat/ui/Scrubber.js +517 -88
  110. package/dist/chat/ui/ShellCommand.js +183 -107
  111. package/dist/chat/ui/SubagentControls.js +212 -24
  112. package/dist/chat/ui/SubagentInfo.js +170 -13
  113. package/dist/chat/ui/SubagentStopButton.js +115 -24
  114. package/dist/chat/ui/SubagentTimeline.js +357 -91
  115. package/dist/chat/ui/Timeline.js +988 -476
  116. package/dist/chat/ui/TimelineMenu.js +552 -37
  117. package/dist/chat/ui/UnifiedDiff.js +234 -27
  118. package/dist/chat/ui/UploadThumb.js +37 -7
  119. package/dist/chat/ui/VisualDialogs.js +251 -20
  120. package/dist/chat/ui/VisualFrame.js +321 -95
  121. package/dist/chat/ui/Welcome.js +72 -7
  122. package/dist/chat/ui/chips.js +1 -1
  123. package/dist/chat/ui/code-lines.js +36 -36
  124. package/dist/chat/ui/code-streaming.js +1 -1
  125. package/dist/chat/ui/code-theme.js +7 -7
  126. package/dist/chat/ui/composer/ComposerControls.js +329 -21
  127. package/dist/chat/ui/composer/ComposerMenu.js +358 -30
  128. package/dist/chat/ui/composer/DraftParts.js +185 -11
  129. package/dist/chat/ui/composer/QueuedMessages.js +138 -13
  130. package/dist/chat/ui/composer/chips.js +29 -19
  131. package/dist/chat/ui/composer/editor.js +79 -63
  132. package/dist/chat/ui/composer/insert.js +26 -12
  133. package/dist/chat/ui/composer/key-handler.js +180 -127
  134. package/dist/chat/ui/composer/keys.js +90 -73
  135. package/dist/chat/ui/composer/menu-aria.js +18 -9
  136. package/dist/chat/ui/composer/placeholder.js +78 -31
  137. package/dist/chat/ui/composer/surface.js +13 -37
  138. package/dist/chat/ui/composer/use-chat-controls.js +230 -90
  139. package/dist/chat/ui/composer/use-compaction-offer.js +50 -18
  140. package/dist/chat/ui/composer/use-composer-draft.js +65 -34
  141. package/dist/chat/ui/composer/use-composer-menus.js +478 -193
  142. package/dist/chat/ui/composer/use-composer-submit.js +251 -109
  143. package/dist/chat/ui/composer/use-continuable-turn.js +33 -6
  144. package/dist/chat/ui/composer/use-draft-files.js +245 -93
  145. package/dist/chat/ui/composer/use-effort-stepper.d.ts +13 -0
  146. package/dist/chat/ui/composer/use-effort-stepper.js +88 -0
  147. package/dist/chat/ui/composer/use-notice.js +61 -16
  148. package/dist/chat/ui/composer/use-pending-requests.js +38 -17
  149. package/dist/chat/ui/composer/use-prompt-history.js +117 -45
  150. package/dist/chat/ui/composer/use-prompt-hold.js +119 -35
  151. package/dist/chat/ui/composer/use-take-back.js +94 -72
  152. package/dist/chat/ui/diff/ApprovalReview.js +101 -23
  153. package/dist/chat/ui/diff/DiffFallbackBoundary.js +21 -13
  154. package/dist/chat/ui/diff/DiffOmitted.js +56 -6
  155. package/dist/chat/ui/diff/DiffStack.js +92 -8
  156. package/dist/chat/ui/diff/DiffUnavailable.js +56 -6
  157. package/dist/chat/ui/diff/EditorEditDiff.js +263 -51
  158. package/dist/chat/ui/diff/EditorPatchDiff.js +39 -14
  159. package/dist/chat/ui/diff/LegacyDiffPool.js +27 -10
  160. package/dist/chat/ui/diff/LegacyEditDiff.js +97 -20
  161. package/dist/chat/ui/diff/LegacyUnifiedDiff.js +172 -42
  162. package/dist/chat/ui/diff/RawDiff.js +65 -5
  163. package/dist/chat/ui/diff/ReviewDiff.js +77 -5
  164. package/dist/chat/ui/diff/diff-props.js +1 -1
  165. package/dist/chat/ui/diff/diff-source-budget.js +46 -35
  166. package/dist/chat/ui/diff/editor-edit-model.js +18 -14
  167. package/dist/chat/ui/diff/review-plan.js +63 -40
  168. package/dist/chat/ui/diff/review-settle.js +24 -9
  169. package/dist/chat/ui/diff/syntax-runtime.js +236 -201
  170. package/dist/chat/ui/diff/use-review-stack.js +146 -25
  171. package/dist/chat/ui/diff-syntax.js +70 -58
  172. package/dist/chat/ui/diff-theme.js +31 -12
  173. package/dist/chat/ui/file-links.js +39 -10
  174. package/dist/chat/ui/find-reveal.js +29 -8
  175. package/dist/chat/ui/full-diff.js +17 -12
  176. package/dist/chat/ui/highlighter.js +143 -113
  177. package/dist/chat/ui/icons.js +16 -7
  178. package/dist/chat/ui/index.js +1 -1
  179. package/dist/chat/ui/intelligent-ui/UiBlockFrame.js +556 -82
  180. package/dist/chat/ui/intelligent-ui/UiFallbackPart.js +73 -5
  181. package/dist/chat/ui/intelligent-ui/UiReply.js +666 -262
  182. package/dist/chat/ui/intelligent-ui/block-local.js +59 -31
  183. package/dist/chat/ui/intelligent-ui/chart-data.js +77 -67
  184. package/dist/chat/ui/intelligent-ui/node-text.js +77 -91
  185. package/dist/chat/ui/intelligent-ui/registry.js +46 -46
  186. package/dist/chat/ui/intelligent-ui/render-context.js +1 -1
  187. package/dist/chat/ui/intelligent-ui/renderers/base-land.js +1 -1
  188. package/dist/chat/ui/intelligent-ui/renderers/binding.js +3 -3
  189. package/dist/chat/ui/intelligent-ui/renderers/chart.js +1223 -123
  190. package/dist/chat/ui/intelligent-ui/renderers/choices.js +312 -34
  191. package/dist/chat/ui/intelligent-ui/renderers/content.js +1241 -86
  192. package/dist/chat/ui/intelligent-ui/renderers/data.js +619 -54
  193. package/dist/chat/ui/intelligent-ui/renderers/inputs.js +868 -67
  194. package/dist/chat/ui/intelligent-ui/renderers/links.js +366 -62
  195. package/dist/chat/ui/intelligent-ui/renderers/map-slots.js +118 -60
  196. package/dist/chat/ui/intelligent-ui/renderers/map-style.js +85 -78
  197. package/dist/chat/ui/intelligent-ui/renderers/map-view.js +282 -217
  198. package/dist/chat/ui/intelligent-ui/renderers/map.js +621 -69
  199. package/dist/chat/ui/intelligent-ui/renderers/place-point.js +15 -8
  200. package/dist/chat/ui/intelligent-ui/renderers/structure.js +262 -20
  201. package/dist/chat/ui/intelligent-ui/renderers/text.js +674 -49
  202. package/dist/chat/ui/intelligent-ui/reply-parts.js +24 -15
  203. package/dist/chat/ui/intelligent-ui/table-data.js +97 -57
  204. package/dist/chat/ui/intelligent-ui/tones.js +31 -31
  205. package/dist/chat/ui/intelligent-ui/use-in-view.js +30 -12
  206. package/dist/chat/ui/intelligent-ui/use-later.js +28 -13
  207. package/dist/chat/ui/intelligent-ui/use-truncated.js +45 -21
  208. package/dist/chat/ui/intelligent-ui/use-ui-links.js +296 -109
  209. package/dist/chat/ui/intelligent-ui/use-ui-queries.js +256 -246
  210. package/dist/chat/ui/markdown-blocks.js +296 -262
  211. package/dist/chat/ui/mermaid.d.ts +8 -20
  212. package/dist/chat/ui/mermaid.js +47 -84
  213. package/dist/chat/ui/quote-selection.js +32 -29
  214. package/dist/chat/ui/rehype-chips.js +35 -23
  215. package/dist/chat/ui/rehype-fade.js +28 -17
  216. package/dist/chat/ui/remark-html-as-text.js +22 -15
  217. package/dist/chat/ui/reply-context.js +1 -1
  218. package/dist/chat/ui/reveal.js +161 -72
  219. package/dist/chat/ui/rows/ForksRow.js +226 -24
  220. package/dist/chat/ui/rows/GeneratedImageRow.js +541 -87
  221. package/dist/chat/ui/rows/MessageRows.js +1706 -159
  222. package/dist/chat/ui/rows/RowContainer.js +57 -7
  223. package/dist/chat/ui/rows/Rows.js +463 -54
  224. package/dist/chat/ui/rows/SubagentRow.js +577 -60
  225. package/dist/chat/ui/rows/TaskRow.js +359 -40
  226. package/dist/chat/ui/rows/VisualRow.js +233 -11
  227. package/dist/chat/ui/rows/WorkRows.d.ts +3 -0
  228. package/dist/chat/ui/rows/WorkRows.js +1945 -164
  229. package/dist/chat/ui/rows/WorkflowRow.js +756 -37
  230. package/dist/chat/ui/rows/row-rhythm.js +27 -17
  231. package/dist/chat/ui/theme-watch.js +26 -23
  232. package/dist/chat/ui/timestamp-format.js +4 -1
  233. package/dist/chat/ui/use-diff-source-tokens.js +70 -34
  234. package/dist/chat/ui/useToggleSet.js +51 -19
  235. package/dist/chat/ui/visual-theme.js +153 -128
  236. package/dist/chat/visual-bridge.js +123 -123
  237. package/dist/chat/visual-viewport.js +94 -83
  238. package/dist/chat/visuals.js +13 -5
  239. package/dist/host-hooks.d.ts +29 -0
  240. package/dist/host-hooks.js +78 -0
  241. package/dist/host.js +120 -48
  242. package/dist/index.js +1 -1
  243. package/dist/lazy.js +38 -32
  244. package/dist/locales/en/agent-chat.d.ts +42 -0
  245. package/dist/locales/en/agent-chat.js +755 -713
  246. package/dist/locales/en/agent-prompts.js +42 -42
  247. package/dist/locales/en/agent-providers.d.ts +1 -0
  248. package/dist/locales/en/agent-providers.js +166 -165
  249. package/dist/locales/en/agent-usage.d.ts +6 -0
  250. package/dist/locales/en/agent-usage.js +129 -123
  251. package/dist/locales/nl/agent-chat.d.ts +42 -0
  252. package/dist/locales/nl/agent-chat.js +755 -713
  253. package/dist/locales/nl/agent-prompts.js +42 -42
  254. package/dist/locales/nl/agent-providers.d.ts +1 -0
  255. package/dist/locales/nl/agent-providers.js +166 -165
  256. package/dist/locales/nl/agent-usage.d.ts +6 -0
  257. package/dist/locales/nl/agent-usage.js +129 -123
  258. package/dist/locales.js +14 -14
  259. package/dist/mounted-registry.js +31 -30
  260. package/dist/port-transport.js +122 -111
  261. package/dist/prompts/index.js +1 -1
  262. package/dist/prompts/logic/focus.js +15 -14
  263. package/dist/prompts/logic/keys.js +93 -59
  264. package/dist/prompts/logic/prompts.js +27 -14
  265. package/dist/prompts/logic/subjects.js +60 -46
  266. package/dist/prompts/logic/useFocusAfterAnswer.js +60 -20
  267. package/dist/prompts/logic/usePromptSession.js +88 -77
  268. package/dist/prompts/ui/ApprovalBody.js +246 -27
  269. package/dist/prompts/ui/PromptActions.js +215 -9
  270. package/dist/prompts/ui/PromptCard.js +215 -36
  271. package/dist/prompts/ui/PromptView.js +372 -34
  272. package/dist/prompts/ui/QuestionBody.js +269 -73
  273. package/dist/providers/AccountColors.js +36 -5
  274. package/dist/providers/AccountDetail.js +683 -50
  275. package/dist/providers/AccountVariables.js +139 -32
  276. package/dist/providers/AddAccountForm.js +125 -38
  277. package/dist/providers/CliDetail.js +343 -36
  278. package/dist/providers/InstallPage.js +644 -59
  279. package/dist/providers/ProviderList.d.ts +2 -1
  280. package/dist/providers/ProviderList.js +215 -23
  281. package/dist/providers/ProvidersPane.d.ts +4 -2
  282. package/dist/providers/ProvidersPane.js +244 -64
  283. package/dist/providers/account-actions.js +54 -30
  284. package/dist/providers/index.js +1 -1
  285. package/dist/providers/install-steps.js +12 -9
  286. package/dist/providers/pane-target.d.ts +3 -0
  287. package/dist/providers/pane-target.js +34 -13
  288. package/dist/providers/parts.js +62 -12
  289. package/dist/providers/provider-abilities.js +5 -5
  290. package/dist/providers/provider-groups.js +75 -40
  291. package/dist/providers/variables.js +73 -39
  292. package/dist/scope.js +6 -6
  293. package/dist/settings/sections.js +18 -18
  294. package/dist/state/chats.js +404 -192
  295. package/dist/state/index.js +1 -1
  296. package/dist/state/provider-accounts.js +70 -47
  297. package/dist/state/providers.js +45 -16
  298. package/dist/state/usage.js +154 -77
  299. package/dist/storage.js +69 -68
  300. package/dist/testing/index.js +1 -1
  301. package/dist/testing/prompts.js +105 -67
  302. package/dist/transport.js +10 -10
  303. package/dist/ui/PlainTextarea.js +27 -3
  304. package/dist/usage/LimitsList.js +646 -40
  305. package/dist/usage/UsageBreakdown.js +879 -38
  306. package/dist/usage/UsageChart.js +311 -32
  307. package/dist/usage/UsageDialog.js +46 -4
  308. package/dist/usage/UsageLimits.js +467 -34
  309. package/dist/usage/UsageLimitsCard.js +95 -11
  310. package/dist/usage/UsagePage.js +817 -96
  311. package/dist/usage/UsagePane.js +164 -10
  312. package/dist/usage/UsageSummary.js +151 -6
  313. package/dist/usage/UsageTiles.js +164 -14
  314. package/dist/usage/format.js +32 -32
  315. package/dist/usage/index.d.ts +1 -1
  316. package/dist/usage/index.js +2 -1
  317. package/dist/usage/limit-groups.d.ts +2 -0
  318. package/dist/usage/limit-groups.js +77 -59
  319. package/dist/usage/limits.js +78 -43
  320. package/dist/usage/money.js +20 -4
  321. package/dist/usage/summary.js +85 -64
  322. package/package.json +9 -9
  323. package/src/agents/ModelOptionControl.tsx +3 -1
  324. package/src/agents/option-words.ts +27 -0
  325. package/src/chat/account-choice.ts +41 -1
  326. package/src/chat/composer-effort.ts +32 -0
  327. package/src/chat/forks.ts +4 -5
  328. package/src/chat/index.ts +3 -1
  329. package/src/chat/logic/effort.ts +32 -0
  330. package/src/chat/logic/picker-entries.ts +45 -10
  331. package/src/chat/logic/resume-compaction.ts +95 -17
  332. package/src/chat/logic/timeline.ts +11 -3
  333. package/src/chat/logic/workflow.ts +86 -4
  334. package/src/chat/preferences.ts +35 -0
  335. package/src/chat/subagent-view.ts +2 -3
  336. package/src/chat/ui/AnsiOutput.tsx +1 -2
  337. package/src/chat/ui/BookmarkSubmenu.tsx +3 -3
  338. package/src/chat/ui/ChatActivity.tsx +14 -22
  339. package/src/chat/ui/ChatReferenceChip.tsx +2 -3
  340. package/src/chat/ui/Composer.tsx +15 -7
  341. package/src/chat/ui/EditDiff.tsx +2 -3
  342. package/src/chat/ui/LimitState.tsx +2 -2
  343. package/src/chat/ui/Markdown.tsx +10 -8
  344. package/src/chat/ui/MermaidBlock.tsx +57 -49
  345. package/src/chat/ui/Pickers.tsx +49 -29
  346. package/src/chat/ui/PromptComposer.tsx +7 -9
  347. package/src/chat/ui/ResumeCompactionDock.tsx +16 -4
  348. package/src/chat/ui/RunSettings.tsx +67 -28
  349. package/src/chat/ui/Scrubber.tsx +3 -3
  350. package/src/chat/ui/ShellCommand.tsx +2 -0
  351. package/src/chat/ui/SubagentInfo.tsx +2 -3
  352. package/src/chat/ui/SubagentTimeline.tsx +12 -16
  353. package/src/chat/ui/Timeline.tsx +6 -6
  354. package/src/chat/ui/UnifiedDiff.tsx +3 -4
  355. package/src/chat/ui/VisualFrame.tsx +5 -8
  356. package/src/chat/ui/code-theme.ts +4 -4
  357. package/src/chat/ui/composer/placeholder.ts +2 -2
  358. package/src/chat/ui/composer/use-compaction-offer.ts +36 -16
  359. package/src/chat/ui/composer/use-composer-menus.ts +4 -9
  360. package/src/chat/ui/composer/use-composer-submit.ts +2 -1
  361. package/src/chat/ui/composer/use-effort-stepper.ts +56 -0
  362. package/src/chat/ui/composer/use-notice.ts +2 -2
  363. package/src/chat/ui/diff/EditorEditDiff.tsx +9 -13
  364. package/src/chat/ui/diff/LegacyEditDiff.tsx +12 -17
  365. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +13 -21
  366. package/src/chat/ui/diff/use-review-stack.ts +4 -4
  367. package/src/chat/ui/diff-theme.ts +3 -3
  368. package/src/chat/ui/file-links.ts +2 -2
  369. package/src/chat/ui/intelligent-ui/UiReply.tsx +2 -0
  370. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +3 -3
  371. package/src/chat/ui/intelligent-ui/renderers/content.tsx +5 -6
  372. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +2 -1
  373. package/src/chat/ui/intelligent-ui/renderers/map.tsx +2 -1
  374. package/src/chat/ui/intelligent-ui/use-truncated.ts +16 -19
  375. package/src/chat/ui/intelligent-ui/use-ui-links.ts +3 -3
  376. package/src/chat/ui/mermaid.ts +52 -96
  377. package/src/chat/ui/rows/ForksRow.tsx +3 -5
  378. package/src/chat/ui/rows/GeneratedImageRow.tsx +2 -1
  379. package/src/chat/ui/rows/MessageRows.tsx +12 -21
  380. package/src/chat/ui/rows/SubagentRow.tsx +3 -3
  381. package/src/chat/ui/rows/TaskRow.tsx +1 -1
  382. package/src/chat/ui/rows/WorkRows.tsx +11 -6
  383. package/src/chat/ui/rows/WorkflowRow.tsx +144 -54
  384. package/src/chat/ui/useToggleSet.ts +15 -18
  385. package/src/chat/ui/visual-theme.ts +2 -3
  386. package/src/host-hooks.ts +113 -0
  387. package/src/lazy.tsx +2 -0
  388. package/src/locales/en/agent-chat.ts +142 -108
  389. package/src/locales/en/agent-prompts.ts +7 -7
  390. package/src/locales/en/agent-providers.ts +35 -34
  391. package/src/locales/en/agent-usage.ts +26 -20
  392. package/src/locales/nl/agent-chat.ts +134 -100
  393. package/src/locales/nl/agent-prompts.ts +5 -5
  394. package/src/locales/nl/agent-providers.ts +33 -32
  395. package/src/locales/nl/agent-usage.ts +25 -20
  396. package/src/providers/CliDetail.tsx +2 -1
  397. package/src/providers/InstallPage.tsx +2 -4
  398. package/src/providers/ProviderList.tsx +12 -2
  399. package/src/providers/ProvidersPane.tsx +45 -15
  400. package/src/providers/pane-target.ts +20 -4
  401. package/src/state/chats.ts +2 -2
  402. package/src/usage/LimitsList.tsx +12 -13
  403. package/src/usage/UsageBreakdown.tsx +17 -6
  404. package/src/usage/UsageLimits.tsx +2 -1
  405. package/src/usage/UsagePage.tsx +3 -3
  406. package/src/usage/index.ts +1 -1
  407. package/src/usage/limit-groups.ts +12 -0
  408. package/src/usage/limits.ts +2 -2
  409. package/src/usage/money.ts +1 -2
@@ -1,4 +1,4 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { c as _c } from "react/compiler-runtime";
2
2
  import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { useVirtualizer } from '@tanstack/react-virtual';
@@ -38,509 +38,1021 @@ import { FileLinkContext } from './file-links.js';
38
38
  import { AgentIcon } from '../../agents/AgentIcon.js';
39
39
  import { useModelName } from '../../agents/model-name.js';
40
40
  import { ContextMenu, EmptyState, ErrorBoundary, Icon, SectionLabel } from '@adecore/ui';
41
+ import { useHostContextSources, useHostHidesFolder, useHostThreadCards, useHostTimelineFind, useHostWelcome } from '../../host-hooks.js';
42
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
41
43
  const ESTIMATED_ROW_PX = 56;
42
44
  // How long a jump waits for a thread that is still loading before it calls the item missing.
43
45
  const JUMP_THREAD_WAIT_MS = 8000;
44
46
  /* A jump puts its row at the top and stops following the end, so the reader stays where they landed. */
45
47
  function showRowAtTop(virtualizer, followRef, index) {
46
- followRef.current = false;
47
- virtualizer.scrollToIndex(index, { align: 'start' });
48
+ followRef.current = false;
49
+ virtualizer.scrollToIndex(index, {
50
+ align: 'start'
51
+ });
48
52
  }
49
53
  /*
50
54
  * A thread before its first message: which model answers, what the lines into this node let it read,
51
55
  * and the folder it works in, so a first question can lean on all three.
52
56
  */
53
- function EmptyThread({ chatId }) {
54
- const host = useChatHost();
55
- const { t } = useTranslation('agent-chat');
56
- const info = useChatRow(chatId, (row) => row?.info ?? null);
57
- const model = useModelName(info?.provider, info?.selection.model ?? '');
58
- const sources = host.useContextSources(chatId);
59
- const hidesFolder = host.useHidesFolder(chatId);
60
- return (_jsx("div", { className: "flex grow items-center justify-center", children: _jsxs("div", { className: "flex max-w-sm flex-col items-center gap-3 px-6 py-8 text-center", children: [_jsx(EmptyState, { icon: info ? _jsx(AgentIcon, { kind: info.provider }) : undefined, className: "p-0", children: info ? t('timeline.empty.ready', { model }) : t('timeline.empty.ask') }), sources.length > 0 && (_jsxs("div", { className: "flex max-w-full flex-col items-center gap-1", children: [_jsx(SectionLabel, { children: t('timeline.empty.canRead') }), _jsx("span", { className: "max-w-full text-xs text-text-muted", children: sources.map((source) => source.title).join(', ') })] })), info?.cwd && !hidesFolder && (_jsxs("div", { className: "flex max-w-full flex-col items-center gap-1", children: [_jsx(SectionLabel, { children: t('timeline.empty.worksIn') }), _jsx("span", { className: "max-w-full font-mono text-xs break-all text-text-muted", children: info.cwd })] }))] }) }));
57
+ function EmptyThread(t0) {
58
+ const $ = _c(20);
59
+ const {
60
+ chatId
61
+ } = t0;
62
+ const {
63
+ t
64
+ } = useTranslation("agent-chat");
65
+ const info = useChatRow(chatId, _temp);
66
+ const model = useModelName(info?.provider, info?.selection.model ?? "");
67
+ const sources = useHostContextSources(chatId);
68
+ const hidesFolder = useHostHidesFolder(chatId);
69
+ let t1;
70
+ if ($[0] !== info) {
71
+ t1 = info ? /*#__PURE__*/_jsx(AgentIcon, {
72
+ kind: info.provider
73
+ }) : undefined;
74
+ $[0] = info;
75
+ $[1] = t1;
76
+ } else {
77
+ t1 = $[1];
78
+ }
79
+ let t2;
80
+ if ($[2] !== info || $[3] !== model || $[4] !== t) {
81
+ t2 = info ? t("timeline.empty.ready", {
82
+ model
83
+ }) : t("timeline.empty.ask");
84
+ $[2] = info;
85
+ $[3] = model;
86
+ $[4] = t;
87
+ $[5] = t2;
88
+ } else {
89
+ t2 = $[5];
90
+ }
91
+ let t3;
92
+ if ($[6] !== t1 || $[7] !== t2) {
93
+ t3 = /*#__PURE__*/_jsx(EmptyState, {
94
+ icon: t1,
95
+ className: "p-0",
96
+ children: t2
97
+ });
98
+ $[6] = t1;
99
+ $[7] = t2;
100
+ $[8] = t3;
101
+ } else {
102
+ t3 = $[8];
103
+ }
104
+ let t4;
105
+ if ($[9] !== sources || $[10] !== t) {
106
+ t4 = sources.length > 0 && /*#__PURE__*/_jsxs("div", {
107
+ className: "flex max-w-full flex-col items-center gap-1",
108
+ children: [/*#__PURE__*/_jsx(SectionLabel, {
109
+ children: t("timeline.empty.canRead")
110
+ }), /*#__PURE__*/_jsx("span", {
111
+ className: "max-w-full text-xs text-text-muted",
112
+ children: sources.map(_temp2).join(", ")
113
+ })]
114
+ });
115
+ $[9] = sources;
116
+ $[10] = t;
117
+ $[11] = t4;
118
+ } else {
119
+ t4 = $[11];
120
+ }
121
+ let t5;
122
+ if ($[12] !== hidesFolder || $[13] !== info || $[14] !== t) {
123
+ t5 = info?.cwd && !hidesFolder && /*#__PURE__*/_jsxs("div", {
124
+ className: "flex max-w-full flex-col items-center gap-1",
125
+ children: [/*#__PURE__*/_jsx(SectionLabel, {
126
+ children: t("timeline.empty.worksIn")
127
+ }), /*#__PURE__*/_jsx("span", {
128
+ className: "max-w-full font-mono text-xs break-all text-text-muted",
129
+ children: info.cwd
130
+ })]
131
+ });
132
+ $[12] = hidesFolder;
133
+ $[13] = info;
134
+ $[14] = t;
135
+ $[15] = t5;
136
+ } else {
137
+ t5 = $[15];
138
+ }
139
+ let t6;
140
+ if ($[16] !== t3 || $[17] !== t4 || $[18] !== t5) {
141
+ t6 = /*#__PURE__*/_jsx("div", {
142
+ className: "flex grow items-center justify-center",
143
+ children: /*#__PURE__*/_jsxs("div", {
144
+ className: "flex max-w-sm flex-col items-center gap-3 px-6 py-8 text-center",
145
+ children: [t3, t4, t5]
146
+ })
147
+ });
148
+ $[16] = t3;
149
+ $[17] = t4;
150
+ $[18] = t5;
151
+ $[19] = t6;
152
+ } else {
153
+ t6 = $[19];
154
+ }
155
+ return t6;
61
156
  }
62
157
  /*
63
158
  * A row of the thread behind the chapter line of its bookmark. A message, the only row that takes a
64
159
  * bookmark, gets its actions under it; every other row carries a bookmark only for a reply it folds.
65
160
  */
66
- function MarkableRow({ row, chatId, bookmark, children }) {
67
- if (row.kind !== 'user' && row.kind !== 'assistant') {
68
- return (_jsxs(_Fragment, { children: [bookmark !== null && _jsx(BookmarkMarker, { chatId: chatId, itemId: bookmark.itemId, bookmark: bookmark }), children] }));
161
+ function _temp2(source) {
162
+ return source.title;
163
+ }
164
+ function _temp(row) {
165
+ return row?.info ?? null;
166
+ }
167
+ function MarkableRow(t0) {
168
+ const $ = _c(19);
169
+ const {
170
+ row,
171
+ chatId,
172
+ bookmark,
173
+ children
174
+ } = t0;
175
+ if (row.kind !== "user" && row.kind !== "assistant") {
176
+ let t1;
177
+ if ($[0] !== bookmark || $[1] !== chatId) {
178
+ t1 = bookmark !== null && /*#__PURE__*/_jsx(BookmarkMarker, {
179
+ chatId: chatId,
180
+ itemId: bookmark.itemId,
181
+ bookmark: bookmark
182
+ });
183
+ $[0] = bookmark;
184
+ $[1] = chatId;
185
+ $[2] = t1;
186
+ } else {
187
+ t1 = $[2];
188
+ }
189
+ let t2;
190
+ if ($[3] !== children || $[4] !== t1) {
191
+ t2 = /*#__PURE__*/_jsxs(_Fragment, {
192
+ children: [t1, children]
193
+ });
194
+ $[3] = children;
195
+ $[4] = t1;
196
+ $[5] = t2;
197
+ } else {
198
+ t2 = $[5];
69
199
  }
70
- return (_jsxs(_Fragment, { children: [_jsx(BookmarkMarker, { chatId: chatId, itemId: row.id, bookmark: bookmark }), _jsxs("div", { className: "group/message", children: [children, _jsx(MessageActions, { chatId: chatId, row: row })] })] }));
200
+ return t2;
201
+ }
202
+ let t1;
203
+ if ($[6] !== bookmark || $[7] !== chatId || $[8] !== row.id) {
204
+ t1 = /*#__PURE__*/_jsx(BookmarkMarker, {
205
+ chatId: chatId,
206
+ itemId: row.id,
207
+ bookmark: bookmark
208
+ });
209
+ $[6] = bookmark;
210
+ $[7] = chatId;
211
+ $[8] = row.id;
212
+ $[9] = t1;
213
+ } else {
214
+ t1 = $[9];
215
+ }
216
+ let t2;
217
+ if ($[10] !== chatId || $[11] !== row) {
218
+ t2 = /*#__PURE__*/_jsx(MessageActions, {
219
+ chatId: chatId,
220
+ row: row
221
+ });
222
+ $[10] = chatId;
223
+ $[11] = row;
224
+ $[12] = t2;
225
+ } else {
226
+ t2 = $[12];
227
+ }
228
+ let t3;
229
+ if ($[13] !== children || $[14] !== t2) {
230
+ t3 = /*#__PURE__*/_jsxs("div", {
231
+ className: "group/message",
232
+ children: [children, t2]
233
+ });
234
+ $[13] = children;
235
+ $[14] = t2;
236
+ $[15] = t3;
237
+ } else {
238
+ t3 = $[15];
239
+ }
240
+ let t4;
241
+ if ($[16] !== t1 || $[17] !== t3) {
242
+ t4 = /*#__PURE__*/_jsxs(_Fragment, {
243
+ children: [t1, t3]
244
+ });
245
+ $[16] = t1;
246
+ $[17] = t3;
247
+ $[18] = t4;
248
+ } else {
249
+ t4 = $[18];
250
+ }
251
+ return t4;
71
252
  }
72
253
  /* Memoized with stable props, so a streamed word or a scroll draws only the rows it changed. */
73
- const ThreadRow = memo(function ThreadRow({ row, previous, index, top, measure, chatId, flashNonce, bookmark, ...actions }) {
74
- const header = replyHeader(row, previous);
75
- return (_jsxs(RowContainer, { row: row, previous: previous, index: index, top: top, ref: measure, children: [flashNonce !== null && _jsx("span", { className: "chat-flash pointer-events-none absolute inset-0 rounded-lg", "aria-hidden": true }, flashNonce), header !== null && (_jsx("div", { className: "chat-column-content w-full", children: _jsx(ReplyHeader, { chatId: chatId, at: header.at }) })), _jsx(MarkableRow, { row: row, chatId: chatId, bookmark: bookmark, children: _jsx(Row, { row: row, chatId: chatId, ...actions }) })] }));
254
+ const ThreadRow = memo(function ThreadRow(t0) {
255
+ const $ = _c(36);
256
+ let actions;
257
+ let bookmark;
258
+ let chatId;
259
+ let flashNonce;
260
+ let index;
261
+ let measure;
262
+ let previous;
263
+ let row;
264
+ let top;
265
+ if ($[0] !== t0) {
266
+ ({
267
+ row,
268
+ previous,
269
+ index,
270
+ top,
271
+ measure,
272
+ chatId,
273
+ flashNonce,
274
+ bookmark,
275
+ ...actions
276
+ } = t0);
277
+ $[0] = t0;
278
+ $[1] = actions;
279
+ $[2] = bookmark;
280
+ $[3] = chatId;
281
+ $[4] = flashNonce;
282
+ $[5] = index;
283
+ $[6] = measure;
284
+ $[7] = previous;
285
+ $[8] = row;
286
+ $[9] = top;
287
+ } else {
288
+ actions = $[1];
289
+ bookmark = $[2];
290
+ chatId = $[3];
291
+ flashNonce = $[4];
292
+ index = $[5];
293
+ measure = $[6];
294
+ previous = $[7];
295
+ row = $[8];
296
+ top = $[9];
297
+ }
298
+ let t1;
299
+ if ($[10] !== previous || $[11] !== row) {
300
+ t1 = replyHeader(row, previous);
301
+ $[10] = previous;
302
+ $[11] = row;
303
+ $[12] = t1;
304
+ } else {
305
+ t1 = $[12];
306
+ }
307
+ const header = t1;
308
+ let t2;
309
+ if ($[13] !== flashNonce) {
310
+ t2 = flashNonce !== null && /*#__PURE__*/_jsx("span", {
311
+ className: "chat-flash pointer-events-none absolute inset-0 rounded-lg",
312
+ "aria-hidden": true
313
+ }, flashNonce);
314
+ $[13] = flashNonce;
315
+ $[14] = t2;
316
+ } else {
317
+ t2 = $[14];
318
+ }
319
+ let t3;
320
+ if ($[15] !== chatId || $[16] !== header) {
321
+ t3 = header !== null && /*#__PURE__*/_jsx("div", {
322
+ className: "chat-column-content w-full",
323
+ children: /*#__PURE__*/_jsx(ReplyHeader, {
324
+ chatId: chatId,
325
+ at: header.at
326
+ })
327
+ });
328
+ $[15] = chatId;
329
+ $[16] = header;
330
+ $[17] = t3;
331
+ } else {
332
+ t3 = $[17];
333
+ }
334
+ let t4;
335
+ if ($[18] !== actions || $[19] !== chatId || $[20] !== row) {
336
+ t4 = /*#__PURE__*/_jsx(Row, {
337
+ row: row,
338
+ chatId: chatId,
339
+ ...actions
340
+ });
341
+ $[18] = actions;
342
+ $[19] = chatId;
343
+ $[20] = row;
344
+ $[21] = t4;
345
+ } else {
346
+ t4 = $[21];
347
+ }
348
+ let t5;
349
+ if ($[22] !== bookmark || $[23] !== chatId || $[24] !== row || $[25] !== t4) {
350
+ t5 = /*#__PURE__*/_jsx(MarkableRow, {
351
+ row: row,
352
+ chatId: chatId,
353
+ bookmark: bookmark,
354
+ children: t4
355
+ });
356
+ $[22] = bookmark;
357
+ $[23] = chatId;
358
+ $[24] = row;
359
+ $[25] = t4;
360
+ $[26] = t5;
361
+ } else {
362
+ t5 = $[26];
363
+ }
364
+ let t6;
365
+ if ($[27] !== index || $[28] !== measure || $[29] !== previous || $[30] !== row || $[31] !== t2 || $[32] !== t3 || $[33] !== t5 || $[34] !== top) {
366
+ t6 = /*#__PURE__*/_jsxs(RowContainer, {
367
+ row: row,
368
+ previous: previous,
369
+ index: index,
370
+ top: top,
371
+ ref: measure,
372
+ children: [t2, t3, t5]
373
+ });
374
+ $[27] = index;
375
+ $[28] = measure;
376
+ $[29] = previous;
377
+ $[30] = row;
378
+ $[31] = t2;
379
+ $[32] = t3;
380
+ $[33] = t5;
381
+ $[34] = top;
382
+ $[35] = t6;
383
+ } else {
384
+ t6 = $[35];
385
+ }
386
+ return t6;
76
387
  });
77
388
  /*
78
389
  * `overlay` stands in the thread's place and ends where the composer starts, so the thread keeps
79
390
  * where it was underneath and the composer stays in reach for the prompts it answers.
80
391
  */
81
- export function Timeline({ chatId, composer, overlay }) {
82
- const host = useChatHost();
83
- const { fork } = host;
84
- const { t } = useTranslation('agent-chat');
85
- const order = useChatRow(chatId, (row) => row?.order);
86
- // The structure, not the items. A delta growing a reply must not derive every row again.
87
- const items = useChatRow(chatId, (row) => row?.structure);
88
- const activeTurnId = useChatRow(chatId, (row) => row?.info.activeTurnId ?? null);
89
- const bookmarks = useChatRow(chatId, (row) => row?.bookmarks);
90
- const visuals = useChatRow(chatId, (row) => row?.visuals);
91
- const forkedTurns = useForkedTurns(chatId);
92
- const info = useChatRow(chatId, (row) => row?.info ?? null);
93
- const cursor = useChatRow(chatId, (row) => row?.history?.cursor ?? null);
94
- const scope = useChatScope();
95
- const { id: scopeId, keyOf } = scope;
96
- // Read when the menu opens rather than subscribed to, for the same reason the rows use the structure.
97
- const fullItems = () => useChats.getState().byKey[keyOf(chatId)]?.items;
98
- const scrollRef = useRef(null);
99
- const threadRef = useRef(null);
100
- const contentRef = useRef(null);
101
- const quoteTakerRef = useRef(null);
102
- const registerQuoteTaker = useCallback((take) => {
103
- quoteTakerRef.current = take;
104
- return () => {
105
- if (quoteTakerRef.current === take) {
106
- quoteTakerRef.current = null;
107
- }
108
- };
109
- }, []);
110
- const followRef = useRef(true);
111
- const stopFollowing = useCallback(() => {
112
- followRef.current = false;
113
- }, []);
114
- const groups = useToggleSet(stopFollowing);
115
- const turns = useToggleSet(stopFollowing);
116
- const subagents = useToggleSet(stopFollowing);
117
- const scrollGeometry = useRef({ top: 0, height: 0, viewport: 0 });
118
- const [target, setTarget] = useState(EMPTY_TARGET);
119
- const { trail, show } = useSubagentTrail(chatId);
120
- const frameRef = useRef(null);
121
- const composerRef = useRef(null);
122
- const [composerHeight, setComposerHeight] = useState(0);
123
- const [frame, setFrame] = useState({ width: 0, height: 0, column: false });
124
- const [loadingEarlier, setLoadingEarlier] = useState(false);
125
- const loadingEarlierRef = useRef(false);
126
- // The row being read and where it stood, taken after every render, so a page going in above puts it back.
127
- const anchorRef = useRef(null);
128
- const headRef = useRef(undefined);
129
- const loadEarlier = useCallback(() => {
130
- if (cursor === null || loadingEarlierRef.current) {
131
- return;
132
- }
133
- loadingEarlierRef.current = true;
134
- setLoadingEarlier(true);
135
- // A page that failed is asked for again on the next scroll; the thread keeps what it has.
136
- void scope.chats
137
- .loadEarlier(chatId, cursor)
138
- .catch(() => undefined)
139
- .finally(() => {
140
- loadingEarlierRef.current = false;
141
- setLoadingEarlier(false);
142
- });
143
- }, [scope, chatId, cursor]);
144
- const threadRows = useMemo(() => {
145
- if (!order || !items) {
146
- return [];
147
- }
148
- return deriveTimelineRows(order.map((id) => items[id]), {
149
- expandedGroups: groups.ids,
150
- expandedTurns: turns.ids,
151
- expandedSubagents: subagents.ids,
152
- activeTurnId,
153
- forkedTurns,
154
- commandLooks: host.commandLooks
155
- });
156
- }, [order, items, groups.ids, turns.ids, subagents.ids, activeTurnId, forkedTurns, host.commandLooks]);
157
- const cards = host.useThreadCards(scopeId, chatId);
158
- const drawsVisuals = host.visuals !== null;
159
- // Where the part of the thread this client holds begins, while there is a page before it.
160
- const heldFrom = cursor === null ? null : (items?.[order?.[0] ?? '']?.createdAt ?? null);
161
- const rows = useMemo(() => withTimedRows(threadRows, timedRowsOf(cards, drawsVisuals ? (visuals ?? []) : null, heldFrom)), [threadRows, cards, drawsVisuals, visuals, heldFrom]);
162
- const welcomes = host.useWelcome(chatId);
163
- const empty = rows.length === 0;
164
- // A chat that took a turn before is one whose thread has not come in yet, not one to greet.
165
- const welcome = welcomes && empty && (info === null || (info.usage.turns === 0 && info.activeTurnId === null));
166
- const marks = useMemo(() => bookmarkRows(rows, bookmarks ?? [], items ?? {}), [rows, bookmarks, items]);
167
- const ticks = useMemo(() => ticksOf(rows, marks), [rows, marks]);
168
- // A message a jump is on its way to; it may first have to open the turn its reply folded into.
169
- const [seeking, setSeeking] = useState(null);
170
- const [landing, setLanding] = useState(null);
171
- // A turn a jump is on its way to, which waits for the page its messages are on to come in.
172
- const [revealing, setRevealing] = useState(null);
173
- const flash = useTimelineFlash((s) => s.target);
174
- const revealBlock = useCallback((block) => {
175
- setLanding(null);
176
- setSeeking({ itemId: block.itemId, block, key: keyOf(chatId) });
177
- }, [keyOf, chatId]);
178
- const uiNavigation = useMemo(() => ({
179
- chatId,
180
- reveal: revealBlock,
181
- flash: flash?.key === keyOf(chatId) && flash.block ? { itemId: flash.rowId, ...flash.block, nonce: flash.nonce } : null
182
- }), [revealBlock, flash, keyOf, chatId]);
183
- // Whether the thread's text clears the strip depends on the width of the view, which only the thread's own frame tells.
184
- useEffect(() => {
185
- const element = frameRef.current;
186
- if (element === null || typeof ResizeObserver === 'undefined') {
187
- return;
188
- }
189
- const column = element.closest('.chat-column') !== null;
190
- const measure = () => setFrame((current) => current.width === element.offsetWidth && current.height === element.offsetHeight && current.column === column
191
- ? current
192
- : { width: element.offsetWidth, height: element.offsetHeight, column });
193
- const observer = new ResizeObserver(measure);
194
- observer.observe(element);
195
- measure();
196
- return () => observer.disconnect();
197
- }, []);
198
- useLayoutEffect(() => {
199
- const element = composerRef.current;
200
- if (element === null) {
201
- return;
202
- }
203
- const measure = () => setComposerHeight(element.offsetHeight);
204
- const observer = new ResizeObserver(measure);
205
- observer.observe(element);
206
- measure();
207
- return () => observer.disconnect();
208
- }, []);
209
- // Oversized prompts scroll in full instead of sticking with their top outside the viewport.
210
- const stickyComposer = !welcome && composerHeight < frame.height - FOLLOW_THRESHOLD_PX;
211
- const coveredHeight = stickyComposer ? composerHeight : 0;
212
- const coveredHeightRef = useRef(coveredHeight);
213
- coveredHeightRef.current = coveredHeight;
214
- // The composer pages through the thread with PageUp and PageDown; this is the element it moves.
215
- useEffect(() => registerTimeline(chatId, scrollRef.current, (enabled) => {
216
- followRef.current = enabled;
217
- }, () => coveredHeightRef.current), [chatId]);
218
- // Nothing here builds with the React Compiler, so its memoization rule has nothing to break.
219
- // oxlint-disable-next-line react/incompatible-library
220
- const virtualizer = useVirtualizer({
221
- count: rows.length,
222
- getScrollElement: () => scrollRef.current,
223
- estimateSize: () => ESTIMATED_ROW_PX,
224
- getItemKey: (index) => rows[index].id,
225
- overscan: 8,
226
- scrollPaddingEnd: coveredHeight
392
+ export function Timeline({
393
+ chatId,
394
+ composer,
395
+ overlay
396
+ }) {
397
+ const host = useChatHost();
398
+ const {
399
+ fork
400
+ } = host;
401
+ const {
402
+ t
403
+ } = useTranslation('agent-chat');
404
+ const order = useChatRow(chatId, row => row?.order);
405
+ // The structure, not the items. A delta growing a reply must not derive every row again.
406
+ const items = useChatRow(chatId, row_0 => row_0?.structure);
407
+ const activeTurnId = useChatRow(chatId, row_1 => row_1?.info.activeTurnId ?? null);
408
+ const bookmarks = useChatRow(chatId, row_2 => row_2?.bookmarks);
409
+ const visuals = useChatRow(chatId, row_3 => row_3?.visuals);
410
+ const forkedTurns = useForkedTurns(chatId);
411
+ const info = useChatRow(chatId, row_4 => row_4?.info ?? null);
412
+ const cursor = useChatRow(chatId, row_5 => row_5?.history?.cursor ?? null);
413
+ const scope = useChatScope();
414
+ const {
415
+ id: scopeId,
416
+ keyOf
417
+ } = scope;
418
+ // Read when the menu opens rather than subscribed to, for the same reason the rows use the structure.
419
+ const fullItems = () => useChats.getState().byKey[keyOf(chatId)]?.items;
420
+ const scrollRef = useRef(null);
421
+ const threadRef = useRef(null);
422
+ const contentRef = useRef(null);
423
+ const quoteTakerRef = useRef(null);
424
+ const registerQuoteTaker = useCallback(take => {
425
+ quoteTakerRef.current = take;
426
+ return () => {
427
+ if (quoteTakerRef.current === take) {
428
+ quoteTakerRef.current = null;
429
+ }
430
+ };
431
+ }, []);
432
+ const followRef = useRef(true);
433
+ const stopFollowing = useCallback(() => {
434
+ followRef.current = false;
435
+ }, []);
436
+ const groups = useToggleSet(stopFollowing);
437
+ const turns = useToggleSet(stopFollowing);
438
+ const subagents = useToggleSet(stopFollowing);
439
+ const scrollGeometry = useRef({
440
+ top: 0,
441
+ height: 0,
442
+ viewport: 0
443
+ });
444
+ const [target, setTarget] = useState(EMPTY_TARGET);
445
+ const {
446
+ trail,
447
+ show
448
+ } = useSubagentTrail(chatId);
449
+ const frameRef = useRef(null);
450
+ const composerRef = useRef(null);
451
+ const [composerHeight, setComposerHeight] = useState(0);
452
+ const [frame, setFrame] = useState({
453
+ width: 0,
454
+ height: 0,
455
+ column: false
456
+ });
457
+ const [loadingEarlier, setLoadingEarlier] = useState(false);
458
+ const loadingEarlierRef = useRef(false);
459
+ // The row being read and where it stood, taken after every render, so a page going in above puts it back.
460
+ const anchorRef = useRef(null);
461
+ const headRef = useRef(undefined);
462
+ const loadEarlier = useCallback(() => {
463
+ if (cursor === null || loadingEarlierRef.current) {
464
+ return;
465
+ }
466
+ loadingEarlierRef.current = true;
467
+ setLoadingEarlier(true);
468
+ // A page that failed is asked for again on the next scroll; the thread keeps what it has.
469
+ void scope.chats.loadEarlier(chatId, cursor).catch(() => undefined).finally(() => {
470
+ loadingEarlierRef.current = false;
471
+ setLoadingEarlier(false);
227
472
  });
228
- /*
229
- * Rows are measured as they render, so the end moves while it is drawn (a streamed line, a diff that
230
- * highlights, an image that loads). `scrollToIndex` aims at where a row was when asked and fell
231
- * short; the scroller's own bottom is a fact, and a new total size says a row was measured again.
232
- * A word that does not add a line moves nothing, so the thread does not listen to the words.
233
- */
234
- const totalSize = virtualizer.getTotalSize();
235
- useLayoutEffect(() => {
236
- const element = scrollRef.current;
237
- if (!followRef.current || element === null) {
238
- return;
239
- }
240
- // The composer is in the scroll flow, so its measured height is part of this end position.
241
- element.scrollTop = element.scrollHeight;
242
- scrollGeometry.current = { top: element.scrollTop, height: element.scrollHeight, viewport: element.clientHeight };
243
- }, [rows.length, totalSize, composerHeight, frame.height]);
244
- // A page that went in above moves every row down by what it measures; the reader stays on the row they were on.
245
- useLayoutEffect(() => {
246
- const element = scrollRef.current;
247
- const head = order?.[0];
248
- const moved = head !== headRef.current;
249
- headRef.current = head;
250
- if (element === null) {
251
- return;
252
- }
253
- const anchor = anchorRef.current;
254
- const index = moved && anchor !== null && !followRef.current ? rows.findIndex((row) => row.id === anchor.id) : -1;
255
- if (anchor !== null && index !== -1) {
256
- // The total measures again what this commit's rows reported; the cache alone lags behind them.
257
- virtualizer.getTotalSize();
258
- element.scrollTop = virtualizer.measurementsCache[index].start - anchor.offset;
259
- return;
260
- }
261
- const row = virtualizer.getVirtualItemForOffset(element.scrollTop);
262
- anchorRef.current = row === undefined ? null : { id: String(row.key), offset: row.start - element.scrollTop };
473
+ }, [scope, chatId, cursor]);
474
+ const threadRows = useMemo(() => {
475
+ if (!order || !items) {
476
+ return [];
477
+ }
478
+ return deriveTimelineRows(order.map(id => items[id]), {
479
+ expandedGroups: groups.ids,
480
+ expandedTurns: turns.ids,
481
+ expandedSubagents: subagents.ids,
482
+ activeTurnId,
483
+ forkedTurns,
484
+ commandLooks: host.commandLooks
263
485
  });
264
- // Close to the top, the page before goes in; again after each page that left too little above the reader.
265
- useEffect(() => {
266
- const element = scrollRef.current;
267
- if (element !== null && wantsEarlier(element)) {
268
- loadEarlier();
269
- }
270
- }, [rows, loadEarlier, frame.height]);
271
- // A sub-agent in the thread's place hides the thread, and the strip and the keyboard go with it.
272
- const onMainAgent = trail.length === 0;
273
- // A node on a canvas has no strip. It is too narrow to give up a column of its width.
274
- const showsScrubber = frame.column && onMainAgent && ticks.length >= SCRUBBER_MIN_TICKS;
275
- const readingWidth = useChatPreferences((preferences) => preferences.readingWidth);
276
- const paddingLeft = threadPaddingLeft(frame.width, showsScrubber, readingMeasure(readingWidth));
277
- /*
278
- * Where each message starts, from the virtualizer's measurements (an estimate for a row it never
279
- * drew), so reading the scroll position costs no layout.
280
- */
281
- const measurements = virtualizer.measurementsCache;
282
- const starts = ticks.map((tick) => measurements[tick.rowIndex]?.start ?? tick.rowIndex * ESTIMATED_ROW_PX);
283
- const scrollTop = virtualizer.scrollOffset ?? 0;
284
- const viewport = virtualizer.scrollRect?.height ?? 0;
285
- const visibleHeight = Math.max(0, viewport - coveredHeight);
286
- const visibleHeightRef = useRef(visibleHeight);
287
- visibleHeightRef.current = visibleHeight;
288
- const inView = showsScrubber ? messagesInView({ starts, scrollTop, visibleHeight }) : null;
289
- /*
290
- * The virtualizer draws again only when its range of rows changes, which a scroll inside one long
291
- * row never does; the scroll itself redraws the strip once the messages in view are others.
292
- */
293
- const [, redrawStrip] = useReducer((count) => count + 1, 0);
294
- const stripRef = useRef({ starts, inView });
295
- stripRef.current = { starts, inView };
296
- const followStrip = (element) => {
297
- const { starts: current, inView: shown } = stripRef.current;
298
- if (shown === null) {
299
- return;
300
- }
301
- const next = messagesInView({ starts: current, scrollTop: element.scrollTop, visibleHeight: visibleHeightRef.current });
302
- if (next?.first !== shown.first || next?.last !== shown.last) {
303
- redrawStrip();
304
- }
486
+ }, [order, items, groups.ids, turns.ids, subagents.ids, activeTurnId, forkedTurns, host.commandLooks]);
487
+ const cards = useHostThreadCards(scopeId, chatId);
488
+ const drawsVisuals = host.visuals !== null;
489
+ // Where the part of the thread this client holds begins, while there is a page before it.
490
+ const heldFrom = cursor === null ? null : items?.[order?.[0] ?? '']?.createdAt ?? null;
491
+ const rows = useMemo(() => withTimedRows(threadRows, timedRowsOf(cards, drawsVisuals ? visuals ?? [] : null, heldFrom)), [threadRows, cards, drawsVisuals, visuals, heldFrom]);
492
+ const welcomes = useHostWelcome(chatId);
493
+ const empty = rows.length === 0;
494
+ // A chat that took a turn before is one whose thread has not come in yet, not one to greet.
495
+ const welcome = welcomes && empty && (info === null || info.usage.turns === 0 && info.activeTurnId === null);
496
+ const marks = useMemo(() => bookmarkRows(rows, bookmarks ?? [], items ?? {}), [rows, bookmarks, items]);
497
+ const ticks = useMemo(() => ticksOf(rows, marks), [rows, marks]);
498
+ // A message a jump is on its way to; it may first have to open the turn its reply folded into.
499
+ const [seeking, setSeeking] = useState(null);
500
+ const [landing, setLanding] = useState(null);
501
+ // A turn a jump is on its way to, which waits for the page its messages are on to come in.
502
+ const [revealing, setRevealing] = useState(null);
503
+ const flash = useTimelineFlash(s => s.target);
504
+ const revealBlock = useCallback(block => {
505
+ setLanding(null);
506
+ setSeeking({
507
+ itemId: block.itemId,
508
+ block,
509
+ key: keyOf(chatId)
510
+ });
511
+ }, [keyOf, chatId]);
512
+ const uiNavigation = useMemo(() => ({
513
+ chatId,
514
+ reveal: revealBlock,
515
+ flash: flash?.key === keyOf(chatId) && flash.block ? {
516
+ itemId: flash.rowId,
517
+ ...flash.block,
518
+ nonce: flash.nonce
519
+ } : null
520
+ }), [revealBlock, flash, keyOf, chatId]);
521
+ // Whether the thread's text clears the strip depends on the width of the view, which only the thread's own frame tells.
522
+ useEffect(() => {
523
+ const element = frameRef.current;
524
+ if (element === null || typeof ResizeObserver === 'undefined') {
525
+ return;
526
+ }
527
+ const column = element.closest('.chat-column') !== null;
528
+ const measure = () => setFrame(current => current.width === element.offsetWidth && current.height === element.offsetHeight && current.column === column ? current : {
529
+ width: element.offsetWidth,
530
+ height: element.offsetHeight,
531
+ column
532
+ });
533
+ const observer = new ResizeObserver(measure);
534
+ observer.observe(element);
535
+ measure();
536
+ return () => observer.disconnect();
537
+ }, []);
538
+ useLayoutEffect(() => {
539
+ const element_0 = composerRef.current;
540
+ if (element_0 === null) {
541
+ return;
542
+ }
543
+ const measure_0 = () => setComposerHeight(element_0.offsetHeight);
544
+ const observer_0 = new ResizeObserver(measure_0);
545
+ observer_0.observe(element_0);
546
+ measure_0();
547
+ return () => observer_0.disconnect();
548
+ }, []);
549
+ // Oversized prompts scroll in full instead of sticking with their top outside the viewport.
550
+ const stickyComposer = !welcome && composerHeight < frame.height - FOLLOW_THRESHOLD_PX;
551
+ const coveredHeight = stickyComposer ? composerHeight : 0;
552
+ const coveredHeightRef = useRef(coveredHeight);
553
+ coveredHeightRef.current = coveredHeight;
554
+ // The composer pages through the thread with PageUp and PageDown; this is the element it moves.
555
+ useEffect(() => registerTimeline(chatId, scrollRef.current, enabled => {
556
+ followRef.current = enabled;
557
+ }, () => coveredHeightRef.current), [chatId]);
558
+ // Nothing here builds with the React Compiler, so its memoization rule has nothing to break.
559
+ // oxlint-disable-next-line react/incompatible-library
560
+ const virtualizer = useVirtualizer({
561
+ count: rows.length,
562
+ getScrollElement: () => scrollRef.current,
563
+ estimateSize: () => ESTIMATED_ROW_PX,
564
+ getItemKey: index => rows[index].id,
565
+ overscan: 8,
566
+ scrollPaddingEnd: coveredHeight
567
+ });
568
+ /*
569
+ * Rows are measured as they render, so the end moves while it is drawn (a streamed line, a diff that
570
+ * highlights, an image that loads). `scrollToIndex` aims at where a row was when asked and fell
571
+ * short; the scroller's own bottom is a fact, and a new total size says a row was measured again.
572
+ * A word that does not add a line moves nothing, so the thread does not listen to the words.
573
+ */
574
+ const totalSize = virtualizer.getTotalSize();
575
+ useLayoutEffect(() => {
576
+ const element_1 = scrollRef.current;
577
+ if (!followRef.current || element_1 === null) {
578
+ return;
579
+ }
580
+ // The composer is in the scroll flow, so its measured height is part of this end position.
581
+ element_1.scrollTop = element_1.scrollHeight;
582
+ scrollGeometry.current = {
583
+ top: element_1.scrollTop,
584
+ height: element_1.scrollHeight,
585
+ viewport: element_1.clientHeight
305
586
  };
306
- // One object while what it holds stays, so a find that keys an effect on it does not run it for every scroll.
307
- const findOptions = useMemo(() => ({
308
- chatId,
309
- frame: frameRef,
310
- thread: threadRef,
311
- scroller: scrollRef,
312
- enabled: onMainAgent,
313
- rows,
314
- structure: items,
315
- order,
316
- firstRowInView: () => {
317
- const top = scrollRef.current?.scrollTop ?? 0;
318
- return virtualizer.getVirtualItems().find((virtualRow) => virtualRow.end > top)?.index ?? 0;
319
- },
320
- openTurn: turns.add,
321
- openGroup: groups.add,
322
- openSubagent: subagents.add,
323
- // A row already on screen stays put; the find scrolls to the match itself once it is drawn.
324
- scrollToRow: (index) => {
325
- const top = scrollRef.current?.scrollTop ?? 0;
326
- const bottom = top + visibleHeightRef.current;
327
- if (!virtualizer.getVirtualItems().some((virtualRow) => virtualRow.index === index && virtualRow.end > top && virtualRow.start < bottom)) {
328
- virtualizer.scrollToIndex(index, { align: 'center' });
329
- }
330
- },
331
- coveredHeight: () => coveredHeightRef.current,
332
- stopFollowing
333
- }), [chatId, onMainAgent, rows, items, order, virtualizer, turns.add, groups.add, subagents.add, stopFollowing]);
334
- const chatFind = host.useTimelineFind(findOptions);
335
- const searching = chatFind.open;
336
- // A find covers the whole thread, so every page before this one comes in while it is open.
337
- useEffect(() => {
338
- if (searching) {
339
- loadEarlier();
340
- }
341
- }, [searching, loadEarlier]);
342
- const found = useMemo(() => (searching ? ticksWithHits(ticks, chatFind.hitRows) : []), [searching, ticks, chatFind.hitRows]);
343
- const foundCurrent = chatFind.currentRow === null ? null : tickOfRow(ticks, chatFind.currentRow);
344
- const jumpTo = (index) => {
345
- const tick = ticks[index];
346
- if (!tick) {
347
- return;
348
- }
349
- if (tick.bookmark !== null) {
350
- setLanding(null);
351
- setSeeking({ itemId: tick.bookmark.itemId, key: keyOf(chatId) });
352
- return;
353
- }
354
- showRowAtTop(virtualizer, followRef, tick.rowIndex);
587
+ }, [rows.length, totalSize, composerHeight, frame.height]);
588
+ // A page that went in above moves every row down by what it measures; the reader stays on the row they were on.
589
+ useLayoutEffect(() => {
590
+ const element_2 = scrollRef.current;
591
+ const head = order?.[0];
592
+ const moved = head !== headRef.current;
593
+ headRef.current = head;
594
+ if (element_2 === null) {
595
+ return;
596
+ }
597
+ const anchor = anchorRef.current;
598
+ const index_0 = moved && anchor !== null && !followRef.current ? rows.findIndex(row_6 => row_6.id === anchor.id) : -1;
599
+ if (anchor !== null && index_0 !== -1) {
600
+ // The total measures again what this commit's rows reported; the cache alone lags behind them.
601
+ virtualizer.getTotalSize();
602
+ element_2.scrollTop = virtualizer.measurementsCache[index_0].start - anchor.offset;
603
+ return;
604
+ }
605
+ const row_7 = virtualizer.getVirtualItemForOffset(element_2.scrollTop);
606
+ anchorRef.current = row_7 === undefined ? null : {
607
+ id: String(row_7.key),
608
+ offset: row_7.start - element_2.scrollTop
355
609
  };
356
- useLayoutEffect(() => {
357
- if (seeking === null) {
358
- return;
359
- }
360
- if (seeking.key !== keyOf(chatId)) {
361
- setSeeking(null);
362
- return;
363
- }
364
- if (seeking.block) {
365
- const jump = uiReplyJump(seeking.block, rows, items, turns.ids, cursor !== null);
366
- if (jump.kind === 'earlier') {
367
- loadEarlier();
368
- }
369
- else if (jump.kind === 'turn') {
370
- turns.add(jump.turnId);
371
- }
372
- else {
373
- setSeeking(null);
374
- if (jump.kind === 'row') {
375
- showRowAtTop(virtualizer, followRef, jump.index);
376
- setLanding({ target: seeking.block, key: seeking.key });
377
- }
378
- }
379
- return;
380
- }
381
- const index = rows.findIndex((row) => row.id === seeking.itemId);
382
- if (index !== -1) {
383
- setSeeking(null);
384
- showRowAtTop(virtualizer, followRef, index);
385
- if (seeking.options?.flash) {
386
- useTimelineFlash.getState().flash(seeking.key, seeking.itemId, false);
387
- }
388
- return;
389
- }
390
- // The thread has not loaded yet, so nothing says the item is missing; the effect below bounds the wait.
391
- if (items === undefined) {
392
- return;
393
- }
394
- const item = items[seeking.itemId];
395
- if (item === undefined && cursor !== null) {
396
- loadEarlier();
397
- return;
398
- }
399
- const turnId = item?.turnId ?? null;
400
- if (turnId !== null && !turns.ids.has(turnId)) {
401
- turns.add(turnId);
402
- return;
403
- }
610
+ });
611
+ // Close to the top, the page before goes in; again after each page that left too little above the reader.
612
+ useEffect(() => {
613
+ const element_3 = scrollRef.current;
614
+ if (element_3 !== null && wantsEarlier(element_3)) {
615
+ loadEarlier();
616
+ }
617
+ }, [rows, loadEarlier, frame.height]);
618
+ // A sub-agent in the thread's place hides the thread, and the strip and the keyboard go with it.
619
+ const onMainAgent = trail.length === 0;
620
+ // A node on a canvas has no strip. It is too narrow to give up a column of its width.
621
+ const showsScrubber = frame.column && onMainAgent && ticks.length >= SCRUBBER_MIN_TICKS;
622
+ const readingWidth = useChatPreferences(preferences => preferences.readingWidth);
623
+ const paddingLeft = threadPaddingLeft(frame.width, showsScrubber, readingMeasure(readingWidth));
624
+ /*
625
+ * Where each message starts, from the virtualizer's measurements (an estimate for a row it never
626
+ * drew), so reading the scroll position costs no layout.
627
+ */
628
+ const measurements = virtualizer.measurementsCache;
629
+ const starts = ticks.map(tick => measurements[tick.rowIndex]?.start ?? tick.rowIndex * ESTIMATED_ROW_PX);
630
+ const scrollTop = virtualizer.scrollOffset ?? 0;
631
+ const viewport = virtualizer.scrollRect?.height ?? 0;
632
+ const visibleHeight = Math.max(0, viewport - coveredHeight);
633
+ const visibleHeightRef = useRef(visibleHeight);
634
+ visibleHeightRef.current = visibleHeight;
635
+ const inView = showsScrubber ? messagesInView({
636
+ starts,
637
+ scrollTop,
638
+ visibleHeight
639
+ }) : null;
640
+ /*
641
+ * The virtualizer draws again only when its range of rows changes, which a scroll inside one long
642
+ * row never does; the scroll itself redraws the strip once the messages in view are others.
643
+ */
644
+ const [, redrawStrip] = useReducer(count => count + 1, 0);
645
+ const stripRef = useRef({
646
+ starts,
647
+ inView
648
+ });
649
+ stripRef.current = {
650
+ starts,
651
+ inView
652
+ };
653
+ const followStrip = element_4 => {
654
+ const {
655
+ starts: current_0,
656
+ inView: shown
657
+ } = stripRef.current;
658
+ if (shown === null) {
659
+ return;
660
+ }
661
+ const next = messagesInView({
662
+ starts: current_0,
663
+ scrollTop: element_4.scrollTop,
664
+ visibleHeight: visibleHeightRef.current
665
+ });
666
+ if (next?.first !== shown.first || next?.last !== shown.last) {
667
+ redrawStrip();
668
+ }
669
+ };
670
+ // One object while what it holds stays, so a find that keys an effect on it does not run it for every scroll.
671
+ const findOptions = useMemo(() => ({
672
+ chatId,
673
+ frame: frameRef,
674
+ thread: threadRef,
675
+ scroller: scrollRef,
676
+ enabled: onMainAgent,
677
+ rows,
678
+ structure: items,
679
+ order,
680
+ firstRowInView: () => {
681
+ const top = scrollRef.current?.scrollTop ?? 0;
682
+ return virtualizer.getVirtualItems().find(virtualRow => virtualRow.end > top)?.index ?? 0;
683
+ },
684
+ openTurn: turns.add,
685
+ openGroup: groups.add,
686
+ openSubagent: subagents.add,
687
+ // A row already on screen stays put; the find scrolls to the match itself once it is drawn.
688
+ scrollToRow: index_1 => {
689
+ const top_0 = scrollRef.current?.scrollTop ?? 0;
690
+ const bottom = top_0 + visibleHeightRef.current;
691
+ if (!virtualizer.getVirtualItems().some(virtualRow_0 => virtualRow_0.index === index_1 && virtualRow_0.end > top_0 && virtualRow_0.start < bottom)) {
692
+ virtualizer.scrollToIndex(index_1, {
693
+ align: 'center'
694
+ });
695
+ }
696
+ },
697
+ coveredHeight: () => coveredHeightRef.current,
698
+ stopFollowing
699
+ }), [chatId, onMainAgent, rows, items, order, virtualizer, turns.add, groups.add, subagents.add, stopFollowing]);
700
+ const chatFind = useHostTimelineFind(findOptions);
701
+ const searching = chatFind.open;
702
+ // A find covers the whole thread, so every page before this one comes in while it is open.
703
+ useEffect(() => {
704
+ if (searching) {
705
+ loadEarlier();
706
+ }
707
+ }, [searching, loadEarlier]);
708
+ const found = useMemo(() => searching ? ticksWithHits(ticks, chatFind.hitRows) : [], [searching, ticks, chatFind.hitRows]);
709
+ const foundCurrent = chatFind.currentRow === null ? null : tickOfRow(ticks, chatFind.currentRow);
710
+ const jumpTo = index_2 => {
711
+ const tick_0 = ticks[index_2];
712
+ if (!tick_0) {
713
+ return;
714
+ }
715
+ if (tick_0.bookmark !== null) {
716
+ setLanding(null);
717
+ setSeeking({
718
+ itemId: tick_0.bookmark.itemId,
719
+ key: keyOf(chatId)
720
+ });
721
+ return;
722
+ }
723
+ showRowAtTop(virtualizer, followRef, tick_0.rowIndex);
724
+ };
725
+ useLayoutEffect(() => {
726
+ if (seeking === null) {
727
+ return;
728
+ }
729
+ if (seeking.key !== keyOf(chatId)) {
730
+ setSeeking(null);
731
+ return;
732
+ }
733
+ if (seeking.block) {
734
+ const jump = uiReplyJump(seeking.block, rows, items, turns.ids, cursor !== null);
735
+ if (jump.kind === 'earlier') {
736
+ loadEarlier();
737
+ } else if (jump.kind === 'turn') {
738
+ turns.add(jump.turnId);
739
+ } else {
404
740
  setSeeking(null);
405
- seeking.options?.onMissing?.();
406
- }, [seeking, rows, items, turns, virtualizer, cursor, loadEarlier, keyOf, chatId]);
407
- const waitingForThread = seeking !== null && items === undefined;
408
- useEffect(() => {
409
- if (!waitingForThread) {
410
- return;
741
+ if (jump.kind === 'row') {
742
+ showRowAtTop(virtualizer, followRef, jump.index);
743
+ setLanding({
744
+ target: seeking.block,
745
+ key: seeking.key
746
+ });
411
747
  }
412
- const timer = window.setTimeout(() => {
413
- setSeeking(null);
414
- seeking?.options?.onMissing?.();
415
- }, JUMP_THREAD_WAIT_MS);
416
- return () => window.clearTimeout(timer);
417
- }, [waitingForThread, seeking]);
418
- useEffect(() => registerItemJumper(keyOf(chatId), (itemId, options) => {
748
+ }
749
+ return;
750
+ }
751
+ const index_3 = rows.findIndex(row_8 => row_8.id === seeking.itemId);
752
+ if (index_3 !== -1) {
753
+ setSeeking(null);
754
+ showRowAtTop(virtualizer, followRef, index_3);
755
+ if (seeking.options?.flash) {
756
+ useTimelineFlash.getState().flash(seeking.key, seeking.itemId, false);
757
+ }
758
+ return;
759
+ }
760
+ // The thread has not loaded yet, so nothing says the item is missing; the effect below bounds the wait.
761
+ if (items === undefined) {
762
+ return;
763
+ }
764
+ const item = items[seeking.itemId];
765
+ if (item === undefined && cursor !== null) {
766
+ loadEarlier();
767
+ return;
768
+ }
769
+ const turnId = item?.turnId ?? null;
770
+ if (turnId !== null && !turns.ids.has(turnId)) {
771
+ turns.add(turnId);
772
+ return;
773
+ }
774
+ setSeeking(null);
775
+ seeking.options?.onMissing?.();
776
+ }, [seeking, rows, items, turns, virtualizer, cursor, loadEarlier, keyOf, chatId]);
777
+ const waitingForThread = seeking !== null && items === undefined;
778
+ useEffect(() => {
779
+ if (!waitingForThread) {
780
+ return;
781
+ }
782
+ const timer = window.setTimeout(() => {
783
+ setSeeking(null);
784
+ seeking?.options?.onMissing?.();
785
+ }, JUMP_THREAD_WAIT_MS);
786
+ return () => window.clearTimeout(timer);
787
+ }, [waitingForThread, seeking]);
788
+ useEffect(() => registerItemJumper(keyOf(chatId), (itemId, options) => {
789
+ setLanding(null);
790
+ setSeeking({
791
+ itemId,
792
+ key: keyOf(chatId),
793
+ options
794
+ });
795
+ }), [keyOf, chatId]);
796
+ useLayoutEffect(() => {
797
+ if (!landing || landing.key !== keyOf(chatId)) {
798
+ return;
799
+ }
800
+ let frame_0 = 0;
801
+ let attempts = 0;
802
+ const reveal = () => {
803
+ const scroller = scrollRef.current;
804
+ if (scroller && revealUiReplyBlock(scroller, landing.target)) {
805
+ useTimelineFlash.getState().flash(keyOf(chatId), landing.target.itemId, false, landing.target);
419
806
  setLanding(null);
420
- setSeeking({ itemId, key: keyOf(chatId), options });
421
- }), [keyOf, chatId]);
422
- useLayoutEffect(() => {
423
- if (!landing || landing.key !== keyOf(chatId)) {
424
- return;
425
- }
426
- let frame = 0;
427
- let attempts = 0;
428
- const reveal = () => {
429
- const scroller = scrollRef.current;
430
- if (scroller && revealUiReplyBlock(scroller, landing.target)) {
431
- useTimelineFlash.getState().flash(keyOf(chatId), landing.target.itemId, false, landing.target);
432
- setLanding(null);
433
- }
434
- else if (++attempts < 30) {
435
- // Virtual rows mount after scrollToIndex updates the visible range.
436
- frame = requestAnimationFrame(reveal);
437
- }
438
- else {
439
- setLanding(null);
440
- }
441
- };
442
- frame = requestAnimationFrame(reveal);
443
- return () => cancelAnimationFrame(frame);
444
- }, [landing, keyOf, chatId]);
445
- useLayoutEffect(() => {
446
- if (revealing === null) {
447
- return;
448
- }
449
- const { turnId, target } = revealing;
450
- const changes = target === 'changes' ? rows.findIndex((row) => row.kind === 'changed-files' && row.turnId === turnId) : -1;
451
- const opening = order?.map((id) => items?.[id]).find((item) => item?.kind === 'user' && item.turnId === turnId);
452
- const found = changes !== -1 ? changes : rows.findIndex((row) => row.id === opening?.id || row.id === `start-${turnId}` || row.id === `fold-${turnId}`);
453
- if (found !== -1) {
454
- setRevealing(null);
455
- showRowAtTop(virtualizer, followRef, found);
456
- useTimelineFlash.getState().flash(keyOf(chatId), rows[found].id, changes !== -1);
457
- return;
458
- }
459
- // The turn is before the page this thread holds; a thread that holds it all has no such turn.
460
- if (cursor === null) {
461
- setRevealing(null);
462
- return;
463
- }
464
- loadEarlier();
465
- }, [revealing, rows, order, items, virtualizer, cursor, loadEarlier, keyOf, chatId]);
466
- useEffect(() => registerTurnJumper(keyOf(chatId), (turnId, target) => setRevealing({ turnId, target })), [keyOf, chatId]);
467
- // The strip is memoized and draws on every scroll, so it gets one callback for its life.
468
- const jumpRef = useRef(jumpTo);
469
- jumpRef.current = jumpTo;
470
- const pick = useCallback((index) => jumpRef.current(index), []);
471
- // Read when the card asks rather than on every render, so the strip's memo holds while the thread streams.
472
- const cardChat = useMemo(() => ({
473
- chatId,
474
- canFork: (turnId) => {
475
- if (fork === null) {
476
- return false;
477
- }
478
- const row = useChats.getState().byKey[keyOf(chatId)];
479
- return forkRefusal(row?.info ?? null, row?.structure[turnId]) === null;
480
- },
481
- fork: (turnId) => fork?.(chatId, turnId)
482
- }), [keyOf, chatId, fork]);
483
- const stepRef = useRef(() => false);
484
- stepRef.current = (direction) => {
485
- if (!onMainAgent) {
486
- return false;
487
- }
488
- const index = stepMessage(starts, (i) => ticks[i].kind === 'person', scrollTop, direction);
489
- if (index === null) {
490
- return false;
491
- }
492
- jumpTo(index);
493
- return true;
807
+ } else if (++attempts < 30) {
808
+ // Virtual rows mount after scrollToIndex updates the visible range.
809
+ frame_0 = requestAnimationFrame(reveal);
810
+ } else {
811
+ setLanding(null);
812
+ }
494
813
  };
495
- useEffect(() => registerMessageStepper(keyOf(chatId), (direction) => stepRef.current(direction)), [keyOf, chatId]);
496
- // Read through a ref, so the rows keep one callback for their life and their memo holds.
497
- const opensRef = useRef({ rows, show });
498
- opensRef.current = { rows, show };
499
- /* The whole of what a sub-agent did, in the thread's place. Its row only keeps the beginning. */
500
- const openConversation = useCallback((step) => {
501
- opensRef.current.show(openFromMain(step));
502
- }, []);
503
- const addSubagent = subagents.add;
504
- /* The header of a turn a sub-agent woke. It points at the row that agent worked in, or the one it hangs under. */
505
- const openSubagent = useCallback((toolUseId) => {
506
- const found = findSubagentBranch(opensRef.current.rows, toolUseId);
507
- if (!found) {
508
- return;
509
- }
510
- addSubagent(found.branch.id);
511
- showRowAtTop(virtualizer, followRef, found.index);
512
- }, [addSubagent, virtualizer]);
513
- const flashedRow = flash !== null && flash.key === keyOf(chatId) && !flash.block ? flash.rowId : null;
514
- return (_jsx(FileLinkContext.Provider, { value: info?.cwd ?? null, children: _jsxs("div", { ref: frameRef, className: "relative flex min-h-0 grow flex-col", style: readingWidthStyle(readingWidth), "data-file-cwd": info?.cwd ?? '', "data-file-scope-id": scope.id, children: [searching && chatFind.bar, _jsxs(ContextMenu.Root, { children: [_jsx("div", { ref: scrollRef,
515
- // No CSS scroll-padding here: Chromium counts the sticky composer's caret as hidden behind
516
- // it and scrolls the thread toward the end on every keystroke.
517
- className: "chat-scroll min-h-0 grow overflow-auto",
518
- // The thread keeps its reader in place itself when a page goes in above.
519
- style: { overflowAnchor: 'none' }, onScrollCapture: (e) => {
520
- const el = e.currentTarget;
521
- // Capture the reader's movement before virtualization remeasures rows and shifts the scroll offset.
522
- const previous = scrollGeometry.current;
523
- const layoutChanged = previous.height !== el.scrollHeight || previous.viewport !== el.clientHeight;
524
- const atEnd = el.scrollHeight - el.scrollTop - el.clientHeight < FOLLOW_THRESHOLD_PX;
525
- if (!layoutChanged && el.scrollTop < previous.top) {
526
- followRef.current = false;
527
- }
528
- else if (atEnd) {
529
- followRef.current = true;
530
- }
531
- scrollGeometry.current = { top: el.scrollTop, height: el.scrollHeight, viewport: el.clientHeight };
532
- setTimelineAtEnd(chatId, followRef.current);
533
- if (wantsEarlier(el)) {
534
- loadEarlier();
535
- }
536
- followStrip(el);
537
- }, children: _jsxs("div", { ref: contentRef, className: clsx('relative flex min-h-full flex-col', welcome && 'justify-center'), children: [_jsx(ContextMenu.Trigger, { ref: threadRef, className: clsx('chat-thread flex flex-col px-4 pt-4', !welcome && 'grow'), style: {
538
- paddingLeft,
539
- '--chat-wide-inset': `${2 * (showsScrubber ? STRIP_CLEARANCE_PX : paddingLeft) - paddingLeft}px`
540
- }, inert: overlay ? true : undefined, onContextMenu: (e) => setTarget(readTimelineTarget(e.target, threadRef.current, withCurrentText(rows, fullItems()))), children: welcome ? (_jsx(WelcomeGreeting, { chatId: chatId })) : empty ? (_jsx(EmptyThread, { chatId: chatId })) : (_jsx("div", { className: "relative w-full shrink-0", style: { height: virtualizer.getTotalSize() }, children: _jsx(UiReplyNavigationContext.Provider, { value: uiNavigation, children: _jsx(FindRevealContext.Provider, { value: chatFind.reveal, children: virtualizer.getVirtualItems().map((virtualRow) => {
541
- const row = rows[virtualRow.index];
542
- return (_jsx(ThreadRow, { row: row, previous: virtualRow.index > 0 ? rows[virtualRow.index - 1] : null, index: virtualRow.index, top: virtualRow.start, measure: virtualizer.measureElement, chatId: chatId, flashNonce: flash !== null && flashedRow === row.id ? flash.nonce : null, bookmark: marks.get(row.id) ?? null, toggleGroup: groups.toggle, toggleTurn: turns.toggle, toggleSubagent: subagents.toggle, openSubagent: openSubagent, openConversation: openConversation }, row.id));
543
- }) }) }) })) }), _jsx(QuoteButton, { thread: threadRef, frame: contentRef, taker: quoteTakerRef }), _jsx("div", { ref: composerRef,
544
- // The card is translucent so the thread shows through it, but the pb-3 strip under it must stay opaque.
545
- className: clsx('relative z-10 shrink-0', composer && 'px-3 pb-3 pt-3 bg-[linear-gradient(to_bottom,transparent_calc(100%-12px),var(--surface)_calc(100%-12px))]'), style: { position: stickyComposer ? 'sticky' : 'relative', bottom: 0 }, children: _jsx(QuoteTakerContext.Provider, { value: registerQuoteTaker, children: composer }) })] }) }), _jsx(TimelineMenuPopup, { target: target, thread: threadRef, chatId: onMainAgent ? chatId : null, column: frame.column })] }), drawsVisuals && _jsx(VisualDialogs, { chatId: chatId }), loadingEarlier && !overlay && (_jsxs("div", { className: "pointer-events-none absolute inset-x-0 top-3 z-10 flex justify-center", role: "status", children: [_jsx(Icon, { icon: LoaderCircle, size: 16, className: "animate-spin text-text-faint" }), _jsx("span", { className: "sr-only", children: t('timeline.loadingEarlier') })] })), showsScrubber && !overlay && (_jsx("div", { className: "absolute top-4", style: { left: STRIP_INSET_PX, width: STRIP_WIDTH_PX, bottom: coveredHeight }, children: _jsx(ErrorBoundary, { label: t('timeline.stripFailed'), resetKeys: [ticks.length], children: _jsx(Scrubber, { ticks: ticks, firstInView: inView?.first ?? null, lastInView: inView?.last ?? null, onPick: pick, chat: cardChat, found: found, foundCurrent: foundCurrent }) }) })), overlay && (_jsx("div", { className: "absolute inset-0 z-5 flex flex-col bg-surface", style: { paddingBottom: coveredHeight }, children: overlay }))] }) }));
546
- }
814
+ frame_0 = requestAnimationFrame(reveal);
815
+ return () => cancelAnimationFrame(frame_0);
816
+ }, [landing, keyOf, chatId]);
817
+ useLayoutEffect(() => {
818
+ if (revealing === null) {
819
+ return;
820
+ }
821
+ const {
822
+ turnId: turnId_0,
823
+ target: target_0
824
+ } = revealing;
825
+ const changes = target_0 === 'changes' ? rows.findIndex(row_9 => row_9.kind === 'changed-files' && row_9.turnId === turnId_0) : -1;
826
+ const opening = order?.map(id_0 => items?.[id_0]).find(item_0 => item_0?.kind === 'user' && item_0.turnId === turnId_0);
827
+ const found_0 = changes !== -1 ? changes : rows.findIndex(row_10 => row_10.id === opening?.id || row_10.id === `start-${turnId_0}` || row_10.id === `fold-${turnId_0}`);
828
+ if (found_0 !== -1) {
829
+ setRevealing(null);
830
+ showRowAtTop(virtualizer, followRef, found_0);
831
+ useTimelineFlash.getState().flash(keyOf(chatId), rows[found_0].id, changes !== -1);
832
+ return;
833
+ }
834
+ // The turn is before the page this thread holds; a thread that holds it all has no such turn.
835
+ if (cursor === null) {
836
+ setRevealing(null);
837
+ return;
838
+ }
839
+ loadEarlier();
840
+ }, [revealing, rows, order, items, virtualizer, cursor, loadEarlier, keyOf, chatId]);
841
+ useEffect(() => registerTurnJumper(keyOf(chatId), (turnId_1, target_1) => setRevealing({
842
+ turnId: turnId_1,
843
+ target: target_1
844
+ })), [keyOf, chatId]);
845
+ // The strip is memoized and draws on every scroll, so it gets one callback for its life.
846
+ const jumpRef = useRef(jumpTo);
847
+ jumpRef.current = jumpTo;
848
+ const pick = useCallback(index_4 => jumpRef.current(index_4), []);
849
+ // Read when the card asks rather than on every render, so the strip's memo holds while the thread streams.
850
+ const cardChat = useMemo(() => ({
851
+ chatId,
852
+ canFork: turnId_2 => {
853
+ if (fork === null) {
854
+ return false;
855
+ }
856
+ const row_11 = useChats.getState().byKey[keyOf(chatId)];
857
+ return forkRefusal(row_11?.info ?? null, row_11?.structure[turnId_2]) === null;
858
+ },
859
+ fork: turnId_3 => fork?.(chatId, turnId_3)
860
+ }), [keyOf, chatId, fork]);
861
+ const stepRef = useRef(() => false);
862
+ stepRef.current = direction => {
863
+ if (!onMainAgent) {
864
+ return false;
865
+ }
866
+ const index_5 = stepMessage(starts, i => ticks[i].kind === 'person', scrollTop, direction);
867
+ if (index_5 === null) {
868
+ return false;
869
+ }
870
+ jumpTo(index_5);
871
+ return true;
872
+ };
873
+ useEffect(() => registerMessageStepper(keyOf(chatId), direction_0 => stepRef.current(direction_0)), [keyOf, chatId]);
874
+ // Read through a ref, so the rows keep one callback for their life and their memo holds.
875
+ const opensRef = useRef({
876
+ rows,
877
+ show
878
+ });
879
+ opensRef.current = {
880
+ rows,
881
+ show
882
+ };
883
+ /* The whole of what a sub-agent did, in the thread's place. Its row only keeps the beginning. */
884
+ const openConversation = useCallback(step => {
885
+ opensRef.current.show(openFromMain(step));
886
+ }, []);
887
+ const addSubagent = subagents.add;
888
+ /* The header of a turn a sub-agent woke. It points at the row that agent worked in, or the one it hangs under. */
889
+ const openSubagent = useCallback(toolUseId => {
890
+ const found_1 = findSubagentBranch(opensRef.current.rows, toolUseId);
891
+ if (!found_1) {
892
+ return;
893
+ }
894
+ addSubagent(found_1.branch.id);
895
+ showRowAtTop(virtualizer, followRef, found_1.index);
896
+ }, [addSubagent, virtualizer]);
897
+ const flashedRow = flash !== null && flash.key === keyOf(chatId) && !flash.block ? flash.rowId : null;
898
+ return (
899
+ /*#__PURE__*/
900
+ // A file an answer names is relative to the folder this chat runs in, which is a worktree as
901
+ // often as it is the project itself.
902
+ _jsx(FileLinkContext.Provider, {
903
+ value: info?.cwd ?? null,
904
+ children: /*#__PURE__*/_jsxs("div", {
905
+ ref: frameRef,
906
+ className: "relative flex min-h-0 grow flex-col",
907
+ style: readingWidthStyle(readingWidth),
908
+ "data-file-cwd": info?.cwd ?? '',
909
+ "data-file-scope-id": scope.id,
910
+ children: [searching && chatFind.bar, /*#__PURE__*/_jsxs(ContextMenu.Root, {
911
+ children: [/*#__PURE__*/_jsx("div", {
912
+ ref: scrollRef
913
+ // No CSS scroll-padding here: Chromium counts the sticky composer's caret as hidden behind
914
+ // it and scrolls the thread toward the end on every keystroke.
915
+ ,
916
+ className: "chat-scroll min-h-0 grow overflow-auto"
917
+ // The thread keeps its reader in place itself when a page goes in above.
918
+ ,
919
+ style: {
920
+ overflowAnchor: 'none'
921
+ },
922
+ onScrollCapture: e => {
923
+ const el = e.currentTarget;
924
+ // Capture the reader's movement before virtualization remeasures rows and shifts the scroll offset.
925
+ const previous = scrollGeometry.current;
926
+ const layoutChanged = previous.height !== el.scrollHeight || previous.viewport !== el.clientHeight;
927
+ const atEnd = el.scrollHeight - el.scrollTop - el.clientHeight < FOLLOW_THRESHOLD_PX;
928
+ if (!layoutChanged && el.scrollTop < previous.top) {
929
+ followRef.current = false;
930
+ } else if (atEnd) {
931
+ followRef.current = true;
932
+ }
933
+ scrollGeometry.current = {
934
+ top: el.scrollTop,
935
+ height: el.scrollHeight,
936
+ viewport: el.clientHeight
937
+ };
938
+ setTimelineAtEnd(chatId, followRef.current);
939
+ if (wantsEarlier(el)) {
940
+ loadEarlier();
941
+ }
942
+ followStrip(el);
943
+ },
944
+ children: /*#__PURE__*/_jsxs("div", {
945
+ ref: contentRef,
946
+ className: clsx('relative flex min-h-full flex-col', welcome && 'justify-center'),
947
+ children: [/*#__PURE__*/_jsx(ContextMenu.Trigger, {
948
+ ref: threadRef,
949
+ className: clsx('chat-thread flex flex-col px-4 pt-4', !welcome && 'grow'),
950
+ style: {
951
+ paddingLeft,
952
+ '--chat-wide-inset': `${2 * (showsScrubber ? STRIP_CLEARANCE_PX : paddingLeft) - paddingLeft}px`
953
+ },
954
+ inert: overlay ? true : undefined,
955
+ onContextMenu: e_0 => setTarget(readTimelineTarget(e_0.target, threadRef.current, withCurrentText(rows, fullItems()))),
956
+ children: welcome ? /*#__PURE__*/_jsx(WelcomeGreeting, {
957
+ chatId: chatId
958
+ }) : empty ? /*#__PURE__*/_jsx(EmptyThread, {
959
+ chatId: chatId
960
+ }) : /*#__PURE__*/_jsx("div", {
961
+ className: "relative w-full shrink-0",
962
+ style: {
963
+ height: virtualizer.getTotalSize()
964
+ },
965
+ children: /*#__PURE__*/_jsx(UiReplyNavigationContext.Provider, {
966
+ value: uiNavigation,
967
+ children: /*#__PURE__*/_jsx(FindRevealContext.Provider, {
968
+ value: chatFind.reveal,
969
+ children: virtualizer.getVirtualItems().map(virtualRow_1 => {
970
+ const row_12 = rows[virtualRow_1.index];
971
+ return /*#__PURE__*/_jsx(ThreadRow, {
972
+ row: row_12,
973
+ previous: virtualRow_1.index > 0 ? rows[virtualRow_1.index - 1] : null,
974
+ index: virtualRow_1.index,
975
+ top: virtualRow_1.start,
976
+ measure: virtualizer.measureElement,
977
+ chatId: chatId,
978
+ flashNonce: flash !== null && flashedRow === row_12.id ? flash.nonce : null,
979
+ bookmark: marks.get(row_12.id) ?? null,
980
+ toggleGroup: groups.toggle,
981
+ toggleTurn: turns.toggle,
982
+ toggleSubagent: subagents.toggle,
983
+ openSubagent: openSubagent,
984
+ openConversation: openConversation
985
+ }, row_12.id);
986
+ })
987
+ })
988
+ })
989
+ })
990
+ }), /*#__PURE__*/_jsx(QuoteButton, {
991
+ thread: threadRef,
992
+ frame: contentRef,
993
+ taker: quoteTakerRef
994
+ }), /*#__PURE__*/_jsx("div", {
995
+ ref: composerRef
996
+ // The card is translucent so the thread shows through it, but the pb-3 strip under it must stay opaque.
997
+ ,
998
+ className: clsx('relative z-10 shrink-0', composer && 'px-3 pb-3 pt-3 bg-[linear-gradient(to_bottom,transparent_calc(100%-12px),var(--surface)_calc(100%-12px))]'),
999
+ style: {
1000
+ position: stickyComposer ? 'sticky' : 'relative',
1001
+ bottom: 0
1002
+ },
1003
+ children: /*#__PURE__*/_jsx(QuoteTakerContext.Provider, {
1004
+ value: registerQuoteTaker,
1005
+ children: composer
1006
+ })
1007
+ })]
1008
+ })
1009
+ }), /*#__PURE__*/_jsx(TimelineMenuPopup, {
1010
+ target: target,
1011
+ thread: threadRef,
1012
+ chatId: onMainAgent ? chatId : null,
1013
+ column: frame.column
1014
+ })]
1015
+ }), drawsVisuals && /*#__PURE__*/_jsx(VisualDialogs, {
1016
+ chatId: chatId
1017
+ }), loadingEarlier && !overlay && /*#__PURE__*/_jsxs("div", {
1018
+ className: "pointer-events-none absolute inset-x-0 top-3 z-10 flex justify-center",
1019
+ role: "status",
1020
+ children: [/*#__PURE__*/_jsx(Icon, {
1021
+ icon: LoaderCircle,
1022
+ size: 16,
1023
+ className: "animate-spin text-text-faint"
1024
+ }), /*#__PURE__*/_jsx("span", {
1025
+ className: "sr-only",
1026
+ children: t('timeline.loadingEarlier')
1027
+ })]
1028
+ }), showsScrubber && !overlay && /*#__PURE__*/_jsx("div", {
1029
+ className: "absolute top-4",
1030
+ style: {
1031
+ left: STRIP_INSET_PX,
1032
+ width: STRIP_WIDTH_PX,
1033
+ bottom: coveredHeight
1034
+ },
1035
+ children: /*#__PURE__*/_jsx(ErrorBoundary, {
1036
+ label: t('timeline.stripFailed'),
1037
+ resetKeys: [ticks.length],
1038
+ children: /*#__PURE__*/_jsx(Scrubber, {
1039
+ ticks: ticks,
1040
+ firstInView: inView?.first ?? null,
1041
+ lastInView: inView?.last ?? null,
1042
+ onPick: pick,
1043
+ chat: cardChat,
1044
+ found: found,
1045
+ foundCurrent: foundCurrent
1046
+ })
1047
+ })
1048
+ }), overlay && /*#__PURE__*/_jsx("div", {
1049
+ className: "absolute inset-0 z-5 flex flex-col bg-surface",
1050
+ style: {
1051
+ paddingBottom: coveredHeight
1052
+ },
1053
+ children: overlay
1054
+ })]
1055
+ })
1056
+ })
1057
+ );
1058
+ }