@btst/stack 1.3.1 → 1.4.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 (229) hide show
  1. package/README.md +85 -37
  2. package/dist/node_modules/.pnpm/{@radix-ui_react-alert-dialog@1.1.15_@types_react-dom@19.2.2_@types_react@19.2.2__@types_ec789942cd38340bd7362c09e7d34384 → @radix-ui_react-alert-dialog@1.1.15_@types_react-dom@19.2.3_@types_react@19.2.6__@types_4f58c757aa677e233cf96d60fda2f1da}/node_modules/@radix-ui/react-alert-dialog/dist/index.cjs +2 -2
  3. package/dist/node_modules/.pnpm/{@radix-ui_react-alert-dialog@1.1.15_@types_react-dom@19.2.2_@types_react@19.2.2__@types_ec789942cd38340bd7362c09e7d34384 → @radix-ui_react-alert-dialog@1.1.15_@types_react-dom@19.2.3_@types_react@19.2.6__@types_4f58c757aa677e233cf96d60fda2f1da}/node_modules/@radix-ui/react-alert-dialog/dist/index.mjs +2 -2
  4. package/dist/node_modules/.pnpm/{@radix-ui_react-arrow@1.1.7_@types_react-dom@19.2.2_@types_react@19.2.2__@types_react@1_9e04309f365863673e44407648bb0cb6 → @radix-ui_react-arrow@1.1.7_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@1_35df44f6d87656c1401686c91d770dbb}/node_modules/@radix-ui/react-arrow/dist/index.cjs +1 -1
  5. package/dist/node_modules/.pnpm/{@radix-ui_react-arrow@1.1.7_@types_react-dom@19.2.2_@types_react@19.2.2__@types_react@1_9e04309f365863673e44407648bb0cb6 → @radix-ui_react-arrow@1.1.7_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@1_35df44f6d87656c1401686c91d770dbb}/node_modules/@radix-ui/react-arrow/dist/index.mjs +1 -1
  6. package/dist/node_modules/.pnpm/{@radix-ui_react-collection@1.1.7_@types_react-dom@19.2.2_@types_react@19.2.2__@types_re_6d7c277722b3619c9ee7e64e9a822c45 → @radix-ui_react-collection@1.1.7_@types_react-dom@19.2.3_@types_react@19.2.6__@types_re_59aa5e150e70a3e7bfb1567148b021b5}/node_modules/@radix-ui/react-collection/dist/index.cjs +2 -2
  7. package/dist/node_modules/.pnpm/{@radix-ui_react-collection@1.1.7_@types_react-dom@19.2.2_@types_react@19.2.2__@types_re_6d7c277722b3619c9ee7e64e9a822c45 → @radix-ui_react-collection@1.1.7_@types_react-dom@19.2.3_@types_react@19.2.6__@types_re_59aa5e150e70a3e7bfb1567148b021b5}/node_modules/@radix-ui/react-collection/dist/index.mjs +2 -2
  8. package/dist/node_modules/.pnpm/{@radix-ui_react-dismissable-layer@1.1.11_@types_react-dom@19.2.2_@types_react@19.2.2__@_ca5522e5d45d4722cb9eb5ce53defa61 → @radix-ui_react-dismissable-layer@1.1.11_@types_react-dom@19.2.3_@types_react@19.2.6__@_9ee1db7daf927866cf505b31d40047ad}/node_modules/@radix-ui/react-dismissable-layer/dist/index.cjs +4 -4
  9. package/dist/node_modules/.pnpm/{@radix-ui_react-dismissable-layer@1.1.11_@types_react-dom@19.2.2_@types_react@19.2.2__@_ca5522e5d45d4722cb9eb5ce53defa61 → @radix-ui_react-dismissable-layer@1.1.11_@types_react-dom@19.2.3_@types_react@19.2.6__@_9ee1db7daf927866cf505b31d40047ad}/node_modules/@radix-ui/react-dismissable-layer/dist/index.mjs +4 -4
  10. package/dist/node_modules/.pnpm/@radix-ui_react-dropdown-menu@2.1.16_@types_react-dom@19.2.3_@types_react@19.2.6__@type_a50051c7210b6fbd5be09388bda08578/node_modules/@radix-ui/react-dropdown-menu/dist/index.cjs +282 -0
  11. package/dist/node_modules/.pnpm/@radix-ui_react-dropdown-menu@2.1.16_@types_react-dom@19.2.3_@types_react@19.2.6__@type_a50051c7210b6fbd5be09388bda08578/node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs +247 -0
  12. package/dist/node_modules/.pnpm/{@radix-ui_react-focus-scope@1.1.7_@types_react-dom@19.2.2_@types_react@19.2.2__@types_r_93de389b3f622f9f764acc8e59ec80c0 → @radix-ui_react-focus-scope@1.1.7_@types_react-dom@19.2.3_@types_react@19.2.6__@types_r_0a31b7f987af9482d13505312e1a1be9}/node_modules/@radix-ui/react-focus-scope/dist/index.cjs +3 -3
  13. package/dist/node_modules/.pnpm/{@radix-ui_react-focus-scope@1.1.7_@types_react-dom@19.2.2_@types_react@19.2.2__@types_r_93de389b3f622f9f764acc8e59ec80c0 → @radix-ui_react-focus-scope@1.1.7_@types_react-dom@19.2.3_@types_react@19.2.6__@types_r_0a31b7f987af9482d13505312e1a1be9}/node_modules/@radix-ui/react-focus-scope/dist/index.mjs +3 -3
  14. package/dist/node_modules/.pnpm/{@radix-ui_react-id@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-id@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-id/dist/index.cjs +1 -1
  15. package/dist/node_modules/.pnpm/{@radix-ui_react-id@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-id@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-id/dist/index.mjs +1 -1
  16. package/dist/node_modules/.pnpm/@radix-ui_react-menu@2.1.16_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@1_82ed1fcd848b6984d9291a784d477cf4/node_modules/@radix-ui/react-menu/dist/index.cjs +845 -0
  17. package/dist/node_modules/.pnpm/@radix-ui_react-menu@2.1.16_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@1_82ed1fcd848b6984d9291a784d477cf4/node_modules/@radix-ui/react-menu/dist/index.mjs +799 -0
  18. package/dist/node_modules/.pnpm/{@radix-ui_react-popper@1.2.8_@types_react-dom@19.2.2_@types_react@19.2.2__@types_react@_d6285b8269ea5d6b59b300f5be279a0c → @radix-ui_react-popper@1.2.8_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@_7ac2caae1e39f9ba93d5015614525161}/node_modules/@radix-ui/react-popper/dist/index.cjs +7 -7
  19. package/dist/node_modules/.pnpm/{@radix-ui_react-popper@1.2.8_@types_react-dom@19.2.2_@types_react@19.2.2__@types_react@_d6285b8269ea5d6b59b300f5be279a0c → @radix-ui_react-popper@1.2.8_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@_7ac2caae1e39f9ba93d5015614525161}/node_modules/@radix-ui/react-popper/dist/index.mjs +7 -7
  20. package/dist/node_modules/.pnpm/{@radix-ui_react-portal@1.1.9_@types_react-dom@19.2.2_@types_react@19.2.2__@types_react@_478b3d5dd4afab1a3dcce7ed1748cb95 → @radix-ui_react-portal@1.1.9_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@_1bb4e0f97f86496802d28a2e74e2a8b9}/node_modules/@radix-ui/react-portal/dist/index.cjs +2 -2
  21. package/dist/node_modules/.pnpm/{@radix-ui_react-portal@1.1.9_@types_react-dom@19.2.2_@types_react@19.2.2__@types_react@_478b3d5dd4afab1a3dcce7ed1748cb95 → @radix-ui_react-portal@1.1.9_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@_1bb4e0f97f86496802d28a2e74e2a8b9}/node_modules/@radix-ui/react-portal/dist/index.mjs +2 -2
  22. package/dist/node_modules/.pnpm/@radix-ui_react-presence@1.1.5_@types_react-dom@19.2.3_@types_react@19.2.6__@types_reac_90f8e5c12233caef3399d5fd66452a13/node_modules/@radix-ui/react-presence/dist/index.cjs +147 -0
  23. package/dist/node_modules/.pnpm/@radix-ui_react-presence@1.1.5_@types_react-dom@19.2.3_@types_react@19.2.6__@types_reac_90f8e5c12233caef3399d5fd66452a13/node_modules/@radix-ui/react-presence/dist/index.mjs +131 -0
  24. package/dist/node_modules/.pnpm/@radix-ui_react-roving-focus@1.1.11_@types_react-dom@19.2.3_@types_react@19.2.6__@types_fe1151d1f393bbc1072b24a86dff3a23/node_modules/@radix-ui/react-roving-focus/dist/index.cjs +244 -0
  25. package/dist/node_modules/.pnpm/@radix-ui_react-roving-focus@1.1.11_@types_react-dom@19.2.3_@types_react@19.2.6__@types_fe1151d1f393bbc1072b24a86dff3a23/node_modules/@radix-ui/react-roving-focus/dist/index.mjs +224 -0
  26. package/dist/node_modules/.pnpm/@radix-ui_react-scroll-area@1.2.10_@types_react-dom@19.2.3_@types_react@19.2.6__@types__e3f7735e9b444a10b3bbfd9fe97d44d0/node_modules/@radix-ui/react-scroll-area/dist/index.cjs +742 -0
  27. package/dist/node_modules/.pnpm/@radix-ui_react-scroll-area@1.2.10_@types_react-dom@19.2.3_@types_react@19.2.6__@types__e3f7735e9b444a10b3bbfd9fe97d44d0/node_modules/@radix-ui/react-scroll-area/dist/index.mjs +719 -0
  28. package/dist/node_modules/.pnpm/{@radix-ui_react-select@2.2.6_@types_react-dom@19.2.2_@types_react@19.2.2__@types_react@_802c3d414c85063bee785fcc98a39c07 → @radix-ui_react-select@2.2.6_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@_38dc681bb1f2bcfeb5249d8ca2bc01f5}/node_modules/@radix-ui/react-select/dist/index.cjs +17 -17
  29. package/dist/node_modules/.pnpm/{@radix-ui_react-select@2.2.6_@types_react-dom@19.2.2_@types_react@19.2.2__@types_react@_802c3d414c85063bee785fcc98a39c07 → @radix-ui_react-select@2.2.6_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@_38dc681bb1f2bcfeb5249d8ca2bc01f5}/node_modules/@radix-ui/react-select/dist/index.mjs +17 -17
  30. package/dist/node_modules/.pnpm/{@radix-ui_react-use-controllable-state@1.2.2_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-use-controllable-state@1.2.2_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-use-controllable-state/dist/index.cjs +1 -1
  31. package/dist/node_modules/.pnpm/{@radix-ui_react-use-controllable-state@1.2.2_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-use-controllable-state@1.2.2_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs +1 -1
  32. package/dist/node_modules/.pnpm/{@radix-ui_react-use-escape-keydown@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-use-escape-keydown@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-use-escape-keydown/dist/index.cjs +1 -1
  33. package/dist/node_modules/.pnpm/{@radix-ui_react-use-escape-keydown@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-use-escape-keydown@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-use-escape-keydown/dist/index.mjs +1 -1
  34. package/dist/node_modules/.pnpm/{@radix-ui_react-use-size@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-use-size@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-use-size/dist/index.cjs +1 -1
  35. package/dist/node_modules/.pnpm/{@radix-ui_react-use-size@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-use-size@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-use-size/dist/index.mjs +1 -1
  36. package/dist/node_modules/.pnpm/{@radix-ui_react-visually-hidden@1.2.3_@types_react-dom@19.2.2_@types_react@19.2.2__@typ_a84e98a44624c31e835a98d4b8b0c30d → @radix-ui_react-visually-hidden@1.2.3_@types_react-dom@19.2.3_@types_react@19.2.6__@typ_f453379bbd5a4932ec515167f81be42a}/node_modules/@radix-ui/react-visually-hidden/dist/index.cjs +1 -1
  37. package/dist/node_modules/.pnpm/{@radix-ui_react-visually-hidden@1.2.3_@types_react-dom@19.2.2_@types_react@19.2.2__@typ_a84e98a44624c31e835a98d4b8b0c30d → @radix-ui_react-visually-hidden@1.2.3_@types_react-dom@19.2.3_@types_react@19.2.6__@typ_f453379bbd5a4932ec515167f81be42a}/node_modules/@radix-ui/react-visually-hidden/dist/index.mjs +1 -1
  38. package/dist/node_modules/.pnpm/{react-remove-scroll-bar@2.3.8_@types_react@19.2.2_react@19.2.0 → react-remove-scroll-bar@2.3.8_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll-bar/dist/es2015/component.cjs +1 -1
  39. package/dist/node_modules/.pnpm/{react-remove-scroll-bar@2.3.8_@types_react@19.2.2_react@19.2.0 → react-remove-scroll-bar@2.3.8_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll-bar/dist/es2015/component.mjs +1 -1
  40. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/SideEffect.cjs +2 -2
  41. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/SideEffect.mjs +2 -2
  42. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/UI.cjs +2 -2
  43. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/UI.mjs +2 -2
  44. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/medium.cjs +1 -1
  45. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/medium.mjs +1 -1
  46. package/dist/node_modules/.pnpm/react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0/node_modules/react-remove-scroll/dist/es2015/sidecar.cjs +9 -0
  47. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/sidecar.mjs +1 -1
  48. package/dist/packages/better-stack/src/plugins/ai-chat/api/plugin.cjs +610 -0
  49. package/dist/packages/better-stack/src/plugins/ai-chat/api/plugin.mjs +608 -0
  50. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/chat-input.cjs +221 -0
  51. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/chat-input.mjs +219 -0
  52. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/chat-interface.cjs +315 -0
  53. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/chat-interface.mjs +313 -0
  54. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/chat-layout.cjs +122 -0
  55. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/chat-layout.mjs +120 -0
  56. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/chat-message.cjs +397 -0
  57. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/chat-message.mjs +391 -0
  58. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/chat-sidebar.cjs +227 -0
  59. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/chat-sidebar.mjs +225 -0
  60. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/loading/chat-page-skeleton.cjs +31 -0
  61. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/loading/chat-page-skeleton.mjs +29 -0
  62. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/loading/index.cjs +11 -0
  63. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/loading/index.mjs +8 -0
  64. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/pages/404-page.cjs +18 -0
  65. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/pages/404-page.mjs +16 -0
  66. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/pages/chat-page.cjs +39 -0
  67. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/pages/chat-page.internal.cjs +22 -0
  68. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/pages/chat-page.internal.mjs +20 -0
  69. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/pages/chat-page.mjs +37 -0
  70. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/shared/default-error.cjs +18 -0
  71. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/shared/default-error.mjs +16 -0
  72. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/shared/error-placeholder.cjs +26 -0
  73. package/dist/packages/better-stack/src/plugins/ai-chat/client/components/shared/error-placeholder.mjs +24 -0
  74. package/dist/packages/better-stack/src/plugins/ai-chat/client/hooks/chat-hooks.cjs +199 -0
  75. package/dist/packages/better-stack/src/plugins/ai-chat/client/hooks/chat-hooks.mjs +191 -0
  76. package/dist/packages/better-stack/src/plugins/ai-chat/client/localization/index.cjs +63 -0
  77. package/dist/packages/better-stack/src/plugins/ai-chat/client/localization/index.mjs +61 -0
  78. package/dist/packages/better-stack/src/plugins/ai-chat/client/overrides.cjs +14 -0
  79. package/dist/packages/better-stack/src/plugins/ai-chat/client/overrides.mjs +11 -0
  80. package/dist/packages/better-stack/src/plugins/ai-chat/client/plugin.cjs +241 -0
  81. package/dist/packages/better-stack/src/plugins/ai-chat/client/plugin.mjs +239 -0
  82. package/dist/packages/better-stack/src/plugins/ai-chat/db.cjs +65 -0
  83. package/dist/packages/better-stack/src/plugins/ai-chat/db.mjs +63 -0
  84. package/dist/packages/better-stack/src/plugins/ai-chat/schemas.cjs +42 -0
  85. package/dist/packages/better-stack/src/plugins/ai-chat/schemas.mjs +38 -0
  86. package/dist/packages/better-stack/src/plugins/blog/client/components/shared/markdown-content.cjs +12 -309
  87. package/dist/packages/better-stack/src/plugins/blog/client/components/shared/markdown-content.mjs +13 -303
  88. package/dist/packages/better-stack/src/plugins/blog/client/components/shared/page-wrapper.cjs +2 -2
  89. package/dist/packages/better-stack/src/plugins/blog/client/components/shared/page-wrapper.mjs +2 -2
  90. package/dist/packages/ui/src/components/alert-dialog.cjs +1 -1
  91. package/dist/packages/ui/src/components/alert-dialog.mjs +1 -1
  92. package/dist/packages/{better-stack/src/plugins/blog/client/components/shared/better-blog-attribution.cjs → ui/src/components/better-stack-attribution.cjs} +3 -3
  93. package/dist/packages/{better-stack/src/plugins/blog/client/components/shared/better-blog-attribution.mjs → ui/src/components/better-stack-attribution.mjs} +3 -3
  94. package/dist/packages/ui/src/components/dialog.cjs +14 -0
  95. package/dist/packages/ui/src/components/dialog.mjs +14 -1
  96. package/dist/packages/ui/src/components/dropdown-menu.cjs +67 -0
  97. package/dist/packages/ui/src/components/dropdown-menu.mjs +62 -0
  98. package/dist/packages/ui/src/components/markdown-content.cjs +306 -0
  99. package/dist/packages/ui/src/components/markdown-content.mjs +297 -0
  100. package/dist/packages/ui/src/components/scroll-area.cjs +63 -0
  101. package/dist/packages/ui/src/components/scroll-area.mjs +60 -0
  102. package/dist/packages/ui/src/components/select.cjs +1 -1
  103. package/dist/packages/ui/src/components/select.mjs +1 -1
  104. package/dist/packages/ui/src/components/sheet.cjs +87 -0
  105. package/dist/packages/ui/src/components/sheet.mjs +69 -0
  106. package/dist/plugins/ai-chat/api/index.cjs +9 -0
  107. package/dist/plugins/ai-chat/api/index.d.cts +9 -0
  108. package/dist/plugins/ai-chat/api/index.d.mts +9 -0
  109. package/dist/plugins/ai-chat/api/index.d.ts +9 -0
  110. package/dist/plugins/ai-chat/api/index.mjs +2 -0
  111. package/dist/plugins/ai-chat/client/components/index.cjs +27 -0
  112. package/dist/plugins/ai-chat/client/components/index.d.cts +30 -0
  113. package/dist/plugins/ai-chat/client/components/index.d.mts +30 -0
  114. package/dist/plugins/ai-chat/client/components/index.d.ts +30 -0
  115. package/dist/plugins/ai-chat/client/components/index.mjs +11 -0
  116. package/dist/plugins/ai-chat/client/hooks/index.cjs +13 -0
  117. package/dist/plugins/ai-chat/client/hooks/index.d.cts +98 -0
  118. package/dist/plugins/ai-chat/client/hooks/index.d.mts +98 -0
  119. package/dist/plugins/ai-chat/client/hooks/index.d.ts +98 -0
  120. package/dist/plugins/ai-chat/client/hooks/index.mjs +1 -0
  121. package/dist/plugins/ai-chat/client/index.cjs +19 -0
  122. package/dist/plugins/ai-chat/client/index.d.cts +327 -0
  123. package/dist/plugins/ai-chat/client/index.d.mts +327 -0
  124. package/dist/plugins/ai-chat/client/index.d.ts +327 -0
  125. package/dist/plugins/ai-chat/client/index.mjs +7 -0
  126. package/dist/plugins/ai-chat/client.css +6 -0
  127. package/dist/plugins/ai-chat/query-keys.cjs +60 -0
  128. package/dist/plugins/ai-chat/query-keys.d.cts +478 -0
  129. package/dist/plugins/ai-chat/query-keys.d.mts +478 -0
  130. package/dist/plugins/ai-chat/query-keys.d.ts +478 -0
  131. package/dist/plugins/ai-chat/query-keys.mjs +58 -0
  132. package/dist/plugins/ai-chat/style.css +19 -0
  133. package/dist/plugins/blog/api/index.d.cts +1 -1
  134. package/dist/plugins/blog/api/index.d.mts +1 -1
  135. package/dist/plugins/blog/api/index.d.ts +1 -1
  136. package/dist/plugins/blog/client/components/shared/markdown-content-styles.css +85 -62
  137. package/dist/plugins/blog/client/hooks/index.d.cts +4 -4
  138. package/dist/plugins/blog/client/hooks/index.d.mts +4 -4
  139. package/dist/plugins/blog/client/hooks/index.d.ts +4 -4
  140. package/dist/plugins/blog/client/index.d.cts +1 -1
  141. package/dist/plugins/blog/client/index.d.mts +1 -1
  142. package/dist/plugins/blog/client/index.d.ts +1 -1
  143. package/dist/plugins/blog/query-keys.d.cts +7 -7
  144. package/dist/plugins/blog/query-keys.d.mts +7 -7
  145. package/dist/plugins/blog/query-keys.d.ts +7 -7
  146. package/dist/shared/stack.Be1QIHEn.d.cts +23 -0
  147. package/dist/shared/stack.Be1QIHEn.d.mts +23 -0
  148. package/dist/shared/stack.Be1QIHEn.d.ts +23 -0
  149. package/dist/shared/stack.DorMi9CZ.d.cts +80 -0
  150. package/dist/shared/stack.DorMi9CZ.d.mts +80 -0
  151. package/dist/shared/stack.DorMi9CZ.d.ts +80 -0
  152. package/package.json +59 -1
  153. package/src/plugins/ai-chat/api/index.ts +2 -0
  154. package/src/plugins/ai-chat/api/plugin.ts +1083 -0
  155. package/src/plugins/ai-chat/client/components/chat-input.tsx +295 -0
  156. package/src/plugins/ai-chat/client/components/chat-interface.tsx +455 -0
  157. package/src/plugins/ai-chat/client/components/chat-layout.tsx +160 -0
  158. package/src/plugins/ai-chat/client/components/chat-message.tsx +505 -0
  159. package/src/plugins/ai-chat/client/components/chat-sidebar.tsx +296 -0
  160. package/src/plugins/ai-chat/client/components/index.ts +16 -0
  161. package/src/plugins/ai-chat/client/components/loading/chat-page-skeleton.tsx +57 -0
  162. package/src/plugins/ai-chat/client/components/loading/index.tsx +11 -0
  163. package/src/plugins/ai-chat/client/components/pages/404-page.tsx +27 -0
  164. package/src/plugins/ai-chat/client/components/pages/chat-page.internal.tsx +31 -0
  165. package/src/plugins/ai-chat/client/components/pages/chat-page.tsx +46 -0
  166. package/src/plugins/ai-chat/client/components/shared/default-error.tsx +28 -0
  167. package/src/plugins/ai-chat/client/components/shared/error-placeholder.tsx +22 -0
  168. package/src/plugins/ai-chat/client/hooks/chat-hooks.tsx +349 -0
  169. package/src/plugins/ai-chat/client/hooks/index.tsx +1 -0
  170. package/src/plugins/ai-chat/client/index.ts +14 -0
  171. package/src/plugins/ai-chat/client/localization/index.ts +156 -0
  172. package/src/plugins/ai-chat/client/overrides.ts +170 -0
  173. package/src/plugins/ai-chat/client/plugin.tsx +449 -0
  174. package/src/plugins/ai-chat/client.css +6 -0
  175. package/src/plugins/ai-chat/db.ts +65 -0
  176. package/src/plugins/ai-chat/query-keys.ts +87 -0
  177. package/src/plugins/ai-chat/schemas.ts +40 -0
  178. package/src/plugins/ai-chat/style.css +19 -0
  179. package/src/plugins/ai-chat/types.ts +29 -0
  180. package/src/plugins/blog/client/components/shared/markdown-content-styles.css +85 -62
  181. package/src/plugins/blog/client/components/shared/markdown-content.tsx +19 -427
  182. package/src/plugins/blog/client/components/shared/page-wrapper.tsx +2 -2
  183. package/dist/node_modules/.pnpm/react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0/node_modules/react-remove-scroll/dist/es2015/sidecar.cjs +0 -9
  184. package/src/plugins/blog/client/components/shared/better-blog-attribution.tsx +0 -19
  185. package/dist/node_modules/.pnpm/{@radix-ui_react-compose-refs@1.1.2_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-compose-refs@1.1.2_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-compose-refs/dist/index.cjs +0 -0
  186. package/dist/node_modules/.pnpm/{@radix-ui_react-compose-refs@1.1.2_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-compose-refs@1.1.2_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-compose-refs/dist/index.mjs +0 -0
  187. package/dist/node_modules/.pnpm/{@radix-ui_react-context@1.1.2_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-context@1.1.2_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-context/dist/index.cjs +0 -0
  188. package/dist/node_modules/.pnpm/{@radix-ui_react-context@1.1.2_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-context@1.1.2_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-context/dist/index.mjs +0 -0
  189. package/dist/node_modules/.pnpm/{@radix-ui_react-direction@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-direction@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-direction/dist/index.cjs +0 -0
  190. package/dist/node_modules/.pnpm/{@radix-ui_react-direction@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-direction@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-direction/dist/index.mjs +0 -0
  191. package/dist/node_modules/.pnpm/{@radix-ui_react-focus-guards@1.1.3_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-focus-guards@1.1.3_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-focus-guards/dist/index.cjs +0 -0
  192. package/dist/node_modules/.pnpm/{@radix-ui_react-focus-guards@1.1.3_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-focus-guards@1.1.3_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-focus-guards/dist/index.mjs +0 -0
  193. package/dist/node_modules/.pnpm/{@radix-ui_react-primitive@2.1.3_@types_react-dom@19.2.2_@types_react@19.2.2__@types_rea_bdc15f10281778271ffcbe8dd3cd491e → @radix-ui_react-primitive@2.1.3_@types_react-dom@19.2.3_@types_react@19.2.6__@types_rea_a92a69cb1cb39305138539e4fa72f596}/node_modules/@radix-ui/react-primitive/dist/index.cjs +0 -0
  194. package/dist/node_modules/.pnpm/{@radix-ui_react-primitive@2.1.3_@types_react-dom@19.2.2_@types_react@19.2.2__@types_rea_bdc15f10281778271ffcbe8dd3cd491e → @radix-ui_react-primitive@2.1.3_@types_react-dom@19.2.3_@types_react@19.2.6__@types_rea_a92a69cb1cb39305138539e4fa72f596}/node_modules/@radix-ui/react-primitive/dist/index.mjs +0 -0
  195. package/dist/node_modules/.pnpm/{@radix-ui_react-use-callback-ref@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-use-callback-ref@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-use-callback-ref/dist/index.cjs +0 -0
  196. package/dist/node_modules/.pnpm/{@radix-ui_react-use-callback-ref@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-use-callback-ref@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-use-callback-ref/dist/index.mjs +0 -0
  197. package/dist/node_modules/.pnpm/{@radix-ui_react-use-layout-effect@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-use-layout-effect@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-use-layout-effect/dist/index.cjs +0 -0
  198. package/dist/node_modules/.pnpm/{@radix-ui_react-use-layout-effect@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-use-layout-effect@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-use-layout-effect/dist/index.mjs +0 -0
  199. package/dist/node_modules/.pnpm/{@radix-ui_react-use-previous@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-use-previous@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-use-previous/dist/index.cjs +0 -0
  200. package/dist/node_modules/.pnpm/{@radix-ui_react-use-previous@1.1.1_@types_react@19.2.2_react@19.2.0 → @radix-ui_react-use-previous@1.1.1_@types_react@19.2.6_react@19.2.0}/node_modules/@radix-ui/react-use-previous/dist/index.mjs +0 -0
  201. package/dist/node_modules/.pnpm/{react-remove-scroll-bar@2.3.8_@types_react@19.2.2_react@19.2.0 → react-remove-scroll-bar@2.3.8_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll-bar/dist/es2015/constants.cjs +0 -0
  202. package/dist/node_modules/.pnpm/{react-remove-scroll-bar@2.3.8_@types_react@19.2.2_react@19.2.0 → react-remove-scroll-bar@2.3.8_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll-bar/dist/es2015/constants.mjs +0 -0
  203. package/dist/node_modules/.pnpm/{react-remove-scroll-bar@2.3.8_@types_react@19.2.2_react@19.2.0 → react-remove-scroll-bar@2.3.8_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll-bar/dist/es2015/utils.cjs +0 -0
  204. package/dist/node_modules/.pnpm/{react-remove-scroll-bar@2.3.8_@types_react@19.2.2_react@19.2.0 → react-remove-scroll-bar@2.3.8_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll-bar/dist/es2015/utils.mjs +0 -0
  205. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/Combination.cjs +0 -0
  206. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/Combination.mjs +0 -0
  207. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/aggresiveCapture.cjs +0 -0
  208. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/aggresiveCapture.mjs +0 -0
  209. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/handleScroll.cjs +0 -0
  210. package/dist/node_modules/.pnpm/{react-remove-scroll@2.7.1_@types_react@19.2.2_react@19.2.0 → react-remove-scroll@2.7.1_@types_react@19.2.6_react@19.2.0}/node_modules/react-remove-scroll/dist/es2015/handleScroll.mjs +0 -0
  211. package/dist/node_modules/.pnpm/{react-style-singleton@2.2.3_@types_react@19.2.2_react@19.2.0 → react-style-singleton@2.2.3_@types_react@19.2.6_react@19.2.0}/node_modules/react-style-singleton/dist/es2015/component.cjs +0 -0
  212. package/dist/node_modules/.pnpm/{react-style-singleton@2.2.3_@types_react@19.2.2_react@19.2.0 → react-style-singleton@2.2.3_@types_react@19.2.6_react@19.2.0}/node_modules/react-style-singleton/dist/es2015/component.mjs +0 -0
  213. package/dist/node_modules/.pnpm/{react-style-singleton@2.2.3_@types_react@19.2.2_react@19.2.0 → react-style-singleton@2.2.3_@types_react@19.2.6_react@19.2.0}/node_modules/react-style-singleton/dist/es2015/hook.cjs +0 -0
  214. package/dist/node_modules/.pnpm/{react-style-singleton@2.2.3_@types_react@19.2.2_react@19.2.0 → react-style-singleton@2.2.3_@types_react@19.2.6_react@19.2.0}/node_modules/react-style-singleton/dist/es2015/hook.mjs +0 -0
  215. package/dist/node_modules/.pnpm/{react-style-singleton@2.2.3_@types_react@19.2.2_react@19.2.0 → react-style-singleton@2.2.3_@types_react@19.2.6_react@19.2.0}/node_modules/react-style-singleton/dist/es2015/singleton.cjs +0 -0
  216. package/dist/node_modules/.pnpm/{react-style-singleton@2.2.3_@types_react@19.2.2_react@19.2.0 → react-style-singleton@2.2.3_@types_react@19.2.6_react@19.2.0}/node_modules/react-style-singleton/dist/es2015/singleton.mjs +0 -0
  217. package/dist/node_modules/.pnpm/{use-callback-ref@1.3.3_@types_react@19.2.2_react@19.2.0 → use-callback-ref@1.3.3_@types_react@19.2.6_react@19.2.0}/node_modules/use-callback-ref/dist/es2015/assignRef.cjs +0 -0
  218. package/dist/node_modules/.pnpm/{use-callback-ref@1.3.3_@types_react@19.2.2_react@19.2.0 → use-callback-ref@1.3.3_@types_react@19.2.6_react@19.2.0}/node_modules/use-callback-ref/dist/es2015/assignRef.mjs +0 -0
  219. package/dist/node_modules/.pnpm/{use-callback-ref@1.3.3_@types_react@19.2.2_react@19.2.0 → use-callback-ref@1.3.3_@types_react@19.2.6_react@19.2.0}/node_modules/use-callback-ref/dist/es2015/useMergeRef.cjs +0 -0
  220. package/dist/node_modules/.pnpm/{use-callback-ref@1.3.3_@types_react@19.2.2_react@19.2.0 → use-callback-ref@1.3.3_@types_react@19.2.6_react@19.2.0}/node_modules/use-callback-ref/dist/es2015/useMergeRef.mjs +0 -0
  221. package/dist/node_modules/.pnpm/{use-callback-ref@1.3.3_@types_react@19.2.2_react@19.2.0 → use-callback-ref@1.3.3_@types_react@19.2.6_react@19.2.0}/node_modules/use-callback-ref/dist/es2015/useRef.cjs +0 -0
  222. package/dist/node_modules/.pnpm/{use-callback-ref@1.3.3_@types_react@19.2.2_react@19.2.0 → use-callback-ref@1.3.3_@types_react@19.2.6_react@19.2.0}/node_modules/use-callback-ref/dist/es2015/useRef.mjs +0 -0
  223. package/dist/node_modules/.pnpm/{use-sidecar@1.1.3_@types_react@19.2.2_react@19.2.0 → use-sidecar@1.1.3_@types_react@19.2.6_react@19.2.0}/node_modules/use-sidecar/dist/es2015/exports.cjs +0 -0
  224. package/dist/node_modules/.pnpm/{use-sidecar@1.1.3_@types_react@19.2.2_react@19.2.0 → use-sidecar@1.1.3_@types_react@19.2.6_react@19.2.0}/node_modules/use-sidecar/dist/es2015/exports.mjs +0 -0
  225. package/dist/node_modules/.pnpm/{use-sidecar@1.1.3_@types_react@19.2.2_react@19.2.0 → use-sidecar@1.1.3_@types_react@19.2.6_react@19.2.0}/node_modules/use-sidecar/dist/es2015/medium.cjs +0 -0
  226. package/dist/node_modules/.pnpm/{use-sidecar@1.1.3_@types_react@19.2.2_react@19.2.0 → use-sidecar@1.1.3_@types_react@19.2.6_react@19.2.0}/node_modules/use-sidecar/dist/es2015/medium.mjs +0 -0
  227. package/dist/shared/{stack.CbuN2zVV.d.ts → stack.CcI4sYJP.d.cts} +3 -3
  228. package/dist/shared/{stack.CbuN2zVV.d.cts → stack.CcI4sYJP.d.mts} +3 -3
  229. package/dist/shared/{stack.CbuN2zVV.d.mts → stack.CcI4sYJP.d.ts} +3 -3
@@ -0,0 +1,455 @@
1
+ "use client";
2
+
3
+ import { useChat } from "@ai-sdk/react";
4
+ import { useEffect, useRef, useState, useMemo, useCallback } from "react";
5
+ import { useQueryClient } from "@tanstack/react-query";
6
+ import { ChatMessage } from "./chat-message";
7
+ import { ChatInput, type AttachedFile } from "./chat-input";
8
+ import { BetterStackAttribution } from "@workspace/ui/components/better-stack-attribution";
9
+ import { ScrollArea } from "@workspace/ui/components/scroll-area";
10
+ import { DefaultChatTransport, type UIMessage } from "ai";
11
+ import { cn } from "@workspace/ui/lib/utils";
12
+ import { usePluginOverrides, useBasePath } from "@btst/stack/context";
13
+ import type { AiChatPluginOverrides } from "../overrides";
14
+ import { AI_CHAT_LOCALIZATION } from "../localization";
15
+ import { createApiClient } from "@btst/stack/plugins/client";
16
+ import type { AiChatApiRouter } from "../../api/plugin";
17
+ import { createAiChatQueryKeys } from "../../query-keys";
18
+ import {
19
+ useConversation,
20
+ useConversations,
21
+ type SerializedConversation,
22
+ } from "../hooks/chat-hooks";
23
+
24
+ interface ChatInterfaceProps {
25
+ apiPath?: string;
26
+ initialMessages?: UIMessage[];
27
+ id?: string;
28
+ /** Variant: 'full' for full-page layout, 'widget' for embedded widget */
29
+ variant?: "full" | "widget";
30
+ className?: string;
31
+ }
32
+
33
+ export function ChatInterface({
34
+ apiPath = "/api/chat",
35
+ initialMessages,
36
+ id,
37
+ variant = "full",
38
+ className,
39
+ }: ChatInterfaceProps) {
40
+ const {
41
+ navigate,
42
+ localization: customLocalization,
43
+ apiBaseURL,
44
+ apiBasePath,
45
+ headers,
46
+ mode,
47
+ showAttribution,
48
+ } = usePluginOverrides<AiChatPluginOverrides, Partial<AiChatPluginOverrides>>(
49
+ "ai-chat",
50
+ { showAttribution: true },
51
+ );
52
+ const basePath = useBasePath();
53
+ const isPublicMode = mode === "public";
54
+
55
+ const localization = { ...AI_CHAT_LOCALIZATION, ...customLocalization };
56
+ const queryClient = useQueryClient();
57
+
58
+ const conversationsListQueryKey = useMemo(() => {
59
+ // In public mode, we don't need conversation queries
60
+ if (isPublicMode) return ["ai-chat", "disabled"];
61
+ const client = createApiClient<AiChatApiRouter>({
62
+ baseURL: apiBaseURL,
63
+ basePath: apiBasePath,
64
+ });
65
+ const queries = createAiChatQueryKeys(client, headers);
66
+ return queries.conversations.list().queryKey;
67
+ }, [apiBaseURL, apiBasePath, headers, isPublicMode]);
68
+
69
+ // Track the current conversation ID - initialized from prop, updated after first message
70
+ // In public mode, we don't track conversation IDs
71
+ const [currentConversationId, setCurrentConversationId] = useState<
72
+ string | undefined
73
+ >(isPublicMode ? undefined : id);
74
+ // Track if we've sent the first message on a new chat (to trigger navigation)
75
+ const isFirstMessageSentRef = useRef(false);
76
+ const hasNavigatedRef = useRef(false);
77
+
78
+ // Update currentConversationId when id prop changes (e.g., navigating to different conversation)
79
+ useEffect(() => {
80
+ if (!isPublicMode) {
81
+ setCurrentConversationId(id);
82
+ isFirstMessageSentRef.current = false;
83
+ hasNavigatedRef.current = false;
84
+ }
85
+ }, [id, isPublicMode]);
86
+
87
+ // Fetch existing conversation messages when id is provided (authenticated mode only)
88
+ const { conversation, isLoading: isLoadingConversation } = useConversation(
89
+ id,
90
+ { enabled: !!id && !isPublicMode },
91
+ );
92
+
93
+ // Fetch conversations list for navigation after first message (authenticated mode only)
94
+ const { conversations } = useConversations({ enabled: !isPublicMode });
95
+
96
+ // Use a ref to track the conversation ID for the transport body
97
+ // This ensures the transport always uses the latest value
98
+ // In public mode, always undefined
99
+ const conversationIdRef = useRef<string | undefined>(
100
+ isPublicMode ? undefined : id,
101
+ );
102
+ useEffect(() => {
103
+ if (!isPublicMode) {
104
+ conversationIdRef.current = currentConversationId;
105
+ }
106
+ }, [currentConversationId, isPublicMode]);
107
+
108
+ // Ref to track edit operation with messages to use
109
+ const editMessagesRef = useRef<UIMessage[] | null>(null);
110
+
111
+ // Memoize the transport to prevent recreation on every render
112
+ const transport = useMemo(
113
+ () =>
114
+ new DefaultChatTransport({
115
+ api: apiPath,
116
+ // In public mode, don't send conversationId
117
+ body: isPublicMode
118
+ ? undefined
119
+ : () => ({ conversationId: conversationIdRef.current }),
120
+ // Handle edit operations by using truncated messages from the ref
121
+ prepareSendMessagesRequest: ({ messages: hookMessages }) => {
122
+ // If we're in an edit operation, use the truncated messages + new user message
123
+ if (editMessagesRef.current !== null) {
124
+ const newUserMessage = hookMessages[hookMessages.length - 1];
125
+ const messagesToSend = [...editMessagesRef.current];
126
+ if (newUserMessage) {
127
+ messagesToSend.push(newUserMessage);
128
+ }
129
+ // Clear the ref after use
130
+ editMessagesRef.current = null;
131
+ return {
132
+ body: {
133
+ messages: messagesToSend,
134
+ conversationId: conversationIdRef.current,
135
+ },
136
+ };
137
+ }
138
+ // Normal case - use the messages as-is
139
+ return {
140
+ body: {
141
+ messages: hookMessages,
142
+ conversationId: conversationIdRef.current,
143
+ },
144
+ };
145
+ },
146
+ }),
147
+ [apiPath, isPublicMode],
148
+ );
149
+
150
+ const { messages, sendMessage, status, error, setMessages, regenerate } =
151
+ useChat({
152
+ transport,
153
+ onError: (err) => {
154
+ console.error("useChat onError:", err);
155
+ },
156
+ onFinish: async () => {
157
+ // In public mode, skip all persistence-related operations
158
+ if (isPublicMode) return;
159
+
160
+ // Invalidate conversation list to show new/updated conversations
161
+ await queryClient.invalidateQueries({
162
+ queryKey: conversationsListQueryKey,
163
+ });
164
+
165
+ // If this was the first message on a new chat, update the URL without full navigation
166
+ // This avoids losing the in-memory messages during component remount
167
+ if (isFirstMessageSentRef.current && !id && !hasNavigatedRef.current) {
168
+ hasNavigatedRef.current = true;
169
+ // Wait for the invalidation to complete and refetch conversations
170
+ await queryClient.refetchQueries({
171
+ queryKey: conversationsListQueryKey,
172
+ });
173
+ // Get the updated conversations from cache
174
+ const cachedConversations = queryClient.getQueryData<
175
+ SerializedConversation[]
176
+ >(conversationsListQueryKey);
177
+ if (cachedConversations && cachedConversations.length > 0) {
178
+ // The most recently updated conversation should be the one we just created
179
+ const newConversation = cachedConversations[0];
180
+ if (newConversation) {
181
+ // Update our local state
182
+ setCurrentConversationId(newConversation.id);
183
+ conversationIdRef.current = newConversation.id;
184
+ // Update URL without navigation to preserve in-memory messages
185
+ // Use replaceState to avoid adding to history stack
186
+ const newUrl = `${basePath}/chat/${newConversation.id}`;
187
+ if (typeof window !== "undefined") {
188
+ window.history.replaceState(
189
+ { ...window.history.state },
190
+ "",
191
+ newUrl,
192
+ );
193
+ }
194
+ }
195
+ }
196
+ }
197
+ },
198
+ });
199
+
200
+ // Load existing conversation messages when navigating to a conversation
201
+ useEffect(() => {
202
+ if (
203
+ conversation?.messages &&
204
+ conversation.messages.length > 0 &&
205
+ messages.length === 0
206
+ ) {
207
+ // Filter out "data" role messages as UIMessage only accepts "user" | "assistant" | "system"
208
+ const uiMessages: UIMessage[] = conversation.messages
209
+ .filter((m) => m.role !== "data")
210
+ .map((m) => {
211
+ // Try to parse content as JSON parts (new format with images)
212
+ let parts: UIMessage["parts"];
213
+ try {
214
+ const parsed = JSON.parse(m.content);
215
+ if (Array.isArray(parsed)) {
216
+ parts = parsed;
217
+ } else {
218
+ // Fallback: wrap as text
219
+ parts = [{ type: "text" as const, text: m.content }];
220
+ }
221
+ } catch {
222
+ // Not JSON - legacy format, wrap as text
223
+ parts = [{ type: "text" as const, text: m.content }];
224
+ }
225
+ return {
226
+ id: m.id,
227
+ role: m.role as "user" | "assistant" | "system",
228
+ parts,
229
+ };
230
+ });
231
+ setMessages(uiMessages);
232
+ }
233
+ }, [conversation, messages.length, setMessages]);
234
+
235
+ // Set initial messages on mount (for SSR hydration)
236
+ useEffect(() => {
237
+ if (
238
+ initialMessages &&
239
+ initialMessages.length > 0 &&
240
+ messages.length === 0
241
+ ) {
242
+ setMessages(initialMessages);
243
+ }
244
+ }, [initialMessages, setMessages, messages.length]);
245
+
246
+ const [input, setInput] = useState("");
247
+ const [attachedFiles, setAttachedFiles] = useState<AttachedFile[]>([]);
248
+ const scrollRef = useRef<HTMLDivElement>(null);
249
+
250
+ // Auto-scroll to bottom when messages change
251
+ useEffect(() => {
252
+ if (scrollRef.current) {
253
+ const scrollElement = scrollRef.current.querySelector(
254
+ "[data-radix-scroll-area-viewport]",
255
+ );
256
+ if (scrollElement) {
257
+ scrollElement.scrollTop = scrollElement.scrollHeight;
258
+ }
259
+ }
260
+ }, [messages]);
261
+
262
+ const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
263
+ setInput(e.target.value);
264
+ };
265
+
266
+ const handleSubmit = async (
267
+ e: React.FormEvent<HTMLFormElement>,
268
+ files?: AttachedFile[],
269
+ ) => {
270
+ e.preventDefault();
271
+ const text = input.trim();
272
+ // Allow submit if there's text OR files
273
+ if (!text && (!files || files.length === 0)) return;
274
+
275
+ // Track if this is the first message on a new chat (authenticated mode only)
276
+ if (!isPublicMode && !id && messages.length === 0) {
277
+ isFirstMessageSentRef.current = true;
278
+ }
279
+
280
+ // Save current values before clearing - we'll restore them if send fails
281
+ const savedInput = input;
282
+ const savedFiles = files ? [...files] : [];
283
+
284
+ // Clear input immediately (optimistically) - the AI SDK renders messages optimistically,
285
+ // so we need to clear the input before the message appears to avoid duplicate text
286
+ setInput("");
287
+ setAttachedFiles([]);
288
+
289
+ try {
290
+ // Use AI SDK's file attachment format
291
+ // The SDK automatically converts supported file types (images, text) to the correct format
292
+ if (files && files.length > 0) {
293
+ // Convert AttachedFile[] to FileUIPart[] format expected by AI SDK
294
+ const fileUIParts = files.map((file) => ({
295
+ type: "file" as const,
296
+ mediaType: file.mediaType,
297
+ url: file.url,
298
+ filename: file.filename,
299
+ }));
300
+
301
+ await sendMessage({
302
+ text: text || "", // AI SDK requires text, even if empty
303
+ files: fileUIParts,
304
+ });
305
+ } else {
306
+ await sendMessage({ text });
307
+ }
308
+ } catch (error) {
309
+ // Restore input on failure so user can retry
310
+ setInput(savedInput);
311
+ setAttachedFiles(savedFiles);
312
+ console.error("Error sending message:", error);
313
+ }
314
+ };
315
+
316
+ const isLoading = status === "streaming" || status === "submitted";
317
+
318
+ // Handler for retrying/regenerating the last AI response
319
+ const handleRetry = useCallback(() => {
320
+ regenerate();
321
+ }, [regenerate]);
322
+
323
+ // Pending edit state - stores the text to send after messages are truncated
324
+ const [pendingEdit, setPendingEdit] = useState<{
325
+ text: string;
326
+ expectedLength: number;
327
+ } | null>(null);
328
+
329
+ // Effect to send the edited message after React has processed the truncation
330
+ useEffect(() => {
331
+ if (pendingEdit && messages.length === pendingEdit.expectedLength) {
332
+ const textToSend = pendingEdit.text;
333
+ setPendingEdit(null);
334
+ sendMessage({ text: textToSend });
335
+ }
336
+ }, [messages.length, pendingEdit, sendMessage]);
337
+
338
+ // Handler for editing a user message - replaces the message and all subsequent messages
339
+ const handleEditMessage = useCallback(
340
+ (messageId: string, newText: string) => {
341
+ const messageIndex = messages.findIndex((m) => m.id === messageId);
342
+ if (messageIndex === -1) return;
343
+
344
+ // Get the message to edit
345
+ const messageToEdit = messages[messageIndex];
346
+ if (!messageToEdit || messageToEdit.role !== "user") return;
347
+
348
+ // Truncate to BEFORE the edited message (remove it and all subsequent)
349
+ const truncatedMessages = messages.slice(0, messageIndex);
350
+
351
+ // Store the truncated messages in the ref for the transport to use
352
+ editMessagesRef.current = truncatedMessages;
353
+
354
+ // Set the pending edit - the useEffect will send after truncation is processed
355
+ setPendingEdit({ text: newText, expectedLength: messageIndex });
356
+
357
+ // Truncate the messages - React will batch this and the useEffect will fire after
358
+ setMessages(truncatedMessages);
359
+ },
360
+ [messages, setMessages],
361
+ );
362
+
363
+ const isWidget = variant === "widget";
364
+
365
+ return (
366
+ <>
367
+ <div className="flex-1 overflow-hidden">
368
+ <div
369
+ className={cn(
370
+ "flex flex-col h-full w-full bg-background",
371
+ isWidget && "rounded-xl",
372
+ className,
373
+ )}
374
+ data-testid="chat-interface"
375
+ >
376
+ {/* Messages Area */}
377
+ <ScrollArea ref={scrollRef} className="flex-1 h-full">
378
+ <div
379
+ className={cn(
380
+ "flex flex-col p-4",
381
+ isWidget ? "max-w-full" : "max-w-3xl mx-auto w-full",
382
+ )}
383
+ >
384
+ {messages.length === 0 ? (
385
+ <div className="flex flex-col items-center justify-center h-full min-h-[300px] text-muted-foreground">
386
+ <p>{localization.CHAT_EMPTY_STATE}</p>
387
+ </div>
388
+ ) : (
389
+ messages.map((m, index) => (
390
+ <ChatMessage
391
+ key={m.id || `msg-${index}`}
392
+ message={m}
393
+ isStreaming={
394
+ status === "streaming" &&
395
+ m.id === messages[messages.length - 1]?.id &&
396
+ m.role === "assistant"
397
+ }
398
+ variant={isWidget ? "compact" : "default"}
399
+ onRetry={
400
+ // Only show retry on the last assistant message
401
+ m.role === "assistant" && index === messages.length - 1
402
+ ? handleRetry
403
+ : undefined
404
+ }
405
+ onEdit={
406
+ // Allow editing user messages
407
+ m.role === "user"
408
+ ? (newText) => handleEditMessage(m.id, newText)
409
+ : undefined
410
+ }
411
+ isRetrying={isLoading && m.role === "assistant"}
412
+ />
413
+ ))
414
+ )}
415
+ {isLoading &&
416
+ messages[messages.length - 1]?.role !== "assistant" && (
417
+ <div className="flex items-center gap-2 text-muted-foreground text-sm py-4">
418
+ <div className="animate-pulse">
419
+ {localization.CHAT_LOADING}
420
+ </div>
421
+ </div>
422
+ )}
423
+ {error && (
424
+ <div className="flex items-center gap-2 text-destructive text-sm py-4 px-3 bg-destructive/10 rounded-md">
425
+ <span>{localization.CHAT_ERROR}</span>
426
+ </div>
427
+ )}
428
+ </div>
429
+ </ScrollArea>
430
+ </div>
431
+ </div>
432
+ {/* Input Area */}
433
+ <div
434
+ className={cn(
435
+ "border-t bg-background p-4",
436
+ isWidget ? "px-3 py-3" : "px-4",
437
+ )}
438
+ >
439
+ <div className={cn(!isWidget && "max-w-3xl mx-auto")}>
440
+ <ChatInput
441
+ input={input}
442
+ handleInputChange={handleInputChange}
443
+ handleSubmit={handleSubmit}
444
+ isLoading={isLoading}
445
+ placeholder={localization.CHAT_PLACEHOLDER}
446
+ variant={isWidget ? "compact" : "default"}
447
+ onFilesAttached={setAttachedFiles}
448
+ attachedFiles={attachedFiles}
449
+ />
450
+ {showAttribution && <BetterStackAttribution />}
451
+ </div>
452
+ </div>
453
+ </>
454
+ );
455
+ }
@@ -0,0 +1,160 @@
1
+ "use client";
2
+
3
+ import { useState, useCallback } from "react";
4
+ import { Button } from "@workspace/ui/components/button";
5
+ import {
6
+ Sheet,
7
+ SheetContent,
8
+ SheetTrigger,
9
+ } from "@workspace/ui/components/sheet";
10
+ import { Menu, PanelLeftClose, PanelLeft } from "lucide-react";
11
+ import { cn } from "@workspace/ui/lib/utils";
12
+ import { ChatSidebar } from "./chat-sidebar";
13
+ import { ChatInterface } from "./chat-interface";
14
+
15
+ export interface ChatLayoutProps {
16
+ /** API base URL */
17
+ apiBaseURL: string;
18
+ /** API base path */
19
+ apiBasePath: string;
20
+ /** Current conversation ID (if viewing existing conversation) */
21
+ conversationId?: string;
22
+ /** Layout mode: 'full' for full page with sidebar, 'widget' for embeddable widget */
23
+ layout?: "full" | "widget";
24
+ /** Additional class name for the container */
25
+ className?: string;
26
+ /** Whether to show the sidebar (default: true for full layout) */
27
+ showSidebar?: boolean;
28
+ /** Height of the widget (only applies to widget layout) */
29
+ widgetHeight?: string | number;
30
+ }
31
+
32
+ /**
33
+ * ChatLayout component that provides a full-page chat experience with sidebar
34
+ * or a compact widget mode for embedding.
35
+ */
36
+ export function ChatLayout({
37
+ apiBaseURL,
38
+ apiBasePath,
39
+ conversationId,
40
+ layout = "full",
41
+ className,
42
+ showSidebar = true,
43
+ widgetHeight = "600px",
44
+ }: ChatLayoutProps) {
45
+ const [sidebarOpen, setSidebarOpen] = useState(true);
46
+ const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
47
+ // Key to force ChatInterface remount when starting a new chat
48
+ const [chatResetKey, setChatResetKey] = useState(0);
49
+
50
+ const apiPath = `${apiBaseURL}${apiBasePath}/chat`;
51
+
52
+ // Handler for "New chat" button - increments key to force remount
53
+ const handleNewChat = useCallback(() => {
54
+ // Only needed when we're already on the "new chat" route (/chat).
55
+ // If we're on /chat/:id, navigation to /chat will remount ChatLayout/ChatInterface anyway.
56
+ if (!conversationId) {
57
+ setChatResetKey((prev) => prev + 1);
58
+ }
59
+ }, [conversationId]);
60
+
61
+ if (layout === "widget") {
62
+ return (
63
+ <div
64
+ className={cn(
65
+ "flex flex-col w-full border rounded-xl overflow-hidden bg-background shadow-sm",
66
+ className,
67
+ )}
68
+ style={{ height: widgetHeight }}
69
+ >
70
+ <ChatInterface apiPath={apiPath} id={conversationId} variant="widget" />
71
+ </div>
72
+ );
73
+ }
74
+
75
+ // Full layout with sidebar
76
+ return (
77
+ <div
78
+ className={cn(
79
+ "flex h-[calc(100vh-4rem)] w-full overflow-hidden",
80
+ className,
81
+ )}
82
+ data-testid="chat-layout"
83
+ >
84
+ {/* Desktop Sidebar */}
85
+ {showSidebar && (
86
+ <div
87
+ className={cn(
88
+ "hidden md:flex transition-all duration-300 ease-in-out",
89
+ sidebarOpen ? "w-72" : "w-0",
90
+ )}
91
+ >
92
+ {sidebarOpen && (
93
+ <ChatSidebar
94
+ currentConversationId={conversationId}
95
+ onNewChat={handleNewChat}
96
+ className="w-72"
97
+ />
98
+ )}
99
+ </div>
100
+ )}
101
+
102
+ {/* Main Chat Area */}
103
+ <div className="flex-1 flex flex-col min-w-0">
104
+ {/* Header */}
105
+ <div className="flex items-center gap-2 p-2 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
106
+ {/* Mobile menu button */}
107
+ {showSidebar && (
108
+ <Sheet open={mobileSidebarOpen} onOpenChange={setMobileSidebarOpen}>
109
+ <SheetTrigger asChild>
110
+ <Button
111
+ variant="ghost"
112
+ size="icon"
113
+ className="md:hidden"
114
+ aria-label="Open menu"
115
+ >
116
+ <Menu className="h-5 w-5" />
117
+ </Button>
118
+ </SheetTrigger>
119
+ <SheetContent side="left" className="p-0 w-72">
120
+ <ChatSidebar
121
+ currentConversationId={conversationId}
122
+ onNewChat={() => {
123
+ handleNewChat();
124
+ setMobileSidebarOpen(false);
125
+ }}
126
+ />
127
+ </SheetContent>
128
+ </Sheet>
129
+ )}
130
+
131
+ {/* Desktop sidebar toggle */}
132
+ {showSidebar && (
133
+ <Button
134
+ variant="ghost"
135
+ size="icon"
136
+ className="hidden md:flex"
137
+ onClick={() => setSidebarOpen(!sidebarOpen)}
138
+ aria-label={sidebarOpen ? "Close sidebar" : "Open sidebar"}
139
+ >
140
+ {sidebarOpen ? (
141
+ <PanelLeftClose className="h-5 w-5" />
142
+ ) : (
143
+ <PanelLeft className="h-5 w-5" />
144
+ )}
145
+ </Button>
146
+ )}
147
+
148
+ <div className="flex-1" />
149
+ </div>
150
+
151
+ <ChatInterface
152
+ key={`chat-${conversationId ?? "new"}-${chatResetKey}`}
153
+ apiPath={apiPath}
154
+ id={conversationId}
155
+ variant="full"
156
+ />
157
+ </div>
158
+ </div>
159
+ );
160
+ }