@adecore/agents-react 0.39.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 (317) hide show
  1. package/dist/agents/AgentModelLabel.js +2 -1
  2. package/dist/agents/model-name.js +2 -1
  3. package/dist/agents/provider-paths.d.ts +2 -1
  4. package/dist/agents/provider-paths.js +7 -3
  5. package/dist/chat/account-choice.js +57 -28
  6. package/dist/chat/bookmarks.js +2 -1
  7. package/dist/chat/drafts.d.ts +0 -1
  8. package/dist/chat/drafts.js +9 -28
  9. package/dist/chat/logic/picker-entries.js +2 -8
  10. package/dist/chat/logic/resume-compaction.js +2 -5
  11. package/dist/chat/logic/timeline-copy.js +2 -8
  12. package/dist/chat/logic/timeline.js +9 -10
  13. package/dist/chat/logic/workflow.js +2 -4
  14. package/dist/chat/subagent-conversation.js +2 -1
  15. package/dist/chat/ui/GoalDock.js +2 -1
  16. package/dist/chat/ui/LimitState.js +4 -6
  17. package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
  18. package/dist/chat/ui/composer/use-notice.js +2 -1
  19. package/dist/chat/ui/composer/use-prompt-history.js +1 -1
  20. package/dist/chat/ui/composer/use-prompt-menus.js +216 -253
  21. package/dist/chat/ui/composer/use-take-back.js +2 -2
  22. package/dist/chat/ui/diff/ApprovalReview.js +1 -1
  23. package/dist/chat/ui/highlighter.d.ts +0 -6
  24. package/dist/chat/ui/highlighter.js +0 -12
  25. package/dist/chat/ui/icons.d.ts +0 -1
  26. package/dist/chat/ui/icons.js +0 -3
  27. package/dist/chat/ui/rows/MessageRows.js +2 -5
  28. package/dist/chat/ui/rows/row-rhythm.js +2 -5
  29. package/dist/chat/ui/theme-watch.js +3 -7
  30. package/dist/prompts/logic/usePromptSession.js +2 -1
  31. package/dist/providers/AddAccountForm.js +2 -1
  32. package/dist/providers/CliDetail.js +104 -130
  33. package/dist/providers/account-actions.js +6 -9
  34. package/dist/state/chats.d.ts +1 -0
  35. package/dist/state/chats.js +6 -11
  36. package/dist/state/provider-accounts.d.ts +1 -1
  37. package/dist/state/provider-accounts.js +2 -8
  38. package/dist/transport.d.ts +2 -0
  39. package/dist/transport.js +14 -0
  40. package/dist/usage/ChartLogo.d.ts +5 -0
  41. package/dist/usage/ChartLogo.js +48 -0
  42. package/dist/usage/LimitsList.js +6 -16
  43. package/dist/usage/UsageBreakdown.js +17 -40
  44. package/dist/usage/UsageLimits.js +183 -211
  45. package/dist/usage/UsagePage.js +3 -17
  46. package/dist/usage/UsageSummary.js +4 -9
  47. package/dist/usage/limits.js +16 -31
  48. package/dist/usage/summary.d.ts +1 -1
  49. package/dist/usage/summary.js +3 -5
  50. package/package.json +26 -26
  51. package/src/agents/AccountDot.tsx +1 -0
  52. package/src/agents/AgentIcon.tsx +2 -0
  53. package/src/agents/AgentModelLabel.tsx +4 -6
  54. package/src/agents/ModelOptionControl.tsx +8 -0
  55. package/src/agents/account-limits.ts +3 -0
  56. package/src/agents/accounts.ts +12 -0
  57. package/src/agents/model-name.ts +13 -1
  58. package/src/agents/model-options.ts +2 -0
  59. package/src/agents/model-requirement.ts +4 -0
  60. package/src/agents/option-words.ts +2 -0
  61. package/src/agents/provider-paths.ts +8 -3
  62. package/src/agents/status-look.ts +1 -0
  63. package/src/chat/account-choice.ts +25 -19
  64. package/src/chat/actions.ts +5 -0
  65. package/src/chat/attachments.ts +8 -0
  66. package/src/chat/bookmarks.ts +13 -1
  67. package/src/chat/chat-client.ts +63 -0
  68. package/src/chat/chat-references.ts +3 -0
  69. package/src/chat/composer-effort.ts +5 -0
  70. package/src/chat/composer-send.ts +4 -0
  71. package/src/chat/drafts.ts +21 -17
  72. package/src/chat/forks.ts +4 -0
  73. package/src/chat/guards.ts +2 -0
  74. package/src/chat/index.ts +7 -0
  75. package/src/chat/logic/ansi.ts +47 -0
  76. package/src/chat/logic/bookmarks.ts +7 -0
  77. package/src/chat/logic/command-looks.ts +29 -0
  78. package/src/chat/logic/context-usage.ts +3 -0
  79. package/src/chat/logic/continue-after-stop.ts +5 -0
  80. package/src/chat/logic/effort.ts +4 -0
  81. package/src/chat/logic/embedded-code.ts +63 -0
  82. package/src/chat/logic/fork.ts +22 -0
  83. package/src/chat/logic/generated-image.ts +7 -0
  84. package/src/chat/logic/goal.ts +7 -0
  85. package/src/chat/logic/handback.ts +2 -0
  86. package/src/chat/logic/limit.ts +8 -0
  87. package/src/chat/logic/picker-entries.ts +16 -4
  88. package/src/chat/logic/resume-compaction.ts +22 -5
  89. package/src/chat/logic/scrubber.ts +38 -5
  90. package/src/chat/logic/thought.ts +5 -0
  91. package/src/chat/logic/thread-cards.ts +6 -0
  92. package/src/chat/logic/timeline-copy.ts +4 -9
  93. package/src/chat/logic/timeline-target.ts +15 -0
  94. package/src/chat/logic/timeline.ts +113 -10
  95. package/src/chat/logic/tools.ts +30 -0
  96. package/src/chat/logic/visual-height.ts +5 -0
  97. package/src/chat/logic/visual-viewport.ts +1 -0
  98. package/src/chat/logic/welcome.ts +3 -0
  99. package/src/chat/logic/workflow.ts +26 -7
  100. package/src/chat/mentions.ts +56 -0
  101. package/src/chat/pending-prompts.ts +16 -0
  102. package/src/chat/persisted-json.ts +15 -0
  103. package/src/chat/preferences.ts +16 -0
  104. package/src/chat/prompt-hold.ts +2 -0
  105. package/src/chat/quote.ts +7 -0
  106. package/src/chat/reading-width.ts +1 -0
  107. package/src/chat/recent-messages.ts +5 -0
  108. package/src/chat/resume-compaction-dismissals.ts +1 -0
  109. package/src/chat/stash.ts +12 -0
  110. package/src/chat/subagent-conversation.ts +24 -4
  111. package/src/chat/subagent-list.ts +14 -0
  112. package/src/chat/subagent-support.ts +1 -0
  113. package/src/chat/subagent-view.ts +9 -0
  114. package/src/chat/thumbnails.ts +14 -0
  115. package/src/chat/timeline-flash.ts +2 -0
  116. package/src/chat/timeline-scroll.ts +23 -0
  117. package/src/chat/ui/AccountPill.tsx +4 -0
  118. package/src/chat/ui/AnsiOutput.tsx +1 -0
  119. package/src/chat/ui/BookmarkMarker.tsx +5 -0
  120. package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
  121. package/src/chat/ui/ChatActivity.tsx +13 -0
  122. package/src/chat/ui/CodeBlock.tsx +8 -0
  123. package/src/chat/ui/Composer.tsx +15 -0
  124. package/src/chat/ui/ComposerInput.tsx +22 -0
  125. package/src/chat/ui/DiffPool.tsx +3 -0
  126. package/src/chat/ui/EditDiff.tsx +5 -0
  127. package/src/chat/ui/GoalDock.tsx +13 -1
  128. package/src/chat/ui/ImageView.tsx +11 -0
  129. package/src/chat/ui/LimitState.tsx +23 -6
  130. package/src/chat/ui/Markdown.tsx +41 -0
  131. package/src/chat/ui/MermaidBlock.tsx +16 -0
  132. package/src/chat/ui/MessageActions.tsx +8 -0
  133. package/src/chat/ui/ModelMarks.tsx +1 -0
  134. package/src/chat/ui/Pickers.tsx +26 -0
  135. package/src/chat/ui/PromptComposer.tsx +2 -0
  136. package/src/chat/ui/PromptEditor.tsx +6 -0
  137. package/src/chat/ui/QuoteButton.tsx +12 -0
  138. package/src/chat/ui/ReferenceChip.tsx +3 -0
  139. package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
  140. package/src/chat/ui/RowTime.tsx +1 -0
  141. package/src/chat/ui/RunSettings.tsx +38 -0
  142. package/src/chat/ui/Scrubber.tsx +14 -0
  143. package/src/chat/ui/SessionLine.tsx +1 -0
  144. package/src/chat/ui/ShellCommand.tsx +32 -0
  145. package/src/chat/ui/SubagentControls.tsx +7 -0
  146. package/src/chat/ui/SubagentInfo.tsx +1 -0
  147. package/src/chat/ui/SubagentStopButton.tsx +6 -0
  148. package/src/chat/ui/SubagentTimeline.tsx +10 -0
  149. package/src/chat/ui/Timeline.tsx +119 -0
  150. package/src/chat/ui/TimelineMenu.tsx +5 -0
  151. package/src/chat/ui/UnifiedDiff.tsx +6 -0
  152. package/src/chat/ui/UploadThumb.tsx +2 -0
  153. package/src/chat/ui/VisualDialogs.tsx +3 -0
  154. package/src/chat/ui/VisualFrame.tsx +15 -0
  155. package/src/chat/ui/Welcome.tsx +1 -0
  156. package/src/chat/ui/chip-clipboard.ts +20 -0
  157. package/src/chat/ui/code-lines.ts +14 -0
  158. package/src/chat/ui/code-theme.ts +1 -0
  159. package/src/chat/ui/composer/ComposerControls.tsx +5 -0
  160. package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
  161. package/src/chat/ui/composer/DraftParts.tsx +2 -0
  162. package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
  163. package/src/chat/ui/composer/chip-paste.ts +16 -0
  164. package/src/chat/ui/composer/chips.ts +6 -0
  165. package/src/chat/ui/composer/editor.ts +9 -0
  166. package/src/chat/ui/composer/insert.ts +2 -0
  167. package/src/chat/ui/composer/key-handler.ts +39 -0
  168. package/src/chat/ui/composer/keys.ts +23 -0
  169. package/src/chat/ui/composer/placeholder.ts +6 -0
  170. package/src/chat/ui/composer/prompt-picks.ts +1 -0
  171. package/src/chat/ui/composer/surface.ts +5 -0
  172. package/src/chat/ui/composer/use-chat-controls.ts +13 -0
  173. package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
  174. package/src/chat/ui/composer/use-composer-menus.ts +2 -0
  175. package/src/chat/ui/composer/use-composer-submit.ts +27 -0
  176. package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
  177. package/src/chat/ui/composer/use-draft-files.ts +24 -0
  178. package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
  179. package/src/chat/ui/composer/use-notice.ts +4 -1
  180. package/src/chat/ui/composer/use-pending-requests.ts +3 -0
  181. package/src/chat/ui/composer/use-prompt-history.ts +20 -11
  182. package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
  183. package/src/chat/ui/composer/use-prompt-menus.ts +34 -14
  184. package/src/chat/ui/composer/use-take-back.ts +20 -2
  185. package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
  186. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
  187. package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
  188. package/src/chat/ui/diff/DiffStack.tsx +2 -0
  189. package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
  190. package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
  191. package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
  192. package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
  193. package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
  194. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
  195. package/src/chat/ui/diff/RawDiff.tsx +1 -0
  196. package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
  197. package/src/chat/ui/diff/diff-source-budget.ts +8 -0
  198. package/src/chat/ui/diff/editor-edit-model.ts +4 -0
  199. package/src/chat/ui/diff/review-plan.ts +9 -0
  200. package/src/chat/ui/diff/review-settle.ts +1 -0
  201. package/src/chat/ui/diff/syntax-runtime.ts +52 -0
  202. package/src/chat/ui/diff/use-review-stack.ts +9 -0
  203. package/src/chat/ui/diff-syntax.ts +12 -0
  204. package/src/chat/ui/diff-theme.ts +2 -0
  205. package/src/chat/ui/file-links.ts +2 -0
  206. package/src/chat/ui/find-reveal.ts +1 -0
  207. package/src/chat/ui/full-diff.ts +1 -0
  208. package/src/chat/ui/highlighter.ts +29 -15
  209. package/src/chat/ui/icons.tsx +2 -4
  210. package/src/chat/ui/index.ts +4 -0
  211. package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
  212. package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
  213. package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
  214. package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
  215. package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
  216. package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
  217. package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
  218. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
  219. package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
  220. package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
  221. package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
  222. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
  223. package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
  224. package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
  225. package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
  226. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
  227. package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
  228. package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
  229. package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
  230. package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
  231. package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
  232. package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
  233. package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
  234. package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
  235. package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
  236. package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
  237. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
  238. package/src/chat/ui/markdown-blocks.ts +64 -0
  239. package/src/chat/ui/mermaid.ts +4 -0
  240. package/src/chat/ui/quote-selection.ts +10 -0
  241. package/src/chat/ui/rehype-chips.ts +5 -0
  242. package/src/chat/ui/rehype-fade.ts +2 -0
  243. package/src/chat/ui/remark-html-as-text.ts +5 -0
  244. package/src/chat/ui/reveal.ts +18 -0
  245. package/src/chat/ui/rows/ForksRow.tsx +5 -0
  246. package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
  247. package/src/chat/ui/rows/MessageRows.tsx +29 -5
  248. package/src/chat/ui/rows/RowContainer.tsx +1 -0
  249. package/src/chat/ui/rows/Rows.tsx +2 -0
  250. package/src/chat/ui/rows/SubagentRow.tsx +13 -0
  251. package/src/chat/ui/rows/TaskRow.tsx +7 -0
  252. package/src/chat/ui/rows/VisualRow.tsx +4 -0
  253. package/src/chat/ui/rows/WorkRows.tsx +46 -0
  254. package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
  255. package/src/chat/ui/rows/row-rhythm.ts +7 -5
  256. package/src/chat/ui/theme-watch.ts +8 -7
  257. package/src/chat/ui/thread-references.ts +7 -0
  258. package/src/chat/ui/use-diff-source-tokens.ts +8 -0
  259. package/src/chat/ui/useToggleSet.ts +3 -0
  260. package/src/chat/ui/visual-theme.ts +30 -0
  261. package/src/chat/visual-bridge.ts +23 -0
  262. package/src/chat/visual-viewport.ts +12 -0
  263. package/src/host-hooks.ts +6 -4
  264. package/src/host.ts +2 -0
  265. package/src/index.ts +3 -0
  266. package/src/lazy.tsx +9 -0
  267. package/src/mounted-registry.ts +4 -0
  268. package/src/port-transport.ts +32 -0
  269. package/src/prompts/index.ts +2 -0
  270. package/src/prompts/logic/focus.ts +5 -0
  271. package/src/prompts/logic/keys.ts +12 -0
  272. package/src/prompts/logic/prompts.ts +8 -0
  273. package/src/prompts/logic/subjects.ts +8 -0
  274. package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
  275. package/src/prompts/logic/usePromptSession.ts +13 -1
  276. package/src/prompts/ui/ApprovalBody.tsx +2 -0
  277. package/src/prompts/ui/PromptActions.tsx +2 -0
  278. package/src/prompts/ui/PromptCard.tsx +6 -0
  279. package/src/prompts/ui/PromptView.tsx +6 -0
  280. package/src/prompts/ui/QuestionBody.tsx +11 -0
  281. package/src/providers/AccountColors.tsx +1 -0
  282. package/src/providers/AccountDetail.tsx +7 -0
  283. package/src/providers/AccountVariables.tsx +2 -0
  284. package/src/providers/AddAccountForm.tsx +5 -1
  285. package/src/providers/CliDetail.tsx +6 -5
  286. package/src/providers/InstallPage.tsx +11 -0
  287. package/src/providers/ProviderList.tsx +3 -0
  288. package/src/providers/ProvidersPane.tsx +18 -0
  289. package/src/providers/account-actions.ts +10 -8
  290. package/src/providers/install-steps.ts +3 -0
  291. package/src/providers/pane-target.ts +5 -0
  292. package/src/providers/provider-abilities.ts +1 -0
  293. package/src/providers/provider-groups.ts +6 -0
  294. package/src/providers/variables.ts +12 -0
  295. package/src/scope.ts +2 -0
  296. package/src/state/chats.ts +61 -14
  297. package/src/state/index.ts +2 -0
  298. package/src/state/provider-accounts.ts +7 -9
  299. package/src/state/providers.ts +1 -0
  300. package/src/state/usage.ts +6 -0
  301. package/src/storage.ts +18 -0
  302. package/src/testing/prompts.ts +5 -0
  303. package/src/transport.ts +18 -0
  304. package/src/usage/ChartLogo.tsx +12 -0
  305. package/src/usage/LimitsList.tsx +7 -8
  306. package/src/usage/UsageBreakdown.tsx +11 -4
  307. package/src/usage/UsageChart.tsx +5 -0
  308. package/src/usage/UsageLimits.tsx +10 -7
  309. package/src/usage/UsageLimitsCard.tsx +2 -0
  310. package/src/usage/UsagePage.tsx +16 -19
  311. package/src/usage/UsageSummary.tsx +4 -5
  312. package/src/usage/UsageTiles.tsx +2 -0
  313. package/src/usage/format.ts +7 -0
  314. package/src/usage/limit-groups.ts +14 -0
  315. package/src/usage/limits.ts +11 -13
  316. package/src/usage/money.ts +1 -0
  317. package/src/usage/summary.ts +15 -7
@@ -17,15 +17,18 @@ export class UiPageMemory<Value> {
17
17
  /* Reading a value counts as using it. */
18
18
  get(key: string): Value | undefined {
19
19
  const value = this.entries.get(key);
20
+
20
21
  if (value !== undefined) {
21
22
  this.set(key, value);
22
23
  }
24
+
23
25
  return value;
24
26
  }
25
27
 
26
28
  set(key: string, value: Value): void {
27
29
  this.entries.delete(key);
28
30
  this.entries.set(key, value);
31
+
29
32
  if (this.entries.size > this.limit) {
30
33
  this.entries.delete(this.entries.keys().next().value!);
31
34
  }
@@ -43,9 +46,11 @@ const remembered = new UiPageMemory<unknown>(512);
43
46
  export function useBlockLocal<Value>(context: UiRenderContext, nodeId: string, initial: () => Value): [Value, (next: Value) => void] {
44
47
  const key = `${context.scopeId}\n${context.chatId}\n${context.itemId}\n${context.blockId}\n${nodeId}`;
45
48
  const [value, setValue] = useState<Value>(() => (remembered.has(key) ? (remembered.get(key) as Value) : initial()));
49
+
46
50
  const set = (next: Value): void => {
47
51
  remembered.set(key, next);
48
52
  setValue(next);
49
53
  };
54
+
50
55
  return [value, set];
51
56
  }
@@ -36,6 +36,7 @@ export interface UiChartData extends UiChartValues {
36
36
 
37
37
  function numericKeys(rows: readonly ChartRow[]): string[] {
38
38
  const keys: string[] = [];
39
+
39
40
  for (const row of rows) {
40
41
  for (const [key, value] of Object.entries(row)) {
41
42
  if (key !== 'label' && !keys.includes(key) && typeof value === 'number' && Number.isFinite(value)) {
@@ -43,6 +44,7 @@ function numericKeys(rows: readonly ChartRow[]): string[] {
43
44
  }
44
45
  }
45
46
  }
47
+
46
48
  return keys;
47
49
  }
48
50
 
@@ -53,6 +55,7 @@ function valuesOf(rows: readonly ChartRow[], keys: readonly string[]): UiChartVa
53
55
  key,
54
56
  values: rows.map((row) => {
55
57
  const value = row[key];
58
+
56
59
  return typeof value === 'number' && Number.isFinite(value) ? value : null;
57
60
  })
58
61
  }))
@@ -72,6 +75,7 @@ export function uiChartRange(min: number, max: number): { low: number; high: num
72
75
  const low = min < 0 ? -niceCeiling(-min) : 0;
73
76
  const high = max > 0 ? niceCeiling(max) : low === 0 ? 1 : 0;
74
77
  const span = high - low;
78
+
75
79
  return span > 0 && Number.isFinite(span) && Number.isFinite(1 / span) ? { low, high } : null;
76
80
  }
77
81
 
@@ -83,21 +87,27 @@ export function uiChartRange(min: number, max: number): { low: number; high: num
83
87
  export function uiChartData(data: UiProps<'Chart'>['data'], kind: ChartKind): UiChartData | null {
84
88
  const rows = data.slice(0, UI_CHART_ROWS);
85
89
  const keys = numericKeys(rows);
90
+
86
91
  if (rows.length === 0 || keys.length === 0) {
87
92
  return null;
88
93
  }
94
+
89
95
  const { labels, series } = valuesOf(rows, keys.slice(0, UI_CHART_SERIES));
90
96
  const negative = series.some(({ values }) => values.some((value) => value !== null && value < 0));
91
97
  const drawn = kind === 'stacked' && negative ? 'bar' : kind;
98
+
92
99
  const extents =
93
100
  drawn === 'stacked'
94
101
  ? labels.map((_, index) => series.reduce((sum, { values }) => sum + (values[index] ?? 0), 0))
95
102
  : series.flatMap(({ values }) => values.filter((value) => value !== null));
103
+
96
104
  const min = Math.min(0, ...extents);
97
105
  const max = Math.max(0, ...extents);
106
+
98
107
  if (uiChartRange(min, max) === null) {
99
108
  return null;
100
109
  }
110
+
101
111
  return {
102
112
  labels,
103
113
  series,
@@ -112,6 +122,7 @@ export function uiChartData(data: UiProps<'Chart'>['data'], kind: ChartKind): Ui
112
122
 
113
123
  /* The width of one character of a chart's type at a 16px root, an estimate since SVG text cannot wrap or be measured before it draws. */
114
124
  export const UI_CHART_CHARACTER = 7.5;
125
+
115
126
  /* The narrowest a value axis is, and the gap between a tick label and the plot. */
116
127
  const AXIS_WIDTH = 44;
117
128
  const AXIS_GAP = 8;
@@ -128,6 +139,7 @@ export function uiChartTextWidth(text: string, character = UI_CHART_CHARACTER):
128
139
  /* Text cut to the room it has, so a label never reaches past its place; the whole of it is in the values. */
129
140
  export function uiChartText(text: string, room: number, character = UI_CHART_CHARACTER): string {
130
141
  const fit = Math.floor(room / character);
142
+
131
143
  return text.length <= fit ? text : `${text.slice(0, Math.max(1, fit - 1))}…`;
132
144
  }
133
145
 
@@ -136,6 +148,7 @@ export function uiChartAxis(ticks: readonly string[], width: number, character =
136
148
  const scale = character / UI_CHART_CHARACTER;
137
149
  const room = Math.max(AXIS_WIDTH * scale, Math.floor(width * 0.4));
138
150
  const tickTexts = ticks.map((tick) => uiChartText(tick, room - AXIS_GAP, character));
151
+
139
152
  return {
140
153
  axisWidth: Math.round(Math.min(room, Math.max(AXIS_WIDTH * scale, ...tickTexts.map((text) => uiChartTextWidth(text, character) + AXIS_GAP)))),
141
154
  tickTexts
@@ -147,7 +160,9 @@ export function niceCeiling(value: number): number {
147
160
  if (value <= 0) {
148
161
  return 0;
149
162
  }
163
+
150
164
  const power = 10 ** Math.floor(Math.log10(value));
151
165
  const step = [1, 2, 2.5, 5, 10].find((factor) => factor * power >= value) ?? 10;
166
+
152
167
  return step * power;
153
168
  }
@@ -39,9 +39,11 @@ function withoutMarks(text: string): string {
39
39
  /* The Summary that heads a block, which is its first node; a Summary further down is a subheading. */
40
40
  export function uiBlockHead(nodes: readonly UiViewNode[]): { id: string; label: string } | null {
41
41
  const first = nodes[0];
42
+
42
43
  if (first === undefined || first.type !== 'Summary' || first.error !== undefined) {
43
44
  return null;
44
45
  }
46
+
45
47
  return { id: first.id, label: withoutMarks(uiNodeLabel(first)) };
46
48
  }
47
49
 
@@ -52,22 +54,28 @@ export function uiBlockHead(nodes: readonly UiViewNode[]): { id: string; label:
52
54
  export async function revealUiSource(root: ParentNode, nodeId: string, ancestors: readonly string[] = []): Promise<boolean> {
53
55
  for (const id of ancestors) {
54
56
  const control = [...root.querySelectorAll<HTMLButtonElement>('[data-ui-reveal]')].find((element) => element.dataset.uiReveal === id);
57
+
55
58
  if (control && (control.getAttribute('aria-expanded') === 'false' || control.getAttribute('aria-selected') === 'false')) {
56
59
  control.click();
57
60
  await new Promise<void>((resolve) => setTimeout(resolve, 0));
58
61
  }
59
62
  }
63
+
60
64
  const row = [...root.querySelectorAll<HTMLElement>('[data-ui-source]')].find((element) => element.dataset.uiSource === nodeId);
65
+
61
66
  if (row === undefined) {
62
67
  return false;
63
68
  }
69
+
64
70
  // The light runs when focus arrives, so a row that already has it lets go first.
65
71
  if (row.ownerDocument.activeElement === row) {
66
72
  row.blur();
67
73
  }
74
+
68
75
  row.focus({ preventScroll: true });
69
76
  const reduced = row.ownerDocument.defaultView?.matchMedia('(prefers-reduced-motion: reduce)').matches === true;
70
77
  row.scrollIntoView({ block: 'nearest', behavior: reduced ? 'auto' : 'smooth' });
78
+
71
79
  return true;
72
80
  }
73
81
 
@@ -105,6 +113,7 @@ export function uiWithUnit(text: string, unit: string | undefined): string {
105
113
  if (unit === undefined || unit === '') {
106
114
  return text;
107
115
  }
116
+
108
117
  return unit === '%' ? `${text}${unit}` : `${text}\u00a0${unit}`;
109
118
  }
110
119
 
@@ -113,10 +122,13 @@ export function uiLiveChoiceReason(t: TFunction<'agent-chat'>, live: UiLiveStatu
113
122
  if (live === null || live === undefined || live.state === 'fresh') {
114
123
  return null;
115
124
  }
125
+
116
126
  if (live.state === 'reading') {
117
127
  return t('blocks.choice.reading');
118
128
  }
129
+
119
130
  const source = live.source ?? live.sources[0] ?? '';
131
+
120
132
  return uiReasonText(t, live.code, live.reason) ?? t(live.state === 'failed' ? 'blocks.live.failed' : 'blocks.live.refused', { source });
121
133
  }
122
134
 
@@ -136,19 +148,24 @@ export function uiUrlDomain(url: string): string {
136
148
  */
137
149
  export function uiEventTime(at: string, now: Date | number = Date.now()): string {
138
150
  const moment = parseUiEventTime(at);
151
+
139
152
  if (moment === null) {
140
153
  return at;
141
154
  }
155
+
142
156
  const zone = moment.written ? 'UTC' : undefined;
143
157
  const year = moment.written ? moment.date.getUTCFullYear() : moment.date.getFullYear();
144
158
  const withYear = year !== new Date(now).getFullYear();
159
+
145
160
  if (moment.kind === 'time') {
146
161
  return moment.seconds
147
162
  ? formatDateTime(moment.date, { hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone: 'UTC' })
148
163
  : formatClock(moment.date, zone);
149
164
  }
165
+
150
166
  if (moment.kind === 'date') {
151
167
  return withYear ? formatDayWithYear(moment.date, zone) : formatDay(moment.date, zone);
152
168
  }
169
+
153
170
  return withYear ? formatDayClockWithYear(moment.date, zone) : formatDayClock(moment.date, zone);
154
171
  }
@@ -7,6 +7,7 @@ import type { UiRenderContext } from '../render-context';
7
7
  */
8
8
  export function bindingOf<Value>(node: UiViewNode<{ value?: Value }>, context: UiRenderContext): UiBinding<Value> | null {
9
9
  const binding = node.bindings.value as UiBinding<Value> | undefined;
10
+
10
11
  return binding === undefined || !node.complete || context.answer !== null ? null : binding;
11
12
  }
12
13
 
@@ -57,13 +57,16 @@ function verticalLayout(chart: UiChartData, unit: string | undefined, width: num
57
57
  const { low, high } = uiChartRange(chart.min, chart.max)!;
58
58
  const ticks = low === 0 ? [0, high / 2, high] : [low, 0, high];
59
59
  const tickUnit = unitApart(unit) ? undefined : unit;
60
+
60
61
  const { axisWidth, tickTexts } = uiChartAxis(
61
62
  ticks.map((tick) => valueText(tick, tickUnit)),
62
63
  width,
63
64
  character
64
65
  );
66
+
65
67
  const band = Math.max(0, width - axisWidth) / chart.labels.length;
66
68
  const labelEvery = Math.max(1, Math.ceil(LABEL_BAND / Math.max(1, band)));
69
+
67
70
  return { low, high, ticks, tickTexts, axisWidth, band, labelEvery, labelRoom: Math.min(band * labelEvery, Math.max(0, width - axisWidth)) };
68
71
  }
69
72
 
@@ -72,7 +75,9 @@ function labelsCut(chart: UiChartData, unit: string | undefined, width: number,
72
75
  if (width <= 0 || chart.kind === 'hbar' || chart.kind === 'stacked') {
73
76
  return false;
74
77
  }
78
+
75
79
  const { labelRoom } = verticalLayout(chart, unit, width, character);
80
+
76
81
  return chart.labels.some((label) => uiChartTextWidth(label, character) > labelRoom);
77
82
  }
78
83
 
@@ -81,29 +86,35 @@ function useChartCharacter(): number {
81
86
  const [character] = useState(() =>
82
87
  typeof document === 'undefined' ? UI_CHART_CHARACTER : uiChartCharacter(Number.parseFloat(getComputedStyle(document.documentElement).fontSize))
83
88
  );
89
+
84
90
  return character;
85
91
  }
86
92
 
87
93
  /* The height a chart will take, which its place holds while the node streams. */
88
94
  function chartHeight(kind: ChartKind, rows: number): number {
89
95
  const count = Math.max(1, Math.min(rows, UI_CHART_ROWS));
96
+
90
97
  return kind === 'hbar' ? count * HBAR_ROW : kind === 'stacked' ? count * STACKED_ROW : HEIGHT;
91
98
  }
92
99
 
93
100
  /* One sentence with the main point over all of the data, drawn or not, which a screen reader hears in place of the drawing. */
94
101
  function chartSummary(values: UiChartValues, unit: string | undefined, t: TFunction<'agent-chat'>): string {
95
102
  let top: { label: string; series: string; value: number } | null = null;
103
+
96
104
  for (const series of values.series) {
97
105
  for (let i = 0; i < series.values.length; i++) {
98
106
  const value = series.values[i];
107
+
99
108
  if (value !== null && value !== undefined && (top === null || value > top.value)) {
100
109
  top = { label: values.labels[i] ?? '', series: series.key, value };
101
110
  }
102
111
  }
103
112
  }
113
+
104
114
  if (top === null) {
105
115
  return t('blocks.chart.values');
106
116
  }
117
+
107
118
  return values.series.length > 1
108
119
  ? t('blocks.chart.summarySeries', { count: values.labels.length, series: top.series, label: top.label, value: valueText(top.value, unit) })
109
120
  : t('blocks.chart.summary', { count: values.labels.length, label: top.label, value: valueText(top.value, unit) });
@@ -125,6 +136,7 @@ function Legend({ chart }: { chart: UiChartData }) {
125
136
  /* Every number of the data, for a screen reader, and on screen once a person opens it. */
126
137
  function ValuesTable({ values, unit, shown }: { values: UiChartValues; unit: string | undefined; shown: boolean }) {
127
138
  const { t } = useTranslation('agent-chat');
139
+
128
140
  const table = (
129
141
  <table className={shown ? 'w-full border-collapse text-xs tabular-nums' : undefined}>
130
142
  <caption className={shown ? 'sr-only' : undefined}>{t('blocks.chart.values')}</caption>
@@ -154,6 +166,7 @@ function ValuesTable({ values, unit, shown }: { values: UiChartValues; unit: str
154
166
  </tbody>
155
167
  </table>
156
168
  );
169
+
157
170
  // A table grows to its widest cell whatever width it is given, so it is hidden inside a block that clips.
158
171
  return <div className={shown ? 'max-h-80 w-full overflow-auto rounded-md border border-border-soft' : 'sr-only'}>{table}</div>;
159
172
  }
@@ -195,13 +208,19 @@ function VerticalChart({
195
208
  const [hover, setHover] = useState<number | null>(null);
196
209
  const { low, high, ticks, tickTexts, axisWidth, band, labelEvery, labelRoom } = verticalLayout(chart, unit, width, character);
197
210
  const plotHeight = HEIGHT - PLOT_TOP - Math.round((LABEL_ROOM * character) / UI_CHART_CHARACTER);
211
+
198
212
  const y = (value: number): number => Math.round(PLOT_TOP + ((high - value) / (high - low)) * plotHeight);
213
+
199
214
  const baseline = y(0);
215
+
200
216
  const center = (index: number): number => Math.round(axisWidth + band * index + band / 2);
217
+
201
218
  const gap = band * 0.2;
202
219
  const barWidth = Math.max(1, (band - gap) / chart.series.length);
220
+
203
221
  const labelAt = (index: number, text: string): number => {
204
222
  const half = uiChartTextWidth(text, character) / 2;
223
+
205
224
  return Math.round(Math.min(Math.max(center(index), axisWidth + half), width - half));
206
225
  };
207
226
 
@@ -212,6 +231,7 @@ function VerticalChart({
212
231
  if (point !== null) {
213
232
  paths.push(`${index === 0 || all[index - 1] === null ? 'M' : 'L'}${point}`);
214
233
  }
234
+
215
235
  return paths;
216
236
  }, [])
217
237
  .join(' ');
@@ -241,7 +261,9 @@ function VerticalChart({
241
261
  if (index % labelEvery !== 0) {
242
262
  return null;
243
263
  }
264
+
244
265
  const text = uiChartText(label, labelRoom, character);
266
+
245
267
  return (
246
268
  <text key={index} x={labelAt(index, text)} y={HEIGHT - 4} textAnchor="middle" className="fill-text-faint text-xs">
247
269
  {text}
@@ -310,6 +332,7 @@ function HorizontalChart({ chart, unit }: { chart: UiChartData; unit: string | u
310
332
  const span = high - low;
311
333
  const zero = (-low / span) * 100;
312
334
  const single = chart.series.length === 1;
335
+
313
336
  return (
314
337
  <div aria-hidden className="flex flex-col">
315
338
  {chart.labels.map((label, index) => (
@@ -319,6 +342,7 @@ function HorizontalChart({ chart, unit }: { chart: UiChartData; unit: string | u
319
342
  {low < 0 && <span className="absolute inset-y-0 w-px bg-border" style={{ left: `${zero}%` }} />}
320
343
  {chart.series.map((series, at) => {
321
344
  const value = series.values[index] ?? 0;
345
+
322
346
  return (
323
347
  <span key={series.key} className={clsx('relative block', single ? 'h-3' : 'h-1')}>
324
348
  <span
@@ -389,14 +413,17 @@ function ChartNotes({
389
413
  }) {
390
414
  const { t } = useTranslation('agent-chat');
391
415
  const [shown, setShown] = useBlockLocal(context, `${nodeId}:values`, () => false);
416
+
392
417
  const notes = [
393
418
  ...(chart.truncated ? [t('blocks.chart.rowsLeftOut', { shown: formatNumber(chart.labels.length), total: formatNumber(chart.total) })] : []),
394
419
  ...(chart.omitted.length > 0 ? [t('blocks.chart.seriesLeftOut', { count: chart.omitted.length, series: chart.omitted.join(', ') })] : []),
395
420
  ...(requested === 'stacked' && chart.kind !== 'stacked' ? [t('blocks.chart.sideBySide')] : [])
396
421
  ];
422
+
397
423
  const partial = chart.truncated || chart.omitted.length > 0 || cut;
398
424
  const [measure, truncated] = useTruncated<HTMLSpanElement>();
399
425
  const toggle = shown ? t('blocks.chart.hideValues') : t('blocks.chart.showValues');
426
+
400
427
  return (
401
428
  <>
402
429
  {notes.length > 0 && (
@@ -441,6 +468,7 @@ function DrawnChart({
441
468
  const [measureRows, rowsCut] = useTruncated<HTMLDivElement>(true);
442
469
  const values = uiChartValues(data);
443
470
  const { kind } = chart;
471
+
444
472
  return (
445
473
  <figure className="chat-ui-flat flex flex-col gap-2.5 rounded-xl bg-ui-tile p-3.5">
446
474
  {chart.series.length > 1 && <Legend chart={chart} />}
@@ -474,11 +502,14 @@ function DrawnChart({
474
502
  export function ChartRenderer({ node, context }: UiRendererProps<UiProps<'Chart'>>) {
475
503
  const { kind, data, unit } = node.props;
476
504
  const chart = node.complete ? uiChartData(data, kind) : null;
505
+
477
506
  if (!node.complete) {
478
507
  return <div aria-hidden className="rounded-xl bg-ui-tile" style={{ height: chartHeight(kind, data.length) }} />;
479
508
  }
509
+
480
510
  if (chart === null) {
481
511
  return <UiFallbackPart fallback={node.fallback} problem={{ kind: 'failed' }} />;
482
512
  }
513
+
483
514
  return <DrawnChart chart={chart} requested={kind} data={data} unit={unit} context={context} nodeId={node.id} />;
484
515
  }
@@ -17,6 +17,7 @@ const SPINNER_AFTER_MS = 300;
17
17
  export function ChoicesRenderer({ children, context }: UiRendererProps<UiProps<'Choices'>>) {
18
18
  const { t } = useTranslation('agent-chat');
19
19
  const [streamed] = useState(context.phase === 'streaming');
20
+
20
21
  return (
21
22
  <div role="group" aria-label={t('blocks.choices')} className="chat-ui-nodes @container flex flex-wrap items-start gap-2">
22
23
  {children}
@@ -46,14 +47,17 @@ export function ChoiceRenderer({ node, children, context }: UiRendererProps<UiPr
46
47
  const failed = answer === null && context.failedChoiceId === node.id;
47
48
  const closed = waiting || unavailable || answer !== null;
48
49
  const slow = useLater(chosen && answer.state === 'sending', SPINNER_AFTER_MS);
50
+
49
51
  if (!waiting && uiChoiceLabel(node) === null) {
50
52
  return <UiFallbackPart fallback={node.fallback} problem={{ kind: 'failed' }} />;
51
53
  }
54
+
52
55
  const reason = waiting
53
56
  ? t('blocks.choice.waiting')
54
57
  : answer === null && unavailable
55
58
  ? ((disabled === true ? null : context.choiceReason) ?? t('blocks.choice.unavailable'))
56
59
  : null;
60
+
57
61
  const described = detail !== undefined && detail !== '';
58
62
  const strong = primary === true;
59
63
 
@@ -20,6 +20,7 @@ const SourceNumbers = createContext<ReadonlyMap<string, number>>(new Map());
20
20
  /* Plain text in the same measure until the node closes, so highlighting never makes it jump. */
21
21
  export function CodeBlockRenderer({ node }: UiRendererProps<UiProps<'CodeBlock'>>) {
22
22
  const { language } = node.props;
23
+
23
24
  return (
24
25
  <div className="chat-ui-code chat-ui-flat flex flex-col rounded-xl bg-ui-tile p-1.5">
25
26
  {language !== undefined && language !== '' && (
@@ -57,9 +58,11 @@ function AttachedImage({
57
58
  unnamed: boolean;
58
59
  }) {
59
60
  const source = useHostAttachmentUrl(scopeId, chatId, attachmentId);
61
+
60
62
  if (!cell) {
61
63
  return <ImageThumb source={source} alt={alt} className="max-h-70 max-w-full rounded-xl object-contain" />;
62
64
  }
65
+
63
66
  return (
64
67
  <>
65
68
  <ImageThumb source={source} alt={alt} aspect={1} />
@@ -78,6 +81,7 @@ export function ImageRenderer({ node, children, context }: UiRendererProps<UiPro
78
81
  const { attachment, alt } = node.props;
79
82
  const caption = uiNodeLabel(node);
80
83
  const name = alt ?? (caption === '' ? t('blocks.image.label') : caption);
84
+
81
85
  return (
82
86
  <figure className={cell ? 'flex min-w-0 flex-col gap-1' : 'flex flex-col items-start gap-1.5'}>
83
87
  {attachment === undefined ? (
@@ -96,6 +100,7 @@ export function ImageRenderer({ node, children, context }: UiRendererProps<UiPro
96
100
  /* Three columns, two in a narrow block; the container decides, not the window, since a chat can be any width. */
97
101
  export function GalleryRenderer({ node, children }: UiRendererProps<UiProps<'Gallery'>>) {
98
102
  const { t } = useTranslation('agent-chat');
103
+
99
104
  return (
100
105
  <GalleryContext value>
101
106
  <div role="group" aria-label={t('blocks.gallery.label', { count: uiChildrenOf(node, 'Image').length })} className="@container">
@@ -125,11 +130,15 @@ export function CompareRenderer({ node, children, context }: UiRendererProps<UiP
125
130
  const [split, setSplit] = useState(0.5);
126
131
  const [aspect, setAspect] = useState(COMPARE_ASPECT);
127
132
  const caption = uiNodeLabel(node);
133
+
128
134
  // Each picture is named by its side and by what the whole comparison shows.
129
135
  const describe = (side: string): string => (caption === '' ? side : `${side}: ${caption}`);
136
+
130
137
  const loading = (source: typeof first) => source.url === null && source.failure === null;
138
+
131
139
  const label = t('blocks.compare.label', { before: beforeLabel, after: afterLabel });
132
140
  let body: ReactNode;
141
+
133
142
  if (loading(first) || loading(second)) {
134
143
  body = <ImageThumb source={{ url: null, failure: null }} alt={label} aspect={COMPARE_ASPECT} />;
135
144
  } else if (first.url !== null && second.url !== null) {
@@ -151,6 +160,7 @@ export function CompareRenderer({ node, children, context }: UiRendererProps<UiP
151
160
  className="size-full object-contain select-none"
152
161
  onLoad={(event) => {
153
162
  const { naturalWidth, naturalHeight } = event.currentTarget;
163
+
154
164
  if (naturalWidth > 0 && naturalHeight > 0) {
155
165
  setAspect(naturalWidth / naturalHeight);
156
166
  }
@@ -167,6 +177,7 @@ export function CompareRenderer({ node, children, context }: UiRendererProps<UiP
167
177
  body = <ImageThumb source={first} alt={label} aspect={COMPARE_ASPECT} />;
168
178
  } else {
169
179
  const shown = first.url !== null ? { source: first, name: describe(beforeLabel) } : { source: second, name: describe(afterLabel) };
180
+
170
181
  body = (
171
182
  <>
172
183
  <ImageThumb source={shown.source} alt={shown.name} className="max-h-70 max-w-full object-contain" />
@@ -174,6 +185,7 @@ export function CompareRenderer({ node, children, context }: UiRendererProps<UiP
174
185
  </>
175
186
  );
176
187
  }
188
+
177
189
  return (
178
190
  <figure className="flex w-full flex-col items-start gap-1.5">
179
191
  {body}
@@ -209,6 +221,7 @@ export function ModelRenderer({ node, children, context }: UiRendererProps<UiPro
209
221
  const label = alt ?? (caption === '' ? t('blocks.model.label') : caption);
210
222
  const View = host.ModelView;
211
223
  let body: ReactNode;
224
+
212
225
  if (View !== undefined && source.url !== null) {
213
226
  body = (
214
227
  <div role="group" aria-label={label} className="relative h-80 w-full overflow-hidden rounded-xl bg-ui-tile">
@@ -236,6 +249,7 @@ export function ModelRenderer({ node, children, context }: UiRendererProps<UiPro
236
249
  </div>
237
250
  );
238
251
  }
252
+
239
253
  return (
240
254
  <figure className="flex w-full flex-col items-start gap-1.5">
241
255
  {body}
@@ -248,6 +262,7 @@ export function SourcesRenderer({ node, children }: UiRendererProps<UiProps<'Sou
248
262
  const { t } = useTranslation('agent-chat');
249
263
  const headingId = useId();
250
264
  const numbers = new Map(uiChildrenOf(node, 'Source').map((source, index) => [source.id, index + 1]));
265
+
251
266
  return (
252
267
  <SourceNumbers value={numbers}>
253
268
  <section aria-labelledby={headingId} className="chat-ui-flat flex flex-col rounded-xl bg-ui-tile p-1.5">
@@ -277,6 +292,7 @@ export interface UiSourceCitationProps {
277
292
  */
278
293
  export function UiSourceCitation({ number, title, url, onReveal, className }: UiSourceCitationProps) {
279
294
  const { t } = useTranslation('agent-chat');
295
+
280
296
  return (
281
297
  <PreviewCard.Root>
282
298
  <PreviewCard.Trigger
@@ -310,6 +326,7 @@ export function SourceRenderer({ node, context }: UiRendererProps<UiProps<'Sourc
310
326
  const { title, url } = node.props;
311
327
  const { openUrl } = context;
312
328
  const [measureDomain, domainCut] = useTruncated<HTMLSpanElement>();
329
+
313
330
  const face = (
314
331
  <>
315
332
  <span className="grid size-5 shrink-0 place-items-center rounded-md bg-ui-inset text-2xs font-semibold text-text tabular-nums">{number}</span>
@@ -321,6 +338,7 @@ export function SourceRenderer({ node, context }: UiRendererProps<UiProps<'Sourc
321
338
  </span>
322
339
  </>
323
340
  );
341
+
324
342
  return (
325
343
  <li data-ui-source={node.id} tabIndex={-1} className="chat-ui-source scroll-my-2 rounded-md">
326
344
  {/* The title wraps and the domain may be cut short, so the tooltip says both whole; a row that opens nothing takes focus only while it cuts. */}
@@ -26,6 +26,7 @@ const EntityTiles = createContext(false);
26
26
  */
27
27
  function LiveFigure({ text, previous, live, className }: { text: string; previous?: string; live?: UiLiveValue; className?: string }) {
28
28
  const lit = live !== undefined && live.changed && !live.stale && previous !== text;
29
+
29
30
  return (
30
31
  <span key={text} className={clsx(live?.stale ? 'text-text-muted' : 'text-text', lit && 'chat-ui-changed', className)}>
31
32
  {text}
@@ -48,6 +49,7 @@ export function StatRenderer({ node, context }: UiRendererProps<UiProps<'Stat'>>
48
49
  // A change from zero has no percentage.
49
50
  const percent = change === null || previous === undefined || previous === 0 ? null : formatPercent(Math.abs((change / previous) * 100));
50
51
  const said = percent === null ? t('blocks.stat.from', { previous: from }) : t('blocks.stat.change', { change: percent, previous: from });
52
+
51
53
  return (
52
54
  <div className="flex min-w-0 flex-col items-start gap-1.5 rounded-xl bg-ui-tile p-3">
53
55
  <span className="max-w-full truncate text-2xs text-text-muted">{label}</span>
@@ -80,6 +82,7 @@ export function StatRenderer({ node, context }: UiRendererProps<UiProps<'Stat'>>
80
82
 
81
83
  export function EntityListRenderer({ node, children }: UiRendererProps<UiProps<'EntityList'>>) {
82
84
  const count = uiChildrenOf(node, 'Entry').length;
85
+
83
86
  if (count <= TILED_UP_TO) {
84
87
  return (
85
88
  <EntityTiles value>
@@ -87,6 +90,7 @@ export function EntityListRenderer({ node, children }: UiRendererProps<UiProps<'
87
90
  </EntityTiles>
88
91
  );
89
92
  }
93
+
90
94
  return (
91
95
  <KeyValueList.Root divided={count >= DIVIDED_FROM} className="chat-ui-nodes chat-ui-flat rounded-xl bg-ui-tile px-3 py-2">
92
96
  {children}
@@ -96,6 +100,7 @@ export function EntityListRenderer({ node, children }: UiRendererProps<UiProps<'
96
100
 
97
101
  export function EntryRenderer({ node, children }: UiRendererProps<UiProps<'Entry'>>) {
98
102
  const tiled = useContext(EntityTiles);
103
+
99
104
  if (tiled) {
100
105
  return (
101
106
  <div className="flex min-w-0 flex-col gap-0.5 rounded-xl bg-ui-tile px-3 py-2">
@@ -104,6 +109,7 @@ export function EntryRenderer({ node, children }: UiRendererProps<UiProps<'Entry
104
109
  </div>
105
110
  );
106
111
  }
112
+
107
113
  return (
108
114
  <KeyValueList.Item>
109
115
  <KeyValueList.Name>{node.props.label}</KeyValueList.Name>
@@ -118,6 +124,7 @@ function numberText(cell: Extract<UiTableCell, { kind: 'number' }>, unit: string
118
124
 
119
125
  function Cell({ value, column, live }: { value: unknown; column: UiTableColumn; live?: UiLiveValue }) {
120
126
  const cell = uiTableCell(value, column);
127
+
121
128
  switch (cell.kind) {
122
129
  case 'empty':
123
130
  return null;
@@ -125,6 +132,7 @@ function Cell({ value, column, live }: { value: unknown; column: UiTableColumn;
125
132
  return <>{cell.text}</>;
126
133
  case 'number': {
127
134
  const before = live?.previous === undefined ? undefined : uiTableCell(live.previous, column);
135
+
128
136
  return (
129
137
  <LiveFigure
130
138
  text={numberText(cell, column.unit)}
@@ -155,11 +163,14 @@ export function TableRenderer({ node, context }: UiRendererProps<UiProps<'Table'
155
163
  const { t } = useTranslation('agent-chat');
156
164
  const [all, setAll] = useBlockLocal(context, node.id, () => false);
157
165
  const columns = uiTableColumns(node);
166
+
158
167
  if (columns.length === 0) {
159
168
  return <UiFallbackPart fallback={node.fallback} problem={{ kind: 'failed' }} />;
160
169
  }
170
+
161
171
  const { rows } = node.props;
162
172
  const shown = all ? rows : rows.slice(0, UI_TABLE_ROWS);
173
+
163
174
  return (
164
175
  <div className="flex flex-col items-start gap-1.5">
165
176
  <div className="chat-ui-flat max-h-80 w-full overflow-auto rounded-xl bg-ui-tile px-1.5 pb-1.5">