@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
@@ -0,0 +1,111 @@
1
+ import { useRef, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { EditorView } from '@codemirror/view';
4
+ import { useChatHost } from '../../../host.js';
5
+ import { checkAttachmentLimits, filesOf, readAttachments, uploadBytes } from '../../attachments.js';
6
+ import { pasteBecomesAttachment, pastedTextName } from '../../guards.js';
7
+ import { carriesMentions, droppedMentions, pastedMentions } from '../../mentions.js';
8
+ /* The card turns dropped files and paths into attachments and chips; CodeMirror would also paste their text at the drop point. */
9
+ export const LEAVE_DROPS_TO_THE_CARD = EditorView.domEventHandlers({
10
+ drop: (event) => event.dataTransfer !== null && (event.dataTransfer.types.includes('Files') || carriesMentions(event.dataTransfer.types))
11
+ });
12
+ /* What reaches the draft besides typing: files as attachments, dragged paths as mentions, and a long paste as a file of its own. */
13
+ export function useDraftFiles({ capabilities, providerName, writable, draft: { draft, setDraft, setText }, inputRef, setNotice }) {
14
+ const { t } = useTranslation('agent-chat');
15
+ const { isApplePlatform } = useChatHost();
16
+ const [dragging, setDragging] = useState(false);
17
+ // A paste event says nothing about the keys behind it, so the key that asked for text inline is remembered here.
18
+ const pasteInline = useRef(false);
19
+ /* Paths dragged in from a list of files. With no mention query to insert into, they land at the end of the prompt. */
20
+ const addMentions = (paths) => {
21
+ const fresh = paths.filter((path) => path !== '');
22
+ if (fresh.length === 0 || capabilities?.mentions === false) {
23
+ return;
24
+ }
25
+ const appended = fresh.map((path) => `@${path}`).join(' ');
26
+ const before = draft.text.replace(/\s+$/, '');
27
+ setText(before === '' ? `${appended} ` : `${before} ${appended} `, [...draft.mentions, ...fresh.filter((path) => !draft.mentions.includes(path))]);
28
+ };
29
+ const addFiles = (incoming) => {
30
+ if (incoming.length === 0) {
31
+ return;
32
+ }
33
+ if (capabilities?.attachments === false) {
34
+ setNotice(t('composer.notice.noAttachments', { agent: providerName ?? t('composer.notice.thisAgent') }));
35
+ return;
36
+ }
37
+ const checked = checkAttachmentLimits(draft.attachments.length, incoming.map((file) => ({ name: file.name, mime: file.type, bytes: file.size, file })), draft.attachments.reduce((bytes, attachment) => bytes + uploadBytes(attachment), 0));
38
+ if (checked.rejected[0]) {
39
+ setNotice(t('composer.notice.rejected', { name: checked.rejected[0].name || t('composer.notice.thatFile'), reason: checked.rejected[0].reason }));
40
+ }
41
+ if (checked.accepted.length === 0) {
42
+ return;
43
+ }
44
+ readAttachments(checked.accepted.map((entry) => entry.file))
45
+ .then((attachments) => setDraft((current) => ({ ...current, attachments: [...current.attachments, ...attachments] })))
46
+ .catch(() => setNotice(t('composer.notice.unreadable')));
47
+ };
48
+ /* Shift with the paste shortcut pastes a large text inline after all; any other key forgets it. */
49
+ const noteKey = (e) => {
50
+ pasteInline.current = e.shiftKey && e.key.toLowerCase() === 'v' && (isApplePlatform() ? e.metaKey : e.ctrlKey);
51
+ };
52
+ /* Files become attachments and a long text a file of its own; anything else the editor pastes as plain text. */
53
+ const onPaste = (e, view) => {
54
+ const data = e.clipboardData;
55
+ if (!data) {
56
+ return false;
57
+ }
58
+ const pasted = filesOf(data);
59
+ if (pasted.length > 0) {
60
+ addFiles(pasted);
61
+ return true;
62
+ }
63
+ const inline = pasteInline.current;
64
+ pasteInline.current = false;
65
+ const clip = data.getData('text/plain');
66
+ const mentions = capabilities?.mentions === false ? [] : pastedMentions(clip);
67
+ if (mentions.length > 0) {
68
+ // The editor's own change reads the draft as it was rendered, so the mentions join it after that change.
69
+ view.dispatch(view.state.replaceSelection(clip), { scrollIntoView: true, userEvent: 'input.paste' });
70
+ setDraft((current) => ({ ...current, mentions: [...current.mentions, ...mentions.filter((path) => !current.mentions.includes(path))] }));
71
+ return true;
72
+ }
73
+ // Without attachments there is nowhere else for the text to go, so it pastes as it always did.
74
+ if (inline || capabilities?.attachments === false || !pasteBecomesAttachment(clip)) {
75
+ return false;
76
+ }
77
+ // The paste still replaces what was selected; it just puts nothing in its place.
78
+ const { from, to } = view.state.selection.main;
79
+ if (from !== to) {
80
+ view.dispatch({ changes: { from, to }, userEvent: 'delete' });
81
+ }
82
+ const name = pastedTextName(draft.attachments.map((attachment) => attachment.name));
83
+ addFiles([new File([clip], name, { type: 'text/plain' })]);
84
+ return true;
85
+ };
86
+ /* The whole card takes a dropped file, so a layout around it that takes drops of its own leaves it alone. */
87
+ const dropTarget = {
88
+ 'data-takes-drop': 'all',
89
+ onDragOver: (e) => {
90
+ if (writable && (e.dataTransfer.types.includes('Files') || carriesMentions(e.dataTransfer.types))) {
91
+ e.preventDefault();
92
+ e.stopPropagation();
93
+ setDragging(true);
94
+ }
95
+ },
96
+ onDragLeave: (e) => {
97
+ if (!e.currentTarget.contains(e.relatedTarget)) {
98
+ setDragging(false);
99
+ }
100
+ },
101
+ onDrop: (e) => {
102
+ e.preventDefault();
103
+ e.stopPropagation();
104
+ setDragging(false);
105
+ addMentions(droppedMentions(e.dataTransfer));
106
+ addFiles(filesOf(e.dataTransfer));
107
+ inputRef.current?.focus();
108
+ }
109
+ };
110
+ return { dragging, dropTarget, addFiles, noteKey, onPaste };
111
+ }
@@ -0,0 +1,6 @@
1
+ export interface ComposerNotice {
2
+ notice: string | null;
3
+ setNotice(notice: string | null): void;
4
+ showError(error: unknown): void;
5
+ }
6
+ export declare function useNotice(): ComposerNotice;
@@ -0,0 +1,18 @@
1
+ import { useEffect, useState } from 'react';
2
+ const NOTICE_MS = 4000;
3
+ /* A line under the box that says what went wrong, gone again after a few seconds. */
4
+ export function useNotice() {
5
+ const [notice, setNotice] = useState(null);
6
+ useEffect(() => {
7
+ if (!notice) {
8
+ return;
9
+ }
10
+ const timer = setTimeout(() => setNotice(null), NOTICE_MS);
11
+ return () => clearTimeout(timer);
12
+ }, [notice]);
13
+ return {
14
+ notice,
15
+ setNotice,
16
+ showError: (error) => setNotice(error instanceof Error ? error.message : String(error))
17
+ };
18
+ }
@@ -0,0 +1,2 @@
1
+ import type { ChatApprovalItem, ChatQuestionItem } from '@adecore/agent-contracts';
2
+ export declare function usePendingRequests(chatId: string): Array<ChatApprovalItem | ChatQuestionItem>;
@@ -0,0 +1,22 @@
1
+ import { useMemo } from 'react';
2
+ import { useChatRow, waitingRequestsOf } from '../../../state/chats.js';
3
+ /* The approvals and then the questions the chat waits on, which take the composer's place until answered. */
4
+ export function usePendingRequests(chatId) {
5
+ // The structure, which a delta leaves alone. The prompts and the requests are items of their own.
6
+ const structure = useChatRow(chatId, (row) => row?.structure);
7
+ const order = useChatRow(chatId, (row) => row?.order);
8
+ const waitingBefore = useChatRow(chatId, (row) => row?.waitingBefore);
9
+ return useMemo(() => {
10
+ const approvals = [];
11
+ const questions = [];
12
+ for (const item of waitingRequestsOf(structure && order ? { structure, order, waitingBefore } : undefined)) {
13
+ if (item.kind === 'approval') {
14
+ approvals.push(item);
15
+ }
16
+ else {
17
+ questions.push(item);
18
+ }
19
+ }
20
+ return [...approvals, ...questions];
21
+ }, [structure, order, waitingBefore]);
22
+ }
@@ -0,0 +1,8 @@
1
+ export interface PromptHistory {
2
+ recalled(text: string): boolean;
3
+ recall(direction: -1 | 1): boolean;
4
+ forget(): void;
5
+ leave(): boolean;
6
+ edited(text: string): void;
7
+ }
8
+ export declare function usePromptHistory(chatId: string, setText: (next: string, mentions?: string[], skills?: string[]) => void): PromptHistory;
@@ -0,0 +1,57 @@
1
+ import { useMemo, useState } from 'react';
2
+ import { useChatRow } from '../../../state/chats.js';
3
+ // How many sent prompts the arrow keys walk back through, per chat.
4
+ const PROMPT_HISTORY = 50;
5
+ /* The prompts sent in this chat, for the arrow keys to put back in an empty box. */
6
+ export function usePromptHistory(chatId, setText) {
7
+ const order = useChatRow(chatId, (row) => row?.order);
8
+ const items = useChatRow(chatId, (row) => row?.structure);
9
+ const [index, setIndex] = useState(null);
10
+ const history = useMemo(() => {
11
+ const prompts = [];
12
+ for (const id of order ?? []) {
13
+ const item = items?.[id];
14
+ if (item?.kind === 'user') {
15
+ prompts.push({ text: item.text, mentions: item.mentions ?? [] });
16
+ }
17
+ }
18
+ // Walking back further than this is faster with the timeline than with the arrow key.
19
+ return prompts.slice(-PROMPT_HISTORY);
20
+ }, [order, items]);
21
+ const recall = (direction) => {
22
+ if (history.length === 0) {
23
+ return false;
24
+ }
25
+ const next = index === null ? (direction === -1 ? history.length - 1 : null) : index + direction;
26
+ if (next === null || next >= history.length) {
27
+ setIndex(null);
28
+ setText('', [], []);
29
+ return true;
30
+ }
31
+ if (next < 0) {
32
+ return true;
33
+ }
34
+ setIndex(next);
35
+ setText(history[next].text, history[next].mentions);
36
+ return true;
37
+ };
38
+ const leave = () => {
39
+ if (index === null) {
40
+ return false;
41
+ }
42
+ setIndex(null);
43
+ setText('', [], []);
44
+ return true;
45
+ };
46
+ return {
47
+ recalled: (text) => index !== null && text === history[index]?.text,
48
+ recall,
49
+ forget: () => setIndex(null),
50
+ leave,
51
+ edited: (text) => {
52
+ if (index !== null && text !== history[index]?.text) {
53
+ setIndex(null);
54
+ }
55
+ }
56
+ };
57
+ }
@@ -0,0 +1,6 @@
1
+ import type { PromptHold } from '../PromptComposer.js';
2
+ export type ComposerPromptHold = PromptHold & {
3
+ noteInput(): void;
4
+ setComposing(composing: boolean): void;
5
+ };
6
+ export declare function usePromptHold(): ComposerPromptHold;
@@ -0,0 +1,48 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { holdLiftsIn } from '../../prompt-hold.js';
3
+ /*
4
+ * Keeps a waiting prompt card behind the draft while the person types. Typing, pasting and dictating
5
+ * all change the draft, so `noteInput` is called from its change handler; the hold lifts after a pause.
6
+ */
7
+ export function usePromptHold() {
8
+ const typedAt = useRef(null);
9
+ const composing = useRef(false);
10
+ const timer = useRef(null);
11
+ const [state, setState] = useState({ held: false, releasedBy: null });
12
+ const clearTimer = () => {
13
+ if (timer.current !== null) {
14
+ clearTimeout(timer.current);
15
+ timer.current = null;
16
+ }
17
+ };
18
+ const release = (reason) => {
19
+ clearTimer();
20
+ typedAt.current = null;
21
+ composing.current = false;
22
+ setState((current) => (current.held ? { held: false, releasedBy: reason } : current));
23
+ };
24
+ const schedule = () => {
25
+ clearTimer();
26
+ const wait = holdLiftsIn({ typedAt: typedAt.current, composing: composing.current }, Date.now());
27
+ if (wait !== null) {
28
+ timer.current = setTimeout(() => release('pause'), wait);
29
+ }
30
+ };
31
+ const noteInput = () => {
32
+ typedAt.current = Date.now();
33
+ setState({ held: true, releasedBy: null });
34
+ schedule();
35
+ };
36
+ const setComposing = (next) => {
37
+ composing.current = next;
38
+ if (next) {
39
+ setState({ held: true, releasedBy: null });
40
+ }
41
+ else {
42
+ typedAt.current = Date.now();
43
+ }
44
+ schedule();
45
+ };
46
+ useEffect(() => clearTimer, []);
47
+ return { held: state.held, releasedBy: state.releasedBy, release, noteInput, setComposing };
48
+ }
@@ -0,0 +1,13 @@
1
+ import type { ChatQueuedMessage } from '@adecore/agent-contracts';
2
+ import type { ComposerDraft } from './use-composer-draft.js';
3
+ interface TakeBackInput {
4
+ chatId: string;
5
+ draft: ComposerDraft;
6
+ setNotice(notice: string): void;
7
+ onTaken(): void;
8
+ }
9
+ export declare function useTakeBack({ chatId, draft: { latest, setDraft }, setNotice, onTaken }: TakeBackInput): {
10
+ takingBack: string | null;
11
+ takeBack: (message: ChatQueuedMessage) => Promise<void>;
12
+ };
13
+ export {};
@@ -0,0 +1,82 @@
1
+ import { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { useChatHost } from '../../../host.js';
4
+ import { useChatScope } from '../../../scope.js';
5
+ import { errorCode } from '../../../transport.js';
6
+ import { useChatActions } from '../../actions.js';
7
+ import { readStoredAttachments } from '../../attachments.js';
8
+ import { takeBackIntoDraft } from '../../drafts.js';
9
+ /* Takes a queued message out of the queue and back into the draft, to be edited before it goes. */
10
+ export function useTakeBack({ chatId, draft: { latest, setDraft }, setNotice, onTaken }) {
11
+ const { t } = useTranslation('agent-chat');
12
+ const host = useChatHost();
13
+ const scope = useChatScope();
14
+ const actions = useChatActions();
15
+ const [takingBack, setTakingBack] = useState(null);
16
+ /*
17
+ * The files come back from the machine before the message leaves the queue, since the machine only
18
+ * serves a file some message still holds. The machine's refusal is what says it already went out.
19
+ */
20
+ const takeBack = async (message) => {
21
+ if (takingBack !== null) {
22
+ return;
23
+ }
24
+ const failed = (e) => {
25
+ host.notify({ kind: 'error', title: t('composer.queue.editFailed'), description: e instanceof Error ? e.message : String(e) });
26
+ };
27
+ const readFiles = async () => {
28
+ const stored = message.attachments ?? [];
29
+ if (stored.length === 0) {
30
+ return [];
31
+ }
32
+ const files = host.attachments;
33
+ return readStoredAttachments((attachment) => files.read(scope.id, chatId, attachment.id), stored);
34
+ };
35
+ setTakingBack(message.id);
36
+ try {
37
+ let uploads;
38
+ try {
39
+ uploads = await readFiles();
40
+ }
41
+ catch (e) {
42
+ failed(e);
43
+ return;
44
+ }
45
+ const taken = {
46
+ text: message.text,
47
+ mentions: message.mentions ?? [],
48
+ skills: message.skills ?? [],
49
+ chats: message.chats ?? [],
50
+ attachments: uploads
51
+ };
52
+ // A file that would not fit is refused here, while the message still holds it on the machine.
53
+ const wouldReject = takeBackIntoDraft(latest.current, taken).rejected[0];
54
+ if (wouldReject) {
55
+ setNotice(t('composer.notice.queuedDoesNotFit', { name: wouldReject.name || t('composer.notice.thatFile'), reason: wouldReject.reason }));
56
+ return;
57
+ }
58
+ try {
59
+ await actions.unqueue(chatId, message.id);
60
+ }
61
+ catch (e) {
62
+ if (errorCode(e) === 'request-not-found') {
63
+ host.notify({ kind: 'error', title: t('composer.queue.alreadySent') });
64
+ }
65
+ else {
66
+ failed(e);
67
+ }
68
+ return;
69
+ }
70
+ const merged = takeBackIntoDraft(latest.current, taken);
71
+ setDraft(merged.draft);
72
+ if (merged.rejected[0]) {
73
+ setNotice(t('composer.notice.rejected', { name: merged.rejected[0].name, reason: merged.rejected[0].reason }));
74
+ }
75
+ onTaken();
76
+ }
77
+ finally {
78
+ setTakingBack(null);
79
+ }
80
+ };
81
+ return { takingBack, takeBack };
82
+ }
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
5
5
  import { getFiletypeFromFileName } from '@pierre/diffs';
6
6
  import { DiffView } from '@adecore/editor-react';
7
7
  import { diffDocument, patchOf } from '@adecore/merge';
8
- import { chatHost } from '../../../host.js';
8
+ import { useChatHost } from '../../../host.js';
9
9
  import { useDiffSourceTokens } from '../use-diff-source-tokens.js';
10
10
  import { diffSourceBudget } from './diff-source-budget.js';
11
11
  import { DiffOmitted } from './DiffOmitted.js';
@@ -13,10 +13,11 @@ import { canRenderEditorDiff, EDITOR_DIFF_LIMITS, EDITOR_DIFF_WORK } from './edi
13
13
  // Virtualized rows remount; retain the engine so their height does not return to the loading placeholder.
14
14
  const engines = new WeakMap();
15
15
  function ReadyDiff({ document, path, engine, wrap, fill, layout, maxHeight }) {
16
+ const host = useChatHost();
16
17
  useReviewSettled();
17
18
  const { t } = useTranslation('agent-chat');
18
- const mode = chatHost().code.useMode();
19
- const theme = chatHost().code.useThemes()[mode];
19
+ const mode = host.code.useMode();
20
+ const theme = host.code.useThemes()[mode];
20
21
  const language = getFiletypeFromFileName(path);
21
22
  const tokens = useDiffSourceTokens(document, language, theme);
22
23
  return (_jsx(DiffView, { engine: engine, document: document, tokens: tokens, theme: theme, maxHeight: maxHeight, unfoldLimits: EDITOR_DIFF_LIMITS, label: t('work.diffLabel', { path }), layout: layout, wrap: wrap, fill: fill, className: "chat-diff select-text" }));
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useWorkerPool } from '@pierre/diffs/react';
3
- import { chatHost } from '../../../host.js';
3
+ import { useChatHost } from '../../../host.js';
4
4
  import DiffPool from '../DiffPool.js';
5
5
  export function LegacyDiffPool({ children }) {
6
+ const host = useChatHost();
6
7
  const outer = useWorkerPool();
7
- const workerFactory = chatHost().code.legacyDiffWorker;
8
+ const workerFactory = host.code.legacyDiffWorker;
8
9
  if (outer !== undefined || workerFactory === undefined) {
9
10
  return children;
10
11
  }
@@ -3,11 +3,12 @@ import { LegacyDiffPool } from './LegacyDiffPool.js';
3
3
  import { useReviewSettled } from './review-settle.js';
4
4
  import { useMemo } from 'react';
5
5
  import { MultiFileDiff } from '@pierre/diffs/react';
6
- import { chatHost } from '../../../host.js';
6
+ import { useChatHost } from '../../../host.js';
7
7
  import { useDiffTheme } from '../diff-theme.js';
8
8
  export default function LegacyEditDiff({ change, maxHeight }) {
9
+ const host = useChatHost();
9
10
  useReviewSettled();
10
- const resolved = chatHost().code.useMode();
11
+ const resolved = host.code.useMode();
11
12
  const theme = useDiffTheme();
12
13
  const oldFile = useMemo(() => ({ name: change.path, contents: change.before }), [change.path, change.before]);
13
14
  const newFile = useMemo(() => ({ name: change.path, contents: change.after }), [change.path, change.after]);
@@ -4,7 +4,7 @@ import { useReviewSettled } from './review-settle.js';
4
4
  import { useMemo } from 'react';
5
5
  import clsx from 'clsx';
6
6
  import { FileDiff, PatchDiff } from '@pierre/diffs/react';
7
- import { chatHost } from '../../../host.js';
7
+ import { useChatHost } from '../../../host.js';
8
8
  import { useDiffTheme } from '../diff-theme.js';
9
9
  import { fullFileDiff } from '../full-diff.js';
10
10
  /*
@@ -40,8 +40,9 @@ function lineClass(line) {
40
40
  return 'text-text-muted';
41
41
  }
42
42
  export default function LegacyUnifiedDiff({ change, overflow = 'wrap', diffStyle = 'unified', fill = false, contents, maxHeight }) {
43
+ const host = useChatHost();
43
44
  useReviewSettled();
44
- const resolved = chatHost().code.useMode();
45
+ const resolved = host.code.useMode();
45
46
  const theme = useDiffTheme();
46
47
  const patch = useMemo(() => asPatch(change), [change]);
47
48
  const oldText = contents?.old;
@@ -1,5 +1,6 @@
1
1
  import type { Highlighter, ThemeRegistration } from 'shiki';
2
2
  import { type DiffSyntaxDocument, type DiffSyntaxTokens } from '@adecore/editor/shiki-worker';
3
+ import type { ChatHost } from '../../../host.js';
3
4
  export interface DiffSyntaxSession {
4
5
  request(index: number): Promise<DiffSyntaxTokens | null>;
5
6
  cancel(): void;
@@ -14,5 +15,5 @@ export interface DiffSyntaxRuntimeOptions {
14
15
  themes: readonly ThemeRegistration[];
15
16
  highlighter?: () => Promise<Highlighter>;
16
17
  }
17
- export declare function diffSyntaxRuntime(): DiffSyntaxRuntime;
18
+ export declare function diffSyntaxRuntime(code: ChatHost['code']): DiffSyntaxRuntime;
18
19
  export declare function createDiffSyntaxRuntime(options: DiffSyntaxRuntimeOptions): DiffSyntaxRuntime;
@@ -1,10 +1,14 @@
1
1
  import { createSyntaxService } from '@adecore/editor/shiki-worker';
2
- import { chatHost } from '../../../host.js';
3
2
  let nextGeneration = 0;
4
- let shared;
5
- export function diffSyntaxRuntime() {
6
- const code = chatHost().code;
7
- return (shared ??= createDiffSyntaxRuntime({ workerFactory: code.syntaxWorker, themes: code.custom, highlighter: code.highlighter }));
3
+ const shared = new WeakMap();
4
+ /* The runtime of a host's `code`, made on first use and shared by every diff under that host. */
5
+ export function diffSyntaxRuntime(code) {
6
+ let runtime = shared.get(code);
7
+ if (runtime === undefined) {
8
+ runtime = createDiffSyntaxRuntime({ workerFactory: code.syntaxWorker, themes: code.custom, highlighter: code.highlighter });
9
+ shared.set(code, runtime);
10
+ }
11
+ return runtime;
8
12
  }
9
13
  export function createDiffSyntaxRuntime(options) {
10
14
  const sessions = new Map();
@@ -1,7 +1,7 @@
1
1
  import type { LineToken } from '@adecore/editor';
2
2
  import { type DiffSyntaxDocument } from '@adecore/editor/shiki-worker';
3
3
  import type { DiffDocument } from '@adecore/merge';
4
- import { type DiffSyntaxRuntime } from './diff/syntax-runtime.js';
4
+ import type { DiffSyntaxRuntime } from './diff/syntax-runtime.js';
5
5
  export { createDiffSyntaxRuntime, diffSyntaxRuntime } from './diff/syntax-runtime.js';
6
6
  export type { DiffSyntaxRuntime, DiffSyntaxRuntimeOptions, DiffSyntaxSession } from './diff/syntax-runtime.js';
7
7
  export interface DiffSyntaxMapping {
@@ -16,4 +16,4 @@ export interface DiffSourceTokenSession {
16
16
  subscribe(listener: () => void): () => void;
17
17
  dispose(): void;
18
18
  }
19
- export declare function diffSourceTokens(document: DiffDocument, mapping: DiffSyntaxMapping, runtime?: DiffSyntaxRuntime): DiffSourceTokenSession;
19
+ export declare function diffSourceTokens(document: DiffDocument, mapping: DiffSyntaxMapping, runtime: DiffSyntaxRuntime): DiffSourceTokenSession;
@@ -1,8 +1,7 @@
1
1
  import { lineTokensOf } from '@adecore/editor/shiki-worker';
2
- import { diffSyntaxRuntime } from './diff/syntax-runtime.js';
3
2
  export { createDiffSyntaxRuntime, diffSyntaxRuntime } from './diff/syntax-runtime.js';
4
3
  /* Structurally accepts the editor's source mapping without depending on its React renderer. */
5
- export function diffSourceTokens(document, mapping, runtime = diffSyntaxRuntime()) {
4
+ export function diffSourceTokens(document, mapping, runtime) {
6
5
  const sides = { old: runtime.open(mapping.documents.old), new: runtime.open(mapping.documents.new) };
7
6
  const cache = { old: new Map(), new: new Map() };
8
7
  const pending = { old: new Set(), new: new Set() };
@@ -1,20 +1,21 @@
1
1
  import { useMemo } from 'react';
2
2
  import { registerCustomTheme } from '@pierre/diffs';
3
- import { chatHost } from '../../host.js';
4
- let registered = false;
5
- /* The library resolves Shiki's bundled themes by id on its own; the app's own it only knows by registration, done once the host is set. */
6
- function registerThemes() {
7
- if (registered) {
3
+ import { useChatHost } from '../../host.js';
4
+ const registered = new WeakSet();
5
+ /* The library resolves Shiki's bundled themes by id on its own; the app's own it only knows by registration, done once per host. */
6
+ function registerThemes(custom) {
7
+ if (registered.has(custom)) {
8
8
  return;
9
9
  }
10
- registered = true;
11
- for (const theme of chatHost().code.custom) {
10
+ registered.add(custom);
11
+ for (const theme of custom) {
12
12
  registerCustomTheme(theme.name, () => Promise.resolve(theme));
13
13
  }
14
14
  }
15
15
  /* The code themes a diff is drawn in, the same pair a chat's code blocks use. */
16
16
  export function useDiffTheme() {
17
- registerThemes();
18
- const { light, dark } = chatHost().code.useThemes();
17
+ const { code } = useChatHost();
18
+ registerThemes(code.custom);
19
+ const { light, dark } = code.useThemes();
19
20
  return useMemo(() => ({ light, dark }), [light, dark]);
20
21
  }
@@ -3,4 +3,4 @@ export declare const FileLinkContext: import("react").Context<string | null>;
3
3
  export declare function useFileLinkCwd(): string | null;
4
4
  export declare function useFileLinkScopeId(): string | null;
5
5
  export declare function useFileLinkTarget(text: string): FileRef | null;
6
- export declare function openFileLink(cwd: string | null, ref: FileRef, scopeId?: string | null): void;
6
+ export declare function useOpenFileLink(): (cwd: string | null, ref: FileRef, scopeId?: string | null) => void;
@@ -1,5 +1,5 @@
1
1
  import { createContext, useContext, useMemo } from 'react';
2
- import { chatHost } from '../../host.js';
2
+ import { useChatHost } from '../../host.js';
3
3
  import { ChatScopeContext } from '../../scope.js';
4
4
  /*
5
5
  * The folder a relative reference in rendered text counts from: a chat's own cwd, which is a
@@ -17,9 +17,11 @@ export function useFileLinkScopeId() {
17
17
  export function useFileLinkTarget(text) {
18
18
  const cwd = useFileLinkCwd();
19
19
  const scopeId = useFileLinkScopeId();
20
- return useMemo(() => chatHost().fileLinks?.target(text, cwd, scopeId) ?? null, [text, cwd, scopeId]);
20
+ const { fileLinks } = useChatHost();
21
+ return useMemo(() => fileLinks?.target(text, cwd, scopeId) ?? null, [fileLinks, text, cwd, scopeId]);
21
22
  }
22
- /* A reference followed, the way the app opens a file or a folder. */
23
- export function openFileLink(cwd, ref, scopeId = null) {
24
- chatHost().fileLinks?.open(cwd, ref, scopeId);
23
+ /* Follows a reference the way the app opens a file or a folder. */
24
+ export function useOpenFileLink() {
25
+ const { fileLinks } = useChatHost();
26
+ return (cwd, ref, scopeId = null) => fileLinks?.open(cwd, ref, scopeId);
25
27
  }
@@ -0,0 +1,27 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { Highlighter } from 'shiki';
3
+ import type { CodeToken, Tokenize } from './code-lines.js';
4
+ import { type ChatHost } from '../../host.js';
5
+ export type { Highlighter };
6
+ export type GrammarState = ReturnType<Highlighter['getLastGrammarState']>;
7
+ export interface CodeHighlighter {
8
+ loadLanguage(lang: string): Promise<void>;
9
+ loadTheme(theme: string, custom: ChatHost['code']['custom']): Promise<void>;
10
+ ready(langs: Iterable<string>, theme: string): Highlighter | null;
11
+ language(lang: string): string;
12
+ tokenizer(lang: string, theme: string): {
13
+ tokenize: Tokenize<GrammarState>;
14
+ fg: string | undefined;
15
+ } | null;
16
+ }
17
+ export declare function codeHighlighter(source?: () => Promise<Highlighter>): CodeHighlighter;
18
+ export declare function useCodeHighlighter(): CodeHighlighter;
19
+ export declare function loadLanguage(lang: string): Promise<void>;
20
+ export declare function loadTheme(theme: string, custom: ChatHost['code']['custom']): Promise<void>;
21
+ export declare function readyHighlighter(langs: Iterable<string>, theme: string): Highlighter | null;
22
+ export declare function loadedLanguage(lang: string): string;
23
+ export declare function tokenizerFor(lang: string, theme: string): {
24
+ tokenize: Tokenize<GrammarState>;
25
+ fg: string | undefined;
26
+ } | null;
27
+ export declare function tokenStyle(token: CodeToken): CSSProperties | undefined;