@flanksource/clicky-ui 0.3.46 → 0.3.47

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 (379) hide show
  1. package/NOTICE.md +11 -0
  2. package/dist/ai.cjs +13 -8
  3. package/dist/ai.cjs.map +1 -1
  4. package/dist/ai.js +7 -2
  5. package/dist/ai.js.map +1 -1
  6. package/dist/components/ErrorWrapper.cjs +2 -3
  7. package/dist/components/ErrorWrapper.cjs.map +1 -1
  8. package/dist/components/ErrorWrapper.d.ts.map +1 -1
  9. package/dist/components/ErrorWrapper.js +3 -4
  10. package/dist/components/ErrorWrapper.js.map +1 -1
  11. package/dist/data/Clicky.cjs +3 -0
  12. package/dist/data/Clicky.cjs.map +1 -1
  13. package/dist/data/Clicky.js +3 -0
  14. package/dist/data/Clicky.js.map +1 -1
  15. package/dist/data/FilterPill.cjs +15 -2
  16. package/dist/data/FilterPill.cjs.map +1 -1
  17. package/dist/data/FilterPill.d.ts +1 -1
  18. package/dist/data/FilterPill.d.ts.map +1 -1
  19. package/dist/data/FilterPill.js +15 -2
  20. package/dist/data/FilterPill.js.map +1 -1
  21. package/dist/data/LogViewer.cjs +27 -8
  22. package/dist/data/LogViewer.cjs.map +1 -1
  23. package/dist/data/LogViewer.d.ts +11 -3
  24. package/dist/data/LogViewer.d.ts.map +1 -1
  25. package/dist/data/LogViewer.js +27 -8
  26. package/dist/data/LogViewer.js.map +1 -1
  27. package/dist/data/Properties.cjs +2 -15
  28. package/dist/data/Properties.cjs.map +1 -1
  29. package/dist/data/Properties.d.ts +4 -0
  30. package/dist/data/Properties.d.ts.map +1 -1
  31. package/dist/data/Properties.js +2 -15
  32. package/dist/data/Properties.js.map +1 -1
  33. package/dist/data/PropertyValue.cjs +37 -0
  34. package/dist/data/PropertyValue.cjs.map +1 -0
  35. package/dist/data/PropertyValue.d.ts +6 -0
  36. package/dist/data/PropertyValue.d.ts.map +1 -0
  37. package/dist/data/PropertyValue.js +37 -0
  38. package/dist/data/PropertyValue.js.map +1 -0
  39. package/dist/data/Tree.cjs +4 -3
  40. package/dist/data/Tree.cjs.map +1 -1
  41. package/dist/data/Tree.d.ts +3 -1
  42. package/dist/data/Tree.d.ts.map +1 -1
  43. package/dist/data/Tree.js +4 -3
  44. package/dist/data/Tree.js.map +1 -1
  45. package/dist/data/ai/ChatFab.cjs +1 -1
  46. package/dist/data/ai/ChatFab.cjs.map +1 -1
  47. package/dist/data/ai/ChatFab.js +1 -1
  48. package/dist/data/ai/ChatFab.js.map +1 -1
  49. package/dist/data/ai/ChatWindow.cjs +106 -171
  50. package/dist/data/ai/ChatWindow.cjs.map +1 -1
  51. package/dist/data/ai/ChatWindow.d.ts +6 -2
  52. package/dist/data/ai/ChatWindow.d.ts.map +1 -1
  53. package/dist/data/ai/ChatWindow.header.cjs +104 -0
  54. package/dist/data/ai/ChatWindow.header.cjs.map +1 -0
  55. package/dist/data/ai/ChatWindow.header.d.ts +29 -0
  56. package/dist/data/ai/ChatWindow.header.d.ts.map +1 -0
  57. package/dist/data/ai/ChatWindow.header.js +104 -0
  58. package/dist/data/ai/ChatWindow.header.js.map +1 -0
  59. package/dist/data/ai/ChatWindow.js +107 -172
  60. package/dist/data/ai/ChatWindow.js.map +1 -1
  61. package/dist/data/ai/ChatWindow.placement.cjs +95 -0
  62. package/dist/data/ai/ChatWindow.placement.cjs.map +1 -0
  63. package/dist/data/ai/ChatWindow.placement.d.ts +32 -0
  64. package/dist/data/ai/ChatWindow.placement.d.ts.map +1 -0
  65. package/dist/data/ai/ChatWindow.placement.js +95 -0
  66. package/dist/data/ai/ChatWindow.placement.js.map +1 -0
  67. package/dist/data/ai/SessionChatComposer.cjs +3 -1
  68. package/dist/data/ai/SessionChatComposer.cjs.map +1 -1
  69. package/dist/data/ai/SessionChatComposer.d.ts +4 -1
  70. package/dist/data/ai/SessionChatComposer.d.ts.map +1 -1
  71. package/dist/data/ai/SessionChatComposer.js +3 -1
  72. package/dist/data/ai/SessionChatComposer.js.map +1 -1
  73. package/dist/data/ai/SessionInspector.cjs +68 -72
  74. package/dist/data/ai/SessionInspector.cjs.map +1 -1
  75. package/dist/data/ai/SessionInspector.composer-slot.cjs +8 -0
  76. package/dist/data/ai/SessionInspector.composer-slot.cjs.map +1 -0
  77. package/dist/data/ai/SessionInspector.composer-slot.d.ts +9 -0
  78. package/dist/data/ai/SessionInspector.composer-slot.d.ts.map +1 -0
  79. package/dist/data/ai/SessionInspector.composer-slot.js +8 -0
  80. package/dist/data/ai/SessionInspector.composer-slot.js.map +1 -0
  81. package/dist/data/ai/SessionInspector.d.ts +6 -1
  82. package/dist/data/ai/SessionInspector.d.ts.map +1 -1
  83. package/dist/data/ai/SessionInspector.js +69 -73
  84. package/dist/data/ai/SessionInspector.js.map +1 -1
  85. package/dist/data/ai/SessionInspector.legacy.cjs +41 -42
  86. package/dist/data/ai/SessionInspector.legacy.cjs.map +1 -1
  87. package/dist/data/ai/SessionInspector.legacy.d.ts.map +1 -1
  88. package/dist/data/ai/SessionInspector.legacy.js +41 -42
  89. package/dist/data/ai/SessionInspector.legacy.js.map +1 -1
  90. package/dist/data/ai/SessionInspector.remote-view.cjs +14 -9
  91. package/dist/data/ai/SessionInspector.remote-view.cjs.map +1 -1
  92. package/dist/data/ai/SessionInspector.remote-view.d.ts +6 -2
  93. package/dist/data/ai/SessionInspector.remote-view.d.ts.map +1 -1
  94. package/dist/data/ai/SessionInspector.remote-view.js +14 -9
  95. package/dist/data/ai/SessionInspector.remote-view.js.map +1 -1
  96. package/dist/data/ai/SessionInspector.remote.cjs +84 -16
  97. package/dist/data/ai/SessionInspector.remote.cjs.map +1 -1
  98. package/dist/data/ai/SessionInspector.remote.d.ts +42 -7
  99. package/dist/data/ai/SessionInspector.remote.d.ts.map +1 -1
  100. package/dist/data/ai/SessionInspector.remote.js +86 -18
  101. package/dist/data/ai/SessionInspector.remote.js.map +1 -1
  102. package/dist/data/ai/SessionInspector.transcript.cjs +5 -0
  103. package/dist/data/ai/SessionInspector.transcript.cjs.map +1 -1
  104. package/dist/data/ai/SessionInspector.transcript.d.ts.map +1 -1
  105. package/dist/data/ai/SessionInspector.transcript.js +5 -0
  106. package/dist/data/ai/SessionInspector.transcript.js.map +1 -1
  107. package/dist/data/ai/SessionTail.cjs +63 -0
  108. package/dist/data/ai/SessionTail.cjs.map +1 -0
  109. package/dist/data/ai/SessionTail.d.ts +14 -0
  110. package/dist/data/ai/SessionTail.d.ts.map +1 -0
  111. package/dist/data/ai/SessionTail.js +63 -0
  112. package/dist/data/ai/SessionTail.js.map +1 -0
  113. package/dist/data/ai/SessionViewer.cjs +10 -3
  114. package/dist/data/ai/SessionViewer.cjs.map +1 -1
  115. package/dist/data/ai/SessionViewer.d.ts +7 -1
  116. package/dist/data/ai/SessionViewer.d.ts.map +1 -1
  117. package/dist/data/ai/SessionViewer.js +10 -3
  118. package/dist/data/ai/SessionViewer.js.map +1 -1
  119. package/dist/data/ai/SessionViewer.unified.d.ts +14 -0
  120. package/dist/data/ai/SessionViewer.unified.d.ts.map +1 -1
  121. package/dist/data/ai/index.d.ts +4 -3
  122. package/dist/data/ai/index.d.ts.map +1 -1
  123. package/dist/data/ai/use-session-scroll.cjs +30 -6
  124. package/dist/data/ai/use-session-scroll.cjs.map +1 -1
  125. package/dist/data/ai/use-session-scroll.d.ts +7 -0
  126. package/dist/data/ai/use-session-scroll.d.ts.map +1 -1
  127. package/dist/data/ai/use-session-scroll.js +30 -6
  128. package/dist/data/ai/use-session-scroll.js.map +1 -1
  129. package/dist/data/chat/Chat.cjs +1 -1
  130. package/dist/data/chat/Chat.cjs.map +1 -1
  131. package/dist/data/chat/Chat.js +1 -1
  132. package/dist/data/chat/Chat.js.map +1 -1
  133. package/dist/data/chat/ChatRuntimeToolbar.cjs +4 -4
  134. package/dist/data/chat/ChatRuntimeToolbar.cjs.map +1 -1
  135. package/dist/data/chat/ChatRuntimeToolbar.d.ts.map +1 -1
  136. package/dist/data/chat/ChatRuntimeToolbar.js +4 -4
  137. package/dist/data/chat/ChatRuntimeToolbar.js.map +1 -1
  138. package/dist/data/chat/PromptInput.cjs +4 -2
  139. package/dist/data/chat/PromptInput.cjs.map +1 -1
  140. package/dist/data/chat/PromptInput.d.ts +5 -1
  141. package/dist/data/chat/PromptInput.d.ts.map +1 -1
  142. package/dist/data/chat/PromptInput.js +4 -2
  143. package/dist/data/chat/PromptInput.js.map +1 -1
  144. package/dist/data/diagnostics/ErrorDetails.cjs +13 -2
  145. package/dist/data/diagnostics/ErrorDetails.cjs.map +1 -1
  146. package/dist/data/diagnostics/ErrorDetails.d.ts +3 -1
  147. package/dist/data/diagnostics/ErrorDetails.d.ts.map +1 -1
  148. package/dist/data/diagnostics/ErrorDetails.js +14 -3
  149. package/dist/data/diagnostics/ErrorDetails.js.map +1 -1
  150. package/dist/data/diagnostics/FrameSourceWindow.cjs +3 -2
  151. package/dist/data/diagnostics/FrameSourceWindow.cjs.map +1 -1
  152. package/dist/data/diagnostics/FrameSourceWindow.d.ts.map +1 -1
  153. package/dist/data/diagnostics/FrameSourceWindow.js +3 -2
  154. package/dist/data/diagnostics/FrameSourceWindow.js.map +1 -1
  155. package/dist/data/diagnostics/error-diagnostics.cjs +5 -0
  156. package/dist/data/diagnostics/error-diagnostics.cjs.map +1 -1
  157. package/dist/data/diagnostics/error-diagnostics.d.ts +1 -0
  158. package/dist/data/diagnostics/error-diagnostics.d.ts.map +1 -1
  159. package/dist/data/diagnostics/error-diagnostics.js +6 -1
  160. package/dist/data/diagnostics/error-diagnostics.js.map +1 -1
  161. package/dist/data/har/HarPanel.cjs +3 -1
  162. package/dist/data/har/HarPanel.cjs.map +1 -1
  163. package/dist/data/har/HarPanel.d.ts +4 -1
  164. package/dist/data/har/HarPanel.d.ts.map +1 -1
  165. package/dist/data/har/HarPanel.js +3 -1
  166. package/dist/data/har/HarPanel.js.map +1 -1
  167. package/dist/data/test-runner/Expectation.cjs +57 -0
  168. package/dist/data/test-runner/Expectation.cjs.map +1 -0
  169. package/dist/data/test-runner/Expectation.d.ts +19 -0
  170. package/dist/data/test-runner/Expectation.d.ts.map +1 -0
  171. package/dist/data/test-runner/Expectation.js +57 -0
  172. package/dist/data/test-runner/Expectation.js.map +1 -0
  173. package/dist/data/test-runner/MarkIcons.cjs +38 -0
  174. package/dist/data/test-runner/MarkIcons.cjs.map +1 -0
  175. package/dist/data/test-runner/MarkIcons.d.ts +15 -0
  176. package/dist/data/test-runner/MarkIcons.d.ts.map +1 -0
  177. package/dist/data/test-runner/MarkIcons.js +38 -0
  178. package/dist/data/test-runner/MarkIcons.js.map +1 -0
  179. package/dist/data/test-runner/TestDetailPanel.cjs +74 -42
  180. package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
  181. package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
  182. package/dist/data/test-runner/TestDetailPanel.js +76 -44
  183. package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
  184. package/dist/data/test-runner/TestFailureDetail.cjs +23 -27
  185. package/dist/data/test-runner/TestFailureDetail.cjs.map +1 -1
  186. package/dist/data/test-runner/TestFailureDetail.d.ts +2 -2
  187. package/dist/data/test-runner/TestFailureDetail.d.ts.map +1 -1
  188. package/dist/data/test-runner/TestFailureDetail.js +23 -27
  189. package/dist/data/test-runner/TestFailureDetail.js.map +1 -1
  190. package/dist/data/test-runner/TestFilterBar.cjs +87 -93
  191. package/dist/data/test-runner/TestFilterBar.cjs.map +1 -1
  192. package/dist/data/test-runner/TestFilterBar.d.ts +3 -2
  193. package/dist/data/test-runner/TestFilterBar.d.ts.map +1 -1
  194. package/dist/data/test-runner/TestFilterBar.js +89 -95
  195. package/dist/data/test-runner/TestFilterBar.js.map +1 -1
  196. package/dist/data/test-runner/TestRunSummary.cjs +35 -21
  197. package/dist/data/test-runner/TestRunSummary.cjs.map +1 -1
  198. package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -1
  199. package/dist/data/test-runner/TestRunSummary.js +35 -21
  200. package/dist/data/test-runner/TestRunSummary.js.map +1 -1
  201. package/dist/data/test-runner/TestRunner.cjs +52 -30
  202. package/dist/data/test-runner/TestRunner.cjs.map +1 -1
  203. package/dist/data/test-runner/TestRunner.d.ts +3 -2
  204. package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
  205. package/dist/data/test-runner/TestRunner.js +52 -30
  206. package/dist/data/test-runner/TestRunner.js.map +1 -1
  207. package/dist/data/test-runner/TestTree.cjs +5 -2
  208. package/dist/data/test-runner/TestTree.cjs.map +1 -1
  209. package/dist/data/test-runner/TestTree.d.ts +5 -2
  210. package/dist/data/test-runner/TestTree.d.ts.map +1 -1
  211. package/dist/data/test-runner/TestTree.js +5 -2
  212. package/dist/data/test-runner/TestTree.js.map +1 -1
  213. package/dist/data/test-runner/TestTreeNode.cjs +27 -19
  214. package/dist/data/test-runner/TestTreeNode.cjs.map +1 -1
  215. package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -1
  216. package/dist/data/test-runner/TestTreeNode.js +28 -20
  217. package/dist/data/test-runner/TestTreeNode.js.map +1 -1
  218. package/dist/data/test-runner/adapter.cjs +11 -0
  219. package/dist/data/test-runner/adapter.cjs.map +1 -1
  220. package/dist/data/test-runner/adapter.d.ts +18 -7
  221. package/dist/data/test-runner/adapter.d.ts.map +1 -1
  222. package/dist/data/test-runner/adapter.js +11 -0
  223. package/dist/data/test-runner/adapter.js.map +1 -1
  224. package/dist/data/test-runner/copy.cjs +114 -0
  225. package/dist/data/test-runner/copy.cjs.map +1 -0
  226. package/dist/data/test-runner/copy.d.ts +47 -0
  227. package/dist/data/test-runner/copy.d.ts.map +1 -0
  228. package/dist/data/test-runner/copy.js +114 -0
  229. package/dist/data/test-runner/copy.js.map +1 -0
  230. package/dist/data/test-runner/index.d.ts +5 -2
  231. package/dist/data/test-runner/index.d.ts.map +1 -1
  232. package/dist/data/test-runner/marks.cjs +143 -0
  233. package/dist/data/test-runner/marks.cjs.map +1 -0
  234. package/dist/data/test-runner/marks.d.ts +30 -0
  235. package/dist/data/test-runner/marks.d.ts.map +1 -0
  236. package/dist/data/test-runner/marks.js +143 -0
  237. package/dist/data/test-runner/marks.js.map +1 -0
  238. package/dist/data/test-runner/status.cjs +27 -44
  239. package/dist/data/test-runner/status.cjs.map +1 -1
  240. package/dist/data/test-runner/status.d.ts +12 -3
  241. package/dist/data/test-runner/status.d.ts.map +1 -1
  242. package/dist/data/test-runner/status.js +27 -44
  243. package/dist/data/test-runner/status.js.map +1 -1
  244. package/dist/data/verification/adapterViews.cjs +16 -41
  245. package/dist/data/verification/adapterViews.cjs.map +1 -1
  246. package/dist/data/verification/adapterViews.d.ts.map +1 -1
  247. package/dist/data/verification/adapterViews.js +16 -41
  248. package/dist/data/verification/adapterViews.js.map +1 -1
  249. package/dist/data/verification/adapters.cjs +115 -3
  250. package/dist/data/verification/adapters.cjs.map +1 -1
  251. package/dist/data/verification/adapters.d.ts.map +1 -1
  252. package/dist/data/verification/adapters.js +115 -3
  253. package/dist/data/verification/adapters.js.map +1 -1
  254. package/dist/data/verification/verifyReportTests.cjs.map +1 -1
  255. package/dist/data/verification/verifyReportTests.js.map +1 -1
  256. package/dist/data/version-info.cjs +3 -3
  257. package/dist/data/version-info.js +3 -3
  258. package/dist/data.cjs +44 -3
  259. package/dist/data.cjs.map +1 -1
  260. package/dist/data.d.ts +2 -0
  261. package/dist/data.d.ts.map +1 -1
  262. package/dist/data.js +45 -4
  263. package/dist/data.js.map +1 -1
  264. package/dist/icons/components/UiLogoEslint.cjs +10 -0
  265. package/dist/icons/components/UiLogoEslint.cjs.map +1 -0
  266. package/dist/icons/components/UiLogoEslint.d.ts +9 -0
  267. package/dist/icons/components/UiLogoEslint.d.ts.map +1 -0
  268. package/dist/icons/components/UiLogoEslint.js +10 -0
  269. package/dist/icons/components/UiLogoEslint.js.map +1 -0
  270. package/dist/icons/components/UiLogoGo.cjs +10 -0
  271. package/dist/icons/components/UiLogoGo.cjs.map +1 -0
  272. package/dist/icons/components/UiLogoGo.d.ts +9 -0
  273. package/dist/icons/components/UiLogoGo.d.ts.map +1 -0
  274. package/dist/icons/components/UiLogoGo.js +10 -0
  275. package/dist/icons/components/UiLogoGo.js.map +1 -0
  276. package/dist/icons/components/UiLogoJest.cjs +10 -0
  277. package/dist/icons/components/UiLogoJest.cjs.map +1 -0
  278. package/dist/icons/components/UiLogoJest.d.ts +9 -0
  279. package/dist/icons/components/UiLogoJest.d.ts.map +1 -0
  280. package/dist/icons/components/UiLogoJest.js +10 -0
  281. package/dist/icons/components/UiLogoJest.js.map +1 -0
  282. package/dist/icons/components/UiLogoMarkdown.cjs +10 -0
  283. package/dist/icons/components/UiLogoMarkdown.cjs.map +1 -0
  284. package/dist/icons/components/UiLogoMarkdown.d.ts +9 -0
  285. package/dist/icons/components/UiLogoMarkdown.d.ts.map +1 -0
  286. package/dist/icons/components/UiLogoMarkdown.js +10 -0
  287. package/dist/icons/components/UiLogoMarkdown.js.map +1 -0
  288. package/dist/icons/components/UiLogoOxc.cjs +10 -0
  289. package/dist/icons/components/UiLogoOxc.cjs.map +1 -0
  290. package/dist/icons/components/UiLogoOxc.d.ts +9 -0
  291. package/dist/icons/components/UiLogoOxc.d.ts.map +1 -0
  292. package/dist/icons/components/UiLogoOxc.js +10 -0
  293. package/dist/icons/components/UiLogoOxc.js.map +1 -0
  294. package/dist/icons/components/UiLogoPlaywright.cjs +10 -0
  295. package/dist/icons/components/UiLogoPlaywright.cjs.map +1 -0
  296. package/dist/icons/components/UiLogoPlaywright.d.ts +9 -0
  297. package/dist/icons/components/UiLogoPlaywright.d.ts.map +1 -0
  298. package/dist/icons/components/UiLogoPlaywright.js +10 -0
  299. package/dist/icons/components/UiLogoPlaywright.js.map +1 -0
  300. package/dist/icons/components/UiLogoPython.cjs +10 -0
  301. package/dist/icons/components/UiLogoPython.cjs.map +1 -0
  302. package/dist/icons/components/UiLogoPython.d.ts +9 -0
  303. package/dist/icons/components/UiLogoPython.d.ts.map +1 -0
  304. package/dist/icons/components/UiLogoPython.js +10 -0
  305. package/dist/icons/components/UiLogoPython.js.map +1 -0
  306. package/dist/icons/components/UiLogoReact.cjs +10 -0
  307. package/dist/icons/components/UiLogoReact.cjs.map +1 -0
  308. package/dist/icons/components/UiLogoReact.d.ts +9 -0
  309. package/dist/icons/components/UiLogoReact.d.ts.map +1 -0
  310. package/dist/icons/components/UiLogoReact.js +10 -0
  311. package/dist/icons/components/UiLogoReact.js.map +1 -0
  312. package/dist/icons/components/UiLogoRuff.cjs +10 -0
  313. package/dist/icons/components/UiLogoRuff.cjs.map +1 -0
  314. package/dist/icons/components/UiLogoRuff.d.ts +9 -0
  315. package/dist/icons/components/UiLogoRuff.d.ts.map +1 -0
  316. package/dist/icons/components/UiLogoRuff.js +10 -0
  317. package/dist/icons/components/UiLogoRuff.js.map +1 -0
  318. package/dist/icons/components/UiLogoTypescript.cjs +10 -0
  319. package/dist/icons/components/UiLogoTypescript.cjs.map +1 -0
  320. package/dist/icons/components/UiLogoTypescript.d.ts +9 -0
  321. package/dist/icons/components/UiLogoTypescript.d.ts.map +1 -0
  322. package/dist/icons/components/UiLogoTypescript.js +10 -0
  323. package/dist/icons/components/UiLogoTypescript.js.map +1 -0
  324. package/dist/icons/components/UiLogoVitest.cjs +10 -0
  325. package/dist/icons/components/UiLogoVitest.cjs.map +1 -0
  326. package/dist/icons/components/UiLogoVitest.d.ts +9 -0
  327. package/dist/icons/components/UiLogoVitest.d.ts.map +1 -0
  328. package/dist/icons/components/UiLogoVitest.js +10 -0
  329. package/dist/icons/components/UiLogoVitest.js.map +1 -0
  330. package/dist/icons/index.d.ts +11 -0
  331. package/dist/icons/index.d.ts.map +1 -1
  332. package/dist/icons/registry.cjs +22 -0
  333. package/dist/icons/registry.cjs.map +1 -1
  334. package/dist/icons/registry.d.ts.map +1 -1
  335. package/dist/icons/registry.js +22 -0
  336. package/dist/icons/registry.js.map +1 -1
  337. package/dist/icons.cjs +22 -0
  338. package/dist/icons.cjs.map +1 -1
  339. package/dist/icons.js +22 -0
  340. package/dist/icons.js.map +1 -1
  341. package/dist/index.cjs +44 -3
  342. package/dist/index.cjs.map +1 -1
  343. package/dist/index.js +45 -4
  344. package/dist/index.js.map +1 -1
  345. package/dist/overlay/DropdownMenuSubmenu.cjs +4 -4
  346. package/dist/overlay/DropdownMenuSubmenu.cjs.map +1 -1
  347. package/dist/overlay/DropdownMenuSubmenu.js +4 -4
  348. package/dist/overlay/DropdownMenuSubmenu.js.map +1 -1
  349. package/dist/rpc/InlineError.cjs +12 -43
  350. package/dist/rpc/InlineError.cjs.map +1 -1
  351. package/dist/rpc/InlineError.d.ts.map +1 -1
  352. package/dist/rpc/InlineError.js +13 -44
  353. package/dist/rpc/InlineError.js.map +1 -1
  354. package/dist/rpc/OperationScheduleDialog.cjs +63 -10
  355. package/dist/rpc/OperationScheduleDialog.cjs.map +1 -1
  356. package/dist/rpc/OperationScheduleDialog.d.ts +4 -0
  357. package/dist/rpc/OperationScheduleDialog.d.ts.map +1 -1
  358. package/dist/rpc/OperationScheduleDialog.js +63 -10
  359. package/dist/rpc/OperationScheduleDialog.js.map +1 -1
  360. package/dist/rpc/OperationSchedules.cjs +12 -2
  361. package/dist/rpc/OperationSchedules.cjs.map +1 -1
  362. package/dist/rpc/OperationSchedules.d.ts.map +1 -1
  363. package/dist/rpc/OperationSchedules.js +12 -2
  364. package/dist/rpc/OperationSchedules.js.map +1 -1
  365. package/dist/rpc/operation-schedule-model.cjs +2 -0
  366. package/dist/rpc/operation-schedule-model.cjs.map +1 -1
  367. package/dist/rpc/operation-schedule-model.d.ts.map +1 -1
  368. package/dist/rpc/operation-schedule-model.js +2 -0
  369. package/dist/rpc/operation-schedule-model.js.map +1 -1
  370. package/dist/rpc/operation-schedule-types.d.ts +7 -1
  371. package/dist/rpc/operation-schedule-types.d.ts.map +1 -1
  372. package/dist/styles.css +1 -1
  373. package/package.json +1 -1
  374. package/dist/data/test-runner/frameworkIcon.cjs +0 -27
  375. package/dist/data/test-runner/frameworkIcon.cjs.map +0 -1
  376. package/dist/data/test-runner/frameworkIcon.d.ts +0 -3
  377. package/dist/data/test-runner/frameworkIcon.d.ts.map +0 -1
  378. package/dist/data/test-runner/frameworkIcon.js +0 -27
  379. package/dist/data/test-runner/frameworkIcon.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"use-session-scroll.cjs","sources":["../../../src/data/ai/use-session-scroll.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState, type RefObject } from \"react\";\n\n// Progressive windowing for the SessionViewer log. A recorded session runs to\n// thousands of rows (many with syntax-highlighted code blocks), so rendering the\n// whole transcript blocks first paint. Instead we render the *newest* screenful,\n// keep the view pinned to the bottom (most recent activity), and backfill older\n// rows as the reader scrolls up — the classic reverse-infinite-scroll of a chat\n// log. The pure helpers below hold the windowing math; the hook wires the DOM\n// listeners that drive it. Load-more is driven off scroll position rather than an\n// IntersectionObserver so it stays reliable in backgrounded/hidden tabs (where the\n// browser pauses observer callbacks tied to the render loop).\n\n/** The last `visibleCount` of `items`, plus where the window starts and whether\n * older items remain above it. */\nexport function windowSlice<T>(\n items: readonly T[],\n visibleCount: number,\n): { items: T[]; startIndex: number; hasMore: boolean } {\n const clamped = Math.max(0, Math.min(visibleCount, items.length));\n const startIndex = items.length - clamped;\n return { items: items.slice(startIndex), startIndex, hasMore: startIndex > 0 };\n}\n\n/** Grow the window by `batchSize`, never past `total`. */\nexport function nextVisibleCount(current: number, batchSize: number, total: number): number {\n return Math.min(current + Math.max(1, batchSize), total);\n}\n\n/** True when the scroll position sits within `threshold` px of the bottom. */\nexport function isPinnedToBottom(\n metrics: { scrollTop: number; scrollHeight: number; clientHeight: number },\n threshold: number,\n): boolean {\n return metrics.scrollHeight - metrics.scrollTop - metrics.clientHeight <= threshold;\n}\n\n/** True when the scroll position sits within `threshold` px of the top. */\nexport function isNearTop(scrollTop: number, threshold: number): boolean {\n return scrollTop <= threshold;\n}\n\nexport interface SessionScrollOptions {\n /** Number of filtered events available to render. */\n total: number;\n /** Turns the whole mechanism on; when false the hook renders every event. */\n enabled: boolean;\n /** Rows rendered on first paint (and after a session change). */\n windowSize: number;\n /** Rows added each time older content is loaded. */\n batchSize: number;\n /** Changing this resets the window and re-pins to the bottom — pass a value\n * derived from the session identity, not the filter state. */\n resetKey: string;\n /** Distance (px) from the bottom still counted as \"pinned\". */\n bottomThreshold?: number;\n /** Distance (px) from the top at which older rows start loading. */\n topThreshold?: number;\n}\n\nexport interface SessionScrollState {\n scrollRef: RefObject<HTMLDivElement | null>;\n contentRef: RefObject<HTMLDivElement | null>;\n startIndex: number;\n hasMore: boolean;\n following: boolean;\n resumeFollowing: () => void;\n}\n\nexport function useSessionScroll(options: SessionScrollOptions): SessionScrollState {\n const { total, enabled, windowSize, batchSize, resetKey } = options;\n const bottomThreshold = options.bottomThreshold ?? 64;\n const topThreshold = options.topThreshold ?? 600;\n\n const scrollRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const followingRef = useRef(true);\n const lastScrollTopRef = useRef(0);\n const anchorHeightRef = useRef<number | null>(null);\n const scrollBottomPendingRef = useRef(true);\n const loadingOlderRef = useRef(false);\n const previousTotalRef = useRef({ resetKey, total });\n\n const [visibleCount, setVisibleCount] = useState(windowSize);\n const [following, setFollowing] = useState(true);\n const effectiveCount = enabled ? visibleCount : total;\n const startIndex = Math.max(0, total - effectiveCount);\n const hasMore = enabled && startIndex > 0;\n const hasMoreRef = useRef(hasMore);\n hasMoreRef.current = hasMore;\n\n const scrollToBottom = useCallback(() => {\n const el = scrollRef.current;\n if (el) {\n el.scrollTop = el.scrollHeight;\n lastScrollTopRef.current = el.scrollTop;\n }\n }, []);\n\n const resumeFollowing = useCallback(() => {\n followingRef.current = true;\n setFollowing(true);\n scrollToBottom();\n }, [scrollToBottom]);\n\n // Prepend an older batch, latching until the layout effect settles the prepend so\n // rapid scroll events don't stack multiple batches at once.\n const growOlder = useCallback(() => {\n if (loadingOlderRef.current || !hasMoreRef.current) return;\n loadingOlderRef.current = true;\n const el = scrollRef.current;\n anchorHeightRef.current = el ? el.scrollHeight : null;\n setVisibleCount((count) => nextVisibleCount(count, batchSize, total));\n }, [batchSize, total]);\n\n const growOlderRef = useRef(growOlder);\n growOlderRef.current = growOlder;\n\n // A new session (resetKey change) snaps back to the newest screenful and re-pins.\n useLayoutEffect(() => {\n if (!enabled) return;\n setVisibleCount(windowSize);\n followingRef.current = true;\n setFollowing(true);\n loadingOlderRef.current = false;\n scrollBottomPendingRef.current = true;\n }, [enabled, resetKey, windowSize]);\n\n useLayoutEffect(() => {\n const previous = previousTotalRef.current;\n previousTotalRef.current = { resetKey, total };\n if (enabled && previous.resetKey === resetKey && !followingRef.current && total > previous.total) {\n setVisibleCount((count) => count + total - previous.total);\n }\n }, [enabled, resetKey, total]);\n\n // After each commit, either snap to bottom (pending pin) or preserve the reader's\n // position when older rows were prepended (anchor), then release the load latch.\n useLayoutEffect(() => {\n const el = scrollRef.current;\n if (!enabled || !el) return;\n if (scrollBottomPendingRef.current) {\n scrollBottomPendingRef.current = false;\n anchorHeightRef.current = null;\n scrollToBottom();\n } else if (anchorHeightRef.current != null) {\n el.scrollTop += el.scrollHeight - anchorHeightRef.current;\n anchorHeightRef.current = null;\n lastScrollTopRef.current = el.scrollTop;\n } else if (followingRef.current) {\n scrollToBottom();\n }\n loadingOlderRef.current = false;\n }, [enabled, visibleCount, total, scrollToBottom]);\n\n // Track pinned-to-bottom, and load older rows as the reader nears the top.\n useEffect(() => {\n const el = scrollRef.current;\n if (!enabled || !el) return;\n const onScroll = () => {\n const movedUp = el.scrollTop < lastScrollTopRef.current;\n if (movedUp || !isPinnedToBottom(el, bottomThreshold)) {\n followingRef.current = false;\n setFollowing(false);\n }\n lastScrollTopRef.current = el.scrollTop;\n if (hasMoreRef.current && movedUp && isNearTop(el.scrollTop, topThreshold)) growOlderRef.current();\n };\n const onWheel = (event: WheelEvent) => {\n if (event.deltaY >= 0) return;\n followingRef.current = false;\n setFollowing(false);\n if (hasMoreRef.current && isNearTop(el.scrollTop, topThreshold)) growOlderRef.current();\n };\n el.addEventListener(\"scroll\", onScroll, { passive: true });\n el.addEventListener(\"wheel\", onWheel, { passive: true });\n return () => {\n el.removeEventListener(\"scroll\", onScroll);\n el.removeEventListener(\"wheel\", onWheel);\n };\n }, [enabled, bottomThreshold, topThreshold]);\n\n // Keep the newest row in view as rows grow (shiki highlighting settles after paint).\n useEffect(() => {\n const content = contentRef.current;\n if (!enabled || !content || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n if (followingRef.current) scrollToBottom();\n });\n observer.observe(content);\n return () => observer.disconnect();\n }, [enabled, scrollToBottom]);\n\n return { scrollRef, contentRef, startIndex, hasMore, following, resumeFollowing };\n}\n"],"names":["useRef","useState","useCallback","useLayoutEffect","useEffect"],"mappings":"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"}
1
+ {"version":3,"file":"use-session-scroll.cjs","sources":["../../../src/data/ai/use-session-scroll.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState, type RefObject } from \"react\";\n\n// Progressive windowing for the SessionViewer log. A recorded session runs to\n// thousands of rows (many with syntax-highlighted code blocks), so rendering the\n// whole transcript blocks first paint. Instead we render the *newest* screenful,\n// keep the view pinned to the bottom (most recent activity), and backfill older\n// rows as the reader scrolls up — the classic reverse-infinite-scroll of a chat\n// log. The pure helpers below hold the windowing math; the hook wires the DOM\n// listeners that drive it. Load-more is driven off scroll position rather than an\n// IntersectionObserver so it stays reliable in backgrounded/hidden tabs (where the\n// browser pauses observer callbacks tied to the render loop).\n\n/** The last `visibleCount` of `items`, plus where the window starts and whether\n * older items remain above it. */\nexport function windowSlice<T>(\n items: readonly T[],\n visibleCount: number,\n): { items: T[]; startIndex: number; hasMore: boolean } {\n const clamped = Math.max(0, Math.min(visibleCount, items.length));\n const startIndex = items.length - clamped;\n return { items: items.slice(startIndex), startIndex, hasMore: startIndex > 0 };\n}\n\n/** Grow the window by `batchSize`, never past `total`. */\nexport function nextVisibleCount(current: number, batchSize: number, total: number): number {\n return Math.min(current + Math.max(1, batchSize), total);\n}\n\n/** True when the scroll position sits within `threshold` px of the bottom. */\nexport function isPinnedToBottom(\n metrics: { scrollTop: number; scrollHeight: number; clientHeight: number },\n threshold: number,\n): boolean {\n return metrics.scrollHeight - metrics.scrollTop - metrics.clientHeight <= threshold;\n}\n\n/** True when the scroll position sits within `threshold` px of the top. */\nexport function isNearTop(scrollTop: number, threshold: number): boolean {\n return scrollTop <= threshold;\n}\n\nexport interface SessionScrollOptions {\n /** Number of filtered events available to render. */\n total: number;\n /** Turns the whole mechanism on; when false the hook renders every event. */\n enabled: boolean;\n /** Rows rendered on first paint (and after a session change). */\n windowSize: number;\n /** Rows added each time older content is loaded. */\n batchSize: number;\n /** Changing this resets the window and re-pins to the bottom — pass a value\n * derived from the session identity, not the filter state. */\n resetKey: string;\n /** Distance (px) from the bottom still counted as \"pinned\". */\n bottomThreshold?: number;\n /** Distance (px) from the top at which older rows start loading. */\n topThreshold?: number;\n /** Identity of the first item. When it changes under the same resetKey while\n * `total` grows, older items were prepended (a fetched page of history): the\n * window grows to include them and the reader's position is kept. */\n firstKey?: string;\n /** Called as the reader scrolls up near the top with every local item\n * already rendered — the cue to fetch older history. */\n onReachStart?: () => void;\n}\n\nexport interface SessionScrollState {\n scrollRef: RefObject<HTMLDivElement | null>;\n contentRef: RefObject<HTMLDivElement | null>;\n startIndex: number;\n hasMore: boolean;\n following: boolean;\n resumeFollowing: () => void;\n}\n\nexport function useSessionScroll(options: SessionScrollOptions): SessionScrollState {\n const { total, enabled, windowSize, batchSize, resetKey } = options;\n const bottomThreshold = options.bottomThreshold ?? 64;\n const topThreshold = options.topThreshold ?? 600;\n\n const scrollRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const followingRef = useRef(true);\n const lastScrollTopRef = useRef(0);\n const anchorHeightRef = useRef<number | null>(null);\n const scrollBottomPendingRef = useRef(true);\n const loadingOlderRef = useRef(false);\n const firstKey = options.firstKey;\n const previousTotalRef = useRef({ resetKey, total, firstKey });\n const lastScrollHeightRef = useRef<number | null>(null);\n const prependedRef = useRef(false);\n const onReachStartRef = useRef(options.onReachStart);\n onReachStartRef.current = options.onReachStart;\n\n const [visibleCount, setVisibleCount] = useState(windowSize);\n const [following, setFollowing] = useState(true);\n // A prepend grows the window during render, so the commit that mounts the\n // older rows is the one whose layout effect restores the reader's position.\n const [tracked, setTracked] = useState({ resetKey, total, firstKey });\n if (tracked.resetKey !== resetKey || tracked.total !== total || tracked.firstKey !== firstKey) {\n if (enabled && tracked.resetKey === resetKey && tracked.firstKey !== firstKey && total > tracked.total) {\n prependedRef.current = true;\n setVisibleCount((count) => count + total - tracked.total);\n }\n setTracked({ resetKey, total, firstKey });\n }\n const effectiveCount = enabled ? visibleCount : total;\n const startIndex = Math.max(0, total - effectiveCount);\n const hasMore = enabled && startIndex > 0;\n const hasMoreRef = useRef(hasMore);\n hasMoreRef.current = hasMore;\n\n const scrollToBottom = useCallback(() => {\n const el = scrollRef.current;\n if (el) {\n el.scrollTop = el.scrollHeight;\n lastScrollTopRef.current = el.scrollTop;\n }\n }, []);\n\n const resumeFollowing = useCallback(() => {\n followingRef.current = true;\n setFollowing(true);\n scrollToBottom();\n }, [scrollToBottom]);\n\n // Prepend an older batch, latching until the layout effect settles the prepend so\n // rapid scroll events don't stack multiple batches at once.\n const growOlder = useCallback(() => {\n if (loadingOlderRef.current || !hasMoreRef.current) return;\n loadingOlderRef.current = true;\n const el = scrollRef.current;\n anchorHeightRef.current = el ? el.scrollHeight : null;\n setVisibleCount((count) => nextVisibleCount(count, batchSize, total));\n }, [batchSize, total]);\n\n const growOlderRef = useRef(growOlder);\n growOlderRef.current = growOlder;\n\n // A new session (resetKey change) snaps back to the newest screenful and re-pins.\n useLayoutEffect(() => {\n if (!enabled) return;\n setVisibleCount(windowSize);\n followingRef.current = true;\n setFollowing(true);\n loadingOlderRef.current = false;\n scrollBottomPendingRef.current = true;\n }, [enabled, resetKey, windowSize]);\n\n useLayoutEffect(() => {\n const previous = previousTotalRef.current;\n previousTotalRef.current = { resetKey, total, firstKey };\n if (\n enabled &&\n previous.resetKey === resetKey &&\n previous.firstKey === firstKey &&\n !followingRef.current &&\n total > previous.total\n ) {\n setVisibleCount((count) => count + total - previous.total);\n }\n }, [enabled, resetKey, total, firstKey]);\n\n // After each commit, either snap to bottom (pending pin) or preserve the reader's\n // position when older rows were prepended (anchor), then release the load latch.\n useLayoutEffect(() => {\n const el = scrollRef.current;\n if (!enabled || !el) return;\n if (scrollBottomPendingRef.current) {\n scrollBottomPendingRef.current = false;\n anchorHeightRef.current = null;\n scrollToBottom();\n } else if (prependedRef.current && lastScrollHeightRef.current != null) {\n el.scrollTop += el.scrollHeight - lastScrollHeightRef.current;\n lastScrollTopRef.current = el.scrollTop;\n } else if (anchorHeightRef.current != null) {\n el.scrollTop += el.scrollHeight - anchorHeightRef.current;\n anchorHeightRef.current = null;\n lastScrollTopRef.current = el.scrollTop;\n } else if (followingRef.current) {\n scrollToBottom();\n }\n prependedRef.current = false;\n lastScrollHeightRef.current = el.scrollHeight;\n loadingOlderRef.current = false;\n }, [enabled, visibleCount, total, scrollToBottom]);\n\n // Track pinned-to-bottom, and load older rows as the reader nears the top.\n useEffect(() => {\n const el = scrollRef.current;\n if (!enabled || !el) return;\n const onScroll = () => {\n const movedUp = el.scrollTop < lastScrollTopRef.current;\n if (movedUp || !isPinnedToBottom(el, bottomThreshold)) {\n followingRef.current = false;\n setFollowing(false);\n }\n lastScrollTopRef.current = el.scrollTop;\n lastScrollHeightRef.current = el.scrollHeight;\n if (movedUp && isNearTop(el.scrollTop, topThreshold)) loadOlder();\n };\n const onWheel = (event: WheelEvent) => {\n if (event.deltaY >= 0) return;\n followingRef.current = false;\n setFollowing(false);\n if (isNearTop(el.scrollTop, topThreshold)) loadOlder();\n };\n // Rendered rows first; once every local row is mounted, ask for history.\n const loadOlder = () => {\n if (hasMoreRef.current) growOlderRef.current();\n else onReachStartRef.current?.();\n };\n el.addEventListener(\"scroll\", onScroll, { passive: true });\n el.addEventListener(\"wheel\", onWheel, { passive: true });\n return () => {\n el.removeEventListener(\"scroll\", onScroll);\n el.removeEventListener(\"wheel\", onWheel);\n };\n }, [enabled, bottomThreshold, topThreshold]);\n\n // Keep the newest row in view as rows grow (shiki highlighting settles after paint).\n useEffect(() => {\n const content = contentRef.current;\n if (!enabled || !content || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n if (followingRef.current) scrollToBottom();\n });\n observer.observe(content);\n return () => observer.disconnect();\n }, [enabled, scrollToBottom]);\n\n return { scrollRef, contentRef, startIndex, hasMore, following, resumeFollowing };\n}\n"],"names":["useRef","useState","useCallback","useLayoutEffect","useEffect"],"mappings":"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"}
@@ -32,6 +32,13 @@ export interface SessionScrollOptions {
32
32
  bottomThreshold?: number;
33
33
  /** Distance (px) from the top at which older rows start loading. */
34
34
  topThreshold?: number;
35
+ /** Identity of the first item. When it changes under the same resetKey while
36
+ * `total` grows, older items were prepended (a fetched page of history): the
37
+ * window grows to include them and the reader's position is kept. */
38
+ firstKey?: string;
39
+ /** Called as the reader scrolls up near the top with every local item
40
+ * already rendered — the cue to fetch older history. */
41
+ onReachStart?: () => void;
35
42
  }
36
43
  export interface SessionScrollState {
37
44
  scrollRef: RefObject<HTMLDivElement | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"use-session-scroll.d.ts","sourceRoot":"","sources":["../../../src/data/ai/use-session-scroll.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6D,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAYlG;mCACmC;AACnC,wBAAgB,WAAW,CAAC,CAAC,EAC3B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,YAAY,EAAE,MAAM,GACnB;IAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAItD;AAED,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1F;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EAC1E,SAAS,EAAE,MAAM,GAChB,OAAO,CAET;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAEvE;AAED,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAC;IACjB,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB;mEAC+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC5C,UAAU,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,GAAG,kBAAkB,CA6HlF"}
1
+ {"version":3,"file":"use-session-scroll.d.ts","sourceRoot":"","sources":["../../../src/data/ai/use-session-scroll.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6D,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAYlG;mCACmC;AACnC,wBAAgB,WAAW,CAAC,CAAC,EAC3B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,YAAY,EAAE,MAAM,GACnB;IAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAItD;AAED,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1F;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EAC1E,SAAS,EAAE,MAAM,GAChB,OAAO,CAET;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAEvE;AAED,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAC;IACjB,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB;mEAC+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;0EAEsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;6DACyD;IACzD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC5C,UAAU,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,GAAG,kBAAkB,CA6JlF"}
@@ -19,9 +19,22 @@ function useSessionScroll(options) {
19
19
  const anchorHeightRef = useRef(null);
20
20
  const scrollBottomPendingRef = useRef(true);
21
21
  const loadingOlderRef = useRef(false);
22
- const previousTotalRef = useRef({ resetKey, total });
22
+ const firstKey = options.firstKey;
23
+ const previousTotalRef = useRef({ resetKey, total, firstKey });
24
+ const lastScrollHeightRef = useRef(null);
25
+ const prependedRef = useRef(false);
26
+ const onReachStartRef = useRef(options.onReachStart);
27
+ onReachStartRef.current = options.onReachStart;
23
28
  const [visibleCount, setVisibleCount] = useState(windowSize);
24
29
  const [following, setFollowing] = useState(true);
30
+ const [tracked, setTracked] = useState({ resetKey, total, firstKey });
31
+ if (tracked.resetKey !== resetKey || tracked.total !== total || tracked.firstKey !== firstKey) {
32
+ if (enabled && tracked.resetKey === resetKey && tracked.firstKey !== firstKey && total > tracked.total) {
33
+ prependedRef.current = true;
34
+ setVisibleCount((count) => count + total - tracked.total);
35
+ }
36
+ setTracked({ resetKey, total, firstKey });
37
+ }
25
38
  const effectiveCount = enabled ? visibleCount : total;
26
39
  const startIndex = Math.max(0, total - effectiveCount);
27
40
  const hasMore = enabled && startIndex > 0;
@@ -58,11 +71,11 @@ function useSessionScroll(options) {
58
71
  }, [enabled, resetKey, windowSize]);
59
72
  useLayoutEffect(() => {
60
73
  const previous = previousTotalRef.current;
61
- previousTotalRef.current = { resetKey, total };
62
- if (enabled && previous.resetKey === resetKey && !followingRef.current && total > previous.total) {
74
+ previousTotalRef.current = { resetKey, total, firstKey };
75
+ if (enabled && previous.resetKey === resetKey && previous.firstKey === firstKey && !followingRef.current && total > previous.total) {
63
76
  setVisibleCount((count) => count + total - previous.total);
64
77
  }
65
- }, [enabled, resetKey, total]);
78
+ }, [enabled, resetKey, total, firstKey]);
66
79
  useLayoutEffect(() => {
67
80
  const el = scrollRef.current;
68
81
  if (!enabled || !el) return;
@@ -70,6 +83,9 @@ function useSessionScroll(options) {
70
83
  scrollBottomPendingRef.current = false;
71
84
  anchorHeightRef.current = null;
72
85
  scrollToBottom();
86
+ } else if (prependedRef.current && lastScrollHeightRef.current != null) {
87
+ el.scrollTop += el.scrollHeight - lastScrollHeightRef.current;
88
+ lastScrollTopRef.current = el.scrollTop;
73
89
  } else if (anchorHeightRef.current != null) {
74
90
  el.scrollTop += el.scrollHeight - anchorHeightRef.current;
75
91
  anchorHeightRef.current = null;
@@ -77,6 +93,8 @@ function useSessionScroll(options) {
77
93
  } else if (followingRef.current) {
78
94
  scrollToBottom();
79
95
  }
96
+ prependedRef.current = false;
97
+ lastScrollHeightRef.current = el.scrollHeight;
80
98
  loadingOlderRef.current = false;
81
99
  }, [enabled, visibleCount, total, scrollToBottom]);
82
100
  useEffect(() => {
@@ -89,13 +107,19 @@ function useSessionScroll(options) {
89
107
  setFollowing(false);
90
108
  }
91
109
  lastScrollTopRef.current = el.scrollTop;
92
- if (hasMoreRef.current && movedUp && isNearTop(el.scrollTop, topThreshold)) growOlderRef.current();
110
+ lastScrollHeightRef.current = el.scrollHeight;
111
+ if (movedUp && isNearTop(el.scrollTop, topThreshold)) loadOlder();
93
112
  };
94
113
  const onWheel = (event) => {
95
114
  if (event.deltaY >= 0) return;
96
115
  followingRef.current = false;
97
116
  setFollowing(false);
98
- if (hasMoreRef.current && isNearTop(el.scrollTop, topThreshold)) growOlderRef.current();
117
+ if (isNearTop(el.scrollTop, topThreshold)) loadOlder();
118
+ };
119
+ const loadOlder = () => {
120
+ var _a;
121
+ if (hasMoreRef.current) growOlderRef.current();
122
+ else (_a = onReachStartRef.current) == null ? void 0 : _a.call(onReachStartRef);
99
123
  };
100
124
  el.addEventListener("scroll", onScroll, { passive: true });
101
125
  el.addEventListener("wheel", onWheel, { passive: true });
@@ -1 +1 @@
1
- {"version":3,"file":"use-session-scroll.js","sources":["../../../src/data/ai/use-session-scroll.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState, type RefObject } from \"react\";\n\n// Progressive windowing for the SessionViewer log. A recorded session runs to\n// thousands of rows (many with syntax-highlighted code blocks), so rendering the\n// whole transcript blocks first paint. Instead we render the *newest* screenful,\n// keep the view pinned to the bottom (most recent activity), and backfill older\n// rows as the reader scrolls up — the classic reverse-infinite-scroll of a chat\n// log. The pure helpers below hold the windowing math; the hook wires the DOM\n// listeners that drive it. Load-more is driven off scroll position rather than an\n// IntersectionObserver so it stays reliable in backgrounded/hidden tabs (where the\n// browser pauses observer callbacks tied to the render loop).\n\n/** The last `visibleCount` of `items`, plus where the window starts and whether\n * older items remain above it. */\nexport function windowSlice<T>(\n items: readonly T[],\n visibleCount: number,\n): { items: T[]; startIndex: number; hasMore: boolean } {\n const clamped = Math.max(0, Math.min(visibleCount, items.length));\n const startIndex = items.length - clamped;\n return { items: items.slice(startIndex), startIndex, hasMore: startIndex > 0 };\n}\n\n/** Grow the window by `batchSize`, never past `total`. */\nexport function nextVisibleCount(current: number, batchSize: number, total: number): number {\n return Math.min(current + Math.max(1, batchSize), total);\n}\n\n/** True when the scroll position sits within `threshold` px of the bottom. */\nexport function isPinnedToBottom(\n metrics: { scrollTop: number; scrollHeight: number; clientHeight: number },\n threshold: number,\n): boolean {\n return metrics.scrollHeight - metrics.scrollTop - metrics.clientHeight <= threshold;\n}\n\n/** True when the scroll position sits within `threshold` px of the top. */\nexport function isNearTop(scrollTop: number, threshold: number): boolean {\n return scrollTop <= threshold;\n}\n\nexport interface SessionScrollOptions {\n /** Number of filtered events available to render. */\n total: number;\n /** Turns the whole mechanism on; when false the hook renders every event. */\n enabled: boolean;\n /** Rows rendered on first paint (and after a session change). */\n windowSize: number;\n /** Rows added each time older content is loaded. */\n batchSize: number;\n /** Changing this resets the window and re-pins to the bottom — pass a value\n * derived from the session identity, not the filter state. */\n resetKey: string;\n /** Distance (px) from the bottom still counted as \"pinned\". */\n bottomThreshold?: number;\n /** Distance (px) from the top at which older rows start loading. */\n topThreshold?: number;\n}\n\nexport interface SessionScrollState {\n scrollRef: RefObject<HTMLDivElement | null>;\n contentRef: RefObject<HTMLDivElement | null>;\n startIndex: number;\n hasMore: boolean;\n following: boolean;\n resumeFollowing: () => void;\n}\n\nexport function useSessionScroll(options: SessionScrollOptions): SessionScrollState {\n const { total, enabled, windowSize, batchSize, resetKey } = options;\n const bottomThreshold = options.bottomThreshold ?? 64;\n const topThreshold = options.topThreshold ?? 600;\n\n const scrollRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const followingRef = useRef(true);\n const lastScrollTopRef = useRef(0);\n const anchorHeightRef = useRef<number | null>(null);\n const scrollBottomPendingRef = useRef(true);\n const loadingOlderRef = useRef(false);\n const previousTotalRef = useRef({ resetKey, total });\n\n const [visibleCount, setVisibleCount] = useState(windowSize);\n const [following, setFollowing] = useState(true);\n const effectiveCount = enabled ? visibleCount : total;\n const startIndex = Math.max(0, total - effectiveCount);\n const hasMore = enabled && startIndex > 0;\n const hasMoreRef = useRef(hasMore);\n hasMoreRef.current = hasMore;\n\n const scrollToBottom = useCallback(() => {\n const el = scrollRef.current;\n if (el) {\n el.scrollTop = el.scrollHeight;\n lastScrollTopRef.current = el.scrollTop;\n }\n }, []);\n\n const resumeFollowing = useCallback(() => {\n followingRef.current = true;\n setFollowing(true);\n scrollToBottom();\n }, [scrollToBottom]);\n\n // Prepend an older batch, latching until the layout effect settles the prepend so\n // rapid scroll events don't stack multiple batches at once.\n const growOlder = useCallback(() => {\n if (loadingOlderRef.current || !hasMoreRef.current) return;\n loadingOlderRef.current = true;\n const el = scrollRef.current;\n anchorHeightRef.current = el ? el.scrollHeight : null;\n setVisibleCount((count) => nextVisibleCount(count, batchSize, total));\n }, [batchSize, total]);\n\n const growOlderRef = useRef(growOlder);\n growOlderRef.current = growOlder;\n\n // A new session (resetKey change) snaps back to the newest screenful and re-pins.\n useLayoutEffect(() => {\n if (!enabled) return;\n setVisibleCount(windowSize);\n followingRef.current = true;\n setFollowing(true);\n loadingOlderRef.current = false;\n scrollBottomPendingRef.current = true;\n }, [enabled, resetKey, windowSize]);\n\n useLayoutEffect(() => {\n const previous = previousTotalRef.current;\n previousTotalRef.current = { resetKey, total };\n if (enabled && previous.resetKey === resetKey && !followingRef.current && total > previous.total) {\n setVisibleCount((count) => count + total - previous.total);\n }\n }, [enabled, resetKey, total]);\n\n // After each commit, either snap to bottom (pending pin) or preserve the reader's\n // position when older rows were prepended (anchor), then release the load latch.\n useLayoutEffect(() => {\n const el = scrollRef.current;\n if (!enabled || !el) return;\n if (scrollBottomPendingRef.current) {\n scrollBottomPendingRef.current = false;\n anchorHeightRef.current = null;\n scrollToBottom();\n } else if (anchorHeightRef.current != null) {\n el.scrollTop += el.scrollHeight - anchorHeightRef.current;\n anchorHeightRef.current = null;\n lastScrollTopRef.current = el.scrollTop;\n } else if (followingRef.current) {\n scrollToBottom();\n }\n loadingOlderRef.current = false;\n }, [enabled, visibleCount, total, scrollToBottom]);\n\n // Track pinned-to-bottom, and load older rows as the reader nears the top.\n useEffect(() => {\n const el = scrollRef.current;\n if (!enabled || !el) return;\n const onScroll = () => {\n const movedUp = el.scrollTop < lastScrollTopRef.current;\n if (movedUp || !isPinnedToBottom(el, bottomThreshold)) {\n followingRef.current = false;\n setFollowing(false);\n }\n lastScrollTopRef.current = el.scrollTop;\n if (hasMoreRef.current && movedUp && isNearTop(el.scrollTop, topThreshold)) growOlderRef.current();\n };\n const onWheel = (event: WheelEvent) => {\n if (event.deltaY >= 0) return;\n followingRef.current = false;\n setFollowing(false);\n if (hasMoreRef.current && isNearTop(el.scrollTop, topThreshold)) growOlderRef.current();\n };\n el.addEventListener(\"scroll\", onScroll, { passive: true });\n el.addEventListener(\"wheel\", onWheel, { passive: true });\n return () => {\n el.removeEventListener(\"scroll\", onScroll);\n el.removeEventListener(\"wheel\", onWheel);\n };\n }, [enabled, bottomThreshold, topThreshold]);\n\n // Keep the newest row in view as rows grow (shiki highlighting settles after paint).\n useEffect(() => {\n const content = contentRef.current;\n if (!enabled || !content || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n if (followingRef.current) scrollToBottom();\n });\n observer.observe(content);\n return () => observer.disconnect();\n }, [enabled, scrollToBottom]);\n\n return { scrollRef, contentRef, startIndex, hasMore, following, resumeFollowing };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-session-scroll.js","sources":["../../../src/data/ai/use-session-scroll.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState, type RefObject } from \"react\";\n\n// Progressive windowing for the SessionViewer log. A recorded session runs to\n// thousands of rows (many with syntax-highlighted code blocks), so rendering the\n// whole transcript blocks first paint. Instead we render the *newest* screenful,\n// keep the view pinned to the bottom (most recent activity), and backfill older\n// rows as the reader scrolls up — the classic reverse-infinite-scroll of a chat\n// log. The pure helpers below hold the windowing math; the hook wires the DOM\n// listeners that drive it. Load-more is driven off scroll position rather than an\n// IntersectionObserver so it stays reliable in backgrounded/hidden tabs (where the\n// browser pauses observer callbacks tied to the render loop).\n\n/** The last `visibleCount` of `items`, plus where the window starts and whether\n * older items remain above it. */\nexport function windowSlice<T>(\n items: readonly T[],\n visibleCount: number,\n): { items: T[]; startIndex: number; hasMore: boolean } {\n const clamped = Math.max(0, Math.min(visibleCount, items.length));\n const startIndex = items.length - clamped;\n return { items: items.slice(startIndex), startIndex, hasMore: startIndex > 0 };\n}\n\n/** Grow the window by `batchSize`, never past `total`. */\nexport function nextVisibleCount(current: number, batchSize: number, total: number): number {\n return Math.min(current + Math.max(1, batchSize), total);\n}\n\n/** True when the scroll position sits within `threshold` px of the bottom. */\nexport function isPinnedToBottom(\n metrics: { scrollTop: number; scrollHeight: number; clientHeight: number },\n threshold: number,\n): boolean {\n return metrics.scrollHeight - metrics.scrollTop - metrics.clientHeight <= threshold;\n}\n\n/** True when the scroll position sits within `threshold` px of the top. */\nexport function isNearTop(scrollTop: number, threshold: number): boolean {\n return scrollTop <= threshold;\n}\n\nexport interface SessionScrollOptions {\n /** Number of filtered events available to render. */\n total: number;\n /** Turns the whole mechanism on; when false the hook renders every event. */\n enabled: boolean;\n /** Rows rendered on first paint (and after a session change). */\n windowSize: number;\n /** Rows added each time older content is loaded. */\n batchSize: number;\n /** Changing this resets the window and re-pins to the bottom — pass a value\n * derived from the session identity, not the filter state. */\n resetKey: string;\n /** Distance (px) from the bottom still counted as \"pinned\". */\n bottomThreshold?: number;\n /** Distance (px) from the top at which older rows start loading. */\n topThreshold?: number;\n /** Identity of the first item. When it changes under the same resetKey while\n * `total` grows, older items were prepended (a fetched page of history): the\n * window grows to include them and the reader's position is kept. */\n firstKey?: string;\n /** Called as the reader scrolls up near the top with every local item\n * already rendered — the cue to fetch older history. */\n onReachStart?: () => void;\n}\n\nexport interface SessionScrollState {\n scrollRef: RefObject<HTMLDivElement | null>;\n contentRef: RefObject<HTMLDivElement | null>;\n startIndex: number;\n hasMore: boolean;\n following: boolean;\n resumeFollowing: () => void;\n}\n\nexport function useSessionScroll(options: SessionScrollOptions): SessionScrollState {\n const { total, enabled, windowSize, batchSize, resetKey } = options;\n const bottomThreshold = options.bottomThreshold ?? 64;\n const topThreshold = options.topThreshold ?? 600;\n\n const scrollRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const followingRef = useRef(true);\n const lastScrollTopRef = useRef(0);\n const anchorHeightRef = useRef<number | null>(null);\n const scrollBottomPendingRef = useRef(true);\n const loadingOlderRef = useRef(false);\n const firstKey = options.firstKey;\n const previousTotalRef = useRef({ resetKey, total, firstKey });\n const lastScrollHeightRef = useRef<number | null>(null);\n const prependedRef = useRef(false);\n const onReachStartRef = useRef(options.onReachStart);\n onReachStartRef.current = options.onReachStart;\n\n const [visibleCount, setVisibleCount] = useState(windowSize);\n const [following, setFollowing] = useState(true);\n // A prepend grows the window during render, so the commit that mounts the\n // older rows is the one whose layout effect restores the reader's position.\n const [tracked, setTracked] = useState({ resetKey, total, firstKey });\n if (tracked.resetKey !== resetKey || tracked.total !== total || tracked.firstKey !== firstKey) {\n if (enabled && tracked.resetKey === resetKey && tracked.firstKey !== firstKey && total > tracked.total) {\n prependedRef.current = true;\n setVisibleCount((count) => count + total - tracked.total);\n }\n setTracked({ resetKey, total, firstKey });\n }\n const effectiveCount = enabled ? visibleCount : total;\n const startIndex = Math.max(0, total - effectiveCount);\n const hasMore = enabled && startIndex > 0;\n const hasMoreRef = useRef(hasMore);\n hasMoreRef.current = hasMore;\n\n const scrollToBottom = useCallback(() => {\n const el = scrollRef.current;\n if (el) {\n el.scrollTop = el.scrollHeight;\n lastScrollTopRef.current = el.scrollTop;\n }\n }, []);\n\n const resumeFollowing = useCallback(() => {\n followingRef.current = true;\n setFollowing(true);\n scrollToBottom();\n }, [scrollToBottom]);\n\n // Prepend an older batch, latching until the layout effect settles the prepend so\n // rapid scroll events don't stack multiple batches at once.\n const growOlder = useCallback(() => {\n if (loadingOlderRef.current || !hasMoreRef.current) return;\n loadingOlderRef.current = true;\n const el = scrollRef.current;\n anchorHeightRef.current = el ? el.scrollHeight : null;\n setVisibleCount((count) => nextVisibleCount(count, batchSize, total));\n }, [batchSize, total]);\n\n const growOlderRef = useRef(growOlder);\n growOlderRef.current = growOlder;\n\n // A new session (resetKey change) snaps back to the newest screenful and re-pins.\n useLayoutEffect(() => {\n if (!enabled) return;\n setVisibleCount(windowSize);\n followingRef.current = true;\n setFollowing(true);\n loadingOlderRef.current = false;\n scrollBottomPendingRef.current = true;\n }, [enabled, resetKey, windowSize]);\n\n useLayoutEffect(() => {\n const previous = previousTotalRef.current;\n previousTotalRef.current = { resetKey, total, firstKey };\n if (\n enabled &&\n previous.resetKey === resetKey &&\n previous.firstKey === firstKey &&\n !followingRef.current &&\n total > previous.total\n ) {\n setVisibleCount((count) => count + total - previous.total);\n }\n }, [enabled, resetKey, total, firstKey]);\n\n // After each commit, either snap to bottom (pending pin) or preserve the reader's\n // position when older rows were prepended (anchor), then release the load latch.\n useLayoutEffect(() => {\n const el = scrollRef.current;\n if (!enabled || !el) return;\n if (scrollBottomPendingRef.current) {\n scrollBottomPendingRef.current = false;\n anchorHeightRef.current = null;\n scrollToBottom();\n } else if (prependedRef.current && lastScrollHeightRef.current != null) {\n el.scrollTop += el.scrollHeight - lastScrollHeightRef.current;\n lastScrollTopRef.current = el.scrollTop;\n } else if (anchorHeightRef.current != null) {\n el.scrollTop += el.scrollHeight - anchorHeightRef.current;\n anchorHeightRef.current = null;\n lastScrollTopRef.current = el.scrollTop;\n } else if (followingRef.current) {\n scrollToBottom();\n }\n prependedRef.current = false;\n lastScrollHeightRef.current = el.scrollHeight;\n loadingOlderRef.current = false;\n }, [enabled, visibleCount, total, scrollToBottom]);\n\n // Track pinned-to-bottom, and load older rows as the reader nears the top.\n useEffect(() => {\n const el = scrollRef.current;\n if (!enabled || !el) return;\n const onScroll = () => {\n const movedUp = el.scrollTop < lastScrollTopRef.current;\n if (movedUp || !isPinnedToBottom(el, bottomThreshold)) {\n followingRef.current = false;\n setFollowing(false);\n }\n lastScrollTopRef.current = el.scrollTop;\n lastScrollHeightRef.current = el.scrollHeight;\n if (movedUp && isNearTop(el.scrollTop, topThreshold)) loadOlder();\n };\n const onWheel = (event: WheelEvent) => {\n if (event.deltaY >= 0) return;\n followingRef.current = false;\n setFollowing(false);\n if (isNearTop(el.scrollTop, topThreshold)) loadOlder();\n };\n // Rendered rows first; once every local row is mounted, ask for history.\n const loadOlder = () => {\n if (hasMoreRef.current) growOlderRef.current();\n else onReachStartRef.current?.();\n };\n el.addEventListener(\"scroll\", onScroll, { passive: true });\n el.addEventListener(\"wheel\", onWheel, { passive: true });\n return () => {\n el.removeEventListener(\"scroll\", onScroll);\n el.removeEventListener(\"wheel\", onWheel);\n };\n }, [enabled, bottomThreshold, topThreshold]);\n\n // Keep the newest row in view as rows grow (shiki highlighting settles after paint).\n useEffect(() => {\n const content = contentRef.current;\n if (!enabled || !content || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n if (followingRef.current) scrollToBottom();\n });\n observer.observe(content);\n return () => observer.disconnect();\n }, [enabled, scrollToBottom]);\n\n return { scrollRef, contentRef, startIndex, hasMore, following, resumeFollowing };\n}\n"],"names":[],"mappings":"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"}
@@ -352,7 +352,7 @@ function Chat({
352
352
  ...renderToolResult ? { renderToolResult } : {}
353
353
  }
354
354
  ),
355
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 p-4 pt-0", children: [
355
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-col gap-2 p-4 pt-0 pb-[max(1rem,env(safe-area-inset-bottom))]", children: [
356
356
  chips && chips.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Suggestion.Suggestions, { suggestions: chips, onSelect: pickSuggestion }),
357
357
  /* @__PURE__ */ jsxRuntime.jsx(
358
358
  PromptInput.PromptInput,
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.cjs","sources":["../../../src/data/chat/Chat.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport, type ChatTransport, type UIMessage } from \"ai\";\nimport { cn } from \"../../lib/utils\";\nimport { Conversation } from \"./Conversation\";\nimport { PromptInput } from \"./PromptInput\";\nimport { Suggestions } from \"./Suggestion\";\nimport { DEFAULT_REASONING_EFFORTS } from \"./effort-icons\";\nimport { defaultChatModelId } from \"./models\";\nimport {\n getChatSession,\n postToolApproval,\n type CaptainChatSession,\n} from \"./approval\";\nimport { isForkSeedMessage } from \"./fork-seed\";\nimport {\n hasStructuredRuntime,\n resolveChatRuntime,\n selectedChatModel,\n} from \"./Chat.runtime\";\nimport {\n createAttachmentUploadAdapter,\n type AttachmentLimits,\n type AttachmentUploadAdapter,\n} from \"./attachment-upload\";\nimport { toToolRenderRegistry } from \"./tool-render/registry\";\nimport { ToolRenderRegistryProvider } from \"./tool-render/context\";\nimport { ChatRuntimeToolbar } from \"./ChatRuntimeToolbar\";\nimport type { SpecRuntimeFamily } from \"../runtime/runtime-mode\";\nimport type {\n ToolRenderAdapter,\n ToolRenderRegistry,\n} from \"./tool-render/adapter\";\nimport type {\n ChatBudgetConfig,\n ChatModel,\n ChatMessageMetadata,\n ChatModelRuntime,\n ChatUsageSummary,\n ClaudePermissionMode,\n Suggestion,\n ToolMeta,\n ToolResultRenderer,\n} from \"./types\";\nimport {\n usageSnapshotFromMetadata,\n usageSnapshotFromSession,\n} from \"./usage-snapshot\";\n\n/** Assistant messages carry token usage + cost the backend rode on the finish\n * part's `messageMetadata`. */\ntype ChatUIMessage = UIMessage<ChatMessageMetadata>;\n\n// Composer seeds are keyed by a monotonic id so re-seeding the same text works.\n// Minted here rather than reusing a caller's `initialPrompt.id` so a host's id\n// space can never collide with one a suggestion pick produced.\nlet _draftId = 0;\nconst nextDraftId = () => ++_draftId;\n\nexport type ChatProps = {\n /** Endpoint that speaks the AI SDK v6 UI Message Stream protocol.\n * Ignored when `transport` is supplied. Defaults to \"/api/chat\". */\n api?: string;\n /** Model menu. When omitted and `modelsApi` is set, it is fetched. */\n models?: ChatModel[];\n /** Endpoint returning the model menu (ModelInfo[]). Defaults to\n * \"/api/chat/models\". Set to null to disable fetching. */\n modelsApi?: string | null;\n /** Initially selected model id; otherwise the first configured model. */\n defaultModel?: string;\n /** Controlled selected model id. */\n model?: string;\n /** Initially selected structured runtime. */\n defaultRuntime?: ChatModelRuntime;\n /** Controlled structured runtime. */\n runtime?: ChatModelRuntime;\n /** Runtime families and their host availability. */\n runtimeFamilies?: SpecRuntimeFamily[];\n /** Reasoning-effort options for capable models. */\n reasoningEfforts?: string[];\n /** Initially selected reasoning effort (\"\" = none). */\n defaultReasoningEffort?: string;\n /** Controlled selected reasoning effort (\"\" = none). */\n reasoningEffort?: string;\n /** Sampling temperature forwarded to the backend when set. */\n temperature?: number;\n /** Per-request budget forwarded to the backend when set. */\n budget?: ChatBudgetConfig;\n /** Claude Agent SDK permission mode forwarded to the backend when set. */\n permissionMode?: ClaudePermissionMode;\n /** Notified when the user changes the model. */\n onModelChange?: (id: string) => void;\n /** Notified when the user changes any runtime field. */\n onRuntimeChange?: (runtime: ChatModelRuntime) => void;\n /** Notified when the user changes reasoning effort. */\n onReasoningEffortChange?: (effort: string) => void;\n /** Notified when the user changes Claude permission mode. */\n onPermissionModeChange?: (mode: ClaudePermissionMode) => void;\n /** Notified after each assistant turn with a usage snapshot (tokens used out\n * of the model's context window + cumulative cost), for a usage gauge. */\n onUsage?: (usage: ChatUsageSummary) => void;\n /** Suggested prompts offered while the conversation is still empty. */\n suggestions?: Suggestion[];\n /** Prompts proposed for this particular open — e.g. the opener a host's \"Ask\n * AI\" button rendered for the thing being asked about. Shown in place of\n * `suggestions`, and unlike them they stay offered once the conversation has\n * started, because a host may propose them into a chat that is already\n * running. Cleared through `onProposedPromptsConsumed`. */\n proposedPrompts?: Suggestion[] | null;\n /** Called once the proposed prompts have been acted on — one was picked, or\n * the user sent a message of their own. */\n onProposedPromptsConsumed?: () => void;\n /** Enables file/image attachments. */\n enableAttachments?: boolean;\n /** Upload endpoint used before an attachment is added to a chat message. */\n attachmentsApi?: string;\n /** Custom durable upload adapter; overrides attachmentsApi. */\n attachmentUpload?: AttachmentUploadAdapter;\n attachmentLimits?: AttachmentLimits;\n /** Captain thread id used as the AI SDK chat id and forwarded in the body. */\n threadId?: string;\n /** Canonical Captain session endpoint used to hydrate messages and resolve\n * approvals. The session and approval ids are appended to this URL. */\n sessionsApi?: string | null;\n /** Prevents changing model/backend for an existing Captain session. Effort\n * and other per-turn generation settings remain editable. */\n runtimeLocked?: boolean;\n /** Reports the number of real (non-fork-seed) persisted/UI messages. */\n onMessageCountChange?: (count: number) => void;\n /** Reports the authoritative Captain session after detail hydration. */\n onSessionHydrated?: (session: CaptainChatSession) => void;\n /** Optional host renderer for recognized completed tool outputs. Takes\n * priority over `toolRenderers` on the output surface. */\n renderToolResult?: ToolResultRenderer;\n /** Tool catalog, used to resolve each call's input/output JSON Schema so\n * params and results render with real labels instead of raw JSON. */\n tools?: ToolMeta[];\n /** Domain tool renderers. Host adapters are matched before the built-in\n * heuristics, which stay as the floor. */\n toolRenderers?: ToolRenderAdapter[] | ToolRenderRegistry;\n /** Extra fields merged into every request body. */\n body?: Record<string, unknown>;\n /** Pre-built transport (e.g. a mock for stories/tests). */\n transport?: ChatTransport<UIMessage>;\n /** Initial messages to seed the conversation. */\n initialMessages?: UIMessage[];\n /** Prompt to prefill the composer with once. It is never submitted on the\n * user's behalf — they read it, edit it if they want, and press send. A new\n * `id` re-seeds even when the text repeats. */\n initialPrompt?: { id: number; text: string } | null;\n /** Called after `initialPrompt` has been placed in the composer. */\n onInitialPromptConsumed?: () => void;\n placeholder?: string;\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** Self-contained AI chat over a v6-compatible `/api/chat`: a streaming\n * conversation with model/effort selectors, suggestions, attachments,\n * per-message copy/regenerate, and human-in-the-loop tool approvals. The\n * backend owns model selection and tool execution; the selected model,\n * effort, permission mode and thread id are forwarded in the request body. */\nexport function Chat({\n api = \"/api/chat\",\n models: modelsProp,\n modelsApi = \"/api/chat/models\",\n defaultModel,\n model: controlledModel,\n defaultRuntime,\n runtime: controlledRuntime,\n runtimeFamilies,\n reasoningEfforts = DEFAULT_REASONING_EFFORTS,\n defaultReasoningEffort,\n reasoningEffort: controlledEffort,\n temperature,\n budget,\n permissionMode,\n onModelChange,\n onRuntimeChange,\n onReasoningEffortChange,\n onUsage,\n suggestions,\n proposedPrompts,\n onProposedPromptsConsumed,\n enableAttachments = false,\n attachmentsApi = \"/api/attachments\",\n attachmentUpload,\n attachmentLimits,\n threadId,\n sessionsApi = null,\n runtimeLocked = false,\n onMessageCountChange,\n onSessionHydrated,\n renderToolResult,\n tools,\n toolRenderers,\n body,\n transport,\n initialMessages,\n initialPrompt,\n onInitialPromptConsumed,\n placeholder,\n emptyState,\n className,\n}: ChatProps) {\n const [models, setModels] = useState<ChatModel[]>(modelsProp ?? []);\n const [internalRuntime, setInternalRuntime] = useState<ChatModelRuntime>(\n () =>\n controlledRuntime ??\n resolveChatRuntime({\n models: modelsProp ?? [],\n current: defaultRuntime,\n preferredModel:\n controlledModel ??\n (defaultRuntime\n ? undefined\n : (defaultModel ?? defaultChatModelId(modelsProp ?? []))),\n effort: controlledEffort ?? defaultReasoningEffort,\n temperature,\n reasoningEfforts,\n }),\n );\n const [usage, setUsage] = useState<ChatUsageSummary | null>(null);\n const [approvalError, setApprovalError] = useState<Error | undefined>();\n const [approvalsInFlight, setApprovalsInFlight] = useState(\n () => new Map<string | undefined, number>(),\n );\n const lastDefaultModel = useRef(defaultModel);\n const [draft, setDraft] = useState<{ id: number; text: string } | null>(null);\n const seededInitialPromptId = useRef<number | null>(null);\n const hadProposals = useRef(false);\n if (proposedPrompts?.length) hadProposals.current = true;\n const activeThreadRef = useRef(threadId);\n const onMessageCountChangeRef = useRef(onMessageCountChange);\n const onSessionHydratedRef = useRef(onSessionHydrated);\n activeThreadRef.current = threadId;\n onMessageCountChangeRef.current = onMessageCountChange;\n onSessionHydratedRef.current = onSessionHydrated;\n const runtime = controlledRuntime ?? internalRuntime;\n\n // Provided once here; <ToolCall> reads it from context, so no tool-render\n // props thread through Conversation/Message.\n const toolRegistry = useMemo(\n () => toToolRenderRegistry(toolRenderers, tools ? { tools } : {}),\n [toolRenderers, tools],\n );\n\n useEffect(() => {\n if (!modelsProp) return;\n setModels(modelsProp);\n }, [modelsProp]);\n\n // Fetch the model menu unless one was supplied or fetching is disabled.\n useEffect(() => {\n if (modelsProp || !modelsApi) return;\n let cancelled = false;\n fetch(modelsApi)\n .then((r) =>\n r.ok ? r.json() : Promise.reject(new Error(`models ${r.status}`)),\n )\n .then((data: ChatModel[]) => {\n if (cancelled) return;\n setModels(data);\n })\n .catch((err) =>\n console.warn(\"clicky-ui: failed to load chat models\", err),\n );\n return () => {\n cancelled = true;\n };\n }, [modelsProp, modelsApi]);\n\n useEffect(() => {\n if (controlledRuntime !== undefined) return;\n const defaultChanged =\n Boolean(defaultModel) && defaultModel !== lastDefaultModel.current;\n lastDefaultModel.current = defaultModel;\n setInternalRuntime((current) =>\n resolveChatRuntime({\n models,\n current,\n preferredModel:\n controlledModel ??\n (defaultChanged\n ? defaultModel\n : (selectedChatModel(models, current)?.id ??\n defaultModel ??\n defaultChatModelId(models))),\n effort: controlledEffort,\n temperature,\n reasoningEfforts,\n }),\n );\n }, [\n controlledEffort,\n controlledModel,\n controlledRuntime,\n defaultModel,\n models,\n reasoningEfforts,\n temperature,\n ]);\n\n const selectedModel = selectedChatModel(models, runtime);\n const structuredRuntime = hasStructuredRuntime(runtime, selectedModel);\n const resolvedAttachmentUpload = useMemo(\n () =>\n attachmentUpload ??\n createAttachmentUploadAdapter({ endpoint: attachmentsApi }),\n [attachmentUpload, attachmentsApi],\n );\n\n // A function body keeps the transport stable while always sending the latest\n // model/effort/runtime selections.\n const bodyRef = useRef<Record<string, unknown>>({});\n bodyRef.current = {\n ...body,\n ...(structuredRuntime\n ? { runtime }\n : {\n ...(runtime.model ? { model: runtime.model } : {}),\n ...(runtime.effort ? { reasoningEffort: runtime.effort } : {}),\n ...(runtime.temperature !== undefined\n ? { temperature: runtime.temperature }\n : {}),\n }),\n ...(budget && (budget.cost !== undefined || budget.maxTokens !== undefined)\n ? { budget }\n : {}),\n ...(permissionMode ? { permissionMode } : {}),\n ...(threadId ? { threadId } : {}),\n };\n\n const resolvedTransport = useMemo<ChatTransport<UIMessage>>(\n () =>\n transport ??\n new DefaultChatTransport({ api, body: () => bodyRef.current }),\n [transport, api],\n );\n\n const {\n messages,\n setMessages,\n sendMessage,\n regenerate,\n status,\n error,\n clearError,\n stop,\n } = useChat<ChatUIMessage>({\n transport: resolvedTransport,\n ...(threadId ? { id: threadId } : {}),\n ...(initialMessages\n ? { messages: initialMessages as ChatUIMessage[] }\n : {}),\n });\n const setMessagesRef = useRef(setMessages);\n setMessagesRef.current = setMessages;\n const hydratedUsageRef = useRef<ChatUsageSummary | null>(null);\n const hydratedAssistantRef = useRef<ChatUIMessage | undefined>(undefined);\n\n const applySession = useCallback((session: CaptainChatSession) => {\n const snapshot = usageSnapshotFromSession(session);\n hydratedUsageRef.current = snapshot;\n hydratedAssistantRef.current = [...session.messages].reverse().find((message) => message.role === \"assistant\") as ChatUIMessage | undefined;\n setUsage(snapshot);\n setMessagesRef.current(session.messages as ChatUIMessage[]);\n }, []);\n\n useEffect(() => {\n if (!sessionsApi || !threadId) return;\n let cancelled = false;\n setApprovalError(undefined);\n hydratedUsageRef.current = null;\n hydratedAssistantRef.current = undefined;\n setUsage(null);\n setMessagesRef.current([]);\n void getChatSession(sessionsApi, threadId)\n .then((session) => {\n if (cancelled) return;\n if (session.id !== threadId) {\n throw new Error(\n `Captain chat session response ID \"${session.id}\" does not match requested session \"${threadId}\".`,\n );\n }\n applySession(session);\n onSessionHydratedRef.current?.(session);\n })\n .catch((cause) => {\n if (cancelled) return;\n setApprovalError(\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n });\n return () => {\n cancelled = true;\n };\n }, [applySession, sessionsApi, threadId]);\n\n useEffect(() => {\n onMessageCountChangeRef.current?.(\n messages.filter((message) => !isForkSeedMessage(message)).length,\n );\n }, [messages]);\n\n // An initial prompt is a draft, not a turn: it lands in the composer and\n // waits for the user to send it. Unlike a send it needs no live transport and\n // no hydrated session, so it seeds immediately.\n useEffect(() => {\n if (!initialPrompt) return;\n if (seededInitialPromptId.current === initialPrompt.id) return;\n const text = initialPrompt.text.trim();\n if (!text) return;\n seededInitialPromptId.current = initialPrompt.id;\n setDraft({ id: nextDraftId(), text });\n onInitialPromptConsumed?.();\n }, [initialPrompt, onInitialPromptConsumed]);\n\n // Surface a usage snapshot after each settled assistant turn. The backend\n // rides usage/cost on the finish part's messageMetadata; we read it off the\n // last assistant message, preserving provider context independently of billing.\n const onUsageRef = useRef(onUsage);\n onUsageRef.current = onUsage;\n useEffect(() => {\n if (status !== \"ready\") return;\n const last = [...messages].reverse().find((m) => m.role === \"assistant\");\n if (hydratedUsageRef.current && last?.id === hydratedAssistantRef.current?.id && last?.metadata === hydratedAssistantRef.current?.metadata) {\n setUsage(hydratedUsageRef.current);\n onUsageRef.current?.(hydratedUsageRef.current);\n return;\n }\n const meta = last?.metadata;\n if (!meta) {\n setUsage(null);\n return;\n }\n const settledModel = meta.model\n ? selectedChatModel(models, { id: meta.model, model: meta.model })\n : undefined;\n const snapshot = usageSnapshotFromMetadata(meta, {\n modelLabel: settledModel?.label,\n messageCount: messages.length,\n });\n setUsage(snapshot);\n onUsageRef.current?.(snapshot);\n }, [messages, models, status]);\n\n const handleRuntimeChange = (next: ChatModelRuntime) => {\n if (controlledRuntime === undefined) setInternalRuntime(next);\n onRuntimeChange?.(next);\n const nextModel = selectedChatModel(models, next);\n const currentModelId = selectedModel?.id ?? runtime.id ?? runtime.model ?? \"\";\n const nextModelId = nextModel?.id ?? next.id ?? next.model ?? \"\";\n if (nextModelId !== currentModelId) {\n onModelChange?.(nextModelId);\n }\n if ((next.effort ?? \"\") !== (runtime.effort ?? \"\")) {\n onReasoningEffortChange?.(next.effort ?? \"\");\n }\n };\n\n const resolveToolApproval = async (\n id: string,\n approved: boolean,\n reason?: string,\n ) => {\n const approvalThreadId = threadId;\n setApprovalError(undefined);\n setApprovalsInFlight((counts) =>\n new Map(counts).set(approvalThreadId, (counts.get(approvalThreadId) ?? 0) + 1),\n );\n try {\n if (!sessionsApi) {\n throw new Error(\n \"A Captain sessions API is required to resolve tool approvals.\",\n );\n }\n const session = await postToolApproval({\n sessionsApi,\n sessionId: approvalThreadId ?? \"\",\n approvalId: id,\n approved,\n ...(reason ? { reason } : {}),\n });\n if (activeThreadRef.current !== approvalThreadId) return;\n if (session.id !== approvalThreadId) {\n throw new Error(\n `Captain chat session response ID \"${session.id}\" does not match active session \"${approvalThreadId ?? \"\"}\".`,\n );\n }\n applySession(session);\n } catch (cause) {\n if (activeThreadRef.current !== approvalThreadId) return;\n setApprovalError(\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n } finally {\n setApprovalsInFlight((counts) => {\n const count = counts.get(approvalThreadId);\n if (count === undefined || count < 1) {\n throw new Error(\"Tool approval completed without a pending request.\");\n }\n const next = new Map(counts);\n if (count === 1) next.delete(approvalThreadId);\n else next.set(approvalThreadId, count - 1);\n return next;\n });\n }\n };\n\n const toolbar = (\n <ChatRuntimeToolbar\n models={models}\n runtime={runtime}\n runtimeFamilies={runtimeFamilies}\n reasoningEfforts={reasoningEfforts}\n selectedModel={selectedModel}\n usage={usage}\n threadId={threadId}\n locked={runtimeLocked}\n onRuntimeChange={handleRuntimeChange}\n />\n );\n\n const empty =\n messages.length === 0 && emptyState ? (\n <div className=\"flex flex-col items-center gap-4\">{emptyState}</div>\n ) : undefined;\n\n // Prompts proposed for this open outrank the static suggestions and survive a\n // running conversation — a host can propose into a chat that already has one.\n // The static list is an empty-state affordance and retires once a turn exists;\n // it also never comes back after proposals were consumed, because falling back\n // to the application's generic prompts under a chat opened about one cell\n // reads as an unrelated non-sequitur.\n const chips = proposedPrompts?.length\n ? proposedPrompts\n : messages.length === 0 && !hadProposals.current\n ? suggestions\n : undefined;\n\n const pickSuggestion = (text: string) => {\n setDraft({ id: nextDraftId(), text });\n onProposedPromptsConsumed?.();\n };\n\n return (\n <ToolRenderRegistryProvider value={toolRegistry}>\n <div className={cn(\"flex h-full flex-col\", className)}>\n <Conversation\n messages={messages}\n status={status}\n error={approvalError ?? error}\n onClearError={() => {\n setApprovalError(undefined);\n clearError();\n }}\n {...(threadId ? { sessionId: threadId } : {})}\n {...(selectedModel?.id\n ? { model: selectedModel.id }\n : runtime.model\n ? { model: runtime.model }\n : {})}\n suppressWaiting={(approvalsInFlight.get(threadId) ?? 0) > 0}\n emptyState={empty}\n onRegenerate={(messageId) => void regenerate({ messageId })}\n onApprove={resolveToolApproval}\n {...(renderToolResult ? { renderToolResult } : {})}\n />\n <div className=\"flex flex-col gap-2 p-4 pt-0\">\n {chips && chips.length > 0 && (\n <Suggestions suggestions={chips} onSelect={pickSuggestion} />\n )}\n <PromptInput\n status={status}\n onStop={() => void stop()}\n placeholder={placeholder}\n enableAttachments={enableAttachments}\n attachmentUpload={resolvedAttachmentUpload}\n {...(selectedModel?.inputMediaTypes\n ? { acceptedMediaTypes: selectedModel.inputMediaTypes }\n : {})}\n {...(attachmentLimits ? { attachmentLimits } : {})}\n draft={draft}\n toolbar={toolbar}\n onSubmit={(text, files) => {\n onProposedPromptsConsumed?.();\n void sendMessage({ text, files });\n }}\n />\n </div>\n </div>\n </ToolRenderRegistryProvider>\n );\n}\n"],"names":["DEFAULT_REASONING_EFFORTS","attachmentUpload","models","useState","resolveChatRuntime","defaultChatModelId","useRef","useMemo","toToolRenderRegistry","useEffect","selectedChatModel","hasStructuredRuntime","createAttachmentUploadAdapter","DefaultChatTransport","useChat","useCallback","usageSnapshotFromSession","getChatSession","isForkSeedMessage","usageSnapshotFromMetadata","postToolApproval","jsx","ChatRuntimeToolbar","ToolRenderRegistryProvider","jsxs","cn","Conversation","Suggestions","PromptInput"],"mappings":"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"}
1
+ {"version":3,"file":"Chat.cjs","sources":["../../../src/data/chat/Chat.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport, type ChatTransport, type UIMessage } from \"ai\";\nimport { cn } from \"../../lib/utils\";\nimport { Conversation } from \"./Conversation\";\nimport { PromptInput } from \"./PromptInput\";\nimport { Suggestions } from \"./Suggestion\";\nimport { DEFAULT_REASONING_EFFORTS } from \"./effort-icons\";\nimport { defaultChatModelId } from \"./models\";\nimport {\n getChatSession,\n postToolApproval,\n type CaptainChatSession,\n} from \"./approval\";\nimport { isForkSeedMessage } from \"./fork-seed\";\nimport {\n hasStructuredRuntime,\n resolveChatRuntime,\n selectedChatModel,\n} from \"./Chat.runtime\";\nimport {\n createAttachmentUploadAdapter,\n type AttachmentLimits,\n type AttachmentUploadAdapter,\n} from \"./attachment-upload\";\nimport { toToolRenderRegistry } from \"./tool-render/registry\";\nimport { ToolRenderRegistryProvider } from \"./tool-render/context\";\nimport { ChatRuntimeToolbar } from \"./ChatRuntimeToolbar\";\nimport type { SpecRuntimeFamily } from \"../runtime/runtime-mode\";\nimport type {\n ToolRenderAdapter,\n ToolRenderRegistry,\n} from \"./tool-render/adapter\";\nimport type {\n ChatBudgetConfig,\n ChatModel,\n ChatMessageMetadata,\n ChatModelRuntime,\n ChatUsageSummary,\n ClaudePermissionMode,\n Suggestion,\n ToolMeta,\n ToolResultRenderer,\n} from \"./types\";\nimport {\n usageSnapshotFromMetadata,\n usageSnapshotFromSession,\n} from \"./usage-snapshot\";\n\n/** Assistant messages carry token usage + cost the backend rode on the finish\n * part's `messageMetadata`. */\ntype ChatUIMessage = UIMessage<ChatMessageMetadata>;\n\n// Composer seeds are keyed by a monotonic id so re-seeding the same text works.\n// Minted here rather than reusing a caller's `initialPrompt.id` so a host's id\n// space can never collide with one a suggestion pick produced.\nlet _draftId = 0;\nconst nextDraftId = () => ++_draftId;\n\nexport type ChatProps = {\n /** Endpoint that speaks the AI SDK v6 UI Message Stream protocol.\n * Ignored when `transport` is supplied. Defaults to \"/api/chat\". */\n api?: string;\n /** Model menu. When omitted and `modelsApi` is set, it is fetched. */\n models?: ChatModel[];\n /** Endpoint returning the model menu (ModelInfo[]). Defaults to\n * \"/api/chat/models\". Set to null to disable fetching. */\n modelsApi?: string | null;\n /** Initially selected model id; otherwise the first configured model. */\n defaultModel?: string;\n /** Controlled selected model id. */\n model?: string;\n /** Initially selected structured runtime. */\n defaultRuntime?: ChatModelRuntime;\n /** Controlled structured runtime. */\n runtime?: ChatModelRuntime;\n /** Runtime families and their host availability. */\n runtimeFamilies?: SpecRuntimeFamily[];\n /** Reasoning-effort options for capable models. */\n reasoningEfforts?: string[];\n /** Initially selected reasoning effort (\"\" = none). */\n defaultReasoningEffort?: string;\n /** Controlled selected reasoning effort (\"\" = none). */\n reasoningEffort?: string;\n /** Sampling temperature forwarded to the backend when set. */\n temperature?: number;\n /** Per-request budget forwarded to the backend when set. */\n budget?: ChatBudgetConfig;\n /** Claude Agent SDK permission mode forwarded to the backend when set. */\n permissionMode?: ClaudePermissionMode;\n /** Notified when the user changes the model. */\n onModelChange?: (id: string) => void;\n /** Notified when the user changes any runtime field. */\n onRuntimeChange?: (runtime: ChatModelRuntime) => void;\n /** Notified when the user changes reasoning effort. */\n onReasoningEffortChange?: (effort: string) => void;\n /** Notified when the user changes Claude permission mode. */\n onPermissionModeChange?: (mode: ClaudePermissionMode) => void;\n /** Notified after each assistant turn with a usage snapshot (tokens used out\n * of the model's context window + cumulative cost), for a usage gauge. */\n onUsage?: (usage: ChatUsageSummary) => void;\n /** Suggested prompts offered while the conversation is still empty. */\n suggestions?: Suggestion[];\n /** Prompts proposed for this particular open — e.g. the opener a host's \"Ask\n * AI\" button rendered for the thing being asked about. Shown in place of\n * `suggestions`, and unlike them they stay offered once the conversation has\n * started, because a host may propose them into a chat that is already\n * running. Cleared through `onProposedPromptsConsumed`. */\n proposedPrompts?: Suggestion[] | null;\n /** Called once the proposed prompts have been acted on — one was picked, or\n * the user sent a message of their own. */\n onProposedPromptsConsumed?: () => void;\n /** Enables file/image attachments. */\n enableAttachments?: boolean;\n /** Upload endpoint used before an attachment is added to a chat message. */\n attachmentsApi?: string;\n /** Custom durable upload adapter; overrides attachmentsApi. */\n attachmentUpload?: AttachmentUploadAdapter;\n attachmentLimits?: AttachmentLimits;\n /** Captain thread id used as the AI SDK chat id and forwarded in the body. */\n threadId?: string;\n /** Canonical Captain session endpoint used to hydrate messages and resolve\n * approvals. The session and approval ids are appended to this URL. */\n sessionsApi?: string | null;\n /** Prevents changing model/backend for an existing Captain session. Effort\n * and other per-turn generation settings remain editable. */\n runtimeLocked?: boolean;\n /** Reports the number of real (non-fork-seed) persisted/UI messages. */\n onMessageCountChange?: (count: number) => void;\n /** Reports the authoritative Captain session after detail hydration. */\n onSessionHydrated?: (session: CaptainChatSession) => void;\n /** Optional host renderer for recognized completed tool outputs. Takes\n * priority over `toolRenderers` on the output surface. */\n renderToolResult?: ToolResultRenderer;\n /** Tool catalog, used to resolve each call's input/output JSON Schema so\n * params and results render with real labels instead of raw JSON. */\n tools?: ToolMeta[];\n /** Domain tool renderers. Host adapters are matched before the built-in\n * heuristics, which stay as the floor. */\n toolRenderers?: ToolRenderAdapter[] | ToolRenderRegistry;\n /** Extra fields merged into every request body. */\n body?: Record<string, unknown>;\n /** Pre-built transport (e.g. a mock for stories/tests). */\n transport?: ChatTransport<UIMessage>;\n /** Initial messages to seed the conversation. */\n initialMessages?: UIMessage[];\n /** Prompt to prefill the composer with once. It is never submitted on the\n * user's behalf — they read it, edit it if they want, and press send. A new\n * `id` re-seeds even when the text repeats. */\n initialPrompt?: { id: number; text: string } | null;\n /** Called after `initialPrompt` has been placed in the composer. */\n onInitialPromptConsumed?: () => void;\n placeholder?: string;\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** Self-contained AI chat over a v6-compatible `/api/chat`: a streaming\n * conversation with model/effort selectors, suggestions, attachments,\n * per-message copy/regenerate, and human-in-the-loop tool approvals. The\n * backend owns model selection and tool execution; the selected model,\n * effort, permission mode and thread id are forwarded in the request body. */\nexport function Chat({\n api = \"/api/chat\",\n models: modelsProp,\n modelsApi = \"/api/chat/models\",\n defaultModel,\n model: controlledModel,\n defaultRuntime,\n runtime: controlledRuntime,\n runtimeFamilies,\n reasoningEfforts = DEFAULT_REASONING_EFFORTS,\n defaultReasoningEffort,\n reasoningEffort: controlledEffort,\n temperature,\n budget,\n permissionMode,\n onModelChange,\n onRuntimeChange,\n onReasoningEffortChange,\n onUsage,\n suggestions,\n proposedPrompts,\n onProposedPromptsConsumed,\n enableAttachments = false,\n attachmentsApi = \"/api/attachments\",\n attachmentUpload,\n attachmentLimits,\n threadId,\n sessionsApi = null,\n runtimeLocked = false,\n onMessageCountChange,\n onSessionHydrated,\n renderToolResult,\n tools,\n toolRenderers,\n body,\n transport,\n initialMessages,\n initialPrompt,\n onInitialPromptConsumed,\n placeholder,\n emptyState,\n className,\n}: ChatProps) {\n const [models, setModels] = useState<ChatModel[]>(modelsProp ?? []);\n const [internalRuntime, setInternalRuntime] = useState<ChatModelRuntime>(\n () =>\n controlledRuntime ??\n resolveChatRuntime({\n models: modelsProp ?? [],\n current: defaultRuntime,\n preferredModel:\n controlledModel ??\n (defaultRuntime\n ? undefined\n : (defaultModel ?? defaultChatModelId(modelsProp ?? []))),\n effort: controlledEffort ?? defaultReasoningEffort,\n temperature,\n reasoningEfforts,\n }),\n );\n const [usage, setUsage] = useState<ChatUsageSummary | null>(null);\n const [approvalError, setApprovalError] = useState<Error | undefined>();\n const [approvalsInFlight, setApprovalsInFlight] = useState(\n () => new Map<string | undefined, number>(),\n );\n const lastDefaultModel = useRef(defaultModel);\n const [draft, setDraft] = useState<{ id: number; text: string } | null>(null);\n const seededInitialPromptId = useRef<number | null>(null);\n const hadProposals = useRef(false);\n if (proposedPrompts?.length) hadProposals.current = true;\n const activeThreadRef = useRef(threadId);\n const onMessageCountChangeRef = useRef(onMessageCountChange);\n const onSessionHydratedRef = useRef(onSessionHydrated);\n activeThreadRef.current = threadId;\n onMessageCountChangeRef.current = onMessageCountChange;\n onSessionHydratedRef.current = onSessionHydrated;\n const runtime = controlledRuntime ?? internalRuntime;\n\n // Provided once here; <ToolCall> reads it from context, so no tool-render\n // props thread through Conversation/Message.\n const toolRegistry = useMemo(\n () => toToolRenderRegistry(toolRenderers, tools ? { tools } : {}),\n [toolRenderers, tools],\n );\n\n useEffect(() => {\n if (!modelsProp) return;\n setModels(modelsProp);\n }, [modelsProp]);\n\n // Fetch the model menu unless one was supplied or fetching is disabled.\n useEffect(() => {\n if (modelsProp || !modelsApi) return;\n let cancelled = false;\n fetch(modelsApi)\n .then((r) =>\n r.ok ? r.json() : Promise.reject(new Error(`models ${r.status}`)),\n )\n .then((data: ChatModel[]) => {\n if (cancelled) return;\n setModels(data);\n })\n .catch((err) =>\n console.warn(\"clicky-ui: failed to load chat models\", err),\n );\n return () => {\n cancelled = true;\n };\n }, [modelsProp, modelsApi]);\n\n useEffect(() => {\n if (controlledRuntime !== undefined) return;\n const defaultChanged =\n Boolean(defaultModel) && defaultModel !== lastDefaultModel.current;\n lastDefaultModel.current = defaultModel;\n setInternalRuntime((current) =>\n resolveChatRuntime({\n models,\n current,\n preferredModel:\n controlledModel ??\n (defaultChanged\n ? defaultModel\n : (selectedChatModel(models, current)?.id ??\n defaultModel ??\n defaultChatModelId(models))),\n effort: controlledEffort,\n temperature,\n reasoningEfforts,\n }),\n );\n }, [\n controlledEffort,\n controlledModel,\n controlledRuntime,\n defaultModel,\n models,\n reasoningEfforts,\n temperature,\n ]);\n\n const selectedModel = selectedChatModel(models, runtime);\n const structuredRuntime = hasStructuredRuntime(runtime, selectedModel);\n const resolvedAttachmentUpload = useMemo(\n () =>\n attachmentUpload ??\n createAttachmentUploadAdapter({ endpoint: attachmentsApi }),\n [attachmentUpload, attachmentsApi],\n );\n\n // A function body keeps the transport stable while always sending the latest\n // model/effort/runtime selections.\n const bodyRef = useRef<Record<string, unknown>>({});\n bodyRef.current = {\n ...body,\n ...(structuredRuntime\n ? { runtime }\n : {\n ...(runtime.model ? { model: runtime.model } : {}),\n ...(runtime.effort ? { reasoningEffort: runtime.effort } : {}),\n ...(runtime.temperature !== undefined\n ? { temperature: runtime.temperature }\n : {}),\n }),\n ...(budget && (budget.cost !== undefined || budget.maxTokens !== undefined)\n ? { budget }\n : {}),\n ...(permissionMode ? { permissionMode } : {}),\n ...(threadId ? { threadId } : {}),\n };\n\n const resolvedTransport = useMemo<ChatTransport<UIMessage>>(\n () =>\n transport ??\n new DefaultChatTransport({ api, body: () => bodyRef.current }),\n [transport, api],\n );\n\n const {\n messages,\n setMessages,\n sendMessage,\n regenerate,\n status,\n error,\n clearError,\n stop,\n } = useChat<ChatUIMessage>({\n transport: resolvedTransport,\n ...(threadId ? { id: threadId } : {}),\n ...(initialMessages\n ? { messages: initialMessages as ChatUIMessage[] }\n : {}),\n });\n const setMessagesRef = useRef(setMessages);\n setMessagesRef.current = setMessages;\n const hydratedUsageRef = useRef<ChatUsageSummary | null>(null);\n const hydratedAssistantRef = useRef<ChatUIMessage | undefined>(undefined);\n\n const applySession = useCallback((session: CaptainChatSession) => {\n const snapshot = usageSnapshotFromSession(session);\n hydratedUsageRef.current = snapshot;\n hydratedAssistantRef.current = [...session.messages].reverse().find((message) => message.role === \"assistant\") as ChatUIMessage | undefined;\n setUsage(snapshot);\n setMessagesRef.current(session.messages as ChatUIMessage[]);\n }, []);\n\n useEffect(() => {\n if (!sessionsApi || !threadId) return;\n let cancelled = false;\n setApprovalError(undefined);\n hydratedUsageRef.current = null;\n hydratedAssistantRef.current = undefined;\n setUsage(null);\n setMessagesRef.current([]);\n void getChatSession(sessionsApi, threadId)\n .then((session) => {\n if (cancelled) return;\n if (session.id !== threadId) {\n throw new Error(\n `Captain chat session response ID \"${session.id}\" does not match requested session \"${threadId}\".`,\n );\n }\n applySession(session);\n onSessionHydratedRef.current?.(session);\n })\n .catch((cause) => {\n if (cancelled) return;\n setApprovalError(\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n });\n return () => {\n cancelled = true;\n };\n }, [applySession, sessionsApi, threadId]);\n\n useEffect(() => {\n onMessageCountChangeRef.current?.(\n messages.filter((message) => !isForkSeedMessage(message)).length,\n );\n }, [messages]);\n\n // An initial prompt is a draft, not a turn: it lands in the composer and\n // waits for the user to send it. Unlike a send it needs no live transport and\n // no hydrated session, so it seeds immediately.\n useEffect(() => {\n if (!initialPrompt) return;\n if (seededInitialPromptId.current === initialPrompt.id) return;\n const text = initialPrompt.text.trim();\n if (!text) return;\n seededInitialPromptId.current = initialPrompt.id;\n setDraft({ id: nextDraftId(), text });\n onInitialPromptConsumed?.();\n }, [initialPrompt, onInitialPromptConsumed]);\n\n // Surface a usage snapshot after each settled assistant turn. The backend\n // rides usage/cost on the finish part's messageMetadata; we read it off the\n // last assistant message, preserving provider context independently of billing.\n const onUsageRef = useRef(onUsage);\n onUsageRef.current = onUsage;\n useEffect(() => {\n if (status !== \"ready\") return;\n const last = [...messages].reverse().find((m) => m.role === \"assistant\");\n if (hydratedUsageRef.current && last?.id === hydratedAssistantRef.current?.id && last?.metadata === hydratedAssistantRef.current?.metadata) {\n setUsage(hydratedUsageRef.current);\n onUsageRef.current?.(hydratedUsageRef.current);\n return;\n }\n const meta = last?.metadata;\n if (!meta) {\n setUsage(null);\n return;\n }\n const settledModel = meta.model\n ? selectedChatModel(models, { id: meta.model, model: meta.model })\n : undefined;\n const snapshot = usageSnapshotFromMetadata(meta, {\n modelLabel: settledModel?.label,\n messageCount: messages.length,\n });\n setUsage(snapshot);\n onUsageRef.current?.(snapshot);\n }, [messages, models, status]);\n\n const handleRuntimeChange = (next: ChatModelRuntime) => {\n if (controlledRuntime === undefined) setInternalRuntime(next);\n onRuntimeChange?.(next);\n const nextModel = selectedChatModel(models, next);\n const currentModelId = selectedModel?.id ?? runtime.id ?? runtime.model ?? \"\";\n const nextModelId = nextModel?.id ?? next.id ?? next.model ?? \"\";\n if (nextModelId !== currentModelId) {\n onModelChange?.(nextModelId);\n }\n if ((next.effort ?? \"\") !== (runtime.effort ?? \"\")) {\n onReasoningEffortChange?.(next.effort ?? \"\");\n }\n };\n\n const resolveToolApproval = async (\n id: string,\n approved: boolean,\n reason?: string,\n ) => {\n const approvalThreadId = threadId;\n setApprovalError(undefined);\n setApprovalsInFlight((counts) =>\n new Map(counts).set(approvalThreadId, (counts.get(approvalThreadId) ?? 0) + 1),\n );\n try {\n if (!sessionsApi) {\n throw new Error(\n \"A Captain sessions API is required to resolve tool approvals.\",\n );\n }\n const session = await postToolApproval({\n sessionsApi,\n sessionId: approvalThreadId ?? \"\",\n approvalId: id,\n approved,\n ...(reason ? { reason } : {}),\n });\n if (activeThreadRef.current !== approvalThreadId) return;\n if (session.id !== approvalThreadId) {\n throw new Error(\n `Captain chat session response ID \"${session.id}\" does not match active session \"${approvalThreadId ?? \"\"}\".`,\n );\n }\n applySession(session);\n } catch (cause) {\n if (activeThreadRef.current !== approvalThreadId) return;\n setApprovalError(\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n } finally {\n setApprovalsInFlight((counts) => {\n const count = counts.get(approvalThreadId);\n if (count === undefined || count < 1) {\n throw new Error(\"Tool approval completed without a pending request.\");\n }\n const next = new Map(counts);\n if (count === 1) next.delete(approvalThreadId);\n else next.set(approvalThreadId, count - 1);\n return next;\n });\n }\n };\n\n const toolbar = (\n <ChatRuntimeToolbar\n models={models}\n runtime={runtime}\n runtimeFamilies={runtimeFamilies}\n reasoningEfforts={reasoningEfforts}\n selectedModel={selectedModel}\n usage={usage}\n threadId={threadId}\n locked={runtimeLocked}\n onRuntimeChange={handleRuntimeChange}\n />\n );\n\n const empty =\n messages.length === 0 && emptyState ? (\n <div className=\"flex flex-col items-center gap-4\">{emptyState}</div>\n ) : undefined;\n\n // Prompts proposed for this open outrank the static suggestions and survive a\n // running conversation — a host can propose into a chat that already has one.\n // The static list is an empty-state affordance and retires once a turn exists;\n // it also never comes back after proposals were consumed, because falling back\n // to the application's generic prompts under a chat opened about one cell\n // reads as an unrelated non-sequitur.\n const chips = proposedPrompts?.length\n ? proposedPrompts\n : messages.length === 0 && !hadProposals.current\n ? suggestions\n : undefined;\n\n const pickSuggestion = (text: string) => {\n setDraft({ id: nextDraftId(), text });\n onProposedPromptsConsumed?.();\n };\n\n return (\n <ToolRenderRegistryProvider value={toolRegistry}>\n <div className={cn(\"flex h-full flex-col\", className)}>\n <Conversation\n messages={messages}\n status={status}\n error={approvalError ?? error}\n onClearError={() => {\n setApprovalError(undefined);\n clearError();\n }}\n {...(threadId ? { sessionId: threadId } : {})}\n {...(selectedModel?.id\n ? { model: selectedModel.id }\n : runtime.model\n ? { model: runtime.model }\n : {})}\n suppressWaiting={(approvalsInFlight.get(threadId) ?? 0) > 0}\n emptyState={empty}\n onRegenerate={(messageId) => void regenerate({ messageId })}\n onApprove={resolveToolApproval}\n {...(renderToolResult ? { renderToolResult } : {})}\n />\n <div className=\"flex shrink-0 flex-col gap-2 p-4 pt-0 pb-[max(1rem,env(safe-area-inset-bottom))]\">\n {chips && chips.length > 0 && (\n <Suggestions suggestions={chips} onSelect={pickSuggestion} />\n )}\n <PromptInput\n status={status}\n onStop={() => void stop()}\n placeholder={placeholder}\n enableAttachments={enableAttachments}\n attachmentUpload={resolvedAttachmentUpload}\n {...(selectedModel?.inputMediaTypes\n ? { acceptedMediaTypes: selectedModel.inputMediaTypes }\n : {})}\n {...(attachmentLimits ? { attachmentLimits } : {})}\n draft={draft}\n toolbar={toolbar}\n onSubmit={(text, files) => {\n onProposedPromptsConsumed?.();\n void sendMessage({ text, files });\n }}\n />\n </div>\n </div>\n </ToolRenderRegistryProvider>\n );\n}\n"],"names":["DEFAULT_REASONING_EFFORTS","attachmentUpload","models","useState","resolveChatRuntime","defaultChatModelId","useRef","useMemo","toToolRenderRegistry","useEffect","selectedChatModel","hasStructuredRuntime","createAttachmentUploadAdapter","DefaultChatTransport","useChat","useCallback","usageSnapshotFromSession","getChatSession","isForkSeedMessage","usageSnapshotFromMetadata","postToolApproval","jsx","ChatRuntimeToolbar","ToolRenderRegistryProvider","jsxs","cn","Conversation","Suggestions","PromptInput"],"mappings":"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"}
@@ -350,7 +350,7 @@ function Chat({
350
350
  ...renderToolResult ? { renderToolResult } : {}
351
351
  }
352
352
  ),
353
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 p-4 pt-0", children: [
353
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col gap-2 p-4 pt-0 pb-[max(1rem,env(safe-area-inset-bottom))]", children: [
354
354
  chips && chips.length > 0 && /* @__PURE__ */ jsx(Suggestions, { suggestions: chips, onSelect: pickSuggestion }),
355
355
  /* @__PURE__ */ jsx(
356
356
  PromptInput,
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.js","sources":["../../../src/data/chat/Chat.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport, type ChatTransport, type UIMessage } from \"ai\";\nimport { cn } from \"../../lib/utils\";\nimport { Conversation } from \"./Conversation\";\nimport { PromptInput } from \"./PromptInput\";\nimport { Suggestions } from \"./Suggestion\";\nimport { DEFAULT_REASONING_EFFORTS } from \"./effort-icons\";\nimport { defaultChatModelId } from \"./models\";\nimport {\n getChatSession,\n postToolApproval,\n type CaptainChatSession,\n} from \"./approval\";\nimport { isForkSeedMessage } from \"./fork-seed\";\nimport {\n hasStructuredRuntime,\n resolveChatRuntime,\n selectedChatModel,\n} from \"./Chat.runtime\";\nimport {\n createAttachmentUploadAdapter,\n type AttachmentLimits,\n type AttachmentUploadAdapter,\n} from \"./attachment-upload\";\nimport { toToolRenderRegistry } from \"./tool-render/registry\";\nimport { ToolRenderRegistryProvider } from \"./tool-render/context\";\nimport { ChatRuntimeToolbar } from \"./ChatRuntimeToolbar\";\nimport type { SpecRuntimeFamily } from \"../runtime/runtime-mode\";\nimport type {\n ToolRenderAdapter,\n ToolRenderRegistry,\n} from \"./tool-render/adapter\";\nimport type {\n ChatBudgetConfig,\n ChatModel,\n ChatMessageMetadata,\n ChatModelRuntime,\n ChatUsageSummary,\n ClaudePermissionMode,\n Suggestion,\n ToolMeta,\n ToolResultRenderer,\n} from \"./types\";\nimport {\n usageSnapshotFromMetadata,\n usageSnapshotFromSession,\n} from \"./usage-snapshot\";\n\n/** Assistant messages carry token usage + cost the backend rode on the finish\n * part's `messageMetadata`. */\ntype ChatUIMessage = UIMessage<ChatMessageMetadata>;\n\n// Composer seeds are keyed by a monotonic id so re-seeding the same text works.\n// Minted here rather than reusing a caller's `initialPrompt.id` so a host's id\n// space can never collide with one a suggestion pick produced.\nlet _draftId = 0;\nconst nextDraftId = () => ++_draftId;\n\nexport type ChatProps = {\n /** Endpoint that speaks the AI SDK v6 UI Message Stream protocol.\n * Ignored when `transport` is supplied. Defaults to \"/api/chat\". */\n api?: string;\n /** Model menu. When omitted and `modelsApi` is set, it is fetched. */\n models?: ChatModel[];\n /** Endpoint returning the model menu (ModelInfo[]). Defaults to\n * \"/api/chat/models\". Set to null to disable fetching. */\n modelsApi?: string | null;\n /** Initially selected model id; otherwise the first configured model. */\n defaultModel?: string;\n /** Controlled selected model id. */\n model?: string;\n /** Initially selected structured runtime. */\n defaultRuntime?: ChatModelRuntime;\n /** Controlled structured runtime. */\n runtime?: ChatModelRuntime;\n /** Runtime families and their host availability. */\n runtimeFamilies?: SpecRuntimeFamily[];\n /** Reasoning-effort options for capable models. */\n reasoningEfforts?: string[];\n /** Initially selected reasoning effort (\"\" = none). */\n defaultReasoningEffort?: string;\n /** Controlled selected reasoning effort (\"\" = none). */\n reasoningEffort?: string;\n /** Sampling temperature forwarded to the backend when set. */\n temperature?: number;\n /** Per-request budget forwarded to the backend when set. */\n budget?: ChatBudgetConfig;\n /** Claude Agent SDK permission mode forwarded to the backend when set. */\n permissionMode?: ClaudePermissionMode;\n /** Notified when the user changes the model. */\n onModelChange?: (id: string) => void;\n /** Notified when the user changes any runtime field. */\n onRuntimeChange?: (runtime: ChatModelRuntime) => void;\n /** Notified when the user changes reasoning effort. */\n onReasoningEffortChange?: (effort: string) => void;\n /** Notified when the user changes Claude permission mode. */\n onPermissionModeChange?: (mode: ClaudePermissionMode) => void;\n /** Notified after each assistant turn with a usage snapshot (tokens used out\n * of the model's context window + cumulative cost), for a usage gauge. */\n onUsage?: (usage: ChatUsageSummary) => void;\n /** Suggested prompts offered while the conversation is still empty. */\n suggestions?: Suggestion[];\n /** Prompts proposed for this particular open — e.g. the opener a host's \"Ask\n * AI\" button rendered for the thing being asked about. Shown in place of\n * `suggestions`, and unlike them they stay offered once the conversation has\n * started, because a host may propose them into a chat that is already\n * running. Cleared through `onProposedPromptsConsumed`. */\n proposedPrompts?: Suggestion[] | null;\n /** Called once the proposed prompts have been acted on — one was picked, or\n * the user sent a message of their own. */\n onProposedPromptsConsumed?: () => void;\n /** Enables file/image attachments. */\n enableAttachments?: boolean;\n /** Upload endpoint used before an attachment is added to a chat message. */\n attachmentsApi?: string;\n /** Custom durable upload adapter; overrides attachmentsApi. */\n attachmentUpload?: AttachmentUploadAdapter;\n attachmentLimits?: AttachmentLimits;\n /** Captain thread id used as the AI SDK chat id and forwarded in the body. */\n threadId?: string;\n /** Canonical Captain session endpoint used to hydrate messages and resolve\n * approvals. The session and approval ids are appended to this URL. */\n sessionsApi?: string | null;\n /** Prevents changing model/backend for an existing Captain session. Effort\n * and other per-turn generation settings remain editable. */\n runtimeLocked?: boolean;\n /** Reports the number of real (non-fork-seed) persisted/UI messages. */\n onMessageCountChange?: (count: number) => void;\n /** Reports the authoritative Captain session after detail hydration. */\n onSessionHydrated?: (session: CaptainChatSession) => void;\n /** Optional host renderer for recognized completed tool outputs. Takes\n * priority over `toolRenderers` on the output surface. */\n renderToolResult?: ToolResultRenderer;\n /** Tool catalog, used to resolve each call's input/output JSON Schema so\n * params and results render with real labels instead of raw JSON. */\n tools?: ToolMeta[];\n /** Domain tool renderers. Host adapters are matched before the built-in\n * heuristics, which stay as the floor. */\n toolRenderers?: ToolRenderAdapter[] | ToolRenderRegistry;\n /** Extra fields merged into every request body. */\n body?: Record<string, unknown>;\n /** Pre-built transport (e.g. a mock for stories/tests). */\n transport?: ChatTransport<UIMessage>;\n /** Initial messages to seed the conversation. */\n initialMessages?: UIMessage[];\n /** Prompt to prefill the composer with once. It is never submitted on the\n * user's behalf — they read it, edit it if they want, and press send. A new\n * `id` re-seeds even when the text repeats. */\n initialPrompt?: { id: number; text: string } | null;\n /** Called after `initialPrompt` has been placed in the composer. */\n onInitialPromptConsumed?: () => void;\n placeholder?: string;\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** Self-contained AI chat over a v6-compatible `/api/chat`: a streaming\n * conversation with model/effort selectors, suggestions, attachments,\n * per-message copy/regenerate, and human-in-the-loop tool approvals. The\n * backend owns model selection and tool execution; the selected model,\n * effort, permission mode and thread id are forwarded in the request body. */\nexport function Chat({\n api = \"/api/chat\",\n models: modelsProp,\n modelsApi = \"/api/chat/models\",\n defaultModel,\n model: controlledModel,\n defaultRuntime,\n runtime: controlledRuntime,\n runtimeFamilies,\n reasoningEfforts = DEFAULT_REASONING_EFFORTS,\n defaultReasoningEffort,\n reasoningEffort: controlledEffort,\n temperature,\n budget,\n permissionMode,\n onModelChange,\n onRuntimeChange,\n onReasoningEffortChange,\n onUsage,\n suggestions,\n proposedPrompts,\n onProposedPromptsConsumed,\n enableAttachments = false,\n attachmentsApi = \"/api/attachments\",\n attachmentUpload,\n attachmentLimits,\n threadId,\n sessionsApi = null,\n runtimeLocked = false,\n onMessageCountChange,\n onSessionHydrated,\n renderToolResult,\n tools,\n toolRenderers,\n body,\n transport,\n initialMessages,\n initialPrompt,\n onInitialPromptConsumed,\n placeholder,\n emptyState,\n className,\n}: ChatProps) {\n const [models, setModels] = useState<ChatModel[]>(modelsProp ?? []);\n const [internalRuntime, setInternalRuntime] = useState<ChatModelRuntime>(\n () =>\n controlledRuntime ??\n resolveChatRuntime({\n models: modelsProp ?? [],\n current: defaultRuntime,\n preferredModel:\n controlledModel ??\n (defaultRuntime\n ? undefined\n : (defaultModel ?? defaultChatModelId(modelsProp ?? []))),\n effort: controlledEffort ?? defaultReasoningEffort,\n temperature,\n reasoningEfforts,\n }),\n );\n const [usage, setUsage] = useState<ChatUsageSummary | null>(null);\n const [approvalError, setApprovalError] = useState<Error | undefined>();\n const [approvalsInFlight, setApprovalsInFlight] = useState(\n () => new Map<string | undefined, number>(),\n );\n const lastDefaultModel = useRef(defaultModel);\n const [draft, setDraft] = useState<{ id: number; text: string } | null>(null);\n const seededInitialPromptId = useRef<number | null>(null);\n const hadProposals = useRef(false);\n if (proposedPrompts?.length) hadProposals.current = true;\n const activeThreadRef = useRef(threadId);\n const onMessageCountChangeRef = useRef(onMessageCountChange);\n const onSessionHydratedRef = useRef(onSessionHydrated);\n activeThreadRef.current = threadId;\n onMessageCountChangeRef.current = onMessageCountChange;\n onSessionHydratedRef.current = onSessionHydrated;\n const runtime = controlledRuntime ?? internalRuntime;\n\n // Provided once here; <ToolCall> reads it from context, so no tool-render\n // props thread through Conversation/Message.\n const toolRegistry = useMemo(\n () => toToolRenderRegistry(toolRenderers, tools ? { tools } : {}),\n [toolRenderers, tools],\n );\n\n useEffect(() => {\n if (!modelsProp) return;\n setModels(modelsProp);\n }, [modelsProp]);\n\n // Fetch the model menu unless one was supplied or fetching is disabled.\n useEffect(() => {\n if (modelsProp || !modelsApi) return;\n let cancelled = false;\n fetch(modelsApi)\n .then((r) =>\n r.ok ? r.json() : Promise.reject(new Error(`models ${r.status}`)),\n )\n .then((data: ChatModel[]) => {\n if (cancelled) return;\n setModels(data);\n })\n .catch((err) =>\n console.warn(\"clicky-ui: failed to load chat models\", err),\n );\n return () => {\n cancelled = true;\n };\n }, [modelsProp, modelsApi]);\n\n useEffect(() => {\n if (controlledRuntime !== undefined) return;\n const defaultChanged =\n Boolean(defaultModel) && defaultModel !== lastDefaultModel.current;\n lastDefaultModel.current = defaultModel;\n setInternalRuntime((current) =>\n resolveChatRuntime({\n models,\n current,\n preferredModel:\n controlledModel ??\n (defaultChanged\n ? defaultModel\n : (selectedChatModel(models, current)?.id ??\n defaultModel ??\n defaultChatModelId(models))),\n effort: controlledEffort,\n temperature,\n reasoningEfforts,\n }),\n );\n }, [\n controlledEffort,\n controlledModel,\n controlledRuntime,\n defaultModel,\n models,\n reasoningEfforts,\n temperature,\n ]);\n\n const selectedModel = selectedChatModel(models, runtime);\n const structuredRuntime = hasStructuredRuntime(runtime, selectedModel);\n const resolvedAttachmentUpload = useMemo(\n () =>\n attachmentUpload ??\n createAttachmentUploadAdapter({ endpoint: attachmentsApi }),\n [attachmentUpload, attachmentsApi],\n );\n\n // A function body keeps the transport stable while always sending the latest\n // model/effort/runtime selections.\n const bodyRef = useRef<Record<string, unknown>>({});\n bodyRef.current = {\n ...body,\n ...(structuredRuntime\n ? { runtime }\n : {\n ...(runtime.model ? { model: runtime.model } : {}),\n ...(runtime.effort ? { reasoningEffort: runtime.effort } : {}),\n ...(runtime.temperature !== undefined\n ? { temperature: runtime.temperature }\n : {}),\n }),\n ...(budget && (budget.cost !== undefined || budget.maxTokens !== undefined)\n ? { budget }\n : {}),\n ...(permissionMode ? { permissionMode } : {}),\n ...(threadId ? { threadId } : {}),\n };\n\n const resolvedTransport = useMemo<ChatTransport<UIMessage>>(\n () =>\n transport ??\n new DefaultChatTransport({ api, body: () => bodyRef.current }),\n [transport, api],\n );\n\n const {\n messages,\n setMessages,\n sendMessage,\n regenerate,\n status,\n error,\n clearError,\n stop,\n } = useChat<ChatUIMessage>({\n transport: resolvedTransport,\n ...(threadId ? { id: threadId } : {}),\n ...(initialMessages\n ? { messages: initialMessages as ChatUIMessage[] }\n : {}),\n });\n const setMessagesRef = useRef(setMessages);\n setMessagesRef.current = setMessages;\n const hydratedUsageRef = useRef<ChatUsageSummary | null>(null);\n const hydratedAssistantRef = useRef<ChatUIMessage | undefined>(undefined);\n\n const applySession = useCallback((session: CaptainChatSession) => {\n const snapshot = usageSnapshotFromSession(session);\n hydratedUsageRef.current = snapshot;\n hydratedAssistantRef.current = [...session.messages].reverse().find((message) => message.role === \"assistant\") as ChatUIMessage | undefined;\n setUsage(snapshot);\n setMessagesRef.current(session.messages as ChatUIMessage[]);\n }, []);\n\n useEffect(() => {\n if (!sessionsApi || !threadId) return;\n let cancelled = false;\n setApprovalError(undefined);\n hydratedUsageRef.current = null;\n hydratedAssistantRef.current = undefined;\n setUsage(null);\n setMessagesRef.current([]);\n void getChatSession(sessionsApi, threadId)\n .then((session) => {\n if (cancelled) return;\n if (session.id !== threadId) {\n throw new Error(\n `Captain chat session response ID \"${session.id}\" does not match requested session \"${threadId}\".`,\n );\n }\n applySession(session);\n onSessionHydratedRef.current?.(session);\n })\n .catch((cause) => {\n if (cancelled) return;\n setApprovalError(\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n });\n return () => {\n cancelled = true;\n };\n }, [applySession, sessionsApi, threadId]);\n\n useEffect(() => {\n onMessageCountChangeRef.current?.(\n messages.filter((message) => !isForkSeedMessage(message)).length,\n );\n }, [messages]);\n\n // An initial prompt is a draft, not a turn: it lands in the composer and\n // waits for the user to send it. Unlike a send it needs no live transport and\n // no hydrated session, so it seeds immediately.\n useEffect(() => {\n if (!initialPrompt) return;\n if (seededInitialPromptId.current === initialPrompt.id) return;\n const text = initialPrompt.text.trim();\n if (!text) return;\n seededInitialPromptId.current = initialPrompt.id;\n setDraft({ id: nextDraftId(), text });\n onInitialPromptConsumed?.();\n }, [initialPrompt, onInitialPromptConsumed]);\n\n // Surface a usage snapshot after each settled assistant turn. The backend\n // rides usage/cost on the finish part's messageMetadata; we read it off the\n // last assistant message, preserving provider context independently of billing.\n const onUsageRef = useRef(onUsage);\n onUsageRef.current = onUsage;\n useEffect(() => {\n if (status !== \"ready\") return;\n const last = [...messages].reverse().find((m) => m.role === \"assistant\");\n if (hydratedUsageRef.current && last?.id === hydratedAssistantRef.current?.id && last?.metadata === hydratedAssistantRef.current?.metadata) {\n setUsage(hydratedUsageRef.current);\n onUsageRef.current?.(hydratedUsageRef.current);\n return;\n }\n const meta = last?.metadata;\n if (!meta) {\n setUsage(null);\n return;\n }\n const settledModel = meta.model\n ? selectedChatModel(models, { id: meta.model, model: meta.model })\n : undefined;\n const snapshot = usageSnapshotFromMetadata(meta, {\n modelLabel: settledModel?.label,\n messageCount: messages.length,\n });\n setUsage(snapshot);\n onUsageRef.current?.(snapshot);\n }, [messages, models, status]);\n\n const handleRuntimeChange = (next: ChatModelRuntime) => {\n if (controlledRuntime === undefined) setInternalRuntime(next);\n onRuntimeChange?.(next);\n const nextModel = selectedChatModel(models, next);\n const currentModelId = selectedModel?.id ?? runtime.id ?? runtime.model ?? \"\";\n const nextModelId = nextModel?.id ?? next.id ?? next.model ?? \"\";\n if (nextModelId !== currentModelId) {\n onModelChange?.(nextModelId);\n }\n if ((next.effort ?? \"\") !== (runtime.effort ?? \"\")) {\n onReasoningEffortChange?.(next.effort ?? \"\");\n }\n };\n\n const resolveToolApproval = async (\n id: string,\n approved: boolean,\n reason?: string,\n ) => {\n const approvalThreadId = threadId;\n setApprovalError(undefined);\n setApprovalsInFlight((counts) =>\n new Map(counts).set(approvalThreadId, (counts.get(approvalThreadId) ?? 0) + 1),\n );\n try {\n if (!sessionsApi) {\n throw new Error(\n \"A Captain sessions API is required to resolve tool approvals.\",\n );\n }\n const session = await postToolApproval({\n sessionsApi,\n sessionId: approvalThreadId ?? \"\",\n approvalId: id,\n approved,\n ...(reason ? { reason } : {}),\n });\n if (activeThreadRef.current !== approvalThreadId) return;\n if (session.id !== approvalThreadId) {\n throw new Error(\n `Captain chat session response ID \"${session.id}\" does not match active session \"${approvalThreadId ?? \"\"}\".`,\n );\n }\n applySession(session);\n } catch (cause) {\n if (activeThreadRef.current !== approvalThreadId) return;\n setApprovalError(\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n } finally {\n setApprovalsInFlight((counts) => {\n const count = counts.get(approvalThreadId);\n if (count === undefined || count < 1) {\n throw new Error(\"Tool approval completed without a pending request.\");\n }\n const next = new Map(counts);\n if (count === 1) next.delete(approvalThreadId);\n else next.set(approvalThreadId, count - 1);\n return next;\n });\n }\n };\n\n const toolbar = (\n <ChatRuntimeToolbar\n models={models}\n runtime={runtime}\n runtimeFamilies={runtimeFamilies}\n reasoningEfforts={reasoningEfforts}\n selectedModel={selectedModel}\n usage={usage}\n threadId={threadId}\n locked={runtimeLocked}\n onRuntimeChange={handleRuntimeChange}\n />\n );\n\n const empty =\n messages.length === 0 && emptyState ? (\n <div className=\"flex flex-col items-center gap-4\">{emptyState}</div>\n ) : undefined;\n\n // Prompts proposed for this open outrank the static suggestions and survive a\n // running conversation — a host can propose into a chat that already has one.\n // The static list is an empty-state affordance and retires once a turn exists;\n // it also never comes back after proposals were consumed, because falling back\n // to the application's generic prompts under a chat opened about one cell\n // reads as an unrelated non-sequitur.\n const chips = proposedPrompts?.length\n ? proposedPrompts\n : messages.length === 0 && !hadProposals.current\n ? suggestions\n : undefined;\n\n const pickSuggestion = (text: string) => {\n setDraft({ id: nextDraftId(), text });\n onProposedPromptsConsumed?.();\n };\n\n return (\n <ToolRenderRegistryProvider value={toolRegistry}>\n <div className={cn(\"flex h-full flex-col\", className)}>\n <Conversation\n messages={messages}\n status={status}\n error={approvalError ?? error}\n onClearError={() => {\n setApprovalError(undefined);\n clearError();\n }}\n {...(threadId ? { sessionId: threadId } : {})}\n {...(selectedModel?.id\n ? { model: selectedModel.id }\n : runtime.model\n ? { model: runtime.model }\n : {})}\n suppressWaiting={(approvalsInFlight.get(threadId) ?? 0) > 0}\n emptyState={empty}\n onRegenerate={(messageId) => void regenerate({ messageId })}\n onApprove={resolveToolApproval}\n {...(renderToolResult ? { renderToolResult } : {})}\n />\n <div className=\"flex flex-col gap-2 p-4 pt-0\">\n {chips && chips.length > 0 && (\n <Suggestions suggestions={chips} onSelect={pickSuggestion} />\n )}\n <PromptInput\n status={status}\n onStop={() => void stop()}\n placeholder={placeholder}\n enableAttachments={enableAttachments}\n attachmentUpload={resolvedAttachmentUpload}\n {...(selectedModel?.inputMediaTypes\n ? { acceptedMediaTypes: selectedModel.inputMediaTypes }\n : {})}\n {...(attachmentLimits ? { attachmentLimits } : {})}\n draft={draft}\n toolbar={toolbar}\n onSubmit={(text, files) => {\n onProposedPromptsConsumed?.();\n void sendMessage({ text, files });\n }}\n />\n </div>\n </div>\n </ToolRenderRegistryProvider>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Chat.js","sources":["../../../src/data/chat/Chat.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport, type ChatTransport, type UIMessage } from \"ai\";\nimport { cn } from \"../../lib/utils\";\nimport { Conversation } from \"./Conversation\";\nimport { PromptInput } from \"./PromptInput\";\nimport { Suggestions } from \"./Suggestion\";\nimport { DEFAULT_REASONING_EFFORTS } from \"./effort-icons\";\nimport { defaultChatModelId } from \"./models\";\nimport {\n getChatSession,\n postToolApproval,\n type CaptainChatSession,\n} from \"./approval\";\nimport { isForkSeedMessage } from \"./fork-seed\";\nimport {\n hasStructuredRuntime,\n resolveChatRuntime,\n selectedChatModel,\n} from \"./Chat.runtime\";\nimport {\n createAttachmentUploadAdapter,\n type AttachmentLimits,\n type AttachmentUploadAdapter,\n} from \"./attachment-upload\";\nimport { toToolRenderRegistry } from \"./tool-render/registry\";\nimport { ToolRenderRegistryProvider } from \"./tool-render/context\";\nimport { ChatRuntimeToolbar } from \"./ChatRuntimeToolbar\";\nimport type { SpecRuntimeFamily } from \"../runtime/runtime-mode\";\nimport type {\n ToolRenderAdapter,\n ToolRenderRegistry,\n} from \"./tool-render/adapter\";\nimport type {\n ChatBudgetConfig,\n ChatModel,\n ChatMessageMetadata,\n ChatModelRuntime,\n ChatUsageSummary,\n ClaudePermissionMode,\n Suggestion,\n ToolMeta,\n ToolResultRenderer,\n} from \"./types\";\nimport {\n usageSnapshotFromMetadata,\n usageSnapshotFromSession,\n} from \"./usage-snapshot\";\n\n/** Assistant messages carry token usage + cost the backend rode on the finish\n * part's `messageMetadata`. */\ntype ChatUIMessage = UIMessage<ChatMessageMetadata>;\n\n// Composer seeds are keyed by a monotonic id so re-seeding the same text works.\n// Minted here rather than reusing a caller's `initialPrompt.id` so a host's id\n// space can never collide with one a suggestion pick produced.\nlet _draftId = 0;\nconst nextDraftId = () => ++_draftId;\n\nexport type ChatProps = {\n /** Endpoint that speaks the AI SDK v6 UI Message Stream protocol.\n * Ignored when `transport` is supplied. Defaults to \"/api/chat\". */\n api?: string;\n /** Model menu. When omitted and `modelsApi` is set, it is fetched. */\n models?: ChatModel[];\n /** Endpoint returning the model menu (ModelInfo[]). Defaults to\n * \"/api/chat/models\". Set to null to disable fetching. */\n modelsApi?: string | null;\n /** Initially selected model id; otherwise the first configured model. */\n defaultModel?: string;\n /** Controlled selected model id. */\n model?: string;\n /** Initially selected structured runtime. */\n defaultRuntime?: ChatModelRuntime;\n /** Controlled structured runtime. */\n runtime?: ChatModelRuntime;\n /** Runtime families and their host availability. */\n runtimeFamilies?: SpecRuntimeFamily[];\n /** Reasoning-effort options for capable models. */\n reasoningEfforts?: string[];\n /** Initially selected reasoning effort (\"\" = none). */\n defaultReasoningEffort?: string;\n /** Controlled selected reasoning effort (\"\" = none). */\n reasoningEffort?: string;\n /** Sampling temperature forwarded to the backend when set. */\n temperature?: number;\n /** Per-request budget forwarded to the backend when set. */\n budget?: ChatBudgetConfig;\n /** Claude Agent SDK permission mode forwarded to the backend when set. */\n permissionMode?: ClaudePermissionMode;\n /** Notified when the user changes the model. */\n onModelChange?: (id: string) => void;\n /** Notified when the user changes any runtime field. */\n onRuntimeChange?: (runtime: ChatModelRuntime) => void;\n /** Notified when the user changes reasoning effort. */\n onReasoningEffortChange?: (effort: string) => void;\n /** Notified when the user changes Claude permission mode. */\n onPermissionModeChange?: (mode: ClaudePermissionMode) => void;\n /** Notified after each assistant turn with a usage snapshot (tokens used out\n * of the model's context window + cumulative cost), for a usage gauge. */\n onUsage?: (usage: ChatUsageSummary) => void;\n /** Suggested prompts offered while the conversation is still empty. */\n suggestions?: Suggestion[];\n /** Prompts proposed for this particular open — e.g. the opener a host's \"Ask\n * AI\" button rendered for the thing being asked about. Shown in place of\n * `suggestions`, and unlike them they stay offered once the conversation has\n * started, because a host may propose them into a chat that is already\n * running. Cleared through `onProposedPromptsConsumed`. */\n proposedPrompts?: Suggestion[] | null;\n /** Called once the proposed prompts have been acted on — one was picked, or\n * the user sent a message of their own. */\n onProposedPromptsConsumed?: () => void;\n /** Enables file/image attachments. */\n enableAttachments?: boolean;\n /** Upload endpoint used before an attachment is added to a chat message. */\n attachmentsApi?: string;\n /** Custom durable upload adapter; overrides attachmentsApi. */\n attachmentUpload?: AttachmentUploadAdapter;\n attachmentLimits?: AttachmentLimits;\n /** Captain thread id used as the AI SDK chat id and forwarded in the body. */\n threadId?: string;\n /** Canonical Captain session endpoint used to hydrate messages and resolve\n * approvals. The session and approval ids are appended to this URL. */\n sessionsApi?: string | null;\n /** Prevents changing model/backend for an existing Captain session. Effort\n * and other per-turn generation settings remain editable. */\n runtimeLocked?: boolean;\n /** Reports the number of real (non-fork-seed) persisted/UI messages. */\n onMessageCountChange?: (count: number) => void;\n /** Reports the authoritative Captain session after detail hydration. */\n onSessionHydrated?: (session: CaptainChatSession) => void;\n /** Optional host renderer for recognized completed tool outputs. Takes\n * priority over `toolRenderers` on the output surface. */\n renderToolResult?: ToolResultRenderer;\n /** Tool catalog, used to resolve each call's input/output JSON Schema so\n * params and results render with real labels instead of raw JSON. */\n tools?: ToolMeta[];\n /** Domain tool renderers. Host adapters are matched before the built-in\n * heuristics, which stay as the floor. */\n toolRenderers?: ToolRenderAdapter[] | ToolRenderRegistry;\n /** Extra fields merged into every request body. */\n body?: Record<string, unknown>;\n /** Pre-built transport (e.g. a mock for stories/tests). */\n transport?: ChatTransport<UIMessage>;\n /** Initial messages to seed the conversation. */\n initialMessages?: UIMessage[];\n /** Prompt to prefill the composer with once. It is never submitted on the\n * user's behalf — they read it, edit it if they want, and press send. A new\n * `id` re-seeds even when the text repeats. */\n initialPrompt?: { id: number; text: string } | null;\n /** Called after `initialPrompt` has been placed in the composer. */\n onInitialPromptConsumed?: () => void;\n placeholder?: string;\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** Self-contained AI chat over a v6-compatible `/api/chat`: a streaming\n * conversation with model/effort selectors, suggestions, attachments,\n * per-message copy/regenerate, and human-in-the-loop tool approvals. The\n * backend owns model selection and tool execution; the selected model,\n * effort, permission mode and thread id are forwarded in the request body. */\nexport function Chat({\n api = \"/api/chat\",\n models: modelsProp,\n modelsApi = \"/api/chat/models\",\n defaultModel,\n model: controlledModel,\n defaultRuntime,\n runtime: controlledRuntime,\n runtimeFamilies,\n reasoningEfforts = DEFAULT_REASONING_EFFORTS,\n defaultReasoningEffort,\n reasoningEffort: controlledEffort,\n temperature,\n budget,\n permissionMode,\n onModelChange,\n onRuntimeChange,\n onReasoningEffortChange,\n onUsage,\n suggestions,\n proposedPrompts,\n onProposedPromptsConsumed,\n enableAttachments = false,\n attachmentsApi = \"/api/attachments\",\n attachmentUpload,\n attachmentLimits,\n threadId,\n sessionsApi = null,\n runtimeLocked = false,\n onMessageCountChange,\n onSessionHydrated,\n renderToolResult,\n tools,\n toolRenderers,\n body,\n transport,\n initialMessages,\n initialPrompt,\n onInitialPromptConsumed,\n placeholder,\n emptyState,\n className,\n}: ChatProps) {\n const [models, setModels] = useState<ChatModel[]>(modelsProp ?? []);\n const [internalRuntime, setInternalRuntime] = useState<ChatModelRuntime>(\n () =>\n controlledRuntime ??\n resolveChatRuntime({\n models: modelsProp ?? [],\n current: defaultRuntime,\n preferredModel:\n controlledModel ??\n (defaultRuntime\n ? undefined\n : (defaultModel ?? defaultChatModelId(modelsProp ?? []))),\n effort: controlledEffort ?? defaultReasoningEffort,\n temperature,\n reasoningEfforts,\n }),\n );\n const [usage, setUsage] = useState<ChatUsageSummary | null>(null);\n const [approvalError, setApprovalError] = useState<Error | undefined>();\n const [approvalsInFlight, setApprovalsInFlight] = useState(\n () => new Map<string | undefined, number>(),\n );\n const lastDefaultModel = useRef(defaultModel);\n const [draft, setDraft] = useState<{ id: number; text: string } | null>(null);\n const seededInitialPromptId = useRef<number | null>(null);\n const hadProposals = useRef(false);\n if (proposedPrompts?.length) hadProposals.current = true;\n const activeThreadRef = useRef(threadId);\n const onMessageCountChangeRef = useRef(onMessageCountChange);\n const onSessionHydratedRef = useRef(onSessionHydrated);\n activeThreadRef.current = threadId;\n onMessageCountChangeRef.current = onMessageCountChange;\n onSessionHydratedRef.current = onSessionHydrated;\n const runtime = controlledRuntime ?? internalRuntime;\n\n // Provided once here; <ToolCall> reads it from context, so no tool-render\n // props thread through Conversation/Message.\n const toolRegistry = useMemo(\n () => toToolRenderRegistry(toolRenderers, tools ? { tools } : {}),\n [toolRenderers, tools],\n );\n\n useEffect(() => {\n if (!modelsProp) return;\n setModels(modelsProp);\n }, [modelsProp]);\n\n // Fetch the model menu unless one was supplied or fetching is disabled.\n useEffect(() => {\n if (modelsProp || !modelsApi) return;\n let cancelled = false;\n fetch(modelsApi)\n .then((r) =>\n r.ok ? r.json() : Promise.reject(new Error(`models ${r.status}`)),\n )\n .then((data: ChatModel[]) => {\n if (cancelled) return;\n setModels(data);\n })\n .catch((err) =>\n console.warn(\"clicky-ui: failed to load chat models\", err),\n );\n return () => {\n cancelled = true;\n };\n }, [modelsProp, modelsApi]);\n\n useEffect(() => {\n if (controlledRuntime !== undefined) return;\n const defaultChanged =\n Boolean(defaultModel) && defaultModel !== lastDefaultModel.current;\n lastDefaultModel.current = defaultModel;\n setInternalRuntime((current) =>\n resolveChatRuntime({\n models,\n current,\n preferredModel:\n controlledModel ??\n (defaultChanged\n ? defaultModel\n : (selectedChatModel(models, current)?.id ??\n defaultModel ??\n defaultChatModelId(models))),\n effort: controlledEffort,\n temperature,\n reasoningEfforts,\n }),\n );\n }, [\n controlledEffort,\n controlledModel,\n controlledRuntime,\n defaultModel,\n models,\n reasoningEfforts,\n temperature,\n ]);\n\n const selectedModel = selectedChatModel(models, runtime);\n const structuredRuntime = hasStructuredRuntime(runtime, selectedModel);\n const resolvedAttachmentUpload = useMemo(\n () =>\n attachmentUpload ??\n createAttachmentUploadAdapter({ endpoint: attachmentsApi }),\n [attachmentUpload, attachmentsApi],\n );\n\n // A function body keeps the transport stable while always sending the latest\n // model/effort/runtime selections.\n const bodyRef = useRef<Record<string, unknown>>({});\n bodyRef.current = {\n ...body,\n ...(structuredRuntime\n ? { runtime }\n : {\n ...(runtime.model ? { model: runtime.model } : {}),\n ...(runtime.effort ? { reasoningEffort: runtime.effort } : {}),\n ...(runtime.temperature !== undefined\n ? { temperature: runtime.temperature }\n : {}),\n }),\n ...(budget && (budget.cost !== undefined || budget.maxTokens !== undefined)\n ? { budget }\n : {}),\n ...(permissionMode ? { permissionMode } : {}),\n ...(threadId ? { threadId } : {}),\n };\n\n const resolvedTransport = useMemo<ChatTransport<UIMessage>>(\n () =>\n transport ??\n new DefaultChatTransport({ api, body: () => bodyRef.current }),\n [transport, api],\n );\n\n const {\n messages,\n setMessages,\n sendMessage,\n regenerate,\n status,\n error,\n clearError,\n stop,\n } = useChat<ChatUIMessage>({\n transport: resolvedTransport,\n ...(threadId ? { id: threadId } : {}),\n ...(initialMessages\n ? { messages: initialMessages as ChatUIMessage[] }\n : {}),\n });\n const setMessagesRef = useRef(setMessages);\n setMessagesRef.current = setMessages;\n const hydratedUsageRef = useRef<ChatUsageSummary | null>(null);\n const hydratedAssistantRef = useRef<ChatUIMessage | undefined>(undefined);\n\n const applySession = useCallback((session: CaptainChatSession) => {\n const snapshot = usageSnapshotFromSession(session);\n hydratedUsageRef.current = snapshot;\n hydratedAssistantRef.current = [...session.messages].reverse().find((message) => message.role === \"assistant\") as ChatUIMessage | undefined;\n setUsage(snapshot);\n setMessagesRef.current(session.messages as ChatUIMessage[]);\n }, []);\n\n useEffect(() => {\n if (!sessionsApi || !threadId) return;\n let cancelled = false;\n setApprovalError(undefined);\n hydratedUsageRef.current = null;\n hydratedAssistantRef.current = undefined;\n setUsage(null);\n setMessagesRef.current([]);\n void getChatSession(sessionsApi, threadId)\n .then((session) => {\n if (cancelled) return;\n if (session.id !== threadId) {\n throw new Error(\n `Captain chat session response ID \"${session.id}\" does not match requested session \"${threadId}\".`,\n );\n }\n applySession(session);\n onSessionHydratedRef.current?.(session);\n })\n .catch((cause) => {\n if (cancelled) return;\n setApprovalError(\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n });\n return () => {\n cancelled = true;\n };\n }, [applySession, sessionsApi, threadId]);\n\n useEffect(() => {\n onMessageCountChangeRef.current?.(\n messages.filter((message) => !isForkSeedMessage(message)).length,\n );\n }, [messages]);\n\n // An initial prompt is a draft, not a turn: it lands in the composer and\n // waits for the user to send it. Unlike a send it needs no live transport and\n // no hydrated session, so it seeds immediately.\n useEffect(() => {\n if (!initialPrompt) return;\n if (seededInitialPromptId.current === initialPrompt.id) return;\n const text = initialPrompt.text.trim();\n if (!text) return;\n seededInitialPromptId.current = initialPrompt.id;\n setDraft({ id: nextDraftId(), text });\n onInitialPromptConsumed?.();\n }, [initialPrompt, onInitialPromptConsumed]);\n\n // Surface a usage snapshot after each settled assistant turn. The backend\n // rides usage/cost on the finish part's messageMetadata; we read it off the\n // last assistant message, preserving provider context independently of billing.\n const onUsageRef = useRef(onUsage);\n onUsageRef.current = onUsage;\n useEffect(() => {\n if (status !== \"ready\") return;\n const last = [...messages].reverse().find((m) => m.role === \"assistant\");\n if (hydratedUsageRef.current && last?.id === hydratedAssistantRef.current?.id && last?.metadata === hydratedAssistantRef.current?.metadata) {\n setUsage(hydratedUsageRef.current);\n onUsageRef.current?.(hydratedUsageRef.current);\n return;\n }\n const meta = last?.metadata;\n if (!meta) {\n setUsage(null);\n return;\n }\n const settledModel = meta.model\n ? selectedChatModel(models, { id: meta.model, model: meta.model })\n : undefined;\n const snapshot = usageSnapshotFromMetadata(meta, {\n modelLabel: settledModel?.label,\n messageCount: messages.length,\n });\n setUsage(snapshot);\n onUsageRef.current?.(snapshot);\n }, [messages, models, status]);\n\n const handleRuntimeChange = (next: ChatModelRuntime) => {\n if (controlledRuntime === undefined) setInternalRuntime(next);\n onRuntimeChange?.(next);\n const nextModel = selectedChatModel(models, next);\n const currentModelId = selectedModel?.id ?? runtime.id ?? runtime.model ?? \"\";\n const nextModelId = nextModel?.id ?? next.id ?? next.model ?? \"\";\n if (nextModelId !== currentModelId) {\n onModelChange?.(nextModelId);\n }\n if ((next.effort ?? \"\") !== (runtime.effort ?? \"\")) {\n onReasoningEffortChange?.(next.effort ?? \"\");\n }\n };\n\n const resolveToolApproval = async (\n id: string,\n approved: boolean,\n reason?: string,\n ) => {\n const approvalThreadId = threadId;\n setApprovalError(undefined);\n setApprovalsInFlight((counts) =>\n new Map(counts).set(approvalThreadId, (counts.get(approvalThreadId) ?? 0) + 1),\n );\n try {\n if (!sessionsApi) {\n throw new Error(\n \"A Captain sessions API is required to resolve tool approvals.\",\n );\n }\n const session = await postToolApproval({\n sessionsApi,\n sessionId: approvalThreadId ?? \"\",\n approvalId: id,\n approved,\n ...(reason ? { reason } : {}),\n });\n if (activeThreadRef.current !== approvalThreadId) return;\n if (session.id !== approvalThreadId) {\n throw new Error(\n `Captain chat session response ID \"${session.id}\" does not match active session \"${approvalThreadId ?? \"\"}\".`,\n );\n }\n applySession(session);\n } catch (cause) {\n if (activeThreadRef.current !== approvalThreadId) return;\n setApprovalError(\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n } finally {\n setApprovalsInFlight((counts) => {\n const count = counts.get(approvalThreadId);\n if (count === undefined || count < 1) {\n throw new Error(\"Tool approval completed without a pending request.\");\n }\n const next = new Map(counts);\n if (count === 1) next.delete(approvalThreadId);\n else next.set(approvalThreadId, count - 1);\n return next;\n });\n }\n };\n\n const toolbar = (\n <ChatRuntimeToolbar\n models={models}\n runtime={runtime}\n runtimeFamilies={runtimeFamilies}\n reasoningEfforts={reasoningEfforts}\n selectedModel={selectedModel}\n usage={usage}\n threadId={threadId}\n locked={runtimeLocked}\n onRuntimeChange={handleRuntimeChange}\n />\n );\n\n const empty =\n messages.length === 0 && emptyState ? (\n <div className=\"flex flex-col items-center gap-4\">{emptyState}</div>\n ) : undefined;\n\n // Prompts proposed for this open outrank the static suggestions and survive a\n // running conversation — a host can propose into a chat that already has one.\n // The static list is an empty-state affordance and retires once a turn exists;\n // it also never comes back after proposals were consumed, because falling back\n // to the application's generic prompts under a chat opened about one cell\n // reads as an unrelated non-sequitur.\n const chips = proposedPrompts?.length\n ? proposedPrompts\n : messages.length === 0 && !hadProposals.current\n ? suggestions\n : undefined;\n\n const pickSuggestion = (text: string) => {\n setDraft({ id: nextDraftId(), text });\n onProposedPromptsConsumed?.();\n };\n\n return (\n <ToolRenderRegistryProvider value={toolRegistry}>\n <div className={cn(\"flex h-full flex-col\", className)}>\n <Conversation\n messages={messages}\n status={status}\n error={approvalError ?? error}\n onClearError={() => {\n setApprovalError(undefined);\n clearError();\n }}\n {...(threadId ? { sessionId: threadId } : {})}\n {...(selectedModel?.id\n ? { model: selectedModel.id }\n : runtime.model\n ? { model: runtime.model }\n : {})}\n suppressWaiting={(approvalsInFlight.get(threadId) ?? 0) > 0}\n emptyState={empty}\n onRegenerate={(messageId) => void regenerate({ messageId })}\n onApprove={resolveToolApproval}\n {...(renderToolResult ? { renderToolResult } : {})}\n />\n <div className=\"flex shrink-0 flex-col gap-2 p-4 pt-0 pb-[max(1rem,env(safe-area-inset-bottom))]\">\n {chips && chips.length > 0 && (\n <Suggestions suggestions={chips} onSelect={pickSuggestion} />\n )}\n <PromptInput\n status={status}\n onStop={() => void stop()}\n placeholder={placeholder}\n enableAttachments={enableAttachments}\n attachmentUpload={resolvedAttachmentUpload}\n {...(selectedModel?.inputMediaTypes\n ? { acceptedMediaTypes: selectedModel.inputMediaTypes }\n : {})}\n {...(attachmentLimits ? { attachmentLimits } : {})}\n draft={draft}\n toolbar={toolbar}\n onSubmit={(text, files) => {\n onProposedPromptsConsumed?.();\n void sendMessage({ text, files });\n }}\n />\n </div>\n </div>\n </ToolRenderRegistryProvider>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -21,8 +21,8 @@ function ChatRuntimeToolbar({
21
21
  const showContextMeter = Boolean(threadId || selectedModel || usage);
22
22
  if (!showRuntimeBar && !showContextMeter) return null;
23
23
  const ModelGlyph = providerIcons.providerIcon(usage == null ? void 0 : usage.provider);
24
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 items-center gap-2", children: [
25
- showRuntimeBar && /* @__PURE__ */ jsxRuntime.jsx(
24
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
25
+ showRuntimeBar && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
26
26
  RuntimeBar.RuntimeBar,
27
27
  {
28
28
  value: runtime,
@@ -32,9 +32,9 @@ function ChatRuntimeToolbar({
32
32
  locked,
33
33
  ...runtimeFamilies ? { families: runtimeFamilies } : {}
34
34
  }
35
- ),
35
+ ) }),
36
36
  showContextMeter && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
37
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
37
+ !showRuntimeBar && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
38
38
  /* @__PURE__ */ jsxRuntime.jsx(
39
39
  ContextMeter.ContextMeter,
40
40
  {