@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
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
4
4
  import clsx from 'clsx';
5
5
  import { visualThemeFragment } from '@adecore/agent-contracts/visual';
6
6
  import { Spinner, useMeasuredWidth } 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 { initialVisualContentHeight, rememberVisualHeight } from '../logic/visual-height.js';
10
10
  import { VisualBridge } from '../visual-bridge.js';
@@ -24,9 +24,10 @@ function personActsIn(frame) {
24
24
  * when the host page is served on the app's own. Its box keeps one height while it loads or fails.
25
25
  */
26
26
  export function VisualFrame({ chatId, visual, fill = false, className }) {
27
+ const { visuals, attachments } = useChatHost();
27
28
  const { t } = useTranslation('agent-chat');
28
29
  const scope = useChatScope();
29
- const frameUrl = chatHost().visuals?.frameUrl ?? null;
30
+ const frameUrl = visuals?.frameUrl ?? null;
30
31
  const boxRef = useRef(null);
31
32
  const frameRef = useRef(null);
32
33
  const bridgeRef = useRef(null);
@@ -61,12 +62,10 @@ export function VisualFrame({ chatId, visual, fill = false, className }) {
61
62
  const bridge = new VisualBridge({
62
63
  frame: () => frame.contentWindow,
63
64
  visual: { maxHeight: visual.maxHeight },
64
- page: chatHost()
65
- .attachments.read(scope.id, chatId, visual.id)
66
- .then((blob) => blob.text()),
65
+ page: attachments.read(scope.id, chatId, visual.id).then((blob) => blob.text()),
67
66
  theme: opening.theme,
68
67
  mayOpenLink: () => personActsIn(frame),
69
- openLink: (url) => chatHost().visuals?.openLink(url),
68
+ openLink: (url) => visuals?.openLink(url),
70
69
  onHeight: (next) => {
71
70
  if (fill) {
72
71
  return;
@@ -92,7 +91,7 @@ export function VisualFrame({ chatId, visual, fill = false, className }) {
92
91
  bridge.dispose();
93
92
  bridgeRef.current = null;
94
93
  };
95
- }, [opening, scope.id, chatId, visual.id, visual.maxHeight, fill]);
94
+ }, [opening, scope.id, chatId, visual.id, visual.maxHeight, fill, visuals, attachments]);
96
95
  useLayoutEffect(() => {
97
96
  const box = boxRef.current;
98
97
  const viewport = viewportRef.current;
@@ -1,3 +1,4 @@
1
1
  import type { ThemeRegistration } from 'shiki';
2
+ import { type ChatHost } from '../../host.js';
2
3
  export declare function useCodeTheme(): string;
3
- export declare function shikiThemeOf(id: string): string | ThemeRegistration;
4
+ export declare function shikiThemeOf(id: string, custom: ChatHost['code']['custom']): string | ThemeRegistration;
@@ -1,12 +1,12 @@
1
- import { chatHost } from '../../host.js';
1
+ import { useChatHost } from '../../host.js';
2
2
  /* The Shiki theme code is drawn in under the app's current light or dark. */
3
3
  export function useCodeTheme() {
4
- const { code } = chatHost();
4
+ const { code } = useChatHost();
5
5
  const mode = code.useMode();
6
6
  const themes = code.useThemes();
7
7
  return mode === 'dark' ? themes.dark : themes.light;
8
8
  }
9
9
  /* What to hand Shiki for a theme id: a theme the app ships itself, or the id of a bundled one for Shiki to load. */
10
- export function shikiThemeOf(id) {
11
- return chatHost().code.custom.find((theme) => theme.name === id) ?? id;
10
+ export function shikiThemeOf(id, custom) {
11
+ return custom.find((theme) => theme.name === id) ?? id;
12
12
  }
@@ -0,0 +1,27 @@
1
+ import { type Ref } from 'react';
2
+ import type { ChatInfo } from '@adecore/agent-contracts';
3
+ import { type Shortcut } from '@adecore/ui';
4
+ import type { StashedPrompt } from '../../stash.js';
5
+ import type { ChatControls } from './use-chat-controls.js';
6
+ interface SendProps {
7
+ visible: boolean;
8
+ disabled: boolean;
9
+ label: string;
10
+ kbd: Shortcut;
11
+ onSend(alternate: boolean): void;
12
+ }
13
+ interface ComposerControlsProps {
14
+ chatId: string;
15
+ info: ChatInfo;
16
+ providerFixed: boolean;
17
+ writable: boolean;
18
+ attachable: boolean;
19
+ controls: ChatControls;
20
+ send: SendProps;
21
+ dictationToolbar: Ref<HTMLDivElement>;
22
+ onFiles(files: File[]): void;
23
+ onRestore(prompt: StashedPrompt): void;
24
+ insert(text: string): void;
25
+ }
26
+ export declare function ComposerControls({ chatId, info, providerFixed, writable, attachable, controls, send, dictationToolbar, onFiles, onRestore, insert }: ComposerControlsProps): import("react").JSX.Element;
27
+ export {};
@@ -0,0 +1,34 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { ArrowUp, Plus, Square } from 'lucide-react';
5
+ import { ErrorBoundary, Icon, Tooltip } from '@adecore/ui';
6
+ import { useChatHost } from '../../../host.js';
7
+ import { useChatScope } from '../../../scope.js';
8
+ import { useProviders } from '../../../state/providers.js';
9
+ import { useAccountChoice } from '../../account-choice.js';
10
+ import { composerStopLabel } from '../../subagent-list.js';
11
+ import { StashPicker } from '../Pickers.js';
12
+ import { RunSettings } from '../RunSettings.js';
13
+ /* The row under the box: attach, the chat's settings, the stash, the app's own control, and stop and send. */
14
+ export function ComposerControls({ chatId, info, providerFixed, writable, attachable, controls, send, dictationToolbar, onFiles, onRestore, insert }) {
15
+ const { t } = useTranslation('agent-chat');
16
+ const scope = useChatScope();
17
+ const Slot = useChatHost().ComposerSlot;
18
+ const providers = useProviders((row) => row.providers);
19
+ const accountChoice = useAccountChoice(info.provider, info.account);
20
+ const fileInputRef = useRef(null);
21
+ const busy = info.activeTurnId !== null;
22
+ const model = providers.find((entry) => entry.kind === info.provider)?.models.find((entry) => entry.slug === info.selection.model);
23
+ // The CLI announces its session on the first message, so anything before that is still a blank chat.
24
+ const started = info.agentSessionId !== null || info.usage.turns > 0;
25
+ // A chat that has started keeps its provider (the CLI holds the thread), and so does one opened for a
26
+ // named CLI; its models stay switchable, since the CLI takes a new one on the restart the next send does.
27
+ const pickable = started || providerFixed
28
+ ? providers.filter((entry) => entry.kind === info.provider)
29
+ : providers.filter((entry) => entry.installed && entry.capabilities.chat);
30
+ return (_jsxs("div", { className: "flex items-center gap-2 pt-3 pr-3.5 pb-3.5 pl-5 @max-md/composer:gap-1.5 @max-md/composer:p-3 @max-md/composer:pt-2.5", children: [attachable && (_jsx(Tooltip, { label: t('composer.attach'), name: true, children: _jsx("button", { type: "button", className: "grid h-8 w-8 shrink-0 place-items-center rounded-full border border-border text-text-muted hover:bg-surface-hover hover:text-text disabled:opacity-50", disabled: !writable, onClick: () => fileInputRef.current?.click(), children: _jsx(Icon, { icon: Plus, size: 16 }) }) })), _jsx("input", { ref: fileInputRef, type: "file", multiple: true, hidden: true, onChange: (e) => {
31
+ onFiles(Array.from(e.target.files ?? []));
32
+ e.target.value = '';
33
+ } }), _jsx(RunSettings, { disabled: controls.configurationPending || !writable, providers: pickable, provider: info.provider, selection: info.selection, model: model, runtimeMode: info.runtimeMode, effectiveRuntimeMode: info.effectiveRuntimeMode, permissionMode: info.permissionMode, open: controls.modelPickerOpen, onOpenChange: controls.setModelPickerOpen, onModel: controls.chooseModel, onOption: (id, value) => controls.configure({ selection: { ...info.selection, options: { ...info.selection.options, [id]: value } } }), onMode: (runtimeMode) => controls.configure({ runtimeMode }), usage: info.usage, compactDisabled: busy || !writable, onCompact: controls.compact, account: accountChoice === null ? null : { choice: accountChoice, started, onPick: controls.chooseAccount } }), _jsx(StashPicker, { onRestore: onRestore }), Slot !== null && (_jsx(ErrorBoundary, { label: t('composer.slotFailed'), resetKeys: [chatId], compact: true, className: "relative rounded-lg", children: _jsx(Slot, { scopeId: scope.id, chatId: chatId, disabled: !writable, insert: insert }) })), _jsx("span", { className: "grow" }), _jsxs("div", { className: "flex h-9 shrink-0 items-center gap-0.5 rounded-full bg-surface-hover", children: [_jsx("div", { ref: dictationToolbar, className: "flex items-center pl-1 empty:hidden [&_.icon-btn]:rounded-full" }), busy && (_jsx(Tooltip, { label: composerStopLabel(), name: true, children: _jsx("button", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-status-error text-accent-text", onClick: (event) => controls.stop(event.shiftKey), children: _jsx(Icon, { icon: Square, size: 16 }) }) })), send.visible && (_jsx(Tooltip, { label: send.label, kbd: send.kbd, name: true, children: _jsx("button", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent text-accent-text hover:brightness-90 disabled:opacity-50 disabled:hover:brightness-100", disabled: send.disabled, onClick: (event) => send.onSend(event.altKey), children: _jsx(Icon, { icon: ArrowUp, size: 16 }) }) }))] })] }));
34
+ }
@@ -0,0 +1,4 @@
1
+ import type { ComposerMenus as Menus } from './use-composer-menus.js';
2
+ export declare function ComposerMenu({ menus }: {
3
+ menus: Menus;
4
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTranslation } from 'react-i18next';
3
+ import clsx from 'clsx';
4
+ import { MessageSquare, SquareSlash, Zap } from 'lucide-react';
5
+ import { FileIcon, Icon, Menu, MiddleTruncate } from '@adecore/ui';
6
+ import { optionIdOf } from './menu-aria.js';
7
+ function splitPath(path) {
8
+ const slash = path.lastIndexOf('/');
9
+ return slash < 0 ? { name: path, dir: '' } : { name: path.slice(slash + 1), dir: path.slice(0, slash) };
10
+ }
11
+ /* A row of a picker. The focus stays in the editor, which names the active row through `aria-activedescendant`. */
12
+ function MenuOption({ menus, index, className, onPick, children }) {
13
+ return (_jsx("button", { id: optionIdOf(menus.listId, index), role: "option", "aria-selected": index === menus.activeIndex, tabIndex: -1, "data-active": index === menus.activeIndex, className: clsx('cursor-row flex w-full gap-2 rounded-md px-2 py-1 text-left text-xs text-text-muted', className), onMouseEnter: () => menus.setMenuIndex(index), onMouseDown: (e) => e.preventDefault(), onClick: onPick, children: children }));
14
+ }
15
+ function CommandMenu({ menus }) {
16
+ const { t } = useTranslation('agent-chat');
17
+ return (_jsx("div", { id: menus.listId, role: "listbox", "aria-label": t('composer.menu.commands'), className: "border-b border-border px-1.5 py-1.5", children: menus.commands.map((command, index) => (_jsxs(MenuOption, { menus: menus, index: index, className: "items-center", onPick: () => menus.pickCommand(command, index), children: [_jsx(Icon, { icon: command.skill ? Zap : SquareSlash, size: 12, className: clsx('shrink-0', command.skill ? 'text-skill' : 'text-text-faint') }), _jsxs("span", { className: "font-mono text-text", children: ["/", command.name] }), _jsx("span", { className: "text-text-faint", children: command.hint })] }, command.name))) }));
18
+ }
19
+ function SkillMenu({ menus }) {
20
+ const { t } = useTranslation('agent-chat');
21
+ return (_jsx("div", { id: menus.listId, role: "listbox", "aria-label": t('composer.menu.skills'), className: "border-b border-border px-1.5 py-1.5", children: menus.skillMatches.map((skill, index) => (_jsxs(MenuOption, { menus: menus, index: index, className: "items-start", onPick: () => menus.chooseSkill(skill.name), children: [_jsx(Icon, { icon: Zap, size: 12, className: "mt-0.5 shrink-0 text-skill" }), _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsxs("span", { className: "truncate font-mono text-text", children: ["$", skill.name] }), skill.description !== '' && _jsx("span", { className: "line-clamp-1 text-text-faint", children: skill.description })] })] }, skill.name))) }));
22
+ }
23
+ function MentionMenu({ menus, query }) {
24
+ const { t } = useTranslation('agent-chat');
25
+ const { listId, chatMatches, files, mentionCount } = menus;
26
+ const mentionsLabelId = `${listId}-chats`;
27
+ const filesLabelId = `${listId}-files`;
28
+ const filesLabelShown = query === '' || chatMatches.length > 0;
29
+ return (_jsxs("div", { className: "border-b border-border px-1.5 py-1.5", children: [mentionCount > 0 && (_jsxs("div", { id: listId, role: "listbox", "aria-label": t('composer.menu.mentions'), children: [chatMatches.length > 0 && (_jsxs("div", { role: "group", "aria-labelledby": mentionsLabelId, children: [_jsx(Menu.Label, { id: mentionsLabelId, children: t('composer.mentions.chats') }), chatMatches.map((chat, index) => (_jsxs(MenuOption, { menus: menus, index: index, className: "items-center", onPick: () => menus.chooseChat(chat.id), children: [_jsx(Icon, { icon: MessageSquare, size: 14, className: "shrink-0 text-text-faint" }), _jsx("span", { className: "truncate text-text", children: chat.title || t('chatReference.untitled') })] }, chat.id)))] })), files.length > 0 && (_jsxs("div", { role: "group", ...(filesLabelShown ? { 'aria-labelledby': filesLabelId } : { 'aria-label': t('composer.mentions.files') }), children: [filesLabelShown && (_jsx(Menu.Label, { id: filesLabelId, children: query === '' ? t('composer.mentions.here') : t('composer.mentions.files') })), files.map((path, index) => {
30
+ const { name, dir } = splitPath(path);
31
+ return (_jsxs(MenuOption, { menus: menus, index: chatMatches.length + index, className: "items-center", onPick: () => menus.chooseMention(path), children: [_jsx(FileIcon, { path: path, size: 14 }), _jsx("span", { className: "truncate font-mono text-text", children: name }), dir && _jsx(MiddleTruncate, { text: dir, tooltip: false, className: "text-text-faint" })] }, path));
32
+ })] }))] })), mentionCount === 0 && (_jsx("div", { className: "px-2 py-1 text-xs text-text-faint", children: query ? t('composer.mentions.noMatch') : t('composer.mentions.empty') }))] }));
33
+ }
34
+ /* Whichever picker the caret opened, over the box. */
35
+ export function ComposerMenu({ menus }) {
36
+ if (menus.commandMenuOpen) {
37
+ return _jsx(CommandMenu, { menus: menus });
38
+ }
39
+ if (menus.skillMenuOpen) {
40
+ return _jsx(SkillMenu, { menus: menus });
41
+ }
42
+ if (menus.mentionMenuOpen && menus.mention !== null) {
43
+ return _jsx(MentionMenu, { menus: menus, query: menus.mention.query });
44
+ }
45
+ return null;
46
+ }
@@ -0,0 +1,18 @@
1
+ import type { ChatAttachmentUpload } from '@adecore/agent-contracts';
2
+ interface DraftQuoteProps {
3
+ chatId: string;
4
+ quote: string;
5
+ source: string | null;
6
+ onRemove(): void;
7
+ onSourceGone(): void;
8
+ }
9
+ export declare function DraftQuote({ chatId, quote, source, onRemove, onSourceGone }: DraftQuoteProps): import("react").JSX.Element;
10
+ export declare function DraftChats({ chats, onRemove }: {
11
+ chats: readonly string[];
12
+ onRemove(id: string): void;
13
+ }): import("react").JSX.Element;
14
+ export declare function DraftAttachments({ attachments, onRemove }: {
15
+ attachments: readonly ChatAttachmentUpload[];
16
+ onRemove(index: number): void;
17
+ }): import("react").JSX.Element;
18
+ export {};
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Paperclip, X } from 'lucide-react';
4
+ import { Icon, IconButton, Tooltip } from '@adecore/ui';
5
+ import { useChatScope } from '../../../scope.js';
6
+ import { formatBytes, isImageAttachment, uploadBytes } from '../../attachments.js';
7
+ import { jumpToTimelineItem } from '../../timeline-scroll.js';
8
+ import { ChatReferenceChip } from '../ChatReferenceChip.js';
9
+ import { UploadThumb } from '../UploadThumb.js';
10
+ const ROW = 'px-5 pt-4 @max-md/composer:px-3.5 @max-md/composer:pt-3';
11
+ /* The quote that goes above the message, as a blockquote. */
12
+ export function DraftQuote({ chatId, quote, source, onRemove, onSourceGone }) {
13
+ const { t } = useTranslation('agent-chat');
14
+ const scope = useChatScope();
15
+ return (_jsxs("div", { className: `flex items-center gap-2 ${ROW}`, children: [source === null ? (_jsx("span", { className: "min-w-0 grow truncate border-l-2 border-border-strong pl-2.5 text-xs text-text-muted", children: quote })) : (_jsx(Tooltip, { label: t('composer.quote.goToSource'), children: _jsx("button", { type: "button", className: "min-w-0 grow truncate border-l-2 border-border-strong pl-2.5 text-left text-xs text-text-muted hover:text-text", onClick: () => jumpToTimelineItem(scope.keyOf(chatId), source, { flash: true, onMissing: onSourceGone }), children: quote }) })), _jsx(IconButton, { icon: X, size: "2xs", label: t('composer.quote.remove'), onClick: onRemove })] }));
16
+ }
17
+ /* Other chats the message points at, beside the text rather than in it. */
18
+ export function DraftChats({ chats, onRemove }) {
19
+ return (_jsx("div", { className: `flex flex-wrap gap-1.5 ${ROW}`, children: chats.map((id) => (_jsx(ChatReferenceChip, { chatId: id, onRemove: () => onRemove(id) }, id))) }));
20
+ }
21
+ /* The files the message carries: an image as a thumbnail, anything else by its name and size. */
22
+ export function DraftAttachments({ attachments, onRemove }) {
23
+ const { t } = useTranslation('agent-chat');
24
+ return (_jsx("div", { className: `flex flex-wrap gap-2 ${ROW}`, children: attachments.map((attachment, index) => (_jsxs("div", { className: "group/thumb relative", children: [isImageAttachment(attachment.mime) ? (_jsx(UploadThumb, { upload: attachment })) : (_jsxs("span", { className: "flex h-14 w-36 flex-col justify-center gap-0.5 rounded-lg border border-border bg-surface-sunken px-2.5", children: [_jsxs("span", { className: "flex items-center gap-1.5 text-xs text-text", children: [_jsx(Icon, { icon: Paperclip, size: 12, className: "shrink-0 text-text-faint" }), _jsx("span", { className: "truncate", children: attachment.name })] }), _jsx("span", { className: "pl-5 text-xs text-text-faint", children: formatBytes(uploadBytes(attachment)) })] })), _jsx(IconButton, { icon: X, size: "2xs", label: t('composer.removeAttachment', { name: attachment.name }), className: "absolute -top-1.5 -right-1.5 border border-border bg-surface-raised opacity-0 transition-opacity group-hover/thumb:opacity-100 focus-visible:opacity-100", onClick: () => onRemove(index) })] }, `${attachment.name}-${index}`))) }));
25
+ }
@@ -0,0 +1,10 @@
1
+ import type { ChatQueuedMessage } from '@adecore/agent-contracts';
2
+ interface QueuedMessagesProps {
3
+ chatId: string;
4
+ queue: readonly ChatQueuedMessage[];
5
+ paused: boolean;
6
+ takingBack: boolean;
7
+ onEdit(message: ChatQueuedMessage): void;
8
+ }
9
+ export declare function QueuedMessages({ chatId, queue, paused, takingBack, onEdit }: QueuedMessagesProps): import("react").JSX.Element;
10
+ export {};
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Clock, Copy, FastForward, Pencil, X } from 'lucide-react';
4
+ import { ButtonGroup, ContextMenu, copyText, Icon, IconButton } from '@adecore/ui';
5
+ import { useChatActions } from '../../actions.js';
6
+ import { scrollTimelineToEnd } from '../../timeline-scroll.js';
7
+ /* The messages waiting for the running turn to end, each with a way to send it now, edit it or drop it. */
8
+ export function QueuedMessages({ chatId, queue, paused, takingBack, onEdit }) {
9
+ const { t } = useTranslation('agent-chat');
10
+ const actions = useChatActions();
11
+ return (_jsxs("div", { className: "flex flex-col gap-1 border-b border-border px-2 py-1.5", children: [paused && _jsx("p", { className: "px-1.5 text-xs text-text-muted", children: t('composer.queue.paused') }), queue.map((message) => {
12
+ const sendNow = () => {
13
+ scrollTimelineToEnd(chatId, 'instant');
14
+ void actions.sendNow(chatId, message.id).catch(() => undefined);
15
+ };
16
+ const unqueue = () => void actions.unqueue(chatId, message.id).catch(() => undefined);
17
+ const edit = () => onEdit(message);
18
+ return (_jsxs(ContextMenu.Root, { children: [_jsxs(ContextMenu.Trigger, { className: "group/queued flex items-center gap-2 rounded-md px-1.5 py-1 text-xs text-text-muted", children: [_jsx(Icon, { icon: Clock, size: 12, className: "shrink-0 text-text-faint" }), _jsx("span", { className: "min-w-0 grow truncate", children: message.text || t('composer.queue.attachments', { count: message.attachments?.length ?? 0 }) }), _jsxs(ButtonGroup, { render: _jsx("span", {}), className: "opacity-0 transition-opacity group-hover/queued:opacity-100 focus-within:opacity-100", children: [_jsx(IconButton, { icon: FastForward, size: "2xs", label: t('composer.queue.sendNow'), onClick: sendNow }), _jsx(IconButton, { icon: Pencil, size: "2xs", label: t('common.action.edit'), disabled: takingBack, onClick: edit }), _jsx(IconButton, { icon: X, size: "2xs", label: t('common.action.remove'), onClick: unqueue })] })] }), _jsxs(ContextMenu.Popup, { children: [_jsxs(ContextMenu.Item, { onClick: sendNow, children: [_jsx(Icon, { icon: FastForward, size: 14 }), " ", t('composer.queue.sendNow')] }), _jsxs(ContextMenu.Item, { disabled: takingBack, onClick: edit, children: [_jsx(Icon, { icon: Pencil, size: 14 }), " ", t('common.action.edit')] }), _jsxs(ContextMenu.Item, { disabled: message.text === '', onClick: () => copyText(message.text), children: [_jsx(Icon, { icon: Copy, size: 14 }), " ", t('common.action.copy')] }), _jsx(ContextMenu.Separator, {}), _jsxs(ContextMenu.Item, { className: "text-status-error", onClick: unqueue, children: [_jsx(Icon, { icon: X, size: 14 }), " ", t('common.action.remove')] })] })] }, message.id));
19
+ })] }));
20
+ }
@@ -0,0 +1,20 @@
1
+ import type { EditorView } from '@codemirror/view';
2
+ import type { ChatHost } from '../../../host.js';
3
+ import type { ComposerSend } from '../../composer-send.js';
4
+ import type { ChatSendKey } from '../../send-key.js';
5
+ import type { ComposerMenus } from './use-composer-menus.js';
6
+ import type { PromptHistory } from './use-prompt-history.js';
7
+ interface KeyHandlerInput {
8
+ chatId: string;
9
+ host: Pick<ChatHost, 'isApplePlatform' | 'isAppShortcut' | 'openNewChat'>;
10
+ sendKey: ChatSendKey;
11
+ text: string;
12
+ menus: ComposerMenus;
13
+ history: PromptHistory;
14
+ noteKey(e: KeyboardEvent): void;
15
+ toggleStash(): void;
16
+ setText(next: string): void;
17
+ submit(how: ComposerSend): void;
18
+ }
19
+ export declare function composerKeyHandler(input: KeyHandlerInput): (e: KeyboardEvent, view: EditorView) => boolean;
20
+ export {};
@@ -0,0 +1,150 @@
1
+ import { indentLess, indentMore, insertNewline } from '@codemirror/commands';
2
+ import { ensureSyntaxTree, syntaxTree } from '@codemirror/language';
3
+ import { isModHeld, matchesShortcut } from '@adecore/ui';
4
+ import { STASH_SHORTCUT } from '../../stash.js';
5
+ import { pageTimeline } from '../../timeline-scroll.js';
6
+ import { enterAction, inCode, inFenceBody, inOpenFence, listItemAt, recallDirection, tabSpaces } from './keys.js';
7
+ const SEND_HOW = {
8
+ send: 'default',
9
+ 'send-alternate': 'alternate',
10
+ 'send-and-new': 'and-new'
11
+ };
12
+ /* In a fence Tab indents like a code editor; anywhere else it keeps moving the focus. */
13
+ function indentInFence(e, view) {
14
+ const { state } = view;
15
+ const { from, to, head } = state.selection.main;
16
+ const tree = ensureSyntaxTree(state, state.doc.length, 50) ?? syntaxTree(state);
17
+ if (!inFenceBody(tree, state.doc.toString(), head)) {
18
+ return false;
19
+ }
20
+ // Handled even when there is nothing to outdent, so Shift+Tab never throws the focus out of a block.
21
+ if (e.shiftKey) {
22
+ indentLess(view);
23
+ }
24
+ else if (from !== to) {
25
+ indentMore(view);
26
+ }
27
+ else {
28
+ view.dispatch(state.replaceSelection(tabSpaces(head - state.doc.lineAt(head).from)), { scrollIntoView: true, userEvent: 'input' });
29
+ }
30
+ return true;
31
+ }
32
+ /* The keys an open picker takes: the arrows walk it, and Tab (or Enter, outside the commands) picks. */
33
+ function pickInMenu(e, { menus, setText }) {
34
+ const { activeIndex } = menus;
35
+ if (menus.commandMenuOpen) {
36
+ if (menus.moveInMenu(e.key, menus.commands.length)) {
37
+ return true;
38
+ }
39
+ if (e.key === 'Tab') {
40
+ const chosen = menus.commands[activeIndex];
41
+ if (chosen.skill) {
42
+ menus.chooseSkill(chosen.name);
43
+ }
44
+ else {
45
+ setText(`/${chosen.name} `);
46
+ }
47
+ return true;
48
+ }
49
+ }
50
+ if (menus.skillMenuOpen) {
51
+ if (menus.moveInMenu(e.key, menus.skillMatches.length)) {
52
+ return true;
53
+ }
54
+ if (e.key === 'Tab' || e.key === 'Enter') {
55
+ menus.chooseSkill((menus.skillMatches[activeIndex] ?? menus.skillMatches[0]).name);
56
+ return true;
57
+ }
58
+ }
59
+ if (menus.mentionMenuOpen && menus.mentionCount > 0) {
60
+ if (menus.moveInMenu(e.key, menus.mentionCount)) {
61
+ return true;
62
+ }
63
+ if (e.key === 'Tab' || e.key === 'Enter') {
64
+ menus.chooseMentionAt(activeIndex);
65
+ return true;
66
+ }
67
+ }
68
+ return false;
69
+ }
70
+ /* Enter sends, adds a line, or carries a list on, by the send key and where the caret is. */
71
+ function enter(e, view, { host, sendKey, submit }) {
72
+ const { state } = view;
73
+ const { from, to, head } = state.selection.main;
74
+ // Parsed to the end, since the fence that closes the block can sit after the caret.
75
+ const tree = ensureSyntaxTree(state, state.doc.length, 50) ?? syntaxTree(state);
76
+ const line = state.doc.lineAt(head);
77
+ const list = from === to && !inCode(tree, state.doc.toString(), head) ? listItemAt(line.text) : null;
78
+ const action = enterAction({ shift: e.shiftKey, mod: isModHeld(e, host.isApplePlatform()), alt: e.altKey }, {
79
+ inOpenFence: inOpenFence(tree, head),
80
+ list,
81
+ column: head - line.from,
82
+ sendKey,
83
+ multiline: state.doc.lines > 1,
84
+ startsNewChat: host.openNewChat !== null
85
+ });
86
+ if (action === 'newline') {
87
+ return insertNewline(view);
88
+ }
89
+ if (action === 'continue-list') {
90
+ if (list) {
91
+ view.dispatch({
92
+ changes: { from: head, insert: `\n${list.next}` },
93
+ selection: { anchor: head + 1 + list.next.length },
94
+ scrollIntoView: true,
95
+ userEvent: 'input'
96
+ });
97
+ }
98
+ return true;
99
+ }
100
+ if (action === 'leave-list') {
101
+ view.dispatch({ changes: { from: line.from, to: line.to }, selection: { anchor: line.from }, userEvent: 'delete' });
102
+ return true;
103
+ }
104
+ submit(SEND_HOW[action]);
105
+ return true;
106
+ }
107
+ /* Runs before the editor's own keymap; true means the key is handled and its default prevented. */
108
+ export function composerKeyHandler(input) {
109
+ const { chatId, host, text, history } = input;
110
+ return (e, view) => {
111
+ input.noteKey(e);
112
+ // Escape leaves the node, unless it first has a recalled prompt to put back.
113
+ if (e.key === 'Escape' && !e.isComposing) {
114
+ if (!history.leave()) {
115
+ return false;
116
+ }
117
+ e.stopPropagation();
118
+ return true;
119
+ }
120
+ // The app's own shortcuts stay the app's while you type: Mod+Shift+Enter would otherwise send as well.
121
+ if (host.isAppShortcut(e)) {
122
+ return false;
123
+ }
124
+ e.stopPropagation();
125
+ // A key inside an IME composition belongs to the composition, Enter and the arrows included.
126
+ if (e.isComposing) {
127
+ return false;
128
+ }
129
+ // Reading back through a long answer should not mean leaving the box you are typing in.
130
+ if (e.key === 'PageUp' || e.key === 'PageDown') {
131
+ return pageTimeline(chatId, e.key === 'PageUp' ? -1 : 1);
132
+ }
133
+ if (matchesShortcut(STASH_SHORTCUT, e, host.isApplePlatform())) {
134
+ input.toggleStash();
135
+ return true;
136
+ }
137
+ if (pickInMenu(e, input)) {
138
+ return true;
139
+ }
140
+ if (e.key === 'Tab' && !e.altKey && !e.metaKey && !e.ctrlKey && indentInFence(e, view)) {
141
+ return true;
142
+ }
143
+ if (e.key === 'Enter') {
144
+ return enter(e, view, input);
145
+ }
146
+ const { from, to } = view.state.selection.main;
147
+ const direction = recallDirection({ key: e.key, text, from, to, recalled: history.recalled(text) });
148
+ return direction !== null && history.recall(direction);
149
+ };
150
+ }
@@ -1,8 +1,10 @@
1
1
  import type { Tree } from '@lezer/common';
2
- export type EnterAction = 'send' | 'newline' | 'continue-list' | 'leave-list';
2
+ import { type ChatSendKey } from '../../send-key.js';
3
+ export type EnterAction = 'send' | 'send-alternate' | 'send-and-new' | 'newline' | 'continue-list' | 'leave-list';
3
4
  export interface EnterKeys {
4
5
  shift: boolean;
5
6
  mod: boolean;
7
+ alt?: boolean;
6
8
  }
7
9
  export interface ListItem {
8
10
  prefix: string;
@@ -13,9 +15,12 @@ export interface EnterContext {
13
15
  inOpenFence: boolean;
14
16
  list: ListItem | null;
15
17
  column: number;
18
+ sendKey?: ChatSendKey;
19
+ multiline?: boolean;
20
+ startsNewChat?: boolean;
16
21
  }
17
22
  export declare function listItemAt(line: string): ListItem | null;
18
- export declare function enterAction(keys: EnterKeys, { inOpenFence, list, column }: EnterContext): EnterAction;
23
+ export declare function enterAction(keys: EnterKeys, { inOpenFence, list, column, sendKey, multiline, startsNewChat }: EnterContext): EnterAction;
19
24
  export declare function inOpenFence(tree: Tree, pos: number): boolean;
20
25
  export declare function inFenceBody(tree: Tree, text: string, pos: number): boolean;
21
26
  export declare function tabSpaces(column: number): string;
@@ -1,3 +1,4 @@
1
+ import { modSends } from '../../send-key.js';
1
2
  const LIST_ITEM = /^([ \t]*)(?:([-*+])|(\d{1,9})([.)]))([ \t]+)(\[[ xX]\](?:[ \t]+|$))?/;
2
3
  /* The list item a line starts, if it starts one. */
3
4
  export function listItemAt(line) {
@@ -12,23 +13,32 @@ export function listItemAt(line) {
12
13
  /*
13
14
  * Enter sends, except where whoever types is still writing: inside a fence nobody has closed yet
14
15
  * it adds a line, and in a list it starts the next item, or leaves the list from an empty one, so
15
- * a second Enter breaks out. Shift adds a plain line and Mod sends from anywhere.
16
+ * a second Enter breaks out. Shift adds a plain line and Mod sends from anywhere. Where the send key
17
+ * is Mod+Enter, Enter adds a line instead of sending. Alt sends the other way (queue or steer), and so
18
+ * does Mod+Shift+Enter where Mod+Enter is the send key; Mod+Alt+Enter is the send that opens a new chat.
16
19
  */
17
- export function enterAction(keys, { inOpenFence, list, column }) {
20
+ export function enterAction(keys, { inOpenFence, list, column, sendKey = 'enter', multiline = false, startsNewChat = false }) {
21
+ const modOnly = modSends(sendKey, multiline);
18
22
  if (keys.mod) {
19
- return 'send';
23
+ if (keys.alt && startsNewChat) {
24
+ return 'send-and-new';
25
+ }
26
+ return keys.alt || (modOnly && keys.shift) ? 'send-alternate' : 'send';
20
27
  }
21
28
  if (keys.shift || inOpenFence) {
22
29
  return 'newline';
23
30
  }
24
- if (list === null) {
25
- return 'send';
31
+ if (list !== null) {
32
+ // A caret in front of the marker moves the item down rather than splitting its marker.
33
+ if (column < list.prefix.length) {
34
+ return 'newline';
35
+ }
36
+ return list.empty ? 'leave-list' : 'continue-list';
26
37
  }
27
- // A caret in front of the marker moves the item down rather than splitting its marker.
28
- if (column < list.prefix.length) {
29
- return 'newline';
38
+ if (keys.alt) {
39
+ return 'send-alternate';
30
40
  }
31
- return list.empty ? 'leave-list' : 'continue-list';
41
+ return modOnly ? 'newline' : 'send';
32
42
  }
33
43
  /* The innermost fenced code block around `pos`, if any. */
34
44
  function enclosingFence(tree, pos) {
@@ -0,0 +1,9 @@
1
+ interface PlaceholderInput {
2
+ chatId: string;
3
+ disabled: boolean;
4
+ readOnly: boolean;
5
+ mentionable: boolean;
6
+ hasSkills: boolean;
7
+ }
8
+ export declare function useHintedPlaceholder({ chatId, disabled, readOnly, mentionable, hasSkills }: PlaceholderInput): string | HTMLElement;
9
+ export {};
@@ -0,0 +1,38 @@
1
+ import { useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { useChatHost } from '../../../host.js';
4
+ import { useChatScope } from '../../../scope.js';
5
+ /* The empty box names its sigils as key caps, so they read as keys to press rather than as punctuation. */
6
+ function hintedPlaceholder(lead, joiner, hints) {
7
+ const root = document.createElement('span');
8
+ root.className = 'composer-hints';
9
+ const extra = document.createElement('span');
10
+ extra.className = 'composer-hints-extra';
11
+ extra.append(joiner);
12
+ for (const hint of hints) {
13
+ const cap = document.createElement('kbd');
14
+ cap.textContent = hint.key;
15
+ extra.append(cap, hint.label);
16
+ }
17
+ root.append(lead, extra);
18
+ return root;
19
+ }
20
+ /* What the empty box says: why nothing can be written, or the host's words and the keys that open a picker. */
21
+ export function useHintedPlaceholder({ chatId, disabled, readOnly, mentionable, hasSkills }) {
22
+ const { t } = useTranslation('agent-chat');
23
+ const scope = useChatScope();
24
+ const lead = useChatHost().useComposerPlaceholder(scope.id, chatId);
25
+ return useMemo(() => {
26
+ if (disabled) {
27
+ return t('composer.placeholder.disconnected');
28
+ }
29
+ if (readOnly) {
30
+ return t('composer.placeholder.readOnly');
31
+ }
32
+ return hintedPlaceholder(lead ?? t('composer.placeholder.lead'), t('composer.placeholder.joiner'), [
33
+ { key: '/', label: t('composer.placeholder.commands') },
34
+ ...(mentionable ? [{ key: '@', label: t('composer.placeholder.files') }] : []),
35
+ ...(hasSkills ? [{ key: '$', label: t('composer.placeholder.skills') }] : [])
36
+ ]);
37
+ }, [disabled, readOnly, lead, mentionable, hasSkills, t]);
38
+ }
@@ -0,0 +1,28 @@
1
+ import type { AgentKind, ChatInfo, ModelSelection, RuntimeMode } from '@adecore/agent-contracts';
2
+ import type { ComposerNotice } from './use-notice.js';
3
+ interface ControlsInput {
4
+ chatId: string;
5
+ info: ChatInfo;
6
+ notice: ComposerNotice;
7
+ onRetarget(provider: AgentKind, selection: ModelSelection): Promise<void>;
8
+ }
9
+ export declare function useChatControls({ chatId, info, notice: { setNotice, showError }, onRetarget }: ControlsInput): {
10
+ configurationPending: boolean;
11
+ configure: (patch: {
12
+ selection?: ModelSelection;
13
+ runtimeMode?: RuntimeMode;
14
+ account?: string;
15
+ }) => void;
16
+ chooseModel: (provider: AgentKind, slug: string) => void;
17
+ chooseAccount: (account: string) => void;
18
+ stop: (shiftKey: boolean) => void;
19
+ compact: () => void;
20
+ runCommand: (name: string) => boolean;
21
+ modelPickerOpen: boolean;
22
+ setModelPickerOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
23
+ confirmClear: boolean;
24
+ cancelClear: () => void;
25
+ forceClearThread: () => Promise<void>;
26
+ };
27
+ export type ChatControls = ReturnType<typeof useChatControls>;
28
+ export {};