@signalflare-ai/ui 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (362) hide show
  1. package/CHANGELOG.md +102 -5
  2. package/README.md +1 -1
  3. package/ai/component-registry.json +531 -79
  4. package/ai/component-registry.md +4067 -6
  5. package/ai/schemas.ts +6 -1
  6. package/dist/.build-complete +1 -1
  7. package/dist/ai/schemas.d.ts +76 -58
  8. package/dist/ai/schemas.d.ts.map +1 -1
  9. package/dist/{ai-actions-DSVeQn4e.js → ai-actions-CBfz5XEf.js} +4 -4
  10. package/dist/{ai-actions-DSVeQn4e.js.map → ai-actions-CBfz5XEf.js.map} +1 -1
  11. package/dist/{ai-agent-card-BXHwhWAU.js → ai-agent-card-CByAUe0q.js} +3 -3
  12. package/dist/ai-agent-card-CByAUe0q.js.map +1 -0
  13. package/dist/{ai-approval-aa0qvjFN.js → ai-approval-Ci8N70a7.js} +4 -3
  14. package/dist/{ai-approval-aa0qvjFN.js.map → ai-approval-Ci8N70a7.js.map} +1 -1
  15. package/dist/{ai-code-block-BgtIxtZZ.js → ai-code-block-P9TJHvaC.js} +37 -39
  16. package/dist/ai-code-block-P9TJHvaC.js.map +1 -0
  17. package/dist/ai-conversation-Qslfdi1t.js +228 -0
  18. package/dist/ai-conversation-Qslfdi1t.js.map +1 -0
  19. package/dist/{ai-info-banner-uFxHHwBA.js → ai-info-banner-B_9vtGK3.js} +8 -4
  20. package/dist/ai-info-banner-B_9vtGK3.js.map +1 -0
  21. package/dist/{ai-message-BjnFznXy.js → ai-message-Ci3gwM7G.js} +29 -10
  22. package/dist/ai-message-Ci3gwM7G.js.map +1 -0
  23. package/dist/{ai-mission-header-08__gULL.js → ai-mission-header-CaBc19-t.js} +2 -2
  24. package/dist/{ai-mission-header-08__gULL.js.map → ai-mission-header-CaBc19-t.js.map} +1 -1
  25. package/dist/{ai-part-group-DBtgTgAn.js → ai-part-group-Dx1Mr92B.js} +5 -4
  26. package/dist/ai-part-group-Dx1Mr92B.js.map +1 -0
  27. package/dist/{ai-prompt-input-CuluUzpf.js → ai-prompt-input-Bm4XoSj2.js} +44 -55
  28. package/dist/ai-prompt-input-Bm4XoSj2.js.map +1 -0
  29. package/dist/{ai-question-CHHoDJMg.js → ai-question-OyJovxGe.js} +4 -3
  30. package/dist/{ai-question-CHHoDJMg.js.map → ai-question-OyJovxGe.js.map} +1 -1
  31. package/dist/{ai-reasoning-CnL6ZSr5.js → ai-reasoning-BLfBXx3F.js} +9 -5
  32. package/dist/ai-reasoning-BLfBXx3F.js.map +1 -0
  33. package/dist/{ai-response-BEUg3xvd.js → ai-response-hbVCZJmo.js} +9 -4
  34. package/dist/ai-response-hbVCZJmo.js.map +1 -0
  35. package/dist/{ai-shimmer-By5_L05p.js → ai-shimmer-BamNMNK3.js} +2 -2
  36. package/dist/{ai-shimmer-By5_L05p.js.map → ai-shimmer-BamNMNK3.js.map} +1 -1
  37. package/dist/{ai-status-badge-BGYGWYF6.js → ai-status-badge-BZLczdkI.js} +2 -2
  38. package/dist/{ai-status-badge-BGYGWYF6.js.map → ai-status-badge-BZLczdkI.js.map} +1 -1
  39. package/dist/{ai-streaming-text-CMfoThV0.js → ai-streaming-text-DgYu64UH.js} +44 -16
  40. package/dist/ai-streaming-text-DgYu64UH.js.map +1 -0
  41. package/dist/{ai-subagent-DcPRqkAA.js → ai-subagent-p97AI1h9.js} +14 -6
  42. package/dist/ai-subagent-p97AI1h9.js.map +1 -0
  43. package/dist/{ai-suggestion-MgeCg5Ar.js → ai-suggestion-Bj6vF7CT.js} +3 -3
  44. package/dist/{ai-suggestion-MgeCg5Ar.js.map → ai-suggestion-Bj6vF7CT.js.map} +1 -1
  45. package/dist/{ai-task-list-Da9zIm00.js → ai-task-list-C_UQYpk9.js} +15 -6
  46. package/dist/ai-task-list-C_UQYpk9.js.map +1 -0
  47. package/dist/{ai-timeline-Cwu045IR.js → ai-timeline-CePL1LOU.js} +3 -3
  48. package/dist/ai-timeline-CePL1LOU.js.map +1 -0
  49. package/dist/{ai-tool-Cn1O4xjP.js → ai-tool-CfRcwmHT.js} +35 -16
  50. package/dist/ai-tool-CfRcwmHT.js.map +1 -0
  51. package/dist/{ai-usage-bar-DjS12DMp.js → ai-usage-bar-45pVRCGA.js} +2 -2
  52. package/dist/{ai-usage-bar-DjS12DMp.js.map → ai-usage-bar-45pVRCGA.js.map} +1 -1
  53. package/dist/{badge-D_eaA6wv.js → badge-Beb-6uut.js} +5 -5
  54. package/dist/{badge-D_eaA6wv.js.map → badge-Beb-6uut.js.map} +1 -1
  55. package/dist/{banner-B_6oBrsu.js → banner-CCEksxPg.js} +8 -3
  56. package/dist/banner-CCEksxPg.js.map +1 -0
  57. package/dist/{breadcrumbs-BlmeYfgq.js → breadcrumbs-HiTmgaZ4.js} +5 -5
  58. package/dist/{breadcrumbs-BlmeYfgq.js.map → breadcrumbs-HiTmgaZ4.js.map} +1 -1
  59. package/dist/{button-De0267YU.js → button-BHOgXJRU.js} +4 -4
  60. package/dist/{button-De0267YU.js.map → button-BHOgXJRU.js.map} +1 -1
  61. package/dist/catalog.js +1 -1
  62. package/dist/catalog.js.map +1 -1
  63. package/dist/{chart-BK3sVPnD.js → chart-B9FfZdKs.js} +7 -7
  64. package/dist/chart-B9FfZdKs.js.map +1 -0
  65. package/dist/{checkbox-DYhUmZNw.js → checkbox-Cy_OCyay.js} +3 -3
  66. package/dist/{checkbox-DYhUmZNw.js.map → checkbox-Cy_OCyay.js.map} +1 -1
  67. package/dist/{clipboard-text-ssybngLw.js → clipboard-text-CKSvNp9L.js} +6 -5
  68. package/dist/clipboard-text-CKSvNp9L.js.map +1 -0
  69. package/dist/{cn-YROP2_ox.js → cn-CmAOpn49.js} +2 -2
  70. package/dist/{cn-YROP2_ox.js.map → cn-CmAOpn49.js.map} +1 -1
  71. package/dist/{code-Cx-QSoOT.js → code-JsQz-0G_.js} +4 -4
  72. package/dist/{code-Cx-QSoOT.js.map → code-JsQz-0G_.js.map} +1 -1
  73. package/dist/{collapsible-DWsXeXmS.js → collapsible-1kOZ-89L.js} +2 -2
  74. package/dist/{collapsible-DWsXeXmS.js.map → collapsible-1kOZ-89L.js.map} +1 -1
  75. package/dist/{combobox-C0iW6a0r.js → combobox-CQwDmqgA.js} +4 -4
  76. package/dist/{combobox-C0iW6a0r.js.map → combobox-CQwDmqgA.js.map} +1 -1
  77. package/dist/command-line/cli.js +3 -3
  78. package/dist/{command-palette-DGzioeki.js → command-palette-Bkuv3e6o.js} +20 -5
  79. package/dist/command-palette-Bkuv3e6o.js.map +1 -0
  80. package/dist/components/ai-actions.js +1 -1
  81. package/dist/components/ai-agent-card.js +1 -1
  82. package/dist/components/ai-approval.js +1 -1
  83. package/dist/components/ai-code-block.js +1 -1
  84. package/dist/components/ai-conversation.js +2 -2
  85. package/dist/components/ai-info-banner.js +1 -1
  86. package/dist/components/ai-message.js +1 -1
  87. package/dist/components/ai-mission-header.js +1 -1
  88. package/dist/components/ai-part-group.js +1 -1
  89. package/dist/components/ai-prompt-input.js +1 -1
  90. package/dist/components/ai-question.js +1 -1
  91. package/dist/components/ai-reasoning.js +1 -1
  92. package/dist/components/ai-response.js +1 -1
  93. package/dist/components/ai-shimmer.js +1 -1
  94. package/dist/components/ai-status-badge.js +1 -1
  95. package/dist/components/ai-streaming-text.js +2 -2
  96. package/dist/components/ai-subagent.js +1 -1
  97. package/dist/components/ai-suggestion.js +1 -1
  98. package/dist/components/ai-task-list.js +1 -1
  99. package/dist/components/ai-timeline.js +1 -1
  100. package/dist/components/ai-tool.js +1 -1
  101. package/dist/components/ai-usage-bar.js +1 -1
  102. package/dist/components/badge.js +1 -1
  103. package/dist/components/banner.js +1 -1
  104. package/dist/components/breadcrumbs.js +1 -1
  105. package/dist/components/button.js +1 -1
  106. package/dist/components/chart.js +2 -2
  107. package/dist/components/checkbox.js +1 -1
  108. package/dist/components/clipboard-text.js +1 -1
  109. package/dist/components/code.js +1 -1
  110. package/dist/components/collapsible.js +1 -1
  111. package/dist/components/combobox.js +1 -1
  112. package/dist/components/command-palette.js +1 -1
  113. package/dist/components/data-grid.js +1 -1
  114. package/dist/components/date-picker.js +1 -1
  115. package/dist/components/date-range-picker.js +1 -1
  116. package/dist/components/dialog.js +1 -1
  117. package/dist/components/dropdown.js +1 -1
  118. package/dist/components/empty.js +1 -1
  119. package/dist/components/field.js +1 -1
  120. package/dist/components/filters.js +1 -1
  121. package/dist/components/flow.js +1 -1
  122. package/dist/components/grid.js +1 -1
  123. package/dist/components/input.js +2 -2
  124. package/dist/components/label.js +1 -1
  125. package/dist/components/layer-card.js +1 -1
  126. package/dist/components/link.js +3 -3
  127. package/dist/components/link.js.map +1 -1
  128. package/dist/components/loader.js +2 -2
  129. package/dist/components/menubar.js +1 -1
  130. package/dist/components/meter.js +1 -1
  131. package/dist/components/pagination.js +1 -1
  132. package/dist/components/popover.js +1 -1
  133. package/dist/components/radio.js +1 -1
  134. package/dist/components/select.js +1 -1
  135. package/dist/components/sensitive-input.js +1 -1
  136. package/dist/components/sidebar.js +1 -1
  137. package/dist/components/signalflare-ai-logo.js +1 -1
  138. package/dist/components/sparkline.js +1 -1
  139. package/dist/components/stat-card.js +1 -1
  140. package/dist/components/surface.js +1 -1
  141. package/dist/components/switch.js +1 -1
  142. package/dist/components/table.js +1 -1
  143. package/dist/components/tabs.js +1 -1
  144. package/dist/components/text-roll.js +1 -1
  145. package/dist/components/text.js +1 -1
  146. package/dist/components/theme-toggle.js +1 -1
  147. package/dist/components/toast.js +1 -1
  148. package/dist/components/tooltip.js +1 -1
  149. package/dist/components/use-agent-harness.js +1 -1
  150. package/dist/{data-grid-CG76N_hK.js → data-grid-DDSFMHud.js} +136 -53
  151. package/dist/data-grid-DDSFMHud.js.map +1 -0
  152. package/dist/{date-picker-Dqg9L4xu.js → date-picker-O34AqG3f.js} +2 -2
  153. package/dist/{date-picker-Dqg9L4xu.js.map → date-picker-O34AqG3f.js.map} +1 -1
  154. package/dist/{date-range-picker-D75LLINc.js → date-range-picker-YKYvum_r.js} +29 -39
  155. package/dist/{date-range-picker-D75LLINc.js.map → date-range-picker-YKYvum_r.js.map} +1 -1
  156. package/dist/{dialog-CyHEQXEY.js → dialog-DYqu4aDO.js} +3 -3
  157. package/dist/{dialog-CyHEQXEY.js.map → dialog-DYqu4aDO.js.map} +1 -1
  158. package/dist/{dist-1-gcEL2L.js → dist-6AtBsaJE.js} +153 -47
  159. package/dist/dist-6AtBsaJE.js.map +1 -0
  160. package/dist/{dropdown-qnEYRFXZ.js → dropdown-XzbnRLYR.js} +15 -5
  161. package/dist/dropdown-XzbnRLYR.js.map +1 -0
  162. package/dist/{echart-DURZEyai.js → echart-DGBIVAv1.js} +23 -57
  163. package/dist/{echart-DURZEyai.js.map → echart-DGBIVAv1.js.map} +1 -1
  164. package/dist/{empty-D2TypIId.js → empty-C1tAkawe.js} +12 -7
  165. package/dist/empty-C1tAkawe.js.map +1 -0
  166. package/dist/{field-Y_UK1_Cg.js → field-DBpFzzBS.js} +3 -3
  167. package/dist/{field-Y_UK1_Cg.js.map → field-DBpFzzBS.js.map} +1 -1
  168. package/dist/{filters-Bw_U6ZTx.js → filters-SmEl93za.js} +10 -10
  169. package/dist/filters-SmEl93za.js.map +1 -0
  170. package/dist/{flow-BRsYUCJa.js → flow-BLzgbq1T.js} +6 -6
  171. package/dist/flow-BLzgbq1T.js.map +1 -0
  172. package/dist/genui.js +2 -2
  173. package/dist/genui.js.map +1 -1
  174. package/dist/{grid-qUAN9hFx.js → grid-CifjQL-5.js} +2 -2
  175. package/dist/{grid-qUAN9hFx.js.map → grid-CifjQL-5.js.map} +1 -1
  176. package/dist/{highlight-to-react-ClEfL81q.js → highlight-to-react-DN9dUCS2.js} +9 -15
  177. package/dist/highlight-to-react-DN9dUCS2.js.map +1 -0
  178. package/dist/index.js +72 -72
  179. package/dist/index.js.map +1 -1
  180. package/dist/{input-DddtBN-g.js → input-COmx2M_R.js} +5 -5
  181. package/dist/{input-DddtBN-g.js.map → input-COmx2M_R.js.map} +1 -1
  182. package/dist/{input-DXYUjGgD.js → input-GkfMQZC_.js} +3 -3
  183. package/dist/{input-DXYUjGgD.js.map → input-GkfMQZC_.js.map} +1 -1
  184. package/dist/{label-QtJxtJ4u.js → label-CiGZ464N.js} +3 -3
  185. package/dist/{label-QtJxtJ4u.js.map → label-CiGZ464N.js.map} +1 -1
  186. package/dist/{layer-card-BME0eljh.js → layer-card-8l8GuLQr.js} +2 -2
  187. package/dist/{layer-card-BME0eljh.js.map → layer-card-8l8GuLQr.js.map} +1 -1
  188. package/dist/layout-CWBE0qwx.js +6207 -0
  189. package/dist/layout-CWBE0qwx.js.map +1 -0
  190. package/dist/{link-provider-BUZKXaNE.js → link-provider-BSn8YJon.js} +2 -2
  191. package/dist/link-provider-BSn8YJon.js.map +1 -0
  192. package/dist/{loader-DAcc-Uag.js → loader-BEMz8pJO.js} +1 -1
  193. package/dist/{loader-DAcc-Uag.js.map → loader-BEMz8pJO.js.map} +1 -1
  194. package/dist/measured-text-CXkdw9Yr.js +305 -0
  195. package/dist/measured-text-CXkdw9Yr.js.map +1 -0
  196. package/dist/{menubar-C8NzAjfd.js → menubar-CoOr4ocj.js} +3 -3
  197. package/dist/{menubar-C8NzAjfd.js.map → menubar-CoOr4ocj.js.map} +1 -1
  198. package/dist/{meter-CpmTenEr.js → meter-Pf_VOl59.js} +2 -2
  199. package/dist/{meter-CpmTenEr.js.map → meter-Pf_VOl59.js.map} +1 -1
  200. package/dist/{pagination-BVqdlONY.js → pagination-DSY279Ta.js} +2 -2
  201. package/dist/{pagination-BVqdlONY.js.map → pagination-DSY279Ta.js.map} +1 -1
  202. package/dist/{popover-BRQZ2b6z.js → popover-BY-e9co1.js} +2 -2
  203. package/dist/{popover-BRQZ2b6z.js.map → popover-BY-e9co1.js.map} +1 -1
  204. package/dist/{radio-BNSwOt3B.js → radio-DZwL13j0.js} +2 -2
  205. package/dist/{radio-BNSwOt3B.js.map → radio-DZwL13j0.js.map} +1 -1
  206. package/dist/{select-1w2aebGQ.js → select-BFifYqHA.js} +6 -6
  207. package/dist/{select-1w2aebGQ.js.map → select-BFifYqHA.js.map} +1 -1
  208. package/dist/{sensitive-input-82Cez3vj.js → sensitive-input-DHLZcM73.js} +4 -4
  209. package/dist/{sensitive-input-82Cez3vj.js.map → sensitive-input-DHLZcM73.js.map} +1 -1
  210. package/dist/{sidebar-CAsCmSpM.js → sidebar-odGsdvG4.js} +6 -7
  211. package/dist/sidebar-odGsdvG4.js.map +1 -0
  212. package/dist/{signalflare-ai-logo-DDhxMJD6.js → signalflare-ai-logo-CNaDT_w8.js} +2 -2
  213. package/dist/{signalflare-ai-logo-DDhxMJD6.js.map → signalflare-ai-logo-CNaDT_w8.js.map} +1 -1
  214. package/dist/{skeleton-line-Do3UmGk9.js → skeleton-line-CxxYVTO2.js} +2 -2
  215. package/dist/{skeleton-line-Do3UmGk9.js.map → skeleton-line-CxxYVTO2.js.map} +1 -1
  216. package/dist/{sparkline-DdbeM4Ai.js → sparkline-BQ-4j2W2.js} +2 -2
  217. package/dist/{sparkline-DdbeM4Ai.js.map → sparkline-BQ-4j2W2.js.map} +1 -1
  218. package/dist/src/blocks/agent-harness/agent-harness.d.ts.map +1 -1
  219. package/dist/src/blocks/agent-harness/agent-harness.tsx +40 -16
  220. package/dist/src/blocks/commander/commander.tsx +15 -15
  221. package/dist/src/blocks/map-block/map-block.d.ts.map +1 -1
  222. package/dist/src/blocks/map-block/map-block.tsx +11 -7
  223. package/dist/src/components/ai-approval/ai-approval.d.ts.map +1 -1
  224. package/dist/src/components/ai-code-block/ai-code-block.d.ts +14 -13
  225. package/dist/src/components/ai-code-block/ai-code-block.d.ts.map +1 -1
  226. package/dist/src/components/ai-conversation/ai-conversation.d.ts +69 -37
  227. package/dist/src/components/ai-conversation/ai-conversation.d.ts.map +1 -1
  228. package/dist/src/components/ai-conversation/index.d.ts +2 -1
  229. package/dist/src/components/ai-conversation/index.d.ts.map +1 -1
  230. package/dist/src/components/ai-conversation/measurement-constants.d.ts +30 -0
  231. package/dist/src/components/ai-conversation/measurement-constants.d.ts.map +1 -0
  232. package/dist/src/components/ai-info-banner/ai-info-banner.d.ts.map +1 -1
  233. package/dist/src/components/ai-message/ai-message.d.ts +3 -0
  234. package/dist/src/components/ai-message/ai-message.d.ts.map +1 -1
  235. package/dist/src/components/ai-part-group/ai-part-group.d.ts.map +1 -1
  236. package/dist/src/components/ai-prompt-input/ai-prompt-input.d.ts +13 -3
  237. package/dist/src/components/ai-prompt-input/ai-prompt-input.d.ts.map +1 -1
  238. package/dist/src/components/ai-prompt-input/controller.d.ts.map +1 -1
  239. package/dist/src/components/ai-prompt-input/index.d.ts +1 -1
  240. package/dist/src/components/ai-prompt-input/index.d.ts.map +1 -1
  241. package/dist/src/components/ai-prompt-input/types.d.ts.map +1 -1
  242. package/dist/src/components/ai-question/ai-question.d.ts.map +1 -1
  243. package/dist/src/components/ai-reasoning/ai-reasoning.d.ts.map +1 -1
  244. package/dist/src/components/ai-response/ai-response.d.ts +12 -1
  245. package/dist/src/components/ai-response/ai-response.d.ts.map +1 -1
  246. package/dist/src/components/ai-streaming-text/ai-streaming-text.d.ts +27 -0
  247. package/dist/src/components/ai-streaming-text/ai-streaming-text.d.ts.map +1 -1
  248. package/dist/src/components/ai-streaming-text/index.d.ts +1 -1
  249. package/dist/src/components/ai-streaming-text/index.d.ts.map +1 -1
  250. package/dist/src/components/ai-subagent/ai-subagent.d.ts.map +1 -1
  251. package/dist/src/components/ai-task-list/ai-task-list.d.ts.map +1 -1
  252. package/dist/src/components/ai-tool/ai-tool.d.ts.map +1 -1
  253. package/dist/src/components/banner/banner.d.ts.map +1 -1
  254. package/dist/src/components/chart/echart.d.ts.map +1 -1
  255. package/dist/src/components/clipboard-text/clipboard-text.d.ts.map +1 -1
  256. package/dist/src/components/data-grid/data-grid.d.ts +2 -1
  257. package/dist/src/components/data-grid/data-grid.d.ts.map +1 -1
  258. package/dist/src/components/data-grid/features.d.ts +20 -0
  259. package/dist/src/components/data-grid/features.d.ts.map +1 -0
  260. package/dist/src/components/data-grid/types.d.ts +38 -7
  261. package/dist/src/components/data-grid/types.d.ts.map +1 -1
  262. package/dist/src/components/empty/empty.d.ts.map +1 -1
  263. package/dist/src/components/filters/filters.d.ts.map +1 -1
  264. package/dist/src/components/flow/use-children.d.ts +1 -1
  265. package/dist/src/components/link/link.d.ts.map +1 -1
  266. package/dist/src/components/sidebar/sidebar.d.ts +1 -1
  267. package/dist/src/components/signalflare-ai-logo/signalflare-ai-logo.d.ts.map +1 -1
  268. package/dist/src/components/stat-card/stat-card.d.ts +5 -0
  269. package/dist/src/components/stat-card/stat-card.d.ts.map +1 -1
  270. package/dist/src/components/text/text.d.ts +36 -1
  271. package/dist/src/components/text/text.d.ts.map +1 -1
  272. package/dist/src/components/text-roll/text-roll.d.ts.map +1 -1
  273. package/dist/src/components/theme-toggle/theme-toggle.d.ts.map +1 -1
  274. package/dist/src/components/toast/toast.d.ts.map +1 -1
  275. package/dist/src/components/tooltip/tooltip.d.ts.map +1 -1
  276. package/dist/src/index.d.ts +2 -2
  277. package/dist/src/index.d.ts.map +1 -1
  278. package/dist/src/utils/highlight-to-react.d.ts.map +1 -1
  279. package/dist/src/utils/index.d.ts +2 -0
  280. package/dist/src/utils/index.d.ts.map +1 -1
  281. package/dist/src/utils/measured-text.d.ts +40 -0
  282. package/dist/src/utils/measured-text.d.ts.map +1 -0
  283. package/dist/src/utils/use-measured-text.d.ts +59 -0
  284. package/dist/src/utils/use-measured-text.d.ts.map +1 -0
  285. package/dist/{stat-card-CEZscNh8.js → stat-card-Bspk4XFr.js} +30 -12
  286. package/dist/stat-card-Bspk4XFr.js.map +1 -0
  287. package/dist/styles/sf-binding.css +1 -1
  288. package/dist/styles/sf-standalone.css +2 -2
  289. package/dist/styles/shadcn.css +1 -1
  290. package/dist/styles/theme-minimal.css +9 -9
  291. package/dist/styles/theme-sf.css +14 -20
  292. package/dist/styles/theme-vesper.css +91 -0
  293. package/dist/{surface-BduI7Ehl.js → surface-CWdSFVUx.js} +3 -3
  294. package/dist/{surface-BduI7Ehl.js.map → surface-CWdSFVUx.js.map} +1 -1
  295. package/dist/{switch-CzZBRBL7.js → switch-TA4cByCJ.js} +5 -5
  296. package/dist/switch-TA4cByCJ.js.map +1 -0
  297. package/dist/{table-Rv4JMy0B.js → table-BM8JBGBs.js} +3 -3
  298. package/dist/{table-Rv4JMy0B.js.map → table-BM8JBGBs.js.map} +1 -1
  299. package/dist/{tabs-1cHrYoel.js → tabs-bnH2vGLv.js} +2 -2
  300. package/dist/{tabs-1cHrYoel.js.map → tabs-bnH2vGLv.js.map} +1 -1
  301. package/dist/{text-KJmGkwnf.js → text-iQ0YUFNg.js} +27 -6
  302. package/dist/text-iQ0YUFNg.js.map +1 -0
  303. package/dist/{text-roll-BZ3I1umc.js → text-roll-C3U2jd2u.js} +5 -2
  304. package/dist/text-roll-C3U2jd2u.js.map +1 -0
  305. package/dist/{theme-toggle-Bhu681D7.js → theme-toggle-BTVxD-fD.js} +10 -9
  306. package/dist/theme-toggle-BTVxD-fD.js.map +1 -0
  307. package/dist/{toast-Nw28a5Cx.js → toast-CgZVaAkw.js} +3 -3
  308. package/dist/{toast-Nw28a5Cx.js.map → toast-CgZVaAkw.js.map} +1 -1
  309. package/dist/{tooltip-Cb7QW-7H.js → tooltip-uobk6Oh-.js} +9 -3
  310. package/dist/tooltip-uobk6Oh-.js.map +1 -0
  311. package/dist/{use-agent-harness-BMyF8pTq.js → use-agent-harness-Dl8w6X5O.js} +3 -3
  312. package/dist/{use-agent-harness-BMyF8pTq.js.map → use-agent-harness-Dl8w6X5O.js.map} +1 -1
  313. package/dist/utils.js +4 -3
  314. package/package.json +27 -24
  315. package/scripts/component-registry/discovery.ts +11 -10
  316. package/scripts/component-registry/example-cleanup.ts +8 -8
  317. package/scripts/component-registry/index.ts +6 -6
  318. package/scripts/component-registry/schema-generator.ts +1 -1
  319. package/scripts/component-registry/sub-components.ts +35 -23
  320. package/scripts/component-registry/utils.ts +11 -11
  321. package/scripts/component-registry/variant-parser.ts +17 -15
  322. package/scripts/convert-demos-to-stories.ts +5 -5
  323. package/scripts/css-build.ts +1 -1
  324. package/scripts/theme-generator/config.ts +28 -146
  325. package/scripts/theme-generator/generate-css.ts +1 -2
  326. package/scripts/theme-generator/index.ts +0 -1
  327. package/scripts/theme-generator/migrate.ts +3 -3
  328. package/dist/ai-agent-card-BXHwhWAU.js.map +0 -1
  329. package/dist/ai-code-block-BgtIxtZZ.js.map +0 -1
  330. package/dist/ai-conversation-CArP7C8K.js +0 -184
  331. package/dist/ai-conversation-CArP7C8K.js.map +0 -1
  332. package/dist/ai-info-banner-uFxHHwBA.js.map +0 -1
  333. package/dist/ai-message-BjnFznXy.js.map +0 -1
  334. package/dist/ai-part-group-DBtgTgAn.js.map +0 -1
  335. package/dist/ai-prompt-input-CuluUzpf.js.map +0 -1
  336. package/dist/ai-reasoning-CnL6ZSr5.js.map +0 -1
  337. package/dist/ai-response-BEUg3xvd.js.map +0 -1
  338. package/dist/ai-streaming-text-CMfoThV0.js.map +0 -1
  339. package/dist/ai-subagent-DcPRqkAA.js.map +0 -1
  340. package/dist/ai-task-list-Da9zIm00.js.map +0 -1
  341. package/dist/ai-timeline-Cwu045IR.js.map +0 -1
  342. package/dist/ai-tool-Cn1O4xjP.js.map +0 -1
  343. package/dist/banner-B_6oBrsu.js.map +0 -1
  344. package/dist/chart-BK3sVPnD.js.map +0 -1
  345. package/dist/clipboard-text-ssybngLw.js.map +0 -1
  346. package/dist/command-palette-DGzioeki.js.map +0 -1
  347. package/dist/data-grid-CG76N_hK.js.map +0 -1
  348. package/dist/dist-1-gcEL2L.js.map +0 -1
  349. package/dist/dropdown-qnEYRFXZ.js.map +0 -1
  350. package/dist/empty-D2TypIId.js.map +0 -1
  351. package/dist/filters-Bw_U6ZTx.js.map +0 -1
  352. package/dist/flow-BRsYUCJa.js.map +0 -1
  353. package/dist/highlight-to-react-ClEfL81q.js.map +0 -1
  354. package/dist/link-provider-BUZKXaNE.js.map +0 -1
  355. package/dist/sidebar-CAsCmSpM.js.map +0 -1
  356. package/dist/stat-card-CEZscNh8.js.map +0 -1
  357. package/dist/styles/theme-blue-tint.css +0 -98
  358. package/dist/switch-CzZBRBL7.js.map +0 -1
  359. package/dist/text-KJmGkwnf.js.map +0 -1
  360. package/dist/text-roll-BZ3I1umc.js.map +0 -1
  361. package/dist/theme-toggle-Bhu681D7.js.map +0 -1
  362. package/dist/tooltip-Cb7QW-7H.js.map +0 -1
@@ -1,11 +1,13 @@
1
1
  "use client";
2
- import { t as cn } from "./cn-YROP2_ox.js";
3
- import { t as Tooltip } from "./tooltip-Cb7QW-7H.js";
4
- import { t as Button } from "./button-De0267YU.js";
2
+ import { t as cn } from "./cn-CmAOpn49.js";
3
+ import { t as Text } from "./text-iQ0YUFNg.js";
4
+ import { t as Tooltip } from "./tooltip-uobk6Oh-.js";
5
+ import { t as Button } from "./button-BHOgXJRU.js";
5
6
  import { createContext, memo, useCallback, useContext, useEffect, useState } from "react";
6
7
  import { jsx, jsxs } from "react/jsx-runtime";
7
8
  import { CaretLeftIcon, CaretRightIcon, FileIcon, FileTextIcon, ImageIcon, XIcon } from "@phosphor-icons/react";
8
9
  //#region src/components/ai-message/ai-message.tsx
10
+ var AiMessageContext = createContext(null);
9
11
  var SF_AI_MESSAGE_VARIANTS = { from: {
10
12
  user: {
11
13
  classes: "ml-auto w-full max-w-[70%] justify-end is-user",
@@ -38,19 +40,36 @@ var SF_AI_MESSAGE_DEFAULT_VARIANTS = { from: "assistant" };
38
40
  * ```
39
41
  */
40
42
  function AiMessage({ className, from, ...props }) {
41
- return /* @__PURE__ */ jsx("div", {
42
- className: cn("group flex flex-col gap-2 p-4", SF_AI_MESSAGE_VARIANTS.from[from].classes, className),
43
- ...props
43
+ return /* @__PURE__ */ jsx(AiMessageContext.Provider, {
44
+ value: { from },
45
+ children: /* @__PURE__ */ jsx("div", {
46
+ className: cn("group flex flex-col gap-2 p-4", SF_AI_MESSAGE_VARIANTS.from[from].classes, className),
47
+ ...props
48
+ })
44
49
  });
45
50
  }
46
51
  /**
52
+ * Detects if children is plain text (string or number) suitable for shrink wrapping.
53
+ */
54
+ function isPlainText(children) {
55
+ return typeof children === "string" || typeof children === "number";
56
+ }
57
+ /**
47
58
  * Content bubble. Applies user/assistant bubble styles via group selectors.
59
+ *
60
+ * For user messages with plain text content, uses `Text wrap="shrink"` so short
61
+ * messages don't stretch to the full 70% container width.
48
62
  */
49
63
  function AiMessageContent({ children, className, ...props }) {
64
+ const shouldShrink = useContext(AiMessageContext)?.from === "user" && isPlainText(children);
50
65
  return /* @__PURE__ */ jsx("div", {
51
- className: cn("flex w-fit max-w-full flex-col gap-2 overflow-hidden text-sm", "group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-sf-control group-[.is-user]:px-4 group-[.is-user]:py-3", "group-[.is-assistant]:text-sf-default", className),
66
+ className: cn("flex w-fit max-w-full flex-col gap-2 overflow-hidden whitespace-pre-wrap text-sm", "group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-sf-control group-[.is-user]:px-4 group-[.is-user]:py-3", "group-[.is-assistant]:text-sf-default", className),
52
67
  ...props,
53
- children
68
+ children: shouldShrink ? /* @__PURE__ */ jsx(Text, {
69
+ wrap: "shrink",
70
+ as: "span",
71
+ children
72
+ }) : children
54
73
  });
55
74
  }
56
75
  /**
@@ -204,7 +223,7 @@ var DOC_EXTS = new Set([
204
223
  "md",
205
224
  "rtf"
206
225
  ]);
207
- var EXT_RE = /\.[^/.]+$/;
226
+ var EXT_RE = /\.[^/.]+$/u;
208
227
  function fileIcon(filename) {
209
228
  const ext = filename.split(".").pop()?.toLowerCase() ?? "";
210
229
  if (IMAGE_EXTS.has(ext)) return /* @__PURE__ */ jsx(ImageIcon, { className: "size-3.5" });
@@ -283,4 +302,4 @@ AiMessageAttachment.displayName = "AiMessageAttachment";
283
302
  //#endregion
284
303
  export { AiMessageAttachments as a, AiMessageBranchSelector as c, SF_AI_MESSAGE_DEFAULT_VARIANTS as d, SF_AI_MESSAGE_VARIANTS as f, AiMessageAttachment as i, AiMessageContent as l, AiMessageAction as n, AiMessageBranch as o, AiMessageActions as r, AiMessageBranchContent as s, AiMessage as t, AiMessageToolbar as u };
285
304
 
286
- //# sourceMappingURL=ai-message-BjnFznXy.js.map
305
+ //# sourceMappingURL=ai-message-Ci3gwM7G.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-message-Ci3gwM7G.js","names":[],"sources":["../src/components/ai-message/ai-message.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n FileIcon,\n FileTextIcon,\n ImageIcon,\n CaretLeftIcon,\n CaretRightIcon,\n XIcon,\n} from \"@phosphor-icons/react\";\nimport type {\n ComponentProps,\n HTMLAttributes,\n ReactElement,\n ReactNode,\n} from \"react\";\nimport {\n createContext,\n memo,\n useCallback,\n useContext,\n useEffect,\n useState,\n} from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"../button\";\nimport { Text } from \"../text\";\nimport { Tooltip } from \"../tooltip\";\n\n// ─── Context ─────────────────────────────────────────────────────────────────\n\ninterface AiMessageContextValue {\n from: SFAiMessageFrom;\n}\n\nconst AiMessageContext = createContext<AiMessageContextValue | null>(null);\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_MESSAGE_VARIANTS = {\n from: {\n user: {\n classes: \"ml-auto w-full max-w-[70%] justify-end is-user\",\n description: \"User message — right-aligned with max width\",\n },\n assistant: {\n classes: \"w-full is-assistant\",\n description: \"Assistant message — full width left-aligned\",\n },\n system: {\n classes: \"w-full is-system\",\n description: \"System message — full width\",\n },\n },\n} as const;\n\nexport const SF_AI_MESSAGE_DEFAULT_VARIANTS = {\n from: \"assistant\",\n} as const;\n\nexport type SFAiMessageFrom = keyof typeof SF_AI_MESSAGE_VARIANTS.from;\n\n// ─── AiMessage ───────────────────────────────────────────────────────────────\n\nexport type AiMessageProps = HTMLAttributes<HTMLDivElement> & {\n /** Message sender role — controls layout and group class. */\n from: SFAiMessageFrom;\n};\n\n/**\n * Root message container. Sets layout, group class, and role context.\n *\n * @example\n * ```tsx\n * <AiMessage from=\"user\">\n * <AiMessageContent>Hello!</AiMessageContent>\n * </AiMessage>\n * <AiMessage from=\"assistant\">\n * <AiMessageContent><AiResponse>{text}</AiResponse></AiMessageContent>\n * <AiMessageToolbar>\n * <AiActions><AiAction tooltip=\"Copy\">...</AiAction></AiActions>\n * </AiMessageToolbar>\n * </AiMessage>\n * ```\n */\nexport function AiMessage({ className, from, ...props }: AiMessageProps) {\n return (\n <AiMessageContext.Provider value={{ from }}>\n <div\n className={cn(\n \"group flex flex-col gap-2 p-4\",\n SF_AI_MESSAGE_VARIANTS.from[from].classes,\n className\n )}\n {...props}\n />\n </AiMessageContext.Provider>\n );\n}\n\n// ─── AiMessageContent ────────────────────────────────────────────────────────\n\nexport type AiMessageContentProps = HTMLAttributes<HTMLDivElement>;\n\n/**\n * Detects if children is plain text (string or number) suitable for shrink wrapping.\n */\nfunction isPlainText(children: ReactNode): children is string | number {\n return typeof children === \"string\" || typeof children === \"number\";\n}\n\n/**\n * Content bubble. Applies user/assistant bubble styles via group selectors.\n *\n * For user messages with plain text content, uses `Text wrap=\"shrink\"` so short\n * messages don't stretch to the full 70% container width.\n */\nexport function AiMessageContent({\n children,\n className,\n ...props\n}: AiMessageContentProps) {\n const isUserBubble = useContext(AiMessageContext)?.from === \"user\";\n const shouldShrink = isUserBubble && isPlainText(children);\n\n return (\n <div\n className={cn(\n \"flex w-fit max-w-full flex-col gap-2 overflow-hidden whitespace-pre-wrap text-sm\",\n // User bubble\n \"group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-sf-control group-[.is-user]:px-4 group-[.is-user]:py-3\",\n // Assistant text\n \"group-[.is-assistant]:text-sf-default\",\n className\n )}\n {...props}\n >\n {shouldShrink ? (\n <Text wrap=\"shrink\" as=\"span\">\n {children}\n </Text>\n ) : (\n children\n )}\n </div>\n );\n}\n\n// ─── AiMessageToolbar ────────────────────────────────────────────────────────\n\nexport type AiMessageToolbarProps = ComponentProps<\"div\">;\n\n/**\n * Bottom toolbar row for action buttons.\n */\nexport function AiMessageToolbar({\n className,\n children,\n ...props\n}: AiMessageToolbarProps) {\n return (\n <div\n className={cn(\n \"flex w-full items-center justify-between gap-4\",\n className\n )}\n {...props}\n >\n {children}\n </div>\n );\n}\n\n// ─── AiMessageActions ────────────────────────────────────────────────────────\n\nexport type AiMessageActionsProps = ComponentProps<\"div\">;\n\n/** Flex row of message action buttons. */\nexport function AiMessageActions({\n className,\n children,\n ...props\n}: AiMessageActionsProps) {\n return (\n <div className={cn(\"flex items-center gap-1\", className)} {...props}>\n {children}\n </div>\n );\n}\n\n// ─── AiMessageAction ─────────────────────────────────────────────────────────\n\nexport type AiMessageActionProps = ComponentProps<typeof Button> & {\n /** Tooltip text and accessible label. */\n tooltip?: string;\n /** Override accessible label separately from tooltip. */\n label?: string;\n};\n\n/** Single icon action button with optional tooltip. */\nexport function AiMessageAction({\n tooltip,\n label,\n children,\n className,\n variant = \"ghost\",\n size = \"sm\",\n ...props\n}: AiMessageActionProps) {\n const button = (\n <Button\n className={cn(\"text-sf-subtle hover:text-sf-default\", className)}\n size={size}\n variant={variant}\n {...props}\n >\n {children}\n {(label ?? tooltip) && (\n <span className=\"sr-only\">{label ?? tooltip}</span>\n )}\n </Button>\n );\n\n if (tooltip) {\n return <Tooltip content={tooltip}>{button}</Tooltip>;\n }\n return button;\n}\n\n// ─── Branch context ───────────────────────────────────────────────────────────\n\ninterface AiMessageBranchContextValue {\n currentBranch: number;\n totalBranches: number;\n goToPrevious: () => void;\n goToNext: () => void;\n branches: ReactElement[];\n setBranches: (branches: ReactElement[]) => void;\n}\n\nconst AiMessageBranchContext =\n createContext<AiMessageBranchContextValue | null>(null);\n\nfunction useAiMessageBranch() {\n const ctx = useContext(AiMessageBranchContext);\n if (!ctx)\n throw new Error(\n \"AiMessageBranch sub-components must be used within <AiMessageBranch>\"\n );\n return ctx;\n}\n\n// ─── AiMessageBranch ─────────────────────────────────────────────────────────\n\nexport type AiMessageBranchProps = HTMLAttributes<HTMLDivElement> & {\n defaultBranch?: number;\n onBranchChange?: (index: number) => void;\n};\n\n/**\n * Multi-branch message container. Wraps multiple alternative responses and\n * provides prev/next navigation.\n */\nexport function AiMessageBranch({\n defaultBranch = 0,\n onBranchChange,\n className,\n ...props\n}: AiMessageBranchProps) {\n const [currentBranch, setCurrentBranch] = useState(defaultBranch);\n const [branches, setBranches] = useState<ReactElement[]>([]);\n\n const handleChange = useCallback(\n (next: number) => {\n setCurrentBranch(next);\n onBranchChange?.(next);\n },\n [onBranchChange]\n );\n\n const goToPrevious = useCallback(() => {\n handleChange(currentBranch > 0 ? currentBranch - 1 : branches.length - 1);\n }, [currentBranch, branches.length, handleChange]);\n\n const goToNext = useCallback(() => {\n handleChange(currentBranch < branches.length - 1 ? currentBranch + 1 : 0);\n }, [currentBranch, branches.length, handleChange]);\n\n return (\n <AiMessageBranchContext.Provider\n value={{\n currentBranch,\n totalBranches: branches.length,\n goToPrevious,\n goToNext,\n branches,\n setBranches,\n }}\n >\n <div className={cn(\"grid w-full gap-2\", className)} {...props} />\n </AiMessageBranchContext.Provider>\n );\n}\n\n// ─── AiMessageBranchContent ──────────────────────────────────────────────────\n\nexport type AiMessageBranchContentProps = HTMLAttributes<HTMLDivElement>;\n\n/** Renders only the active branch child. */\nexport function AiMessageBranchContent({\n children,\n ...props\n}: AiMessageBranchContentProps) {\n const { currentBranch, setBranches, branches } = useAiMessageBranch();\n const arr = Array.isArray(children) ? children : [children];\n\n useEffect(() => {\n if (branches.length !== arr.length) setBranches(arr as ReactElement[]);\n }, [arr, branches, setBranches]);\n\n return arr.map((branch, i) => (\n <div\n className={cn(\n \"grid gap-2 overflow-hidden\",\n i !== currentBranch && \"hidden\"\n )}\n key={(branch as ReactElement)?.key ?? i}\n {...props}\n >\n {branch}\n </div>\n ));\n}\n\n// ─── AiMessageBranchSelector ─────────────────────────────────────────────────\n\nexport type AiMessageBranchSelectorProps = ComponentProps<\"div\">;\n\n/** Prev/next navigator for branches. Hidden when there's only one branch. */\nexport function AiMessageBranchSelector({\n className,\n ...props\n}: AiMessageBranchSelectorProps) {\n const { totalBranches, currentBranch, goToPrevious, goToNext } =\n useAiMessageBranch();\n\n if (totalBranches <= 1) return null;\n\n return (\n <div className={cn(\"flex items-center gap-0.5\", className)} {...props}>\n <Button\n aria-label=\"Previous branch\"\n disabled={totalBranches <= 1}\n onClick={goToPrevious}\n size=\"sm\"\n variant=\"ghost\"\n >\n <CaretLeftIcon className=\"size-3\" />\n </Button>\n <span className=\"tabular-nums text-sf-subtle text-xs\">\n {currentBranch + 1} / {totalBranches}\n </span>\n <Button\n aria-label=\"Next branch\"\n disabled={totalBranches <= 1}\n onClick={goToNext}\n size=\"sm\"\n variant=\"ghost\"\n >\n <CaretRightIcon className=\"size-3\" />\n </Button>\n </div>\n );\n}\n\n// ─── Attachment helpers ───────────────────────────────────────────────────────\n\nconst IMAGE_EXTS = new Set([\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"svg\", \"ico\"]);\nconst DOC_EXTS = new Set([\"pdf\", \"doc\", \"docx\", \"txt\", \"md\", \"rtf\"]);\nconst EXT_RE = /\\.[^/.]+$/u;\n\nfunction fileIcon(filename: string): ReactNode {\n const ext = filename.split(\".\").pop()?.toLowerCase() ?? \"\";\n if (IMAGE_EXTS.has(ext)) return <ImageIcon className=\"size-3.5\" />;\n if (DOC_EXTS.has(ext)) return <FileTextIcon className=\"size-3.5\" />;\n return <FileIcon className=\"size-3.5\" />;\n}\n\n// ─── AiMessageAttachments ────────────────────────────────────────────────────\n\nexport type AiMessageAttachmentsProps = ComponentProps<\"div\">;\n\n/** Wrapping flex container for attachment chips. */\nexport function AiMessageAttachments({\n children,\n className,\n ...props\n}: AiMessageAttachmentsProps) {\n if (!children) return null;\n return (\n <div\n className={cn(\n \"ml-auto flex w-fit flex-wrap items-start gap-2\",\n className\n )}\n {...props}\n >\n {children}\n </div>\n );\n}\n\n// ─── AiMessageAttachment ─────────────────────────────────────────────────────\n\nexport type AiMessageAttachmentProps = HTMLAttributes<HTMLDivElement> & {\n /** Filename for display and icon selection. */\n filename: string;\n /** Object URL or data URL for images. */\n url?: string;\n /** MIME type — used to detect images. */\n mediaType?: string;\n /** Called when the remove button is clicked. */\n onRemove?: () => void;\n};\n\n/**\n * Single file attachment chip. Shows image thumbnail or file badge.\n */\nexport const AiMessageAttachment = memo(\n ({\n filename,\n url,\n mediaType,\n onRemove,\n className,\n ...props\n }: AiMessageAttachmentProps) => {\n const isImage = mediaType?.startsWith(\"image/\") && url;\n\n if (isImage) {\n return (\n <div\n className={cn(\n \"group relative size-24 overflow-hidden rounded-lg\",\n \"animate-in fade-in-0 slide-in-from-bottom-2 duration-200\",\n className\n )}\n {...props}\n >\n <img\n alt={filename || \"attachment\"}\n className=\"size-full object-cover\"\n src={url}\n />\n {onRemove && (\n <Button\n aria-label=\"Remove attachment\"\n className=\"absolute top-2 right-2 size-6 rounded-full bg-sf-overlay/80 p-0 opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100\"\n onClick={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n >\n <XIcon className=\"size-3\" />\n </Button>\n )}\n </div>\n );\n }\n\n const ext = filename.split(\".\").pop()?.toUpperCase() ?? \"FILE\";\n const name = filename.replace(EXT_RE, \"\");\n\n return (\n <div\n className={cn(\n \"group relative inline-flex items-center gap-2 rounded-lg bg-sf-tint/50 px-3 py-2 transition-colors hover:bg-sf-tint\",\n \"animate-in fade-in-0 slide-in-from-bottom-2 duration-200\",\n className\n )}\n {...props}\n >\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-md bg-sf-control\">\n {fileIcon(filename)}\n </div>\n <div className=\"flex flex-col\">\n <span className=\"max-w-[200px] truncate font-medium text-sf-default text-sm\">\n {name || \"File\"}\n </span>\n <span className=\"w-fit rounded bg-sf-tint px-1.5 py-0.5 font-semibold text-sf-subtle text-[10px] uppercase\">\n {ext}\n </span>\n </div>\n {onRemove && (\n <Button\n aria-label=\"Remove attachment\"\n className=\"absolute top-1 right-1 size-5 rounded-full bg-sf-overlay/80 p-0 opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100\"\n onClick={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n size=\"sm\"\n type=\"button\"\n variant=\"ghost\"\n >\n <XIcon className=\"size-3\" />\n </Button>\n )}\n </div>\n );\n }\n);\n\nAiMessageAttachment.displayName = \"AiMessageAttachment\";\n"],"mappings":";;;;;;;;;AAoCA,IAAM,mBAAmB,cAA4C,IAAI;AAIzE,IAAa,yBAAyB,EACpC,MAAM;CACJ,MAAM;EACJ,SAAS;EACT,aAAa;CACf;CACA,WAAW;EACT,SAAS;EACT,aAAa;CACf;CACA,QAAQ;EACN,SAAS;EACT,aAAa;CACf;AACF,EACF;AAEA,IAAa,iCAAiC,EAC5C,MAAM,YACR;;;;;;;;;;;;;;;;;AA2BA,SAAgB,UAAU,EAAE,WAAW,MAAM,GAAG,SAAyB;CACvE,OACE,oBAAC,iBAAiB,UAAlB;EAA2B,OAAO,EAAE,KAAK;YACvC,oBAAC,OAAD;GACE,WAAW,GACT,iCACA,uBAAuB,KAAK,MAAM,SAClC,SACF;GACA,GAAI;EACL,CAAA;CACwB,CAAA;AAE/B;;;;AASA,SAAS,YAAY,UAAkD;CACrE,OAAO,OAAO,aAAa,YAAY,OAAO,aAAa;AAC7D;;;;;;;AAQA,SAAgB,iBAAiB,EAC/B,UACA,WACA,GAAG,SACqB;CAExB,MAAM,eADe,WAAW,gBAAgB,GAAG,SAAS,UACvB,YAAY,QAAQ;CAEzD,OACE,oBAAC,OAAD;EACE,WAAW,GACT,oFAEA,mIAEA,yCACA,SACF;EACA,GAAI;YAEH,eACC,oBAAC,MAAD;GAAM,MAAK;GAAS,IAAG;GACpB;EACG,CAAA,IAEN;CAEC,CAAA;AAET;;;;AASA,SAAgB,iBAAiB,EAC/B,WACA,UACA,GAAG,SACqB;CACxB,OACE,oBAAC,OAAD;EACE,WAAW,GACT,kDACA,SACF;EACA,GAAI;EAEH;CACE,CAAA;AAET;;AAOA,SAAgB,iBAAiB,EAC/B,WACA,UACA,GAAG,SACqB;CACxB,OACE,oBAAC,OAAD;EAAK,WAAW,GAAG,2BAA2B,SAAS;EAAG,GAAI;EAC3D;CACE,CAAA;AAET;;AAYA,SAAgB,gBAAgB,EAC9B,SACA,OACA,UACA,WACA,UAAU,SACV,OAAO,MACP,GAAG,SACoB;CACvB,MAAM,SACJ,qBAAC,QAAD;EACE,WAAW,GAAG,wCAAwC,SAAS;EACzD;EACG;EACT,GAAI;YAJN,CAMG,WACC,SAAS,YACT,oBAAC,QAAD;GAAM,WAAU;aAAW,SAAS;EAAc,CAAA,CAE9C;;CAGV,IAAI,SACF,OAAO,oBAAC,SAAD;EAAS,SAAS;YAAU;CAAgB,CAAA;CAErD,OAAO;AACT;AAaA,IAAM,yBACJ,cAAkD,IAAI;AAExD,SAAS,qBAAqB;CAC5B,MAAM,MAAM,WAAW,sBAAsB;CAC7C,IAAI,CAAC,KACH,MAAM,IAAI,MACR,sEACF;CACF,OAAO;AACT;;;;;AAaA,SAAgB,gBAAgB,EAC9B,gBAAgB,GAChB,gBACA,WACA,GAAG,SACoB;CACvB,MAAM,CAAC,eAAe,oBAAoB,SAAS,aAAa;CAChE,MAAM,CAAC,UAAU,eAAe,SAAyB,CAAC,CAAC;CAE3D,MAAM,eAAe,aAClB,SAAiB;EAChB,iBAAiB,IAAI;EACrB,iBAAiB,IAAI;CACvB,GACA,CAAC,cAAc,CACjB;CAEA,MAAM,eAAe,kBAAkB;EACrC,aAAa,gBAAgB,IAAI,gBAAgB,IAAI,SAAS,SAAS,CAAC;CAC1E,GAAG;EAAC;EAAe,SAAS;EAAQ;CAAY,CAAC;CAEjD,MAAM,WAAW,kBAAkB;EACjC,aAAa,gBAAgB,SAAS,SAAS,IAAI,gBAAgB,IAAI,CAAC;CAC1E,GAAG;EAAC;EAAe,SAAS;EAAQ;CAAY,CAAC;CAEjD,OACE,oBAAC,uBAAuB,UAAxB;EACE,OAAO;GACL;GACA,eAAe,SAAS;GACxB;GACA;GACA;GACA;EACF;YAEA,oBAAC,OAAD;GAAK,WAAW,GAAG,qBAAqB,SAAS;GAAG,GAAI;EAAQ,CAAA;CACjC,CAAA;AAErC;;AAOA,SAAgB,uBAAuB,EACrC,UACA,GAAG,SAC2B;CAC9B,MAAM,EAAE,eAAe,aAAa,aAAa,mBAAmB;CACpE,MAAM,MAAM,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAQ;CAE1D,gBAAgB;EACd,IAAI,SAAS,WAAW,IAAI,QAAQ,YAAY,GAAqB;CACvE,GAAG;EAAC;EAAK;EAAU;CAAW,CAAC;CAE/B,OAAO,IAAI,KAAK,QAAQ,MACtB,oBAAC,OAAD;EACE,WAAW,GACT,8BACA,MAAM,iBAAiB,QACzB;EAEA,GAAI;YAEH;CACE,GAJG,QAAyB,OAAO,CAInC,CACN;AACH;;AAOA,SAAgB,wBAAwB,EACtC,WACA,GAAG,SAC4B;CAC/B,MAAM,EAAE,eAAe,eAAe,cAAc,aAClD,mBAAmB;CAErB,IAAI,iBAAiB,GAAG,OAAO;CAE/B,OACE,qBAAC,OAAD;EAAK,WAAW,GAAG,6BAA6B,SAAS;EAAG,GAAI;YAAhE;GACE,oBAAC,QAAD;IACE,cAAW;IACX,UAAU,iBAAiB;IAC3B,SAAS;IACT,MAAK;IACL,SAAQ;cAER,oBAAC,eAAD,EAAe,WAAU,SAAU,CAAA;GAC7B,CAAA;GACR,qBAAC,QAAD;IAAM,WAAU;cAAhB;KACG,gBAAgB;KAAE;KAAI;IACnB;;GACN,oBAAC,QAAD;IACE,cAAW;IACX,UAAU,iBAAiB;IAC3B,SAAS;IACT,MAAK;IACL,SAAQ;cAER,oBAAC,gBAAD,EAAgB,WAAU,SAAU,CAAA;GAC9B,CAAA;EACL;;AAET;AAIA,IAAM,aAAa,IAAI,IAAI;CAAC;CAAO;CAAQ;CAAO;CAAO;CAAQ;CAAO;AAAK,CAAC;AAC9E,IAAM,WAAW,IAAI,IAAI;CAAC;CAAO;CAAO;CAAQ;CAAO;CAAM;AAAK,CAAC;AACnE,IAAM,SAAS;AAEf,SAAS,SAAS,UAA6B;CAC7C,MAAM,MAAM,SAAS,MAAM,GAAG,EAAE,IAAI,GAAG,YAAY,KAAK;CACxD,IAAI,WAAW,IAAI,GAAG,GAAG,OAAO,oBAAC,WAAD,EAAW,WAAU,WAAY,CAAA;CACjE,IAAI,SAAS,IAAI,GAAG,GAAG,OAAO,oBAAC,cAAD,EAAc,WAAU,WAAY,CAAA;CAClE,OAAO,oBAAC,UAAD,EAAU,WAAU,WAAY,CAAA;AACzC;;AAOA,SAAgB,qBAAqB,EACnC,UACA,WACA,GAAG,SACyB;CAC5B,IAAI,CAAC,UAAU,OAAO;CACtB,OACE,oBAAC,OAAD;EACE,WAAW,GACT,kDACA,SACF;EACA,GAAI;EAEH;CACE,CAAA;AAET;;;;AAkBA,IAAa,sBAAsB,MAChC,EACC,UACA,KACA,WACA,UACA,WACA,GAAG,YAC2B;CAG9B,IAFgB,WAAW,WAAW,QAAQ,KAAK,KAGjD,OACE,qBAAC,OAAD;EACE,WAAW,GACT,qDACA,4DACA,SACF;EACA,GAAI;YANN,CAQE,oBAAC,OAAD;GACE,KAAK,YAAY;GACjB,WAAU;GACV,KAAK;EACN,CAAA,GACA,YACC,oBAAC,QAAD;GACE,cAAW;GACX,WAAU;GACV,UAAU,MAAM;IACd,EAAE,gBAAgB;IAClB,SAAS;GACX;GACA,MAAK;GACL,MAAK;GACL,SAAQ;aAER,oBAAC,OAAD,EAAO,WAAU,SAAU,CAAA;EACrB,CAAA,CAEP;;CAIT,MAAM,MAAM,SAAS,MAAM,GAAG,EAAE,IAAI,GAAG,YAAY,KAAK;CACxD,MAAM,OAAO,SAAS,QAAQ,QAAQ,EAAE;CAExC,OACE,qBAAC,OAAD;EACE,WAAW,GACT,uHACA,4DACA,SACF;EACA,GAAI;YANN;GAQE,oBAAC,OAAD;IAAK,WAAU;cACZ,SAAS,QAAQ;GACf,CAAA;GACL,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAAC,QAAD;KAAM,WAAU;eACb,QAAQ;IACL,CAAA,GACN,oBAAC,QAAD;KAAM,WAAU;eACb;IACG,CAAA,CACH;;GACJ,YACC,oBAAC,QAAD;IACE,cAAW;IACX,WAAU;IACV,UAAU,MAAM;KACd,EAAE,gBAAgB;KAClB,SAAS;IACX;IACA,MAAK;IACL,MAAK;IACL,SAAQ;cAER,oBAAC,OAAD,EAAO,WAAU,SAAU,CAAA;GACrB,CAAA;EAEP;;AAET,CACF;AAEA,oBAAoB,cAAc"}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { t as cn } from "./cn-YROP2_ox.js";
2
+ import { t as cn } from "./cn-CmAOpn49.js";
3
3
  import { forwardRef, useEffect, useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { CheckCircleIcon, ClockIcon, CoinsIcon, CpuIcon, SpinnerGapIcon, StopCircleIcon, UsersIcon } from "@phosphor-icons/react";
@@ -168,4 +168,4 @@ AiMissionHeader.displayName = "AiMissionHeader";
168
168
  //#endregion
169
169
  export { SF_AI_MISSION_HEADER_DEFAULT_VARIANTS as n, SF_AI_MISSION_HEADER_VARIANTS as r, AiMissionHeader as t };
170
170
 
171
- //# sourceMappingURL=ai-mission-header-08__gULL.js.map
171
+ //# sourceMappingURL=ai-mission-header-CaBc19-t.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ai-mission-header-08__gULL.js","names":[],"sources":["../src/components/ai-mission-header/ai-mission-header.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n CheckCircleIcon,\n ClockIcon,\n CoinsIcon,\n CpuIcon,\n SpinnerGapIcon,\n StopCircleIcon,\n UsersIcon,\n} from \"@phosphor-icons/react\";\nimport type { ComponentProps } from \"react\";\nimport { forwardRef, useEffect, useState } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport type { AgentTaskList, AgentUsage } from \"../use-agent-harness\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_MISSION_HEADER_VARIANTS = {\n variant: {\n default: { classes: \"\", description: \"Full header with all stats\" },\n compact: { classes: \"\", description: \"Single-row compact header\" },\n },\n} as const;\n\nexport const SF_AI_MISSION_HEADER_DEFAULT_VARIANTS = {\n variant: \"default\",\n} as const;\n\nexport type SFAiMissionHeaderVariant =\n keyof typeof SF_AI_MISSION_HEADER_VARIANTS.variant;\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type AiMissionHeaderProps = Omit<ComponentProps<\"div\">, \"title\"> & {\n /** The mission goal or title. */\n title?: string;\n /** Task list for progress calculation. */\n taskList?: AgentTaskList | null;\n /** Token/cost usage summary. */\n usage?: AgentUsage | null;\n /** Number of agents currently active. */\n activeAgentCount?: number;\n /** Total number of agents that have run or are running. */\n totalAgentCount?: number;\n /** Wall-clock ms when the mission started. */\n startedAt?: number;\n /** Wall-clock ms when the mission ended. Undefined while still running. */\n endedAt?: number;\n /** Whether the mission is currently running. */\n isRunning?: boolean;\n /** Called when the user clicks the abort/stop button. */\n onAbort?: () => void;\n /** Display variant. @default \"default\" */\n variant?: SFAiMissionHeaderVariant;\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction formatElapsed(ms: number): string {\n const s = Math.floor(ms / 1000);\n if (s < 60) return `${s}s`;\n const m = Math.floor(s / 60);\n const rem = s % 60;\n return `${m}m ${rem.toString().padStart(2, \"0\")}s`;\n}\n\nfunction formatCost(cost: number): string {\n if (cost < 0.01) return `${(cost * 100).toFixed(2)}¢`;\n return `$${cost.toFixed(4)}`;\n}\n\nfunction formatTokens(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;\n return String(n);\n}\n\n/** Live elapsed time that ticks every ~500ms while running. */\nfunction useElapsed(startedAt?: number, endedAt?: number, isRunning = false) {\n const [elapsed, setElapsed] = useState<number>(\n startedAt ? (endedAt ?? Date.now()) - startedAt : 0\n );\n\n useEffect(() => {\n if (!startedAt || !isRunning) {\n if (startedAt) setElapsed((endedAt ?? Date.now()) - startedAt);\n return;\n }\n\n // Tick at 2Hz — efficient for a human-readable seconds display\n const id = setInterval(() => {\n setElapsed(Date.now() - startedAt);\n }, 500);\n return () => clearInterval(id);\n }, [startedAt, endedAt, isRunning]);\n\n return elapsed;\n}\n\n// ─── Progress bar ─────────────────────────────────────────────────────────────\n\nfunction MissionProgress({ taskList }: { taskList?: AgentTaskList | null }) {\n if (!taskList?.tasks.length) return null;\n\n const total = taskList.tasks.length;\n const done = taskList.tasks.filter(\n (t) => t.status === \"completed\" || t.status === \"cancelled\"\n ).length;\n const inProgress = taskList.tasks.filter(\n (t) => t.status === \"in_progress\"\n ).length;\n const pct = total > 0 ? Math.round((done / total) * 100) : 0;\n const activePct = total > 0 ? Math.round((inProgress / total) * 100) : 0;\n\n return (\n <div className=\"flex items-center gap-3\">\n {/* Bar */}\n <div className=\"relative h-1.5 flex-1 overflow-hidden rounded-full bg-sf-fill\">\n {/* Active (in-progress) ghost — slightly ahead of done */}\n {activePct > 0 && (\n <div\n className=\"absolute inset-y-0 left-0 rounded-full bg-sf-brand/30 transition-[width] duration-500\"\n style={{ width: `${pct + activePct}%` }}\n />\n )}\n {/* Done */}\n <div\n className=\"absolute inset-y-0 left-0 rounded-full bg-sf-brand transition-[width] duration-500\"\n style={{ width: `${pct}%` }}\n />\n </div>\n {/* Fraction */}\n <span className=\"shrink-0 tabular-nums text-[11px] text-sf-subtle\">\n {done}/{total}\n </span>\n </div>\n );\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * `AiMissionHeader` — top bar for the Commander dashboard.\n *\n * Shows: mission title, progress bar (from task list), agent count,\n * live elapsed time, token usage, and an abort button while running.\n *\n * @example\n * ```tsx\n * <AiMissionHeader\n * title=\"Refactor auth module\"\n * taskList={state.taskList}\n * usage={state.usage}\n * activeAgentCount={activeAgents.length}\n * totalAgentCount={allAgents.length}\n * startedAt={state.missionStartedAt}\n * isRunning={state.isRunning}\n * onAbort={abort}\n * />\n * ```\n */\nexport const AiMissionHeader = forwardRef<HTMLDivElement, AiMissionHeaderProps>(\n (\n {\n title,\n taskList,\n usage,\n activeAgentCount,\n totalAgentCount,\n startedAt,\n endedAt,\n isRunning = false,\n onAbort,\n variant = SF_AI_MISSION_HEADER_DEFAULT_VARIANTS.variant,\n className,\n ...props\n },\n ref\n ) => {\n const elapsed = useElapsed(startedAt, endedAt, isRunning);\n const isCompact = variant === \"compact\";\n\n const statusLabel = isRunning\n ? \"Running\"\n : startedAt && endedAt\n ? \"Completed\"\n : startedAt\n ? \"Paused\"\n : \"Idle\";\n\n return (\n <div\n ref={ref}\n className={cn(\n \"flex flex-col gap-2 border-b border-sf-line bg-sf-base px-4 py-3\",\n isCompact && \"flex-row items-center gap-4 py-2\",\n className\n )}\n {...props}\n >\n {/* Top row: title + status + abort */}\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n {/* Status icon */}\n {isRunning ? (\n <SpinnerGapIcon\n size={14}\n className=\"shrink-0 animate-spin text-sf-brand\"\n />\n ) : startedAt && endedAt ? (\n <CheckCircleIcon size={14} className=\"shrink-0 text-sf-success\" />\n ) : (\n <CpuIcon size={14} className=\"shrink-0 text-sf-subtle\" />\n )}\n\n {/* Title */}\n {title ? (\n <h2 className=\"truncate text-sm font-semibold text-sf-default\">\n {title}\n </h2>\n ) : (\n <span className=\"text-sm font-medium text-sf-subtle\">\n {statusLabel}\n </span>\n )}\n </div>\n\n {/* Right: stats + abort */}\n <div className=\"flex shrink-0 items-center gap-3\">\n {/* Agent count */}\n {(activeAgentCount !== undefined ||\n totalAgentCount !== undefined) && (\n <div className=\"flex items-center gap-1 text-[11px] text-sf-subtle\">\n <UsersIcon size={11} />\n <span className=\"tabular-nums\">\n {activeAgentCount !== undefined &&\n totalAgentCount !== undefined\n ? `${activeAgentCount}/${totalAgentCount}`\n : (activeAgentCount ?? totalAgentCount)}\n </span>\n </div>\n )}\n\n {/* Elapsed time */}\n {startedAt && (\n <div className=\"flex items-center gap-1 text-[11px] text-sf-subtle\">\n <ClockIcon size={11} />\n <span className=\"tabular-nums\">{formatElapsed(elapsed)}</span>\n </div>\n )}\n\n {/* Token cost */}\n {usage?.cost !== undefined && usage.cost > 0 && (\n <div className=\"flex items-center gap-1 text-[11px] text-sf-subtle\">\n <CoinsIcon size={11} />\n <span className=\"tabular-nums\">{formatCost(usage.cost)}</span>\n </div>\n )}\n\n {/* Tokens */}\n {usage?.totalTokens !== undefined && (\n <span className=\"tabular-nums text-[11px] text-sf-subtle\">\n {formatTokens(usage.totalTokens)} tok\n </span>\n )}\n\n {/* Abort */}\n {isRunning && onAbort && (\n <button\n type=\"button\"\n aria-label=\"Abort mission\"\n onClick={onAbort}\n className={cn(\n \"flex items-center gap-1 rounded-md px-2 py-1 text-[11px] font-medium\",\n \"text-sf-danger transition-colors hover:bg-sf-danger/10\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sf-ring\"\n )}\n >\n <StopCircleIcon size={11} />\n Stop\n </button>\n )}\n </div>\n </div>\n\n {/* Progress bar (only in default variant) */}\n {!isCompact && <MissionProgress taskList={taskList} />}\n </div>\n );\n }\n);\n\nAiMissionHeader.displayName = \"AiMissionHeader\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"ai-mission-header-CaBc19-t.js","names":[],"sources":["../src/components/ai-mission-header/ai-mission-header.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n CheckCircleIcon,\n ClockIcon,\n CoinsIcon,\n CpuIcon,\n SpinnerGapIcon,\n StopCircleIcon,\n UsersIcon,\n} from \"@phosphor-icons/react\";\nimport type { ComponentProps } from \"react\";\nimport { forwardRef, useEffect, useState } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport type { AgentTaskList, AgentUsage } from \"../use-agent-harness\";\n\n// ─── Variants ────────────────────────────────────────────────────────────────\n\nexport const SF_AI_MISSION_HEADER_VARIANTS = {\n variant: {\n default: { classes: \"\", description: \"Full header with all stats\" },\n compact: { classes: \"\", description: \"Single-row compact header\" },\n },\n} as const;\n\nexport const SF_AI_MISSION_HEADER_DEFAULT_VARIANTS = {\n variant: \"default\",\n} as const;\n\nexport type SFAiMissionHeaderVariant =\n keyof typeof SF_AI_MISSION_HEADER_VARIANTS.variant;\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type AiMissionHeaderProps = Omit<ComponentProps<\"div\">, \"title\"> & {\n /** The mission goal or title. */\n title?: string;\n /** Task list for progress calculation. */\n taskList?: AgentTaskList | null;\n /** Token/cost usage summary. */\n usage?: AgentUsage | null;\n /** Number of agents currently active. */\n activeAgentCount?: number;\n /** Total number of agents that have run or are running. */\n totalAgentCount?: number;\n /** Wall-clock ms when the mission started. */\n startedAt?: number;\n /** Wall-clock ms when the mission ended. Undefined while still running. */\n endedAt?: number;\n /** Whether the mission is currently running. */\n isRunning?: boolean;\n /** Called when the user clicks the abort/stop button. */\n onAbort?: () => void;\n /** Display variant. @default \"default\" */\n variant?: SFAiMissionHeaderVariant;\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction formatElapsed(ms: number): string {\n const s = Math.floor(ms / 1000);\n if (s < 60) return `${s}s`;\n const m = Math.floor(s / 60);\n const rem = s % 60;\n return `${m}m ${rem.toString().padStart(2, \"0\")}s`;\n}\n\nfunction formatCost(cost: number): string {\n if (cost < 0.01) return `${(cost * 100).toFixed(2)}¢`;\n return `$${cost.toFixed(4)}`;\n}\n\nfunction formatTokens(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;\n return String(n);\n}\n\n/** Live elapsed time that ticks every ~500ms while running. */\nfunction useElapsed(startedAt?: number, endedAt?: number, isRunning = false) {\n const [elapsed, setElapsed] = useState<number>(\n startedAt ? (endedAt ?? Date.now()) - startedAt : 0\n );\n\n useEffect(() => {\n if (!startedAt || !isRunning) {\n if (startedAt) setElapsed((endedAt ?? Date.now()) - startedAt);\n return;\n }\n\n // Tick at 2Hz — efficient for a human-readable seconds display\n const id = setInterval(() => {\n setElapsed(Date.now() - startedAt);\n }, 500);\n return () => clearInterval(id);\n }, [startedAt, endedAt, isRunning]);\n\n return elapsed;\n}\n\n// ─── Progress bar ─────────────────────────────────────────────────────────────\n\nfunction MissionProgress({ taskList }: { taskList?: AgentTaskList | null }) {\n if (!taskList?.tasks.length) return null;\n\n const total = taskList.tasks.length;\n const done = taskList.tasks.filter(\n (t) => t.status === \"completed\" || t.status === \"cancelled\"\n ).length;\n const inProgress = taskList.tasks.filter(\n (t) => t.status === \"in_progress\"\n ).length;\n const pct = total > 0 ? Math.round((done / total) * 100) : 0;\n const activePct = total > 0 ? Math.round((inProgress / total) * 100) : 0;\n\n return (\n <div className=\"flex items-center gap-3\">\n {/* Bar */}\n <div className=\"relative h-1.5 flex-1 overflow-hidden rounded-full bg-sf-fill\">\n {/* Active (in-progress) ghost — slightly ahead of done */}\n {activePct > 0 && (\n <div\n className=\"absolute inset-y-0 left-0 rounded-full bg-sf-brand/30 transition-[width] duration-500\"\n style={{ width: `${pct + activePct}%` }}\n />\n )}\n {/* Done */}\n <div\n className=\"absolute inset-y-0 left-0 rounded-full bg-sf-brand transition-[width] duration-500\"\n style={{ width: `${pct}%` }}\n />\n </div>\n {/* Fraction */}\n <span className=\"shrink-0 tabular-nums text-[11px] text-sf-subtle\">\n {done}/{total}\n </span>\n </div>\n );\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * `AiMissionHeader` — top bar for the Commander dashboard.\n *\n * Shows: mission title, progress bar (from task list), agent count,\n * live elapsed time, token usage, and an abort button while running.\n *\n * @example\n * ```tsx\n * <AiMissionHeader\n * title=\"Refactor auth module\"\n * taskList={state.taskList}\n * usage={state.usage}\n * activeAgentCount={activeAgents.length}\n * totalAgentCount={allAgents.length}\n * startedAt={state.missionStartedAt}\n * isRunning={state.isRunning}\n * onAbort={abort}\n * />\n * ```\n */\nexport const AiMissionHeader = forwardRef<HTMLDivElement, AiMissionHeaderProps>(\n (\n {\n title,\n taskList,\n usage,\n activeAgentCount,\n totalAgentCount,\n startedAt,\n endedAt,\n isRunning = false,\n onAbort,\n variant = SF_AI_MISSION_HEADER_DEFAULT_VARIANTS.variant,\n className,\n ...props\n },\n ref\n ) => {\n const elapsed = useElapsed(startedAt, endedAt, isRunning);\n const isCompact = variant === \"compact\";\n\n const statusLabel = isRunning\n ? \"Running\"\n : startedAt && endedAt\n ? \"Completed\"\n : startedAt\n ? \"Paused\"\n : \"Idle\";\n\n return (\n <div\n ref={ref}\n className={cn(\n \"flex flex-col gap-2 border-b border-sf-line bg-sf-base px-4 py-3\",\n isCompact && \"flex-row items-center gap-4 py-2\",\n className\n )}\n {...props}\n >\n {/* Top row: title + status + abort */}\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n {/* Status icon */}\n {isRunning ? (\n <SpinnerGapIcon\n size={14}\n className=\"shrink-0 animate-spin text-sf-brand\"\n />\n ) : startedAt && endedAt ? (\n <CheckCircleIcon size={14} className=\"shrink-0 text-sf-success\" />\n ) : (\n <CpuIcon size={14} className=\"shrink-0 text-sf-subtle\" />\n )}\n\n {/* Title */}\n {title ? (\n <h2 className=\"truncate text-sm font-semibold text-sf-default\">\n {title}\n </h2>\n ) : (\n <span className=\"text-sm font-medium text-sf-subtle\">\n {statusLabel}\n </span>\n )}\n </div>\n\n {/* Right: stats + abort */}\n <div className=\"flex shrink-0 items-center gap-3\">\n {/* Agent count */}\n {(activeAgentCount !== undefined ||\n totalAgentCount !== undefined) && (\n <div className=\"flex items-center gap-1 text-[11px] text-sf-subtle\">\n <UsersIcon size={11} />\n <span className=\"tabular-nums\">\n {activeAgentCount !== undefined &&\n totalAgentCount !== undefined\n ? `${activeAgentCount}/${totalAgentCount}`\n : (activeAgentCount ?? totalAgentCount)}\n </span>\n </div>\n )}\n\n {/* Elapsed time */}\n {startedAt && (\n <div className=\"flex items-center gap-1 text-[11px] text-sf-subtle\">\n <ClockIcon size={11} />\n <span className=\"tabular-nums\">{formatElapsed(elapsed)}</span>\n </div>\n )}\n\n {/* Token cost */}\n {usage?.cost !== undefined && usage.cost > 0 && (\n <div className=\"flex items-center gap-1 text-[11px] text-sf-subtle\">\n <CoinsIcon size={11} />\n <span className=\"tabular-nums\">{formatCost(usage.cost)}</span>\n </div>\n )}\n\n {/* Tokens */}\n {usage?.totalTokens !== undefined && (\n <span className=\"tabular-nums text-[11px] text-sf-subtle\">\n {formatTokens(usage.totalTokens)} tok\n </span>\n )}\n\n {/* Abort */}\n {isRunning && onAbort && (\n <button\n type=\"button\"\n aria-label=\"Abort mission\"\n onClick={onAbort}\n className={cn(\n \"flex items-center gap-1 rounded-md px-2 py-1 text-[11px] font-medium\",\n \"text-sf-danger transition-colors hover:bg-sf-danger/10\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sf-ring\"\n )}\n >\n <StopCircleIcon size={11} />\n Stop\n </button>\n )}\n </div>\n </div>\n\n {/* Progress bar (only in default variant) */}\n {!isCompact && <MissionProgress taskList={taskList} />}\n </div>\n );\n }\n);\n\nAiMissionHeader.displayName = \"AiMissionHeader\";\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- import { t as cn } from "./cn-YROP2_ox.js";
3
- import { d as hasToolResult, l as hasToolError } from "./ai-tool-Cn1O4xjP.js";
4
- import { t as TextRoll } from "./text-roll-BZ3I1umc.js";
2
+ import { t as cn } from "./cn-CmAOpn49.js";
3
+ import { d as hasToolResult, l as hasToolError } from "./ai-tool-CfRcwmHT.js";
4
+ import { t as TextRoll } from "./text-roll-C3U2jd2u.js";
5
5
  import { Children, isValidElement, useState } from "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { CaretDownIcon, SpinnerGapIcon } from "@phosphor-icons/react";
@@ -123,6 +123,7 @@ function TitledSummary({ snapshot, defaultOpen, className, ...props }) {
123
123
  children: [/* @__PURE__ */ jsxs(Collapsible.Trigger, {
124
124
  render: /* @__PURE__ */ jsx("button", {
125
125
  type: "button",
126
+ "aria-label": snapshot.title,
126
127
  className: cn("flex w-fit items-center gap-1.5 rounded px-1 py-0.5 text-left", "transition-colors hover:bg-sf-tint")
127
128
  }),
128
129
  children: [
@@ -274,4 +275,4 @@ function groupParts(parts, options = {}) {
274
275
  //#endregion
275
276
  export { getAiPartGroupSnapshot as n, groupParts as r, AiPartGroup as t };
276
277
 
277
- //# sourceMappingURL=ai-part-group-DBtgTgAn.js.map
278
+ //# sourceMappingURL=ai-part-group-Dx1Mr92B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-part-group-Dx1Mr92B.js","names":[],"sources":["../src/components/ai-part-group/ai-part-group.tsx"],"sourcesContent":["\"use client\";\n\nimport { Collapsible as BaseCollapsible } from \"@base-ui/react/collapsible\";\nimport { CaretDownIcon, SpinnerGapIcon } from \"@phosphor-icons/react\";\nimport type { ComponentProps, ReactElement, ReactNode } from \"react\";\nimport { Children, isValidElement, useState } from \"react\";\n\nimport { cn } from \"../../utils/cn\";\nimport { type AiToolPart, hasToolError, hasToolResult } from \"../ai-tool\";\nimport { TextRoll } from \"../text-roll\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\ntype FrozenStep = {\n kind: \"tool\" | \"reasoning\";\n name: string;\n ok: boolean;\n duration?: number;\n};\n\ntype FrozenSnapshot = {\n title: string;\n steps: FrozenStep[];\n errorCount: number;\n totalDuration: number;\n};\n\n// Module-level snapshot cache. Survives re-renders within the page session.\n// Keyed by `groupKey` — usually the parent message id.\nconst snapshotCache = new Map<string, FrozenSnapshot>();\n\n/** Test-only escape hatch for clearing the snapshot cache. */\nexport function _clearAiPartGroupCache() {\n snapshotCache.clear();\n}\n\n/**\n * Reads the frozen snapshot for a group, or `undefined` if it hasn't completed\n * yet. Useful for consumers driving custom layouts.\n */\nexport function getAiPartGroupSnapshot(\n groupKey: string\n): FrozenSnapshot | undefined {\n return snapshotCache.get(groupKey);\n}\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\nexport type AiPartGroupProps = Omit<ComponentProps<\"div\">, \"title\"> & {\n /**\n * Stable identifier — usually the parent message id. Required so the group\n * can persist its frozen snapshot across re-mounts (scroll-back into a\n * virtualized list re-renders without restarting animations).\n */\n groupKey: string;\n /**\n * Set to `true` once the parent stream has finished. Triggers the group to\n * snapshot its current children and from then on render only the static\n * collapsed summary — ephemeral children never re-mount.\n */\n complete?: boolean;\n /**\n * Max number of live ephemeral rows visible at once during streaming.\n * Older rows fall off the top. Pinned rows (errors / user-expanded) render\n * above the live window and do not count toward this cap.\n * @default 3\n */\n max?: number;\n /**\n * Title shown in the collapsed summary line. Falls back to the first\n * reasoning text snippet, then to a step count.\n */\n title?: string;\n /** Child ephemeral rows (`AiToolCall variant=\"ephemeral\"`, `AiReasoning variant=\"ephemeral\"`). */\n children?: ReactNode;\n};\n\n/**\n * Groups streaming ephemeral activity (tool calls + reasoning) into a single\n * rolling window during streaming, then collapses to a frozen summary line\n * once the parent message completes.\n *\n * Behavior:\n * - **Streaming:** shows the most recent `max` (default 3) ephemeral children.\n * Pinned rows (errors / user-expanded) sit above the live window.\n * - **Completed:** writes a snapshot keyed by `groupKey`, unmounts ephemeral\n * children entirely, and renders a static `▸ {title} · N steps` line.\n * - **Re-mount after completion:** always renders the cached snapshot — no\n * animations, no `useEffect` re-runs, no tool re-execution risk.\n *\n * @example\n * ```tsx\n * <AiPartGroup\n * groupKey={message.id}\n * complete={message.status === \"complete\"}\n * title={message.reasoningSummary}\n * max={3}\n * >\n * {message.parts.map((p) =>\n * p.type === \"reasoning\"\n * ? <AiReasoning key={p.id} variant=\"ephemeral\" part={p} />\n * : <AiToolCall key={p.toolCallId} variant=\"ephemeral\" part={p} />\n * )}\n * </AiPartGroup>\n * ```\n */\nexport function AiPartGroup({\n groupKey,\n complete = false,\n max = 3,\n title,\n children,\n className,\n ...props\n}: AiPartGroupProps) {\n const cached = snapshotCache.get(groupKey);\n\n // Cache hit: always render the frozen snapshot, never re-mount ephemerals.\n // This is the scroll-back-into-an-old-message path.\n if (cached) {\n return (\n <TitledSummary\n snapshot={cached}\n className={className}\n defaultOpen={false}\n {...props}\n />\n );\n }\n\n // First-time completion: derive a snapshot, persist it, render frozen.\n if (complete) {\n const snapshot = deriveSnapshot(children, title);\n snapshotCache.set(groupKey, snapshot);\n return (\n <TitledSummary\n snapshot={snapshot}\n className={className}\n defaultOpen={false}\n {...props}\n />\n );\n }\n\n // Streaming: persistent title row + rolling window of the latest `max` children.\n // oxlint-disable-next-line no-react-children\n const allChildren = Children.toArray(children).filter(isValidElement);\n const live = allChildren.slice(Math.max(0, allChildren.length - max));\n const liveTitle = title?.trim() || deriveLiveTitle(allChildren);\n const stepCount = allChildren.length;\n\n return (\n <LiveGroup\n className={className}\n liveRows={live}\n stepCount={stepCount}\n title={liveTitle}\n {...props}\n />\n );\n}\n\nAiPartGroup.displayName = \"AiPartGroup\";\n\n// ─── Live group (streaming) ──────────────────────────────────────────────────\n\nfunction LiveGroup({\n title,\n liveRows,\n stepCount,\n className,\n ...props\n}: {\n title: string;\n liveRows: ReactElement[];\n stepCount: number;\n} & Omit<ComponentProps<\"div\">, \"title\">) {\n // During streaming the group is always open — the caret is purely decorative\n // so the frozen/live states share a visual footprint (no layout jump on settle).\n const stepLabel = stepCount === 1 ? \"step\" : \"steps\";\n\n return (\n <div\n className={cn(\"flex flex-col\", className)}\n data-streaming=\"true\"\n {...props}\n >\n <div\n className={cn(\"flex w-fit items-center gap-1.5 rounded px-1 py-0.5\")}\n >\n <SpinnerGapIcon className=\"size-3.5 shrink-0 animate-spin text-sf-subtle\" />\n <span className=\"min-w-0 truncate text-sm text-sf-default\">\n <TextRoll\n duration={0.3}\n getEnterDelay={(i) => i * 0.03}\n getExitDelay={(i) => i * 0.03 + 0.12}\n key={title}\n >\n {title}\n </TextRoll>\n </span>\n {stepCount > 0 ? (\n <span className=\"text-xs tabular-nums text-sf-subtle/60\">\n {stepCount} {stepLabel}\n </span>\n ) : null}\n <CaretDownIcon className=\"size-3 shrink-0 text-sf-subtle/60\" />\n </div>\n {liveRows.length > 0 ? (\n <div className=\"ml-[0.3125rem] flex flex-col gap-0.5 border-l border-sf-line/40 pl-3\">\n {liveRows}\n </div>\n ) : null}\n </div>\n );\n}\n\n// ─── Frozen summary (titled) ─────────────────────────────────────────────────\n\nfunction TitledSummary({\n snapshot,\n defaultOpen,\n className,\n ...props\n}: {\n snapshot: FrozenSnapshot;\n defaultOpen: boolean;\n} & Omit<ComponentProps<\"div\">, \"title\">) {\n const [isOpen, setIsOpen] = useState(defaultOpen);\n const stepLabel = snapshot.steps.length === 1 ? \"step\" : \"steps\";\n\n return (\n <BaseCollapsible.Root open={isOpen} onOpenChange={setIsOpen}>\n <div\n className={cn(\"flex flex-col\", className)}\n data-streaming=\"false\"\n {...props}\n >\n <BaseCollapsible.Trigger\n render={\n <button\n type=\"button\"\n aria-label={snapshot.title}\n className={cn(\n \"flex w-fit items-center gap-1.5 rounded px-1 py-0.5 text-left\",\n \"transition-colors hover:bg-sf-tint\"\n )}\n />\n }\n >\n <span\n aria-hidden\n className={cn(\n \"size-1.5 shrink-0 rounded-full bg-current\",\n snapshot.errorCount > 0 ? \"text-sf-danger\" : \"text-sf-success\"\n )}\n />\n <span className=\"min-w-0 truncate text-sm text-sf-subtle\">\n {snapshot.title}\n </span>\n <span className=\"text-xs tabular-nums text-sf-subtle/60\">\n {snapshot.steps.length} {stepLabel}\n {snapshot.errorCount > 0 ? ` · ${snapshot.errorCount} error` : \"\"}\n </span>\n <CaretDownIcon\n className={cn(\n \"size-3 shrink-0 text-sf-subtle/60 transition-transform duration-200\",\n !isOpen && \"-rotate-90\"\n )}\n />\n </BaseCollapsible.Trigger>\n\n <BaseCollapsible.Panel className=\"overflow-hidden\">\n <ul className=\"mt-1 ml-[0.3125rem] flex flex-col gap-0.5 border-l border-sf-line/40 pl-3\">\n {snapshot.steps.map((step, i) => (\n <li\n className=\"flex items-center gap-1.5 text-xs text-sf-subtle\"\n key={`${step.kind}-${i}-${step.name}`}\n >\n <span\n aria-hidden\n className={cn(\n \"size-1.5 shrink-0 rounded-full bg-current\",\n step.ok ? \"text-sf-success\" : \"text-sf-danger\"\n )}\n />\n <span className=\"min-w-0 truncate\">{step.name}</span>\n {step.duration ? (\n <span className=\"tabular-nums text-sf-subtle/60\">\n {step.duration}ms\n </span>\n ) : null}\n </li>\n ))}\n </ul>\n </BaseCollapsible.Panel>\n </div>\n </BaseCollapsible.Root>\n );\n}\n\nfunction deriveLiveTitle(elements: ReactElement[]): string {\n for (const el of elements as ChildLike[]) {\n const part = el.props.part as\n | { text?: string; summary?: string }\n | undefined;\n const text = part?.summary ?? part?.text;\n if (text) return truncate(text, 60);\n }\n return \"Working…\";\n}\n\n// ─── Snapshot derivation ─────────────────────────────────────────────────────\n\ntype ChildLike = ReactElement<{\n part?: AiToolPart | { text?: string; summary?: string; name?: string };\n duration?: number;\n}>;\n\nfunction deriveSnapshot(\n children: ReactNode,\n explicitTitle: string | undefined\n): FrozenSnapshot {\n // oxlint-disable-next-line no-react-children\n const elements = Children.toArray(children).filter(\n isValidElement\n ) as ChildLike[];\n\n const steps: FrozenStep[] = [];\n let errorCount = 0;\n let totalDuration = 0;\n let firstReasoningSnippet: string | undefined;\n\n for (const el of elements) {\n const kind = inferKind(el);\n const part = el.props.part as\n | (AiToolPart & { text?: string; summary?: string; name?: string })\n | undefined;\n const duration = el.props.duration;\n if (typeof duration === \"number\") totalDuration += duration;\n\n if (kind === \"tool\") {\n const toolPart = part as AiToolPart | undefined;\n const ok = toolPart ? !hasToolError(toolPart) : true;\n const settled = toolPart ? hasToolResult(toolPart) : true;\n if (!ok) errorCount += 1;\n steps.push({\n kind: \"tool\",\n name: toolPart?.toolName ?? \"tool\",\n ok: ok && settled,\n duration,\n });\n } else {\n const text =\n (part?.summary as string | undefined) ??\n (part?.text as string | undefined);\n if (!firstReasoningSnippet && text) {\n firstReasoningSnippet = text;\n }\n steps.push({\n kind: \"reasoning\",\n name: text ? truncate(text, 60) : \"Thinking\",\n ok: true,\n duration,\n });\n }\n }\n\n const title =\n explicitTitle?.trim() ||\n (firstReasoningSnippet && truncate(firstReasoningSnippet, 60)) ||\n `${steps.length} ${steps.length === 1 ? \"step\" : \"steps\"}`;\n\n return { title, steps, errorCount, totalDuration };\n}\n\nfunction inferKind(el: ChildLike): \"tool\" | \"reasoning\" {\n // Best-effort: check common indicators. Tool parts have `toolName`/`toolCallId`.\n // Reasoning parts have `text`.\n const part = el.props.part as Record<string, unknown> | undefined;\n if (part && typeof part === \"object\") {\n if (\"toolName\" in part || \"toolCallId\" in part) return \"tool\";\n if (\"text\" in part || \"summary\" in part) return \"reasoning\";\n }\n // Fallback: inspect displayName.\n const displayName =\n (el.type as { displayName?: string; name?: string } | undefined)\n ?.displayName ??\n (el.type as { displayName?: string; name?: string } | undefined)?.name ??\n \"\";\n if (displayName.includes(\"Tool\")) return \"tool\";\n return \"reasoning\";\n}\n\nfunction truncate(s: string, n: number): string {\n if (s.length <= n) return s;\n return `${s.slice(0, n - 1).trimEnd()}…`;\n}\n\n// ─── groupParts ──────────────────────────────────────────────────────\n\n/**\n * A message part that is either narrative text or an \"activity\" (reasoning or\n * tool call). Consumers pass parts shaped like this — the helper does not read\n * any field other than `type`.\n */\nexport type GroupablePart =\n | { type: \"text\"; [key: string]: unknown }\n | { type: \"reasoning\"; [key: string]: unknown }\n | { type: \"tool-call\" | \"tool-result\" | string; [key: string]: unknown };\n\n/** Emitted by {@link groupParts}. */\nexport type PartChunk<TPart = GroupablePart> =\n | { kind: \"text\"; parts: TPart[] }\n | { kind: \"activity\"; parts: TPart[] };\n\n/**\n * Split a message's parts into alternating `text` and `activity` chunks so each\n * contiguous run of reasoning/tool parts can be rendered inside its own\n * {@link AiPartGroup}, with narrative text rendered between them.\n *\n * @example\n * ```tsx\n * {groupParts(message.parts).map((chunk, i) =>\n * chunk.kind === \"text\" ? (\n * <AiMessageContent key={i}>{joinText(chunk.parts)}</AiMessageContent>\n * ) : (\n * <AiPartGroup\n * key={i}\n * groupKey={`${message.id}:activity:${i}`}\n * complete={!message.isStreaming}\n * >\n * {chunk.parts.map(renderActivityPart)}\n * </AiPartGroup>\n * )\n * )}\n * ```\n */\nexport function groupParts<TPart extends { type: string }>(\n parts: readonly TPart[],\n options: {\n /** Which `type` values count as \"activity\". Defaults to reasoning + tool parts. */\n isActivity?: (part: TPart) => boolean;\n } = {}\n): PartChunk<TPart>[] {\n const isActivity =\n options.isActivity ??\n ((p: TPart) => {\n const t = p.type;\n return (\n t === \"reasoning\" ||\n t === \"tool-call\" ||\n t === \"tool-result\" ||\n t.startsWith(\"tool-\")\n );\n });\n\n const chunks: PartChunk<TPart>[] = [];\n for (const part of parts) {\n const kind: PartChunk<TPart>[\"kind\"] = isActivity(part)\n ? \"activity\"\n : \"text\";\n const last = chunks.at(-1);\n if (last && last.kind === kind) {\n last.parts.push(part);\n } else {\n chunks.push({ kind, parts: [part] });\n }\n }\n return chunks;\n}\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  "use client";
2
- import { t as cn } from "./cn-YROP2_ox.js";
3
- import { t as Tooltip } from "./tooltip-Cb7QW-7H.js";
4
- import { t as Button } from "./button-De0267YU.js";
2
+ import { t as cn } from "./cn-CmAOpn49.js";
3
+ import { n as layoutWithLines, o as prepareWithSegments } from "./layout-CWBE0qwx.js";
4
+ import { t as Tooltip } from "./tooltip-uobk6Oh-.js";
5
+ import { t as Button } from "./button-BHOgXJRU.js";
5
6
  import { Fragment, createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
6
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
8
  import { ArrowUpIcon, ArrowsClockwiseIcon, CaretDownIcon, CheckIcon, FileIcon, FileTextIcon, ImageIcon, MicrophoneIcon, PaperclipIcon, PlusIcon, SpinnerGapIcon, SquareIcon, XIcon } from "@phosphor-icons/react";
@@ -370,7 +371,7 @@ var Store = class {
370
371
  }
371
372
  };
372
373
  //#endregion
373
- //#region ../../node_modules/.bun/@tanstack+react-store@0.11.0+21ccd8898788a04d/node_modules/@tanstack/react-store/dist/useSelector.js
374
+ //#region ../../node_modules/.bun/@tanstack+react-store@0.11.0+7492c01c6988791b/node_modules/@tanstack/react-store/dist/useSelector.js
374
375
  function defaultCompare(a, b) {
375
376
  return a === b;
376
377
  }
@@ -404,7 +405,7 @@ function useSelector(source, selector = (s) => s, options) {
404
405
  return useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, selector, compare);
405
406
  }
406
407
  //#endregion
407
- //#region ../../node_modules/.bun/@tanstack+react-store@0.11.0+21ccd8898788a04d/node_modules/@tanstack/react-store/dist/useStore.js
408
+ //#region ../../node_modules/.bun/@tanstack+react-store@0.11.0+7492c01c6988791b/node_modules/@tanstack/react-store/dist/useStore.js
408
409
  /**
409
410
  * Deprecated alias for {@link useSelector}.
410
411
  *
@@ -506,14 +507,14 @@ function createPromptInputRequestController(options = {}) {
506
507
  },
507
508
  insertTag(tag, range) {
508
509
  request.setState((prev) => {
509
- const mention = tag.mention ?? `@${tag.label.replace(/\s+/g, "-").toLowerCase()}`;
510
+ const mention = tag.mention ?? `@${tag.label.replace(/\s+/gu, "-").toLowerCase()}`;
510
511
  const text = prev.text ?? "";
511
512
  const start = range?.start ?? text.length;
512
513
  const end = range?.end ?? text.length;
513
514
  const prefix = text.slice(0, start);
514
515
  const suffix = text.slice(end);
515
- const needsPrefixSpace = prefix.length > 0 && !/\s$/.test(prefix);
516
- const needsSuffixSpace = suffix.length > 0 && !/^\s/.test(suffix);
516
+ const needsPrefixSpace = prefix.length > 0 && !/\s$/u.test(prefix);
517
+ const needsSuffixSpace = suffix.length > 0 && !/^\s/u.test(suffix);
517
518
  const nextText = `${prefix}${needsPrefixSpace ? " " : ""}${mention}${needsSuffixSpace ? " " : ""}${suffix}`;
518
519
  const nextTag = {
519
520
  ...tag,
@@ -718,7 +719,7 @@ var usePromptInputAttachments = () => {
718
719
  /** Convert a blob URL to a base64 data URL for serialization. */
719
720
  async function convertBlobToDataUrl(url) {
720
721
  const blob = await (await fetch(url)).blob();
721
- return new Promise((resolve, reject) => {
722
+ return await new Promise((resolve, reject) => {
722
723
  const reader = new FileReader();
723
724
  reader.addEventListener("loadend", () => resolve(reader.result));
724
725
  reader.addEventListener("error", () => reject(/* @__PURE__ */ new Error("FileReader error")));
@@ -745,7 +746,7 @@ function makeAttachmentId() {
745
746
  * </PromptInput>
746
747
  * ```
747
748
  */
748
- function PromptInput({ className, accept, multiple, globalDrop, maxFiles, maxFileSize, onError, onSubmit, children, backLayer, backLayerTitle = "Context", backLayerOpen, onBackLayerOpenChange, autoOpenBackLayerWhen, ...props }) {
749
+ function PromptInput({ className, accept, multiple, globalDrop, maxFiles, maxFileSize, onError, onSubmit, children, backLayer, backLayerTitle = "Context", backLayerStatus = "idle", backLayerOpen, onBackLayerOpenChange, autoOpenBackLayerWhen, ...props }) {
749
750
  const hasBackLayer = backLayer !== void 0;
750
751
  const controller = useOptionalPromptInputController();
751
752
  const requestCtrl = useOptionalPromptInputRequestController();
@@ -886,7 +887,9 @@ function PromptInput({ className, accept, multiple, globalDrop, maxFiles, maxFil
886
887
  const handleSubmit = (event) => {
887
888
  event.preventDefault();
888
889
  const form = event.currentTarget;
889
- const text = requestCtrl ? requestCtrl.request.state.text : usingProvider ? controller.textInput.value : new FormData(form).get("message") || "";
890
+ const text = requestCtrl ? requestCtrl.request.state.text : usingProvider ? controller.textInput.value : (() => {
891
+ return new FormData(form).get("message") || "";
892
+ })();
890
893
  if (!usingProvider) form.reset();
891
894
  const doSubmit = async () => {
892
895
  const convertedFiles = await Promise.all(files.map(async (item) => {
@@ -921,9 +924,19 @@ function PromptInput({ className, accept, multiple, globalDrop, maxFiles, maxFil
921
924
  className: "flex w-full items-center justify-between gap-2 px-3 py-2 text-left",
922
925
  onClick: () => onBackLayerOpenChange?.(!isBackLayerOpen),
923
926
  type: "button",
924
- children: [/* @__PURE__ */ jsx("span", {
925
- className: "text-sm font-medium text-sf-subtle uppercase tracking-wide select-none",
926
- children: backLayerTitle
927
+ children: [/* @__PURE__ */ jsxs("span", {
928
+ className: "flex items-center gap-1.5 text-sm font-medium text-sf-subtle uppercase tracking-wide select-none",
929
+ children: [
930
+ backLayerStatus === "running" ? /* @__PURE__ */ jsx(SpinnerGapIcon, {
931
+ className: "size-3.5 animate-spin",
932
+ "aria-hidden": "true"
933
+ }) : null,
934
+ backLayerStatus === "error" ? /* @__PURE__ */ jsx(XIcon, {
935
+ className: "size-3.5 text-sf-danger",
936
+ "aria-hidden": "true"
937
+ }) : null,
938
+ backLayerTitle
939
+ ]
927
940
  }), /* @__PURE__ */ jsx(CaretDownIcon, { className: cn("size-3.5 text-sf-subtle transition-transform duration-200", isBackLayerOpen && "rotate-180") })]
928
941
  }),
929
942
  /* @__PURE__ */ jsx(PromptInputBackLayerPanel, {
@@ -1066,10 +1079,10 @@ var PromptInputTextarea = ({ onChange, className, placeholder = "What would you
1066
1079
  ...controlledProps
1067
1080
  });
1068
1081
  };
1069
- var getTagMention = (tag) => tag.mention ?? `@${tag.label.replace(/\s+/g, "-").toLowerCase()}`;
1082
+ var getTagMention = (tag) => tag.mention ?? `@${tag.label.replace(/\s+/gu, "-").toLowerCase()}`;
1070
1083
  var findMentionAtCursor = (text, cursor) => {
1071
1084
  const beforeCursor = text.slice(0, cursor);
1072
- const match = /(^|\s)@([\w-]*)$/.exec(beforeCursor);
1085
+ const match = /(^|\s)@([\w-]*)$/u.exec(beforeCursor);
1073
1086
  if (!match) return null;
1074
1087
  return {
1075
1088
  start: cursor - match[0].trimStart().length,
@@ -1110,43 +1123,19 @@ var getTextareaCursorPosition = (textarea, wrapper) => {
1110
1123
  top: 0
1111
1124
  };
1112
1125
  const computed = window.getComputedStyle(textarea);
1113
- const mirror = document.createElement("div");
1114
- const marker = document.createElement("span");
1115
1126
  const wrapperRect = wrapper.getBoundingClientRect();
1116
- for (const property of [
1117
- "borderBottomWidth",
1118
- "borderLeftWidth",
1119
- "borderRightWidth",
1120
- "borderTopWidth",
1121
- "boxSizing",
1122
- "fontFamily",
1123
- "fontSize",
1124
- "fontStyle",
1125
- "fontWeight",
1126
- "letterSpacing",
1127
- "lineHeight",
1128
- "paddingBottom",
1129
- "paddingLeft",
1130
- "paddingRight",
1131
- "paddingTop",
1132
- "textTransform",
1133
- "width",
1134
- "wordSpacing"
1135
- ]) mirror.style[property] = computed[property];
1136
- mirror.style.position = "absolute";
1137
- mirror.style.visibility = "hidden";
1138
- mirror.style.whiteSpace = "pre-wrap";
1139
- mirror.style.overflowWrap = "break-word";
1140
- mirror.textContent = textarea.value.slice(0, textarea.selectionStart);
1141
- marker.textContent = "​";
1142
- mirror.append(marker);
1143
- document.body.append(mirror);
1144
- const markerRect = marker.getBoundingClientRect();
1145
- const top = markerRect.top - mirror.getBoundingClientRect().top + textarea.offsetTop - 6;
1146
- const left = Math.min(Math.max(markerRect.left - mirror.getBoundingClientRect().left + textarea.offsetLeft, 12), Math.max(wrapperRect.width - 300, 12));
1147
- mirror.remove();
1127
+ const paddingLeft = parseFloat(computed.paddingLeft) || 0;
1128
+ const paddingTop = parseFloat(computed.paddingTop) || 0;
1129
+ const contentWidth = Math.max(1, textarea.clientWidth - paddingLeft - (parseFloat(computed.paddingRight) || 0));
1130
+ const lineHeightPx = parseFloat(computed.lineHeight) || 20;
1131
+ const font = computed.font || `${computed.fontStyle} ${computed.fontWeight} ${computed.fontSize} ${computed.fontFamily}`;
1132
+ const result = layoutWithLines(prepareWithSegments(textarea.value.slice(0, textarea.selectionStart), font, { whiteSpace: "pre-wrap" }), contentWidth, lineHeightPx);
1133
+ const lineCount = result.lines.length || 1;
1134
+ const lastLine = result.lines[result.lines.length - 1];
1135
+ const lastLineWidth = lastLine ? lastLine.width : 0;
1136
+ const top = (lineCount - 1) * lineHeightPx + paddingTop + textarea.offsetTop - 6;
1148
1137
  return {
1149
- left,
1138
+ left: Math.min(Math.max(lastLineWidth + paddingLeft + textarea.offsetLeft, 12), Math.max(wrapperRect.width - 300, 12)),
1150
1139
  top
1151
1140
  };
1152
1141
  };
@@ -1585,9 +1574,9 @@ var PromptInputCompactCycle = ({ options, value, onChange, "aria-label": ariaLab
1585
1574
  const enabledOptions = options.filter((o) => !o.disabled);
1586
1575
  if (enabledOptions.length === 0) return null;
1587
1576
  const currentIndex = options.findIndex((o) => o.value === value);
1588
- const current = currentIndex !== -1 ? options[currentIndex] : enabledOptions[0];
1577
+ const current = currentIndex === -1 ? enabledOptions[0] : options[currentIndex];
1589
1578
  const advance = (direction) => {
1590
- const start = currentIndex !== -1 ? currentIndex : -direction;
1579
+ const start = currentIndex === -1 ? -direction : currentIndex;
1591
1580
  const len = options.length;
1592
1581
  for (let step = 1; step <= len; step++) {
1593
1582
  const candidate = options[(start + direction * step + len * len) % len];
@@ -1730,7 +1719,7 @@ function PromptInputAttachment({ data, className, ...props }) {
1730
1719
  })]
1731
1720
  });
1732
1721
  const ext = data.filename.split(".").pop()?.toUpperCase() ?? "FILE";
1733
- const name = data.filename.replace(/\.[^/.]+$/, "");
1722
+ const name = data.filename.replace(/\.[^/.]+$/u, "");
1734
1723
  return /* @__PURE__ */ jsxs("div", {
1735
1724
  className: cn("group relative inline-flex h-8 items-center gap-2 rounded-md bg-sf-tint/50 px-2 text-sm", className),
1736
1725
  ...props,
@@ -1993,4 +1982,4 @@ PromptInput.Attachment = PromptInputAttachment;
1993
1982
  //#endregion
1994
1983
  export { SF_AI_PROMPT_INPUT_DEFAULT_VARIANTS as A, useRequestField as B, PromptInputSpeechButton as C, PromptInputTextarea as D, PromptInputTags as E, PromptInputRequestControllerProvider as F, createPromptInputRequestController as I, useDisplayField as L, usePromptInputAttachments as M, usePromptInputController as N, PromptInputToolbar as O, useProviderAttachments as P, useOptionalPromptInputRequestController as R, PromptInputProvider as S, PromptInputTag as T, useSetRequestField as V, PromptInputModeCycle as _, PromptInputActionMenuItem as a, PromptInputModelCycle as b, PromptInputAttachButton as c, PromptInputBackLayer as d, PromptInputBody as f, PromptInputEditor as g, PromptInputCompactSelect as h, PromptInputActionMenuContent as i, SF_AI_PROMPT_INPUT_VARIANTS as j, PromptInputTools as k, PromptInputAttachment as l, PromptInputCompactCycle as m, PromptInputActionAddAttachments as n, PromptInputActionMenuTrigger as o, PromptInputButton as p, PromptInputActionMenu as r, PromptInputAddTagButton as s, PromptInput as t, PromptInputAttachments as u, PromptInputModeSelect as v, PromptInputSubmit as w, PromptInputModelSelect as x, PromptInputModeSelector as y, usePromptInputRequestController as z };
1995
1984
 
1996
- //# sourceMappingURL=ai-prompt-input-CuluUzpf.js.map
1985
+ //# sourceMappingURL=ai-prompt-input-Bm4XoSj2.js.map