@adecore/agents-react 0.0.1 → 0.17.0-beta.2

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 (513) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +61 -0
  3. package/dist/agents/AccountDot.d.ts +4 -0
  4. package/dist/agents/AccountDot.js +8 -0
  5. package/dist/agents/AgentIcon.d.ts +6 -0
  6. package/dist/agents/AgentIcon.js +20 -0
  7. package/dist/agents/ModelOptionControl.d.ts +15 -0
  8. package/dist/agents/ModelOptionControl.js +33 -0
  9. package/dist/agents/ProviderLogo.d.ts +8 -0
  10. package/dist/agents/ProviderLogo.js +8 -0
  11. package/dist/agents/account-limits.d.ts +7 -0
  12. package/dist/agents/account-limits.js +40 -0
  13. package/dist/agents/accounts.d.ts +22 -0
  14. package/dist/agents/accounts.js +113 -0
  15. package/dist/agents/model-name.d.ts +12 -0
  16. package/dist/agents/model-name.js +56 -0
  17. package/dist/agents/model-options.d.ts +5 -0
  18. package/dist/agents/model-options.js +15 -0
  19. package/dist/agents/provider-paths.d.ts +2 -0
  20. package/dist/agents/provider-paths.js +8 -0
  21. package/dist/agents/status-look.d.ts +13 -0
  22. package/dist/agents/status-look.js +24 -0
  23. package/dist/chat/account-choice.d.ts +11 -0
  24. package/dist/chat/account-choice.js +30 -0
  25. package/dist/chat/actions.d.ts +6 -0
  26. package/dist/chat/actions.js +68 -0
  27. package/dist/chat/activity.d.ts +5 -0
  28. package/dist/chat/activity.js +6 -0
  29. package/dist/chat/attachments.d.ts +23 -0
  30. package/dist/chat/attachments.js +86 -0
  31. package/dist/chat/bookmarks.d.ts +14 -0
  32. package/dist/chat/bookmarks.js +73 -0
  33. package/dist/chat/chat-client.d.ts +55 -0
  34. package/dist/chat/chat-client.js +279 -0
  35. package/dist/chat/chat-references.d.ts +5 -0
  36. package/dist/chat/chat-references.js +5 -0
  37. package/dist/chat/drafts.d.ts +29 -0
  38. package/dist/chat/drafts.js +116 -0
  39. package/dist/chat/forks.d.ts +2 -0
  40. package/dist/chat/forks.js +28 -0
  41. package/dist/chat/guards.d.ts +12 -0
  42. package/dist/chat/guards.js +69 -0
  43. package/dist/chat/logic/ansi.d.ts +13 -0
  44. package/dist/chat/logic/ansi.js +185 -0
  45. package/dist/chat/logic/answers.d.ts +1 -0
  46. package/dist/chat/logic/answers.js +8 -0
  47. package/dist/chat/logic/bookmarks.d.ts +5 -0
  48. package/dist/chat/logic/bookmarks.js +33 -0
  49. package/dist/chat/logic/context-usage.d.ts +12 -0
  50. package/dist/chat/logic/context-usage.js +21 -0
  51. package/dist/chat/logic/fork.d.ts +45 -0
  52. package/dist/chat/logic/fork.js +148 -0
  53. package/dist/chat/logic/handback.d.ts +2 -0
  54. package/dist/chat/logic/handback.js +8 -0
  55. package/dist/chat/logic/json.d.ts +1 -0
  56. package/dist/chat/logic/json.js +4 -0
  57. package/dist/chat/logic/limit.d.ts +7 -0
  58. package/dist/chat/logic/limit.js +34 -0
  59. package/dist/chat/logic/resume-compaction.d.ts +14 -0
  60. package/dist/chat/logic/resume-compaction.js +47 -0
  61. package/dist/chat/logic/scrubber.d.ts +50 -0
  62. package/dist/chat/logic/scrubber.js +220 -0
  63. package/dist/chat/logic/thread-cards.d.ts +3 -0
  64. package/dist/chat/logic/thread-cards.js +59 -0
  65. package/dist/chat/logic/timeline-copy.d.ts +4 -0
  66. package/dist/chat/logic/timeline-copy.js +59 -0
  67. package/dist/chat/logic/timeline-target.d.ts +12 -0
  68. package/dist/chat/logic/timeline-target.js +34 -0
  69. package/dist/chat/logic/timeline.d.ts +115 -0
  70. package/dist/chat/logic/timeline.js +343 -0
  71. package/dist/chat/logic/tool-catalog.d.ts +10 -0
  72. package/dist/chat/logic/tool-catalog.js +44 -0
  73. package/dist/chat/logic/tools.d.ts +15 -0
  74. package/dist/chat/logic/tools.js +82 -0
  75. package/dist/chat/logic/welcome.d.ts +2 -0
  76. package/dist/chat/logic/welcome.js +10 -0
  77. package/dist/chat/logic/workflow.d.ts +14 -0
  78. package/dist/chat/logic/workflow.js +47 -0
  79. package/dist/chat/mentions.d.ts +65 -0
  80. package/dist/chat/mentions.js +184 -0
  81. package/dist/chat/persisted-json.d.ts +5 -0
  82. package/dist/chat/persisted-json.js +58 -0
  83. package/dist/chat/preferences.d.ts +27 -0
  84. package/dist/chat/preferences.js +139 -0
  85. package/dist/chat/quote.d.ts +2 -0
  86. package/dist/chat/quote.js +25 -0
  87. package/dist/chat/recent-messages.d.ts +17 -0
  88. package/dist/chat/recent-messages.js +36 -0
  89. package/dist/chat/resume-compaction-dismissals.d.ts +3 -0
  90. package/dist/chat/resume-compaction-dismissals.js +14 -0
  91. package/dist/chat/runtime-modes.d.ts +4 -0
  92. package/dist/chat/runtime-modes.js +15 -0
  93. package/dist/chat/stash.d.ts +24 -0
  94. package/dist/chat/stash.js +91 -0
  95. package/dist/chat/subagent-conversation.d.ts +41 -0
  96. package/dist/chat/subagent-conversation.js +167 -0
  97. package/dist/chat/subagent-list.d.ts +19 -0
  98. package/dist/chat/subagent-list.js +106 -0
  99. package/dist/chat/subagent-support.d.ts +6 -0
  100. package/dist/chat/subagent-support.js +14 -0
  101. package/dist/chat/subagent-view.d.ts +35 -0
  102. package/dist/chat/subagent-view.js +96 -0
  103. package/dist/chat/thumbnails.d.ts +10 -0
  104. package/dist/chat/thumbnails.js +89 -0
  105. package/dist/chat/timeline-flash.d.ts +12 -0
  106. package/dist/chat/timeline-flash.js +16 -0
  107. package/dist/chat/timeline-scroll.d.ts +27 -0
  108. package/dist/chat/timeline-scroll.js +152 -0
  109. package/dist/chat/ui/AccountPill.d.ts +3 -0
  110. package/dist/chat/ui/AccountPill.js +23 -0
  111. package/dist/chat/ui/AnsiOutput.d.ts +5 -0
  112. package/dist/chat/ui/AnsiOutput.js +9 -0
  113. package/dist/chat/ui/BookmarkMarker.d.ts +8 -0
  114. package/dist/chat/ui/BookmarkMarker.js +57 -0
  115. package/dist/chat/ui/BookmarkSubmenu.d.ts +3 -0
  116. package/dist/chat/ui/BookmarkSubmenu.js +33 -0
  117. package/dist/chat/ui/ChatActivity.d.ts +7 -0
  118. package/dist/chat/ui/ChatActivity.js +84 -0
  119. package/dist/chat/ui/ChatReferenceChip.d.ts +4 -0
  120. package/dist/chat/ui/ChatReferenceChip.js +16 -0
  121. package/dist/chat/ui/CodeBlock.d.ts +4 -0
  122. package/dist/chat/ui/CodeBlock.js +132 -0
  123. package/dist/chat/ui/Composer.d.ts +15 -0
  124. package/dist/chat/ui/Composer.js +856 -0
  125. package/dist/chat/ui/ComposerInput.d.ts +29 -0
  126. package/dist/chat/ui/ComposerInput.js +127 -0
  127. package/dist/chat/ui/DiffPool.d.ts +5 -0
  128. package/dist/chat/ui/DiffPool.js +26 -0
  129. package/dist/chat/ui/EditDiff.d.ts +4 -0
  130. package/dist/chat/ui/EditDiff.js +21 -0
  131. package/dist/chat/ui/FadingWords.d.ts +3 -0
  132. package/dist/chat/ui/FadingWords.js +5 -0
  133. package/dist/chat/ui/ImageView.d.ts +6 -0
  134. package/dist/chat/ui/ImageView.js +81 -0
  135. package/dist/chat/ui/LimitState.d.ts +8 -0
  136. package/dist/chat/ui/LimitState.js +112 -0
  137. package/dist/chat/ui/Markdown.d.ts +18 -0
  138. package/dist/chat/ui/Markdown.js +159 -0
  139. package/dist/chat/ui/MessageActions.d.ts +9 -0
  140. package/dist/chat/ui/MessageActions.js +53 -0
  141. package/dist/chat/ui/Pickers.d.ts +21 -0
  142. package/dist/chat/ui/Pickers.js +111 -0
  143. package/dist/chat/ui/PromptComposer.d.ts +13 -0
  144. package/dist/chat/ui/PromptComposer.js +56 -0
  145. package/dist/chat/ui/QuoteButton.d.ts +7 -0
  146. package/dist/chat/ui/QuoteButton.js +74 -0
  147. package/dist/chat/ui/ResumeCompactionDock.d.ts +5 -0
  148. package/dist/chat/ui/ResumeCompactionDock.js +14 -0
  149. package/dist/chat/ui/RunSettings.d.ts +28 -0
  150. package/dist/chat/ui/RunSettings.js +153 -0
  151. package/dist/chat/ui/Scrubber.d.ts +17 -0
  152. package/dist/chat/ui/Scrubber.js +118 -0
  153. package/dist/chat/ui/SubagentControls.d.ts +11 -0
  154. package/dist/chat/ui/SubagentControls.js +36 -0
  155. package/dist/chat/ui/SubagentInfo.d.ts +4 -0
  156. package/dist/chat/ui/SubagentInfo.js +34 -0
  157. package/dist/chat/ui/SubagentStopButton.d.ts +7 -0
  158. package/dist/chat/ui/SubagentStopButton.js +34 -0
  159. package/dist/chat/ui/SubagentTimeline.d.ts +4 -0
  160. package/dist/chat/ui/SubagentTimeline.js +107 -0
  161. package/dist/chat/ui/Timeline.d.ts +6 -0
  162. package/dist/chat/ui/Timeline.js +424 -0
  163. package/dist/chat/ui/TimelineMenu.d.ts +12 -0
  164. package/dist/chat/ui/TimelineMenu.js +54 -0
  165. package/dist/chat/ui/UnifiedDiff.d.ts +11 -0
  166. package/dist/chat/ui/UnifiedDiff.js +66 -0
  167. package/dist/chat/ui/UploadThumb.d.ts +4 -0
  168. package/dist/chat/ui/UploadThumb.js +11 -0
  169. package/dist/chat/ui/Welcome.d.ts +4 -0
  170. package/dist/chat/ui/Welcome.js +16 -0
  171. package/dist/chat/ui/chips.d.ts +4 -0
  172. package/dist/chat/ui/chips.js +11 -0
  173. package/dist/chat/ui/code-lines.d.ts +18 -0
  174. package/dist/chat/ui/code-lines.js +46 -0
  175. package/dist/chat/ui/code-streaming.d.ts +1 -0
  176. package/dist/chat/ui/code-streaming.js +3 -0
  177. package/dist/chat/ui/code-theme.d.ts +3 -0
  178. package/dist/chat/ui/code-theme.js +12 -0
  179. package/dist/chat/ui/composer/chips.d.ts +6 -0
  180. package/dist/chat/ui/composer/chips.js +31 -0
  181. package/dist/chat/ui/composer/editor.d.ts +11 -0
  182. package/dist/chat/ui/composer/editor.js +98 -0
  183. package/dist/chat/ui/composer/insert.d.ts +2 -0
  184. package/dist/chat/ui/composer/insert.js +19 -0
  185. package/dist/chat/ui/composer/keys.d.ts +30 -0
  186. package/dist/chat/ui/composer/keys.js +100 -0
  187. package/dist/chat/ui/diff-theme.d.ts +2 -0
  188. package/dist/chat/ui/diff-theme.js +20 -0
  189. package/dist/chat/ui/file-links.d.ts +5 -0
  190. package/dist/chat/ui/file-links.js +20 -0
  191. package/dist/chat/ui/find-reveal.d.ts +7 -0
  192. package/dist/chat/ui/find-reveal.js +13 -0
  193. package/dist/chat/ui/full-diff.d.ts +6 -0
  194. package/dist/chat/ui/full-diff.js +19 -0
  195. package/dist/chat/ui/icons.d.ts +4 -0
  196. package/dist/chat/ui/icons.js +20 -0
  197. package/dist/chat/ui/markdown-blocks.d.ts +6 -0
  198. package/dist/chat/ui/markdown-blocks.js +103 -0
  199. package/dist/chat/ui/quote-selection.d.ts +3 -0
  200. package/dist/chat/ui/quote-selection.js +46 -0
  201. package/dist/chat/ui/rehype-chips.d.ts +23 -0
  202. package/dist/chat/ui/rehype-chips.js +32 -0
  203. package/dist/chat/ui/rehype-fade.d.ts +23 -0
  204. package/dist/chat/ui/rehype-fade.js +36 -0
  205. package/dist/chat/ui/remark-html-as-text.d.ts +8 -0
  206. package/dist/chat/ui/remark-html-as-text.js +26 -0
  207. package/dist/chat/ui/reply-context.d.ts +5 -0
  208. package/dist/chat/ui/reply-context.js +2 -0
  209. package/dist/chat/ui/reveal.d.ts +13 -0
  210. package/dist/chat/ui/reveal.js +114 -0
  211. package/dist/chat/ui/rows/ForksRow.d.ts +4 -0
  212. package/dist/chat/ui/rows/ForksRow.js +38 -0
  213. package/dist/chat/ui/rows/MessageRows.d.ts +40 -0
  214. package/dist/chat/ui/rows/MessageRows.js +204 -0
  215. package/dist/chat/ui/rows/Rows.d.ts +12 -0
  216. package/dist/chat/ui/rows/Rows.js +61 -0
  217. package/dist/chat/ui/rows/SubagentRow.d.ts +17 -0
  218. package/dist/chat/ui/rows/SubagentRow.js +101 -0
  219. package/dist/chat/ui/rows/TaskRow.d.ts +8 -0
  220. package/dist/chat/ui/rows/TaskRow.js +63 -0
  221. package/dist/chat/ui/rows/WorkRows.d.ts +47 -0
  222. package/dist/chat/ui/rows/WorkRows.js +168 -0
  223. package/dist/chat/ui/rows/WorkflowRow.d.ts +8 -0
  224. package/dist/chat/ui/rows/WorkflowRow.js +48 -0
  225. package/dist/chat/ui/rows/row-rhythm.d.ts +6 -0
  226. package/dist/chat/ui/rows/row-rhythm.js +34 -0
  227. package/dist/chat/ui/useToggleSet.d.ts +6 -0
  228. package/dist/chat/ui/useToggleSet.js +25 -0
  229. package/dist/host.d.ts +195 -0
  230. package/dist/host.js +55 -0
  231. package/dist/lazy.d.ts +6 -0
  232. package/dist/lazy.js +57 -0
  233. package/dist/locales/en/agent-chat.json +446 -0
  234. package/dist/locales/en/agent-prompts.json +42 -0
  235. package/dist/locales/en/agent-providers.json +131 -0
  236. package/dist/locales/en/agent-usage.json +131 -0
  237. package/dist/locales/nl/agent-chat.json +446 -0
  238. package/dist/locales/nl/agent-prompts.json +42 -0
  239. package/dist/locales/nl/agent-providers.json +131 -0
  240. package/dist/locales/nl/agent-usage.json +131 -0
  241. package/dist/locales.d.ts +5 -0
  242. package/dist/locales.js +20 -0
  243. package/dist/mounted-registry.d.ts +7 -0
  244. package/dist/mounted-registry.js +39 -0
  245. package/dist/port-transport.d.ts +6 -0
  246. package/dist/port-transport.js +129 -0
  247. package/dist/prompts/logic/focus.d.ts +2 -0
  248. package/dist/prompts/logic/focus.js +23 -0
  249. package/dist/prompts/logic/keys.d.ts +42 -0
  250. package/dist/prompts/logic/keys.js +94 -0
  251. package/dist/prompts/logic/prompts.d.ts +34 -0
  252. package/dist/prompts/logic/prompts.fixtures.d.ts +5 -0
  253. package/dist/prompts/logic/prompts.fixtures.js +79 -0
  254. package/dist/prompts/logic/prompts.js +32 -0
  255. package/dist/prompts/logic/subjects.d.ts +50 -0
  256. package/dist/prompts/logic/subjects.js +63 -0
  257. package/dist/prompts/logic/useFocusAfterAnswer.d.ts +6 -0
  258. package/dist/prompts/logic/useFocusAfterAnswer.js +28 -0
  259. package/dist/prompts/logic/usePromptSession.d.ts +24 -0
  260. package/dist/prompts/logic/usePromptSession.js +87 -0
  261. package/dist/prompts/ui/ApprovalBody.d.ts +13 -0
  262. package/dist/prompts/ui/ApprovalBody.js +34 -0
  263. package/dist/prompts/ui/PromptActions.d.ts +27 -0
  264. package/dist/prompts/ui/PromptActions.js +18 -0
  265. package/dist/prompts/ui/PromptCard.d.ts +16 -0
  266. package/dist/prompts/ui/PromptCard.js +50 -0
  267. package/dist/prompts/ui/PromptView.d.ts +2 -0
  268. package/dist/prompts/ui/PromptView.js +47 -0
  269. package/dist/prompts/ui/QuestionBody.d.ts +9 -0
  270. package/dist/prompts/ui/QuestionBody.js +87 -0
  271. package/dist/providers/AccountColors.d.ts +5 -0
  272. package/dist/providers/AccountColors.js +8 -0
  273. package/dist/providers/AccountDetail.d.ts +12 -0
  274. package/dist/providers/AccountDetail.js +69 -0
  275. package/dist/providers/AccountVariables.d.ts +8 -0
  276. package/dist/providers/AccountVariables.js +43 -0
  277. package/dist/providers/AddAccountForm.d.ts +10 -0
  278. package/dist/providers/AddAccountForm.js +54 -0
  279. package/dist/providers/CliDetail.d.ts +12 -0
  280. package/dist/providers/CliDetail.js +54 -0
  281. package/dist/providers/ProvidersPane.d.ts +9 -0
  282. package/dist/providers/ProvidersPane.js +86 -0
  283. package/dist/providers/account-actions.d.ts +7 -0
  284. package/dist/providers/account-actions.js +57 -0
  285. package/dist/providers/parts.d.ts +9 -0
  286. package/dist/providers/parts.js +18 -0
  287. package/dist/providers/provider-abilities.d.ts +2 -0
  288. package/dist/providers/provider-abilities.js +7 -0
  289. package/dist/providers/variables.d.ts +13 -0
  290. package/dist/providers/variables.js +63 -0
  291. package/dist/scope.d.ts +11 -0
  292. package/dist/scope.js +10 -0
  293. package/dist/settings/sections.d.ts +13 -0
  294. package/dist/settings/sections.js +32 -0
  295. package/dist/state/chats.d.ts +44 -0
  296. package/dist/state/chats.js +199 -0
  297. package/dist/state/provider-accounts.d.ts +17 -0
  298. package/dist/state/provider-accounts.js +67 -0
  299. package/dist/state/providers.d.ts +17 -0
  300. package/dist/state/providers.js +33 -0
  301. package/dist/state/usage.d.ts +44 -0
  302. package/dist/state/usage.js +121 -0
  303. package/dist/storage.d.ts +20 -0
  304. package/dist/storage.js +89 -0
  305. package/dist/theme.css +212 -0
  306. package/dist/transport.d.ts +24 -0
  307. package/dist/transport.js +18 -0
  308. package/dist/ui/PlainTextarea.d.ts +2 -0
  309. package/dist/ui/PlainTextarea.js +5 -0
  310. package/dist/usage/LimitsList.d.ts +18 -0
  311. package/dist/usage/LimitsList.js +73 -0
  312. package/dist/usage/UsageBreakdown.d.ts +9 -0
  313. package/dist/usage/UsageBreakdown.js +67 -0
  314. package/dist/usage/UsageChart.d.ts +10 -0
  315. package/dist/usage/UsageChart.js +47 -0
  316. package/dist/usage/UsageDialog.d.ts +6 -0
  317. package/dist/usage/UsageDialog.js +12 -0
  318. package/dist/usage/UsageLimits.d.ts +1 -0
  319. package/dist/usage/UsageLimits.js +57 -0
  320. package/dist/usage/UsageLimitsCard.d.ts +4 -0
  321. package/dist/usage/UsageLimitsCard.js +26 -0
  322. package/dist/usage/UsagePage.d.ts +7 -0
  323. package/dist/usage/UsagePage.js +154 -0
  324. package/dist/usage/UsagePane.d.ts +3 -0
  325. package/dist/usage/UsagePane.js +21 -0
  326. package/dist/usage/UsageSummary.d.ts +12 -0
  327. package/dist/usage/UsageSummary.js +15 -0
  328. package/dist/usage/UsageTiles.d.ts +7 -0
  329. package/dist/usage/UsageTiles.js +19 -0
  330. package/dist/usage/format.d.ts +13 -0
  331. package/dist/usage/format.js +55 -0
  332. package/dist/usage/limit-groups.d.ts +22 -0
  333. package/dist/usage/limit-groups.js +86 -0
  334. package/dist/usage/limits.d.ts +4 -0
  335. package/dist/usage/limits.js +58 -0
  336. package/dist/usage/money.d.ts +1 -0
  337. package/dist/usage/money.js +13 -0
  338. package/dist/usage/summary.d.ts +34 -0
  339. package/dist/usage/summary.js +94 -0
  340. package/package.json +895 -3
  341. package/src/agents/AccountDot.tsx +14 -0
  342. package/src/agents/AgentIcon.tsx +26 -0
  343. package/src/agents/ModelOptionControl.tsx +68 -0
  344. package/src/agents/ProviderLogo.tsx +20 -0
  345. package/src/agents/account-limits.ts +61 -0
  346. package/src/agents/accounts.ts +137 -0
  347. package/src/agents/model-name.ts +70 -0
  348. package/src/agents/model-options.ts +21 -0
  349. package/src/agents/provider-paths.ts +10 -0
  350. package/src/agents/status-look.ts +43 -0
  351. package/src/chat/account-choice.ts +44 -0
  352. package/src/chat/actions.ts +72 -0
  353. package/src/chat/activity.ts +8 -0
  354. package/src/chat/attachments.ts +118 -0
  355. package/src/chat/bookmarks.ts +87 -0
  356. package/src/chat/chat-client.ts +345 -0
  357. package/src/chat/chat-references.ts +11 -0
  358. package/src/chat/drafts.ts +165 -0
  359. package/src/chat/forks.ts +33 -0
  360. package/src/chat/guards.ts +84 -0
  361. package/src/chat/logic/ansi.ts +206 -0
  362. package/src/chat/logic/answers.ts +8 -0
  363. package/src/chat/logic/bookmarks.ts +42 -0
  364. package/src/chat/logic/context-usage.ts +36 -0
  365. package/src/chat/logic/fork.ts +200 -0
  366. package/src/chat/logic/handback.ts +10 -0
  367. package/src/chat/logic/json.ts +4 -0
  368. package/src/chat/logic/limit.ts +46 -0
  369. package/src/chat/logic/resume-compaction.ts +71 -0
  370. package/src/chat/logic/scrubber.ts +289 -0
  371. package/src/chat/logic/thread-cards.ts +64 -0
  372. package/src/chat/logic/timeline-copy.ts +65 -0
  373. package/src/chat/logic/timeline-target.ts +52 -0
  374. package/src/chat/logic/timeline.ts +419 -0
  375. package/src/chat/logic/tool-catalog.ts +62 -0
  376. package/src/chat/logic/tools.ts +102 -0
  377. package/src/chat/logic/welcome.ts +13 -0
  378. package/src/chat/logic/workflow.ts +66 -0
  379. package/src/chat/mentions.ts +232 -0
  380. package/src/chat/persisted-json.ts +67 -0
  381. package/src/chat/preferences.ts +179 -0
  382. package/src/chat/quote.ts +26 -0
  383. package/src/chat/recent-messages.ts +59 -0
  384. package/src/chat/resume-compaction-dismissals.ts +17 -0
  385. package/src/chat/runtime-modes.ts +18 -0
  386. package/src/chat/stash.ts +122 -0
  387. package/src/chat/subagent-conversation.ts +203 -0
  388. package/src/chat/subagent-list.ts +141 -0
  389. package/src/chat/subagent-support.ts +21 -0
  390. package/src/chat/subagent-view.ts +137 -0
  391. package/src/chat/thumbnails.ts +108 -0
  392. package/src/chat/timeline-flash.ts +33 -0
  393. package/src/chat/timeline-scroll.ts +199 -0
  394. package/src/chat/ui/AccountPill.tsx +31 -0
  395. package/src/chat/ui/AnsiOutput.tsx +18 -0
  396. package/src/chat/ui/BookmarkMarker.tsx +108 -0
  397. package/src/chat/ui/BookmarkSubmenu.tsx +53 -0
  398. package/src/chat/ui/ChatActivity.tsx +198 -0
  399. package/src/chat/ui/ChatReferenceChip.tsx +28 -0
  400. package/src/chat/ui/CodeBlock.tsx +175 -0
  401. package/src/chat/ui/Composer.tsx +1326 -0
  402. package/src/chat/ui/ComposerInput.tsx +187 -0
  403. package/src/chat/ui/DiffPool.tsx +36 -0
  404. package/src/chat/ui/EditDiff.tsx +30 -0
  405. package/src/chat/ui/FadingWords.tsx +18 -0
  406. package/src/chat/ui/ImageView.tsx +159 -0
  407. package/src/chat/ui/LimitState.tsx +165 -0
  408. package/src/chat/ui/Markdown.tsx +258 -0
  409. package/src/chat/ui/MessageActions.tsx +88 -0
  410. package/src/chat/ui/Pickers.tsx +272 -0
  411. package/src/chat/ui/PromptComposer.tsx +119 -0
  412. package/src/chat/ui/QuoteButton.tsx +103 -0
  413. package/src/chat/ui/ResumeCompactionDock.tsx +28 -0
  414. package/src/chat/ui/RunSettings.tsx +546 -0
  415. package/src/chat/ui/Scrubber.tsx +262 -0
  416. package/src/chat/ui/SubagentControls.tsx +66 -0
  417. package/src/chat/ui/SubagentInfo.tsx +67 -0
  418. package/src/chat/ui/SubagentStopButton.tsx +45 -0
  419. package/src/chat/ui/SubagentTimeline.tsx +170 -0
  420. package/src/chat/ui/Timeline.tsx +628 -0
  421. package/src/chat/ui/TimelineMenu.tsx +124 -0
  422. package/src/chat/ui/UnifiedDiff.tsx +99 -0
  423. package/src/chat/ui/UploadThumb.tsx +13 -0
  424. package/src/chat/ui/Welcome.tsx +22 -0
  425. package/src/chat/ui/chips.ts +15 -0
  426. package/src/chat/ui/code-lines.ts +60 -0
  427. package/src/chat/ui/code-streaming.ts +4 -0
  428. package/src/chat/ui/code-theme.ts +15 -0
  429. package/src/chat/ui/composer/chips.ts +43 -0
  430. package/src/chat/ui/composer/editor.ts +118 -0
  431. package/src/chat/ui/composer/insert.ts +22 -0
  432. package/src/chat/ui/composer/keys.ts +144 -0
  433. package/src/chat/ui/diff-theme.ts +23 -0
  434. package/src/chat/ui/file-links.ts +24 -0
  435. package/src/chat/ui/find-reveal.ts +23 -0
  436. package/src/chat/ui/full-diff.ts +25 -0
  437. package/src/chat/ui/icons.tsx +24 -0
  438. package/src/chat/ui/markdown-blocks.ts +123 -0
  439. package/src/chat/ui/quote-selection.ts +53 -0
  440. package/src/chat/ui/rehype-chips.ts +62 -0
  441. package/src/chat/ui/rehype-fade.ts +64 -0
  442. package/src/chat/ui/remark-html-as-text.ts +36 -0
  443. package/src/chat/ui/reply-context.ts +4 -0
  444. package/src/chat/ui/reveal.ts +135 -0
  445. package/src/chat/ui/rows/ForksRow.tsx +79 -0
  446. package/src/chat/ui/rows/MessageRows.tsx +415 -0
  447. package/src/chat/ui/rows/Rows.tsx +81 -0
  448. package/src/chat/ui/rows/SubagentRow.tsx +209 -0
  449. package/src/chat/ui/rows/TaskRow.tsx +91 -0
  450. package/src/chat/ui/rows/WorkRows.tsx +445 -0
  451. package/src/chat/ui/rows/WorkflowRow.tsx +120 -0
  452. package/src/chat/ui/rows/row-rhythm.ts +37 -0
  453. package/src/chat/ui/useToggleSet.ts +39 -0
  454. package/src/host.ts +324 -0
  455. package/src/lazy.tsx +64 -0
  456. package/src/locales/en/agent-chat.json +446 -0
  457. package/src/locales/en/agent-prompts.json +42 -0
  458. package/src/locales/en/agent-providers.json +131 -0
  459. package/src/locales/en/agent-usage.json +131 -0
  460. package/src/locales/nl/agent-chat.json +446 -0
  461. package/src/locales/nl/agent-prompts.json +42 -0
  462. package/src/locales/nl/agent-providers.json +131 -0
  463. package/src/locales/nl/agent-usage.json +131 -0
  464. package/src/locales.ts +33 -0
  465. package/src/mounted-registry.ts +45 -0
  466. package/src/port-transport.ts +146 -0
  467. package/src/prompts/logic/focus.ts +25 -0
  468. package/src/prompts/logic/keys.ts +116 -0
  469. package/src/prompts/logic/prompts.fixtures.ts +84 -0
  470. package/src/prompts/logic/prompts.ts +58 -0
  471. package/src/prompts/logic/subjects.ts +122 -0
  472. package/src/prompts/logic/useFocusAfterAnswer.ts +38 -0
  473. package/src/prompts/logic/usePromptSession.ts +114 -0
  474. package/src/prompts/ui/ApprovalBody.tsx +109 -0
  475. package/src/prompts/ui/PromptActions.tsx +109 -0
  476. package/src/prompts/ui/PromptCard.tsx +101 -0
  477. package/src/prompts/ui/PromptView.tsx +111 -0
  478. package/src/prompts/ui/QuestionBody.tsx +174 -0
  479. package/src/providers/AccountColors.tsx +8 -0
  480. package/src/providers/AccountDetail.tsx +209 -0
  481. package/src/providers/AccountVariables.tsx +126 -0
  482. package/src/providers/AddAccountForm.tsx +121 -0
  483. package/src/providers/CliDetail.tsx +157 -0
  484. package/src/providers/ProvidersPane.tsx +236 -0
  485. package/src/providers/account-actions.ts +67 -0
  486. package/src/providers/parts.tsx +25 -0
  487. package/src/providers/provider-abilities.ts +9 -0
  488. package/src/providers/variables.ts +87 -0
  489. package/src/scope.ts +30 -0
  490. package/src/settings/sections.ts +47 -0
  491. package/src/state/chats.ts +279 -0
  492. package/src/state/provider-accounts.ts +87 -0
  493. package/src/state/providers.ts +50 -0
  494. package/src/state/usage.ts +196 -0
  495. package/src/storage.ts +113 -0
  496. package/src/theme.css +212 -0
  497. package/src/transport.ts +50 -0
  498. package/src/ui/PlainTextarea.tsx +6 -0
  499. package/src/usage/LimitsList.tsx +232 -0
  500. package/src/usage/UsageBreakdown.tsx +210 -0
  501. package/src/usage/UsageChart.tsx +113 -0
  502. package/src/usage/UsageDialog.tsx +20 -0
  503. package/src/usage/UsageLimits.tsx +145 -0
  504. package/src/usage/UsageLimitsCard.tsx +36 -0
  505. package/src/usage/UsagePage.tsx +276 -0
  506. package/src/usage/UsagePane.tsx +52 -0
  507. package/src/usage/UsageSummary.tsx +50 -0
  508. package/src/usage/UsageTiles.tsx +34 -0
  509. package/src/usage/format.ts +71 -0
  510. package/src/usage/limit-groups.ts +119 -0
  511. package/src/usage/limits.ts +71 -0
  512. package/src/usage/money.ts +14 -0
  513. package/src/usage/summary.ts +137 -0
@@ -0,0 +1,137 @@
1
+ import i18next from 'i18next';
2
+ import { useMemo } from 'react';
3
+ import { create } from 'zustand';
4
+ import type { ChatItem, ChatSubagentItem } from '@adecore/agent-contracts';
5
+ import { useChatScope } from '../scope';
6
+ import { useChatRow } from '../state/chats';
7
+ import { useSubagentSupport } from './subagent-support';
8
+
9
+ /* One conversation on the way down from the chat: the call that opened it and what that call called it. */
10
+ export interface SubagentStep {
11
+ toolUseId: string;
12
+ description: string;
13
+ }
14
+
15
+ /* From the chat down to what is on screen, which is the last step; empty is the main agent. */
16
+ export type SubagentTrail = readonly SubagentStep[];
17
+
18
+ export interface BreadcrumbStep {
19
+ label: string;
20
+ /* How many steps of the trail stay when this crumb is picked; the chat's own title is 0. */
21
+ depth: number;
22
+ current: boolean;
23
+ }
24
+
25
+ export const MAIN_AGENT: SubagentTrail = [];
26
+
27
+ const NO_ITEMS: readonly string[] = [];
28
+
29
+ export function crumbOf(item: ChatSubagentItem): SubagentStep {
30
+ return { toolUseId: item.toolUseId, description: item.description };
31
+ }
32
+
33
+ function stepLabel(step: SubagentStep): string {
34
+ return step.description || i18next.t('agent-chat:rows.subagent.label');
35
+ }
36
+
37
+ /* A row in the main agent's thread or an entry of the flyout opens its conversation straight away. */
38
+ export function openFromMain(step: SubagentStep): SubagentTrail {
39
+ return [step];
40
+ }
41
+
42
+ export function openBelow(trail: SubagentTrail, step: SubagentStep): SubagentTrail {
43
+ return [...trail, step];
44
+ }
45
+
46
+ export function trailTo(trail: SubagentTrail, depth: number): SubagentTrail {
47
+ return depth <= 0 ? MAIN_AGENT : trail.slice(0, depth);
48
+ }
49
+
50
+ export function stepBack(trail: SubagentTrail): SubagentTrail {
51
+ return trailTo(trail, trail.length - 1);
52
+ }
53
+
54
+ /*
55
+ * A sub-agent's conversation is read back, so there is nobody to write to. The composer stays, since
56
+ * the chat's own approvals and questions are answered there.
57
+ */
58
+ export function composerWrites(trail: SubagentTrail): boolean {
59
+ return trail.length === 0;
60
+ }
61
+
62
+ /* The crumbs after the chat's own title, which the bar draws as the first crumb and the way back. */
63
+ export function breadcrumbOf(trail: SubagentTrail): BreadcrumbStep[] {
64
+ return trail.map((step, index) => ({ label: stepLabel(step), depth: index + 1, current: index === trail.length - 1 }));
65
+ }
66
+
67
+ /* A row without a pointer of its own can only be opened by a machine that answers `chat.subagent` for it. */
68
+ export function canOpenSubagent(item: ChatSubagentItem, machineRefused: boolean): boolean {
69
+ return item.native !== undefined || !machineRefused;
70
+ }
71
+
72
+ /* The row of the call that opened a conversation, a nested one included, since its row is in the thread too. */
73
+ export function subagentOf(structure: Readonly<Record<string, ChatItem>>, toolUseId: string): ChatSubagentItem | null {
74
+ return Object.values(structure).find((item): item is ChatSubagentItem => item.kind === 'subagent' && item.toolUseId === toolUseId) ?? null;
75
+ }
76
+
77
+ /* The subagents a chat can open, in the order the thread has them. */
78
+ export function openableSubagents(order: readonly string[], structure: Record<string, ChatItem>, machineRefused: boolean): ChatSubagentItem[] {
79
+ return order.flatMap((id) => {
80
+ const item = structure[id];
81
+ return item?.kind === 'subagent' && canOpenSubagent(item, machineRefused) ? [item] : [];
82
+ });
83
+ }
84
+
85
+ interface SubagentViewStore {
86
+ /* Keyed with the scope's `keyOf`; a chat on its main agent has no entry. */
87
+ trails: Record<string, SubagentTrail>;
88
+ show(key: string, trail: SubagentTrail): void;
89
+ /* One level up; false when the main agent was already on screen, so the key can mean something else. */
90
+ back(key: string): boolean;
91
+ }
92
+
93
+ /*
94
+ * What a chat shows in its place, for this page's life only: a look into a subagent is a moment
95
+ * rather than a place, so it is never written to the project or to the host.
96
+ */
97
+ export const useSubagentView = create<SubagentViewStore>((set, get) => ({
98
+ trails: {},
99
+ show(key, trail) {
100
+ const trails = { ...get().trails };
101
+ if (trail.length === 0) {
102
+ delete trails[key];
103
+ } else {
104
+ trails[key] = trail;
105
+ }
106
+ set({ trails });
107
+ },
108
+ back(key) {
109
+ const trail = get().trails[key];
110
+ if (trail === undefined || trail.length === 0) {
111
+ return false;
112
+ }
113
+ get().show(key, stepBack(trail));
114
+ return true;
115
+ }
116
+ }));
117
+
118
+ /* The trail of a chat in the scope this is rendered in, and a way to set it. */
119
+ export function useSubagentTrail(chatId: string): { trail: SubagentTrail; show(trail: SubagentTrail): void } {
120
+ const key = useChatScope().keyOf(chatId);
121
+ const trail = useSubagentView((s) => s.trails[key] ?? MAIN_AGENT);
122
+ return { trail, show: (next) => useSubagentView.getState().show(key, next) };
123
+ }
124
+
125
+ export function useOpenableSubagents(chatId: string): ChatSubagentItem[] {
126
+ const { id } = useChatScope();
127
+ const refused = useSubagentSupport((s) => s.unsupported[id] === true);
128
+ // The structure and not the items, so a delta growing a reply does not walk the thread again.
129
+ const order = useChatRow(chatId, (row) => row?.order);
130
+ const structure = useChatRow(chatId, (row) => row?.structure);
131
+ return useMemo(() => (structure === undefined ? [] : openableSubagents(order ?? NO_ITEMS, structure, refused)), [order, structure, refused]);
132
+ }
133
+
134
+ export function useSubagentItem(chatId: string, toolUseId: string): ChatSubagentItem | null {
135
+ const structure = useChatRow(chatId, (row) => row?.structure);
136
+ return useMemo(() => (structure === undefined ? null : subagentOf(structure, toolUseId)), [structure, toolUseId]);
137
+ }
@@ -0,0 +1,108 @@
1
+ import i18next from 'i18next';
2
+ import { useEffect, useState } from 'react';
3
+ import type { ChatAttachmentUpload } from '@adecore/agent-contracts';
4
+ import { uploadPreviewUrl } from './attachments';
5
+
6
+ /* The edge of a composer thumbnail: a few times the 56 px it is drawn at, so a sharp screen still gets a sharp one. */
7
+ export const THUMBNAIL_PX = 256;
8
+
9
+ export interface ThumbnailCrop {
10
+ sx: number;
11
+ sy: number;
12
+ /* The edge of the square taken from the middle of the image. */
13
+ size: number;
14
+ /* The edge the square is drawn at, never larger than it was. */
15
+ out: number;
16
+ }
17
+
18
+ /* The square in the middle of an image that a thumbnail shows, the same part `object-cover` would. */
19
+ export function thumbnailCrop(width: number, height: number, max: number = THUMBNAIL_PX): ThumbnailCrop {
20
+ const size = Math.max(1, Math.min(width, height));
21
+ return {
22
+ sx: Math.max(0, Math.floor((width - size) / 2)),
23
+ sy: Math.max(0, Math.floor((height - size) / 2)),
24
+ size,
25
+ out: Math.min(max, size)
26
+ };
27
+ }
28
+
29
+ function bytesOf(base64: string): Uint8Array<ArrayBuffer> {
30
+ const binary = atob(base64);
31
+ const bytes = new Uint8Array(new ArrayBuffer(binary.length));
32
+ for (let i = 0; i < binary.length; i++) {
33
+ bytes[i] = binary.charCodeAt(i);
34
+ }
35
+ return bytes;
36
+ }
37
+
38
+ function dataUrlOf(blob: Blob): Promise<string> {
39
+ return new Promise((resolve, reject) => {
40
+ const reader = new FileReader();
41
+ reader.onerror = () => reject(reader.error ?? new Error(i18next.t('agent-chat:attachments.unreadableThumbnail')));
42
+ reader.onload = () => resolve(String(reader.result));
43
+ reader.readAsDataURL(blob);
44
+ });
45
+ }
46
+
47
+ /*
48
+ * A still of the middle of an image, decoded once. The composer used to draw the full data URL in a
49
+ * 56 px box, which a browser decodes again for every paint, so a tall screenshot made typing stutter.
50
+ * An animated image gives its first frame. Whatever cannot be decoded this way (an SVG, a browser
51
+ * without `OffscreenCanvas`) falls back to the image itself, which is what it was before.
52
+ */
53
+ async function makeThumbnail(upload: ChatAttachmentUpload): Promise<string> {
54
+ try {
55
+ const bitmap = await createImageBitmap(new Blob([bytesOf(upload.data)], { type: upload.mime }));
56
+ const crop = thumbnailCrop(bitmap.width, bitmap.height);
57
+ const canvas = new OffscreenCanvas(crop.out, crop.out);
58
+ const context = canvas.getContext('2d');
59
+ if (!context) {
60
+ bitmap.close();
61
+ return uploadPreviewUrl(upload);
62
+ }
63
+ context.drawImage(bitmap, crop.sx, crop.sy, crop.size, crop.size, 0, 0, crop.out, crop.out);
64
+ bitmap.close();
65
+ return await dataUrlOf(await canvas.convertToBlob({ type: 'image/png' }));
66
+ } catch {
67
+ return uploadPreviewUrl(upload);
68
+ }
69
+ }
70
+
71
+ // Keyed by the upload itself, so a composer that mounts again finds it and a removed file lets it go.
72
+ const made = new WeakMap<ChatAttachmentUpload, string>();
73
+ const making = new WeakMap<ChatAttachmentUpload, Promise<string>>();
74
+
75
+ function thumbnailOf(upload: ChatAttachmentUpload): Promise<string> {
76
+ let promise = making.get(upload);
77
+ if (!promise) {
78
+ promise = makeThumbnail(upload).then((url) => {
79
+ made.set(upload, url);
80
+ return url;
81
+ });
82
+ making.set(upload, promise);
83
+ }
84
+ return promise;
85
+ }
86
+
87
+ /* The thumbnail of an image the composer holds, or null while it is being made. */
88
+ export function useUploadThumbnail(upload: ChatAttachmentUpload): string | null {
89
+ const [ready, setReady] = useState<{ upload: ChatAttachmentUpload; url: string } | null>(null);
90
+ const cached = made.get(upload);
91
+
92
+ useEffect(() => {
93
+ if (made.has(upload)) {
94
+ return;
95
+ }
96
+ let cancelled = false;
97
+ void thumbnailOf(upload).then((url) => {
98
+ if (!cancelled) {
99
+ setReady({ upload, url });
100
+ }
101
+ });
102
+ return () => {
103
+ cancelled = true;
104
+ };
105
+ }, [upload]);
106
+
107
+ return cached ?? (ready?.upload === upload ? ready.url : null);
108
+ }
@@ -0,0 +1,33 @@
1
+ import { create } from 'zustand';
2
+
3
+ /* The row of a thread a jump landed on: it lights up once, and a card of changes opens when the jump asked for it. */
4
+ export interface TimelineFlash {
5
+ /* The scope's key of the chat, so two chats showing the same row id never light up together. */
6
+ key: string;
7
+ rowId: string;
8
+ opens: boolean;
9
+ /* A second jump to the same row counts. */
10
+ nonce: number;
11
+ }
12
+
13
+ interface TimelineFlashStore {
14
+ target: TimelineFlash | null;
15
+ flash(key: string, rowId: string, opens: boolean): void;
16
+ }
17
+
18
+ /* A little longer than the animation of `.chat-flash`, so a row that scrolls into view again later is neither lit nor opened a second time. */
19
+ const FLASH_MS = 2000;
20
+
21
+ export const useTimelineFlash = create<TimelineFlashStore>((set, get) => ({
22
+ target: null,
23
+ flash(key, rowId, opens) {
24
+ const nonce = (get().target?.nonce ?? 0) + 1;
25
+ set({ target: { key, rowId, opens, nonce } });
26
+ const timer = setTimeout(() => {
27
+ if (get().target?.nonce === nonce) {
28
+ set({ target: null });
29
+ }
30
+ }, FLASH_MS);
31
+ (timer as { unref?: () => void }).unref?.();
32
+ }
33
+ }));
@@ -0,0 +1,199 @@
1
+ // A page is a screen minus a little, so the line you were reading is still on it after the jump.
2
+ const PAGE_OVERLAP = 0.9;
3
+
4
+ type Scroller = {
5
+ element: HTMLElement;
6
+ follow: (enabled: boolean) => void;
7
+ // The height of the composer standing over the end of the scroller, which a page does not count.
8
+ coveredHeight: () => number;
9
+ };
10
+
11
+ const scrollers = new Map<string, Scroller>();
12
+
13
+ /* The timeline hands its scroller over so the composer, which never sees it, can page through it. */
14
+ export function registerTimeline(
15
+ chatId: string,
16
+ element: HTMLElement | null,
17
+ follow: Scroller['follow'],
18
+ coveredHeight: Scroller['coveredHeight']
19
+ ): () => void {
20
+ if (element === null) {
21
+ return () => undefined;
22
+ }
23
+ scrollers.set(chatId, { element, follow, coveredHeight });
24
+ return () => {
25
+ if (scrollers.get(chatId)?.element === element) {
26
+ scrollers.delete(chatId);
27
+ ends.delete(chatId);
28
+ }
29
+ };
30
+ }
31
+
32
+ /* The timeline owns following; the composer subscribes here to show its jump-to-end button. */
33
+ const ends = new Map<string, boolean>();
34
+ const listeners = new Set<() => void>();
35
+
36
+ export function setTimelineAtEnd(chatId: string, atEnd: boolean): void {
37
+ if (ends.get(chatId) === atEnd) {
38
+ return;
39
+ }
40
+ ends.set(chatId, atEnd);
41
+ for (const listener of listeners) {
42
+ listener();
43
+ }
44
+ }
45
+
46
+ export function subscribeTimelineEnd(listener: () => void): () => void {
47
+ listeners.add(listener);
48
+ return () => {
49
+ listeners.delete(listener);
50
+ };
51
+ }
52
+
53
+ /* A thread nobody has scrolled is at its end, so the button stays away until there is a way back. */
54
+ export function timelineAtEnd(chatId: string): boolean {
55
+ return ends.get(chatId) ?? true;
56
+ }
57
+
58
+ export function scrollTimelineToEnd(chatId: string, behavior: ScrollBehavior = 'smooth'): void {
59
+ const scroller = scrollers.get(chatId);
60
+ if (scroller) {
61
+ scroller.follow(true);
62
+ scroller.element.scrollTo({ top: scroller.element.scrollHeight, behavior });
63
+ }
64
+ }
65
+
66
+ /* Pages the thread of this chat up or down; false when it has no timeline on screen. */
67
+ export function pageTimeline(chatId: string, direction: -1 | 1): boolean {
68
+ const scroller = scrollers.get(chatId);
69
+ if (!scroller) {
70
+ return false;
71
+ }
72
+ scroller.follow(false);
73
+ const { element } = scroller;
74
+ element.scrollBy({ top: direction * Math.max(0, element.clientHeight - scroller.coveredHeight()) * PAGE_OVERLAP, behavior: 'smooth' });
75
+ return true;
76
+ }
77
+
78
+ /*
79
+ * The keyboard steps from one message of the person to the next through the timeline on screen,
80
+ * which is the only side that knows where its rows start. Keyed with the scope's `keyOf`, the way
81
+ * the app's shortcuts name the chat that has the focus.
82
+ */
83
+ const steppers = new Map<string, (direction: -1 | 1) => boolean>();
84
+
85
+ export function registerMessageStepper(key: string, step: (direction: -1 | 1) => boolean): () => void {
86
+ steppers.set(key, step);
87
+ return () => {
88
+ if (steppers.get(key) === step) {
89
+ steppers.delete(key);
90
+ }
91
+ };
92
+ }
93
+
94
+ /* False when that chat has no timeline on screen or no message in that direction. */
95
+ export function stepTimelineMessage(key: string, direction: -1 | 1): boolean {
96
+ return steppers.get(key)?.(direction) ?? false;
97
+ }
98
+
99
+ /*
100
+ * A jump to one message of a chat, asked from outside its thread (the chat's menu). A thread that is
101
+ * not on screen yet gets it when it registers, since the menu may have to show the chat first.
102
+ * Keyed with the scope's `keyOf`, like the steppers.
103
+ */
104
+ const jumpers = new Map<string, (itemId: string) => void>();
105
+ const waitingJumps = new Map<string, string>();
106
+
107
+ export function registerItemJumper(key: string, jump: (itemId: string) => void): () => void {
108
+ jumpers.set(key, jump);
109
+ const waiting = waitingJumps.get(key);
110
+ if (waiting !== undefined) {
111
+ waitingJumps.delete(key);
112
+ jump(waiting);
113
+ }
114
+ return () => {
115
+ if (jumpers.get(key) === jump) {
116
+ jumpers.delete(key);
117
+ }
118
+ };
119
+ }
120
+
121
+ /* False when that chat has no thread on screen; the jump then waits for the next one that registers. */
122
+ export function jumpToTimelineItem(key: string, itemId: string): boolean {
123
+ const jump = jumpers.get(key);
124
+ if (jump === undefined) {
125
+ waitingJumps.set(key, itemId);
126
+ return false;
127
+ }
128
+ jump(itemId);
129
+ return true;
130
+ }
131
+
132
+ /*
133
+ * A jump to the turn of a chat, for a card that says which turn wrote something: the message that opened
134
+ * it, or the files it changed. Kept like the jump to an item, so a chat that is not on screen yet gets it
135
+ * when its thread registers.
136
+ */
137
+ export type TurnTarget = 'prompt' | 'changes';
138
+
139
+ type TurnJump = (turnId: string, target: TurnTarget) => void;
140
+ const turnJumpers = new Map<string, TurnJump>();
141
+ const waitingTurnJumps = new Map<string, { turnId: string; target: TurnTarget }>();
142
+
143
+ export function registerTurnJumper(key: string, jump: TurnJump): () => void {
144
+ turnJumpers.set(key, jump);
145
+ const waiting = waitingTurnJumps.get(key);
146
+ if (waiting !== undefined) {
147
+ waitingTurnJumps.delete(key);
148
+ jump(waiting.turnId, waiting.target);
149
+ }
150
+ return () => {
151
+ if (turnJumpers.get(key) === jump) {
152
+ turnJumpers.delete(key);
153
+ }
154
+ };
155
+ }
156
+
157
+ /* False when that chat has no thread on screen; the jump then waits for the next one that registers. */
158
+ export function jumpToTimelineTurn(key: string, turnId: string, target: TurnTarget): boolean {
159
+ const jump = turnJumpers.get(key);
160
+ if (jump === undefined) {
161
+ waitingTurnJumps.set(key, { turnId, target });
162
+ return false;
163
+ }
164
+ jump(turnId, target);
165
+ return true;
166
+ }
167
+
168
+ /* How near the top, in screens, the page before is asked for, so it is there before a reader reaches the edge. */
169
+ const EARLIER_SCREENS = 1.5;
170
+
171
+ export function wantsEarlier(scroller: HTMLElement): boolean {
172
+ return scroller.clientHeight > 0 && scroller.scrollTop < scroller.clientHeight * EARLIER_SCREENS;
173
+ }
174
+
175
+ /* A row being read and how far below the top of its scroller it starts, so a page going in above leaves it there. */
176
+ export interface ReadingAnchor {
177
+ id: string;
178
+ offset: number;
179
+ }
180
+
181
+ /* The first row in view, found by the rows' `data-item-id`, for a thread that keeps every row in the document. */
182
+ export function firstRowInView(scroller: HTMLElement): ReadingAnchor | null {
183
+ const top = scroller.getBoundingClientRect().top;
184
+ for (const row of scroller.querySelectorAll<HTMLElement>('[data-item-id]')) {
185
+ const rect = row.getBoundingClientRect();
186
+ if (rect.bottom > top) {
187
+ return { id: row.dataset.itemId!, offset: rect.top - top };
188
+ }
189
+ }
190
+ return null;
191
+ }
192
+
193
+ /* Puts the anchored row back where it was; nothing moves when that row is gone. */
194
+ export function restoreAnchor(scroller: HTMLElement, anchor: ReadingAnchor): void {
195
+ const row = scroller.querySelector<HTMLElement>(`[data-item-id="${CSS.escape(anchor.id)}"]`);
196
+ if (row !== null) {
197
+ scroller.scrollTop += row.getBoundingClientRect().top - scroller.getBoundingClientRect().top - anchor.offset;
198
+ }
199
+ }
@@ -0,0 +1,31 @@
1
+ import { useTranslation } from 'react-i18next';
2
+ import type { AgentKind } from '@adecore/agent-contracts';
3
+ import { AccountDot } from '../../agents/AccountDot';
4
+ import { useAccountChoice } from '../account-choice';
5
+ import { useChats } from '../../state/chats';
6
+ import { useChatScope } from '../../scope';
7
+ import { Pill, Tooltip } from '@adecore/ui';
8
+
9
+ function AccountPillOf({ provider, account }: { provider: AgentKind; account: string | undefined }) {
10
+ const { t } = useTranslation('agent-chat');
11
+ const choice = useAccountChoice(provider, account);
12
+ if (choice === null) {
13
+ return null;
14
+ }
15
+ const name = choice.current === null ? choice.currentId : choice.nameOf(choice.current);
16
+ return (
17
+ <Tooltip label={t('account.pill', { provider: choice.providerName, account: name })}>
18
+ <Pill icon={<AccountDot color={choice.current?.account.color} className="size-1.75" />}>
19
+ <span className="max-w-32 truncate">{name}</span>
20
+ </Pill>
21
+ </Tooltip>
22
+ );
23
+ }
24
+
25
+ /* Which account a chat node runs under, from the status every client gets; only while its CLI has a choice of accounts. */
26
+ export function AccountPill({ chatId }: { chatId: string }) {
27
+ const { keyOf } = useChatScope();
28
+ const provider = useChats((s) => s.statusByKey[keyOf(chatId)]?.info.provider);
29
+ const account = useChats((s) => s.statusByKey[keyOf(chatId)]?.info.account);
30
+ return provider === undefined ? null : <AccountPillOf provider={provider} account={account} />;
31
+ }
@@ -0,0 +1,18 @@
1
+ import { useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { parseAnsi } from '../logic/ansi';
4
+
5
+ export function AnsiOutput({ text, limit, tailLines }: { text: string; limit?: number; tailLines?: number }) {
6
+ const { t } = useTranslation('agent-chat');
7
+ const { tokens, omitted } = useMemo(() => parseAnsi(text, { limit, tailLines }), [text, limit, tailLines]);
8
+ return (
9
+ <>
10
+ {tokens.map((token, index) => (
11
+ <span key={index} style={token.style}>
12
+ {token.content}
13
+ </span>
14
+ ))}
15
+ {omitted > 0 && `\n${t('work.moreCharacters', { count: omitted })}`}
16
+ </>
17
+ );
18
+ }
@@ -0,0 +1,108 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import clsx from 'clsx';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Bookmark, Pencil, X } from 'lucide-react';
5
+ import { CHAT_BOOKMARK_LIMITS, type ChatBookmark } from '@adecore/agent-contracts';
6
+ import { nameBookmark, removeBookmark, useBookmarkNaming } from '../bookmarks';
7
+ import { bookmarkLabel } from '../logic/bookmarks';
8
+ import { useChatScope } from '../../scope';
9
+ import { ButtonGroup, Icon, IconButton, Input } from '@adecore/ui';
10
+
11
+ interface BookmarkMarkerProps {
12
+ chatId: string;
13
+ /* The message the bookmark is on, which for a folded turn is not the row it is drawn before. */
14
+ itemId: string;
15
+ bookmark: ChatBookmark | null;
16
+ }
17
+
18
+ /*
19
+ * A bookmark as a chapter line across the thread, drawn before the message it marks. Without a
20
+ * bookmark it only stands while the name field of a mark still on its way to the host is open.
21
+ */
22
+ export function BookmarkMarker({ chatId, itemId, bookmark }: BookmarkMarkerProps) {
23
+ const { t } = useTranslation('agent-chat');
24
+ const scope = useChatScope();
25
+ const chatKey = scope.keyOf(chatId);
26
+ const naming = useBookmarkNaming((s) => s.chatKey === chatKey && s.itemId === itemId);
27
+
28
+ if (bookmark === null && !naming) {
29
+ return null;
30
+ }
31
+
32
+ return (
33
+ <div className="group/bookmark mb-2 flex h-7 min-w-0 items-center gap-2 text-xs select-none">
34
+ <Icon icon={Bookmark} size={14} className="shrink-0 text-accent" />
35
+ {naming ? (
36
+ <NameField
37
+ initial={bookmark?.name ?? ''}
38
+ onDone={(name) => {
39
+ useBookmarkNaming.getState().close();
40
+ if (name !== null) {
41
+ void nameBookmark(scope, chatId, itemId, name, bookmark?.name);
42
+ }
43
+ }}
44
+ />
45
+ ) : (
46
+ bookmark !== null && (
47
+ <span className={clsx('min-w-0 truncate', bookmark.name === undefined ? 'text-text-muted' : 'text-text')}>{bookmarkLabel(bookmark)}</span>
48
+ )
49
+ )}
50
+ <span className="min-w-6 grow border-t border-border" />
51
+ {bookmark !== null && !naming && (
52
+ <ButtonGroup
53
+ render={<span />}
54
+ className="-ml-2 max-w-0 shrink-0 overflow-hidden opacity-0 group-hover/bookmark:ml-0 group-hover/bookmark:max-w-none group-hover/bookmark:opacity-100 group-has-focus-visible/bookmark:ml-0 group-has-focus-visible/bookmark:max-w-none group-has-focus-visible/bookmark:opacity-100"
55
+ >
56
+ <IconButton icon={Pencil} size="xs" label={t('bookmarks.rename')} onClick={() => useBookmarkNaming.getState().open(chatKey, itemId)} />
57
+ <IconButton icon={X} size="xs" label={t('bookmarks.remove')} onClick={() => void removeBookmark(scope, chatId, bookmark)} />
58
+ </ButtonGroup>
59
+ )}
60
+ </div>
61
+ );
62
+ }
63
+
64
+ /* Settles with the name on Enter or when the focus leaves, and with null on Escape, which keeps the name as it was. */
65
+ function NameField({ initial, onDone }: { initial: string; onDone: (name: string | null) => void }) {
66
+ const { t } = useTranslation('agent-chat');
67
+ const ref = useRef<HTMLInputElement>(null);
68
+ const settled = useRef(false);
69
+
70
+ // Again a frame later: the menu the field was opened from hands the focus back as it closes.
71
+ useEffect(() => {
72
+ ref.current?.focus();
73
+ const frame = requestAnimationFrame(() => ref.current?.focus());
74
+ return () => cancelAnimationFrame(frame);
75
+ }, []);
76
+
77
+ const settle = (name: string | null): void => {
78
+ if (settled.current) {
79
+ return;
80
+ }
81
+ settled.current = true;
82
+ onDone(name);
83
+ };
84
+
85
+ return (
86
+ <Input
87
+ ref={ref}
88
+ size="sm"
89
+ defaultValue={initial}
90
+ maxLength={CHAT_BOOKMARK_LIMITS.name}
91
+ placeholder={t('bookmarks.namePlaceholder')}
92
+ aria-label={t('bookmarks.nameLabel')}
93
+ className="w-64 max-w-full min-w-0 select-text"
94
+ onPointerDown={(e) => e.stopPropagation()}
95
+ onFocus={(e) => e.currentTarget.select()}
96
+ onBlur={(e) => settle(e.currentTarget.value)}
97
+ onKeyDown={(e) => {
98
+ e.stopPropagation();
99
+ if (e.key === 'Enter') {
100
+ settle(e.currentTarget.value);
101
+ }
102
+ if (e.key === 'Escape') {
103
+ settle(null);
104
+ }
105
+ }}
106
+ />
107
+ );
108
+ }