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

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 +53 -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 +196 -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 +265 -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 +196 -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,232 @@
1
+ import { Fragment } from 'react';
2
+ import clsx from 'clsx';
3
+ import { useTranslation } from 'react-i18next';
4
+ import type { UsageLimitsSnapshot, UsageWindow } from '@adecore/agent-contracts';
5
+ import { limitsAccountId } from '../agents/account-limits';
6
+ import { AccountDot } from '../agents/AccountDot';
7
+ import { Tooltip } from '@adecore/ui';
8
+ import { formatClock, formatWeekdayClock, isSameDay, formatCountdown, formatPercent } from '@adecore/ui/format';
9
+ import { ProviderLogo } from '../agents/ProviderLogo';
10
+ import { PROVIDER_COLORS, PROVIDER_LABELS } from './format';
11
+ import { accountNote, explain, isSignedOut, nextReset, type LimitAccount, type LimitGroup } from './limit-groups';
12
+
13
+ /* Red where a window is nearly spent, amber where it is worth knowing. A window with room to spare
14
+ is not news, so it takes the text color rather than a hue that competes with the two that are. */
15
+ function toneOf(used: number): string {
16
+ return used >= 0.9 ? 'bg-status-error' : used >= 0.7 ? 'bg-status-needs-you' : 'bg-text';
17
+ }
18
+
19
+ /* A time of day when the reset is today, a weekday and a time when it is not. */
20
+ function resetAtLabel(resetsAt: number, now: number): string {
21
+ return isSameDay(resetsAt, now) ? formatClock(resetsAt) : formatWeekdayClock(resetsAt);
22
+ }
23
+
24
+ /* How long the window still has, or nothing once it has run out. */
25
+ function resetInLabel(resetsAt: number, now: number): string | null {
26
+ return resetsAt <= now ? null : formatCountdown(resetsAt - now);
27
+ }
28
+
29
+ /* How much of the window has run, or null when the provider named neither a reset nor a length. */
30
+ function elapsedShare(window: UsageWindow, now: number): number | null {
31
+ if (window.resetsAt === null || window.durationMs === null || window.durationMs <= 0) {
32
+ return null;
33
+ }
34
+ return Math.min(1, Math.max(0, 1 - (window.resetsAt - now) / window.durationMs));
35
+ }
36
+
37
+ export function WindowBar({ window, now, compact }: { window: UsageWindow; now: number; compact: boolean }) {
38
+ const { t } = useTranslation('agent-usage');
39
+ const percent = Math.round(window.used * 100);
40
+ const elapsed = elapsedShare(window, now);
41
+ // The bar fills as the quota is spent, so the mark beside it is how much of the window has run.
42
+ const mark = elapsed === null ? null : Math.round(elapsed * 100);
43
+ const resetsAt = window.resetsAt === null ? null : resetAtLabel(window.resetsAt, now);
44
+ const resetsIn = window.resetsAt === null ? null : resetInLabel(window.resetsAt, now);
45
+
46
+ const bar = (
47
+ <div
48
+ role="progressbar"
49
+ aria-label={t('limits.bar', { window: window.label, percent })}
50
+ aria-valuenow={percent}
51
+ aria-valuemin={0}
52
+ aria-valuemax={100}
53
+ className={clsx('relative cursor-default', compact ? 'h-4' : 'h-6')}
54
+ >
55
+ <div className={clsx('absolute inset-x-0 rounded-full bg-surface-sunken', compact ? 'inset-y-1' : 'inset-y-1.5')} />
56
+ {/* Past a hundred there is nothing left to fill, so the bar stops rather than overflows. */}
57
+ <div
58
+ className={clsx('absolute left-0 rounded-full', compact ? 'inset-y-1' : 'inset-y-1.5', toneOf(window.used))}
59
+ style={{ width: `${Math.min(100, percent)}%` }}
60
+ />
61
+ {/* Past the mark is spending faster than the window gives back, short of it slower. */}
62
+ {mark !== null && (
63
+ // Inside the fill the line has to be cut out of it, outside it drawn on the track.
64
+ <span
65
+ aria-hidden
66
+ className={clsx('absolute inset-y-0.5 w-px -translate-x-1/2', mark <= percent ? 'bg-surface' : 'bg-text-muted')}
67
+ style={{ left: `${mark}%` }}
68
+ />
69
+ )}
70
+ </div>
71
+ );
72
+
73
+ return (
74
+ <div className="flex flex-col gap-1">
75
+ <div className="flex items-baseline gap-2 text-xs">
76
+ <span className="text-text">{window.label}</span>
77
+ <span className="ml-auto tabular-nums text-text-muted">{t('limits.used', { percent })}</span>
78
+ </div>
79
+ {compact ? (
80
+ bar
81
+ ) : (
82
+ <Tooltip
83
+ label={
84
+ <span className="flex flex-col gap-0.5">
85
+ <span>
86
+ {t('limits.used', { percent })}
87
+ {mark !== null && ` · ${t('limits.windowPassed', { percent: mark })}`}
88
+ </span>
89
+ {mark !== null && <span className="text-text-muted">{mark <= percent ? t('limits.faster') : t('limits.slower')}</span>}
90
+ </span>
91
+ }
92
+ >
93
+ {bar}
94
+ </Tooltip>
95
+ )}
96
+ {/* Under the bar rather than beside the label. A weekday and a countdown are too long to
97
+ share that line, and a reset that wrapped would read as two of them. */}
98
+ {resetsAt !== null && (
99
+ <p className="text-xs whitespace-nowrap text-text-faint">
100
+ {t('limits.resets', { at: resetsAt })}
101
+ {resetsIn !== null && ` · ${resetsIn}`}
102
+ </p>
103
+ )}
104
+ </div>
105
+ );
106
+ }
107
+
108
+ interface LimitsListProps {
109
+ limits: UsageLimitsSnapshot;
110
+ now: number;
111
+ /* Shorter bars, and no tooltip of their own, what a list that is itself a hover card needs. */
112
+ compact?: boolean;
113
+ }
114
+
115
+ /* Every provider with what its plan has left, shared by the usage page and the sidebar card. */
116
+ export function LimitsList({ limits, now, compact = false }: LimitsListProps) {
117
+ return (
118
+ <div className={clsx('flex flex-col', compact ? 'gap-4' : 'gap-6')}>
119
+ {limits.providers.map((provider) => {
120
+ const note = explain(provider);
121
+ return (
122
+ <div key={limitsAccountId(provider)} className="flex flex-col gap-2">
123
+ <p className="flex items-center gap-2 text-xs font-medium">
124
+ <span style={{ color: PROVIDER_COLORS[provider.kind] }}>
125
+ <ProviderLogo provider={provider.kind} />
126
+ </span>
127
+ {PROVIDER_LABELS[provider.kind]}
128
+ {provider.plan !== null && <span className="text-text-muted">· {provider.plan}</span>}
129
+ </p>
130
+ {note === null ? (
131
+ <div className={clsx('flex flex-col', compact ? 'gap-3' : 'gap-4')}>
132
+ {provider.windows.map((window) => (
133
+ <WindowBar key={window.id} window={window} now={now} compact={compact} />
134
+ ))}
135
+ </div>
136
+ ) : (
137
+ <p className="text-xs text-text-faint">{note}</p>
138
+ )}
139
+ </div>
140
+ );
141
+ })}
142
+ </div>
143
+ );
144
+ }
145
+
146
+ /* One window on one line, for a card that holds several accounts: the label, a thin bar with the elapsed mark, the percent. */
147
+ function WindowLine({ window, now }: { window: UsageWindow; now: number }) {
148
+ const { t } = useTranslation('agent-usage');
149
+ const percent = Math.round(window.used * 100);
150
+ const elapsed = elapsedShare(window, now);
151
+ const mark = elapsed === null ? null : Math.round(elapsed * 100);
152
+ return (
153
+ <>
154
+ <span className="text-text-muted">{window.label}</span>
155
+ <div
156
+ role="progressbar"
157
+ aria-label={t('limits.bar', { window: window.label, percent })}
158
+ aria-valuenow={percent}
159
+ aria-valuemin={0}
160
+ aria-valuemax={100}
161
+ className="relative h-1.5 rounded-full bg-surface-sunken"
162
+ >
163
+ <div className={clsx('absolute inset-y-0 left-0 rounded-full', toneOf(window.used))} style={{ width: `${Math.min(100, percent)}%` }} />
164
+ {mark !== null && (
165
+ // Cut out of the fill in the popup's own color, drawn on the track past it.
166
+ <span
167
+ aria-hidden
168
+ className={clsx('absolute -inset-y-0.5 w-px -translate-x-1/2', mark <= percent ? 'bg-surface-raised' : 'bg-text-muted')}
169
+ style={{ left: `${mark}%` }}
170
+ />
171
+ )}
172
+ </div>
173
+ <span className={clsx('text-right tabular-nums', window.used >= 0.9 ? 'text-status-error' : 'text-text')}>{formatPercent(percent)}</span>
174
+ </>
175
+ );
176
+ }
177
+
178
+ function AccountLines({ account, now }: { account: LimitAccount; now: number }) {
179
+ const { t } = useTranslation('agent-usage');
180
+ const signedOut = isSignedOut(account);
181
+ const note = signedOut ? null : accountNote(account);
182
+ const windows = signedOut || note !== null ? [] : (account.entry?.windows ?? []);
183
+ const next = nextReset(windows, now);
184
+ const resetsIn = next === null ? null : resetInLabel(next.resetsAt, now);
185
+ return (
186
+ <>
187
+ <p className="col-span-full mt-2 flex min-w-0 items-center gap-2">
188
+ <AccountDot color={account.color} />
189
+ <span className="truncate">{account.name}</span>
190
+ {signedOut ? (
191
+ <span className="shrink-0 text-text-faint">· {t('limits.signedOut')}</span>
192
+ ) : (
193
+ account.entry?.plan && <span className="shrink-0 text-text-muted">· {account.entry.plan}</span>
194
+ )}
195
+ </p>
196
+ {note !== null && <p className="col-span-full text-text-faint">{note}</p>}
197
+ {windows.map((window) => (
198
+ <WindowLine key={window.id} window={window} now={now} />
199
+ ))}
200
+ {next !== null && (
201
+ <p className="col-span-full whitespace-nowrap text-text-faint">
202
+ {t('limits.nextReset', { window: next.label, at: resetAtLabel(next.resetsAt, now) })}
203
+ {resetsIn !== null && ` · ${resetsIn}`}
204
+ </p>
205
+ )}
206
+ </>
207
+ );
208
+ }
209
+
210
+ /*
211
+ * The hover card once a CLI has several accounts: a block per account, a line per window. One grid over
212
+ * the whole card, so the bars of every account start and end at the same place whatever their labels.
213
+ */
214
+ export function AccountLimitsList({ groups, now }: { groups: readonly LimitGroup[]; now: number }) {
215
+ return (
216
+ <div className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-2 gap-y-1 text-xs">
217
+ {groups.map((group) => (
218
+ <Fragment key={group.kind}>
219
+ <p className="col-span-full mt-3 flex items-center gap-2 font-medium first:mt-0">
220
+ <span style={{ color: PROVIDER_COLORS[group.kind] }}>
221
+ <ProviderLogo provider={group.kind} />
222
+ </span>
223
+ {PROVIDER_LABELS[group.kind]}
224
+ </p>
225
+ {group.accounts.map((account) => (
226
+ <AccountLines key={account.id} account={account} now={now} />
227
+ ))}
228
+ </Fragment>
229
+ ))}
230
+ </div>
231
+ );
232
+ }
@@ -0,0 +1,210 @@
1
+ import { useState } from 'react';
2
+ import clsx from 'clsx';
3
+ import { CircleHelp, Folder, Info } from 'lucide-react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import type { UsageModel, UsageProject, UsageProvider, UsageSummaryResult } from '@adecore/agent-contracts';
6
+ import { EmptyState, Icon, SectionLabel, Segmented, Tooltip } from '@adecore/ui';
7
+ import { useChatScope } from '../scope';
8
+ import { chatHost } from '../host';
9
+ import type { UsageMetric } from '../state/usage';
10
+ import { ProviderLogo } from '../agents/ProviderLogo';
11
+ import { USAGE_PROVIDERS, totalTokensOf } from '@adecore/agent-contracts';
12
+ import { modelNameFromSlug } from '../agents/model-name';
13
+ import { formatCount, formatTokens, PROVIDER_COLORS, PROVIDER_LABELS, shortPath, slotLabel } from './format';
14
+ import { useMoney } from './money';
15
+ import { deriveDays } from './summary';
16
+
17
+ type Breakdown = 'models' | 'projects' | 'day';
18
+
19
+ /* The row of every table, the same height and the same hover as a commit row in the git panel. */
20
+ const ROW = 'flex h-8 items-center gap-2 rounded-md px-2 text-xs hover:bg-surface-hover';
21
+
22
+ const HEAD = 'flex h-6 items-center gap-2 px-2 text-xs text-text-faint';
23
+
24
+ /* A row is padded so its hover has room around the text, and the table pulls that padding back off
25
+ again. The first column then starts on the same line as every other section of the page. */
26
+ const TABLE = '-mx-2 flex flex-col';
27
+
28
+ const TABS: readonly Breakdown[] = ['models', 'projects', 'day'];
29
+
30
+ /* A share of the biggest row, never under one pixel. A row that did something has to be visible. */
31
+ function barWidth(value: number, top: number): number {
32
+ return value <= 0 || top <= 0 ? 0 : Math.max(1, Math.round((value / top) * 100));
33
+ }
34
+
35
+ function ProviderMark({ provider }: { provider: UsageProvider }) {
36
+ return (
37
+ <Tooltip label={PROVIDER_LABELS[provider]}>
38
+ <span style={{ color: PROVIDER_COLORS[provider] }}>
39
+ <ProviderLogo provider={provider} />
40
+ </span>
41
+ </Tooltip>
42
+ );
43
+ }
44
+
45
+ function ModelRows({ models, metric, total }: { models: readonly UsageModel[]; metric: UsageMetric; total: number }) {
46
+ const { t } = useTranslation('agent-usage');
47
+ const money = useMoney();
48
+ /* On the tokens metric the biggest row is the one that moved the most, not the dearest. */
49
+ const sorted = [...models].sort((a, b) =>
50
+ metric === 'tokens' ? totalTokensOf(b.totals) - totalTokensOf(a.totals) : (b.costUsd ?? -1) - (a.costUsd ?? -1)
51
+ );
52
+ return (
53
+ <div className={TABLE}>
54
+ <div className={HEAD}>
55
+ <span className="grow">{t('breakdown.models.model')}</span>
56
+ <span className="w-16 text-right">{t('breakdown.models.calls')}</span>
57
+ <span className="w-16 text-right">{t('breakdown.models.input')}</span>
58
+ <span className="w-16 text-right">{t('breakdown.models.output')}</span>
59
+ <span className="w-24 text-right">{t('breakdown.models.cache')}</span>
60
+ <span className="w-20 text-right">{t('breakdown.models.cost')}</span>
61
+ <span className="w-12 text-right">{t('breakdown.models.share')}</span>
62
+ </div>
63
+ {sorted.map((model) => (
64
+ <div key={`${model.provider} ${model.model}`} className={ROW}>
65
+ <ProviderMark provider={model.provider} />
66
+ <span className="truncate">{modelNameFromSlug(model.model)}</span>
67
+ {model.priceBasis === 'family' && model.pricedAs !== null && (
68
+ <Tooltip label={t('breakdown.models.pricedAs', { model: modelNameFromSlug(model.pricedAs) })}>
69
+ <span className="text-text-faint">
70
+ <Icon icon={Info} size={12} />
71
+ </span>
72
+ </Tooltip>
73
+ )}
74
+ <span className="ml-auto w-16 text-right tabular-nums text-text-muted">{formatCount(model.totals.calls)}</span>
75
+ <span className="w-16 text-right tabular-nums text-text-muted">{formatTokens(model.totals.input)}</span>
76
+ <span className="w-16 text-right tabular-nums text-text-muted">{formatTokens(model.totals.output)}</span>
77
+ <span className="w-24 text-right tabular-nums text-text-muted">
78
+ {formatTokens(model.totals.cacheRead)} / {formatTokens(model.totals.cacheWrite)}
79
+ </span>
80
+ <span className="w-20 text-right tabular-nums">
81
+ {model.costUsd === null ? (
82
+ <Tooltip label={t('breakdown.models.noPrice')}>
83
+ <span className="inline-flex items-center gap-1 text-text-faint">
84
+ <Icon icon={CircleHelp} size={12} />?
85
+ </span>
86
+ </Tooltip>
87
+ ) : (
88
+ money(model.costUsd)
89
+ )}
90
+ </span>
91
+ <span className="w-12 text-right tabular-nums text-text-muted">
92
+ {model.costUsd === null || total <= 0 ? '' : `${Math.round((model.costUsd / total) * 100)}%`}
93
+ </span>
94
+ </div>
95
+ ))}
96
+ </div>
97
+ );
98
+ }
99
+
100
+ /* One project's row, under the name and the mark its host gives it, or its folder's own under a folder. */
101
+ function ProjectRow({ project, top, money }: { project: UsageProject; top: number; money(usd: number): string }) {
102
+ // The usage on screen is one host's, so the look of a project is that host's too.
103
+ const look = chatHost().useProjectLook(useChatScope().id, project.projectId);
104
+ return (
105
+ /* The mark sits on the title rather than between the two lines, so a column of icons lines
106
+ up with the names beside it and not with the paths under them. */
107
+ <div className={`${ROW} h-auto items-start py-1.5`}>
108
+ <span className="mt-px flex">
109
+ {look?.mark ?? (
110
+ <span className="text-text-faint">
111
+ <Icon icon={Folder} size={16} />
112
+ </span>
113
+ )}
114
+ </span>
115
+ <span className="flex min-w-0 grow flex-col">
116
+ <span className="truncate">{look?.name ?? project.name}</span>
117
+ <span className="truncate text-text-faint">{shortPath(project.folder)}</span>
118
+ </span>
119
+ <span className="mt-1 flex h-[9px] w-32 shrink-0 overflow-hidden rounded-full bg-surface-sunken">
120
+ {USAGE_PROVIDERS.map((provider) => {
121
+ const share = project.byProvider[provider];
122
+ const width = barWidth(share?.costUsd ?? 0, top);
123
+ return width === 0 ? null : <span key={provider} style={{ width: `${width}%`, background: PROVIDER_COLORS[provider] }} />;
124
+ })}
125
+ </span>
126
+ <span className="w-20 text-right tabular-nums">{money(project.costUsd)}</span>
127
+ <span className="w-16 text-right tabular-nums text-text-muted">{formatTokens(totalTokensOf(project.totals))}</span>
128
+ </div>
129
+ );
130
+ }
131
+
132
+ function ProjectRows({ projects }: { projects: readonly UsageProject[] }) {
133
+ const money = useMoney();
134
+ const top = Math.max(...projects.map((project) => project.costUsd), 0);
135
+ return (
136
+ <div className={clsx(TABLE, 'gap-1')}>
137
+ {projects.map((project) => (
138
+ <ProjectRow key={project.folder} project={project} top={top} money={money} />
139
+ ))}
140
+ </div>
141
+ );
142
+ }
143
+
144
+ /* The chart written out per calendar day, a column per provider that did anything in the period. */
145
+ function DayRows({ summary, providers }: { summary: UsageSummaryResult; providers: readonly UsageProvider[] }) {
146
+ const { t } = useTranslation('agent-usage');
147
+ const money = useMoney();
148
+ const rows = deriveDays(summary);
149
+ if (rows.length === 0) {
150
+ return <EmptyState>{t('breakdown.days.empty')}</EmptyState>;
151
+ }
152
+ return (
153
+ <div className={TABLE}>
154
+ <div className={HEAD}>
155
+ <span className="grow">{t('breakdown.days.day')}</span>
156
+ {providers.map((provider) => (
157
+ <span key={provider} className="w-20 text-right">
158
+ {PROVIDER_LABELS[provider]}
159
+ </span>
160
+ ))}
161
+ <span className="w-20 text-right">{t('breakdown.days.total')}</span>
162
+ <span className="w-16 text-right">{t('breakdown.days.tokens')}</span>
163
+ </div>
164
+ {rows.map((row) => (
165
+ <div key={row.slot} className={ROW}>
166
+ <span className="grow truncate">{slotLabel(row.slot)}</span>
167
+ {providers.map((provider) => (
168
+ <span key={provider} className="w-20 text-right tabular-nums text-text-muted">
169
+ {row.costByProvider[provider] === undefined ? '' : money(row.costByProvider[provider]!)}
170
+ </span>
171
+ ))}
172
+ <span className="w-20 text-right tabular-nums">{money(row.costUsd)}</span>
173
+ <span className="w-16 text-right tabular-nums text-text-muted">{formatTokens(row.tokens)}</span>
174
+ </div>
175
+ ))}
176
+ </div>
177
+ );
178
+ }
179
+
180
+ interface UsageBreakdownProps {
181
+ summary: UsageSummaryResult;
182
+ metric: UsageMetric;
183
+ /* The providers that did anything, in the order the chart stacks them. */
184
+ providers: readonly UsageProvider[];
185
+ }
186
+
187
+ /* The same period cut three ways: which model, which checkout, and which day it went on. */
188
+ export function UsageBreakdown({ summary, metric, providers }: UsageBreakdownProps) {
189
+ const { t } = useTranslation('agent-usage');
190
+ const [tab, setTab] = useState<Breakdown>('models');
191
+ const total = summary.models.reduce((sum, model) => sum + (model.costUsd ?? 0), 0);
192
+ return (
193
+ <section className="flex flex-col gap-3">
194
+ <div className="flex items-center gap-3">
195
+ <SectionLabel render={<h2 />}>{t('breakdown.title')}</SectionLabel>
196
+ <div className="ml-auto">
197
+ <Segmented
198
+ value={tab}
199
+ options={TABS.map((id) => ({ id, label: t(`breakdown.tabs.${id}`) }))}
200
+ onValueChange={(id) => setTab(id)}
201
+ label={t('breakdown.title')}
202
+ />
203
+ </div>
204
+ </div>
205
+ {tab === 'models' && <ModelRows models={summary.models} metric={metric} total={total} />}
206
+ {tab === 'projects' && <ProjectRows projects={summary.projects} />}
207
+ {tab === 'day' && <DayRows summary={summary} providers={providers} />}
208
+ </section>
209
+ );
210
+ }
@@ -0,0 +1,113 @@
1
+ import { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import type { UsageProvider } from '@adecore/agent-contracts';
4
+ import { Surface, useMeasuredWidth } from '@adecore/ui';
5
+ import { PROVIDER_COLORS, PROVIDER_LABELS, slotAxisLabel, slotLabel } from './format';
6
+ import { niceScale, type ChartSlot } from './summary';
7
+
8
+ interface UsageChartProps {
9
+ slots: readonly ChartSlot[];
10
+ providers: readonly UsageProvider[];
11
+ format(value: number): string;
12
+ /* Which x labels to draw, every nth slot, so 24 hours and 90 days both stay readable. */
13
+ labelEvery: number;
14
+ }
15
+
16
+ const HEIGHT = 224;
17
+ const TOOLTIP_WIDTH = 168;
18
+ /* A bar that is dimmed still has to read as its own color, so the rest keeps well over half of it. */
19
+ const DIMMED = 0.55;
20
+
21
+ /*
22
+ * One stacked bar per slot, a segment per provider. Stacked rather than layered areas, since the total
23
+ * of a day is the height of its bar, and the smaller provider never disappears under the larger one.
24
+ */
25
+ export function UsageChart({ slots, providers, format, labelEvery }: UsageChartProps) {
26
+ const { t } = useTranslation('agent-usage');
27
+ const [measure, width] = useMeasuredWidth();
28
+ const [hovered, setHovered] = useState<number | null>(null);
29
+ const scale = niceScale(Math.max(...slots.map((slot) => slot.total), 0));
30
+ const band = slots.length === 0 ? 0 : width / slots.length;
31
+ const barWidth = Math.max(1, Math.round(band * 0.62));
32
+ const ticks = Array.from({ length: Math.round(scale.max / scale.step) + 1 }, (_, index) => index * scale.step);
33
+ const active = hovered === null ? null : slots[hovered];
34
+
35
+ const onPointerMove = (e: React.PointerEvent<HTMLDivElement>): void => {
36
+ const bounds = e.currentTarget.getBoundingClientRect();
37
+ const index = band === 0 ? -1 : Math.floor((e.clientX - bounds.left) / band);
38
+ setHovered(index >= 0 && index < slots.length ? index : null);
39
+ };
40
+
41
+ /* Near the right edge the card would run off the column, so it hangs from the other side. */
42
+ const tooltipLeft =
43
+ hovered === null ? 0 : Math.round(Math.min(Math.max(0, hovered * band + band / 2 - TOOLTIP_WIDTH / 2), Math.max(0, width - TOOLTIP_WIDTH)));
44
+
45
+ return (
46
+ <div className="flex flex-col">
47
+ <div ref={measure} className="relative" style={{ height: HEIGHT }} onPointerMove={onPointerMove} onPointerLeave={() => setHovered(null)}>
48
+ <svg width={width} height={HEIGHT} role="presentation">
49
+ {ticks.map((tick) => {
50
+ const y = Math.round(HEIGHT - (tick / scale.max) * HEIGHT) + 0.5;
51
+ return <line key={tick} x1={0} x2={width} y1={y} y2={y} stroke="var(--border)" strokeWidth={1} />;
52
+ })}
53
+ {slots.map((slot, index) => {
54
+ const left = Math.round(index * band + (band - barWidth) / 2);
55
+ let bottom = HEIGHT;
56
+ return (
57
+ <g key={slot.slot} opacity={hovered === null || hovered === index ? 1 : DIMMED}>
58
+ {providers.map((provider) => {
59
+ const value = slot.byProvider[provider] ?? 0;
60
+ if (value <= 0) {
61
+ return null;
62
+ }
63
+ const height = Math.max(1, Math.round((value / scale.max) * HEIGHT));
64
+ bottom -= height;
65
+ return <rect key={provider} x={left} y={bottom} width={barWidth} height={height} fill={PROVIDER_COLORS[provider]} />;
66
+ })}
67
+ </g>
68
+ );
69
+ })}
70
+ {hovered !== null && (
71
+ <line
72
+ x1={Math.round(hovered * band + band / 2) + 0.5}
73
+ x2={Math.round(hovered * band + band / 2) + 0.5}
74
+ y1={0}
75
+ y2={HEIGHT}
76
+ stroke="var(--border-strong)"
77
+ strokeWidth={1}
78
+ />
79
+ )}
80
+ </svg>
81
+ {active && (
82
+ <Surface className="pointer-events-none absolute top-2 rounded-lg p-2 text-xs" style={{ left: tooltipLeft, width: TOOLTIP_WIDTH }}>
83
+ <p className="mb-1 font-medium">{slotLabel(active.slot)}</p>
84
+ {providers.map((provider) => (
85
+ <p key={provider} className="flex items-center gap-1.5 text-text-muted">
86
+ <span className="size-2 shrink-0 rounded-full" style={{ background: PROVIDER_COLORS[provider] }} />
87
+ {PROVIDER_LABELS[provider]}
88
+ <span className="ml-auto tabular-nums text-text">{format(active.byProvider[provider] ?? 0)}</span>
89
+ </p>
90
+ ))}
91
+ <p className="mt-1 flex items-center gap-1.5 border-t border-border pt-1 font-medium">
92
+ {t('chart.total')}
93
+ <span className="ml-auto tabular-nums">{format(active.total)}</span>
94
+ </p>
95
+ </Surface>
96
+ )}
97
+ </div>
98
+ <div className="relative mt-2 h-4">
99
+ {slots.map((slot, index) =>
100
+ index % labelEvery === 0 ? (
101
+ <span
102
+ key={slot.slot}
103
+ className="absolute -translate-x-1/2 text-xs whitespace-nowrap text-text-faint"
104
+ style={{ left: Math.round(index * band + band / 2) }}
105
+ >
106
+ {slotAxisLabel(slot.slot)}
107
+ </span>
108
+ ) : null
109
+ )}
110
+ </div>
111
+ </div>
112
+ );
113
+ }
@@ -0,0 +1,20 @@
1
+ import type { ReactNode } from 'react';
2
+ import i18next from 'i18next';
3
+ import { Dialog } from '@adecore/ui';
4
+
5
+ /*
6
+ * The frame of the usage page: larger than the settings, since the chart and the breakdown need the
7
+ * width. What goes in it (`UsagePage`, in an `ErrorBoundary` of the app's) mounts only while it is
8
+ * open, so nothing is scanned behind a closed dialog. It is a dialog and not a view, since none of it
9
+ * belongs to one project.
10
+ */
11
+ export function UsageDialog({ open, onOpenChange, children }: { open: boolean; onOpenChange(open: boolean): void; children: ReactNode }) {
12
+ return (
13
+ <Dialog.Root open={open} onOpenChange={onOpenChange}>
14
+ <Dialog.Popup className="flex h-[820px] w-[1080px] flex-col">
15
+ {children}
16
+ <Dialog.Description className="sr-only">{i18next.t('agent-usage:dialog.description')}</Dialog.Description>
17
+ </Dialog.Popup>
18
+ </Dialog.Root>
19
+ );
20
+ }