@adecore/agents-react 0.23.1 → 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
@@ -2,6 +2,20 @@ export interface MarkdownBlock {
2
2
  text: string;
3
3
  /* The block ends inside a code fence that has not closed yet. */
4
4
  openFence: boolean;
5
+ /* Set when the block is one code fence at the top level of the text, read as markdown reads it. */
6
+ fence?: MarkdownFence;
7
+ }
8
+
9
+ export interface MarkdownFence {
10
+ /* The run of backticks or tildes that opens it. */
11
+ marker: string;
12
+ /* What follows the marker on its line, trimmed. Its first word is the language. */
13
+ info: string;
14
+ /* The lines between the fences, less the indentation of the opening fence. */
15
+ body: string;
16
+ closed: boolean;
17
+ /* The fence as written, from its opening marker to the end of its closing one or of the text. */
18
+ source: string;
5
19
  }
6
20
 
7
21
  interface OpenFence {
@@ -10,45 +24,202 @@ interface OpenFence {
10
24
  indent: number;
11
25
  }
12
26
 
27
+ interface TopFence {
28
+ // Where the opening marker stands in the text.
29
+ from: number;
30
+ marker: string;
31
+ info: string;
32
+ indent: number;
33
+ body: string;
34
+ // The line ending of the last line in `body`, which goes in front of the next one; null before the first.
35
+ ending: string | null;
36
+ // Markdown decodes an escape or a character reference in the language, so such a fence is left to it.
37
+ direct: boolean;
38
+ }
39
+
40
+ /* The outermost list item a line may still belong to. */
41
+ interface ListItem {
42
+ // The column its content starts at; a line less indented than that is not part of it.
43
+ column: number;
44
+ // The bullet, or the delimiter of a number, which a next item of the same list repeats.
45
+ kind: string;
46
+ // False once a line may or may not have ended it, which only markdown itself can tell.
47
+ certain: boolean;
48
+ // Nothing stands in it yet.
49
+ empty: boolean;
50
+ }
51
+
13
52
  const FENCE = /^([ \t]*)(`{3,}|~{3,})(.*)$/;
53
+ const CLOSING_FENCE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
14
54
  // Spaces and tabs only: a line that holds a no-break space is text to markdown, not a blank line.
15
55
  const BLANK = /^[ \t]*$/;
16
56
  // A line that continues what stood before the blank line: an indented paragraph of a list item, or
17
57
  // the next item of a loose list. Splitting there would change what the markdown means.
18
58
  const CONTINUES = /^([ \t]|[-*+][ \t]|\d+[.)][ \t])/;
59
+ const LIST_MARKER = /^[ \t]*(?:([-*+])|(\d{1,9})([.)]))(?:([ \t]+)(.*))?$/;
60
+ // A list item with nothing after its marker.
61
+ const EMPTY_ITEM = /^ {0,3}(?:[-*+]|\d{1,9}[.)])[ \t]*$/;
62
+ const THEMATIC_BREAK = /^ {0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$/;
63
+ // A heading or a quote, which ends a list item instead of continuing its paragraph.
64
+ const ENDS_ITEM = /^ {0,3}(?:#{1,6}(?:[ \t]|$)|>)/;
65
+ const HTML_START = /^ {0,3}<[A-Za-z/!?]/;
66
+ // The HTML that runs on until a marker rather than a blank line, with that marker.
67
+ const HTML_UNTIL: [RegExp, RegExp][] = [
68
+ [/^ {0,3}<(?:pre|script|style|textarea)(?:[ \t>]|$)/i, /<\/(?:pre|script|style|textarea)>/i],
69
+ [/^ {0,3}<!--/, /-->/],
70
+ [/^ {0,3}<\?/, /\?>/],
71
+ [/^ {0,3}<![A-Za-z]/, />/],
72
+ [/^ {0,3}<!\[CDATA\[/, /\]\]>/]
73
+ ];
19
74
  // A reference or footnote definition may be used anywhere in the text, so a block cannot stand alone.
20
75
  const DEFINITION = /^ {0,3}\[[^\]]+\]:/m;
76
+ // Markdown ends a line at a lone carriage return too, which a split at line feeds would not see.
77
+ const LONE_RETURN = /\r(?!\n|$)/;
21
78
  // How much deeper than its opening a closing fence may sit; deeper than that it is a line of code.
22
79
  const CLOSING_INDENT = 3;
80
+ // From four columns in, a line is indented code rather than the start of a block.
81
+ const CODE_INDENT = 4;
82
+ const TAB_STOP = 4;
23
83
  // A section title: an ATX heading, or a line of only bold text, which models write as a heading too.
24
84
  const HEADING = /^ {0,3}#{1,6}(?:[ \t]|$)/;
25
85
  const BOLD_LINE = /^ {0,3}\*\*(?:[^*]|\*(?!\*))+\*\*:?[ \t]*$/;
26
86
 
27
87
  /*
28
88
  * A reply cut into blocks at blank lines outside a code fence, so each block parses on its own and
29
- * only the one still growing is parsed again. Joined back together the blocks are the text.
89
+ * only the one still growing is parsed again. A code fence at the top level is a block of its own,
90
+ * through its closing line and the blank lines under it, so it can be drawn without parsing at all.
91
+ * Joined back together the blocks are the text.
30
92
  */
31
93
  export function splitMarkdownBlocks(text: string): MarkdownBlock[] {
32
- if (DEFINITION.test(text)) {
94
+ if (DEFINITION.test(text) || LONE_RETURN.test(text)) {
33
95
  return [{ text, openFence: isFenceOpenAtEnd(text) }];
34
96
  }
35
97
  const lines = text.split('\n');
36
98
  const blocks: MarkdownBlock[] = [];
37
99
  let start = 0;
38
100
  let offset = 0;
39
- let fence: OpenFence | null = null;
101
+ // The fence as a reading line by line sees it, blind to lists and indented code. A blank line is
102
+ // only cut at where this reading agrees, so every such cut is one this split made before it
103
+ // followed fences any further.
104
+ let lineFence: OpenFence | null = null;
40
105
  let afterBlank = false;
106
+ // A fence inside a list item.
107
+ let fence: OpenFence | null = null;
108
+ let top: TopFence | null = null;
109
+ // A top-level fence that has closed; its block takes the blank lines under it.
110
+ let closed: { fence: TopFence; source: string } | null = null;
111
+ let item: ListItem | null = null;
112
+ // The marker that ends the raw HTML the line stands in.
113
+ let html: RegExp | null = null;
114
+ // Whether the top level is still known here. Past HTML, or a line that may or may not end a list
115
+ // item, a fence is left inside the block it stands in, which markdown parses as a whole.
116
+ let known = true;
117
+ // Where the line above starts when it is blank.
118
+ let blankAbove: number | null = null;
41
119
 
42
- lines.forEach((line, index) => {
43
- if (fence === null && afterBlank && !BLANK.test(line) && !CONTINUES.test(line)) {
44
- blocks.push({ text: text.slice(start, offset), openFence: false });
45
- start = offset;
120
+ const cutAt = (end: number): void => {
121
+ if (end > start) {
122
+ blocks.push({ text: text.slice(start, end), openFence: false });
46
123
  }
47
- fence = nextFence(fence, line);
48
- afterBlank = fence === null && BLANK.test(line) && index > 0;
124
+ start = end;
125
+ };
126
+
127
+ for (const [index, line] of lines.entries()) {
128
+ const at = offset;
49
129
  offset += line.length + 1;
50
- });
51
- blocks.push({ text: text.slice(start), openFence: fence !== null });
130
+ const content = withoutReturn(line);
131
+ const blank = BLANK.test(line);
132
+ // Blank to markdown, which a line ending in a carriage return is too.
133
+ const empty = BLANK.test(content);
134
+ const blankCut = lineFence === null && afterBlank && !blank && !CONTINUES.test(line);
135
+ lineFence = nextFence(lineFence, line);
136
+ afterBlank = lineFence === null && blank && index > 0;
137
+ if (top !== null) {
138
+ if (closesFence(top.marker, content)) {
139
+ closed = { fence: top, source: text.slice(top.from, at + content.length) };
140
+ top = null;
141
+ } else if (index < lines.length - 1 || line !== '') {
142
+ // The empty line after a line ending at the end of the text is no line of the fence.
143
+ top.body += (top.ending ?? '') + outdent(content, top.indent);
144
+ top.ending = line.length > content.length ? '\r\n' : '\n';
145
+ }
146
+ continue;
147
+ }
148
+ if (closed !== null) {
149
+ if (empty) {
150
+ continue;
151
+ }
152
+ blocks.push(fenceBlock(text.slice(start, at), closed.fence, closed.source, true));
153
+ start = at;
154
+ closed = null;
155
+ }
156
+ if (html !== null) {
157
+ // Raw HTML holds no fence and no blank line to cut at until its marker.
158
+ if (html.test(content)) {
159
+ html = null;
160
+ }
161
+ continue;
162
+ }
163
+ if (known && fence !== null && item !== null && !empty && columns(content) < item.column) {
164
+ // Code is no paragraph a line may continue lazily, so a line left of the item's content ends
165
+ // the item and its code. A next item of the same list keeps the list going.
166
+ fence = null;
167
+ if (!LIST_MARKER.test(content)) {
168
+ item = null;
169
+ }
170
+ }
171
+ if (blankCut && fence === null) {
172
+ cutAt(at);
173
+ }
174
+ const above = index > 0 ? withoutReturn(lines[index - 1]!) : '';
175
+ const opening = known && fence === null ? openingFence(content) : null;
176
+ if (opening !== null && EMPTY_ITEM.test(above)) {
177
+ // Markdown reads an empty item right above a fence as a list or as text depending on what
178
+ // stands before it, so the two stay in one block.
179
+ known = false;
180
+ } else if (opening !== null && opening.indent < CODE_INDENT && (item === null || opening.indent < item.column)) {
181
+ // The blank line right above goes with the fence. Code in a list item this fence ends takes
182
+ // every blank line under it but the last, and at the end of a block it would take them all.
183
+ cutAt(blankAbove !== null && blankAbove > start ? blankAbove : at);
184
+ // Three columns at most, so the indentation is spaces.
185
+ top = { ...opening, from: at + opening.indent, body: '', ending: null };
186
+ item = null;
187
+ blankAbove = null;
188
+ continue;
189
+ }
190
+ if (known && item?.empty === true && empty) {
191
+ // An item with nothing on its first line ends at a blank line right under it.
192
+ item = null;
193
+ }
194
+ if (known && fence === null && !empty) {
195
+ // A fence in an item is followed only while the item is known to be open, and less than four
196
+ // columns deeper than its content: deeper it is code of the item, or of an item inside it.
197
+ if (opening !== null && item !== null && (!item.certain || opening.indent < item.column || opening.indent >= item.column + CODE_INDENT)) {
198
+ known = false;
199
+ } else if (opening === null) {
200
+ item = nextItem(item, content, index > 0 && !BLANK.test(above));
201
+ }
202
+ }
203
+ if (fence === null && HTML_START.test(content)) {
204
+ known = false;
205
+ const until = HTML_UNTIL.find(([begins]) => begins.test(content))?.[1];
206
+ html = until !== undefined && !until.test(content) ? until : null;
207
+ }
208
+ if (known && (fence !== null || item !== null)) {
209
+ fence = nextFence(fence, fence === null ? itemContent(content) : content);
210
+ }
211
+ if (item?.empty === true && !empty && columns(content) >= item.column) {
212
+ item = { ...item, empty: false };
213
+ }
214
+ blankAbove = empty ? at : null;
215
+ }
216
+ if (top !== null) {
217
+ blocks.push(fenceBlock(text.slice(start), top, text.slice(top.from), false));
218
+ } else if (closed !== null) {
219
+ blocks.push(fenceBlock(text.slice(start), closed.fence, closed.source, true));
220
+ } else {
221
+ blocks.push({ text: text.slice(start), openFence: (known ? fence : lineFence) !== null });
222
+ }
52
223
  return blocks;
53
224
  }
54
225
 
@@ -117,7 +288,124 @@ function nextFence(fence: OpenFence | null, line: string): OpenFence | null {
117
288
  function isFenceOpenAtEnd(text: string): boolean {
118
289
  let fence: OpenFence | null = null;
119
290
  for (const line of text.split('\n')) {
120
- fence = nextFence(fence, line);
291
+ fence = nextFence(fence, withoutReturn(line));
121
292
  }
122
293
  return fence !== null;
123
294
  }
295
+
296
+ /* The fence a line opens wherever it stands, with its indentation in columns. */
297
+ function openingFence(content: string): Pick<TopFence, 'marker' | 'info' | 'indent' | 'direct'> | null {
298
+ const match = FENCE.exec(content);
299
+ if (!match) {
300
+ return null;
301
+ }
302
+ const marker = match[2]!;
303
+ const info = match[3]!.replace(/^[ \t]+|[ \t]+$/g, '');
304
+ // A backtick fence's info string may not hold a backtick, or the line is inline code.
305
+ if (marker[0] === '`' && info.includes('`')) {
306
+ return null;
307
+ }
308
+ return { marker, info, indent: columns(content), direct: !/[\\&]/.test(info.split(/[ \t]/, 1)[0]!) };
309
+ }
310
+
311
+ /* A line without the carriage return of a CRLF line ending. */
312
+ function withoutReturn(line: string): string {
313
+ return line.endsWith('\r') ? line.slice(0, -1) : line;
314
+ }
315
+
316
+ function closesFence(marker: string, content: string): boolean {
317
+ const run = CLOSING_FENCE.exec(content)?.[1];
318
+ return run !== undefined && run[0] === marker[0] && run.length >= marker.length;
319
+ }
320
+
321
+ function fenceBlock(text: string, fence: TopFence, source: string, closed: boolean): MarkdownBlock {
322
+ if (!fence.direct) {
323
+ return { text, openFence: !closed };
324
+ }
325
+ return { text, openFence: !closed, fence: { marker: fence.marker, info: fence.info, body: fence.body, closed, source } };
326
+ }
327
+
328
+ /* The list item a line that is neither blank nor a fence leaves open, as far as a fence needs to know. */
329
+ function nextItem(item: ListItem | null, content: string, underLine: boolean): ListItem | null {
330
+ const indent = columns(content);
331
+ if (item !== null && indent >= item.column) {
332
+ return item;
333
+ }
334
+ if (THEMATIC_BREAK.test(content) || ENDS_ITEM.test(content)) {
335
+ return null;
336
+ }
337
+ const marker = indent < CODE_INDENT ? itemMarker(content) : null;
338
+ if (marker === null) {
339
+ if (item === null || !underLine) {
340
+ return null;
341
+ }
342
+ // A line right under an item may continue its paragraph, or follow an item that ends in something else.
343
+ return { ...item, certain: false };
344
+ }
345
+ // Right under another line, only a bullet or a 1 with text after it surely starts a list; anything
346
+ // else may be text of a paragraph above.
347
+ const certain = !underLine || (item !== null && item.certain && item.kind === marker.kind) || (marker.first && marker.rest !== '');
348
+ return { column: marker.column, kind: marker.kind, certain, empty: marker.rest === '' };
349
+ }
350
+
351
+ /* The marker a list item starts with: its kind, whether it may start a list under text, and the column its content starts at. */
352
+ function itemMarker(content: string): { kind: string; first: boolean; column: number; rest: string } | null {
353
+ const match = LIST_MARKER.exec(content);
354
+ if (match === null) {
355
+ return null;
356
+ }
357
+ const [, bullet, number, delimiter, spacing = '', rest = ''] = match;
358
+ const indent = columns(content);
359
+ const width = bullet !== undefined ? 1 : number!.length + 1;
360
+ // The content starts after the spaces behind the marker, unless there are five or more: then the
361
+ // item starts with indented code one column after the marker.
362
+ const gap = rest === '' ? 1 : columns(spacing, indent + width);
363
+ const column = indent + width + (gap > CODE_INDENT ? 1 : gap);
364
+ return { kind: bullet ?? delimiter!, first: bullet !== undefined || Number(number) === 1, column, rest };
365
+ }
366
+
367
+ /* A list item's line with its marker as spaces, so a fence on that line is seen where the item's content starts. */
368
+ function itemContent(content: string): string {
369
+ const marker = itemMarker(content);
370
+ return marker === null ? content : ' '.repeat(marker.column) + marker.rest;
371
+ }
372
+
373
+ /* The columns of the indentation of a line, which starts at column `from`; a tab reaches to the next stop of four. */
374
+ function columns(line: string, from = 0): number {
375
+ let column = from;
376
+ for (const character of line) {
377
+ if (character === '\t') {
378
+ column += TAB_STOP - (column % TAB_STOP);
379
+ } else if (character === ' ') {
380
+ column += 1;
381
+ } else {
382
+ break;
383
+ }
384
+ }
385
+ return column - from;
386
+ }
387
+
388
+ /*
389
+ * A line of a fence less up to `indent` columns of indentation. A tab reaches to the next stop of
390
+ * four, and what of it lies past `indent` stays as spaces, as markdown reads it.
391
+ */
392
+ function outdent(line: string, indent: number): string {
393
+ let column = 0;
394
+ let index = 0;
395
+ while (column < indent && index < line.length) {
396
+ const character = line[index];
397
+ if (character === '\t') {
398
+ const stop = column + TAB_STOP - (column % TAB_STOP);
399
+ if (stop > indent) {
400
+ return ' '.repeat(stop - indent) + line.slice(index + 1);
401
+ }
402
+ column = stop;
403
+ } else if (character === ' ') {
404
+ column += 1;
405
+ } else {
406
+ break;
407
+ }
408
+ index += 1;
409
+ }
410
+ return line.slice(index);
411
+ }
@@ -0,0 +1,118 @@
1
+ import type { MermaidConfig } from 'mermaid';
2
+
3
+ /* The colors of a diagram, each a token of the theme resolved to an opaque color over the frame it is drawn in. */
4
+ export interface MermaidColors {
5
+ ground: string;
6
+ node: string;
7
+ nodeBorder: string;
8
+ text: string;
9
+ line: string;
10
+ cluster: string;
11
+ clusterBorder: string;
12
+ note: string;
13
+ }
14
+
15
+ /* The token each color reads. Borders carry an alpha, so they are resolved over the ground. */
16
+ export const MERMAID_TOKENS: Readonly<Record<keyof MermaidColors, string>> = {
17
+ ground: '--chat-code-bg',
18
+ node: '--surface-raised',
19
+ nodeBorder: '--border-strong',
20
+ text: '--text',
21
+ line: '--text-muted',
22
+ cluster: '--surface',
23
+ clusterBorder: '--border',
24
+ note: '--accent-soft'
25
+ };
26
+
27
+ // Mermaid's own list plus what keeps a diagram in the theme and out of HTML: a directive in the text cannot change these.
28
+ const SECURE_KEYS = [
29
+ 'secure',
30
+ 'securityLevel',
31
+ 'startOnLoad',
32
+ 'maxTextSize',
33
+ 'maxEdges',
34
+ 'suppressErrorRendering',
35
+ 'htmlLabels',
36
+ 'theme',
37
+ 'themeVariables',
38
+ 'themeCSS',
39
+ 'darkMode',
40
+ 'fontFamily'
41
+ ];
42
+
43
+ /*
44
+ * Strict: no HTML in labels, no click handlers, no links that run script. What comes out is drawn as an
45
+ * image as well, which never runs script and loads nothing, whatever the text of the diagram says.
46
+ */
47
+ export function mermaidConfig(colors: MermaidColors, dark: boolean, font: { family: string; size: string }): MermaidConfig {
48
+ return {
49
+ startOnLoad: false,
50
+ securityLevel: 'strict',
51
+ htmlLabels: false,
52
+ suppressErrorRendering: true,
53
+ secure: SECURE_KEYS,
54
+ theme: 'base',
55
+ darkMode: dark,
56
+ fontFamily: font.family,
57
+ themeVariables: {
58
+ darkMode: dark,
59
+ fontFamily: font.family,
60
+ fontSize: font.size,
61
+ background: colors.ground,
62
+ primaryColor: colors.node,
63
+ primaryTextColor: colors.text,
64
+ primaryBorderColor: colors.nodeBorder,
65
+ secondaryColor: colors.note,
66
+ tertiaryColor: colors.cluster,
67
+ mainBkg: colors.node,
68
+ nodeBorder: colors.nodeBorder,
69
+ textColor: colors.text,
70
+ titleColor: colors.text,
71
+ lineColor: colors.line,
72
+ clusterBkg: colors.cluster,
73
+ clusterBorder: colors.clusterBorder,
74
+ edgeLabelBackground: colors.ground,
75
+ noteBkgColor: colors.note,
76
+ noteTextColor: colors.text,
77
+ noteBorderColor: colors.nodeBorder
78
+ }
79
+ };
80
+ }
81
+
82
+ export interface SizedSvg {
83
+ svg: string;
84
+ width: number;
85
+ height: number;
86
+ }
87
+
88
+ const ROOT = /<svg\b[^>]*>/;
89
+ const VIEW_BOX = /\sviewBox="([^"]*)"/;
90
+ const SIZE_ATTRIBUTES = /\s(?:width|height|style)="[^"]*"/g;
91
+
92
+ /*
93
+ * The diagram at the size it was laid out at. Mermaid fits it to the width of its container instead,
94
+ * which an image does not have, so the root takes the size its view box names. Null for anything
95
+ * without one.
96
+ */
97
+ export function sizedSvg(svg: string): SizedSvg | null {
98
+ const root = ROOT.exec(svg);
99
+ const viewBox = root === null ? null : VIEW_BOX.exec(root[0]);
100
+ if (root === null || viewBox === null) {
101
+ return null;
102
+ }
103
+ const [, , boxWidth, boxHeight] = viewBox[1]!
104
+ .trim()
105
+ .split(/[\s,]+/)
106
+ .map(Number);
107
+ if (!Number.isFinite(boxWidth) || !Number.isFinite(boxHeight) || boxWidth! <= 0 || boxHeight! <= 0) {
108
+ return null;
109
+ }
110
+ const width = Math.ceil(boxWidth!);
111
+ const height = Math.ceil(boxHeight!);
112
+ const tag = root[0].replace(SIZE_ATTRIBUTES, '').replace(/^<svg\b/, `<svg width="${width}" height="${height}"`);
113
+ return { svg: svg.slice(0, root.index) + tag + svg.slice(root.index + root[0].length), width, height };
114
+ }
115
+
116
+ export function svgDataUrl(svg: string): string {
117
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
118
+ }
@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next';
2
2
  import { ChevronRight, GitFork } from 'lucide-react';
3
3
  import { useForkIdsAfter } from '../../forks';
4
4
  import { ROW_GUTTER } from '../icons';
5
- import { chatHost } from '../../../host';
5
+ import { useChatHost } from '../../../host';
6
6
  import { Icon, Menu } from '@adecore/ui';
7
7
 
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';
@@ -52,7 +52,8 @@ function ForksLabel({ label, link = true }: { label: string; link?: boolean }) {
52
52
 
53
53
  // A fork no longer in this project has nowhere to lead, so it only says it is there.
54
54
  function OneFork({ forkId, label }: { forkId: string; label: string }) {
55
- const place = chatHost().useChatPlace(forkId);
55
+ const host = useChatHost();
56
+ const place = host.useChatPlace(forkId);
56
57
  return (
57
58
  <div className="flex">
58
59
  {place.title === null ? (
@@ -69,8 +70,9 @@ function OneFork({ forkId, label }: { forkId: string; label: string }) {
69
70
  }
70
71
 
71
72
  function ForkItem({ forkId }: { forkId: string }) {
73
+ const host = useChatHost();
72
74
  const { t } = useTranslation('agent-chat');
73
- const place = chatHost().useChatPlace(forkId);
75
+ const place = host.useChatPlace(forkId);
74
76
  return (
75
77
  <Menu.Item disabled={place.title === null} onClick={place.go}>
76
78
  <Icon icon={GitFork} size={14} /> <span className="truncate">{place.title ?? t('fork.pill.label')}</span>
@@ -5,14 +5,14 @@ import { Trans, useTranslation } from 'react-i18next';
5
5
  import { Check, Copy, Ellipsis, ExternalLink, FolderDown, ImageIcon, Maximize2 } from 'lucide-react';
6
6
  import type { ChatAttachment, ChatToolItem } from '@adecore/agent-contracts';
7
7
  import { Button, ButtonGroup, copyText, ErrorBoundary, FileIcon, Icon, IconButton, Menu, Surface } from '@adecore/ui';
8
- import { chatHost } from '../../../host';
8
+ import { useChatHost } from '../../../host';
9
9
  import { useChatScope } from '../../../scope';
10
10
  import { useCurrentItem } from '../../../state/chats';
11
11
  import { formatBytes } from '../../attachments';
12
12
  import { attachmentAspect, generatedImageView, type GeneratedImageFailure } from '../../logic/generated-image';
13
13
  import { toolStartedAt } from '../../logic/tools';
14
14
  import { CHIP_IN_MESSAGE, MENTION_TONE } from '../chips';
15
- import { openFileLink, useFileLinkCwd } from '../file-links';
15
+ import { useFileLinkCwd, useOpenFileLink } from '../file-links';
16
16
  import { ImageThumb } from '../ImageView';
17
17
  import { ROW_GUTTER } from '../icons';
18
18
  import { RunningFor, WorkRow } from './WorkRows';
@@ -67,13 +67,15 @@ function HeadLine({ label, detail, live, startedAt }: { label: string; detail?:
67
67
  function SavedFile({ path }: { path: string }) {
68
68
  const cwd = useFileLinkCwd();
69
69
  const { id } = useChatScope();
70
+ const { fileLinks } = useChatHost();
71
+ const openFileLink = useOpenFileLink();
70
72
  const chip = (
71
73
  <>
72
74
  <FileIcon path={path} size={14} />
73
75
  <span className="truncate">{path}</span>
74
76
  </>
75
77
  );
76
- if (chatHost().fileLinks === null) {
78
+ if (fileLinks === null) {
77
79
  return <span className={clsx(CHIP_IN_MESSAGE, MENTION_TONE)}>{chip}</span>;
78
80
  }
79
81
  return (
@@ -91,7 +93,7 @@ function SavedFile({ path }: { path: string }) {
91
93
  function ReadyImage({ chatId, attachment, prompt, transparent }: { chatId: string; attachment: ChatAttachment; prompt: string | null; transparent: boolean }) {
92
94
  const { t } = useTranslation('agent-chat');
93
95
  const { id: scopeId } = useChatScope();
94
- const { attachments } = chatHost();
96
+ const { attachments } = useChatHost();
95
97
  const source = attachments.useUrl(scopeId, chatId, attachment.id);
96
98
  const savedKey = `${scopeId}\n${chatId}\n${attachment.id}`;
97
99
  const [lightbox, setLightbox] = useState(false);