@adecore/agents-react 0.38.0 → 0.40.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 (351) hide show
  1. package/dist/agents/AgentModelLabel.d.ts +10 -0
  2. package/dist/agents/AgentModelLabel.js +98 -0
  3. package/dist/agents/model-name.js +2 -1
  4. package/dist/agents/provider-paths.d.ts +2 -1
  5. package/dist/agents/provider-paths.js +7 -3
  6. package/dist/chat/account-choice.js +57 -28
  7. package/dist/chat/bookmarks.js +2 -1
  8. package/dist/chat/drafts.d.ts +0 -1
  9. package/dist/chat/drafts.js +9 -28
  10. package/dist/chat/logic/picker-entries.js +2 -8
  11. package/dist/chat/logic/resume-compaction.js +2 -5
  12. package/dist/chat/logic/timeline-copy.js +2 -8
  13. package/dist/chat/logic/timeline.js +9 -10
  14. package/dist/chat/logic/workflow.js +2 -4
  15. package/dist/chat/subagent-conversation.js +2 -1
  16. package/dist/chat/ui/Composer.js +385 -360
  17. package/dist/chat/ui/GoalDock.js +2 -1
  18. package/dist/chat/ui/LimitState.js +4 -6
  19. package/dist/chat/ui/Pickers.d.ts +2 -1
  20. package/dist/chat/ui/Pickers.js +126 -123
  21. package/dist/chat/ui/PromptEditor.d.ts +19 -0
  22. package/dist/chat/ui/PromptEditor.js +115 -0
  23. package/dist/chat/ui/Timeline.d.ts +2 -1
  24. package/dist/chat/ui/Timeline.js +13 -1
  25. package/dist/chat/ui/composer/ComposerControls.d.ts +3 -0
  26. package/dist/chat/ui/composer/ComposerControls.js +235 -38
  27. package/dist/chat/ui/composer/key-handler.d.ts +1 -1
  28. package/dist/chat/ui/composer/key-handler.js +3 -11
  29. package/dist/chat/ui/composer/prompt-picks.d.ts +4 -0
  30. package/dist/chat/ui/composer/prompt-picks.js +14 -0
  31. package/dist/chat/ui/composer/use-composer-menus.d.ts +4 -49
  32. package/dist/chat/ui/composer/use-composer-menus.js +65 -519
  33. package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
  34. package/dist/chat/ui/composer/use-notice.js +2 -1
  35. package/dist/chat/ui/composer/use-prompt-history.js +1 -1
  36. package/dist/chat/ui/composer/use-prompt-menus.d.ts +73 -0
  37. package/dist/chat/ui/composer/use-prompt-menus.js +499 -0
  38. package/dist/chat/ui/composer/use-take-back.js +2 -2
  39. package/dist/chat/ui/diff/ApprovalReview.js +1 -1
  40. package/dist/chat/ui/highlighter.d.ts +0 -6
  41. package/dist/chat/ui/highlighter.js +0 -12
  42. package/dist/chat/ui/icons.d.ts +0 -1
  43. package/dist/chat/ui/icons.js +0 -3
  44. package/dist/chat/ui/index.d.ts +3 -0
  45. package/dist/chat/ui/index.js +2 -0
  46. package/dist/chat/ui/rows/MessageRows.js +2 -5
  47. package/dist/chat/ui/rows/row-rhythm.js +2 -5
  48. package/dist/chat/ui/theme-watch.js +3 -7
  49. package/dist/host-hooks.d.ts +2 -1
  50. package/dist/host-hooks.js +6 -0
  51. package/dist/host.d.ts +17 -1
  52. package/dist/index.d.ts +1 -1
  53. package/dist/locales/en/agent-chat.d.ts +1 -0
  54. package/dist/locales/en/agent-chat.js +1 -0
  55. package/dist/locales/nl/agent-chat.d.ts +1 -0
  56. package/dist/locales/nl/agent-chat.js +1 -0
  57. package/dist/prompts/logic/usePromptSession.js +2 -1
  58. package/dist/providers/AddAccountForm.js +2 -1
  59. package/dist/providers/CliDetail.js +104 -130
  60. package/dist/providers/account-actions.js +6 -9
  61. package/dist/providers/index.d.ts +1 -0
  62. package/dist/providers/index.js +1 -0
  63. package/dist/state/chats.d.ts +1 -0
  64. package/dist/state/chats.js +6 -11
  65. package/dist/state/provider-accounts.d.ts +1 -1
  66. package/dist/state/provider-accounts.js +2 -8
  67. package/dist/theme.css +3 -0
  68. package/dist/transport.d.ts +2 -0
  69. package/dist/transport.js +14 -0
  70. package/dist/usage/ChartLogo.d.ts +5 -0
  71. package/dist/usage/ChartLogo.js +48 -0
  72. package/dist/usage/LimitsList.js +6 -16
  73. package/dist/usage/UsageBreakdown.js +17 -40
  74. package/dist/usage/UsageLimits.js +183 -211
  75. package/dist/usage/UsagePage.js +3 -17
  76. package/dist/usage/UsageSummary.js +4 -9
  77. package/dist/usage/limits.js +16 -31
  78. package/dist/usage/summary.d.ts +1 -1
  79. package/dist/usage/summary.js +3 -5
  80. package/package.json +26 -26
  81. package/src/agents/AccountDot.tsx +1 -0
  82. package/src/agents/AgentIcon.tsx +2 -0
  83. package/src/agents/AgentModelLabel.tsx +38 -0
  84. package/src/agents/ModelOptionControl.tsx +8 -0
  85. package/src/agents/account-limits.ts +3 -0
  86. package/src/agents/accounts.ts +12 -0
  87. package/src/agents/model-name.ts +13 -1
  88. package/src/agents/model-options.ts +2 -0
  89. package/src/agents/model-requirement.ts +4 -0
  90. package/src/agents/option-words.ts +2 -0
  91. package/src/agents/provider-paths.ts +8 -3
  92. package/src/agents/status-look.ts +1 -0
  93. package/src/chat/account-choice.ts +25 -19
  94. package/src/chat/actions.ts +5 -0
  95. package/src/chat/attachments.ts +8 -0
  96. package/src/chat/bookmarks.ts +13 -1
  97. package/src/chat/chat-client.ts +63 -0
  98. package/src/chat/chat-references.ts +3 -0
  99. package/src/chat/composer-effort.ts +5 -0
  100. package/src/chat/composer-send.ts +4 -0
  101. package/src/chat/drafts.ts +21 -17
  102. package/src/chat/forks.ts +4 -0
  103. package/src/chat/guards.ts +2 -0
  104. package/src/chat/index.ts +7 -0
  105. package/src/chat/logic/ansi.ts +47 -0
  106. package/src/chat/logic/bookmarks.ts +7 -0
  107. package/src/chat/logic/command-looks.ts +29 -0
  108. package/src/chat/logic/context-usage.ts +3 -0
  109. package/src/chat/logic/continue-after-stop.ts +5 -0
  110. package/src/chat/logic/effort.ts +4 -0
  111. package/src/chat/logic/embedded-code.ts +63 -0
  112. package/src/chat/logic/fork.ts +22 -0
  113. package/src/chat/logic/generated-image.ts +7 -0
  114. package/src/chat/logic/goal.ts +7 -0
  115. package/src/chat/logic/handback.ts +2 -0
  116. package/src/chat/logic/limit.ts +8 -0
  117. package/src/chat/logic/picker-entries.ts +16 -4
  118. package/src/chat/logic/resume-compaction.ts +22 -5
  119. package/src/chat/logic/scrubber.ts +38 -5
  120. package/src/chat/logic/thought.ts +5 -0
  121. package/src/chat/logic/thread-cards.ts +6 -0
  122. package/src/chat/logic/timeline-copy.ts +4 -9
  123. package/src/chat/logic/timeline-target.ts +15 -0
  124. package/src/chat/logic/timeline.ts +113 -10
  125. package/src/chat/logic/tools.ts +30 -0
  126. package/src/chat/logic/visual-height.ts +5 -0
  127. package/src/chat/logic/visual-viewport.ts +1 -0
  128. package/src/chat/logic/welcome.ts +3 -0
  129. package/src/chat/logic/workflow.ts +26 -7
  130. package/src/chat/mentions.ts +56 -0
  131. package/src/chat/pending-prompts.ts +16 -0
  132. package/src/chat/persisted-json.ts +15 -0
  133. package/src/chat/preferences.ts +16 -0
  134. package/src/chat/prompt-hold.ts +2 -0
  135. package/src/chat/quote.ts +7 -0
  136. package/src/chat/reading-width.ts +1 -0
  137. package/src/chat/recent-messages.ts +5 -0
  138. package/src/chat/resume-compaction-dismissals.ts +1 -0
  139. package/src/chat/stash.ts +12 -0
  140. package/src/chat/subagent-conversation.ts +24 -4
  141. package/src/chat/subagent-list.ts +14 -0
  142. package/src/chat/subagent-support.ts +1 -0
  143. package/src/chat/subagent-view.ts +9 -0
  144. package/src/chat/thumbnails.ts +14 -0
  145. package/src/chat/timeline-flash.ts +2 -0
  146. package/src/chat/timeline-scroll.ts +23 -0
  147. package/src/chat/ui/AccountPill.tsx +4 -0
  148. package/src/chat/ui/AnsiOutput.tsx +1 -0
  149. package/src/chat/ui/BookmarkMarker.tsx +5 -0
  150. package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
  151. package/src/chat/ui/ChatActivity.tsx +13 -0
  152. package/src/chat/ui/CodeBlock.tsx +8 -0
  153. package/src/chat/ui/Composer.tsx +27 -3
  154. package/src/chat/ui/ComposerInput.tsx +22 -0
  155. package/src/chat/ui/DiffPool.tsx +3 -0
  156. package/src/chat/ui/EditDiff.tsx +5 -0
  157. package/src/chat/ui/GoalDock.tsx +13 -1
  158. package/src/chat/ui/ImageView.tsx +11 -0
  159. package/src/chat/ui/LimitState.tsx +23 -6
  160. package/src/chat/ui/Markdown.tsx +41 -0
  161. package/src/chat/ui/MermaidBlock.tsx +16 -0
  162. package/src/chat/ui/MessageActions.tsx +8 -0
  163. package/src/chat/ui/ModelMarks.tsx +1 -0
  164. package/src/chat/ui/Pickers.tsx +30 -1
  165. package/src/chat/ui/PromptComposer.tsx +2 -0
  166. package/src/chat/ui/PromptEditor.tsx +136 -0
  167. package/src/chat/ui/QuoteButton.tsx +12 -0
  168. package/src/chat/ui/ReferenceChip.tsx +3 -0
  169. package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
  170. package/src/chat/ui/RowTime.tsx +1 -0
  171. package/src/chat/ui/RunSettings.tsx +38 -0
  172. package/src/chat/ui/Scrubber.tsx +14 -0
  173. package/src/chat/ui/SessionLine.tsx +1 -0
  174. package/src/chat/ui/ShellCommand.tsx +32 -0
  175. package/src/chat/ui/SubagentControls.tsx +7 -0
  176. package/src/chat/ui/SubagentInfo.tsx +1 -0
  177. package/src/chat/ui/SubagentStopButton.tsx +6 -0
  178. package/src/chat/ui/SubagentTimeline.tsx +10 -0
  179. package/src/chat/ui/Timeline.tsx +141 -1
  180. package/src/chat/ui/TimelineMenu.tsx +5 -0
  181. package/src/chat/ui/UnifiedDiff.tsx +6 -0
  182. package/src/chat/ui/UploadThumb.tsx +2 -0
  183. package/src/chat/ui/VisualDialogs.tsx +3 -0
  184. package/src/chat/ui/VisualFrame.tsx +15 -0
  185. package/src/chat/ui/Welcome.tsx +1 -0
  186. package/src/chat/ui/chip-clipboard.ts +20 -0
  187. package/src/chat/ui/code-lines.ts +14 -0
  188. package/src/chat/ui/code-theme.ts +1 -0
  189. package/src/chat/ui/composer/ComposerControls.tsx +70 -15
  190. package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
  191. package/src/chat/ui/composer/DraftParts.tsx +2 -0
  192. package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
  193. package/src/chat/ui/composer/chip-paste.ts +16 -0
  194. package/src/chat/ui/composer/chips.ts +6 -0
  195. package/src/chat/ui/composer/editor.ts +9 -0
  196. package/src/chat/ui/composer/insert.ts +2 -0
  197. package/src/chat/ui/composer/key-handler.ts +42 -9
  198. package/src/chat/ui/composer/keys.ts +23 -0
  199. package/src/chat/ui/composer/placeholder.ts +6 -0
  200. package/src/chat/ui/composer/prompt-picks.ts +18 -0
  201. package/src/chat/ui/composer/surface.ts +5 -0
  202. package/src/chat/ui/composer/use-chat-controls.ts +13 -0
  203. package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
  204. package/src/chat/ui/composer/use-composer-menus.ts +20 -275
  205. package/src/chat/ui/composer/use-composer-submit.ts +27 -0
  206. package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
  207. package/src/chat/ui/composer/use-draft-files.ts +24 -0
  208. package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
  209. package/src/chat/ui/composer/use-notice.ts +4 -1
  210. package/src/chat/ui/composer/use-pending-requests.ts +3 -0
  211. package/src/chat/ui/composer/use-prompt-history.ts +20 -11
  212. package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
  213. package/src/chat/ui/composer/use-prompt-menus.ts +361 -0
  214. package/src/chat/ui/composer/use-take-back.ts +20 -2
  215. package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
  216. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
  217. package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
  218. package/src/chat/ui/diff/DiffStack.tsx +2 -0
  219. package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
  220. package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
  221. package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
  222. package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
  223. package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
  224. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
  225. package/src/chat/ui/diff/RawDiff.tsx +1 -0
  226. package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
  227. package/src/chat/ui/diff/diff-source-budget.ts +8 -0
  228. package/src/chat/ui/diff/editor-edit-model.ts +4 -0
  229. package/src/chat/ui/diff/review-plan.ts +9 -0
  230. package/src/chat/ui/diff/review-settle.ts +1 -0
  231. package/src/chat/ui/diff/syntax-runtime.ts +52 -0
  232. package/src/chat/ui/diff/use-review-stack.ts +9 -0
  233. package/src/chat/ui/diff-syntax.ts +12 -0
  234. package/src/chat/ui/diff-theme.ts +2 -0
  235. package/src/chat/ui/file-links.ts +2 -0
  236. package/src/chat/ui/find-reveal.ts +1 -0
  237. package/src/chat/ui/full-diff.ts +1 -0
  238. package/src/chat/ui/highlighter.ts +29 -15
  239. package/src/chat/ui/icons.tsx +2 -4
  240. package/src/chat/ui/index.ts +7 -0
  241. package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
  242. package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
  243. package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
  244. package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
  245. package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
  246. package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
  247. package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
  248. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
  249. package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
  250. package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
  251. package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
  252. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
  253. package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
  254. package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
  255. package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
  256. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
  257. package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
  258. package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
  259. package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
  260. package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
  261. package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
  262. package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
  263. package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
  264. package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
  265. package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
  266. package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
  267. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
  268. package/src/chat/ui/markdown-blocks.ts +64 -0
  269. package/src/chat/ui/mermaid.ts +4 -0
  270. package/src/chat/ui/quote-selection.ts +10 -0
  271. package/src/chat/ui/rehype-chips.ts +5 -0
  272. package/src/chat/ui/rehype-fade.ts +2 -0
  273. package/src/chat/ui/remark-html-as-text.ts +5 -0
  274. package/src/chat/ui/reveal.ts +18 -0
  275. package/src/chat/ui/rows/ForksRow.tsx +5 -0
  276. package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
  277. package/src/chat/ui/rows/MessageRows.tsx +29 -5
  278. package/src/chat/ui/rows/RowContainer.tsx +1 -0
  279. package/src/chat/ui/rows/Rows.tsx +2 -0
  280. package/src/chat/ui/rows/SubagentRow.tsx +13 -0
  281. package/src/chat/ui/rows/TaskRow.tsx +7 -0
  282. package/src/chat/ui/rows/VisualRow.tsx +4 -0
  283. package/src/chat/ui/rows/WorkRows.tsx +46 -0
  284. package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
  285. package/src/chat/ui/rows/row-rhythm.ts +7 -5
  286. package/src/chat/ui/theme-watch.ts +8 -7
  287. package/src/chat/ui/thread-references.ts +7 -0
  288. package/src/chat/ui/use-diff-source-tokens.ts +8 -0
  289. package/src/chat/ui/useToggleSet.ts +3 -0
  290. package/src/chat/ui/visual-theme.ts +30 -0
  291. package/src/chat/visual-bridge.ts +23 -0
  292. package/src/chat/visual-viewport.ts +12 -0
  293. package/src/host-hooks.ts +15 -5
  294. package/src/host.ts +29 -1
  295. package/src/index.ts +5 -0
  296. package/src/lazy.tsx +9 -0
  297. package/src/locales/en/agent-chat.ts +1 -0
  298. package/src/locales/nl/agent-chat.ts +1 -0
  299. package/src/mounted-registry.ts +4 -0
  300. package/src/port-transport.ts +32 -0
  301. package/src/prompts/index.ts +2 -0
  302. package/src/prompts/logic/focus.ts +5 -0
  303. package/src/prompts/logic/keys.ts +12 -0
  304. package/src/prompts/logic/prompts.ts +8 -0
  305. package/src/prompts/logic/subjects.ts +8 -0
  306. package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
  307. package/src/prompts/logic/usePromptSession.ts +13 -1
  308. package/src/prompts/ui/ApprovalBody.tsx +2 -0
  309. package/src/prompts/ui/PromptActions.tsx +2 -0
  310. package/src/prompts/ui/PromptCard.tsx +6 -0
  311. package/src/prompts/ui/PromptView.tsx +6 -0
  312. package/src/prompts/ui/QuestionBody.tsx +11 -0
  313. package/src/providers/AccountColors.tsx +1 -0
  314. package/src/providers/AccountDetail.tsx +7 -0
  315. package/src/providers/AccountVariables.tsx +2 -0
  316. package/src/providers/AddAccountForm.tsx +5 -1
  317. package/src/providers/CliDetail.tsx +6 -5
  318. package/src/providers/InstallPage.tsx +11 -0
  319. package/src/providers/ProviderList.tsx +3 -0
  320. package/src/providers/ProvidersPane.tsx +18 -0
  321. package/src/providers/account-actions.ts +10 -8
  322. package/src/providers/index.ts +1 -0
  323. package/src/providers/install-steps.ts +3 -0
  324. package/src/providers/pane-target.ts +5 -0
  325. package/src/providers/provider-abilities.ts +1 -0
  326. package/src/providers/provider-groups.ts +6 -0
  327. package/src/providers/variables.ts +12 -0
  328. package/src/scope.ts +2 -0
  329. package/src/state/chats.ts +61 -14
  330. package/src/state/index.ts +2 -0
  331. package/src/state/provider-accounts.ts +7 -9
  332. package/src/state/providers.ts +1 -0
  333. package/src/state/usage.ts +6 -0
  334. package/src/storage.ts +18 -0
  335. package/src/testing/prompts.ts +5 -0
  336. package/src/theme.css +3 -0
  337. package/src/transport.ts +18 -0
  338. package/src/usage/ChartLogo.tsx +12 -0
  339. package/src/usage/LimitsList.tsx +7 -8
  340. package/src/usage/UsageBreakdown.tsx +11 -4
  341. package/src/usage/UsageChart.tsx +5 -0
  342. package/src/usage/UsageLimits.tsx +10 -7
  343. package/src/usage/UsageLimitsCard.tsx +2 -0
  344. package/src/usage/UsagePage.tsx +16 -19
  345. package/src/usage/UsageSummary.tsx +4 -5
  346. package/src/usage/UsageTiles.tsx +2 -0
  347. package/src/usage/format.ts +7 -0
  348. package/src/usage/limit-groups.ts +14 -0
  349. package/src/usage/limits.ts +11 -13
  350. package/src/usage/money.ts +1 -0
  351. package/src/usage/summary.ts +15 -7
@@ -18,6 +18,7 @@ export function fullFileDiff(patch: string, name: string, contents: DiffContents
18
18
  newFile: { name, contents: contents.new },
19
19
  throwOnError: true
20
20
  });
21
+
21
22
  return parsed === undefined || parsed.isPartial || parsed.hunks.length === 0 ? null : parsed;
22
23
  } catch {
23
24
  return null;
@@ -5,7 +5,9 @@ import { useChatHost, type ChatHost } from '../../host';
5
5
  import { shikiThemeOf } from './code-theme';
6
6
 
7
7
  export type { Highlighter };
8
+
8
9
  export type GrammarState = ReturnType<Highlighter['getLastGrammarState']>;
10
+
9
11
  // A plain language needs no grammar; shiki draws it in the theme's own foreground.
10
12
  const PLAIN = 'text';
11
13
 
@@ -27,8 +29,10 @@ async function grammarOf(loaded: Highlighter, lang: string): Promise<string> {
27
29
  if (lang === PLAIN || loaded.getLoadedLanguages().includes(lang)) {
28
30
  return lang;
29
31
  }
32
+
30
33
  try {
31
34
  await loaded.loadLanguage(lang as BundledLanguage);
35
+
32
36
  return lang;
33
37
  } catch {
34
38
  return PLAIN;
@@ -48,8 +52,10 @@ function createCodeHighlighter(source: () => Promise<Highlighter>): CodeHighligh
48
52
  const load = (): Promise<Highlighter> => {
49
53
  highlighterLoad ??= source().then((loaded) => {
50
54
  highlighter = loaded;
55
+
51
56
  return loaded;
52
57
  });
58
+
53
59
  return highlighterLoad;
54
60
  };
55
61
 
@@ -57,11 +63,13 @@ function createCodeHighlighter(source: () => Promise<Highlighter>): CodeHighligh
57
63
  if (highlighter === null || !themes.has(theme)) {
58
64
  return null;
59
65
  }
66
+
60
67
  for (const lang of langs) {
61
68
  if (!languages.has(lang)) {
62
69
  return null;
63
70
  }
64
71
  }
72
+
65
73
  return highlighter;
66
74
  };
67
75
 
@@ -70,49 +78,63 @@ function createCodeHighlighter(source: () => Promise<Highlighter>): CodeHighligh
70
78
  return {
71
79
  loadLanguage(lang) {
72
80
  let pending = languageLoads.get(lang);
81
+
73
82
  if (!pending) {
74
83
  pending = load().then(async (loaded) => {
75
84
  languages.set(lang, await grammarOf(loaded, lang));
76
85
  });
86
+
77
87
  languageLoads.set(lang, pending);
78
88
  }
89
+
79
90
  return pending;
80
91
  },
81
92
  loadTheme(theme, custom) {
82
93
  let pending = themeLoads.get(theme);
94
+
83
95
  if (!pending) {
84
96
  pending = load().then(async (loaded) => {
85
97
  // Loading a theme the highlighter already has replaces it, under whatever else of the host draws with it.
86
98
  if (!loaded.getLoadedThemes().includes(theme)) {
87
99
  await loaded.loadTheme(shikiThemeOf(theme, custom) as BundledTheme | ThemeRegistration);
88
100
  }
101
+
89
102
  themes.add(theme);
90
103
  });
104
+
91
105
  themeLoads.set(theme, pending);
92
106
  }
107
+
93
108
  return pending;
94
109
  },
95
110
  ready,
96
111
  language,
97
112
  tokenizer(lang, theme) {
98
113
  const loaded = ready([lang], theme);
114
+
99
115
  if (loaded === null) {
100
116
  return null;
101
117
  }
118
+
102
119
  const resolved = language(lang);
103
120
  const key = `${resolved}:${theme}`;
104
121
  let entry = tokenizers.get(key);
122
+
105
123
  if (!entry) {
106
124
  const options = { lang: resolved as BundledLanguage, theme };
125
+
107
126
  entry = {
108
127
  tokenize: tokens.wrap(key, (code, state) => {
109
128
  const result = loaded.codeToTokens(code, state ? { ...options, grammarState: state } : options);
129
+
110
130
  return { lines: result.tokens, state: result.grammarState };
111
131
  }),
112
132
  fg: loaded.getTheme(theme).fg
113
133
  };
134
+
114
135
  tokenizers.set(key, entry);
115
136
  }
137
+
116
138
  return entry;
117
139
  }
118
140
  };
@@ -130,13 +152,17 @@ const borrowed = new WeakMap<() => Promise<Highlighter>, CodeHighlighter>();
130
152
  export function codeHighlighter(source?: () => Promise<Highlighter>): CodeHighlighter {
131
153
  if (source === undefined) {
132
154
  bundled ??= createCodeHighlighter(loadBundled);
155
+
133
156
  return bundled;
134
157
  }
158
+
135
159
  let highlighter = borrowed.get(source);
160
+
136
161
  if (highlighter === undefined) {
137
162
  highlighter = createCodeHighlighter(source);
138
163
  borrowed.set(source, highlighter);
139
164
  }
165
+
140
166
  return highlighter;
141
167
  }
142
168
 
@@ -155,28 +181,16 @@ export function loadTheme(theme: string, custom: ChatHost['code']['custom']): Pr
155
181
  return codeHighlighter().loadTheme(theme, custom);
156
182
  }
157
183
 
158
- /* The web bundle's highlighter once every one of `langs` and `theme` is in, or null while one is still on its way. */
159
- export function readyHighlighter(langs: Iterable<string>, theme: string): Highlighter | null {
160
- return codeHighlighter().ready(langs, theme);
161
- }
162
-
163
- /* The language the web bundle draws `lang` in: the bundled grammar, or plain text. Only meaningful once a load settled. */
164
- export function loadedLanguage(lang: string): string {
165
- return codeHighlighter().language(lang);
166
- }
167
-
168
- /* The web bundle's tokenizer for a language in a theme, or null while shiki, the grammar or the theme is still on its way. */
169
- export function tokenizerFor(lang: string, theme: string): { tokenize: Tokenize<GrammarState>; fg: string | undefined } | null {
170
- return codeHighlighter().tokenizer(lang, theme);
171
- }
172
-
173
184
  /* The inline style of one token; undefined when it only has the surrounding color. */
174
185
  export function tokenStyle(token: CodeToken): CSSProperties | undefined {
175
186
  const style = token.fontStyle ?? 0;
187
+
176
188
  if (token.color === undefined && style <= 0) {
177
189
  return undefined;
178
190
  }
191
+
179
192
  const decorations = [style & 4 ? 'underline' : '', style & 8 ? 'line-through' : ''].filter(Boolean).join(' ');
193
+
180
194
  return {
181
195
  color: token.color,
182
196
  ...(style & 1 ? { fontStyle: 'italic' } : {}),
@@ -5,10 +5,6 @@ import { Icon } from '@adecore/ui';
5
5
 
6
6
  const SIZE = 12;
7
7
 
8
- /* A pending dock draws its mark a step up from the lines of a transcript, at the size a sidebar
9
- row uses, because it is a thing to answer rather than a thing to read past. */
10
- export const DOCK_ICON_SIZE = 14;
11
-
12
8
  /* The gutter every row in the thread opens with: 16 wide, on the line box of its first row of text,
13
9
  so the mark of a tool call, a note and a fold all sit in one column and their labels start at the
14
10
  same x. The same box the menu rows and the toasts use. */
@@ -17,8 +13,10 @@ export const ROW_GUTTER = 'grid h-5 w-4 shrink-0 place-items-center';
17
13
  /* The icon that says what a tool call is about; unknown tools get the wrench. */
18
14
  export function toolIcon(name: string, size: number = SIZE): ReactNode {
19
15
  const known = toolEntry(name);
16
+
20
17
  if (known !== undefined) {
21
18
  return <Icon icon={known.icon} size={size} />;
22
19
  }
20
+
23
21
  return name.startsWith('mcp__') ? <Icon icon={Hammer} size={size} /> : <Icon icon={Wrench} size={size} />;
24
22
  }
@@ -14,6 +14,9 @@ export { ImageThumb, type ImageThumbProps } from './ImageView';
14
14
  export { LimitDock, LimitPill } from './LimitState';
15
15
  export { ReplyMarkdown, MessageMarkdown, Markdown } from './Markdown';
16
16
  export { ModelPicker } from './Pickers';
17
+ export { PromptEditor } from './PromptEditor';
18
+ export { promptPicks } from './composer/prompt-picks';
19
+ export type { PromptDraft } from './composer/use-prompt-menus';
17
20
  export { ResumeCompactionDock } from './ResumeCompactionDock';
18
21
  export { SubagentTitleCrumb, SubagentBreadcrumb } from './SubagentControls';
19
22
  export { SubagentInfo } from './SubagentInfo';
@@ -25,6 +28,7 @@ export { UploadThumb } from './UploadThumb';
25
28
  export { VisualPageFrame, type VisualPageFrameProps } from './VisualFrame';
26
29
  export { WelcomeGreeting } from './Welcome';
27
30
  export { externalChange, composerEditorExtensions } from './composer/editor';
31
+
28
32
  export {
29
33
  createDiffSyntaxRuntime,
30
34
  diffSyntaxRuntime,
@@ -35,6 +39,7 @@ export {
35
39
  type DiffSourceTokenSession,
36
40
  diffSourceTokens
37
41
  } from './diff-syntax';
42
+
38
43
  export { useDiffTheme } from './diff-theme';
39
44
  export type { UnifiedDiffProps } from './diff/diff-props';
40
45
  export { FileLinkContext, useFileLinkCwd } from './file-links';
@@ -45,6 +50,7 @@ export { UiReply } from './intelligent-ui/UiReply';
45
50
  export { uiChartData, type UiChartSeries, type UiChartValues, type UiChartData } from './intelligent-ui/chart-data';
46
51
  export { uiNodeLabel } from './intelligent-ui/node-text';
47
52
  export { UI_RENDERERS } from './intelligent-ui/registry';
53
+
48
54
  export type {
49
55
  UiRenderContext,
50
56
  UiRendererProps,
@@ -57,6 +63,7 @@ export type {
57
63
  UiRenderer,
58
64
  UiRenderers
59
65
  } from './intelligent-ui/render-context';
66
+
60
67
  export { uiTableColumns, type UiColumnKind, type UiTableColumn } from './intelligent-ui/table-data';
61
68
  export type { QuoteTaker } from './quote-selection';
62
69
  export { ReplyContext } from './reply-context';
@@ -34,14 +34,19 @@ function LivePart({ live }: { live: UiLiveStatus }) {
34
34
  const slow = useLater(live.state === 'reading', READING_SPINNER_MS);
35
35
  const { readAt } = live;
36
36
  const fresh = live.state === 'fresh' || live.state === 'reading';
37
+
37
38
  const ago = useTickingText(() => {
38
39
  if (readAt === null) {
39
40
  return '';
40
41
  }
42
+
41
43
  const time = formatAgo(Date.now() - readAt);
44
+
42
45
  return fresh ? t('blocks.live.read', { time }) : time;
43
46
  });
47
+
44
48
  const source = live.source ?? live.sources[0] ?? '';
49
+
45
50
  const icon = slow ? (
46
51
  <Spinner size={12} />
47
52
  ) : live.state === 'failed' ? (
@@ -51,14 +56,17 @@ function LivePart({ live }: { live: UiLiveStatus }) {
51
56
  ) : (
52
57
  <Icon icon={Radio} size={12} className="text-positive" />
53
58
  );
59
+
54
60
  const words =
55
61
  live.state === 'failed'
56
62
  ? t('blocks.live.failed', { source })
57
63
  : live.state === 'refused'
58
64
  ? t('blocks.live.refused', { source })
59
65
  : t('blocks.live.label');
66
+
60
67
  const reason = live.state === 'failed' || live.state === 'refused' ? uiReasonText(t, live.code, live.reason) : undefined;
61
68
  const hint = reason !== undefined ? reason : live.sources.length > 0 ? t('blocks.live.sources', { sources: live.sources.join(', ') }) : null;
69
+
62
70
  const body = (
63
71
  <span tabIndex={hint === null ? undefined : 0} className="flex items-center gap-1.5">
64
72
  {icon}
@@ -79,6 +87,7 @@ function LivePart({ live }: { live: UiLiveStatus }) {
79
87
  )}
80
88
  </span>
81
89
  );
90
+
82
91
  return hint === null ? body : <Tooltip label={hint}>{body}</Tooltip>;
83
92
  }
84
93
 
@@ -87,9 +96,11 @@ function UiBlockFooter({ live = null, answered = null, fixes = NO_FIXES, shownAs
87
96
  const { t } = useTranslation('agent-chat');
88
97
  const [fixesOpen, setFixesOpen] = useState(false);
89
98
  const listId = useId();
99
+
90
100
  if (live === null && answered === null && fixes.length === 0 && shownAsText === null) {
91
101
  return null;
92
102
  }
103
+
93
104
  return (
94
105
  <div className="flex flex-col gap-1.5 text-2xs text-text-faint">
95
106
  <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
@@ -155,34 +166,44 @@ function useRisingFloor(active: boolean): [RefObject<HTMLDivElement | null>, num
155
166
  const ref = useRef<HTMLDivElement>(null);
156
167
  const held = useRef(0);
157
168
  const [floor, setFloor] = useState(0);
169
+
158
170
  useLayoutEffect(() => {
159
171
  const element = ref.current;
172
+
160
173
  if (element === null) {
161
174
  return;
162
175
  }
176
+
163
177
  if (active) {
164
178
  const observer = new ResizeObserver(() => {
165
179
  const height = Math.ceil(element.getBoundingClientRect().height);
166
180
  held.current = Math.max(held.current, height);
167
181
  setFloor(held.current);
168
182
  });
183
+
169
184
  observer.observe(element);
185
+
170
186
  return () => {
171
187
  observer.disconnect();
172
188
  setFloor(0);
173
189
  };
174
190
  }
191
+
175
192
  // The final render already let go of the floor, so the card measures its own height here.
176
193
  const from = held.current;
177
194
  const to = element.getBoundingClientRect().height;
178
195
  held.current = 0;
179
196
  const reduced = element.ownerDocument.defaultView?.matchMedia('(prefers-reduced-motion: reduce)').matches === true;
197
+
180
198
  if (from - to < 1 || reduced) {
181
199
  return;
182
200
  }
201
+
183
202
  const release = element.animate([{ minHeight: `${from}px` }, { minHeight: `${to}px` }], { duration: RELEASE_MS, easing: 'ease-out' });
203
+
184
204
  return () => release.cancel();
185
205
  }, [active]);
206
+
186
207
  return [ref, floor];
187
208
  }
188
209
 
@@ -198,17 +219,21 @@ export function UiBlockFrame({ nodes, phase, children, className, ...footer }: U
198
219
  const streaming = phase === 'streaming';
199
220
  const head = uiBlockHead(nodes);
200
221
  const [ref, floor] = useRisingFloor(streaming);
222
+
201
223
  // Only while the focus is inside the block, so the composer keeps the same keys for sending.
202
224
  const pickPrimary = (event: KeyboardEvent<HTMLDivElement>): void => {
203
225
  if (!matchesShortcut(KEY_SHORTCUTS.modEnter, event, host.isApplePlatform())) {
204
226
  return;
205
227
  }
228
+
206
229
  const primary = event.currentTarget.querySelector<HTMLButtonElement>('[data-ui-primary-choice]');
230
+
207
231
  if (primary !== null) {
208
232
  event.preventDefault();
209
233
  primary.click();
210
234
  }
211
235
  };
236
+
212
237
  return (
213
238
  <UiHeadContext value={head?.id ?? null}>
214
239
  <div
@@ -12,6 +12,7 @@ export type UiFallbackProblem = { kind: 'unknown'; component: string } | { kind:
12
12
  */
13
13
  export function UiFallbackPart({ fallback, problem }: { fallback: string; problem: UiFallbackProblem }) {
14
14
  const { t } = useTranslation('agent-chat');
15
+
15
16
  return (
16
17
  <div className="chat-ui-flat flex flex-col gap-1.5 rounded-xl bg-ui-tile p-1.5">
17
18
  <span className="chat-ui-flat-inner flex items-center gap-1.5 px-1.5 pt-0.5 text-2xs text-text-muted">
@@ -45,15 +45,19 @@ const localStates = new UiPageMemory<UiState>(64);
45
45
 
46
46
  function localState(key: string, block: UiBlock, answered?: ChatUiAnswer): UiState {
47
47
  let state = localStates.get(key);
48
+
48
49
  if (!state) {
49
50
  state = new UiState(block);
51
+
50
52
  if (answered && answered.revision === block.revision) {
51
53
  for (const [name, value] of Object.entries(answered.values ?? {})) {
52
54
  state.set(name, value);
53
55
  }
54
56
  }
57
+
55
58
  localStates.set(key, state);
56
59
  }
60
+
57
61
  return state;
58
62
  }
59
63
 
@@ -102,6 +106,7 @@ function remarkInlineProse(this: { data(): object }) {
102
106
  // The field remark-parse reads its syntax extensions from, as remark-gfm adds its own.
103
107
  const data = this.data() as { micromarkExtensions?: unknown[] };
104
108
  data.micromarkExtensions = [...(data.micromarkExtensions ?? []), { disable: { null: BLOCK_CONSTRUCTS } }];
109
+
105
110
  return (tree: ProseTree) => {
106
111
  tree.children = [
107
112
  {
@@ -118,26 +123,35 @@ function remarkCitations() {
118
123
  if (['link', 'inlineCode', 'code'].includes(parent.type)) {
119
124
  return;
120
125
  }
126
+
121
127
  parent.children = parent.children?.flatMap((child) => {
122
128
  if (child.type !== 'text' || !child.value) {
123
129
  visit(child);
130
+
124
131
  return [child];
125
132
  }
133
+
126
134
  const parts: ProseTree[] = [];
127
135
  let start = 0;
136
+
128
137
  for (const match of child.value.matchAll(/\[(\d{1,3})\]/g)) {
129
138
  parts.push({ type: 'text', value: child.value.slice(start, match.index) });
139
+
130
140
  parts.push({
131
141
  type: 'citation',
132
142
  data: { hName: 'span', hProperties: { 'data-ui-citation': match[1] } },
133
143
  children: [{ type: 'text', value: match[0] }]
134
144
  });
145
+
135
146
  start = match.index + match[0].length;
136
147
  }
148
+
137
149
  parts.push({ type: 'text', value: child.value.slice(start) });
150
+
138
151
  return parts;
139
152
  });
140
153
  };
154
+
141
155
  visit(tree);
142
156
  };
143
157
  }
@@ -148,28 +162,35 @@ interface CitationTarget {
148
162
  url: string;
149
163
  ancestors: string[];
150
164
  }
165
+
151
166
  const CitationContext = createContext<{ sources: ReadonlyMap<number, CitationTarget>; reveal?: (source: CitationTarget) => void }>({ sources: new Map() });
152
167
 
153
168
  function ProseCitation({ number, children }: { number: number; children?: ReactNode }) {
154
169
  const { sources, reveal } = useContext(CitationContext);
155
170
  const source = sources.get(number);
171
+
156
172
  return source ? <UiSourceCitation number={number} title={source.title} url={source.url} onReveal={reveal ? () => reveal(source) : undefined} /> : children;
157
173
  }
158
174
 
159
175
  function sourceTargets(nodes: readonly UiViewNode[]): Map<number, CitationTarget> {
160
176
  const lists: CitationTarget[][] = [];
177
+
161
178
  const visit = (nodes: readonly UiViewNode[], ancestors: string[] = []) => {
162
179
  for (const node of nodes) {
163
180
  const path = ['Tab', 'Section'].includes(node.type) ? [...ancestors, node.id] : ancestors;
181
+
164
182
  if (node.type === 'Sources') {
165
183
  lists.push(
166
184
  uiChildrenOf(node, 'Source').map((source) => ({ id: source.id, title: source.props.title, url: source.props.url, ancestors: path }))
167
185
  );
168
186
  }
187
+
169
188
  visit(node.children, path);
170
189
  }
171
190
  };
191
+
172
192
  visit(nodes);
193
+
173
194
  // Numbers restart in each Sources list, so multiple lists have no unambiguous prose target.
174
195
  return new Map(lists.length === 1 ? lists[0].map((source, index) => [index + 1, source]) : []);
175
196
  }
@@ -184,9 +205,11 @@ const ProseUrlContext = createContext<((url: string) => void) | undefined>(undef
184
205
  /* Through the host only, like a Source: the address is the model's, so nothing here navigates by itself. */
185
206
  function ProseLink({ href, children }: { href?: string; children?: ReactNode }) {
186
207
  const openUrl = useContext(ProseUrlContext);
208
+
187
209
  if (openUrl === undefined || href === undefined || !/^https?:\/\//i.test(href)) {
188
210
  return children;
189
211
  }
212
+
190
213
  return (
191
214
  <Tooltip label={uiUrlDomain(href)}>
192
215
  <button type="button" className="cursor-pointer text-accent underline underline-offset-2 select-text" onClick={() => openUrl(href)}>
@@ -215,9 +238,11 @@ const PROSE_COMPONENTS: Components = {
215
238
  */
216
239
  const UiProse = memo(function UiProse({ text, fade, openUrl }: { text: string; fade: boolean; openUrl?: (url: string) => void }) {
217
240
  const [, lead = '', body = '', trail = ''] = /^(\s*)([\s\S]*?)(\s*)$/.exec(text) ?? [];
241
+
218
242
  if (body === '') {
219
243
  return text;
220
244
  }
245
+
221
246
  return (
222
247
  <ProseUrlContext value={openUrl}>
223
248
  {lead}
@@ -241,8 +266,10 @@ function writtenTexts(nodes: readonly UiNode[], into = new Map<string, boolean>(
241
266
  if (node.type === '$text') {
242
267
  into.set(node.id, node.expressions.text === undefined);
243
268
  }
269
+
244
270
  writtenTexts(node.children, into);
245
271
  }
272
+
246
273
  return into;
247
274
  }
248
275
 
@@ -250,6 +277,7 @@ function isInline(node: UiViewNode): boolean {
250
277
  if (node.type === '$text') {
251
278
  return true;
252
279
  }
280
+
253
281
  return node.error === undefined && (node.type === 'Tag' || (LINKS.has(node.type) && node.children.length === 0));
254
282
  }
255
283
 
@@ -271,16 +299,21 @@ function UiNodes({ nodes, parent, texts, context }: UiNodesProps): ReactNode {
271
299
  <UiNodeBody node={node} parent={parent} texts={texts} context={context} />
272
300
  </UiNodeBoundary>
273
301
  );
302
+
274
303
  const paragraph = PARAGRAPHS[parent];
304
+
275
305
  if (parent === 'Choices') {
276
306
  // A primary choice stands first in the order a keyboard and a screen reader meet it too, not only on screen.
277
307
  return [...nodes].sort((a, b) => Number(b.props.primary === true) - Number(a.props.primary === true)).map(drawn);
278
308
  }
309
+
279
310
  if (paragraph === undefined) {
280
311
  return nodes.map(drawn);
281
312
  }
313
+
282
314
  const output: ReactNode[] = [];
283
315
  let run: UiViewNode[] = [];
316
+
284
317
  const close = () => {
285
318
  if (run.some((node) => !isBlank(node))) {
286
319
  output.push(
@@ -289,17 +322,23 @@ function UiNodes({ nodes, parent, texts, context }: UiNodesProps): ReactNode {
289
322
  </div>
290
323
  );
291
324
  }
325
+
292
326
  run = [];
293
327
  };
328
+
294
329
  for (const node of nodes) {
295
330
  if (isInline(node)) {
296
331
  run.push(node);
332
+
297
333
  continue;
298
334
  }
335
+
299
336
  close();
300
337
  output.push(drawn(node));
301
338
  }
339
+
302
340
  close();
341
+
303
342
  return output;
304
343
  }
305
344
 
@@ -342,22 +381,28 @@ function UiNodeBody({
342
381
  }): ReactNode {
343
382
  if (node.type === '$text') {
344
383
  const text = String(node.props.text ?? '');
384
+
345
385
  return LITERAL_PARENTS.has(parent) || texts.get(node.sourceId ?? node.id) !== true ? (
346
386
  text
347
387
  ) : (
348
388
  <UiProse text={text} fade={context.phase === 'streaming'} openUrl={context.openUrl} />
349
389
  );
350
390
  }
391
+
351
392
  if (node.error !== undefined) {
352
393
  return <UiFallbackPart fallback={node.fallback} problem={{ kind: 'failed' }} />;
353
394
  }
395
+
354
396
  if (!isUiComponent(node.type)) {
355
397
  return <UiFallbackPart fallback={node.fallback} problem={{ kind: 'unknown', component: node.type }} />;
356
398
  }
399
+
357
400
  const Renderer = UI_RENDERERS[node.type] as UiRenderer<Record<string, unknown>> | null;
401
+
358
402
  if (Renderer === null) {
359
403
  return null;
360
404
  }
405
+
361
406
  return (
362
407
  <Renderer node={node} context={context}>
363
408
  <UiNodes nodes={node.children} parent={node.type} texts={texts} context={context} />
@@ -374,8 +419,10 @@ function placeReader(frozen: ChatUiQueryState['blocks'][string] | undefined, rev
374
419
  if (frozen === undefined || frozen.revision !== revision) {
375
420
  return () => (unrecorded ? null : undefined);
376
421
  }
422
+
377
423
  return (nodeId, address) => {
378
424
  const reading = Object.hasOwn(frozen.places ?? {}, nodeId) ? frozen.places![nodeId] : undefined;
425
+
379
426
  return reading?.address === address.trim() && reading.state === 'found' && reading.lat !== undefined && reading.lon !== undefined
380
427
  ? { lat: reading.lat, lon: reading.lon, label: reading.label }
381
428
  : null;
@@ -406,14 +453,17 @@ function UiBlockBody({
406
453
  const key = JSON.stringify([context.scopeId, context.chatId, context.itemId, block.id]);
407
454
  const [state] = useState(() => localState(key, block, answered));
408
455
  state.sync(block);
456
+
409
457
  const stateRevision = useSyncExternalStore(
410
458
  (listener) => state.subscribe(listener),
411
459
  () => state.snapshot(),
412
460
  () => state.snapshot()
413
461
  );
462
+
414
463
  const [optimistic, setOptimistic] = useState<{ revision: string; choiceId: string; state: 'sending' | 'sent' | 'queued' } | null>(null);
415
464
  const [failedChoiceId, setFailedChoiceId] = useState<string | null>(null);
416
465
  const pending = useRef(false);
466
+
417
467
  useEffect(() => {
418
468
  if (answered && answered.revision === block.revision && answered.values) {
419
469
  for (const [name, value] of Object.entries(answered.values)) {
@@ -421,17 +471,21 @@ function UiBlockBody({
421
471
  }
422
472
  }
423
473
  }, [state, answered, block.revision]);
474
+
424
475
  const answer =
425
476
  answered && answered.revision === block.revision
426
477
  ? { choiceId: answered.choiceId, state: answered.queued ? ('queued' as const) : ('sent' as const) }
427
478
  : optimistic?.revision === block.revision
428
479
  ? optimistic
429
480
  : null;
481
+
430
482
  const [element, setElement] = useState<HTMLDivElement | null>(null);
431
483
  const navigation = useContext(UiReplyNavigationContext);
432
484
  const flash = navigation?.flash;
485
+
433
486
  const flashing =
434
487
  navigation?.chatId === context.chatId && flash?.itemId === context.itemId && flash.blockId === block.id && flash.revision === block.revision;
488
+
435
489
  const queries = useUiQueries(block, state, context, element, frozen);
436
490
  const links = useUiLinks(block, state, context, queries.reads, frozen);
437
491
  // The state changes in place, so its revision is what tells this evaluation it went stale.
@@ -439,6 +493,7 @@ function UiBlockBody({
439
493
  const evaluation = useMemo(() => evaluateUiBlock(block, state), [block, state, stateRevision]);
440
494
  const texts = useMemo(() => writtenTexts(block.nodes), [block]);
441
495
  const sources = useMemo(() => sourceTargets(evaluation.nodes), [evaluation]);
496
+
442
497
  const citations = useMemo(
443
498
  () => ({
444
499
  sources,
@@ -450,9 +505,11 @@ function UiBlockBody({
450
505
  }),
451
506
  [sources, element]
452
507
  );
508
+
453
509
  const liveValue = useUiLiveValues(block, evaluation.nodes, queries.currentReadings);
454
510
  const sendChoice = host.intelligentUi?.sendChoice;
455
511
  const place = useMemo(() => placeReader(frozen, block.revision, unrecorded), [frozen, block.revision, unrecorded]);
512
+
456
513
  const rendering: UiRenderContext = {
457
514
  ...context,
458
515
  ...links,
@@ -470,15 +527,19 @@ function UiBlockBody({
470
527
  if (pending.current || answer || queries.reading) {
471
528
  return;
472
529
  }
530
+
473
531
  const values = uiInputValues(block, state);
474
532
  const reads = queries.readsFor(values);
533
+
475
534
  if (reads === null) {
476
535
  return;
477
536
  }
537
+
478
538
  pending.current = true;
479
539
  setFailedChoiceId(null);
480
540
  const revision = block.revision!;
481
541
  setOptimistic({ revision, choiceId, state: 'sending' });
542
+
482
543
  void sendChoice(context.scopeId, {
483
544
  chatId: context.chatId,
484
545
  itemId: context.itemId,
@@ -499,9 +560,11 @@ function UiBlockBody({
499
560
  }
500
561
  : undefined
501
562
  };
563
+
502
564
  const unknown = block.catalogVersion !== UI_CATALOG_VERSION;
503
565
  const failed = !unknown && !block.nodes.length && block.diagnostics.length > 0;
504
566
  const shownAsText = unknown ? t('blocks.unreadable') : failed ? block.diagnostics[0].message : undefined;
567
+
505
568
  return (
506
569
  <div ref={setElement} className="relative" data-ui-block={block.id} data-ui-revision={block.revision} tabIndex={-1}>
507
570
  {flashing && <span key={flash.nonce} className="chat-flash pointer-events-none absolute inset-0 rounded-lg" aria-hidden />}