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