@databricks/appkit-ui 0.15.0 → 0.17.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 (274) hide show
  1. package/dist/cli/commands/plugin/add-resource/add-resource.js +10 -4
  2. package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
  3. package/dist/cli/commands/plugin/create/scaffold.js +10 -16
  4. package/dist/cli/commands/plugin/create/scaffold.js.map +1 -1
  5. package/dist/cli/commands/plugin/list/list.js +44 -26
  6. package/dist/cli/commands/plugin/list/list.js.map +1 -1
  7. package/dist/cli/commands/plugin/manifest-resolve.js +57 -0
  8. package/dist/cli/commands/plugin/manifest-resolve.js.map +1 -0
  9. package/dist/cli/commands/plugin/sync/sync.js +121 -71
  10. package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
  11. package/dist/cli/commands/plugin/trusted-js-manifest.js +28 -0
  12. package/dist/cli/commands/plugin/trusted-js-manifest.js.map +1 -0
  13. package/dist/cli/commands/plugin/validate/validate.js +32 -14
  14. package/dist/cli/commands/plugin/validate/validate.js.map +1 -1
  15. package/dist/js/arrow/arrow-client.d.ts.map +1 -1
  16. package/dist/js/arrow/index.d.ts +3 -0
  17. package/dist/js/arrow/lazy-arrow.d.ts +0 -1
  18. package/dist/js/arrow/lazy-arrow.d.ts.map +1 -1
  19. package/dist/js/constants.d.ts.map +1 -1
  20. package/dist/js/index.d.ts +2 -0
  21. package/dist/js/sse/connect-sse.d.ts +0 -1
  22. package/dist/js/sse/connect-sse.d.ts.map +1 -1
  23. package/dist/js/sse/types.d.ts +1 -3
  24. package/dist/js/sse/types.d.ts.map +1 -1
  25. package/dist/react/charts/area/index.d.ts +2 -3
  26. package/dist/react/charts/area/index.d.ts.map +1 -1
  27. package/dist/react/charts/bar/index.d.ts +2 -3
  28. package/dist/react/charts/bar/index.d.ts.map +1 -1
  29. package/dist/react/charts/base.d.ts +2 -2
  30. package/dist/react/charts/base.d.ts.map +1 -1
  31. package/dist/react/charts/base.js.map +1 -1
  32. package/dist/react/charts/chart-error-boundary.js.map +1 -1
  33. package/dist/react/charts/constants.d.ts.map +1 -1
  34. package/dist/react/charts/create-chart.d.ts +2 -3
  35. package/dist/react/charts/create-chart.d.ts.map +1 -1
  36. package/dist/react/charts/create-chart.js.map +1 -1
  37. package/dist/react/charts/empty.js.map +1 -1
  38. package/dist/react/charts/error.js.map +1 -1
  39. package/dist/react/charts/heatmap/index.d.ts +2 -3
  40. package/dist/react/charts/heatmap/index.d.ts.map +1 -1
  41. package/dist/react/charts/index.d.ts +18 -0
  42. package/dist/react/charts/line/index.d.ts +2 -3
  43. package/dist/react/charts/line/index.d.ts.map +1 -1
  44. package/dist/react/charts/loading.js.map +1 -1
  45. package/dist/react/charts/normalize.d.ts +0 -1
  46. package/dist/react/charts/normalize.d.ts.map +1 -1
  47. package/dist/react/charts/options.d.ts.map +1 -1
  48. package/dist/react/charts/pie/index.d.ts +3 -4
  49. package/dist/react/charts/pie/index.d.ts.map +1 -1
  50. package/dist/react/charts/radar/index.d.ts +2 -3
  51. package/dist/react/charts/radar/index.d.ts.map +1 -1
  52. package/dist/react/charts/scatter/index.d.ts +2 -3
  53. package/dist/react/charts/scatter/index.d.ts.map +1 -1
  54. package/dist/react/charts/theme.d.ts +0 -1
  55. package/dist/react/charts/theme.d.ts.map +1 -1
  56. package/dist/react/charts/types.d.ts.map +1 -1
  57. package/dist/react/charts/utils.d.ts.map +1 -1
  58. package/dist/react/charts/wrapper.d.ts +2 -2
  59. package/dist/react/charts/wrapper.d.ts.map +1 -1
  60. package/dist/react/charts/wrapper.js.map +1 -1
  61. package/dist/react/genie/genie-chat-input.d.ts +2 -2
  62. package/dist/react/genie/genie-chat-input.d.ts.map +1 -1
  63. package/dist/react/genie/genie-chat-input.js.map +1 -1
  64. package/dist/react/genie/genie-chat-message-list.d.ts +9 -3
  65. package/dist/react/genie/genie-chat-message-list.d.ts.map +1 -1
  66. package/dist/react/genie/genie-chat-message-list.js +92 -17
  67. package/dist/react/genie/genie-chat-message-list.js.map +1 -1
  68. package/dist/react/genie/genie-chat-message.d.ts +2 -2
  69. package/dist/react/genie/genie-chat-message.d.ts.map +1 -1
  70. package/dist/react/genie/genie-chat-message.js.map +1 -1
  71. package/dist/react/genie/genie-chat.d.ts +2 -2
  72. package/dist/react/genie/genie-chat.d.ts.map +1 -1
  73. package/dist/react/genie/genie-chat.js +4 -2
  74. package/dist/react/genie/genie-chat.js.map +1 -1
  75. package/dist/react/genie/index.d.ts +7 -0
  76. package/dist/react/genie/types.d.ts +8 -1
  77. package/dist/react/genie/types.d.ts.map +1 -1
  78. package/dist/react/genie/use-genie-chat.d.ts +0 -1
  79. package/dist/react/genie/use-genie-chat.d.ts.map +1 -1
  80. package/dist/react/genie/use-genie-chat.js +119 -41
  81. package/dist/react/genie/use-genie-chat.js.map +1 -1
  82. package/dist/react/hooks/index.d.ts +3 -0
  83. package/dist/react/hooks/types.d.ts.map +1 -1
  84. package/dist/react/hooks/use-analytics-query.d.ts +0 -1
  85. package/dist/react/hooks/use-analytics-query.d.ts.map +1 -1
  86. package/dist/react/hooks/use-chart-data.d.ts.map +1 -1
  87. package/dist/react/index.d.ts +5 -0
  88. package/dist/react/lib/utils.d.ts.map +1 -1
  89. package/dist/react/portal-container-context.d.ts +0 -1
  90. package/dist/react/portal-container-context.d.ts.map +1 -1
  91. package/dist/react/portal-container-context.js.map +1 -1
  92. package/dist/react/table/data-table.d.ts +2 -3
  93. package/dist/react/table/data-table.d.ts.map +1 -1
  94. package/dist/react/table/data-table.js.map +1 -1
  95. package/dist/react/table/empty.js.map +1 -1
  96. package/dist/react/table/error.js.map +1 -1
  97. package/dist/react/table/index.d.ts +1 -0
  98. package/dist/react/table/loading.js.map +1 -1
  99. package/dist/react/table/table-wrapper.js.map +1 -1
  100. package/dist/react/table/types.d.ts +0 -1
  101. package/dist/react/table/types.d.ts.map +1 -1
  102. package/dist/react/ui/accordion.d.ts.map +1 -1
  103. package/dist/react/ui/accordion.js.map +1 -1
  104. package/dist/react/ui/alert-dialog.d.ts +12 -12
  105. package/dist/react/ui/alert-dialog.d.ts.map +1 -1
  106. package/dist/react/ui/alert-dialog.js.map +1 -1
  107. package/dist/react/ui/alert.d.ts +4 -4
  108. package/dist/react/ui/alert.d.ts.map +1 -1
  109. package/dist/react/ui/alert.js.map +1 -1
  110. package/dist/react/ui/aspect-ratio.d.ts +2 -2
  111. package/dist/react/ui/aspect-ratio.d.ts.map +1 -1
  112. package/dist/react/ui/aspect-ratio.js.map +1 -1
  113. package/dist/react/ui/avatar.d.ts +4 -4
  114. package/dist/react/ui/avatar.d.ts.map +1 -1
  115. package/dist/react/ui/avatar.js.map +1 -1
  116. package/dist/react/ui/badge.d.ts +2 -2
  117. package/dist/react/ui/badge.d.ts.map +1 -1
  118. package/dist/react/ui/badge.js.map +1 -1
  119. package/dist/react/ui/breadcrumb.d.ts +8 -8
  120. package/dist/react/ui/breadcrumb.d.ts.map +1 -1
  121. package/dist/react/ui/breadcrumb.js.map +1 -1
  122. package/dist/react/ui/button-group.d.ts +6 -6
  123. package/dist/react/ui/button-group.d.ts.map +1 -1
  124. package/dist/react/ui/button-group.js.map +1 -1
  125. package/dist/react/ui/button.d.ts +4 -4
  126. package/dist/react/ui/button.d.ts.map +1 -1
  127. package/dist/react/ui/button.js.map +1 -1
  128. package/dist/react/ui/calendar.d.ts +3 -3
  129. package/dist/react/ui/calendar.d.ts.map +1 -1
  130. package/dist/react/ui/calendar.js.map +1 -1
  131. package/dist/react/ui/card.d.ts +8 -8
  132. package/dist/react/ui/card.d.ts.map +1 -1
  133. package/dist/react/ui/card.js.map +1 -1
  134. package/dist/react/ui/carousel.d.ts +6 -6
  135. package/dist/react/ui/carousel.d.ts.map +1 -1
  136. package/dist/react/ui/carousel.js.map +1 -1
  137. package/dist/react/ui/chart.d.ts +19 -19
  138. package/dist/react/ui/chart.d.ts.map +1 -1
  139. package/dist/react/ui/chart.js.map +1 -1
  140. package/dist/react/ui/checkbox.d.ts +2 -2
  141. package/dist/react/ui/checkbox.d.ts.map +1 -1
  142. package/dist/react/ui/checkbox.js.map +1 -1
  143. package/dist/react/ui/collapsible.d.ts +4 -4
  144. package/dist/react/ui/collapsible.d.ts.map +1 -1
  145. package/dist/react/ui/collapsible.js.map +1 -1
  146. package/dist/react/ui/command.d.ts +10 -10
  147. package/dist/react/ui/command.d.ts.map +1 -1
  148. package/dist/react/ui/command.js.map +1 -1
  149. package/dist/react/ui/context-menu.d.ts +16 -16
  150. package/dist/react/ui/context-menu.d.ts.map +1 -1
  151. package/dist/react/ui/context-menu.js.map +1 -1
  152. package/dist/react/ui/dialog.d.ts +11 -11
  153. package/dist/react/ui/dialog.d.ts.map +1 -1
  154. package/dist/react/ui/dialog.js.map +1 -1
  155. package/dist/react/ui/drawer.d.ts +11 -11
  156. package/dist/react/ui/drawer.d.ts.map +1 -1
  157. package/dist/react/ui/drawer.js.map +1 -1
  158. package/dist/react/ui/dropdown-menu.d.ts +16 -16
  159. package/dist/react/ui/dropdown-menu.d.ts.map +1 -1
  160. package/dist/react/ui/dropdown-menu.js.map +1 -1
  161. package/dist/react/ui/empty.d.ts +9 -9
  162. package/dist/react/ui/empty.d.ts.map +1 -1
  163. package/dist/react/ui/empty.js.map +1 -1
  164. package/dist/react/ui/field.d.ts +13 -13
  165. package/dist/react/ui/field.d.ts.map +1 -1
  166. package/dist/react/ui/field.js.map +1 -1
  167. package/dist/react/ui/form.d.ts +9 -9
  168. package/dist/react/ui/form.d.ts.map +1 -1
  169. package/dist/react/ui/form.js.map +1 -1
  170. package/dist/react/ui/hover-card.d.ts +4 -4
  171. package/dist/react/ui/hover-card.d.ts.map +1 -1
  172. package/dist/react/ui/hover-card.js.map +1 -1
  173. package/dist/react/ui/index.d.ts +53 -0
  174. package/dist/react/ui/input-group.d.ts +10 -10
  175. package/dist/react/ui/input-group.d.ts.map +1 -1
  176. package/dist/react/ui/input-group.js.map +1 -1
  177. package/dist/react/ui/input-otp.d.ts +5 -5
  178. package/dist/react/ui/input-otp.d.ts.map +1 -1
  179. package/dist/react/ui/input-otp.js.map +1 -1
  180. package/dist/react/ui/input.d.ts +2 -2
  181. package/dist/react/ui/input.d.ts.map +1 -1
  182. package/dist/react/ui/input.js.map +1 -1
  183. package/dist/react/ui/item.d.ts +14 -14
  184. package/dist/react/ui/item.d.ts.map +1 -1
  185. package/dist/react/ui/item.js.map +1 -1
  186. package/dist/react/ui/kbd.d.ts +3 -3
  187. package/dist/react/ui/kbd.d.ts.map +1 -1
  188. package/dist/react/ui/kbd.js.map +1 -1
  189. package/dist/react/ui/label.d.ts +2 -2
  190. package/dist/react/ui/label.d.ts.map +1 -1
  191. package/dist/react/ui/label.js.map +1 -1
  192. package/dist/react/ui/menubar.d.ts +17 -17
  193. package/dist/react/ui/menubar.d.ts.map +1 -1
  194. package/dist/react/ui/menubar.js.map +1 -1
  195. package/dist/react/ui/navigation-menu.d.ts +11 -11
  196. package/dist/react/ui/navigation-menu.d.ts.map +1 -1
  197. package/dist/react/ui/navigation-menu.js.map +1 -1
  198. package/dist/react/ui/pagination.d.ts +8 -8
  199. package/dist/react/ui/pagination.d.ts.map +1 -1
  200. package/dist/react/ui/pagination.js.map +1 -1
  201. package/dist/react/ui/popover.d.ts +5 -5
  202. package/dist/react/ui/popover.d.ts.map +1 -1
  203. package/dist/react/ui/popover.js.map +1 -1
  204. package/dist/react/ui/progress.d.ts +2 -2
  205. package/dist/react/ui/progress.d.ts.map +1 -1
  206. package/dist/react/ui/progress.js.map +1 -1
  207. package/dist/react/ui/radio-group.d.ts +3 -3
  208. package/dist/react/ui/radio-group.d.ts.map +1 -1
  209. package/dist/react/ui/radio-group.js.map +1 -1
  210. package/dist/react/ui/resizable.d.ts +4 -4
  211. package/dist/react/ui/resizable.d.ts.map +1 -1
  212. package/dist/react/ui/resizable.js.map +1 -1
  213. package/dist/react/ui/scroll-area.d.ts +3 -3
  214. package/dist/react/ui/scroll-area.d.ts.map +1 -1
  215. package/dist/react/ui/scroll-area.js.map +1 -1
  216. package/dist/react/ui/select.d.ts +11 -11
  217. package/dist/react/ui/select.d.ts.map +1 -1
  218. package/dist/react/ui/select.js.map +1 -1
  219. package/dist/react/ui/separator.d.ts +2 -2
  220. package/dist/react/ui/separator.d.ts.map +1 -1
  221. package/dist/react/ui/separator.js.map +1 -1
  222. package/dist/react/ui/sheet.d.ts +9 -9
  223. package/dist/react/ui/sheet.d.ts.map +1 -1
  224. package/dist/react/ui/sheet.js.map +1 -1
  225. package/dist/react/ui/sidebar.d.ts +38 -55
  226. package/dist/react/ui/sidebar.d.ts.map +1 -1
  227. package/dist/react/ui/sidebar.js.map +1 -1
  228. package/dist/react/ui/skeleton.d.ts +2 -2
  229. package/dist/react/ui/skeleton.d.ts.map +1 -1
  230. package/dist/react/ui/skeleton.js.map +1 -1
  231. package/dist/react/ui/slider.d.ts +2 -2
  232. package/dist/react/ui/slider.d.ts.map +1 -1
  233. package/dist/react/ui/slider.js.map +1 -1
  234. package/dist/react/ui/sonner.d.ts +2 -2
  235. package/dist/react/ui/sonner.d.ts.map +1 -1
  236. package/dist/react/ui/sonner.js.map +1 -1
  237. package/dist/react/ui/spinner.d.ts +2 -2
  238. package/dist/react/ui/spinner.d.ts.map +1 -1
  239. package/dist/react/ui/spinner.js.map +1 -1
  240. package/dist/react/ui/switch.d.ts +2 -2
  241. package/dist/react/ui/switch.d.ts.map +1 -1
  242. package/dist/react/ui/switch.js.map +1 -1
  243. package/dist/react/ui/table.d.ts +9 -9
  244. package/dist/react/ui/table.d.ts.map +1 -1
  245. package/dist/react/ui/table.js.map +1 -1
  246. package/dist/react/ui/tabs.d.ts +5 -5
  247. package/dist/react/ui/tabs.d.ts.map +1 -1
  248. package/dist/react/ui/tabs.js.map +1 -1
  249. package/dist/react/ui/textarea.d.ts +2 -2
  250. package/dist/react/ui/textarea.d.ts.map +1 -1
  251. package/dist/react/ui/textarea.js.map +1 -1
  252. package/dist/react/ui/toggle-group.d.ts +3 -3
  253. package/dist/react/ui/toggle-group.d.ts.map +1 -1
  254. package/dist/react/ui/toggle-group.js.map +1 -1
  255. package/dist/react/ui/toggle.d.ts +4 -4
  256. package/dist/react/ui/toggle.d.ts.map +1 -1
  257. package/dist/react/ui/toggle.js.map +1 -1
  258. package/dist/react/ui/tooltip.d.ts +5 -5
  259. package/dist/react/ui/tooltip.d.ts.map +1 -1
  260. package/dist/react/ui/tooltip.js.map +1 -1
  261. package/dist/shared/src/cache.d.ts +1 -0
  262. package/dist/shared/src/execute.d.ts +1 -0
  263. package/dist/shared/src/genie.d.ts +6 -0
  264. package/dist/shared/src/genie.d.ts.map +1 -1
  265. package/dist/shared/src/index.d.ts +7 -0
  266. package/dist/shared/src/plugin.d.ts +2 -0
  267. package/dist/shared/src/sql/helpers.d.ts +0 -1
  268. package/dist/shared/src/sql/helpers.d.ts.map +1 -1
  269. package/dist/shared/src/sql/types.d.ts.map +1 -1
  270. package/dist/shared/src/tunnel.d.ts +1 -0
  271. package/docs/api/appkit-ui/genie/GenieChatMessageList.md +7 -5
  272. package/docs/development/project-setup.md +1 -1
  273. package/docs/plugins/plugin-management.md +16 -2
  274. package/package.json +3 -1
@@ -1 +1 @@
1
- {"version":3,"file":"wrapper.js","names":[],"sources":["../../../src/react/charts/wrapper.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { useChartData } from \"../hooks/use-chart-data\";\nimport { ChartErrorBoundary } from \"./chart-error-boundary\";\nimport { EmptyState } from \"./empty\";\nimport { ErrorState } from \"./error\";\nimport { LoadingSkeleton } from \"./loading\";\nimport type { ChartData, DataFormat } from \"./types\";\nimport { isArrowTable } from \"./types\";\n\n// ============================================================================\n// Props Types\n// ============================================================================\n\ninterface ChartWrapperQueryProps {\n /** Analytics query key */\n queryKey: string;\n /** Query parameters */\n parameters?: Record<string, unknown>;\n /** Data format preference */\n format?: DataFormat;\n /** Transform data after fetching */\n transformer?: <T>(data: T) => T;\n /** Direct data - not used in query mode */\n data?: never;\n}\n\ninterface ChartWrapperDataProps {\n /** Direct data (Arrow Table or JSON array) */\n data: ChartData;\n /** Not used in data mode */\n queryKey?: never;\n parameters?: never;\n format?: never;\n transformer?: never;\n}\n\ninterface CommonProps {\n /** Chart height in pixels */\n height?: number;\n /** Additional CSS classes */\n className?: string;\n /** Accessibility label */\n ariaLabel?: string;\n /** Test ID for automated testing */\n testId?: string;\n /** Render function receiving the chart data */\n children: (data: ChartData) => ReactNode;\n}\n\nexport type ChartWrapperProps = CommonProps &\n (ChartWrapperQueryProps | ChartWrapperDataProps);\n\n// ============================================================================\n// Query Mode Content\n// ============================================================================\n\nfunction QueryModeContent({\n queryKey,\n parameters,\n format,\n transformer,\n height,\n className,\n ariaLabel,\n testId,\n children,\n}: CommonProps & ChartWrapperQueryProps) {\n const { data, loading, error, isEmpty } = useChartData({\n queryKey,\n parameters,\n format,\n transformer,\n });\n\n if (loading) return <LoadingSkeleton height={height ?? 300} />;\n if (error) return <ErrorState error={error} />;\n if (isEmpty || !data) return <EmptyState />;\n\n return (\n <ChartErrorBoundary\n fallback={<ErrorState error=\"Failed to render chart\" />}\n >\n <div\n className={className}\n style={{ height }}\n aria-label={ariaLabel}\n data-testid={testId}\n role=\"img\"\n >\n {children(data)}\n </div>\n </ChartErrorBoundary>\n );\n}\n\n// ============================================================================\n// Data Mode Content\n// ============================================================================\n\nfunction DataModeContent({\n data,\n height,\n className,\n ariaLabel,\n testId,\n children,\n}: CommonProps & ChartWrapperDataProps) {\n const isEmpty = isArrowTable(data)\n ? data.numRows === 0\n : !Array.isArray(data) || data.length === 0;\n\n if (isEmpty) return <EmptyState />;\n\n return (\n <ChartErrorBoundary\n fallback={<ErrorState error=\"Failed to render chart\" />}\n >\n <div\n className={className}\n style={{ height }}\n aria-label={ariaLabel}\n data-testid={testId}\n role=\"img\"\n >\n {children(data)}\n </div>\n </ChartErrorBoundary>\n );\n}\n\n// ============================================================================\n// Main Wrapper Component\n// ============================================================================\n\n/**\n * Wrapper component for charts.\n * Handles data fetching (query mode) or direct data injection (data mode).\n *\n * @example Query mode - fetches data from analytics endpoint\n * ```tsx\n * <ChartWrapper\n * queryKey=\"spend_data\"\n * parameters={{ limit: 100 }}\n * format=\"auto\"\n * >\n * {(data) => <MyChart data={data} />}\n * </ChartWrapper>\n * ```\n *\n * @example Data mode - uses provided data directly\n * ```tsx\n * <ChartWrapper data={myArrowTable}>\n * {(data) => <MyChart data={data} />}\n * </ChartWrapper>\n * ```\n */\nexport function ChartWrapper(props: ChartWrapperProps) {\n const { height = 300, className, ariaLabel, testId, children } = props;\n\n // Data mode: use provided data directly\n if (\"data\" in props && props.data !== undefined) {\n return (\n <DataModeContent\n data={props.data}\n height={height}\n className={className}\n ariaLabel={ariaLabel}\n testId={testId}\n >\n {children}\n </DataModeContent>\n );\n }\n\n // Query mode: fetch data from analytics endpoint\n if (\"queryKey\" in props && props.queryKey !== undefined) {\n return (\n <QueryModeContent\n queryKey={props.queryKey}\n parameters={props.parameters}\n format={props.format}\n transformer={props.transformer}\n height={height}\n className={className}\n ariaLabel={ariaLabel}\n testId={testId}\n >\n {children}\n </QueryModeContent>\n );\n }\n\n // Should never reach here due to TypeScript, but safety fallback\n return <ErrorState error=\"Chart requires either 'queryKey' or 'data' prop\" />;\n}\n"],"mappings":";;;;;;;;;AAwDA,SAAS,iBAAiB,EACxB,UACA,YACA,QACA,aACA,QACA,WACA,WACA,QACA,YACuC;CACvC,MAAM,EAAE,MAAM,SAAS,OAAO,YAAY,aAAa;EACrD;EACA;EACA;EACA;EACD,CAAC;AAEF,KAAI,QAAS,QAAO,oBAAC,iBAAD,EAAiB,QAAQ,UAAU,KAAO;AAC9D,KAAI,MAAO,QAAO,oBAAC,YAAD,EAAmB,OAAS;AAC9C,KAAI,WAAW,CAAC,KAAM,QAAO,oBAAC,YAAD,EAAc;AAE3C,QACE,oBAAC,oBAAD;EACE,UAAU,oBAAC,YAAD,EAAY,OAAM,0BAA2B;YAEvD,oBAAC,OAAD;GACa;GACX,OAAO,EAAE,QAAQ;GACjB,cAAY;GACZ,eAAa;GACb,MAAK;aAEJ,SAAS,KAAK;GACX;EACa;;AAQzB,SAAS,gBAAgB,EACvB,MACA,QACA,WACA,WACA,QACA,YACsC;AAKtC,KAJgB,aAAa,KAAK,GAC9B,KAAK,YAAY,IACjB,CAAC,MAAM,QAAQ,KAAK,IAAI,KAAK,WAAW,EAE/B,QAAO,oBAAC,YAAD,EAAc;AAElC,QACE,oBAAC,oBAAD;EACE,UAAU,oBAAC,YAAD,EAAY,OAAM,0BAA2B;YAEvD,oBAAC,OAAD;GACa;GACX,OAAO,EAAE,QAAQ;GACjB,cAAY;GACZ,eAAa;GACb,MAAK;aAEJ,SAAS,KAAK;GACX;EACa;;;;;;;;;;;;;;;;;;;;;;;;AA8BzB,SAAgB,aAAa,OAA0B;CACrD,MAAM,EAAE,SAAS,KAAK,WAAW,WAAW,QAAQ,aAAa;AAGjE,KAAI,UAAU,SAAS,MAAM,SAAS,OACpC,QACE,oBAAC,iBAAD;EACE,MAAM,MAAM;EACJ;EACG;EACA;EACH;EAEP;EACe;AAKtB,KAAI,cAAc,SAAS,MAAM,aAAa,OAC5C,QACE,oBAAC,kBAAD;EACE,UAAU,MAAM;EAChB,YAAY,MAAM;EAClB,QAAQ,MAAM;EACd,aAAa,MAAM;EACX;EACG;EACA;EACH;EAEP;EACgB;AAKvB,QAAO,oBAAC,YAAD,EAAY,OAAM,mDAAoD"}
1
+ {"version":3,"file":"wrapper.js","names":[],"sources":["../../../src/react/charts/wrapper.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { useChartData } from \"../hooks/use-chart-data\";\nimport { ChartErrorBoundary } from \"./chart-error-boundary\";\nimport { EmptyState } from \"./empty\";\nimport { ErrorState } from \"./error\";\nimport { LoadingSkeleton } from \"./loading\";\nimport type { ChartData, DataFormat } from \"./types\";\nimport { isArrowTable } from \"./types\";\n\n// ============================================================================\n// Props Types\n// ============================================================================\n\ninterface ChartWrapperQueryProps {\n /** Analytics query key */\n queryKey: string;\n /** Query parameters */\n parameters?: Record<string, unknown>;\n /** Data format preference */\n format?: DataFormat;\n /** Transform data after fetching */\n transformer?: <T>(data: T) => T;\n /** Direct data - not used in query mode */\n data?: never;\n}\n\ninterface ChartWrapperDataProps {\n /** Direct data (Arrow Table or JSON array) */\n data: ChartData;\n /** Not used in data mode */\n queryKey?: never;\n parameters?: never;\n format?: never;\n transformer?: never;\n}\n\ninterface CommonProps {\n /** Chart height in pixels */\n height?: number;\n /** Additional CSS classes */\n className?: string;\n /** Accessibility label */\n ariaLabel?: string;\n /** Test ID for automated testing */\n testId?: string;\n /** Render function receiving the chart data */\n children: (data: ChartData) => ReactNode;\n}\n\nexport type ChartWrapperProps = CommonProps &\n (ChartWrapperQueryProps | ChartWrapperDataProps);\n\n// ============================================================================\n// Query Mode Content\n// ============================================================================\n\nfunction QueryModeContent({\n queryKey,\n parameters,\n format,\n transformer,\n height,\n className,\n ariaLabel,\n testId,\n children,\n}: CommonProps & ChartWrapperQueryProps) {\n const { data, loading, error, isEmpty } = useChartData({\n queryKey,\n parameters,\n format,\n transformer,\n });\n\n if (loading) return <LoadingSkeleton height={height ?? 300} />;\n if (error) return <ErrorState error={error} />;\n if (isEmpty || !data) return <EmptyState />;\n\n return (\n <ChartErrorBoundary\n fallback={<ErrorState error=\"Failed to render chart\" />}\n >\n <div\n className={className}\n style={{ height }}\n aria-label={ariaLabel}\n data-testid={testId}\n role=\"img\"\n >\n {children(data)}\n </div>\n </ChartErrorBoundary>\n );\n}\n\n// ============================================================================\n// Data Mode Content\n// ============================================================================\n\nfunction DataModeContent({\n data,\n height,\n className,\n ariaLabel,\n testId,\n children,\n}: CommonProps & ChartWrapperDataProps) {\n const isEmpty = isArrowTable(data)\n ? data.numRows === 0\n : !Array.isArray(data) || data.length === 0;\n\n if (isEmpty) return <EmptyState />;\n\n return (\n <ChartErrorBoundary\n fallback={<ErrorState error=\"Failed to render chart\" />}\n >\n <div\n className={className}\n style={{ height }}\n aria-label={ariaLabel}\n data-testid={testId}\n role=\"img\"\n >\n {children(data)}\n </div>\n </ChartErrorBoundary>\n );\n}\n\n// ============================================================================\n// Main Wrapper Component\n// ============================================================================\n\n/**\n * Wrapper component for charts.\n * Handles data fetching (query mode) or direct data injection (data mode).\n *\n * @example Query mode - fetches data from analytics endpoint\n * ```tsx\n * <ChartWrapper\n * queryKey=\"spend_data\"\n * parameters={{ limit: 100 }}\n * format=\"auto\"\n * >\n * {(data) => <MyChart data={data} />}\n * </ChartWrapper>\n * ```\n *\n * @example Data mode - uses provided data directly\n * ```tsx\n * <ChartWrapper data={myArrowTable}>\n * {(data) => <MyChart data={data} />}\n * </ChartWrapper>\n * ```\n */\nexport function ChartWrapper(props: ChartWrapperProps) {\n const { height = 300, className, ariaLabel, testId, children } = props;\n\n // Data mode: use provided data directly\n if (\"data\" in props && props.data !== undefined) {\n return (\n <DataModeContent\n data={props.data}\n height={height}\n className={className}\n ariaLabel={ariaLabel}\n testId={testId}\n >\n {children}\n </DataModeContent>\n );\n }\n\n // Query mode: fetch data from analytics endpoint\n if (\"queryKey\" in props && props.queryKey !== undefined) {\n return (\n <QueryModeContent\n queryKey={props.queryKey}\n parameters={props.parameters}\n format={props.format}\n transformer={props.transformer}\n height={height}\n className={className}\n ariaLabel={ariaLabel}\n testId={testId}\n >\n {children}\n </QueryModeContent>\n );\n }\n\n // Should never reach here due to TypeScript, but safety fallback\n return <ErrorState error=\"Chart requires either 'queryKey' or 'data' prop\" />;\n}\n"],"mappings":";;;;;;;;;AAwDA,SAAS,iBAAiB,EACxB,UACA,YACA,QACA,aACA,QACA,WACA,WACA,QACA,YACuC;CACvC,MAAM,EAAE,MAAM,SAAS,OAAO,YAAY,aAAa;EACrD;EACA;EACA;EACA;EACD,CAAC;AAEF,KAAI,QAAS,QAAO,oBAAC,mBAAgB,QAAQ,UAAU,MAAO;AAC9D,KAAI,MAAO,QAAO,oBAAC,cAAkB,QAAS;AAC9C,KAAI,WAAW,CAAC,KAAM,QAAO,oBAAC,eAAa;AAE3C,QACE,oBAAC;EACC,UAAU,oBAAC,cAAW,OAAM,2BAA2B;YAEvD,oBAAC;GACY;GACX,OAAO,EAAE,QAAQ;GACjB,cAAY;GACZ,eAAa;GACb,MAAK;aAEJ,SAAS,KAAK;IACX;GACa;;AAQzB,SAAS,gBAAgB,EACvB,MACA,QACA,WACA,WACA,QACA,YACsC;AAKtC,KAJgB,aAAa,KAAK,GAC9B,KAAK,YAAY,IACjB,CAAC,MAAM,QAAQ,KAAK,IAAI,KAAK,WAAW,EAE/B,QAAO,oBAAC,eAAa;AAElC,QACE,oBAAC;EACC,UAAU,oBAAC,cAAW,OAAM,2BAA2B;YAEvD,oBAAC;GACY;GACX,OAAO,EAAE,QAAQ;GACjB,cAAY;GACZ,eAAa;GACb,MAAK;aAEJ,SAAS,KAAK;IACX;GACa;;;;;;;;;;;;;;;;;;;;;;;;AA8BzB,SAAgB,aAAa,OAA0B;CACrD,MAAM,EAAE,SAAS,KAAK,WAAW,WAAW,QAAQ,aAAa;AAGjE,KAAI,UAAU,SAAS,MAAM,SAAS,OACpC,QACE,oBAAC;EACC,MAAM,MAAM;EACJ;EACG;EACA;EACH;EAEP;GACe;AAKtB,KAAI,cAAc,SAAS,MAAM,aAAa,OAC5C,QACE,oBAAC;EACC,UAAU,MAAM;EAChB,YAAY,MAAM;EAClB,QAAQ,MAAM;EACd,aAAa,MAAM;EACX;EACG;EACA;EACH;EAEP;GACgB;AAKvB,QAAO,oBAAC,cAAW,OAAM,oDAAoD"}
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime287 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/react/genie/genie-chat-input.d.ts
4
4
  interface GenieChatInputProps {
@@ -17,7 +17,7 @@ declare function GenieChatInput({
17
17
  disabled,
18
18
  placeholder,
19
19
  className
20
- }: GenieChatInputProps): react_jsx_runtime287.JSX.Element;
20
+ }: GenieChatInputProps): react_jsx_runtime0.JSX.Element;
21
21
  //#endregion
22
22
  export { GenieChatInput };
23
23
  //# sourceMappingURL=genie-chat-input.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"genie-chat-input.d.ts","names":[],"sources":["../../../src/react/genie/genie-chat-input.tsx"],"sourcesContent":[],"mappings":";;;UAIiB,mBAAA;;;EAAA;EAYD,QAAA,CAAA,EAAA,OAAc;EAAA;aAC5B,CAAA,EAAA,MAAA;;WAEA,CAAA,EAAA,MAAA;;;AAEoB,iBALN,cAAA,CAKM;EAAA,MAAA;EAAA,QAAA;EAAA,WAAA;EAAA;AAAA,CAAA,EAAnB,mBAAmB,CAAA,EAAA,oBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"genie-chat-input.d.ts","names":[],"sources":["../../../src/react/genie/genie-chat-input.tsx"],"mappings":";;;UAIiB,mBAAA;;EAEf,MAAA,GAAS,OAAA;EAFM;EAIf,QAAA;;EAEA,WAAA;EAJA;EAMA,SAAA;AAAA;;iBAIc,cAAA,CAAA;EACd,MAAA;EACA,QAAA;EACA,WAAA;EACA;AAAA,GACC,mBAAA,GAAmB,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"genie-chat-input.js","names":[],"sources":["../../../src/react/genie/genie-chat-input.tsx"],"sourcesContent":["import { type KeyboardEvent, useRef, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../ui/button\";\n\nexport interface GenieChatInputProps {\n /** Callback fired when the user submits a message */\n onSend: (content: string) => void;\n /** Disable the input and send button */\n disabled?: boolean;\n /** Placeholder text shown in the textarea */\n placeholder?: string;\n /** Additional CSS class for the container */\n className?: string;\n}\n\n/** Auto-expanding textarea input with a send button for chat messages. Submits on Enter (Shift+Enter for newline). */\nexport function GenieChatInput({\n onSend,\n disabled = false,\n placeholder = \"Ask a question...\",\n className,\n}: GenieChatInputProps) {\n const [value, setValue] = useState(\"\");\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n const handleSubmit = () => {\n const trimmed = value.trim();\n if (!trimmed || disabled) return;\n onSend(trimmed);\n setValue(\"\");\n if (textareaRef.current) {\n textareaRef.current.style.height = \"auto\";\n }\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n };\n\n const MAX_HEIGHT = 200;\n\n const handleInput = () => {\n const textarea = textareaRef.current;\n if (textarea) {\n textarea.style.height = \"auto\";\n const clamped = Math.min(textarea.scrollHeight, MAX_HEIGHT);\n textarea.style.height = `${clamped}px`;\n textarea.style.overflowY =\n textarea.scrollHeight > MAX_HEIGHT ? \"auto\" : \"hidden\";\n }\n };\n\n return (\n <div className={cn(\"flex gap-2 p-4 border-t shrink-0\", className)}>\n <textarea\n ref={textareaRef}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={handleKeyDown}\n onInput={handleInput}\n placeholder={placeholder}\n disabled={disabled}\n rows={1}\n className={cn(\n \"flex-1 resize-none overflow-hidden rounded-md border border-input bg-background px-3 py-2\",\n \"text-sm placeholder:text-muted-foreground\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n )}\n />\n <Button\n onClick={handleSubmit}\n disabled={disabled || !value.trim()}\n size=\"default\"\n className=\"self-end\"\n >\n Send\n </Button>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAgBA,SAAgB,eAAe,EAC7B,QACA,WAAW,OACX,cAAc,qBACd,aACsB;CACtB,MAAM,CAAC,OAAO,YAAY,SAAS,GAAG;CACtC,MAAM,cAAc,OAA4B,KAAK;CAErD,MAAM,qBAAqB;EACzB,MAAM,UAAU,MAAM,MAAM;AAC5B,MAAI,CAAC,WAAW,SAAU;AAC1B,SAAO,QAAQ;AACf,WAAS,GAAG;AACZ,MAAI,YAAY,QACd,aAAY,QAAQ,MAAM,SAAS;;CAIvC,MAAM,iBAAiB,MAA0C;AAC/D,MAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,UAAU;AACpC,KAAE,gBAAgB;AAClB,iBAAc;;;CAIlB,MAAM,aAAa;CAEnB,MAAM,oBAAoB;EACxB,MAAM,WAAW,YAAY;AAC7B,MAAI,UAAU;AACZ,YAAS,MAAM,SAAS;GACxB,MAAM,UAAU,KAAK,IAAI,SAAS,cAAc,WAAW;AAC3D,YAAS,MAAM,SAAS,GAAG,QAAQ;AACnC,YAAS,MAAM,YACb,SAAS,eAAe,aAAa,SAAS;;;AAIpD,QACE,qBAAC,OAAD;EAAK,WAAW,GAAG,oCAAoC,UAAU;YAAjE,CACE,oBAAC,YAAD;GACE,KAAK;GACE;GACP,WAAW,MAAM,SAAS,EAAE,OAAO,MAAM;GACzC,WAAW;GACX,SAAS;GACI;GACH;GACV,MAAM;GACN,WAAW,GACT,6FACA,6CACA,2EACA,kDACD;GACD,GACF,oBAAC,QAAD;GACE,SAAS;GACT,UAAU,YAAY,CAAC,MAAM,MAAM;GACnC,MAAK;GACL,WAAU;aACX;GAEQ,EACL"}
1
+ {"version":3,"file":"genie-chat-input.js","names":[],"sources":["../../../src/react/genie/genie-chat-input.tsx"],"sourcesContent":["import { type KeyboardEvent, useRef, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../ui/button\";\n\nexport interface GenieChatInputProps {\n /** Callback fired when the user submits a message */\n onSend: (content: string) => void;\n /** Disable the input and send button */\n disabled?: boolean;\n /** Placeholder text shown in the textarea */\n placeholder?: string;\n /** Additional CSS class for the container */\n className?: string;\n}\n\n/** Auto-expanding textarea input with a send button for chat messages. Submits on Enter (Shift+Enter for newline). */\nexport function GenieChatInput({\n onSend,\n disabled = false,\n placeholder = \"Ask a question...\",\n className,\n}: GenieChatInputProps) {\n const [value, setValue] = useState(\"\");\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n const handleSubmit = () => {\n const trimmed = value.trim();\n if (!trimmed || disabled) return;\n onSend(trimmed);\n setValue(\"\");\n if (textareaRef.current) {\n textareaRef.current.style.height = \"auto\";\n }\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n };\n\n const MAX_HEIGHT = 200;\n\n const handleInput = () => {\n const textarea = textareaRef.current;\n if (textarea) {\n textarea.style.height = \"auto\";\n const clamped = Math.min(textarea.scrollHeight, MAX_HEIGHT);\n textarea.style.height = `${clamped}px`;\n textarea.style.overflowY =\n textarea.scrollHeight > MAX_HEIGHT ? \"auto\" : \"hidden\";\n }\n };\n\n return (\n <div className={cn(\"flex gap-2 p-4 border-t shrink-0\", className)}>\n <textarea\n ref={textareaRef}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={handleKeyDown}\n onInput={handleInput}\n placeholder={placeholder}\n disabled={disabled}\n rows={1}\n className={cn(\n \"flex-1 resize-none overflow-hidden rounded-md border border-input bg-background px-3 py-2\",\n \"text-sm placeholder:text-muted-foreground\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n )}\n />\n <Button\n onClick={handleSubmit}\n disabled={disabled || !value.trim()}\n size=\"default\"\n className=\"self-end\"\n >\n Send\n </Button>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAgBA,SAAgB,eAAe,EAC7B,QACA,WAAW,OACX,cAAc,qBACd,aACsB;CACtB,MAAM,CAAC,OAAO,YAAY,SAAS,GAAG;CACtC,MAAM,cAAc,OAA4B,KAAK;CAErD,MAAM,qBAAqB;EACzB,MAAM,UAAU,MAAM,MAAM;AAC5B,MAAI,CAAC,WAAW,SAAU;AAC1B,SAAO,QAAQ;AACf,WAAS,GAAG;AACZ,MAAI,YAAY,QACd,aAAY,QAAQ,MAAM,SAAS;;CAIvC,MAAM,iBAAiB,MAA0C;AAC/D,MAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,UAAU;AACpC,KAAE,gBAAgB;AAClB,iBAAc;;;CAIlB,MAAM,aAAa;CAEnB,MAAM,oBAAoB;EACxB,MAAM,WAAW,YAAY;AAC7B,MAAI,UAAU;AACZ,YAAS,MAAM,SAAS;GACxB,MAAM,UAAU,KAAK,IAAI,SAAS,cAAc,WAAW;AAC3D,YAAS,MAAM,SAAS,GAAG,QAAQ;AACnC,YAAS,MAAM,YACb,SAAS,eAAe,aAAa,SAAS;;;AAIpD,QACE,qBAAC;EAAI,WAAW,GAAG,oCAAoC,UAAU;aAC/D,oBAAC;GACC,KAAK;GACE;GACP,WAAW,MAAM,SAAS,EAAE,OAAO,MAAM;GACzC,WAAW;GACX,SAAS;GACI;GACH;GACV,MAAM;GACN,WAAW,GACT,6FACA,6CACA,2EACA,kDACD;IACD,EACF,oBAAC;GACC,SAAS;GACT,UAAU,YAAY,CAAC,MAAM,MAAM;GACnC,MAAK;GACL,WAAU;aACX;IAEQ;GACL"}
@@ -1,5 +1,5 @@
1
1
  import { GenieChatStatus, GenieMessageItem } from "./types.js";
2
- import * as react_jsx_runtime289 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/react/genie/genie-chat-message-list.d.ts
5
5
  interface GenieChatMessageListProps {
@@ -9,13 +9,19 @@ interface GenieChatMessageListProps {
9
9
  status: GenieChatStatus;
10
10
  /** Additional CSS class for the scroll area */
11
11
  className?: string;
12
+ /** Whether a previous page of older messages exists */
13
+ hasPreviousPage?: boolean;
14
+ /** Callback to fetch the previous page of messages */
15
+ onFetchPreviousPage?: () => void;
12
16
  }
13
17
  /** Scrollable message list that renders Genie chat messages with auto-scroll, skeleton loaders, and a streaming indicator. */
14
18
  declare function GenieChatMessageList({
15
19
  messages,
16
20
  status,
17
- className
18
- }: GenieChatMessageListProps): react_jsx_runtime289.JSX.Element;
21
+ className,
22
+ hasPreviousPage,
23
+ onFetchPreviousPage
24
+ }: GenieChatMessageListProps): react_jsx_runtime0.JSX.Element;
19
25
  //#endregion
20
26
  export { GenieChatMessageList };
21
27
  //# sourceMappingURL=genie-chat-message-list.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"genie-chat-message-list.d.ts","names":[],"sources":["../../../src/react/genie/genie-chat-message-list.tsx"],"sourcesContent":[],"mappings":";;;;UAQiB,yBAAA;;YAEL;EAFK;EAAyB,MAAA,EAIhC,eAJgC;;WAIhC,CAAA,EAAA,MAAA;;AA8BV;AAAoC,iBAApB,oBAAA,CAAoB;EAAA,QAAA;EAAA,MAAA;EAAA;AAAA,CAAA,EAIjC,yBAJiC,CAAA,EAIR,oBAAA,CAAA,GAAA,CAAA,OAJQ"}
1
+ {"version":3,"file":"genie-chat-message-list.d.ts","names":[],"sources":["../../../src/react/genie/genie-chat-message-list.tsx"],"mappings":";;;;UAQiB,yBAAA;;EAEf,QAAA,EAAU,gBAAA;EAFK;EAIf,MAAA,EAAQ,eAAA;;EAER,SAAA;EAJA;EAMA,eAAA;EAJA;EAMA,mBAAA;AAAA;;iBAkIc,oBAAA,CAAA;EACd,QAAA;EACA,MAAA;EACA,SAAA;EACA,eAAA;EACA;AAAA,GACC,yBAAA,GAAyB,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -3,7 +3,7 @@ import { ScrollArea } from "../ui/scroll-area.js";
3
3
  import { Skeleton } from "../ui/skeleton.js";
4
4
  import { Spinner } from "../ui/spinner.js";
5
5
  import { GenieChatMessage } from "./genie-chat-message.js";
6
- import { useEffect, useRef } from "react";
6
+ import { useEffect, useLayoutEffect, useRef } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
 
9
9
  //#region src/react/genie/genie-chat-message-list.tsx
@@ -16,27 +16,102 @@ const STATUS_LABELS = {
16
16
  function formatStatus(status) {
17
17
  return STATUS_LABELS[status] ?? status.replace(/_/g, " ").toLowerCase();
18
18
  }
19
- function StreamingIndicator({ messages }) {
20
- const last = messages[messages.length - 1];
21
- if (last?.role === "assistant" && last.id === "") return /* @__PURE__ */ jsxs("div", {
22
- className: "flex items-center gap-2 text-sm text-muted-foreground px-11",
23
- children: [/* @__PURE__ */ jsx(Spinner, { className: "h-3 w-3" }), /* @__PURE__ */ jsx("span", { children: formatStatus(last.status) })]
24
- });
25
- return null;
19
+ function getViewport(scrollRef) {
20
+ return scrollRef.current?.querySelector("[data-slot=\"scroll-area-viewport\"]");
26
21
  }
27
- /** Scrollable message list that renders Genie chat messages with auto-scroll, skeleton loaders, and a streaming indicator. */
28
- function GenieChatMessageList({ messages, status, className }) {
29
- const scrollRef = useRef(null);
22
+ /**
23
+ * Manages scroll position: scrolls to bottom on append/initial load,
24
+ * preserves position when older messages are prepended.
25
+ */
26
+ function useScrollManagement(scrollRef, messages, status) {
27
+ const prevFirstMessageIdRef = useRef(null);
28
+ const prevScrollHeightRef = useRef(0);
29
+ const prevMessageCountRef = useRef(0);
30
30
  useEffect(() => {
31
- const viewport = scrollRef.current?.querySelector("[data-slot=\"scroll-area-viewport\"]");
32
- if (viewport) viewport.scrollTop = viewport.scrollHeight;
31
+ const viewport = getViewport(scrollRef);
32
+ if (!viewport) return;
33
+ const observer = new ResizeObserver(() => {
34
+ prevScrollHeightRef.current = viewport.scrollHeight;
35
+ });
36
+ observer.observe(viewport);
37
+ return () => observer.disconnect();
38
+ }, [scrollRef]);
39
+ useLayoutEffect(() => {
40
+ const viewport = getViewport(scrollRef);
41
+ if (!viewport) return;
42
+ const count = messages.length;
43
+ const countChanged = count !== prevMessageCountRef.current;
44
+ prevMessageCountRef.current = count;
45
+ if (!countChanged) {
46
+ prevScrollHeightRef.current = viewport.scrollHeight;
47
+ return;
48
+ }
49
+ const firstMessageId = messages[0]?.id ?? null;
50
+ if (prevFirstMessageIdRef.current !== null && firstMessageId !== prevFirstMessageIdRef.current && prevScrollHeightRef.current > 0) {
51
+ const delta = viewport.scrollHeight - prevScrollHeightRef.current;
52
+ viewport.scrollTop += delta;
53
+ } else viewport.scrollTop = viewport.scrollHeight;
54
+ prevFirstMessageIdRef.current = firstMessageId;
55
+ prevScrollHeightRef.current = viewport.scrollHeight;
33
56
  }, [messages.length, status]);
57
+ }
58
+ /**
59
+ * Observes a sentinel element at the top of the scroll area and triggers
60
+ * `onFetchPreviousPage` when the user scrolls to the top (only if content overflows).
61
+ * Returns a ref to attach to the sentinel element.
62
+ */
63
+ function useLoadOlderOnScroll(scrollRef, shouldObserve, onFetchPreviousPage) {
64
+ const sentinelRef = useRef(null);
65
+ const onFetchPreviousPageRef = useRef(onFetchPreviousPage);
66
+ onFetchPreviousPageRef.current = onFetchPreviousPage;
67
+ useEffect(() => {
68
+ const sentinel = sentinelRef.current;
69
+ const viewport = getViewport(scrollRef);
70
+ if (!sentinel || !viewport || !shouldObserve) return;
71
+ let armed = false;
72
+ const frameId = requestAnimationFrame(() => {
73
+ armed = true;
74
+ });
75
+ const observer = new IntersectionObserver((entries) => {
76
+ if (!armed) return;
77
+ const isScrollable = viewport.scrollHeight > viewport.clientHeight;
78
+ if (entries[0]?.isIntersecting && isScrollable) onFetchPreviousPageRef.current?.();
79
+ }, {
80
+ root: viewport,
81
+ threshold: 0
82
+ });
83
+ observer.observe(sentinel);
84
+ return () => {
85
+ cancelAnimationFrame(frameId);
86
+ observer.disconnect();
87
+ };
88
+ }, [scrollRef, shouldObserve]);
89
+ return sentinelRef;
90
+ }
91
+ /** Scrollable message list that renders Genie chat messages with auto-scroll, skeleton loaders, and a streaming indicator. */
92
+ function GenieChatMessageList({ messages, status, className, hasPreviousPage = false, onFetchPreviousPage }) {
93
+ const scrollRef = useRef(null);
94
+ const sentinelRef = useLoadOlderOnScroll(scrollRef, hasPreviousPage && status !== "loading-older", onFetchPreviousPage);
95
+ useScrollManagement(scrollRef, messages, status);
96
+ const lastMessage = messages[messages.length - 1];
97
+ const showStreamingIndicator = status === "streaming" && lastMessage?.role === "assistant" && lastMessage.id === "";
34
98
  return /* @__PURE__ */ jsx(ScrollArea, {
35
99
  ref: scrollRef,
36
100
  className: cn("flex-1 min-h-0 p-4", className),
37
101
  children: /* @__PURE__ */ jsxs("div", {
38
102
  className: "flex flex-col gap-4",
39
103
  children: [
104
+ hasPreviousPage && /* @__PURE__ */ jsx("div", {
105
+ ref: sentinelRef,
106
+ className: "h-px"
107
+ }),
108
+ status === "loading-older" && /* @__PURE__ */ jsxs("div", {
109
+ className: "flex items-center justify-center gap-2 py-2",
110
+ children: [/* @__PURE__ */ jsx(Spinner, { className: "h-3 w-3" }), /* @__PURE__ */ jsx("span", {
111
+ className: "text-sm text-muted-foreground",
112
+ children: "Loading older messages..."
113
+ })]
114
+ }),
40
115
  status === "loading-history" && messages.length === 0 && /* @__PURE__ */ jsxs("div", {
41
116
  className: "flex flex-col gap-4",
42
117
  children: [
@@ -45,11 +120,11 @@ function GenieChatMessageList({ messages, status, className }) {
45
120
  /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-2/3 self-end" })
46
121
  ]
47
122
  }),
48
- messages.map((msg) => {
49
- if (msg.role === "assistant" && msg.id === "" && !msg.content) return null;
50
- return /* @__PURE__ */ jsx(GenieChatMessage, { message: msg }, msg.id);
123
+ messages.filter((msg) => msg.role !== "assistant" || msg.id !== "" || msg.content).map((msg) => /* @__PURE__ */ jsx(GenieChatMessage, { message: msg }, msg.id)),
124
+ showStreamingIndicator && /* @__PURE__ */ jsxs("div", {
125
+ className: "flex items-center gap-2 text-sm text-muted-foreground px-11",
126
+ children: [/* @__PURE__ */ jsx(Spinner, { className: "h-3 w-3" }), /* @__PURE__ */ jsx("span", { children: formatStatus(lastMessage.status) })]
51
127
  }),
52
- status === "streaming" && messages.length > 0 && /* @__PURE__ */ jsx(StreamingIndicator, { messages }),
53
128
  messages.length === 0 && status === "idle" && /* @__PURE__ */ jsx("div", {
54
129
  className: "flex items-center justify-center h-full text-muted-foreground text-sm py-12",
55
130
  children: "Start a conversation by typing a question below."
@@ -1 +1 @@
1
- {"version":3,"file":"genie-chat-message-list.js","names":[],"sources":["../../../src/react/genie/genie-chat-message-list.tsx"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { ScrollArea } from \"../ui/scroll-area\";\nimport { Skeleton } from \"../ui/skeleton\";\nimport { Spinner } from \"../ui/spinner\";\nimport { GenieChatMessage } from \"./genie-chat-message\";\nimport type { GenieChatStatus, GenieMessageItem } from \"./types\";\n\nexport interface GenieChatMessageListProps {\n /** Array of messages to display */\n messages: GenieMessageItem[];\n /** Current chat status (controls loading indicators and skeleton placeholders) */\n status: GenieChatStatus;\n /** Additional CSS class for the scroll area */\n className?: string;\n}\n\nconst STATUS_LABELS: Record<string, string> = {\n ASKING_AI: \"Asking AI...\",\n EXECUTING_QUERY: \"Executing query...\",\n FILTERING_RESULTS: \"Filtering results...\",\n COMPLETED: \"Done\",\n};\n\nfunction formatStatus(status: string): string {\n return STATUS_LABELS[status] ?? status.replace(/_/g, \" \").toLowerCase();\n}\n\nfunction StreamingIndicator({ messages }: { messages: GenieMessageItem[] }) {\n const last = messages[messages.length - 1];\n if (last?.role === \"assistant\" && last.id === \"\") {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground px-11\">\n <Spinner className=\"h-3 w-3\" />\n <span>{formatStatus(last.status)}</span>\n </div>\n );\n }\n return null;\n}\n\n/** Scrollable message list that renders Genie chat messages with auto-scroll, skeleton loaders, and a streaming indicator. */\nexport function GenieChatMessageList({\n messages,\n status,\n className,\n}: GenieChatMessageListProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Scroll only the ScrollArea viewport, not the page\n // biome-ignore lint/correctness/useExhaustiveDependencies: intentional triggers for auto-scroll\n useEffect(() => {\n const viewport = scrollRef.current?.querySelector<HTMLElement>(\n '[data-slot=\"scroll-area-viewport\"]',\n );\n if (viewport) {\n viewport.scrollTop = viewport.scrollHeight;\n }\n }, [messages.length, status]);\n\n return (\n <ScrollArea ref={scrollRef} className={cn(\"flex-1 min-h-0 p-4\", className)}>\n <div className=\"flex flex-col gap-4\">\n {status === \"loading-history\" && messages.length === 0 && (\n <div className=\"flex flex-col gap-4\">\n <Skeleton className=\"h-12 w-3/4\" />\n <Skeleton className=\"h-20 w-4/5 self-start\" />\n <Skeleton className=\"h-12 w-2/3 self-end\" />\n </div>\n )}\n\n {messages.map((msg) => {\n if (msg.role === \"assistant\" && msg.id === \"\" && !msg.content) {\n return null;\n }\n return <GenieChatMessage key={msg.id} message={msg} />;\n })}\n\n {status === \"streaming\" && messages.length > 0 && (\n <StreamingIndicator messages={messages} />\n )}\n\n {messages.length === 0 && status === \"idle\" && (\n <div className=\"flex items-center justify-center h-full text-muted-foreground text-sm py-12\">\n Start a conversation by typing a question below.\n </div>\n )}\n </div>\n </ScrollArea>\n );\n}\n"],"mappings":";;;;;;;;;AAiBA,MAAM,gBAAwC;CAC5C,WAAW;CACX,iBAAiB;CACjB,mBAAmB;CACnB,WAAW;CACZ;AAED,SAAS,aAAa,QAAwB;AAC5C,QAAO,cAAc,WAAW,OAAO,QAAQ,MAAM,IAAI,CAAC,aAAa;;AAGzE,SAAS,mBAAmB,EAAE,YAA8C;CAC1E,MAAM,OAAO,SAAS,SAAS,SAAS;AACxC,KAAI,MAAM,SAAS,eAAe,KAAK,OAAO,GAC5C,QACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACE,oBAAC,SAAD,EAAS,WAAU,WAAY,GAC/B,oBAAC,QAAD,YAAO,aAAa,KAAK,OAAO,EAAQ,EACpC;;AAGV,QAAO;;;AAIT,SAAgB,qBAAqB,EACnC,UACA,QACA,aAC4B;CAC5B,MAAM,YAAY,OAAuB,KAAK;AAI9C,iBAAgB;EACd,MAAM,WAAW,UAAU,SAAS,cAClC,uCACD;AACD,MAAI,SACF,UAAS,YAAY,SAAS;IAE/B,CAAC,SAAS,QAAQ,OAAO,CAAC;AAE7B,QACE,oBAAC,YAAD;EAAY,KAAK;EAAW,WAAW,GAAG,sBAAsB,UAAU;YACxE,qBAAC,OAAD;GAAK,WAAU;aAAf;IACG,WAAW,qBAAqB,SAAS,WAAW,KACnD,qBAAC,OAAD;KAAK,WAAU;eAAf;MACE,oBAAC,UAAD,EAAU,WAAU,cAAe;MACnC,oBAAC,UAAD,EAAU,WAAU,yBAA0B;MAC9C,oBAAC,UAAD,EAAU,WAAU,uBAAwB;MACxC;;IAGP,SAAS,KAAK,QAAQ;AACrB,SAAI,IAAI,SAAS,eAAe,IAAI,OAAO,MAAM,CAAC,IAAI,QACpD,QAAO;AAET,YAAO,oBAAC,kBAAD,EAA+B,SAAS,KAAO,EAAxB,IAAI,GAAoB;MACtD;IAED,WAAW,eAAe,SAAS,SAAS,KAC3C,oBAAC,oBAAD,EAA8B,UAAY;IAG3C,SAAS,WAAW,KAAK,WAAW,UACnC,oBAAC,OAAD;KAAK,WAAU;eAA8E;KAEvF;IAEJ;;EACK"}
1
+ {"version":3,"file":"genie-chat-message-list.js","names":[],"sources":["../../../src/react/genie/genie-chat-message-list.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { ScrollArea } from \"../ui/scroll-area\";\nimport { Skeleton } from \"../ui/skeleton\";\nimport { Spinner } from \"../ui/spinner\";\nimport { GenieChatMessage } from \"./genie-chat-message\";\nimport type { GenieChatStatus, GenieMessageItem } from \"./types\";\n\nexport interface GenieChatMessageListProps {\n /** Array of messages to display */\n messages: GenieMessageItem[];\n /** Current chat status (controls loading indicators and skeleton placeholders) */\n status: GenieChatStatus;\n /** Additional CSS class for the scroll area */\n className?: string;\n /** Whether a previous page of older messages exists */\n hasPreviousPage?: boolean;\n /** Callback to fetch the previous page of messages */\n onFetchPreviousPage?: () => void;\n}\n\nconst STATUS_LABELS: Record<string, string> = {\n ASKING_AI: \"Asking AI...\",\n EXECUTING_QUERY: \"Executing query...\",\n FILTERING_RESULTS: \"Filtering results...\",\n COMPLETED: \"Done\",\n};\n\nfunction formatStatus(status: string): string {\n return STATUS_LABELS[status] ?? status.replace(/_/g, \" \").toLowerCase();\n}\n\nfunction getViewport(scrollRef: React.RefObject<HTMLDivElement | null>) {\n return scrollRef.current?.querySelector<HTMLElement>(\n '[data-slot=\"scroll-area-viewport\"]',\n );\n}\n\n/**\n * Manages scroll position: scrolls to bottom on append/initial load,\n * preserves position when older messages are prepended.\n */\nfunction useScrollManagement(\n scrollRef: React.RefObject<HTMLDivElement | null>,\n messages: GenieMessageItem[],\n status: GenieChatStatus,\n) {\n const prevFirstMessageIdRef = useRef<string | null>(null);\n const prevScrollHeightRef = useRef(0);\n const prevMessageCountRef = useRef(0);\n\n // Keep prevScrollHeightRef fresh when async content (images, embeds)\n // changes the viewport height between renders.\n useEffect(() => {\n const viewport = getViewport(scrollRef);\n if (!viewport) return;\n\n const observer = new ResizeObserver(() => {\n prevScrollHeightRef.current = viewport.scrollHeight;\n });\n observer.observe(viewport);\n return () => observer.disconnect();\n }, [scrollRef]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: react to message count AND status so prevScrollHeightRef stays accurate when the loading indicator appears/disappears\n useLayoutEffect(() => {\n const viewport = getViewport(scrollRef);\n if (!viewport) return;\n\n const count = messages.length;\n const countChanged = count !== prevMessageCountRef.current;\n prevMessageCountRef.current = count;\n\n // Nothing to do if message count didn't change (e.g. status-only transition)\n if (!countChanged) {\n prevScrollHeightRef.current = viewport.scrollHeight;\n return;\n }\n\n const firstMessageId = messages[0]?.id ?? null;\n const wasPrepend =\n prevFirstMessageIdRef.current !== null &&\n firstMessageId !== prevFirstMessageIdRef.current;\n\n if (wasPrepend && prevScrollHeightRef.current > 0) {\n // Older messages prepended — preserve scroll position\n const delta = viewport.scrollHeight - prevScrollHeightRef.current;\n viewport.scrollTop += delta;\n } else {\n // Messages appended or initial load — scroll to bottom\n viewport.scrollTop = viewport.scrollHeight;\n }\n\n prevFirstMessageIdRef.current = firstMessageId;\n prevScrollHeightRef.current = viewport.scrollHeight;\n }, [messages.length, status]);\n}\n\n/**\n * Observes a sentinel element at the top of the scroll area and triggers\n * `onFetchPreviousPage` when the user scrolls to the top (only if content overflows).\n * Returns a ref to attach to the sentinel element.\n */\nfunction useLoadOlderOnScroll(\n scrollRef: React.RefObject<HTMLDivElement | null>,\n shouldObserve: boolean,\n onFetchPreviousPage?: () => void,\n) {\n const sentinelRef = useRef<HTMLDivElement>(null);\n const onFetchPreviousPageRef = useRef(onFetchPreviousPage);\n onFetchPreviousPageRef.current = onFetchPreviousPage;\n\n useEffect(() => {\n const sentinel = sentinelRef.current;\n const viewport = getViewport(scrollRef);\n if (!sentinel || !viewport || !shouldObserve) return;\n\n // The observer fires synchronously on observe() if the sentinel is\n // already visible. We arm it on the next frame so that synchronous\n // initial fire is ignored, but a real intersection (user genuinely\n // at the top on a short conversation) triggers on subsequent frames.\n let armed = false;\n const frameId = requestAnimationFrame(() => {\n armed = true;\n });\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (!armed) return;\n const isScrollable = viewport.scrollHeight > viewport.clientHeight;\n if (entries[0]?.isIntersecting && isScrollable) {\n onFetchPreviousPageRef.current?.();\n }\n },\n { root: viewport, threshold: 0 },\n );\n\n observer.observe(sentinel);\n return () => {\n cancelAnimationFrame(frameId);\n observer.disconnect();\n };\n }, [scrollRef, shouldObserve]);\n\n return sentinelRef;\n}\n\n/** Scrollable message list that renders Genie chat messages with auto-scroll, skeleton loaders, and a streaming indicator. */\nexport function GenieChatMessageList({\n messages,\n status,\n className,\n hasPreviousPage = false,\n onFetchPreviousPage,\n}: GenieChatMessageListProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n\n const sentinelRef = useLoadOlderOnScroll(\n scrollRef,\n hasPreviousPage && status !== \"loading-older\",\n onFetchPreviousPage,\n );\n useScrollManagement(scrollRef, messages, status);\n\n const lastMessage = messages[messages.length - 1];\n const showStreamingIndicator =\n status === \"streaming\" &&\n lastMessage?.role === \"assistant\" &&\n lastMessage.id === \"\";\n\n return (\n <ScrollArea ref={scrollRef} className={cn(\"flex-1 min-h-0 p-4\", className)}>\n <div className=\"flex flex-col gap-4\">\n {hasPreviousPage && <div ref={sentinelRef} className=\"h-px\" />}\n\n {status === \"loading-older\" && (\n <div className=\"flex items-center justify-center gap-2 py-2\">\n <Spinner className=\"h-3 w-3\" />\n <span className=\"text-sm text-muted-foreground\">\n Loading older messages...\n </span>\n </div>\n )}\n\n {status === \"loading-history\" && messages.length === 0 && (\n <div className=\"flex flex-col gap-4\">\n <Skeleton className=\"h-12 w-3/4\" />\n <Skeleton className=\"h-20 w-4/5 self-start\" />\n <Skeleton className=\"h-12 w-2/3 self-end\" />\n </div>\n )}\n\n {messages\n .filter(\n (msg) => msg.role !== \"assistant\" || msg.id !== \"\" || msg.content,\n )\n .map((msg) => (\n <GenieChatMessage key={msg.id} message={msg} />\n ))}\n\n {showStreamingIndicator && (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground px-11\">\n <Spinner className=\"h-3 w-3\" />\n <span>{formatStatus(lastMessage.status)}</span>\n </div>\n )}\n\n {messages.length === 0 && status === \"idle\" && (\n <div className=\"flex items-center justify-center h-full text-muted-foreground text-sm py-12\">\n Start a conversation by typing a question below.\n </div>\n )}\n </div>\n </ScrollArea>\n );\n}\n"],"mappings":";;;;;;;;;AAqBA,MAAM,gBAAwC;CAC5C,WAAW;CACX,iBAAiB;CACjB,mBAAmB;CACnB,WAAW;CACZ;AAED,SAAS,aAAa,QAAwB;AAC5C,QAAO,cAAc,WAAW,OAAO,QAAQ,MAAM,IAAI,CAAC,aAAa;;AAGzE,SAAS,YAAY,WAAmD;AACtE,QAAO,UAAU,SAAS,cACxB,uCACD;;;;;;AAOH,SAAS,oBACP,WACA,UACA,QACA;CACA,MAAM,wBAAwB,OAAsB,KAAK;CACzD,MAAM,sBAAsB,OAAO,EAAE;CACrC,MAAM,sBAAsB,OAAO,EAAE;AAIrC,iBAAgB;EACd,MAAM,WAAW,YAAY,UAAU;AACvC,MAAI,CAAC,SAAU;EAEf,MAAM,WAAW,IAAI,qBAAqB;AACxC,uBAAoB,UAAU,SAAS;IACvC;AACF,WAAS,QAAQ,SAAS;AAC1B,eAAa,SAAS,YAAY;IACjC,CAAC,UAAU,CAAC;AAGf,uBAAsB;EACpB,MAAM,WAAW,YAAY,UAAU;AACvC,MAAI,CAAC,SAAU;EAEf,MAAM,QAAQ,SAAS;EACvB,MAAM,eAAe,UAAU,oBAAoB;AACnD,sBAAoB,UAAU;AAG9B,MAAI,CAAC,cAAc;AACjB,uBAAoB,UAAU,SAAS;AACvC;;EAGF,MAAM,iBAAiB,SAAS,IAAI,MAAM;AAK1C,MAHE,sBAAsB,YAAY,QAClC,mBAAmB,sBAAsB,WAEzB,oBAAoB,UAAU,GAAG;GAEjD,MAAM,QAAQ,SAAS,eAAe,oBAAoB;AAC1D,YAAS,aAAa;QAGtB,UAAS,YAAY,SAAS;AAGhC,wBAAsB,UAAU;AAChC,sBAAoB,UAAU,SAAS;IACtC,CAAC,SAAS,QAAQ,OAAO,CAAC;;;;;;;AAQ/B,SAAS,qBACP,WACA,eACA,qBACA;CACA,MAAM,cAAc,OAAuB,KAAK;CAChD,MAAM,yBAAyB,OAAO,oBAAoB;AAC1D,wBAAuB,UAAU;AAEjC,iBAAgB;EACd,MAAM,WAAW,YAAY;EAC7B,MAAM,WAAW,YAAY,UAAU;AACvC,MAAI,CAAC,YAAY,CAAC,YAAY,CAAC,cAAe;EAM9C,IAAI,QAAQ;EACZ,MAAM,UAAU,4BAA4B;AAC1C,WAAQ;IACR;EAEF,MAAM,WAAW,IAAI,sBAClB,YAAY;AACX,OAAI,CAAC,MAAO;GACZ,MAAM,eAAe,SAAS,eAAe,SAAS;AACtD,OAAI,QAAQ,IAAI,kBAAkB,aAChC,wBAAuB,WAAW;KAGtC;GAAE,MAAM;GAAU,WAAW;GAAG,CACjC;AAED,WAAS,QAAQ,SAAS;AAC1B,eAAa;AACX,wBAAqB,QAAQ;AAC7B,YAAS,YAAY;;IAEtB,CAAC,WAAW,cAAc,CAAC;AAE9B,QAAO;;;AAIT,SAAgB,qBAAqB,EACnC,UACA,QACA,WACA,kBAAkB,OAClB,uBAC4B;CAC5B,MAAM,YAAY,OAAuB,KAAK;CAE9C,MAAM,cAAc,qBAClB,WACA,mBAAmB,WAAW,iBAC9B,oBACD;AACD,qBAAoB,WAAW,UAAU,OAAO;CAEhD,MAAM,cAAc,SAAS,SAAS,SAAS;CAC/C,MAAM,yBACJ,WAAW,eACX,aAAa,SAAS,eACtB,YAAY,OAAO;AAErB,QACE,oBAAC;EAAW,KAAK;EAAW,WAAW,GAAG,sBAAsB,UAAU;YACxE,qBAAC;GAAI,WAAU;;IACZ,mBAAmB,oBAAC;KAAI,KAAK;KAAa,WAAU;MAAS;IAE7D,WAAW,mBACV,qBAAC;KAAI,WAAU;gBACb,oBAAC,WAAQ,WAAU,YAAY,EAC/B,oBAAC;MAAK,WAAU;gBAAgC;OAEzC;MACH;IAGP,WAAW,qBAAqB,SAAS,WAAW,KACnD,qBAAC;KAAI,WAAU;;MACb,oBAAC,YAAS,WAAU,eAAe;MACnC,oBAAC,YAAS,WAAU,0BAA0B;MAC9C,oBAAC,YAAS,WAAU,wBAAwB;;MACxC;IAGP,SACE,QACE,QAAQ,IAAI,SAAS,eAAe,IAAI,OAAO,MAAM,IAAI,QAC3D,CACA,KAAK,QACJ,oBAAC,oBAA8B,SAAS,OAAjB,IAAI,GAAoB,CAC/C;IAEH,0BACC,qBAAC;KAAI,WAAU;gBACb,oBAAC,WAAQ,WAAU,YAAY,EAC/B,oBAAC,oBAAM,aAAa,YAAY,OAAO,GAAQ;MAC3C;IAGP,SAAS,WAAW,KAAK,WAAW,UACnC,oBAAC;KAAI,WAAU;eAA8E;MAEvF;;IAEJ;GACK"}
@@ -1,5 +1,5 @@
1
1
  import { GenieMessageItem } from "./types.js";
2
- import * as react_jsx_runtime288 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/react/genie/genie-chat-message.d.ts
5
5
  interface GenieChatMessageProps {
@@ -12,7 +12,7 @@ interface GenieChatMessageProps {
12
12
  declare function GenieChatMessage({
13
13
  message,
14
14
  className
15
- }: GenieChatMessageProps): react_jsx_runtime288.JSX.Element;
15
+ }: GenieChatMessageProps): react_jsx_runtime0.JSX.Element;
16
16
  //#endregion
17
17
  export { GenieChatMessage };
18
18
  //# sourceMappingURL=genie-chat-message.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"genie-chat-message.d.ts","names":[],"sources":["../../../src/react/genie/genie-chat-message.tsx"],"sourcesContent":[],"mappings":";;;;UAyBiB,qBAAA;;WAEN;EAFM;EAYD,SAAA,CAAA,EAAA,MAAA;;;AAEd,iBAFc,gBAAA,CAEd;EAAA,OAAA;EAAA;AAAA,CAAA,EACC,qBADD,CAAA,EACsB,oBAAA,CAAA,GAAA,CAAA,OADtB"}
1
+ {"version":3,"file":"genie-chat-message.d.ts","names":[],"sources":["../../../src/react/genie/genie-chat-message.tsx"],"mappings":";;;;UAyBiB,qBAAA;;EAEf,OAAA,EAAS,gBAAA;EAFM;EAIf,SAAA;AAAA;;iBAQc,gBAAA,CAAA;EACd,OAAA;EACA;AAAA,GACC,qBAAA,GAAqB,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"genie-chat-message.js","names":[],"sources":["../../../src/react/genie/genie-chat-message.tsx"],"sourcesContent":["import { marked } from \"marked\";\nimport { useMemo } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Avatar, AvatarFallback } from \"../ui/avatar\";\nimport { Card } from \"../ui/card\";\nimport type { GenieAttachmentResponse, GenieMessageItem } from \"./types\";\n\n/**\n * Using `marked` instead of `react-markdown` because `react-markdown` depends on\n * `micromark-util-symbol` which has broken ESM exports with `rolldown-vite`.\n * Content comes from our own Genie API so `dangerouslySetInnerHTML` is safe.\n */\nmarked.setOptions({ breaks: true, gfm: true });\n\nconst markdownStyles = cn(\n \"text-sm\",\n \"[&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1 [&_li]:my-0\",\n \"[&_pre]:bg-background/50 [&_pre]:p-2 [&_pre]:rounded [&_pre]:text-xs [&_pre]:overflow-x-auto\",\n \"[&_code]:text-xs [&_code]:bg-background/50 [&_code]:px-1 [&_code]:rounded\",\n \"[&_table]:text-xs [&_th]:px-2 [&_th]:py-1 [&_td]:px-2 [&_td]:py-1\",\n \"[&_table]:border-collapse [&_th]:border [&_td]:border\",\n \"[&_th]:border-border [&_td]:border-border\",\n \"[&_a]:underline\",\n);\n\nexport interface GenieChatMessageProps {\n /** The message object to render */\n message: GenieMessageItem;\n /** Additional CSS class */\n className?: string;\n}\n\nfunction isQueryAttachment(att: GenieAttachmentResponse): boolean {\n return !!(att.query?.title || att.query?.query);\n}\n\n/** Renders a single Genie message bubble with optional expandable SQL query attachments. */\nexport function GenieChatMessage({\n message,\n className,\n}: GenieChatMessageProps) {\n const isUser = message.role === \"user\";\n const queryAttachments = message.attachments.filter(isQueryAttachment);\n const html = useMemo(\n () => (message.content ? (marked.parse(message.content) as string) : \"\"),\n [message.content],\n );\n\n return (\n <div\n className={cn(\n \"flex gap-3\",\n isUser ? \"flex-row-reverse\" : \"flex-row\",\n className,\n )}\n >\n <Avatar className=\"h-8 w-8 shrink-0 mt-1\">\n <AvatarFallback\n className={cn(\n \"text-xs font-medium\",\n isUser ? \"bg-primary text-primary-foreground\" : \"bg-muted\",\n )}\n >\n {isUser ? \"You\" : \"AI\"}\n </AvatarFallback>\n </Avatar>\n\n <div\n className={cn(\n \"flex flex-col gap-2 max-w-[80%] min-w-0 overflow-hidden\",\n isUser ? \"items-end\" : \"items-start\",\n )}\n >\n <Card\n className={cn(\n \"px-4 py-3 max-w-full overflow-hidden\",\n isUser ? \"bg-primary text-primary-foreground\" : \"bg-muted\",\n )}\n >\n {html && (\n <div\n className={markdownStyles}\n dangerouslySetInnerHTML={{ __html: html }}\n />\n )}\n\n {message.error && (\n <p className=\"text-sm text-destructive mt-1\">{message.error}</p>\n )}\n </Card>\n\n {queryAttachments.length > 0 && (\n <div className=\"flex flex-col gap-2 w-full min-w-0\">\n {queryAttachments.map((att) => (\n <Card\n key={att.attachmentId ?? \"query\"}\n className=\"px-4 py-3 text-xs overflow-hidden shadow-none\"\n >\n <details>\n <summary className=\"cursor-pointer select-none font-medium\">\n {att.query?.title ?? \"SQL Query\"}\n </summary>\n <div className=\"mt-2 flex flex-col gap-1\">\n {att.query?.description && (\n <span className=\"text-muted-foreground\">\n {att.query.description}\n </span>\n )}\n {att.query?.query && (\n <pre className=\"mt-1 p-2 rounded bg-background text-[11px] whitespace-pre-wrap break-all\">\n {att.query.query}\n </pre>\n )}\n </div>\n </details>\n </Card>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,OAAO,WAAW;CAAE,QAAQ;CAAM,KAAK;CAAM,CAAC;AAE9C,MAAM,iBAAiB,GACrB,WACA,kDACA,gGACA,6EACA,qEACA,yDACA,6CACA,kBACD;AASD,SAAS,kBAAkB,KAAuC;AAChE,QAAO,CAAC,EAAE,IAAI,OAAO,SAAS,IAAI,OAAO;;;AAI3C,SAAgB,iBAAiB,EAC/B,SACA,aACwB;CACxB,MAAM,SAAS,QAAQ,SAAS;CAChC,MAAM,mBAAmB,QAAQ,YAAY,OAAO,kBAAkB;CACtE,MAAM,OAAO,cACJ,QAAQ,UAAW,OAAO,MAAM,QAAQ,QAAQ,GAAc,IACrE,CAAC,QAAQ,QAAQ,CAClB;AAED,QACE,qBAAC,OAAD;EACE,WAAW,GACT,cACA,SAAS,qBAAqB,YAC9B,UACD;YALH,CAOE,oBAAC,QAAD;GAAQ,WAAU;aAChB,oBAAC,gBAAD;IACE,WAAW,GACT,uBACA,SAAS,uCAAuC,WACjD;cAEA,SAAS,QAAQ;IACH;GACV,GAET,qBAAC,OAAD;GACE,WAAW,GACT,2DACA,SAAS,cAAc,cACxB;aAJH,CAME,qBAAC,MAAD;IACE,WAAW,GACT,wCACA,SAAS,uCAAuC,WACjD;cAJH,CAMG,QACC,oBAAC,OAAD;KACE,WAAW;KACX,yBAAyB,EAAE,QAAQ,MAAM;KACzC,GAGH,QAAQ,SACP,oBAAC,KAAD;KAAG,WAAU;eAAiC,QAAQ;KAAU,EAE7D;OAEN,iBAAiB,SAAS,KACzB,oBAAC,OAAD;IAAK,WAAU;cACZ,iBAAiB,KAAK,QACrB,oBAAC,MAAD;KAEE,WAAU;eAEV,qBAAC,WAAD,aACE,oBAAC,WAAD;MAAS,WAAU;gBAChB,IAAI,OAAO,SAAS;MACb,GACV,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,IAAI,OAAO,eACV,oBAAC,QAAD;OAAM,WAAU;iBACb,IAAI,MAAM;OACN,GAER,IAAI,OAAO,SACV,oBAAC,OAAD;OAAK,WAAU;iBACZ,IAAI,MAAM;OACP,EAEJ;QACE;KACL,EApBA,IAAI,gBAAgB,QAoBpB,CACP;IACE,EAEJ;KACF"}
1
+ {"version":3,"file":"genie-chat-message.js","names":[],"sources":["../../../src/react/genie/genie-chat-message.tsx"],"sourcesContent":["import { marked } from \"marked\";\nimport { useMemo } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Avatar, AvatarFallback } from \"../ui/avatar\";\nimport { Card } from \"../ui/card\";\nimport type { GenieAttachmentResponse, GenieMessageItem } from \"./types\";\n\n/**\n * Using `marked` instead of `react-markdown` because `react-markdown` depends on\n * `micromark-util-symbol` which has broken ESM exports with `rolldown-vite`.\n * Content comes from our own Genie API so `dangerouslySetInnerHTML` is safe.\n */\nmarked.setOptions({ breaks: true, gfm: true });\n\nconst markdownStyles = cn(\n \"text-sm\",\n \"[&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1 [&_li]:my-0\",\n \"[&_pre]:bg-background/50 [&_pre]:p-2 [&_pre]:rounded [&_pre]:text-xs [&_pre]:overflow-x-auto\",\n \"[&_code]:text-xs [&_code]:bg-background/50 [&_code]:px-1 [&_code]:rounded\",\n \"[&_table]:text-xs [&_th]:px-2 [&_th]:py-1 [&_td]:px-2 [&_td]:py-1\",\n \"[&_table]:border-collapse [&_th]:border [&_td]:border\",\n \"[&_th]:border-border [&_td]:border-border\",\n \"[&_a]:underline\",\n);\n\nexport interface GenieChatMessageProps {\n /** The message object to render */\n message: GenieMessageItem;\n /** Additional CSS class */\n className?: string;\n}\n\nfunction isQueryAttachment(att: GenieAttachmentResponse): boolean {\n return !!(att.query?.title || att.query?.query);\n}\n\n/** Renders a single Genie message bubble with optional expandable SQL query attachments. */\nexport function GenieChatMessage({\n message,\n className,\n}: GenieChatMessageProps) {\n const isUser = message.role === \"user\";\n const queryAttachments = message.attachments.filter(isQueryAttachment);\n const html = useMemo(\n () => (message.content ? (marked.parse(message.content) as string) : \"\"),\n [message.content],\n );\n\n return (\n <div\n className={cn(\n \"flex gap-3\",\n isUser ? \"flex-row-reverse\" : \"flex-row\",\n className,\n )}\n >\n <Avatar className=\"h-8 w-8 shrink-0 mt-1\">\n <AvatarFallback\n className={cn(\n \"text-xs font-medium\",\n isUser ? \"bg-primary text-primary-foreground\" : \"bg-muted\",\n )}\n >\n {isUser ? \"You\" : \"AI\"}\n </AvatarFallback>\n </Avatar>\n\n <div\n className={cn(\n \"flex flex-col gap-2 max-w-[80%] min-w-0 overflow-hidden\",\n isUser ? \"items-end\" : \"items-start\",\n )}\n >\n <Card\n className={cn(\n \"px-4 py-3 max-w-full overflow-hidden\",\n isUser ? \"bg-primary text-primary-foreground\" : \"bg-muted\",\n )}\n >\n {html && (\n <div\n className={markdownStyles}\n dangerouslySetInnerHTML={{ __html: html }}\n />\n )}\n\n {message.error && (\n <p className=\"text-sm text-destructive mt-1\">{message.error}</p>\n )}\n </Card>\n\n {queryAttachments.length > 0 && (\n <div className=\"flex flex-col gap-2 w-full min-w-0\">\n {queryAttachments.map((att) => (\n <Card\n key={att.attachmentId ?? \"query\"}\n className=\"px-4 py-3 text-xs overflow-hidden shadow-none\"\n >\n <details>\n <summary className=\"cursor-pointer select-none font-medium\">\n {att.query?.title ?? \"SQL Query\"}\n </summary>\n <div className=\"mt-2 flex flex-col gap-1\">\n {att.query?.description && (\n <span className=\"text-muted-foreground\">\n {att.query.description}\n </span>\n )}\n {att.query?.query && (\n <pre className=\"mt-1 p-2 rounded bg-background text-[11px] whitespace-pre-wrap break-all\">\n {att.query.query}\n </pre>\n )}\n </div>\n </details>\n </Card>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,OAAO,WAAW;CAAE,QAAQ;CAAM,KAAK;CAAM,CAAC;AAE9C,MAAM,iBAAiB,GACrB,WACA,kDACA,gGACA,6EACA,qEACA,yDACA,6CACA,kBACD;AASD,SAAS,kBAAkB,KAAuC;AAChE,QAAO,CAAC,EAAE,IAAI,OAAO,SAAS,IAAI,OAAO;;;AAI3C,SAAgB,iBAAiB,EAC/B,SACA,aACwB;CACxB,MAAM,SAAS,QAAQ,SAAS;CAChC,MAAM,mBAAmB,QAAQ,YAAY,OAAO,kBAAkB;CACtE,MAAM,OAAO,cACJ,QAAQ,UAAW,OAAO,MAAM,QAAQ,QAAQ,GAAc,IACrE,CAAC,QAAQ,QAAQ,CAClB;AAED,QACE,qBAAC;EACC,WAAW,GACT,cACA,SAAS,qBAAqB,YAC9B,UACD;aAED,oBAAC;GAAO,WAAU;aAChB,oBAAC;IACC,WAAW,GACT,uBACA,SAAS,uCAAuC,WACjD;cAEA,SAAS,QAAQ;KACH;IACV,EAET,qBAAC;GACC,WAAW,GACT,2DACA,SAAS,cAAc,cACxB;cAED,qBAAC;IACC,WAAW,GACT,wCACA,SAAS,uCAAuC,WACjD;eAEA,QACC,oBAAC;KACC,WAAW;KACX,yBAAyB,EAAE,QAAQ,MAAM;MACzC,EAGH,QAAQ,SACP,oBAAC;KAAE,WAAU;eAAiC,QAAQ;MAAU;KAE7D,EAEN,iBAAiB,SAAS,KACzB,oBAAC;IAAI,WAAU;cACZ,iBAAiB,KAAK,QACrB,oBAAC;KAEC,WAAU;eAEV,qBAAC,wBACC,oBAAC;MAAQ,WAAU;gBAChB,IAAI,OAAO,SAAS;OACb,EACV,qBAAC;MAAI,WAAU;iBACZ,IAAI,OAAO,eACV,oBAAC;OAAK,WAAU;iBACb,IAAI,MAAM;QACN,EAER,IAAI,OAAO,SACV,oBAAC;OAAI,WAAU;iBACZ,IAAI,MAAM;QACP;OAEJ,IACE;OAnBL,IAAI,gBAAgB,QAoBpB,CACP;KACE;IAEJ;GACF"}
@@ -1,5 +1,5 @@
1
1
  import { GenieChatProps } from "./types.js";
2
- import * as react_jsx_runtime286 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/react/genie/genie-chat.d.ts
5
5
  /** Full-featured chat interface for a single Databricks AI/BI Genie space. Handles message streaming, conversation history, and auto-reconnection via SSE. */
@@ -8,7 +8,7 @@ declare function GenieChat({
8
8
  basePath,
9
9
  placeholder,
10
10
  className
11
- }: GenieChatProps): react_jsx_runtime286.JSX.Element;
11
+ }: GenieChatProps): react_jsx_runtime0.JSX.Element;
12
12
  //#endregion
13
13
  export { GenieChat };
14
14
  //# sourceMappingURL=genie-chat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"genie-chat.d.ts","names":[],"sources":["../../../src/react/genie/genie-chat.tsx"],"sourcesContent":[],"mappings":";;;;;iBAQgB,SAAA;;;;;GAKb,iBAAc,oBAAA,CAAA,GAAA,CAAA"}
1
+ {"version":3,"file":"genie-chat.d.ts","names":[],"sources":["../../../src/react/genie/genie-chat.tsx"],"mappings":";;;;;iBAQgB,SAAA,CAAA;EACd,KAAA;EACA,QAAA;EACA,WAAA;EACA;AAAA,GACC,cAAA,GAAc,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -8,7 +8,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
8
8
  //#region src/react/genie/genie-chat.tsx
9
9
  /** Full-featured chat interface for a single Databricks AI/BI Genie space. Handles message streaming, conversation history, and auto-reconnection via SSE. */
10
10
  function GenieChat({ alias, basePath, placeholder, className }) {
11
- const { messages, status, error, sendMessage, reset } = useGenieChat({
11
+ const { messages, status, error, sendMessage, reset, hasPreviousPage, fetchPreviousPage } = useGenieChat({
12
12
  alias,
13
13
  basePath
14
14
  });
@@ -27,7 +27,9 @@ function GenieChat({ alias, basePath, placeholder, className }) {
27
27
  }),
28
28
  /* @__PURE__ */ jsx(GenieChatMessageList, {
29
29
  messages,
30
- status
30
+ status,
31
+ hasPreviousPage,
32
+ onFetchPreviousPage: fetchPreviousPage
31
33
  }),
32
34
  error && /* @__PURE__ */ jsx("div", {
33
35
  className: "shrink-0 px-4 py-2 text-sm text-destructive bg-destructive/10 border-t",
@@ -1 +1 @@
1
- {"version":3,"file":"genie-chat.js","names":[],"sources":["../../../src/react/genie/genie-chat.tsx"],"sourcesContent":["import { cn } from \"../lib/utils\";\nimport { Button } from \"../ui/button\";\nimport { GenieChatInput } from \"./genie-chat-input\";\nimport { GenieChatMessageList } from \"./genie-chat-message-list\";\nimport type { GenieChatProps } from \"./types\";\nimport { useGenieChat } from \"./use-genie-chat\";\n\n/** Full-featured chat interface for a single Databricks AI/BI Genie space. Handles message streaming, conversation history, and auto-reconnection via SSE. */\nexport function GenieChat({\n alias,\n basePath,\n placeholder,\n className,\n}: GenieChatProps) {\n const { messages, status, error, sendMessage, reset } = useGenieChat({\n alias,\n basePath,\n });\n\n return (\n <div className={cn(\"flex flex-col h-full overflow-hidden\", className)}>\n {messages.length > 0 && (\n <div className=\"shrink-0 flex justify-end px-4 pt-3 pb-1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={reset}\n className=\"text-xs text-muted-foreground\"\n >\n New conversation\n </Button>\n </div>\n )}\n\n <GenieChatMessageList messages={messages} status={status} />\n\n {error && (\n <div className=\"shrink-0 px-4 py-2 text-sm text-destructive bg-destructive/10 border-t\">\n {error}\n </div>\n )}\n\n <GenieChatInput\n onSend={sendMessage}\n disabled={status === \"streaming\" || status === \"loading-history\"}\n placeholder={placeholder}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,UAAU,EACxB,OACA,UACA,aACA,aACiB;CACjB,MAAM,EAAE,UAAU,QAAQ,OAAO,aAAa,UAAU,aAAa;EACnE;EACA;EACD,CAAC;AAEF,QACE,qBAAC,OAAD;EAAK,WAAW,GAAG,wCAAwC,UAAU;YAArE;GACG,SAAS,SAAS,KACjB,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,QAAD;KACE,SAAQ;KACR,MAAK;KACL,SAAS;KACT,WAAU;eACX;KAEQ;IACL;GAGR,oBAAC,sBAAD;IAAgC;IAAkB;IAAU;GAE3D,SACC,oBAAC,OAAD;IAAK,WAAU;cACZ;IACG;GAGR,oBAAC,gBAAD;IACE,QAAQ;IACR,UAAU,WAAW,eAAe,WAAW;IAClC;IACb;GACE"}
1
+ {"version":3,"file":"genie-chat.js","names":[],"sources":["../../../src/react/genie/genie-chat.tsx"],"sourcesContent":["import { cn } from \"../lib/utils\";\nimport { Button } from \"../ui/button\";\nimport { GenieChatInput } from \"./genie-chat-input\";\nimport { GenieChatMessageList } from \"./genie-chat-message-list\";\nimport type { GenieChatProps } from \"./types\";\nimport { useGenieChat } from \"./use-genie-chat\";\n\n/** Full-featured chat interface for a single Databricks AI/BI Genie space. Handles message streaming, conversation history, and auto-reconnection via SSE. */\nexport function GenieChat({\n alias,\n basePath,\n placeholder,\n className,\n}: GenieChatProps) {\n const {\n messages,\n status,\n error,\n sendMessage,\n reset,\n hasPreviousPage,\n fetchPreviousPage,\n } = useGenieChat({\n alias,\n basePath,\n });\n\n return (\n <div className={cn(\"flex flex-col h-full overflow-hidden\", className)}>\n {messages.length > 0 && (\n <div className=\"shrink-0 flex justify-end px-4 pt-3 pb-1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={reset}\n className=\"text-xs text-muted-foreground\"\n >\n New conversation\n </Button>\n </div>\n )}\n\n <GenieChatMessageList\n messages={messages}\n status={status}\n hasPreviousPage={hasPreviousPage}\n onFetchPreviousPage={fetchPreviousPage}\n />\n\n {error && (\n <div className=\"shrink-0 px-4 py-2 text-sm text-destructive bg-destructive/10 border-t\">\n {error}\n </div>\n )}\n\n <GenieChatInput\n onSend={sendMessage}\n disabled={status === \"streaming\" || status === \"loading-history\"}\n placeholder={placeholder}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,UAAU,EACxB,OACA,UACA,aACA,aACiB;CACjB,MAAM,EACJ,UACA,QACA,OACA,aACA,OACA,iBACA,sBACE,aAAa;EACf;EACA;EACD,CAAC;AAEF,QACE,qBAAC;EAAI,WAAW,GAAG,wCAAwC,UAAU;;GAClE,SAAS,SAAS,KACjB,oBAAC;IAAI,WAAU;cACb,oBAAC;KACC,SAAQ;KACR,MAAK;KACL,SAAS;KACT,WAAU;eACX;MAEQ;KACL;GAGR,oBAAC;IACW;IACF;IACS;IACjB,qBAAqB;KACrB;GAED,SACC,oBAAC;IAAI,WAAU;cACZ;KACG;GAGR,oBAAC;IACC,QAAQ;IACR,UAAU,WAAW,eAAe,WAAW;IAClC;KACb;;GACE"}
@@ -0,0 +1,7 @@
1
+ import { GenieAttachmentResponse, GenieMessageResponse, GenieStreamEvent } from "../../shared/src/genie.js";
2
+ import { GenieChatProps, GenieChatStatus, GenieMessageItem, UseGenieChatOptions, UseGenieChatReturn } from "./types.js";
3
+ import { GenieChat } from "./genie-chat.js";
4
+ import { GenieChatInput } from "./genie-chat-input.js";
5
+ import { GenieChatMessage } from "./genie-chat-message.js";
6
+ import { GenieChatMessageList } from "./genie-chat-message-list.js";
7
+ import { useGenieChat } from "./use-genie-chat.js";
@@ -1,7 +1,8 @@
1
1
  import { GenieAttachmentResponse, GenieMessageResponse, GenieStreamEvent } from "../../shared/src/genie.js";
2
+ import "../../shared/src/index.js";
2
3
 
3
4
  //#region src/react/genie/types.d.ts
4
- type GenieChatStatus = "idle" | "loading-history" | "streaming" | "error";
5
+ type GenieChatStatus = "idle" | "loading-history" | "loading-older" | "streaming" | "error";
5
6
  interface GenieMessageItem {
6
7
  id: string;
7
8
  role: "user" | "assistant";
@@ -28,6 +29,12 @@ interface UseGenieChatReturn {
28
29
  error: string | null;
29
30
  sendMessage: (content: string) => void;
30
31
  reset: () => void;
32
+ /** Whether a previous page of older messages exists */
33
+ hasPreviousPage: boolean;
34
+ /** Whether a previous page is currently being fetched */
35
+ isFetchingPreviousPage: boolean;
36
+ /** Fetch the previous page of older messages */
37
+ fetchPreviousPage: () => void;
31
38
  }
32
39
  interface GenieChatProps {
33
40
  /** Genie space alias (must match a key registered with the genie plugin on the server) */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/react/genie/types.ts"],"sourcesContent":[],"mappings":";;;KAQY,eAAA;AAAA,UAMK,gBAAA,CANU;EAMV,EAAA,EAAA,MAAA;EAAgB,IAAA,EAAA,MAAA,GAAA,WAAA;SAKlB,EAAA,MAAA;QACC,EAAA,MAAA;EAAG,WAAA,EADJ,uBACI,EAAA;EAIF,YAAA,EAJD,GAIC,CAAA,MAAmB,EAAA,OAAA,CAAA;EAWnB,KAAA,CAAA,EAAA,MAAA;;AACL,UAZK,mBAAA,CAYL;;EACa,KAAA,EAAA,MAAA;EAOR;;;;;;;UATA,kBAAA;YACL;UACF;;;;;;UAOO,cAAA"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/react/genie/types.ts"],"mappings":";;;;KAQY,eAAA;AAAA,UAOK,gBAAA;EACf,EAAA;EACA,IAAA;EACA,OAAA;EACA,MAAA;EACA,WAAA,EAAa,uBAAA;EACb,YAAA,EAAc,GAAA;EACd,KAAA;AAAA;AAAA,UAGe,mBAAA;EATf;EAWA,KAAA;EATA;EAWA,QAAA;EATA;EAWA,YAAA;EAVA;EAYA,YAAA;AAAA;AAAA,UAGe,kBAAA;EACf,QAAA,EAAU,gBAAA;EACV,MAAA,EAAQ,eAAA;EACR,cAAA;EACA,KAAA;EACA,WAAA,GAAc,OAAA;EACd,KAAA;EAbA;EAeA,eAAA;EAXA;EAaA,sBAAA;EAbY;EAeZ,iBAAA;AAAA;AAAA,UAGe,cAAA;EAbQ;EAevB,KAAA;EAhBU;EAkBV,QAAA;EAjBQ;EAmBR,WAAA;EAjBA;EAmBA,SAAA;AAAA"}
@@ -1,7 +1,6 @@
1
1
  import { UseGenieChatOptions, UseGenieChatReturn } from "./types.js";
2
2
 
3
3
  //#region src/react/genie/use-genie-chat.d.ts
4
-
5
4
  /**
6
5
  * Manages the full Genie chat lifecycle:
7
6
  * SSE streaming, conversation persistence via URL, and history replay.
@@ -1 +1 @@
1
- {"version":3,"file":"use-genie-chat.d.ts","names":[],"sources":["../../../src/react/genie/use-genie-chat.ts"],"sourcesContent":[],"mappings":";;;;;;AAoFA;;;;;;;iBAAgB,YAAA,UAAsB,sBAAsB"}
1
+ {"version":3,"file":"use-genie-chat.d.ts","names":[],"sources":["../../../src/react/genie/use-genie-chat.ts"],"mappings":";;;;;AAqJA;;;;;;;iBAAgB,YAAA,CAAa,OAAA,EAAS,mBAAA,GAAsB,kBAAA"}