@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 { useMemo, type ReactNode } from 'react';
1
+ import { type ReactNode } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import { useShallow } from 'zustand/shallow';
4
4
  import { useTranslation } from 'react-i18next';
@@ -16,6 +16,7 @@ import { useChatHost } from '../../host';
16
16
  import { useChatScope } from '../../scope';
17
17
  import { useChatActions } from '../actions';
18
18
  import { statusLookOf, type StatusWord } from '../../agents/status-look';
19
+ import { useHostTask, useHostTasks } from '../../host-hooks';
19
20
 
20
21
  const NO_TASKS: readonly ChatBackgroundTask[] = [];
21
22
 
@@ -30,29 +31,21 @@ export function ChatActivity({ chatId }: { chatId: string }) {
30
31
  const order = useChatRow(chatId, (row) => row?.order) ?? NO_ITEMS;
31
32
  const before = useChatRow(chatId, (row) => row?.subagentsBefore) ?? NO_ITEMS;
32
33
  const structure = useChatRow(chatId, (row) => row?.structure);
33
- const subagents = useMemo(() => (structure === undefined ? [] : flyoutSubagents([...before, ...order], structure)), [before, order, structure]);
34
+ const subagents = structure === undefined ? [] : flyoutSubagents([...before, ...order], structure);
34
35
  const { id, keyOf } = useChatScope();
35
- const tasks = host.tasks.useTasks(id);
36
+ const tasks = useHostTasks(id);
36
37
  // The infos of these sub-agents' own chats, so the info of any other chat changing does not draw this again.
37
38
  const children = useChats(useShallow((s) => subagents.map((item) => (item.childId === undefined ? undefined : s.statusByKey[keyOf(item.childId)]?.info))));
38
- const progress = useMemo(
39
- () =>
40
- progressOf(
41
- subagents.map((item, index) => {
42
- const taskId = taskIdOf(item);
43
- return taskRowStateOf(item, taskId === null ? null : (tasks?.[taskId] ?? null), children[index]);
44
- })
45
- ),
46
- [subagents, tasks, children]
47
- );
48
- const words = useMemo(
49
- () =>
50
- subagents.map((item) => {
51
- const taskId = taskIdOf(item);
52
- return statusWordOf(item, taskId === null ? null : (tasks?.[taskId] ?? null));
53
- }),
54
- [subagents, tasks]
39
+ const progress = progressOf(
40
+ subagents.map((item, index) => {
41
+ const taskId = taskIdOf(item);
42
+ return taskRowStateOf(item, taskId === null ? null : (tasks?.[taskId] ?? null), children[index]);
43
+ })
55
44
  );
45
+ const words = subagents.map((item) => {
46
+ const taskId = taskIdOf(item);
47
+ return statusWordOf(item, taskId === null ? null : (tasks?.[taskId] ?? null));
48
+ });
56
49
  const background = useChatRow(chatId, (row) => row?.info.background) ?? NO_TASKS;
57
50
  if (subagents.length === 0 && background.length === 0) {
58
51
  return null;
@@ -174,11 +167,10 @@ function SubagentRows({ chatId, items }: { chatId: string; items: readonly ChatS
174
167
  }
175
168
 
176
169
  function SubagentEntry({ chatId, item, now }: { chatId: string; item: ChatSubagentItem; now: number }) {
177
- const host = useChatHost();
178
170
  const { t } = useTranslation('agent-chat');
179
171
  const { id, keyOf } = useChatScope();
180
172
  const refused = useSubagentSupport((s) => s.unsupported[id] === true);
181
- const task = host.tasks.useTask(id, taskIdOf(item));
173
+ const task = useHostTask(id, taskIdOf(item));
182
174
  const { show } = useSubagentTrail(chatId);
183
175
  const child = useChats((s) => (item.childId === undefined ? undefined : s.statusByKey[keyOf(item.childId)]?.info));
184
176
  const word = statusWordOf(item, task);
@@ -1,15 +1,14 @@
1
1
  import clsx from 'clsx';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { MessageSquare, X } from 'lucide-react';
4
- import { useChatHost } from '../../host';
5
4
  import { CHIP_IN_MESSAGE, MENTION_TONE } from './chips';
6
5
  import { Icon, Tooltip } from '@adecore/ui';
6
+ import { useHostReferableChats } from '../../host-hooks';
7
7
 
8
8
  /* A chat attached to a message, under the title it has now rather than the one it had when it was attached. */
9
9
  export function ChatReferenceChip({ chatId, onRemove }: { chatId: string; onRemove?: () => void }) {
10
- const host = useChatHost();
11
10
  const { t } = useTranslation('agent-chat');
12
- const chat = host.useReferableChats().find((candidate) => candidate.id === chatId);
11
+ const chat = useHostReferableChats().find((candidate) => candidate.id === chatId);
13
12
  const title = chat === undefined ? t('chatReference.gone') : chat.title || t('chatReference.untitled');
14
13
  return (
15
14
  <span className={clsx(MENTION_TONE, CHIP_IN_MESSAGE)}>
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useRef, useState, useSyncExternalStore, type MouseEvent, type PointerEvent } from 'react';
1
+ import { useEffect, useRef, useState, useSyncExternalStore, type MouseEvent, type PointerEvent } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import clsx from 'clsx';
4
4
  import { ChevronDown } from 'lucide-react';
@@ -35,6 +35,7 @@ import { useCompactionOffer } from './composer/use-compaction-offer';
35
35
  import { useComposerDraft } from './composer/use-composer-draft';
36
36
  import { useComposerMenus } from './composer/use-composer-menus';
37
37
  import { useComposerSubmit } from './composer/use-composer-submit';
38
+ import { useEffortStepper } from './composer/use-effort-stepper';
38
39
  import { LEAVE_DROPS_TO_THE_CARD, useDraftFiles } from './composer/use-draft-files';
39
40
  import { useContinuableTurn } from './composer/use-continuable-turn';
40
41
  import { useNotice } from './composer/use-notice';
@@ -43,6 +44,7 @@ import { usePromptHistory } from './composer/use-prompt-history';
43
44
  import { usePromptHold } from './composer/use-prompt-hold';
44
45
  import { surfacePartOf } from './composer/surface';
45
46
  import { useTakeBack } from './composer/use-take-back';
47
+ import { useHostExtraPrompts, useHostSendDelivery } from '../../host-hooks';
46
48
 
47
49
  interface ComposerProps {
48
50
  chatId: string;
@@ -78,7 +80,7 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
78
80
  const { t } = useTranslation('agent-chat');
79
81
  const host = useChatHost();
80
82
  const scope = useChatScope();
81
- const sendDelivery = host.useSendDelivery();
83
+ const sendDelivery = useHostSendDelivery();
82
84
  const sendKey = useChatPreferences((preferences) => preferences.sendKey);
83
85
  const provider = useProviders((row) => row.providers.find((entry) => entry.kind === info.provider));
84
86
  // Absent until the host answered `provider.list`, so only an explicit false hides anything.
@@ -96,11 +98,19 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
96
98
  const menus = useComposerMenus({ chatId, info, capabilities, providerName: provider?.name, draft: box, inputRef });
97
99
  const files = useDraftFiles({ capabilities, providerName: provider?.name, writable, draft: box, inputRef, setNotice: notice.setNotice });
98
100
  const controls = useChatControls({ chatId, info, notice, onRetarget });
101
+ useEffortStepper({
102
+ chatId,
103
+ info,
104
+ model: provider?.models.find((entry) => entry.slug === info.selection.model),
105
+ writable,
106
+ controls,
107
+ setNotice: notice.setNotice
108
+ });
99
109
  const guard = promptGuard(withQuote(draft.quote, draft.text));
100
110
  const continuable = useContinuableTurn(chatId, info);
101
111
  // The stopped turn something went out for, since the chat takes a moment to answer and Continue must not stand there meanwhile.
102
112
  const [sentFor, setSentFor] = useState<string | null>(null);
103
- const extraPrompts = host.prompts.useExtra(scope.id, chatId);
113
+ const extraPrompts = useHostExtraPrompts(scope.id, chatId);
104
114
  const { submit, continueTurn, restoreStashed, toggleStash } = useComposerSubmit({
105
115
  chatId,
106
116
  writable,
@@ -144,10 +154,7 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
144
154
  const pending = usePendingRequests(chatId);
145
155
  const compactionOffer = useCompactionOffer(chatId, info, capabilities?.compaction);
146
156
  const placeholder = useHintedPlaceholder({ chatId, disabled, readOnly, mentionable: capabilities?.mentions !== false, hasSkills: menus.hasSkills });
147
- const editorExtensions = useMemo(
148
- () => [chipDecorations({ mentions: draft.mentions, skills: draft.skills }), LEAVE_DROPS_TO_THE_CARD],
149
- [draft.mentions, draft.skills]
150
- );
157
+ const editorExtensions = [chipDecorations({ mentions: draft.mentions, skills: draft.skills }), LEAVE_DROPS_TO_THE_CARD];
151
158
  /* The timeline owns the scroller; this is the one bit of it the composer needs to know. */
152
159
  const atEnd = useSyncExternalStore(
153
160
  subscribeTimelineEnd,
@@ -232,6 +239,7 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
232
239
  {compactionOffer && (
233
240
  <ResumeCompactionDock
234
241
  tokens={compactionOffer.tokens}
242
+ reason={compactionOffer.reason}
235
243
  onCompact={controls.compact}
236
244
  onDismiss={() => dismissResumeCompaction(scope, chatId, compactionOffer.turnId)}
237
245
  />
@@ -1,4 +1,3 @@
1
- import { useMemo } from 'react';
2
1
  import { LazyBoundary } from '@adecore/ui';
3
2
  import { useTranslation } from 'react-i18next';
4
3
  import { lazyNamed } from '../../lazy';
@@ -17,9 +16,9 @@ export default function EditDiff({ change, allowEditor = true, maxHeight }: { ch
17
16
  const host = useChatHost();
18
17
  const { t } = useTranslation('agent-chat');
19
18
  const loadEngine = host.code.editor;
20
- const sourceAllowed = useMemo(() => diffSourceBudget(change.before, change.after), [change.before, change.after]);
19
+ const sourceAllowed = diffSourceBudget(change.before, change.after);
21
20
  const legacyMode = !allowEditor || loadEngine === undefined;
22
- const legacyAllowed = useMemo(() => legacyMode && legacyDiffBudget(change.before, change.after), [legacyMode, change.before, change.after]);
21
+ const legacyAllowed = legacyMode && legacyDiffBudget(change.before, change.after);
23
22
  const omitted = <DiffOmitted path={change.path} />;
24
23
  const fallback = legacyAllowed ? (
25
24
  <DiffFallbackBoundary fallback={omitted} resetKeys={[change.path, change.before, change.after]}>
@@ -14,6 +14,7 @@ import { useChatHost } from '../../host';
14
14
  import { useChatScope, type ChatScope } from '../../scope';
15
15
  import { useChatActions } from '../actions';
16
16
  import { useUsageStore } from '../../state/usage';
17
+ import { useHostResumeAtReset } from '../../host-hooks';
17
18
 
18
19
  // A time a limit names reads as a clock today and with its day after that, so the words follow the minute.
19
20
  const MINUTE_MS = 60_000;
@@ -43,11 +44,10 @@ function readUnreadAccounts(scope: ChatScope): void {
43
44
  * switch off it stands off and says where that switch is.
44
45
  */
45
46
  function ResumeAtResetToggle({ chatId, info }: { chatId: string; info: ChatInfo }) {
46
- const host = useChatHost();
47
47
  const { t } = useTranslation('agent-chat');
48
48
  const { id } = useChatScope();
49
49
  const actions = useChatActions();
50
- const machineAllows = host.useResumeAtReset(id);
50
+ const machineAllows = useHostResumeAtReset(id);
51
51
  const set = (on: boolean): void => {
52
52
  void actions.configure(chatId, { resumeAtReset: on }).catch(() => undefined);
53
53
  };
@@ -1,4 +1,4 @@
1
- import { createContext, memo, useContext, useMemo, type ReactNode } from 'react';
1
+ import { createContext, memo, useContext, type ReactNode } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import { Zap } from 'lucide-react';
4
4
  import ReactMarkdown, { type Components, type ExtraProps, type Options } from 'react-markdown';
@@ -85,7 +85,7 @@ function languageOf(className: string | undefined): string {
85
85
  return /language-([\w-]+)/.exec(className ?? '')?.[1] ?? 'text';
86
86
  }
87
87
 
88
- const INLINE_CODE = 'rounded-sm bg-surface-sunken px-1 py-px font-mono text-code';
88
+ const INLINE_CODE = 'rounded-sm bg-surface-sunken px-1 py-px font-mono text-code box-decoration-clone';
89
89
 
90
90
  /*
91
91
  * A path an agent wrote down. It keeps the shape of the code chip it would otherwise be, so a sentence
@@ -99,7 +99,7 @@ function FileLink({ target, className, children }: { target: FileRef; className?
99
99
  // A button is not selectable on its own, and a thread is copied as often as it is clicked.
100
100
  <button
101
101
  type="button"
102
- className={clsx('cursor-pointer select-text', className)}
102
+ className={clsx('cursor-pointer select-text text-left', className)}
103
103
  data-file-path={target.path}
104
104
  data-file-line={target.line}
105
105
  data-file-column={target.column}
@@ -121,8 +121,10 @@ function InlineCode({ text }: { text: string }) {
121
121
  return <code className={INLINE_CODE}>{text}</code>;
122
122
  }
123
123
  return (
124
- <FileLink target={target} className={`${INLINE_CODE} text-accent hover:underline`}>
125
- {text}
124
+ // The chip sits on a code inside the button, since a button is always a box of its own and its
125
+ // background would fill the whole box once a long path wraps.
126
+ <FileLink target={target} className="text-accent">
127
+ <code className={clsx(INLINE_CODE, 'hover:underline')}>{text}</code>
126
128
  </FileLink>
127
129
  );
128
130
  }
@@ -393,7 +395,7 @@ export const ReplyMarkdown = memo(function ReplyMarkdown({
393
395
  arriving?: boolean;
394
396
  reply?: ShellCodeBlockReply;
395
397
  }) {
396
- const blocks = useMemo(() => splitMarkdownBlocks(text), [text]);
398
+ const blocks = splitMarkdownBlocks(text);
397
399
  return (
398
400
  <div className="chat-markdown prose prose-sm max-w-none text-sm" data-arriving={arriving || undefined}>
399
401
  {blocks.map((block, index) =>
@@ -420,7 +422,7 @@ export const ReplyMarkdown = memo(function ReplyMarkdown({
420
422
  * it was, and the files and skills picked in the composer are chips again.
421
423
  */
422
424
  export const MessageMarkdown = memo(function MessageMarkdown({ text, mentions, skills }: { text: string; mentions?: string[]; skills?: string[] }) {
423
- const rehypePlugins = useMemo((): [typeof rehypeChips, ChipOptions][] => [[rehypeChips, { mentions, skills }]], [mentions, skills]);
425
+ const rehypePlugins: [typeof rehypeChips, ChipOptions][] = [[rehypeChips, { mentions, skills }]];
424
426
  return (
425
427
  <div className="chat-markdown prose prose-sm max-w-none text-sm">
426
428
  <ReactMarkdown remarkPlugins={MESSAGE_PLUGINS} rehypePlugins={rehypePlugins} components={messageComponents}>
@@ -449,7 +451,7 @@ export const Markdown = memo(function Markdown({
449
451
  rehypePlugins?: Options['rehypePlugins'];
450
452
  componentOverrides?: Components;
451
453
  }) {
452
- const renderComponents = useMemo(() => ({ ...(fileLinks ? components : plainComponents), ...componentOverrides }), [fileLinks, componentOverrides]);
454
+ const renderComponents = { ...(fileLinks ? components : plainComponents), ...componentOverrides };
453
455
  return (
454
456
  <div className="chat-markdown prose prose-sm max-w-none text-sm" data-muted={muted || undefined}>
455
457
  <ReactMarkdown remarkPlugins={breaks ? PLUGINS_WITH_BREAKS : PLUGINS} rehypePlugins={rehypePlugins} components={renderComponents}>
@@ -2,20 +2,23 @@ import { useContext, useEffect, useId, useRef, useState } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { Code, Workflow } from 'lucide-react';
4
4
  import { IconButton } from '@adecore/ui';
5
- import { useChatHost } from '../../host';
6
5
  import { CodeBlock } from './CodeBlock';
7
6
  import { CodeStreamingContext } from './code-streaming';
8
- import { MERMAID_TOKENS, mermaidConfig, sizedSvg, svgDataUrl, type MermaidColors, type SizedSvg } from './mermaid';
7
+ import { DIAGRAM_TOKENS, GROUND_TOKEN, renderOptions, svgDataUrl, themedSvg, type DiagramFont } from './mermaid';
9
8
  import { watchTheme } from './theme-watch';
9
+ import { useHostCodeMode } from '../../host-hooks';
10
10
 
11
- type Drawing = { state: 'drawing' } | { state: 'drawn'; image: SizedSvg } | { state: 'failed' };
11
+ interface LaidOut {
12
+ svg: string;
13
+ width: number;
14
+ height: number;
15
+ }
12
16
 
13
- // Mermaid keeps one configuration for the page, so one diagram is drawn at a time with its own.
14
- let queue: Promise<unknown> = Promise.resolve();
17
+ type Drawing = { state: 'drawing' } | { state: 'drawn'; image: LaidOut } | { state: 'failed' };
15
18
 
16
19
  let probeCanvas: HTMLCanvasElement | null = null;
17
20
 
18
- /* A token as an opaque `#rrggbb` over `ground`, which is what Mermaid's color math reads. */
21
+ /* A token as an opaque `#rrggbb` over `ground`, so a border with an alpha mixes with the ground and not with what is under the image. */
19
22
  function resolveColor(element: HTMLElement, token: string, ground: string): string {
20
23
  probeCanvas ??= document.createElement('canvas');
21
24
  probeCanvas.width = 1;
@@ -33,47 +36,58 @@ function resolveColor(element: HTMLElement, token: string, ground: string): stri
33
36
  return `#${[red, green, blue].map((channel) => channel!.toString(16).padStart(2, '0')).join('')}`;
34
37
  }
35
38
 
36
- function themeColors(element: HTMLElement): MermaidColors {
37
- const ground = resolveColor(element, MERMAID_TOKENS.ground, '#808080');
38
- const entries = Object.entries(MERMAID_TOKENS).map(([name, token]) => [name, name === 'ground' ? ground : resolveColor(element, token, ground)]);
39
- return Object.fromEntries(entries) as unknown as MermaidColors;
40
- }
41
-
42
39
  /* What a diagram reads of the theme where it stands. */
43
40
  interface DiagramLook {
44
- colors: MermaidColors;
45
- font: { family: string; size: string };
41
+ colors: Record<string, string>;
42
+ font: DiagramFont;
46
43
  }
47
44
 
48
45
  function diagramLook(element: HTMLElement): DiagramLook {
49
46
  const style = getComputedStyle(element);
50
- return { colors: themeColors(element), font: { family: style.fontFamily, size: style.fontSize } };
47
+ const ground = resolveColor(element, GROUND_TOKEN, '#808080');
48
+ const colors = Object.fromEntries(
49
+ Object.entries(DIAGRAM_TOKENS).map(([name, token]) => [name, token === GROUND_TOKEN ? ground : resolveColor(element, token, ground)])
50
+ );
51
+ const font = {
52
+ family: style.fontFamily,
53
+ mono: style.getPropertyValue('--font-mono').trim() || 'monospace',
54
+ size: Number.parseFloat(style.fontSize) || 14
55
+ };
56
+ return { colors, font };
57
+ }
58
+
59
+ /* Keeps the look on screen when nothing of it changed, so a change of the root that leaves every color alone draws nothing again. */
60
+ function keepingSame(next: DiagramLook) {
61
+ return (current: DiagramLook | null) => (current !== null && JSON.stringify(current) === JSON.stringify(next) ? current : next);
51
62
  }
52
63
 
53
- async function drawDiagram(id: string, source: string, look: DiagramLook, dark: boolean): Promise<SizedSvg | null> {
54
- const { default: mermaid } = await import('mermaid');
55
- mermaid.initialize(mermaidConfig(look.colors, dark, look.font));
56
- const { svg } = await mermaid.render(id, source);
57
- return sizedSvg(svg);
64
+ async function layOut(id: string, source: string, font: DiagramFont): Promise<LaidOut> {
65
+ const { renderAsync } = await import('pele/lazy');
66
+ const { svg, width, height } = await renderAsync(source, renderOptions(font, id));
67
+ return { svg, width: Math.ceil(width), height: Math.ceil(height) };
58
68
  }
59
69
 
60
70
  /*
61
- * A ```mermaid fence, drawn as the diagram once the fence is closed. Mermaid is large, so it loads on
62
- * the first diagram of the page. While the fence grows, and when the text is no diagram Mermaid can
63
- * draw, it is the code block it would otherwise be.
71
+ * A ```mermaid fence, drawn as the diagram once the fence is closed. Pele loads on the first diagram of the
72
+ * page, and then only what a diagram of that kind needs. While the fence grows, and when the text is no diagram
73
+ * Pele can draw, it is the code block it would otherwise be.
64
74
  */
65
75
  export function MermaidBlock({ code }: { code: string }) {
66
- const host = useChatHost();
67
76
  const { t } = useTranslation('agent-chat');
68
77
  const streaming = useContext(CodeStreamingContext);
69
- const dark = host.code.useMode() === 'dark';
78
+ const dark = useHostCodeMode() === 'dark';
70
79
  const id = `mermaid-${useId().replace(/[^\w-]/g, '')}`;
71
80
  const frame = useRef<HTMLDivElement>(null);
72
81
  const [source, setSource] = useState(false);
73
82
  const [drawing, setDrawing] = useState<Drawing>({ state: 'drawing' });
74
- // The look of the diagram on screen or on its way there, null before the first.
75
- const drawnLook = useRef<string | null>(null);
76
- const [themeRevision, setThemeRevision] = useState(0);
83
+ const [look, setLook] = useState<DiagramLook | null>(null);
84
+
85
+ useEffect(() => {
86
+ const element = frame.current;
87
+ if (element !== null) {
88
+ setLook(keepingSame(diagramLook(element)));
89
+ }
90
+ }, [dark]);
77
91
 
78
92
  // An app may change its colors without turning light or dark, which only the root of the page tells.
79
93
  useEffect(() => {
@@ -81,34 +95,28 @@ export function MermaidBlock({ code }: { code: string }) {
81
95
  if (element === null) {
82
96
  return;
83
97
  }
84
- return watchTheme(element.ownerDocument, () => {
85
- if (drawnLook.current !== null && JSON.stringify(diagramLook(element)) !== drawnLook.current) {
86
- setThemeRevision((revision) => revision + 1);
87
- }
88
- });
98
+ return watchTheme(element.ownerDocument, () => setLook(keepingSame(diagramLook(element))));
89
99
  }, []);
90
100
 
101
+ const fontFamily = look?.font.family;
102
+ const fontMono = look?.font.mono;
103
+ const fontSize = look?.font.size;
91
104
  useEffect(() => {
92
- const element = frame.current;
93
- if (streaming || element === null) {
105
+ if (streaming || fontFamily === undefined || fontMono === undefined || fontSize === undefined) {
94
106
  return;
95
107
  }
96
108
  let cancelled = false;
97
- const look = diagramLook(element);
98
- drawnLook.current = JSON.stringify(look);
99
- // A diagram already drawn stays until the next one is there, so a change of the theme does not make it blink.
100
- setDrawing((current) => (current.state === 'drawn' ? current : { state: 'drawing' }));
101
- const drawn = queue.then(() => (cancelled ? null : drawDiagram(id, code, look, dark)));
102
- queue = drawn.catch(() => undefined);
103
- drawn.then(
104
- (image) => !cancelled && setDrawing(image === null ? { state: 'failed' } : { state: 'drawn', image }),
109
+ // A diagram already drawn stays until the next one is there, so it does not blink.
110
+ layOut(id, code, { family: fontFamily, mono: fontMono, size: fontSize }).then(
111
+ (image) => !cancelled && setDrawing({ state: 'drawn', image }),
105
112
  () => !cancelled && setDrawing({ state: 'failed' })
106
113
  );
107
114
  return () => {
108
115
  cancelled = true;
109
116
  };
110
- }, [code, dark, id, streaming, themeRevision]);
117
+ }, [code, fontFamily, fontMono, fontSize, id, streaming]);
111
118
 
119
+ const image = drawing.state === 'drawn' && look !== null ? { ...drawing.image, svg: themedSvg(drawing.image.svg, look.colors) } : null;
112
120
  const showsDiagram = !streaming && !source && drawing.state !== 'failed';
113
121
  const toggle =
114
122
  streaming || drawing.state === 'failed' ? undefined : (
@@ -128,12 +136,12 @@ export function MermaidBlock({ code }: { code: string }) {
128
136
  actions={toggle}
129
137
  view={
130
138
  showsDiagram ? (
131
- <div className="overflow-x-auto p-3" aria-busy={drawing.state === 'drawing' || undefined}>
132
- {drawing.state === 'drawn' && (
139
+ <div className="overflow-x-auto p-3" aria-busy={image === null || undefined}>
140
+ {image !== null && (
133
141
  <img
134
- src={svgDataUrl(drawing.image.svg)}
135
- width={drawing.image.width}
136
- height={drawing.image.height}
142
+ src={svgDataUrl(image.svg)}
143
+ width={image.width}
144
+ height={image.height}
137
145
  alt={t('code.mermaid.diagram')}
138
146
  className="m-0 max-w-none"
139
147
  draggable={false}
@@ -1,6 +1,6 @@
1
- import { useContext, useEffect, useMemo, useRef, useState, type ReactNode, type RefObject } from 'react';
1
+ import { useContext, useEffect, useRef, useState, type ReactNode, type RefObject } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
- import { Bookmark, ChevronDown, ChevronRight, Search, Trash2 } from 'lucide-react';
3
+ import { Bookmark, ChevronDown, ChevronRight, Search, Star, Trash2 } from 'lucide-react';
4
4
  import type { AgentKind, ModelSelection, ProviderInfo, UsageProvider } from '@adecore/agent-contracts';
5
5
  import { AgentIcon } from '../../agents/AgentIcon';
6
6
  import { ProviderLogo } from '../../agents/ProviderLogo';
@@ -11,8 +11,10 @@ import { ChatScopeContext } from '../../scope';
11
11
  import { useReportedModels } from '../../state/chats';
12
12
  import { ModelMarks } from './ModelMarks';
13
13
  import { pickerEntriesOf, type PickerEntry } from '../logic/picker-entries';
14
+ import { favoriteModelsOf, toggleFavoriteModel, useChatPreferences } from '../preferences';
14
15
  import { forgetStashed, STASH_SHORTCUT, useStash, type StashedPrompt } from '../stash';
15
16
  import { Icon, IconButton, Menu, Popover, Tooltip } from '@adecore/ui';
17
+ import { useHostMachineLabel } from '../../host-hooks';
16
18
 
17
19
  const triggerClass =
18
20
  'flex h-7 shrink-0 items-center gap-1 whitespace-nowrap rounded-md px-2 text-xs text-text-muted hover:bg-surface-hover hover:text-text data-[popup-open]:bg-surface-active data-[popup-open]:text-text';
@@ -74,12 +76,13 @@ export function ModelPicker({
74
76
  const grouped = providers.length > 1;
75
77
  const trimmed = query.trim().toLowerCase();
76
78
 
77
- const entries = useMemo(
78
- () => pickerEntriesOf(providers, trimmed, legacyOpen, expanded, { provider, model: selection.model }),
79
- [providers, trimmed, legacyOpen, expanded, provider, selection.model]
79
+ const favorites = useChatPreferences((preferences) => preferences.favoriteModels);
80
+ const starredHere = (row: PickerEntry | undefined): boolean => row?.kind === 'model' && favorites[row.provider.kind]?.includes(row.model.slug) === true;
81
+ const entries = pickerEntriesOf(providers, trimmed, legacyOpen, expanded, { provider, model: selection.model }, (kind) =>
82
+ favoriteModelsOf(favorites, kind)
80
83
  );
81
84
  const scope = useContext(ChatScopeContext);
82
- const machine = host.useMachineLabel(scope?.id ?? '');
85
+ const machine = useHostMachineLabel(scope?.id ?? '');
83
86
  const reported = useReportedModels(provider);
84
87
  const requirementOf = (entry: PickerEntry & { kind: 'model' }) => modelRequirementOf(entry.model, entry.provider, reported);
85
88
  // A CLI that is too old blocks all its models; one way to its settings under the group says it once.
@@ -226,17 +229,21 @@ export function ModelPicker({
226
229
  const first = grouped && (previous === undefined || previous.kind === 'legacy' || previous.provider.kind !== entry.provider.kind);
227
230
  // The makers read as groups only where the whole list is shown, so a short list is not cut by labels.
228
231
  const vendorOpen = trimmed !== '' || expanded.has(entry.provider.kind);
232
+ // The starred rows lead their CLI without a maker's label; the makers start after them.
229
233
  const vendorFirst =
230
234
  vendorOpen &&
231
235
  entry.model.vendor !== undefined &&
236
+ !starredHere(entry) &&
232
237
  (previous === undefined ||
233
238
  previous.kind !== 'model' ||
234
239
  previous.provider.kind !== entry.provider.kind ||
240
+ starredHere(previous) ||
235
241
  previous.model.vendor !== entry.model.vendor);
236
242
  const requirement = requirementOf(entry);
237
243
  const next = entries[i + 1];
238
244
  const lastOfGroup = next === undefined || next.kind !== 'model' || next.provider.kind !== entry.provider.kind;
239
245
  const chosen = entry.provider.kind === provider && entry.model.slug === selection.model;
246
+ const starred = starredHere(entry);
240
247
  return (
241
248
  <div key={`${entry.provider.kind}/${entry.model.slug}`}>
242
249
  {first && (
@@ -246,30 +253,43 @@ export function ModelPicker({
246
253
  </Menu.Label>
247
254
  )}
248
255
  {vendorFirst && <Menu.Label>{entry.model.vendor}</Menu.Label>}
249
- <button
250
- role="option"
251
- aria-selected={chosen}
252
- aria-disabled={requirement !== null || undefined}
253
- data-active={i === index}
254
- className={`cursor-row flex w-full flex-col items-stretch gap-0.5 rounded-md px-2.5 py-1.5 text-left text-sm ${requirement === null ? 'text-text-muted' : 'text-text-faint'}`}
255
- onMouseEnter={() => setIndex(i)}
256
- onClick={() => choose(entry)}
257
- >
258
- <span className="flex w-full items-center gap-2">
259
- <Menu.Check kind="radio" checked={chosen} />
260
- <span className="min-w-0 truncate">{entry.model.name}</span>
261
- <ModelMarks model={entry.model} />
262
- <span className="grow" />
263
- {entry.model.legacy && <span className="text-xs text-text-faint">{t('pickers.model.legacy')}</span>}
264
- </span>
265
- {requirement !== null && (
266
- <span className="pl-6 text-xs text-text-faint">
267
- {machine === null
268
- ? t('pickers.model.requiresHere', { cli: requirement.cli, version: requirement.minVersion })
269
- : t('pickers.model.requires', { cli: requirement.cli, version: requirement.minVersion, machine })}
256
+ {/* The star sits beside the option rather than in it, since a button holds no other button. */}
257
+ <div className="flex items-start gap-0.5">
258
+ <button
259
+ role="option"
260
+ aria-selected={chosen}
261
+ aria-disabled={requirement !== null || undefined}
262
+ data-active={i === index}
263
+ className={`cursor-row flex min-w-0 grow flex-col items-stretch gap-0.5 rounded-md px-2.5 py-1.5 text-left text-sm ${requirement === null ? 'text-text-muted' : 'text-text-faint'}`}
264
+ onMouseEnter={() => setIndex(i)}
265
+ onClick={() => choose(entry)}
266
+ >
267
+ <span className="flex w-full items-center gap-2">
268
+ <Menu.Check kind="radio" checked={chosen} />
269
+ {/* Wrapped rather than cut, so a long name a provider gives is read whole. */}
270
+ <span className="min-w-0 break-words">{entry.model.name}</span>
271
+ <ModelMarks model={entry.model} />
272
+ <span className="grow" />
273
+ {entry.model.legacy && <span className="text-xs text-text-faint">{t('pickers.model.legacy')}</span>}
270
274
  </span>
271
- )}
272
- </button>
275
+ {requirement !== null && (
276
+ <span className="pl-6 text-xs text-text-faint">
277
+ {machine === null
278
+ ? t('pickers.model.requiresHere', { cli: requirement.cli, version: requirement.minVersion })
279
+ : t('pickers.model.requires', { cli: requirement.cli, version: requirement.minVersion, machine })}
280
+ </span>
281
+ )}
282
+ </button>
283
+ <IconButton
284
+ icon={Star}
285
+ size="sm"
286
+ label={starred ? t('pickers.model.unstar') : t('pickers.model.star')}
287
+ aria-pressed={starred}
288
+ iconClassName={starred ? 'fill-current text-accent' : undefined}
289
+ className="mt-0.5 shrink-0"
290
+ onClick={() => toggleFavoriteModel(entry.provider.kind, entry.model.slug)}
291
+ />
292
+ </div>
273
293
  {lastOfGroup && blockedKinds.has(entry.provider.kind) && (
274
294
  <button
275
295
  type="button"
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useRef, type ReactNode } from 'react';
1
+ import { useEffect, useRef, type ReactNode } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { ArrowDown, Hand, MessageCircleQuestionMark } from 'lucide-react';
4
4
  import type { HoldRelease } from '../prompt-hold';
@@ -12,6 +12,7 @@ import { useChatHost } from '../../host';
12
12
  import { useChatScope } from '../../scope';
13
13
  import { useChatActions } from '../actions';
14
14
  import { Button, Icon } from '@adecore/ui';
15
+ import { useHostExtraPrompts } from '../../host-hooks';
15
16
 
16
17
  /* Keeps a prompt card behind the draft while the person types; `released` says what lifted the hold last. */
17
18
  export interface PromptHold {
@@ -57,14 +58,11 @@ export function PromptComposer({
57
58
  const actions = useChatActions();
58
59
  const elsewhere = onShowElsewhere !== undefined;
59
60
  // A card the app raises itself waits in the chat beside the CLI's own requests.
60
- const extra = host.prompts.useExtra(id, chatId);
61
- const subjects = useMemo(
62
- (): PromptSubject[] => [
63
- ...pending.map((item): PromptSubject => ({ kind: 'chat', nodeId: chatId, item })),
64
- ...extra.map((prompt): PromptSubject => ({ kind: 'host', nodeId: chatId, prompt }))
65
- ],
66
- [pending, extra, chatId]
67
- );
61
+ const extra = useHostExtraPrompts(id, chatId);
62
+ const subjects: PromptSubject[] = [
63
+ ...pending.map((item): PromptSubject => ({ kind: 'chat', nodeId: chatId, item })),
64
+ ...extra.map((prompt): PromptSubject => ({ kind: 'host', nodeId: chatId, prompt }))
65
+ ];
68
66
  const session = usePromptSession({ prompts: subjects, idOf: promptIdOf, pick: pickSubject, disabled });
69
67
  const ref = useRef<HTMLDivElement>(null);
70
68
  const { active, activeId: activeKey } = session;