@adecore/agents-react 0.23.0 → 0.24.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 (341) hide show
  1. package/README.md +14 -11
  2. package/dist/agents/AccountDot.js +3 -1
  3. package/dist/agents/accounts.d.ts +3 -2
  4. package/dist/agents/accounts.js +5 -6
  5. package/dist/chat/actions.d.ts +2 -2
  6. package/dist/chat/actions.js +8 -5
  7. package/dist/chat/bookmarks.d.ts +5 -3
  8. package/dist/chat/bookmarks.js +13 -14
  9. package/dist/chat/chat-client.d.ts +1 -0
  10. package/dist/chat/chat-client.js +7 -0
  11. package/dist/chat/composer-send.d.ts +3 -0
  12. package/dist/chat/composer-send.js +22 -0
  13. package/dist/chat/forks.js +22 -5
  14. package/dist/chat/index.d.ts +31 -0
  15. package/dist/chat/index.js +28 -0
  16. package/dist/chat/logic/embedded-code.d.ts +8 -0
  17. package/dist/chat/logic/embedded-code.js +371 -0
  18. package/dist/chat/logic/fork.js +1 -0
  19. package/dist/chat/logic/goal.d.ts +6 -0
  20. package/dist/chat/logic/goal.js +26 -0
  21. package/dist/chat/logic/scrubber.d.ts +1 -1
  22. package/dist/chat/logic/scrubber.js +6 -5
  23. package/dist/chat/logic/thought.d.ts +3 -0
  24. package/dist/chat/logic/thought.js +35 -0
  25. package/dist/chat/logic/thread-cards.js +2 -0
  26. package/dist/chat/logic/timeline.d.ts +5 -0
  27. package/dist/chat/logic/timeline.js +22 -1
  28. package/dist/chat/logic/tool-catalog.d.ts +1 -0
  29. package/dist/chat/logic/tool-catalog.js +3 -1
  30. package/dist/chat/logic/tools.d.ts +14 -0
  31. package/dist/chat/logic/tools.js +57 -0
  32. package/dist/chat/preferences.d.ts +6 -0
  33. package/dist/chat/preferences.js +19 -3
  34. package/dist/chat/reading-width.d.ts +9 -0
  35. package/dist/chat/reading-width.js +27 -0
  36. package/dist/chat/send-key.d.ts +10 -0
  37. package/dist/chat/send-key.js +24 -0
  38. package/dist/chat/ui/BookmarkMarker.js +4 -2
  39. package/dist/chat/ui/BookmarkSubmenu.js +4 -3
  40. package/dist/chat/ui/ChatActivity.js +14 -10
  41. package/dist/chat/ui/ChatReferenceChip.js +3 -4
  42. package/dist/chat/ui/CodeBlock.d.ts +2 -1
  43. package/dist/chat/ui/CodeBlock.js +14 -96
  44. package/dist/chat/ui/Composer.d.ts +1 -1
  45. package/dist/chat/ui/Composer.js +117 -906
  46. package/dist/chat/ui/ComposerInput.js +5 -4
  47. package/dist/chat/ui/EditDiff.js +3 -2
  48. package/dist/chat/ui/GoalDock.d.ts +9 -0
  49. package/dist/chat/ui/GoalDock.js +63 -0
  50. package/dist/chat/ui/LimitState.js +5 -3
  51. package/dist/chat/ui/Markdown.d.ts +2 -1
  52. package/dist/chat/ui/Markdown.js +70 -31
  53. package/dist/chat/ui/MermaidBlock.d.ts +3 -0
  54. package/dist/chat/ui/MermaidBlock.js +73 -0
  55. package/dist/chat/ui/MessageActions.js +12 -7
  56. package/dist/chat/ui/Pickers.js +6 -5
  57. package/dist/chat/ui/PromptComposer.js +4 -3
  58. package/dist/chat/ui/RowTime.d.ts +3 -0
  59. package/dist/chat/ui/RowTime.js +12 -0
  60. package/dist/chat/ui/RunSettings.js +5 -4
  61. package/dist/chat/ui/ShellCommand.d.ts +3 -0
  62. package/dist/chat/ui/ShellCommand.js +110 -0
  63. package/dist/chat/ui/SubagentInfo.js +4 -3
  64. package/dist/chat/ui/SubagentStopButton.js +4 -3
  65. package/dist/chat/ui/SubagentTimeline.js +4 -1
  66. package/dist/chat/ui/Timeline.js +70 -40
  67. package/dist/chat/ui/TimelineMenu.d.ts +2 -1
  68. package/dist/chat/ui/TimelineMenu.js +18 -8
  69. package/dist/chat/ui/UnifiedDiff.js +3 -2
  70. package/dist/chat/ui/VisualFrame.js +6 -7
  71. package/dist/chat/ui/code-theme.d.ts +2 -1
  72. package/dist/chat/ui/code-theme.js +4 -4
  73. package/dist/chat/ui/composer/ComposerControls.d.ts +27 -0
  74. package/dist/chat/ui/composer/ComposerControls.js +34 -0
  75. package/dist/chat/ui/composer/ComposerMenu.d.ts +4 -0
  76. package/dist/chat/ui/composer/ComposerMenu.js +46 -0
  77. package/dist/chat/ui/composer/DraftParts.d.ts +18 -0
  78. package/dist/chat/ui/composer/DraftParts.js +25 -0
  79. package/dist/chat/ui/composer/QueuedMessages.d.ts +10 -0
  80. package/dist/chat/ui/composer/QueuedMessages.js +20 -0
  81. package/dist/chat/ui/composer/key-handler.d.ts +20 -0
  82. package/dist/chat/ui/composer/key-handler.js +150 -0
  83. package/dist/chat/ui/composer/keys.d.ts +7 -2
  84. package/dist/chat/ui/composer/keys.js +19 -9
  85. package/dist/chat/ui/composer/placeholder.d.ts +9 -0
  86. package/dist/chat/ui/composer/placeholder.js +38 -0
  87. package/dist/chat/ui/composer/use-chat-controls.d.ts +28 -0
  88. package/dist/chat/ui/composer/use-chat-controls.js +112 -0
  89. package/dist/chat/ui/composer/use-compaction-offer.d.ts +2 -0
  90. package/dist/chat/ui/composer/use-compaction-offer.js +23 -0
  91. package/dist/chat/ui/composer/use-composer-draft.d.ts +14 -0
  92. package/dist/chat/ui/composer/use-composer-draft.js +39 -0
  93. package/dist/chat/ui/composer/use-composer-menus.d.ts +52 -0
  94. package/dist/chat/ui/composer/use-composer-menus.js +232 -0
  95. package/dist/chat/ui/composer/use-composer-submit.d.ts +32 -0
  96. package/dist/chat/ui/composer/use-composer-submit.js +113 -0
  97. package/dist/chat/ui/composer/use-draft-files.d.ts +27 -0
  98. package/dist/chat/ui/composer/use-draft-files.js +111 -0
  99. package/dist/chat/ui/composer/use-notice.d.ts +6 -0
  100. package/dist/chat/ui/composer/use-notice.js +18 -0
  101. package/dist/chat/ui/composer/use-pending-requests.d.ts +2 -0
  102. package/dist/chat/ui/composer/use-pending-requests.js +22 -0
  103. package/dist/chat/ui/composer/use-prompt-history.d.ts +8 -0
  104. package/dist/chat/ui/composer/use-prompt-history.js +57 -0
  105. package/dist/chat/ui/composer/use-prompt-hold.d.ts +6 -0
  106. package/dist/chat/ui/composer/use-prompt-hold.js +48 -0
  107. package/dist/chat/ui/composer/use-take-back.d.ts +13 -0
  108. package/dist/chat/ui/composer/use-take-back.js +82 -0
  109. package/dist/chat/ui/diff/EditorEditDiff.js +4 -3
  110. package/dist/chat/ui/diff/LegacyDiffPool.js +3 -2
  111. package/dist/chat/ui/diff/LegacyEditDiff.js +3 -2
  112. package/dist/chat/ui/diff/LegacyUnifiedDiff.js +3 -2
  113. package/dist/chat/ui/diff/syntax-runtime.d.ts +2 -1
  114. package/dist/chat/ui/diff/syntax-runtime.js +9 -5
  115. package/dist/chat/ui/diff-syntax.d.ts +2 -2
  116. package/dist/chat/ui/diff-syntax.js +1 -2
  117. package/dist/chat/ui/diff-theme.js +10 -9
  118. package/dist/chat/ui/file-links.d.ts +1 -1
  119. package/dist/chat/ui/file-links.js +7 -5
  120. package/dist/chat/ui/highlighter.d.ts +27 -0
  121. package/dist/chat/ui/highlighter.js +151 -0
  122. package/dist/chat/ui/index.d.ts +67 -0
  123. package/dist/chat/ui/index.js +64 -0
  124. package/dist/chat/ui/intelligent-ui/UiReply.js +3 -2
  125. package/dist/chat/ui/intelligent-ui/renderers/content.js +3 -2
  126. package/dist/chat/ui/intelligent-ui/use-ui-links.js +2 -2
  127. package/dist/chat/ui/intelligent-ui/use-ui-queries.js +4 -3
  128. package/dist/chat/ui/markdown-blocks.d.ts +8 -0
  129. package/dist/chat/ui/markdown-blocks.js +260 -12
  130. package/dist/chat/ui/mermaid.d.ts +23 -0
  131. package/dist/chat/ui/mermaid.js +93 -0
  132. package/dist/chat/ui/rows/ForksRow.js +5 -3
  133. package/dist/chat/ui/rows/GeneratedImageRow.js +6 -4
  134. package/dist/chat/ui/rows/MessageRows.d.ts +4 -0
  135. package/dist/chat/ui/rows/MessageRows.js +36 -13
  136. package/dist/chat/ui/rows/Rows.js +3 -1
  137. package/dist/chat/ui/rows/SubagentRow.js +3 -2
  138. package/dist/chat/ui/rows/VisualRow.js +3 -2
  139. package/dist/chat/ui/rows/WorkRows.d.ts +6 -3
  140. package/dist/chat/ui/rows/WorkRows.js +57 -20
  141. package/dist/chat/ui/rows/WorkflowRow.js +3 -2
  142. package/dist/chat/ui/timestamp-format.d.ts +1 -0
  143. package/dist/chat/ui/timestamp-format.js +2 -0
  144. package/dist/chat/ui/use-diff-source-tokens.js +6 -3
  145. package/dist/chat/ui/visual-theme.js +3 -2
  146. package/dist/host.d.ts +9 -6
  147. package/dist/host.js +19 -8
  148. package/dist/index.d.ts +8 -0
  149. package/dist/index.js +9 -0
  150. package/dist/locales/en/agent-chat.d.ts +658 -0
  151. package/dist/locales/en/agent-chat.js +657 -0
  152. package/dist/locales/en/agent-prompts.d.ts +44 -0
  153. package/dist/locales/en/agent-prompts.js +43 -0
  154. package/dist/locales/en/agent-providers.d.ts +132 -0
  155. package/dist/locales/en/agent-providers.js +131 -0
  156. package/dist/locales/en/agent-usage.d.ts +132 -0
  157. package/dist/locales/en/agent-usage.js +131 -0
  158. package/dist/locales/nl/agent-chat.d.ts +658 -0
  159. package/dist/locales/nl/agent-chat.js +657 -0
  160. package/dist/locales/nl/agent-prompts.d.ts +44 -0
  161. package/dist/locales/nl/agent-prompts.js +43 -0
  162. package/dist/locales/nl/agent-providers.d.ts +132 -0
  163. package/dist/locales/nl/agent-providers.js +131 -0
  164. package/dist/locales/nl/agent-usage.d.ts +132 -0
  165. package/dist/locales/nl/agent-usage.js +131 -0
  166. package/dist/locales.js +9 -8
  167. package/dist/prompts/index.d.ts +11 -0
  168. package/dist/prompts/index.js +12 -0
  169. package/dist/prompts/logic/subjects.d.ts +2 -1
  170. package/dist/prompts/logic/subjects.js +6 -4
  171. package/dist/prompts/ui/ApprovalBody.js +6 -4
  172. package/dist/prompts/ui/PromptCard.js +5 -4
  173. package/dist/prompts/ui/PromptView.js +3 -2
  174. package/dist/prompts/ui/QuestionBody.js +3 -2
  175. package/dist/providers/AccountColors.js +2 -2
  176. package/dist/providers/AccountDetail.js +7 -6
  177. package/dist/providers/AccountVariables.js +3 -1
  178. package/dist/providers/AddAccountForm.js +3 -2
  179. package/dist/providers/ProvidersPane.js +4 -3
  180. package/dist/providers/account-actions.d.ts +4 -3
  181. package/dist/providers/account-actions.js +6 -7
  182. package/dist/providers/index.d.ts +16 -0
  183. package/dist/providers/index.js +17 -0
  184. package/dist/providers/provider-abilities.d.ts +2 -1
  185. package/dist/providers/provider-abilities.js +4 -0
  186. package/dist/state/chats.js +4 -2
  187. package/dist/state/index.d.ts +4 -0
  188. package/dist/state/index.js +5 -0
  189. package/dist/testing/index.d.ts +1 -0
  190. package/dist/testing/index.js +2 -0
  191. package/dist/{prompts/logic/prompts.fixtures.d.ts → testing/prompts.d.ts} +1 -1
  192. package/dist/theme.css +13 -3
  193. package/dist/usage/UsageBreakdown.js +3 -2
  194. package/dist/usage/UsageLimits.js +4 -3
  195. package/dist/usage/UsagePage.js +3 -2
  196. package/dist/usage/index.d.ts +15 -0
  197. package/dist/usage/index.js +15 -0
  198. package/package.json +53 -1031
  199. package/src/agents/AccountDot.tsx +3 -1
  200. package/src/agents/accounts.ts +6 -6
  201. package/src/chat/actions.ts +8 -5
  202. package/src/chat/bookmarks.ts +23 -14
  203. package/src/chat/chat-client.ts +8 -0
  204. package/src/chat/composer-send.ts +27 -0
  205. package/src/chat/forks.ts +27 -6
  206. package/src/chat/index.ts +98 -0
  207. package/src/chat/logic/embedded-code.ts +409 -0
  208. package/src/chat/logic/fork.ts +1 -0
  209. package/src/chat/logic/goal.ts +33 -0
  210. package/src/chat/logic/scrubber.ts +6 -5
  211. package/src/chat/logic/thought.ts +40 -0
  212. package/src/chat/logic/thread-cards.ts +2 -0
  213. package/src/chat/logic/timeline.ts +25 -1
  214. package/src/chat/logic/tool-catalog.ts +5 -1
  215. package/src/chat/logic/tools.ts +76 -0
  216. package/src/chat/preferences.ts +26 -3
  217. package/src/chat/reading-width.ts +39 -0
  218. package/src/chat/send-key.ts +38 -0
  219. package/src/chat/ui/BookmarkMarker.tsx +4 -2
  220. package/src/chat/ui/BookmarkSubmenu.tsx +4 -3
  221. package/src/chat/ui/ChatActivity.tsx +14 -10
  222. package/src/chat/ui/ChatReferenceChip.tsx +3 -4
  223. package/src/chat/ui/CodeBlock.tsx +33 -124
  224. package/src/chat/ui/Composer.tsx +162 -1324
  225. package/src/chat/ui/ComposerInput.tsx +5 -4
  226. package/src/chat/ui/EditDiff.tsx +3 -2
  227. package/src/chat/ui/GoalDock.tsx +104 -0
  228. package/src/chat/ui/LimitState.tsx +5 -3
  229. package/src/chat/ui/Markdown.tsx +113 -39
  230. package/src/chat/ui/MermaidBlock.tsx +119 -0
  231. package/src/chat/ui/MessageActions.tsx +13 -8
  232. package/src/chat/ui/Pickers.tsx +8 -7
  233. package/src/chat/ui/PromptComposer.tsx +4 -3
  234. package/src/chat/ui/RowTime.tsx +21 -0
  235. package/src/chat/ui/RunSettings.tsx +9 -4
  236. package/src/chat/ui/ShellCommand.tsx +134 -0
  237. package/src/chat/ui/SubagentInfo.tsx +4 -3
  238. package/src/chat/ui/SubagentStopButton.tsx +4 -3
  239. package/src/chat/ui/SubagentTimeline.tsx +5 -1
  240. package/src/chat/ui/Timeline.tsx +167 -105
  241. package/src/chat/ui/TimelineMenu.tsx +42 -7
  242. package/src/chat/ui/UnifiedDiff.tsx +3 -2
  243. package/src/chat/ui/VisualFrame.tsx +6 -7
  244. package/src/chat/ui/code-theme.ts +4 -4
  245. package/src/chat/ui/composer/ComposerControls.tsx +147 -0
  246. package/src/chat/ui/composer/ComposerMenu.tsx +138 -0
  247. package/src/chat/ui/composer/DraftParts.tsx +86 -0
  248. package/src/chat/ui/composer/QueuedMessages.tsx +64 -0
  249. package/src/chat/ui/composer/key-handler.ts +174 -0
  250. package/src/chat/ui/composer/keys.ts +32 -10
  251. package/src/chat/ui/composer/placeholder.ts +48 -0
  252. package/src/chat/ui/composer/use-chat-controls.ts +134 -0
  253. package/src/chat/ui/composer/use-compaction-offer.ts +25 -0
  254. package/src/chat/ui/composer/use-composer-draft.ts +71 -0
  255. package/src/chat/ui/composer/use-composer-menus.ts +289 -0
  256. package/src/chat/ui/composer/use-composer-submit.ts +163 -0
  257. package/src/chat/ui/composer/use-draft-files.ts +135 -0
  258. package/src/chat/ui/composer/use-notice.ts +28 -0
  259. package/src/chat/ui/composer/use-pending-requests.ts +23 -0
  260. package/src/chat/ui/composer/use-prompt-history.ts +76 -0
  261. package/src/chat/ui/composer/use-prompt-hold.ts +58 -0
  262. package/src/chat/ui/composer/use-take-back.ts +91 -0
  263. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +2 -2
  264. package/src/chat/ui/diff/EditorEditDiff.tsx +4 -3
  265. package/src/chat/ui/diff/LegacyDiffPool.tsx +3 -2
  266. package/src/chat/ui/diff/LegacyEditDiff.tsx +3 -2
  267. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +3 -2
  268. package/src/chat/ui/diff/syntax-runtime.ts +10 -5
  269. package/src/chat/ui/diff-syntax.ts +2 -2
  270. package/src/chat/ui/diff-theme.ts +10 -9
  271. package/src/chat/ui/file-links.ts +7 -5
  272. package/src/chat/ui/highlighter.ts +185 -0
  273. package/src/chat/ui/index.ts +109 -0
  274. package/src/chat/ui/intelligent-ui/UiReply.tsx +5 -4
  275. package/src/chat/ui/intelligent-ui/renderers/content.tsx +3 -2
  276. package/src/chat/ui/intelligent-ui/use-ui-links.ts +2 -2
  277. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +4 -3
  278. package/src/chat/ui/markdown-blocks.ts +300 -12
  279. package/src/chat/ui/mermaid.ts +118 -0
  280. package/src/chat/ui/rows/ForksRow.tsx +5 -3
  281. package/src/chat/ui/rows/GeneratedImageRow.tsx +6 -4
  282. package/src/chat/ui/rows/MessageRows.tsx +73 -18
  283. package/src/chat/ui/rows/Rows.tsx +14 -1
  284. package/src/chat/ui/rows/SubagentRow.tsx +3 -2
  285. package/src/chat/ui/rows/VisualRow.tsx +3 -2
  286. package/src/chat/ui/rows/WorkRows.tsx +148 -47
  287. package/src/chat/ui/rows/WorkflowRow.tsx +3 -2
  288. package/src/chat/ui/timestamp-format.ts +2 -0
  289. package/src/chat/ui/use-diff-source-tokens.ts +10 -4
  290. package/src/chat/ui/visual-theme.ts +3 -2
  291. package/src/host.ts +31 -15
  292. package/src/index.ts +49 -0
  293. package/src/locales/en/agent-chat.ts +657 -0
  294. package/src/locales/en/agent-prompts.ts +43 -0
  295. package/src/locales/en/agent-providers.ts +131 -0
  296. package/src/locales/en/agent-usage.ts +131 -0
  297. package/src/locales/nl/agent-chat.ts +657 -0
  298. package/src/locales/nl/agent-prompts.ts +43 -0
  299. package/src/locales/nl/agent-providers.ts +131 -0
  300. package/src/locales/nl/agent-usage.ts +132 -0
  301. package/src/locales.ts +9 -8
  302. package/src/prompts/index.ts +35 -0
  303. package/src/prompts/logic/subjects.ts +12 -4
  304. package/src/prompts/ui/ApprovalBody.tsx +6 -4
  305. package/src/prompts/ui/PromptCard.tsx +11 -4
  306. package/src/prompts/ui/PromptView.tsx +3 -2
  307. package/src/prompts/ui/QuestionBody.tsx +3 -2
  308. package/src/providers/AccountColors.tsx +2 -2
  309. package/src/providers/AccountDetail.tsx +13 -7
  310. package/src/providers/AccountVariables.tsx +3 -1
  311. package/src/providers/AddAccountForm.tsx +3 -2
  312. package/src/providers/ProvidersPane.tsx +4 -3
  313. package/src/providers/account-actions.ts +7 -7
  314. package/src/providers/index.ts +17 -0
  315. package/src/providers/provider-abilities.ts +6 -1
  316. package/src/state/chats.ts +4 -2
  317. package/src/state/index.ts +39 -0
  318. package/src/testing/index.ts +2 -0
  319. package/src/{prompts/logic/prompts.fixtures.ts → testing/prompts.ts} +1 -1
  320. package/src/theme.css +13 -3
  321. package/src/usage/UsageBreakdown.tsx +3 -2
  322. package/src/usage/UsageLimits.tsx +4 -3
  323. package/src/usage/UsagePage.tsx +3 -2
  324. package/src/usage/index.ts +16 -0
  325. package/dist/locales/en/agent-chat.json +0 -597
  326. package/dist/locales/en/agent-prompts.json +0 -43
  327. package/dist/locales/en/agent-providers.json +0 -131
  328. package/dist/locales/en/agent-usage.json +0 -131
  329. package/dist/locales/nl/agent-chat.json +0 -597
  330. package/dist/locales/nl/agent-prompts.json +0 -43
  331. package/dist/locales/nl/agent-providers.json +0 -131
  332. package/dist/locales/nl/agent-usage.json +0 -131
  333. package/src/locales/en/agent-chat.json +0 -597
  334. package/src/locales/en/agent-prompts.json +0 -43
  335. package/src/locales/en/agent-providers.json +0 -131
  336. package/src/locales/en/agent-usage.json +0 -131
  337. package/src/locales/nl/agent-chat.json +0 -597
  338. package/src/locales/nl/agent-prompts.json +0 -43
  339. package/src/locales/nl/agent-providers.json +0 -131
  340. package/src/locales/nl/agent-usage.json +0 -131
  341. /package/dist/{prompts/logic/prompts.fixtures.js → testing/prompts.js} +0 -0
@@ -1,40 +1,176 @@
1
1
  const FENCE = /^([ \t]*)(`{3,}|~{3,})(.*)$/;
2
+ const CLOSING_FENCE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
2
3
  // Spaces and tabs only: a line that holds a no-break space is text to markdown, not a blank line.
3
4
  const BLANK = /^[ \t]*$/;
4
5
  // A line that continues what stood before the blank line: an indented paragraph of a list item, or
5
6
  // the next item of a loose list. Splitting there would change what the markdown means.
6
7
  const CONTINUES = /^([ \t]|[-*+][ \t]|\d+[.)][ \t])/;
8
+ const LIST_MARKER = /^[ \t]*(?:([-*+])|(\d{1,9})([.)]))(?:([ \t]+)(.*))?$/;
9
+ // A list item with nothing after its marker.
10
+ const EMPTY_ITEM = /^ {0,3}(?:[-*+]|\d{1,9}[.)])[ \t]*$/;
11
+ const THEMATIC_BREAK = /^ {0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$/;
12
+ // A heading or a quote, which ends a list item instead of continuing its paragraph.
13
+ const ENDS_ITEM = /^ {0,3}(?:#{1,6}(?:[ \t]|$)|>)/;
14
+ const HTML_START = /^ {0,3}<[A-Za-z/!?]/;
15
+ // The HTML that runs on until a marker rather than a blank line, with that marker.
16
+ const HTML_UNTIL = [
17
+ [/^ {0,3}<(?:pre|script|style|textarea)(?:[ \t>]|$)/i, /<\/(?:pre|script|style|textarea)>/i],
18
+ [/^ {0,3}<!--/, /-->/],
19
+ [/^ {0,3}<\?/, /\?>/],
20
+ [/^ {0,3}<![A-Za-z]/, />/],
21
+ [/^ {0,3}<!\[CDATA\[/, /\]\]>/]
22
+ ];
7
23
  // A reference or footnote definition may be used anywhere in the text, so a block cannot stand alone.
8
24
  const DEFINITION = /^ {0,3}\[[^\]]+\]:/m;
25
+ // Markdown ends a line at a lone carriage return too, which a split at line feeds would not see.
26
+ const LONE_RETURN = /\r(?!\n|$)/;
9
27
  // How much deeper than its opening a closing fence may sit; deeper than that it is a line of code.
10
28
  const CLOSING_INDENT = 3;
29
+ // From four columns in, a line is indented code rather than the start of a block.
30
+ const CODE_INDENT = 4;
31
+ const TAB_STOP = 4;
11
32
  // A section title: an ATX heading, or a line of only bold text, which models write as a heading too.
12
33
  const HEADING = /^ {0,3}#{1,6}(?:[ \t]|$)/;
13
34
  const BOLD_LINE = /^ {0,3}\*\*(?:[^*]|\*(?!\*))+\*\*:?[ \t]*$/;
14
35
  /*
15
36
  * A reply cut into blocks at blank lines outside a code fence, so each block parses on its own and
16
- * only the one still growing is parsed again. Joined back together the blocks are the text.
37
+ * only the one still growing is parsed again. A code fence at the top level is a block of its own,
38
+ * through its closing line and the blank lines under it, so it can be drawn without parsing at all.
39
+ * Joined back together the blocks are the text.
17
40
  */
18
41
  export function splitMarkdownBlocks(text) {
19
- if (DEFINITION.test(text)) {
42
+ if (DEFINITION.test(text) || LONE_RETURN.test(text)) {
20
43
  return [{ text, openFence: isFenceOpenAtEnd(text) }];
21
44
  }
22
45
  const lines = text.split('\n');
23
46
  const blocks = [];
24
47
  let start = 0;
25
48
  let offset = 0;
26
- let fence = null;
49
+ // The fence as a reading line by line sees it, blind to lists and indented code. A blank line is
50
+ // only cut at where this reading agrees, so every such cut is one this split made before it
51
+ // followed fences any further.
52
+ let lineFence = null;
27
53
  let afterBlank = false;
28
- lines.forEach((line, index) => {
29
- if (fence === null && afterBlank && !BLANK.test(line) && !CONTINUES.test(line)) {
30
- blocks.push({ text: text.slice(start, offset), openFence: false });
31
- start = offset;
54
+ // A fence inside a list item.
55
+ let fence = null;
56
+ let top = null;
57
+ // A top-level fence that has closed; its block takes the blank lines under it.
58
+ let closed = null;
59
+ let item = null;
60
+ // The marker that ends the raw HTML the line stands in.
61
+ let html = null;
62
+ // Whether the top level is still known here. Past HTML, or a line that may or may not end a list
63
+ // item, a fence is left inside the block it stands in, which markdown parses as a whole.
64
+ let known = true;
65
+ // Where the line above starts when it is blank.
66
+ let blankAbove = null;
67
+ const cutAt = (end) => {
68
+ if (end > start) {
69
+ blocks.push({ text: text.slice(start, end), openFence: false });
32
70
  }
33
- fence = nextFence(fence, line);
34
- afterBlank = fence === null && BLANK.test(line) && index > 0;
71
+ start = end;
72
+ };
73
+ for (const [index, line] of lines.entries()) {
74
+ const at = offset;
35
75
  offset += line.length + 1;
36
- });
37
- blocks.push({ text: text.slice(start), openFence: fence !== null });
76
+ const content = withoutReturn(line);
77
+ const blank = BLANK.test(line);
78
+ // Blank to markdown, which a line ending in a carriage return is too.
79
+ const empty = BLANK.test(content);
80
+ const blankCut = lineFence === null && afterBlank && !blank && !CONTINUES.test(line);
81
+ lineFence = nextFence(lineFence, line);
82
+ afterBlank = lineFence === null && blank && index > 0;
83
+ if (top !== null) {
84
+ if (closesFence(top.marker, content)) {
85
+ closed = { fence: top, source: text.slice(top.from, at + content.length) };
86
+ top = null;
87
+ }
88
+ else if (index < lines.length - 1 || line !== '') {
89
+ // The empty line after a line ending at the end of the text is no line of the fence.
90
+ top.body += (top.ending ?? '') + outdent(content, top.indent);
91
+ top.ending = line.length > content.length ? '\r\n' : '\n';
92
+ }
93
+ continue;
94
+ }
95
+ if (closed !== null) {
96
+ if (empty) {
97
+ continue;
98
+ }
99
+ blocks.push(fenceBlock(text.slice(start, at), closed.fence, closed.source, true));
100
+ start = at;
101
+ closed = null;
102
+ }
103
+ if (html !== null) {
104
+ // Raw HTML holds no fence and no blank line to cut at until its marker.
105
+ if (html.test(content)) {
106
+ html = null;
107
+ }
108
+ continue;
109
+ }
110
+ if (known && fence !== null && item !== null && !empty && columns(content) < item.column) {
111
+ // Code is no paragraph a line may continue lazily, so a line left of the item's content ends
112
+ // the item and its code. A next item of the same list keeps the list going.
113
+ fence = null;
114
+ if (!LIST_MARKER.test(content)) {
115
+ item = null;
116
+ }
117
+ }
118
+ if (blankCut && fence === null) {
119
+ cutAt(at);
120
+ }
121
+ const above = index > 0 ? withoutReturn(lines[index - 1]) : '';
122
+ const opening = known && fence === null ? openingFence(content) : null;
123
+ if (opening !== null && EMPTY_ITEM.test(above)) {
124
+ // Markdown reads an empty item right above a fence as a list or as text depending on what
125
+ // stands before it, so the two stay in one block.
126
+ known = false;
127
+ }
128
+ else if (opening !== null && opening.indent < CODE_INDENT && (item === null || opening.indent < item.column)) {
129
+ // The blank line right above goes with the fence. Code in a list item this fence ends takes
130
+ // every blank line under it but the last, and at the end of a block it would take them all.
131
+ cutAt(blankAbove !== null && blankAbove > start ? blankAbove : at);
132
+ // Three columns at most, so the indentation is spaces.
133
+ top = { ...opening, from: at + opening.indent, body: '', ending: null };
134
+ item = null;
135
+ blankAbove = null;
136
+ continue;
137
+ }
138
+ if (known && item?.empty === true && empty) {
139
+ // An item with nothing on its first line ends at a blank line right under it.
140
+ item = null;
141
+ }
142
+ if (known && fence === null && !empty) {
143
+ // A fence in an item is followed only while the item is known to be open, and less than four
144
+ // columns deeper than its content: deeper it is code of the item, or of an item inside it.
145
+ if (opening !== null && item !== null && (!item.certain || opening.indent < item.column || opening.indent >= item.column + CODE_INDENT)) {
146
+ known = false;
147
+ }
148
+ else if (opening === null) {
149
+ item = nextItem(item, content, index > 0 && !BLANK.test(above));
150
+ }
151
+ }
152
+ if (fence === null && HTML_START.test(content)) {
153
+ known = false;
154
+ const until = HTML_UNTIL.find(([begins]) => begins.test(content))?.[1];
155
+ html = until !== undefined && !until.test(content) ? until : null;
156
+ }
157
+ if (known && (fence !== null || item !== null)) {
158
+ fence = nextFence(fence, fence === null ? itemContent(content) : content);
159
+ }
160
+ if (item?.empty === true && !empty && columns(content) >= item.column) {
161
+ item = { ...item, empty: false };
162
+ }
163
+ blankAbove = empty ? at : null;
164
+ }
165
+ if (top !== null) {
166
+ blocks.push(fenceBlock(text.slice(start), top, text.slice(top.from), false));
167
+ }
168
+ else if (closed !== null) {
169
+ blocks.push(fenceBlock(text.slice(start), closed.fence, closed.source, true));
170
+ }
171
+ else {
172
+ blocks.push({ text: text.slice(start), openFence: (known ? fence : lineFence) !== null });
173
+ }
38
174
  return blocks;
39
175
  }
40
176
  /*
@@ -97,7 +233,119 @@ function nextFence(fence, line) {
97
233
  function isFenceOpenAtEnd(text) {
98
234
  let fence = null;
99
235
  for (const line of text.split('\n')) {
100
- fence = nextFence(fence, line);
236
+ fence = nextFence(fence, withoutReturn(line));
101
237
  }
102
238
  return fence !== null;
103
239
  }
240
+ /* The fence a line opens wherever it stands, with its indentation in columns. */
241
+ function openingFence(content) {
242
+ const match = FENCE.exec(content);
243
+ if (!match) {
244
+ return null;
245
+ }
246
+ const marker = match[2];
247
+ const info = match[3].replace(/^[ \t]+|[ \t]+$/g, '');
248
+ // A backtick fence's info string may not hold a backtick, or the line is inline code.
249
+ if (marker[0] === '`' && info.includes('`')) {
250
+ return null;
251
+ }
252
+ return { marker, info, indent: columns(content), direct: !/[\\&]/.test(info.split(/[ \t]/, 1)[0]) };
253
+ }
254
+ /* A line without the carriage return of a CRLF line ending. */
255
+ function withoutReturn(line) {
256
+ return line.endsWith('\r') ? line.slice(0, -1) : line;
257
+ }
258
+ function closesFence(marker, content) {
259
+ const run = CLOSING_FENCE.exec(content)?.[1];
260
+ return run !== undefined && run[0] === marker[0] && run.length >= marker.length;
261
+ }
262
+ function fenceBlock(text, fence, source, closed) {
263
+ if (!fence.direct) {
264
+ return { text, openFence: !closed };
265
+ }
266
+ return { text, openFence: !closed, fence: { marker: fence.marker, info: fence.info, body: fence.body, closed, source } };
267
+ }
268
+ /* The list item a line that is neither blank nor a fence leaves open, as far as a fence needs to know. */
269
+ function nextItem(item, content, underLine) {
270
+ const indent = columns(content);
271
+ if (item !== null && indent >= item.column) {
272
+ return item;
273
+ }
274
+ if (THEMATIC_BREAK.test(content) || ENDS_ITEM.test(content)) {
275
+ return null;
276
+ }
277
+ const marker = indent < CODE_INDENT ? itemMarker(content) : null;
278
+ if (marker === null) {
279
+ if (item === null || !underLine) {
280
+ return null;
281
+ }
282
+ // A line right under an item may continue its paragraph, or follow an item that ends in something else.
283
+ return { ...item, certain: false };
284
+ }
285
+ // Right under another line, only a bullet or a 1 with text after it surely starts a list; anything
286
+ // else may be text of a paragraph above.
287
+ const certain = !underLine || (item !== null && item.certain && item.kind === marker.kind) || (marker.first && marker.rest !== '');
288
+ return { column: marker.column, kind: marker.kind, certain, empty: marker.rest === '' };
289
+ }
290
+ /* The marker a list item starts with: its kind, whether it may start a list under text, and the column its content starts at. */
291
+ function itemMarker(content) {
292
+ const match = LIST_MARKER.exec(content);
293
+ if (match === null) {
294
+ return null;
295
+ }
296
+ const [, bullet, number, delimiter, spacing = '', rest = ''] = match;
297
+ const indent = columns(content);
298
+ const width = bullet !== undefined ? 1 : number.length + 1;
299
+ // The content starts after the spaces behind the marker, unless there are five or more: then the
300
+ // item starts with indented code one column after the marker.
301
+ const gap = rest === '' ? 1 : columns(spacing, indent + width);
302
+ const column = indent + width + (gap > CODE_INDENT ? 1 : gap);
303
+ return { kind: bullet ?? delimiter, first: bullet !== undefined || Number(number) === 1, column, rest };
304
+ }
305
+ /* A list item's line with its marker as spaces, so a fence on that line is seen where the item's content starts. */
306
+ function itemContent(content) {
307
+ const marker = itemMarker(content);
308
+ return marker === null ? content : ' '.repeat(marker.column) + marker.rest;
309
+ }
310
+ /* The columns of the indentation of a line, which starts at column `from`; a tab reaches to the next stop of four. */
311
+ function columns(line, from = 0) {
312
+ let column = from;
313
+ for (const character of line) {
314
+ if (character === '\t') {
315
+ column += TAB_STOP - (column % TAB_STOP);
316
+ }
317
+ else if (character === ' ') {
318
+ column += 1;
319
+ }
320
+ else {
321
+ break;
322
+ }
323
+ }
324
+ return column - from;
325
+ }
326
+ /*
327
+ * A line of a fence less up to `indent` columns of indentation. A tab reaches to the next stop of
328
+ * four, and what of it lies past `indent` stays as spaces, as markdown reads it.
329
+ */
330
+ function outdent(line, indent) {
331
+ let column = 0;
332
+ let index = 0;
333
+ while (column < indent && index < line.length) {
334
+ const character = line[index];
335
+ if (character === '\t') {
336
+ const stop = column + TAB_STOP - (column % TAB_STOP);
337
+ if (stop > indent) {
338
+ return ' '.repeat(stop - indent) + line.slice(index + 1);
339
+ }
340
+ column = stop;
341
+ }
342
+ else if (character === ' ') {
343
+ column += 1;
344
+ }
345
+ else {
346
+ break;
347
+ }
348
+ index += 1;
349
+ }
350
+ return line.slice(index);
351
+ }
@@ -0,0 +1,23 @@
1
+ import type { MermaidConfig } from 'mermaid';
2
+ export interface MermaidColors {
3
+ ground: string;
4
+ node: string;
5
+ nodeBorder: string;
6
+ text: string;
7
+ line: string;
8
+ cluster: string;
9
+ clusterBorder: string;
10
+ note: string;
11
+ }
12
+ export declare const MERMAID_TOKENS: Readonly<Record<keyof MermaidColors, string>>;
13
+ export declare function mermaidConfig(colors: MermaidColors, dark: boolean, font: {
14
+ family: string;
15
+ size: string;
16
+ }): MermaidConfig;
17
+ export interface SizedSvg {
18
+ svg: string;
19
+ width: number;
20
+ height: number;
21
+ }
22
+ export declare function sizedSvg(svg: string): SizedSvg | null;
23
+ export declare function svgDataUrl(svg: string): string;
@@ -0,0 +1,93 @@
1
+ /* The token each color reads. Borders carry an alpha, so they are resolved over the ground. */
2
+ export const MERMAID_TOKENS = {
3
+ ground: '--chat-code-bg',
4
+ node: '--surface-raised',
5
+ nodeBorder: '--border-strong',
6
+ text: '--text',
7
+ line: '--text-muted',
8
+ cluster: '--surface',
9
+ clusterBorder: '--border',
10
+ note: '--accent-soft'
11
+ };
12
+ // Mermaid's own list plus what keeps a diagram in the theme and out of HTML: a directive in the text cannot change these.
13
+ const SECURE_KEYS = [
14
+ 'secure',
15
+ 'securityLevel',
16
+ 'startOnLoad',
17
+ 'maxTextSize',
18
+ 'maxEdges',
19
+ 'suppressErrorRendering',
20
+ 'htmlLabels',
21
+ 'theme',
22
+ 'themeVariables',
23
+ 'themeCSS',
24
+ 'darkMode',
25
+ 'fontFamily'
26
+ ];
27
+ /*
28
+ * Strict: no HTML in labels, no click handlers, no links that run script. What comes out is drawn as an
29
+ * image as well, which never runs script and loads nothing, whatever the text of the diagram says.
30
+ */
31
+ export function mermaidConfig(colors, dark, font) {
32
+ return {
33
+ startOnLoad: false,
34
+ securityLevel: 'strict',
35
+ htmlLabels: false,
36
+ suppressErrorRendering: true,
37
+ secure: SECURE_KEYS,
38
+ theme: 'base',
39
+ darkMode: dark,
40
+ fontFamily: font.family,
41
+ themeVariables: {
42
+ darkMode: dark,
43
+ fontFamily: font.family,
44
+ fontSize: font.size,
45
+ background: colors.ground,
46
+ primaryColor: colors.node,
47
+ primaryTextColor: colors.text,
48
+ primaryBorderColor: colors.nodeBorder,
49
+ secondaryColor: colors.note,
50
+ tertiaryColor: colors.cluster,
51
+ mainBkg: colors.node,
52
+ nodeBorder: colors.nodeBorder,
53
+ textColor: colors.text,
54
+ titleColor: colors.text,
55
+ lineColor: colors.line,
56
+ clusterBkg: colors.cluster,
57
+ clusterBorder: colors.clusterBorder,
58
+ edgeLabelBackground: colors.ground,
59
+ noteBkgColor: colors.note,
60
+ noteTextColor: colors.text,
61
+ noteBorderColor: colors.nodeBorder
62
+ }
63
+ };
64
+ }
65
+ const ROOT = /<svg\b[^>]*>/;
66
+ const VIEW_BOX = /\sviewBox="([^"]*)"/;
67
+ const SIZE_ATTRIBUTES = /\s(?:width|height|style)="[^"]*"/g;
68
+ /*
69
+ * The diagram at the size it was laid out at. Mermaid fits it to the width of its container instead,
70
+ * which an image does not have, so the root takes the size its view box names. Null for anything
71
+ * without one.
72
+ */
73
+ export function sizedSvg(svg) {
74
+ const root = ROOT.exec(svg);
75
+ const viewBox = root === null ? null : VIEW_BOX.exec(root[0]);
76
+ if (root === null || viewBox === null) {
77
+ return null;
78
+ }
79
+ const [, , boxWidth, boxHeight] = viewBox[1]
80
+ .trim()
81
+ .split(/[\s,]+/)
82
+ .map(Number);
83
+ if (!Number.isFinite(boxWidth) || !Number.isFinite(boxHeight) || boxWidth <= 0 || boxHeight <= 0) {
84
+ return null;
85
+ }
86
+ const width = Math.ceil(boxWidth);
87
+ const height = Math.ceil(boxHeight);
88
+ const tag = root[0].replace(SIZE_ATTRIBUTES, '').replace(/^<svg\b/, `<svg width="${width}" height="${height}"`);
89
+ return { svg: svg.slice(0, root.index) + tag + svg.slice(root.index + root[0].length), width, height };
90
+ }
91
+ export function svgDataUrl(svg) {
92
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
93
+ }
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
3
3
  import { ChevronRight, GitFork } from 'lucide-react';
4
4
  import { useForkIdsAfter } from '../../forks.js';
5
5
  import { ROW_GUTTER } from '../icons.js';
6
- import { chatHost } from '../../../host.js';
6
+ import { useChatHost } from '../../../host.js';
7
7
  import { Icon, Menu } from '@adecore/ui';
8
8
  const LINE = '-mx-1 mb-0.5 flex h-7 items-center gap-2 rounded-md px-1 text-xs text-text-muted';
9
9
  const LINK = `${LINE} hover:bg-surface-hover hover:text-text data-[popup-open]:bg-surface-hover data-[popup-open]:text-text`;
@@ -28,11 +28,13 @@ function ForksLabel({ label, link = true }) {
28
28
  }
29
29
  // A fork no longer in this project has nowhere to lead, so it only says it is there.
30
30
  function OneFork({ forkId, label }) {
31
- const place = chatHost().useChatPlace(forkId);
31
+ const host = useChatHost();
32
+ const place = host.useChatPlace(forkId);
32
33
  return (_jsx("div", { className: "flex", children: place.title === null ? (_jsx("span", { className: LINE, children: _jsx(ForksLabel, { label: label, link: false }) })) : (_jsx("button", { className: LINK, onClick: place.go, children: _jsx(ForksLabel, { label: label }) })) }));
33
34
  }
34
35
  function ForkItem({ forkId }) {
36
+ const host = useChatHost();
35
37
  const { t } = useTranslation('agent-chat');
36
- const place = chatHost().useChatPlace(forkId);
38
+ const place = host.useChatPlace(forkId);
37
39
  return (_jsxs(Menu.Item, { disabled: place.title === null, onClick: place.go, children: [_jsx(Icon, { icon: GitFork, size: 14 }), " ", _jsx("span", { className: "truncate", children: place.title ?? t('fork.pill.label') })] }));
38
40
  }
@@ -4,14 +4,14 @@ import clsx from 'clsx';
4
4
  import { Trans, useTranslation } from 'react-i18next';
5
5
  import { Check, Copy, Ellipsis, ExternalLink, FolderDown, ImageIcon, Maximize2 } from 'lucide-react';
6
6
  import { Button, ButtonGroup, copyText, ErrorBoundary, FileIcon, Icon, IconButton, Menu, Surface } from '@adecore/ui';
7
- import { chatHost } from '../../../host.js';
7
+ import { useChatHost } from '../../../host.js';
8
8
  import { useChatScope } from '../../../scope.js';
9
9
  import { useCurrentItem } from '../../../state/chats.js';
10
10
  import { formatBytes } from '../../attachments.js';
11
11
  import { attachmentAspect, generatedImageView } from '../../logic/generated-image.js';
12
12
  import { toolStartedAt } from '../../logic/tools.js';
13
13
  import { CHIP_IN_MESSAGE, MENTION_TONE } from '../chips.js';
14
- import { openFileLink, useFileLinkCwd } from '../file-links.js';
14
+ import { useFileLinkCwd, useOpenFileLink } from '../file-links.js';
15
15
  import { ImageThumb } from '../ImageView.js';
16
16
  import { ROW_GUTTER } from '../icons.js';
17
17
  import { RunningFor, WorkRow } from './WorkRows.js';
@@ -50,8 +50,10 @@ function HeadLine({ label, detail, live, startedAt }) {
50
50
  function SavedFile({ path }) {
51
51
  const cwd = useFileLinkCwd();
52
52
  const { id } = useChatScope();
53
+ const { fileLinks } = useChatHost();
54
+ const openFileLink = useOpenFileLink();
53
55
  const chip = (_jsxs(_Fragment, { children: [_jsx(FileIcon, { path: path, size: 14 }), _jsx("span", { className: "truncate", children: path })] }));
54
- if (chatHost().fileLinks === null) {
56
+ if (fileLinks === null) {
55
57
  return _jsx("span", { className: clsx(CHIP_IN_MESSAGE, MENTION_TONE), children: chip });
56
58
  }
57
59
  return (_jsx("button", { type: "button", className: clsx(CHIP_IN_MESSAGE, MENTION_TONE, 'hover:brightness-95'), "data-file-path": path, onClick: () => openFileLink(cwd, { path, directory: false }, id), children: chip }));
@@ -59,7 +61,7 @@ function SavedFile({ path }) {
59
61
  function ReadyImage({ chatId, attachment, prompt, transparent }) {
60
62
  const { t } = useTranslation('agent-chat');
61
63
  const { id: scopeId } = useChatScope();
62
- const { attachments } = chatHost();
64
+ const { attachments } = useChatHost();
63
65
  const source = attachments.useUrl(scopeId, chatId, attachment.id);
64
66
  const savedKey = `${scopeId}\n${chatId}\n${attachment.id}`;
65
67
  const [lightbox, setLightbox] = useState(false);
@@ -19,6 +19,10 @@ export declare function ThinkingRow({ chatId, item: derived }: {
19
19
  chatId: string;
20
20
  item: ChatThinkingItem;
21
21
  }): import("react").JSX.Element;
22
+ export declare function LiveThoughtRow({ chatId, item: derived }: {
23
+ chatId: string;
24
+ item: ChatThinkingItem;
25
+ }): import("react").JSX.Element | null;
22
26
  export declare function NoteRow({ id, level, text, from }: {
23
27
  id: string;
24
28
  level: 'info' | 'warning' | 'error';
@@ -6,21 +6,22 @@ import { Bot, Brain, Check, ChevronDown, CircleAlert, File, Info, MessageCircleQ
6
6
  import { WAITING_NOTE_PREFIX } from '@adecore/agent-contracts';
7
7
  import { fileBadge, formatBytes, isImageAttachment } from '../../attachments.js';
8
8
  import { useChatRow, useCurrentItem } from '../../../state/chats.js';
9
- import { chatHost } from '../../../host.js';
9
+ import { useChatHost } from '../../../host.js';
10
10
  import { useChatScope } from '../../../scope.js';
11
11
  import { splitQuote } from '../../quote.js';
12
12
  import { jumpToTimelineItem } from '../../timeline-scroll.js';
13
13
  import { useProviders } from '../../../state/providers.js';
14
14
  import { ChatReferenceChip } from '../ChatReferenceChip.js';
15
15
  import { ImageThumb } from '../ImageView.js';
16
- import { MessageMarkdown, ReplyMarkdown } from '../Markdown.js';
16
+ import { Markdown, MessageMarkdown, ReplyMarkdown } from '../Markdown.js';
17
17
  import { FadingWords } from '../FadingWords.js';
18
18
  import { settledBlocksText } from '../markdown-blocks.js';
19
19
  import { WHOLE_FADE_CLASS } from '../rehype-fade.js';
20
20
  import { useRevealedText } from '../reveal.js';
21
21
  import { formatAgo, formatElapsedShort, formatMoment, formatTokens } from '@adecore/ui/format';
22
- import { Icon, Tooltip, useTickingText } from '@adecore/ui';
23
- import { toolSummary } from '../../logic/tools.js';
22
+ import { Icon, MiddleTruncate, Tooltip, useTickingText } from '@adecore/ui';
23
+ import { thoughtLine, thoughtPreview } from '../../logic/thought.js';
24
+ import { toolSummary, toolSummaryIsPath } from '../../logic/tools.js';
24
25
  import { ROW_GUTTER } from '../icons.js';
25
26
  import { useOpenForFind } from '../find-reveal.js';
26
27
  import { ReplyContext, UiReplyNavigationContext } from '../reply-context.js';
@@ -31,9 +32,10 @@ const USER_FOLD_CHARS = 600;
31
32
  /* A folded user prompt fades out at the bottom instead of cutting a line in half. */
32
33
  const FOLD = 'max-h-[10em] overflow-hidden [mask-image:linear-gradient(to_bottom,black_70%,transparent)]';
33
34
  function useReplyAuthor(chatId) {
35
+ const host = useChatHost();
34
36
  const { id } = useChatScope();
35
37
  const context = useContext(ReplyContext);
36
- const author = chatHost().useReplyAuthor(id, context?.chatId ?? chatId);
38
+ const author = host.useReplyAuthor(id, context?.chatId ?? chatId);
37
39
  return context !== null && context.chatId === undefined ? null : author;
38
40
  }
39
41
  /*
@@ -41,15 +43,17 @@ function useReplyAuthor(chatId) {
41
43
  * hands a link that opens a window to the system browser, which cannot reach a blob of this page.
42
44
  */
43
45
  function AttachmentLink({ chatId, attachment }) {
46
+ const host = useChatHost();
44
47
  const { id } = useChatScope();
45
- const { url } = chatHost().attachments.useUrl(id, chatId, attachment.id);
48
+ const { url } = host.attachments.useUrl(id, chatId, attachment.id);
46
49
  const badge = fileBadge(attachment.name);
47
50
  return (_jsx(Tooltip, { label: attachment.name, children: _jsxs("a", { href: url ?? undefined, download: attachment.name, target: "_blank", rel: "noreferrer", "aria-disabled": url === null, className: "flex h-28 w-36 flex-col rounded-xl border border-border bg-surface-raised bg-clip-padding p-2.5 text-left hover:bg-surface-hover", children: [_jsx("span", { className: "flex h-6 min-w-6 items-center justify-center self-start rounded-md border border-border px-1.5 text-xs font-medium text-text-muted", children: badge ?? _jsx(Icon, { icon: File, size: 12 }) }), _jsx("span", { className: "mt-auto line-clamp-2 text-xs break-all text-text", children: attachment.name }), _jsx("span", { className: "text-xs text-text-faint", children: formatBytes(attachment.size) })] }) }));
48
51
  }
49
52
  /* A picture attached to a message, which opens large. */
50
53
  function AttachmentThumb({ chatId, attachment }) {
54
+ const host = useChatHost();
51
55
  const { id } = useChatScope();
52
- const source = chatHost().attachments.useUrl(id, chatId, attachment.id);
56
+ const source = host.attachments.useUrl(id, chatId, attachment.id);
53
57
  return _jsx(ImageThumb, { source: source, alt: attachment.name, className: "max-h-32 max-w-48 object-cover" });
54
58
  }
55
59
  export function UserRow({ chatId, item }) {
@@ -139,11 +143,12 @@ function Writing() {
139
143
  * written fades anything: an old reply scrolled back into view just stands there.
140
144
  */
141
145
  export function AssistantRow({ chatId, item: derived }) {
146
+ const host = useChatHost();
142
147
  const item = useCurrentItem(chatId, derived);
143
148
  const scope = useChatScope();
144
149
  const context = useContext(ReplyContext);
145
150
  const reply = useMemo(() => (context === null && !item.parentToolUseId ? { scopeId: scope.id, chatId, itemId: item.id, streaming: item.streaming } : undefined), [context, item.parentToolUseId, scope.id, chatId, item.id, item.streaming]);
146
- const mode = chatHost().useStreaming();
151
+ const mode = host.useStreaming();
147
152
  const [sawWriting] = useState(item.streaming);
148
153
  const live = mode === 'words' && item.streaming;
149
154
  const reveal = useRevealedText(item.text, live);
@@ -161,12 +166,14 @@ export function AssistantRow({ chatId, item: derived }) {
161
166
  }
162
167
  /*
163
168
  * What the model thought before it answered. It shimmers while it streams and folds itself away
164
- * once the answer starts, because the thought is worth a glance and rarely worth reading twice.
169
+ * once the answer starts, because the thought is worth a glance and rarely worth reading twice:
170
+ * folded, it shows the heading it opens with or itself on one line, and opened, it reads as Markdown.
165
171
  */
166
172
  export function ThinkingRow({ chatId, item: derived }) {
173
+ const host = useChatHost();
167
174
  const { t } = useTranslation('agent-chat');
168
175
  const item = useCurrentItem(chatId, derived);
169
- const mode = chatHost().useStreaming();
176
+ const mode = host.useStreaming();
170
177
  const [open, setOpen] = useState(false);
171
178
  const reveal = useRevealedText(item.text, mode === 'words' && item.streaming);
172
179
  // A block at a time, the paragraphs of the thought that closed are shown as they land.
@@ -174,8 +181,23 @@ export function ThinkingRow({ chatId, item: derived }) {
174
181
  // Whole, the thought stays behind "Thinking…" until it is done and then folds like any other.
175
182
  const shown = open || reveal.active || (settled !== null && settled !== '');
176
183
  const text = settled ?? reveal.text;
184
+ const live = useMemo(() => (item.streaming && mode !== 'whole' ? thoughtLine(text) : ''), [item.streaming, mode, text]);
185
+ const preview = useMemo(() => (item.streaming ? '' : thoughtPreview(item.text)), [item.streaming, item.text]);
186
+ const duration = formatElapsedShort((item.endedAt ?? item.createdAt) - item.createdAt);
177
187
  useOpenForFind(item.id, 'text', setOpen);
178
- return (_jsxs("div", { "data-find-item": item.id, className: "-mx-1 px-1 pb-2", children: [_jsxs("button", { className: "flex items-center gap-2 text-xs text-text-muted hover:text-text", disabled: item.streaming, onClick: () => setOpen((o) => !o), children: [_jsx("span", { className: ROW_GUTTER, children: _jsx(Icon, { icon: Brain, size: 12 }) }), item.streaming ? (_jsx("span", { className: "chat-live-text", children: t('rows.thinking.live') })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: t('rows.thinking.thoughtFor', { duration: formatElapsedShort((item.endedAt ?? item.createdAt) - item.createdAt) }) }), _jsx(Icon, { icon: ChevronDown, size: 12, className: clsx('transition-transform', open && 'rotate-180') })] }))] }), shown && text !== '' && (_jsx("div", { "data-find-field": "text", className: "mt-1 border-l border-border pl-2.5 text-sm whitespace-pre-wrap text-text-faint select-text", children: reveal.active ? _jsx(FadingWords, { text: reveal.text }) : text }))] }));
188
+ return (_jsxs("div", { "data-find-item": item.id, className: "-mx-1 px-1 pb-2", children: [_jsxs("button", { className: "flex w-full min-w-0 items-center gap-2 text-left text-xs text-text-muted hover:text-text", disabled: item.streaming, "aria-expanded": item.streaming ? undefined : open, onClick: () => setOpen((o) => !o), children: [_jsx("span", { className: ROW_GUTTER, children: _jsx(Icon, { icon: Brain, size: 12 }) }), item.streaming ? (_jsx("span", { className: "chat-live-text min-w-0 truncate", children: live || t('rows.thinking.live') })) : preview === '' ? (_jsxs(_Fragment, { children: [_jsx("span", { children: t('rows.thinking.thoughtFor', { duration }) }), _jsx(Icon, { icon: ChevronDown, size: 12, className: clsx('shrink-0 transition-transform', open && 'rotate-180') })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "min-w-0 truncate", children: open ? t('rows.thinking.thought') : preview }), _jsx("span", { className: "grow" }), _jsx("span", { className: "shrink-0 text-text-faint tabular-nums", children: duration }), _jsx(Icon, { icon: ChevronDown, size: 12, className: clsx('shrink-0 transition-transform', open && 'rotate-180') })] }))] }), shown && text !== '' && (_jsx("div", { "data-find-field": "text", className: "mt-1 border-l border-border pl-2.5 select-text", children: reveal.active ? (_jsx("div", { className: "text-sm whitespace-pre-wrap text-text-faint", children: _jsx(FadingWords, { text: reveal.text }) })) : (_jsx(Markdown, { text: text, muted: true })) }))] }));
189
+ }
190
+ /*
191
+ * The latest thought of a running turn over the call it led to, as its heading or first sentence,
192
+ * so what the model found stays in view while it checks it. Nothing while the thought has no line yet.
193
+ */
194
+ export function LiveThoughtRow({ chatId, item: derived }) {
195
+ const item = useCurrentItem(chatId, derived);
196
+ const line = useMemo(() => thoughtLine(item.text), [item.text]);
197
+ if (line === '') {
198
+ return null;
199
+ }
200
+ return (_jsxs("div", { className: "-mx-1 mb-0.5 flex min-h-7 items-start gap-2 px-1 py-1 text-xs text-text-muted", children: [_jsx("span", { className: ROW_GUTTER, children: _jsx(Icon, { icon: Brain, size: 12 }) }), _jsx("span", { className: "line-clamp-4 min-w-0 wrap-anywhere select-text", children: line })] }));
179
201
  }
180
202
  const NOTE_ICON = {
181
203
  info: _jsx(Icon, { icon: Info, size: 12 }),
@@ -197,8 +219,9 @@ export function NoteRow({ id, level, text, from }) {
197
219
  }
198
220
  /* "Open fork" on a note a fork sent, or "Open to answer" on one about a waiting child, while that chat is still in the project. */
199
221
  function OpenChatButton({ chatId, label }) {
222
+ const host = useChatHost();
200
223
  const { t } = useTranslation('agent-chat');
201
- const place = chatHost().useChatPlace(chatId);
224
+ const place = host.useChatPlace(chatId);
202
225
  if (place.title === null) {
203
226
  return null;
204
227
  }
@@ -234,7 +257,7 @@ export function ApprovalHistoryRow({ item }) {
234
257
  if (item.decision === 'pending') {
235
258
  return null;
236
259
  }
237
- return (_jsxs("div", { className: "-mx-1 mb-0.5 flex h-7 items-center gap-2 px-1 text-xs text-text-faint", children: [_jsx("span", { className: clsx(ROW_GUTTER, item.decision === 'deny' && 'text-status-error'), children: DECISION_ICON[item.decision] }), _jsx("span", { className: clsx(item.decision === 'deny' && 'text-status-error'), children: t(`rows.approval.${item.decision}`) }), _jsx("span", { className: "text-text-muted", children: item.toolName }), _jsx("span", { className: "min-w-0 truncate font-mono", children: toolSummary(item.toolName, item.input) })] }));
260
+ return (_jsxs("div", { className: "-mx-1 mb-0.5 flex h-7 items-center gap-2 px-1 text-xs text-text-faint", children: [_jsx("span", { className: clsx(ROW_GUTTER, item.decision === 'deny' && 'text-status-error'), children: DECISION_ICON[item.decision] }), _jsx("span", { className: clsx(item.decision === 'deny' && 'text-status-error'), children: t(`rows.approval.${item.decision}`) }), _jsx("span", { className: "text-text-muted", children: item.toolName }), _jsx("span", { className: "min-w-0 truncate font-mono", children: toolSummaryIsPath(item.toolName, item.input) ? (_jsx(MiddleTruncate, { text: toolSummary(item.toolName, item.input) })) : (toolSummary(item.toolName, item.input)) })] }));
238
261
  }
239
262
  export function QuestionHistoryRow({ item }) {
240
263
  const { t } = useTranslation('agent-chat');