@flamingo-stack/openframe-frontend-core 0.0.204 → 0.0.205
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/dist/{chunk-4CWSZPXH.cjs → chunk-24KCAECR.cjs} +9 -9
- package/dist/{chunk-4CWSZPXH.cjs.map → chunk-24KCAECR.cjs.map} +1 -1
- package/dist/chunk-27APPAJN.cjs +24 -0
- package/dist/chunk-27APPAJN.cjs.map +1 -0
- package/dist/{chunk-UC43NICZ.cjs → chunk-664KA5FI.cjs} +2 -35
- package/dist/chunk-664KA5FI.cjs.map +1 -0
- package/dist/chunk-6RZYJICV.cjs +24 -0
- package/dist/chunk-6RZYJICV.cjs.map +1 -0
- package/dist/chunk-7L4DWM7P.js +24 -0
- package/dist/chunk-7L4DWM7P.js.map +1 -0
- package/dist/chunk-BZFW3FOF.cjs +21 -0
- package/dist/chunk-BZFW3FOF.cjs.map +1 -0
- package/dist/{chunk-N57KWHDB.js → chunk-CIPO6DXK.js} +5 -5
- package/dist/chunk-EL5YVPD5.js +21 -0
- package/dist/chunk-EL5YVPD5.js.map +1 -0
- package/dist/{chunk-ARQ4XP64.cjs → chunk-FDCFI7YT.cjs} +40080 -31492
- package/dist/chunk-FDCFI7YT.cjs.map +1 -0
- package/dist/chunk-G7UE6RKV.cjs +121 -0
- package/dist/chunk-G7UE6RKV.cjs.map +1 -0
- package/dist/{chunk-25LVV26X.cjs → chunk-JUZGUQMX.cjs} +178 -50
- package/dist/chunk-JUZGUQMX.cjs.map +1 -0
- package/dist/{chunk-SZPJ5R5B.js → chunk-KSOOKNBG.js} +1 -34
- package/dist/chunk-KSOOKNBG.js.map +1 -0
- package/dist/{chunk-RMB5DVED.cjs → chunk-KUZGEA7U.cjs} +83 -66
- package/dist/chunk-KUZGEA7U.cjs.map +1 -0
- package/dist/chunk-LXC6P2EO.js +63 -0
- package/dist/chunk-LXC6P2EO.js.map +1 -0
- package/dist/chunk-MJNXIEV2.js +24 -0
- package/dist/chunk-MJNXIEV2.js.map +1 -0
- package/dist/{chunk-CPXLQ57U.js → chunk-MVGGXOFA.js} +37 -20
- package/dist/chunk-MVGGXOFA.js.map +1 -0
- package/dist/{chunk-LY34ORX6.js → chunk-O55ZUAX7.js} +39920 -31332
- package/dist/chunk-O55ZUAX7.js.map +1 -0
- package/dist/chunk-OHPI2HRK.js +47 -0
- package/dist/chunk-OHPI2HRK.js.map +1 -0
- package/dist/chunk-PLJLE4A4.js +121 -0
- package/dist/chunk-PLJLE4A4.js.map +1 -0
- package/dist/{chunk-XGL5FKIK.js → chunk-SCN5WFIZ.js} +148 -20
- package/dist/chunk-SCN5WFIZ.js.map +1 -0
- package/dist/chunk-WBR7H6E3.cjs +47 -0
- package/dist/chunk-WBR7H6E3.cjs.map +1 -0
- package/dist/chunk-XL4V2PYG.cjs +63 -0
- package/dist/chunk-XL4V2PYG.cjs.map +1 -0
- package/dist/components/announcement-bar.d.ts.map +1 -1
- package/dist/components/chat/chat-attachment-bar.d.ts +66 -0
- package/dist/components/chat/chat-attachment-bar.d.ts.map +1 -0
- package/dist/components/chat/chat-container.d.ts +21 -1
- package/dist/components/chat/chat-container.d.ts.map +1 -1
- package/dist/components/chat/chat-input.d.ts.map +1 -1
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/chat-panel-context.d.ts +9 -0
- package/dist/components/chat/chat-panel-context.d.ts.map +1 -0
- package/dist/components/chat/chat-ticket-list.d.ts +1 -1
- package/dist/components/chat/chat-ticket-list.d.ts.map +1 -1
- package/dist/components/chat/embeddable-chat.d.ts +42 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/admin-content-card.d.ts +34 -0
- package/dist/components/chat/entity-cards/admin-content-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/block-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/blog-card.d.ts +30 -0
- package/dist/components/chat/entity-cards/blog-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/blog-image-placeholder.d.ts +26 -0
- package/dist/components/chat/entity-cards/blog-image-placeholder.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/campaign-card-admin.d.ts +33 -0
- package/dist/components/chat/entity-cards/campaign-card-admin.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/case-study-card.d.ts +20 -0
- package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/chat-ticket-item.d.ts.map +1 -0
- package/dist/components/chat/{chat-video-entity-card.d.ts → entity-cards/chat-video-entity-card.d.ts} +1 -1
- package/dist/components/chat/entity-cards/chat-video-entity-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/customer-interview-card.d.ts +19 -0
- package/dist/components/chat/entity-cards/customer-interview-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/data-room-doc-card.d.ts +47 -0
- package/dist/components/chat/entity-cards/data-room-doc-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/dispatch.d.ts +119 -0
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/entity-author-card.d.ts +87 -0
- package/dist/components/chat/entity-cards/entity-author-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/generic-entity-card.d.ts +42 -0
- package/dist/components/chat/entity-cards/generic-entity-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/github-activity-card.d.ts +37 -0
- package/dist/components/chat/entity-cards/github-activity-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/hubspot-ticket-card.d.ts +28 -0
- package/dist/components/chat/entity-cards/hubspot-ticket-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/index.d.ts +32 -0
- package/dist/components/chat/entity-cards/index.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/investor-update-card.d.ts +19 -0
- package/dist/components/chat/entity-cards/investor-update-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts +20 -0
- package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/product-release-card-defaults.d.ts +21 -0
- package/dist/components/chat/entity-cards/product-release-card-defaults.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/product-release-card.d.ts +12 -0
- package/dist/components/chat/entity-cards/product-release-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/program-card-defaults.d.ts +32 -0
- package/dist/components/chat/entity-cards/program-card-defaults.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/program-card.d.ts +37 -0
- package/dist/components/chat/entity-cards/program-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/roadmap-card.d.ts +28 -0
- package/dist/components/chat/entity-cards/roadmap-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/roadmap-vote-button.d.ts +12 -0
- package/dist/components/chat/entity-cards/roadmap-vote-button.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/slack-message-card.d.ts +28 -0
- package/dist/components/chat/entity-cards/slack-message-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/task-type-icon.d.ts +6 -0
- package/dist/components/chat/entity-cards/task-type-icon.d.ts.map +1 -0
- package/dist/components/chat/hooks/index.d.ts +10 -0
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chat-attachment-image-gallery.d.ts +5 -0
- package/dist/components/chat/hooks/use-chat-attachment-image-gallery.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-chat-attachments.d.ts +33 -0
- package/dist/components/chat/hooks/use-chat-attachments.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-chat-card-item.d.ts +7 -0
- package/dist/components/chat/hooks/use-chat-card-item.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-chat-identity.d.ts +44 -0
- package/dist/components/chat/hooks/use-chat-identity.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-chat.d.ts +30 -0
- package/dist/components/chat/hooks/use-chat.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-close-on-navigation.d.ts +2 -0
- package/dist/components/chat/hooks/use-close-on-navigation.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-embedded-chat.d.ts +174 -0
- package/dist/components/chat/hooks/use-embedded-chat.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-proxied-image-url.d.ts +18 -0
- package/dist/components/chat/hooks/use-proxied-image-url.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-slash-commands.d.ts +32 -0
- package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-sse.d.ts +57 -0
- package/dist/components/chat/hooks/use-sse.d.ts.map +1 -0
- package/dist/components/chat/index.cjs +393 -0
- package/dist/components/chat/index.cjs.map +1 -0
- package/dist/components/chat/index.d.ts +5 -3
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +393 -0
- package/dist/components/chat/index.js.map +1 -0
- package/dist/components/chat/nav-link-anchor-via-runtime.d.ts +33 -0
- package/dist/components/chat/nav-link-anchor-via-runtime.d.ts.map +1 -0
- package/dist/components/chat/source-action-button.d.ts +39 -0
- package/dist/components/chat/source-action-button.d.ts.map +1 -0
- package/dist/components/chat/types/chat.types.d.ts +36 -0
- package/dist/components/chat/types/chat.types.d.ts.map +1 -1
- package/dist/components/chat/types/component.types.d.ts +56 -11
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/chat/types/entities/blog.d.ts +14 -0
- package/dist/components/chat/types/entities/blog.d.ts.map +1 -0
- package/dist/components/chat/types/entities/case-study.d.ts +10 -0
- package/dist/components/chat/types/entities/case-study.d.ts.map +1 -0
- package/dist/components/chat/types/entities/content-ref.d.ts +23 -0
- package/dist/components/chat/types/entities/content-ref.d.ts.map +1 -0
- package/dist/components/chat/types/entities/customer-interview.d.ts +10 -0
- package/dist/components/chat/types/entities/customer-interview.d.ts.map +1 -0
- package/dist/components/chat/types/entities/data-room-doc.d.ts +37 -0
- package/dist/components/chat/types/entities/data-room-doc.d.ts.map +1 -0
- package/dist/components/chat/types/entities/github-activity.d.ts +29 -0
- package/dist/components/chat/types/entities/github-activity.d.ts.map +1 -0
- package/dist/components/chat/types/entities/hubspot-ticket.d.ts +39 -0
- package/dist/components/chat/types/entities/hubspot-ticket.d.ts.map +1 -0
- package/dist/components/chat/types/entities/index.d.ts +28 -0
- package/dist/components/chat/types/entities/index.d.ts.map +1 -0
- package/dist/components/chat/types/entities/investor-update.d.ts +83 -0
- package/dist/components/chat/types/entities/investor-update.d.ts.map +1 -0
- package/dist/components/chat/types/entities/onboarding-guide.d.ts +79 -0
- package/dist/components/chat/types/entities/onboarding-guide.d.ts.map +1 -0
- package/dist/components/chat/types/entities/program-types.d.ts +303 -0
- package/dist/components/chat/types/entities/program-types.d.ts.map +1 -0
- package/dist/components/chat/types/entities/roadmap-item.d.ts +41 -0
- package/dist/components/chat/types/entities/roadmap-item.d.ts.map +1 -0
- package/dist/components/chat/types/entities/slack-message.d.ts +28 -0
- package/dist/components/chat/types/entities/slack-message.d.ts.map +1 -0
- package/dist/components/chat/types/index.d.ts +1 -0
- package/dist/components/chat/types/index.d.ts.map +1 -1
- package/dist/components/chat/utils/agent-status-message.d.ts +18 -0
- package/dist/components/chat/utils/agent-status-message.d.ts.map +1 -0
- package/dist/components/chat/utils/auto-continuation-directive.d.ts +38 -0
- package/dist/components/chat/utils/auto-continuation-directive.d.ts.map +1 -0
- package/dist/components/chat/utils/chat-attachment-markdown.d.ts +114 -0
- package/dist/components/chat/utils/chat-attachment-markdown.d.ts.map +1 -0
- package/dist/components/chat/utils/chat-authed-fetch.d.ts +13 -0
- package/dist/components/chat/utils/chat-authed-fetch.d.ts.map +1 -0
- package/dist/components/chat/utils/chat-nav-resolution.d.ts +72 -0
- package/dist/components/chat/utils/chat-nav-resolution.d.ts.map +1 -0
- package/dist/components/chat/utils/chat-proxy-auth-storage.d.ts +43 -0
- package/dist/components/chat/utils/chat-proxy-auth-storage.d.ts.map +1 -0
- package/dist/components/chat/utils/chip-action-class.d.ts +16 -0
- package/dist/components/chat/utils/chip-action-class.d.ts.map +1 -0
- package/dist/components/chat/utils/chip-styles.d.ts +32 -0
- package/dist/components/chat/utils/chip-styles.d.ts.map +1 -0
- package/dist/components/chat/utils/clickup-task-type-utils.d.ts +38 -0
- package/dist/components/chat/utils/clickup-task-type-utils.d.ts.map +1 -0
- package/dist/components/chat/utils/compact-card-classes.d.ts +50 -0
- package/dist/components/chat/utils/compact-card-classes.d.ts.map +1 -0
- package/dist/components/chat/utils/decide-new-tab.d.ts +39 -0
- package/dist/components/chat/utils/decide-new-tab.d.ts.map +1 -0
- package/dist/components/chat/utils/external-app-urls.d.ts +14 -0
- package/dist/components/chat/utils/external-app-urls.d.ts.map +1 -0
- package/dist/components/chat/utils/flatten-assistant-content.d.ts +25 -0
- package/dist/components/chat/utils/flatten-assistant-content.d.ts.map +1 -0
- package/dist/components/chat/utils/icon-registry.d.ts +67 -0
- package/dist/components/chat/utils/icon-registry.d.ts.map +1 -0
- package/dist/components/chat/utils/index.d.ts +21 -0
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/is-cross-origin-url.d.ts +22 -0
- package/dist/components/chat/utils/is-cross-origin-url.d.ts.map +1 -0
- package/dist/components/chat/utils/nav-anchor-props.d.ts +54 -0
- package/dist/components/chat/utils/nav-anchor-props.d.ts.map +1 -0
- package/dist/components/chat/utils/nav-click-handler.d.ts +51 -0
- package/dist/components/chat/utils/nav-click-handler.d.ts.map +1 -0
- package/dist/components/chat/utils/scroll-anchor.d.ts +30 -0
- package/dist/components/chat/utils/scroll-anchor.d.ts.map +1 -0
- package/dist/components/chat/utils/slash-dispatch-utils.d.ts +109 -0
- package/dist/components/chat/utils/slash-dispatch-utils.d.ts.map +1 -0
- package/dist/components/chat/utils/source-icons.d.ts +8 -0
- package/dist/components/chat/utils/source-icons.d.ts.map +1 -0
- package/dist/components/chat/utils/source-row-cta.d.ts +111 -0
- package/dist/components/chat/utils/source-row-cta.d.ts.map +1 -0
- package/dist/components/features/figma-prototype-viewer.d.ts.map +1 -1
- package/dist/components/features/index.cjs +12 -6
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +11 -5
- package/dist/components/features/video.d.ts.map +1 -1
- package/dist/components/icons/index.cjs +3 -3
- package/dist/components/icons/index.js +2 -2
- package/dist/components/index.cjs +274 -8
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +273 -7
- package/dist/components/interactive-wrapper.d.ts +3 -3
- package/dist/components/navigation/index.cjs +12 -6
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.js +11 -5
- package/dist/components/resizable.d.ts +1 -1
- package/dist/components/shared/product-release/product-release-card-skeleton.d.ts +1 -1
- package/dist/components/shared/product-release/product-release-card-skeleton.d.ts.map +1 -1
- package/dist/components/shared/product-release/product-release-card.d.ts +19 -12
- package/dist/components/shared/product-release/product-release-card.d.ts.map +1 -1
- package/dist/components/shared/product-release/release-detail-page.d.ts +2 -4
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/ui/button/button.d.ts +13 -0
- package/dist/components/ui/button/button.d.ts.map +1 -1
- package/dist/components/ui/dashboard-info-card.d.ts.map +1 -1
- package/dist/components/ui/entity-image.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +71 -70
- package/dist/components/ui/file-manager/index.cjs.map +1 -1
- package/dist/components/ui/file-manager/index.js +6 -5
- package/dist/components/ui/file-manager/index.js.map +1 -1
- package/dist/components/ui/hover-dropdown.d.ts +66 -0
- package/dist/components/ui/hover-dropdown.d.ts.map +1 -0
- package/dist/components/ui/index.cjs +276 -6
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +278 -8
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
- package/dist/components/ui/square-avatar.d.ts.map +1 -1
- package/dist/contexts/chat-runtime-context.d.ts +109 -0
- package/dist/contexts/chat-runtime-context.d.ts.map +1 -0
- package/dist/contexts/endpoints-runtime-context.d.ts +28 -0
- package/dist/contexts/endpoints-runtime-context.d.ts.map +1 -0
- package/dist/contexts/index.cjs +30 -0
- package/dist/contexts/index.cjs.map +1 -0
- package/dist/contexts/index.d.ts +26 -0
- package/dist/contexts/index.d.ts.map +1 -0
- package/dist/contexts/index.js +30 -0
- package/dist/contexts/index.js.map +1 -0
- package/dist/contexts/use-outer-or-default.d.ts +29 -0
- package/dist/contexts/use-outer-or-default.d.ts.map +1 -0
- package/dist/embed-shims/index.cjs +51 -0
- package/dist/embed-shims/index.cjs.map +1 -0
- package/dist/embed-shims/index.d.ts +31 -0
- package/dist/embed-shims/index.d.ts.map +1 -0
- package/dist/embed-shims/index.js +51 -0
- package/dist/embed-shims/index.js.map +1 -0
- package/dist/embed-shims/next-dynamic.cjs +12 -0
- package/dist/embed-shims/next-dynamic.cjs.map +1 -0
- package/dist/embed-shims/next-dynamic.d.ts +47 -0
- package/dist/embed-shims/next-dynamic.d.ts.map +1 -0
- package/dist/embed-shims/next-dynamic.js +12 -0
- package/dist/embed-shims/next-dynamic.js.map +1 -0
- package/dist/embed-shims/next-image.cjs +12 -0
- package/dist/embed-shims/next-image.cjs.map +1 -0
- package/dist/embed-shims/next-image.d.ts +28 -0
- package/dist/embed-shims/next-image.d.ts.map +1 -0
- package/dist/embed-shims/next-image.js +12 -0
- package/dist/embed-shims/next-image.js.map +1 -0
- package/dist/embed-shims/next-link.cjs +14 -0
- package/dist/embed-shims/next-link.cjs.map +1 -0
- package/dist/embed-shims/next-link.d.ts +22 -0
- package/dist/embed-shims/next-link.d.ts.map +1 -0
- package/dist/embed-shims/next-link.js +14 -0
- package/dist/embed-shims/next-link.js.map +1 -0
- package/dist/embed-shims/next-navigation.cjs +30 -0
- package/dist/embed-shims/next-navigation.cjs.map +1 -0
- package/dist/embed-shims/next-navigation.d.ts +46 -0
- package/dist/embed-shims/next-navigation.d.ts.map +1 -0
- package/dist/embed-shims/next-navigation.js +30 -0
- package/dist/embed-shims/next-navigation.js.map +1 -0
- package/dist/hooks/index.cjs +10 -4
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +9 -3
- package/dist/hooks/use-access-code-integration.d.ts +48 -0
- package/dist/hooks/use-access-code-integration.d.ts.map +1 -0
- package/dist/hooks/use-contact-submission.d.ts.map +1 -1
- package/dist/hooks/use-og-placeholder.d.ts +31 -0
- package/dist/hooks/use-og-placeholder.d.ts.map +1 -0
- package/dist/hooks/use-toast.d.ts +1 -1
- package/dist/index.cjs +367 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +378 -18
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/access-code-client.d.ts +21 -37
- package/dist/utils/access-code-client.d.ts.map +1 -1
- package/dist/utils/cn.d.ts +0 -27
- package/dist/utils/cn.d.ts.map +1 -1
- package/dist/utils/color-analysis.d.ts +33 -0
- package/dist/utils/color-analysis.d.ts.map +1 -0
- package/dist/utils/date-formatters.d.ts +16 -5
- package/dist/utils/date-formatters.d.ts.map +1 -1
- package/dist/utils/fetch-priority.d.ts +3 -0
- package/dist/utils/fetch-priority.d.ts.map +1 -0
- package/dist/utils/format.d.ts +192 -1
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/image-proxy.d.ts +67 -2
- package/dist/utils/image-proxy.d.ts.map +1 -1
- package/dist/utils/index.cjs +1274 -155
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +19 -3
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1200 -157
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/local-storage-adapter.d.ts +46 -0
- package/dist/utils/local-storage-adapter.d.ts.map +1 -0
- package/dist/utils/source-icons.d.ts +78 -0
- package/dist/utils/source-icons.d.ts.map +1 -0
- package/package.json +29 -2
- package/src/components/announcement-bar.tsx +26 -4
- package/src/components/categories-cart.tsx +1 -1
- package/src/components/chat/chat-attachment-bar.tsx +323 -0
- package/src/components/chat/chat-container.tsx +39 -5
- package/src/components/chat/chat-input.tsx +7 -1
- package/src/components/chat/chat-message-enhanced.tsx +32 -22
- package/src/components/chat/chat-message-list.tsx +53 -4
- package/src/components/chat/chat-panel-context.tsx +37 -0
- package/src/components/chat/chat-ticket-list.tsx +1 -1
- package/src/components/chat/embeddable-chat.tsx +1106 -0
- package/src/components/chat/entity-cards/admin-content-card.tsx +155 -0
- package/src/components/chat/entity-cards/blog-card.tsx +259 -0
- package/src/components/chat/entity-cards/blog-image-placeholder.tsx +52 -0
- package/src/components/chat/entity-cards/campaign-card-admin.tsx +113 -0
- package/src/components/chat/entity-cards/case-study-card.tsx +192 -0
- package/src/components/chat/{chat-ticket-item.tsx → entity-cards/chat-ticket-item.tsx} +2 -2
- package/src/components/chat/{chat-video-entity-card.tsx → entity-cards/chat-video-entity-card.tsx} +2 -2
- package/src/components/chat/entity-cards/customer-interview-card.tsx +211 -0
- package/src/components/chat/entity-cards/data-room-doc-card.tsx +120 -0
- package/src/components/chat/entity-cards/dispatch.tsx +1093 -0
- package/src/components/chat/entity-cards/entity-author-card.tsx +193 -0
- package/src/components/chat/entity-cards/generic-entity-card.tsx +144 -0
- package/src/components/chat/entity-cards/github-activity-card.tsx +305 -0
- package/src/components/chat/entity-cards/hubspot-ticket-card.tsx +205 -0
- package/src/components/chat/entity-cards/index.ts +125 -0
- package/src/components/chat/entity-cards/investor-update-card.tsx +150 -0
- package/src/components/chat/entity-cards/onboarding-guide-card.tsx +326 -0
- package/src/components/chat/entity-cards/product-release-card-defaults.ts +57 -0
- package/src/components/chat/entity-cards/product-release-card.tsx +19 -0
- package/src/components/chat/entity-cards/program-card-defaults.ts +62 -0
- package/src/components/chat/entity-cards/program-card.tsx +451 -0
- package/src/components/chat/entity-cards/roadmap-card.tsx +356 -0
- package/src/components/chat/entity-cards/roadmap-vote-button.tsx +54 -0
- package/src/components/chat/entity-cards/slack-message-card.tsx +182 -0
- package/src/components/chat/entity-cards/task-type-icon.tsx +60 -0
- package/src/components/chat/hooks/index.ts +22 -0
- package/src/components/chat/hooks/use-chat-attachment-image-gallery.tsx +114 -0
- package/src/components/chat/hooks/use-chat-attachments.ts +429 -0
- package/src/components/chat/hooks/use-chat-card-item.ts +102 -0
- package/src/components/chat/hooks/use-chat-identity.ts +139 -0
- package/src/components/chat/hooks/use-chat.ts +501 -0
- package/src/components/chat/hooks/use-close-on-navigation.ts +87 -0
- package/src/components/chat/hooks/use-embedded-chat.ts +1023 -0
- package/src/components/chat/hooks/use-proxied-image-url.ts +31 -0
- package/src/components/chat/hooks/use-slash-commands.ts +106 -0
- package/src/components/chat/hooks/use-sse.ts +143 -0
- package/src/components/chat/index.ts +30 -4
- package/src/components/chat/nav-link-anchor-via-runtime.tsx +72 -0
- package/src/components/chat/source-action-button.tsx +120 -0
- package/src/components/chat/types/chat.types.ts +61 -0
- package/src/components/chat/types/component.types.ts +57 -11
- package/src/components/chat/types/entities/blog.ts +27 -0
- package/src/components/chat/types/entities/case-study.ts +14 -0
- package/src/components/chat/types/entities/content-ref.ts +23 -0
- package/src/components/chat/types/entities/customer-interview.ts +15 -0
- package/src/components/chat/types/entities/data-room-doc.ts +37 -0
- package/src/components/chat/types/entities/github-activity.ts +36 -0
- package/src/components/chat/types/entities/hubspot-ticket.ts +39 -0
- package/src/components/chat/types/entities/index.ts +28 -0
- package/src/components/chat/types/entities/investor-update.ts +100 -0
- package/src/components/chat/types/entities/onboarding-guide.ts +101 -0
- package/src/components/chat/types/entities/program-types.ts +433 -0
- package/src/components/chat/types/entities/roadmap-item.ts +42 -0
- package/src/components/chat/types/entities/slack-message.ts +28 -0
- package/src/components/chat/types/index.ts +1 -0
- package/src/components/chat/utils/agent-status-message.ts +52 -0
- package/src/components/chat/utils/auto-continuation-directive.ts +70 -0
- package/src/components/chat/utils/chat-attachment-markdown.ts +190 -0
- package/src/components/chat/utils/chat-authed-fetch.ts +73 -0
- package/src/components/chat/utils/chat-nav-resolution.ts +151 -0
- package/src/components/chat/utils/chat-proxy-auth-storage.ts +148 -0
- package/src/components/chat/utils/chip-action-class.ts +19 -0
- package/src/components/chat/utils/chip-styles.ts +51 -0
- package/src/components/chat/utils/clickup-task-type-utils.ts +59 -0
- package/src/components/chat/utils/compact-card-classes.ts +97 -0
- package/src/components/chat/utils/decide-new-tab.ts +57 -0
- package/src/components/chat/utils/external-app-urls.ts +19 -0
- package/src/components/chat/utils/flatten-assistant-content.ts +35 -0
- package/src/components/chat/utils/icon-registry.ts +297 -0
- package/src/components/chat/utils/index.ts +133 -0
- package/src/components/chat/utils/is-cross-origin-url.ts +28 -0
- package/src/components/chat/utils/nav-anchor-props.ts +78 -0
- package/src/components/chat/utils/nav-click-handler.ts +81 -0
- package/src/components/chat/utils/scroll-anchor.ts +35 -0
- package/src/components/chat/utils/slash-dispatch-utils.ts +183 -0
- package/src/components/chat/utils/source-icons.ts +14 -0
- package/src/components/chat/utils/source-row-cta.ts +215 -0
- package/src/components/empty-state.tsx +1 -1
- package/src/components/features/board/ticket-card.tsx +1 -1
- package/src/components/features/figma-prototype-viewer.tsx +2 -1
- package/src/components/features/media-gallery-manager.tsx +1 -1
- package/src/components/features/parallax-image-showcase.tsx +1 -1
- package/src/components/features/release-media-manager.tsx +1 -1
- package/src/components/features/seo-editor-preview.tsx +1 -1
- package/src/components/features/video.tsx +54 -3
- package/src/components/footer-waitlist-button.tsx +1 -1
- package/src/components/navigation/header.tsx +1 -1
- package/src/components/shared/onboarding/onboarding-step-card.tsx +1 -1
- package/src/components/shared/product-release/product-release-card-skeleton.tsx +8 -44
- package/src/components/shared/product-release/product-release-card.tsx +31 -116
- package/src/components/shared/product-release/release-detail-page.tsx +12 -16
- package/src/components/ui/actions-menu.tsx +1 -1
- package/src/components/ui/button/button.tsx +41 -11
- package/src/components/ui/button/split-button.tsx +1 -1
- package/src/components/ui/dashboard-info-card.tsx +2 -3
- package/src/components/ui/data-table/data-table-row.tsx +1 -1
- package/src/components/ui/entity-image.tsx +2 -8
- package/src/components/ui/hover-dropdown.tsx +258 -0
- package/src/components/ui/image-gallery-modal.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/markdown-editor.tsx +1 -1
- package/src/components/ui/more-actions-menu.tsx +1 -1
- package/src/components/ui/organization-card.tsx +1 -1
- package/src/components/ui/simple-markdown-renderer.tsx +53 -5
- package/src/components/ui/square-avatar.tsx +3 -12
- package/src/components/ui/tab-navigation.tsx +1 -1
- package/src/components/ui/table/table-row.tsx +1 -1
- package/src/components/unified-filter-logic.tsx +1 -1
- package/src/components/unified-pagination.tsx +1 -1
- package/src/components/user-summary-stub.tsx +1 -1
- package/src/components/vendor-display-button.tsx +1 -1
- package/src/components/vendor-icon.tsx +1 -1
- package/src/contexts/chat-runtime-context.tsx +163 -0
- package/src/contexts/endpoints-runtime-context.tsx +68 -0
- package/src/contexts/index.ts +38 -0
- package/src/contexts/use-outer-or-default.ts +42 -0
- package/src/embed-shims/index.ts +42 -0
- package/src/embed-shims/next-dynamic.tsx +70 -0
- package/src/embed-shims/next-image.tsx +114 -0
- package/src/embed-shims/next-link.tsx +91 -0
- package/src/embed-shims/next-navigation.tsx +201 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/state/use-api-params.ts +1 -1
- package/src/hooks/state/use-query-params.ts +1 -1
- package/src/hooks/use-access-code-integration.ts +107 -0
- package/src/hooks/use-contact-submission.ts +7 -3
- package/src/hooks/use-og-placeholder.ts +45 -0
- package/src/stories/OnboardingStepCard.stories.tsx +140 -0
- package/src/styles/chat-animations.css +65 -0
- package/src/styles/index.css +1 -0
- package/src/utils/access-code-client.ts +32 -75
- package/src/utils/cn.ts +0 -65
- package/src/utils/color-analysis.ts +205 -0
- package/src/utils/date-formatters.ts +54 -11
- package/src/utils/fetch-priority.ts +41 -0
- package/src/utils/format.ts +525 -1
- package/src/utils/image-proxy.ts +127 -7
- package/src/utils/index.ts +145 -5
- package/src/utils/local-storage-adapter.ts +105 -0
- package/src/utils/source-icons.ts +219 -0
- package/dist/chunk-25LVV26X.cjs.map +0 -1
- package/dist/chunk-ARQ4XP64.cjs.map +0 -1
- package/dist/chunk-CPXLQ57U.js.map +0 -1
- package/dist/chunk-LY34ORX6.js.map +0 -1
- package/dist/chunk-RMB5DVED.cjs.map +0 -1
- package/dist/chunk-SZPJ5R5B.js.map +0 -1
- package/dist/chunk-UC43NICZ.cjs.map +0 -1
- package/dist/chunk-XGL5FKIK.js.map +0 -1
- package/dist/components/chat/block-card.d.ts.map +0 -1
- package/dist/components/chat/chat-ticket-item.d.ts.map +0 -1
- package/dist/components/chat/chat-video-entity-card.d.ts.map +0 -1
- package/dist/utils/dynamic-icons.d.ts +0 -26
- package/dist/utils/dynamic-icons.d.ts.map +0 -1
- package/dist/utils/format-relative-time.d.ts +0 -21
- package/dist/utils/format-relative-time.d.ts.map +0 -1
- package/src/utils/.dynamic-icons.md +0 -30
- package/src/utils/.format-relative-time.md +0 -36
- package/src/utils/dynamic-icons.tsx +0 -120
- package/src/utils/format-relative-time.ts +0 -52
- /package/dist/{chunk-N57KWHDB.js.map → chunk-CIPO6DXK.js.map} +0 -0
- /package/dist/components/chat/{block-card.d.ts → entity-cards/block-card.d.ts} +0 -0
- /package/dist/components/chat/{chat-ticket-item.d.ts → entity-cards/chat-ticket-item.d.ts} +0 -0
- /package/src/components/chat/{block-card.tsx → entity-cards/block-card.tsx} +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Click-to-expand handler for chat-attachment images in the chat thread.
|
|
5
|
+
*
|
|
6
|
+
* Returns `{ panelRef, modal }`:
|
|
7
|
+
* - `panelRef` — attach to the chat content wrapper. The hook
|
|
8
|
+
* installs a delegated `click` listener on this ref.
|
|
9
|
+
* - `modal` — render as a sibling of the chat panel (so it lives
|
|
10
|
+
* OUTSIDE any overflow-hidden message bubbles). Returns `null`
|
|
11
|
+
* while closed.
|
|
12
|
+
*
|
|
13
|
+
* Behavior on click:
|
|
14
|
+
* 1. Filter for `<img>` whose `src` starts with the runtime's
|
|
15
|
+
* `attachmentViewUrlPrefix` (so non-chat images don't intercept).
|
|
16
|
+
* 2. Walk up to the nearest `[data-message-role]` ancestor — the
|
|
17
|
+
* boundary the chat-shell wraps each message bubble with.
|
|
18
|
+
* 3. Collect ALL chat-attachment `<img>`s inside that boundary in
|
|
19
|
+
* DOM order. Compute the clicked index for `initialIndex`.
|
|
20
|
+
* 4. Open `ImageGalleryModal` (UI-Kit, also used by
|
|
21
|
+
* `roadmap-card.tsx` for screenshot expansion).
|
|
22
|
+
*
|
|
23
|
+
* The hook deliberately owns the click DOM-walk + modal state so
|
|
24
|
+
* the host component consumes a single `{panelRef, modal}` API.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
28
|
+
import { ImageGalleryModal } from '../../ui/image-gallery-modal'
|
|
29
|
+
import { useRequiredChatRuntime } from '../../../contexts/chat-runtime-context'
|
|
30
|
+
|
|
31
|
+
interface GalleryState {
|
|
32
|
+
isOpen: boolean
|
|
33
|
+
images: string[]
|
|
34
|
+
initialIndex: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const CLOSED: GalleryState = { isOpen: false, images: [], initialIndex: 0 }
|
|
38
|
+
|
|
39
|
+
export function useChatAttachmentImageGallery() {
|
|
40
|
+
const runtime = useRequiredChatRuntime()
|
|
41
|
+
const viewUrlPrefix = runtime.endpoints.attachmentViewUrlPrefix
|
|
42
|
+
const panelRef = useRef<HTMLDivElement | null>(null)
|
|
43
|
+
const [state, setState] = useState<GalleryState>(CLOSED)
|
|
44
|
+
|
|
45
|
+
const handleClose = useCallback(() => setState(CLOSED), [])
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const root = panelRef.current
|
|
49
|
+
if (!root) return
|
|
50
|
+
|
|
51
|
+
const onClick = (ev: Event) => {
|
|
52
|
+
const target = ev.target as HTMLElement | null
|
|
53
|
+
if (!target) return
|
|
54
|
+
// Match an <img> whose `src` starts with the proxy URL prefix.
|
|
55
|
+
const img = target.closest('img') as HTMLImageElement | null
|
|
56
|
+
if (!img || !img.src.startsWith(absolutize(viewUrlPrefix))) return
|
|
57
|
+
|
|
58
|
+
// Walk up to the message-bubble boundary.
|
|
59
|
+
const bubble = img.closest('[data-message-role]')
|
|
60
|
+
if (!bubble) {
|
|
61
|
+
// Fallback: open the modal with just the clicked image.
|
|
62
|
+
ev.preventDefault()
|
|
63
|
+
setState({ isOpen: true, images: [img.src], initialIndex: 0 })
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Collect all chat-attachment images inside this bubble in DOM
|
|
68
|
+
// order. The `closest` lookup matched img.src.startsWith already,
|
|
69
|
+
// but we re-check below because `querySelectorAll` returns ALL
|
|
70
|
+
// <img>s in the bubble (avatar images etc.).
|
|
71
|
+
const allImgs = Array.from(bubble.querySelectorAll('img')) as HTMLImageElement[]
|
|
72
|
+
const matching = allImgs.filter((el) => el.src.startsWith(absolutize(viewUrlPrefix)))
|
|
73
|
+
if (matching.length === 0) return
|
|
74
|
+
|
|
75
|
+
const idx = Math.max(0, matching.indexOf(img))
|
|
76
|
+
ev.preventDefault()
|
|
77
|
+
setState({
|
|
78
|
+
isOpen: true,
|
|
79
|
+
images: matching.map((el) => el.src),
|
|
80
|
+
initialIndex: idx,
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
root.addEventListener('click', onClick)
|
|
85
|
+
return () => root.removeEventListener('click', onClick)
|
|
86
|
+
}, [viewUrlPrefix])
|
|
87
|
+
|
|
88
|
+
// `ImageGalleryModal` doesn't accept `null` for `images` — render
|
|
89
|
+
// it only when open. Closing returns `null` so the modal unmounts
|
|
90
|
+
// and React garbage-collects any blob URLs it might hold.
|
|
91
|
+
const modal = state.isOpen ? (
|
|
92
|
+
<ImageGalleryModal
|
|
93
|
+
images={state.images}
|
|
94
|
+
isOpen={state.isOpen}
|
|
95
|
+
onClose={handleClose}
|
|
96
|
+
initialIndex={state.initialIndex}
|
|
97
|
+
/>
|
|
98
|
+
) : null
|
|
99
|
+
|
|
100
|
+
return { panelRef, modal }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Absolutize a prefix for comparison against `<img>.src`. Browsers
|
|
104
|
+
* resolve `<img src="/api/storage/view/...">` to an absolute URL when
|
|
105
|
+
* reading `.src` — we need the same shape on the prefix to match.
|
|
106
|
+
*
|
|
107
|
+
* Embedded apps that supply an absolute prefix already work as-is
|
|
108
|
+
* (the prefix matches `.src` directly). For relative prefixes (host
|
|
109
|
+
* mode), prefix with `window.location.origin`. */
|
|
110
|
+
function absolutize(prefix: string): string {
|
|
111
|
+
if (prefix.startsWith('http://') || prefix.startsWith('https://')) return prefix
|
|
112
|
+
if (typeof window === 'undefined') return prefix
|
|
113
|
+
return `${window.location.origin}${prefix.startsWith('/') ? '' : '/'}${prefix}`
|
|
114
|
+
}
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Chat-attachment upload state machine.
|
|
5
|
+
*
|
|
6
|
+
* Multi-file state (precedent: hub `components/blog/image-upload-manager.tsx`).
|
|
7
|
+
* Magic-byte sniff via `file-type` BEFORE upload (rejects extension
|
|
8
|
+
* spoofs like `evil.exe` renamed `screenshot.png`). Per-attachment
|
|
9
|
+
* `AbortController` for clean remove-mid-upload.
|
|
10
|
+
*
|
|
11
|
+
* Auth propagation:
|
|
12
|
+
* - `chatAuthedFetch` carries bearer-act-as headers + Supabase
|
|
13
|
+
* session cookies — used both for minting the upload URL and as
|
|
14
|
+
* the auth path before falling through to the signed PUT.
|
|
15
|
+
* - The byte-PUT to the signed URL uses an inline `uploadWithProgress`
|
|
16
|
+
* XHR helper (ported from the hub `unified-upload-service`) so the
|
|
17
|
+
* lib-side chat hook is self-contained.
|
|
18
|
+
*
|
|
19
|
+
* URL endpoint: `runtime.endpoints.attachmentUploadUrl` (hub default
|
|
20
|
+
* `/api/storage/generate-upload-url`). Embedded apps override.
|
|
21
|
+
*
|
|
22
|
+
* Dedup: files keyed by `(name, size, lastModified)` — second pick of
|
|
23
|
+
* the same file is silently ignored.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { useCallback, useMemo, useRef, useState } from 'react'
|
|
27
|
+
import { fileTypeFromBlob } from 'file-type'
|
|
28
|
+
import { useRequiredChatRuntime } from '../../../contexts/chat-runtime-context'
|
|
29
|
+
import { chatAuthedFetch } from '../utils/chat-authed-fetch'
|
|
30
|
+
import {
|
|
31
|
+
CHAT_ATTACHMENT_MIME_TYPES,
|
|
32
|
+
CHAT_ATTACHMENT_CONCURRENT_UPLOADS_PER_USER,
|
|
33
|
+
} from '../chat-attachment-bar'
|
|
34
|
+
import type { ChatAttachment } from '../utils/chat-attachment-markdown'
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Inlined hub-side constants (single source of truth — `lib/config/
|
|
38
|
+
// chat-attachment-config.ts` on the hub). Hard-coded here so the lib
|
|
39
|
+
// hook is self-contained; if hub-side bumps the cap, update both.
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
const CHAT_ATTACHMENTS_BUCKET = 'chat-attachments'
|
|
43
|
+
const CHAT_ATTACHMENTS_FOLDER = 'chat'
|
|
44
|
+
const CHAT_ATTACHMENT_MAX_SIZE_BYTES = 25 * 1024 * 1024
|
|
45
|
+
|
|
46
|
+
/** Cache-Control header for content-addressed uploads — Storage paths
|
|
47
|
+
* embed `{timestamp}-{random}-{filename}` so a given URL is immutable. */
|
|
48
|
+
const STORAGE_CACHE_CONTROL_IMMUTABLE = 'public, max-age=31536000, immutable'
|
|
49
|
+
|
|
50
|
+
type Status = 'sniffing' | 'uploading' | 'ready' | 'error'
|
|
51
|
+
|
|
52
|
+
/** Internal staged-attachment shape (more fields than the wire
|
|
53
|
+
* `ChatAttachment` — carries state-machine metadata). */
|
|
54
|
+
export interface StagedAttachment {
|
|
55
|
+
/** Stable client-side id; survives across re-renders. */
|
|
56
|
+
id: string
|
|
57
|
+
file: File
|
|
58
|
+
status: Status
|
|
59
|
+
/** Set when `status === 'ready'`. Server-issued. */
|
|
60
|
+
storagePath?: string
|
|
61
|
+
/** Set when `status === 'ready'`. Server-issued HMAC view token. */
|
|
62
|
+
viewToken?: string
|
|
63
|
+
/** 0-100 during 'uploading'. */
|
|
64
|
+
progress: number
|
|
65
|
+
/** Set when `status === 'error'`. */
|
|
66
|
+
errorMessage?: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Response shape from `/api/storage/generate-upload-url`. */
|
|
70
|
+
interface UploadUrlResponse {
|
|
71
|
+
uploadUrl: string
|
|
72
|
+
filePath: string
|
|
73
|
+
token: string
|
|
74
|
+
bucket: string
|
|
75
|
+
publicUrl: string | null
|
|
76
|
+
viewToken: string | null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface UseChatAttachmentsApi {
|
|
80
|
+
attachments: StagedAttachment[]
|
|
81
|
+
/** All staged files in 'ready' state, projected to the wire
|
|
82
|
+
* `ChatAttachment` shape — pass to `sendMessage`'s `pendingAttachments`. */
|
|
83
|
+
readyAttachments: ChatAttachment[]
|
|
84
|
+
/** True iff any attachment is mid-upload — Send button reads this
|
|
85
|
+
* to disable itself. */
|
|
86
|
+
hasInflightUploads: boolean
|
|
87
|
+
addFiles: (files: FileList | File[]) => void
|
|
88
|
+
removeAttachment: (id: string) => void
|
|
89
|
+
clear: () => void
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Inline `uploadWithProgress` — XHR PUT to a Supabase signed-upload URL
|
|
94
|
+
* with progress + abort support. Ported verbatim from the hub
|
|
95
|
+
* `unified-upload-service.ts`. Maps the XHR 0-100 to the caller's 10-95
|
|
96
|
+
* range (leaving 0-10 for the mint + 95-100 for state transitions).
|
|
97
|
+
*
|
|
98
|
+
* AbortSignal wiring: `signal?.addEventListener('abort', () => xhr.abort())`
|
|
99
|
+
* — without this, calling `removeAttachment`/`clear()` aborts the
|
|
100
|
+
* controller but the XHR runs to completion and bytes still land in
|
|
101
|
+
* Supabase.
|
|
102
|
+
*/
|
|
103
|
+
function uploadWithProgress(
|
|
104
|
+
url: string,
|
|
105
|
+
file: File,
|
|
106
|
+
token: string,
|
|
107
|
+
onProgress?: (progress: number) => void,
|
|
108
|
+
signal?: AbortSignal,
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
if (signal?.aborted) {
|
|
112
|
+
reject(new Error('Upload cancelled'))
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
const xhr = new XMLHttpRequest()
|
|
116
|
+
let isCompleted = false
|
|
117
|
+
|
|
118
|
+
const cleanup = () => {
|
|
119
|
+
if (isCompleted) return
|
|
120
|
+
isCompleted = true
|
|
121
|
+
xhr.upload.removeEventListener('progress', handleProgress)
|
|
122
|
+
xhr.removeEventListener('load', handleLoad)
|
|
123
|
+
xhr.removeEventListener('error', handleError)
|
|
124
|
+
xhr.removeEventListener('abort', handleAbort)
|
|
125
|
+
xhr.removeEventListener('timeout', handleTimeout)
|
|
126
|
+
signal?.removeEventListener('abort', handleSignalAbort)
|
|
127
|
+
if (xhr.readyState !== XMLHttpRequest.DONE) xhr.abort()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const handleProgress = (event: ProgressEvent) => {
|
|
131
|
+
if (event.lengthComputable && onProgress) {
|
|
132
|
+
// Map 0-100 of network progress to 10-95.
|
|
133
|
+
const pct = 10 + (event.loaded / event.total) * 85
|
|
134
|
+
onProgress(Math.round(pct))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const handleLoad = () => {
|
|
139
|
+
cleanup()
|
|
140
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
141
|
+
resolve()
|
|
142
|
+
} else {
|
|
143
|
+
reject(
|
|
144
|
+
new Error(
|
|
145
|
+
`Upload failed with status ${xhr.status}: ${xhr.responseText || xhr.statusText}`,
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const handleError = () => {
|
|
152
|
+
cleanup()
|
|
153
|
+
reject(new Error('Network error during upload'))
|
|
154
|
+
}
|
|
155
|
+
const handleAbort = () => {
|
|
156
|
+
cleanup()
|
|
157
|
+
reject(new Error('Upload cancelled'))
|
|
158
|
+
}
|
|
159
|
+
const handleTimeout = () => {
|
|
160
|
+
cleanup()
|
|
161
|
+
reject(new Error('Upload timed out'))
|
|
162
|
+
}
|
|
163
|
+
const handleSignalAbort = () => {
|
|
164
|
+
xhr.abort()
|
|
165
|
+
}
|
|
166
|
+
signal?.addEventListener('abort', handleSignalAbort, { once: true })
|
|
167
|
+
|
|
168
|
+
xhr.upload.addEventListener('progress', handleProgress)
|
|
169
|
+
xhr.addEventListener('load', handleLoad)
|
|
170
|
+
xhr.addEventListener('error', handleError)
|
|
171
|
+
xhr.addEventListener('abort', handleAbort)
|
|
172
|
+
xhr.addEventListener('timeout', handleTimeout)
|
|
173
|
+
|
|
174
|
+
xhr.open('PUT', url)
|
|
175
|
+
xhr.setRequestHeader('Authorization', `Bearer ${token}`)
|
|
176
|
+
// `x-upsert: true` — signed upload tokens can target the same path
|
|
177
|
+
// more than once on retry; without this header, Storage returns 409
|
|
178
|
+
// on the second attempt instead of overwriting.
|
|
179
|
+
xhr.setRequestHeader('x-upsert', 'true')
|
|
180
|
+
xhr.setRequestHeader('Content-Type', file.type)
|
|
181
|
+
xhr.setRequestHeader('cache-control', STORAGE_CACHE_CONTROL_IMMUTABLE)
|
|
182
|
+
xhr.timeout = 30 * 60 * 1000
|
|
183
|
+
xhr.send(file)
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function useChatAttachments(): UseChatAttachmentsApi {
|
|
188
|
+
const runtime = useRequiredChatRuntime()
|
|
189
|
+
const uploadUrlEndpoint = runtime.endpoints.attachmentUploadUrl
|
|
190
|
+
|
|
191
|
+
const [attachments, setAttachments] = useState<StagedAttachment[]>([])
|
|
192
|
+
// AbortControllers indexed by attachment id. Survives re-renders.
|
|
193
|
+
const controllersRef = useRef<Map<string, AbortController>>(new Map())
|
|
194
|
+
|
|
195
|
+
const updateOne = useCallback((id: string, patch: Partial<StagedAttachment>) => {
|
|
196
|
+
setAttachments((prev) => prev.map((a) => (a.id === id ? { ...a, ...patch } : a)))
|
|
197
|
+
}, [])
|
|
198
|
+
|
|
199
|
+
const removeAttachment = useCallback((id: string) => {
|
|
200
|
+
// Abort any in-flight work for this attachment.
|
|
201
|
+
const ctrl = controllersRef.current.get(id)
|
|
202
|
+
if (ctrl) {
|
|
203
|
+
ctrl.abort()
|
|
204
|
+
controllersRef.current.delete(id)
|
|
205
|
+
}
|
|
206
|
+
setAttachments((prev) => prev.filter((a) => a.id !== id))
|
|
207
|
+
}, [])
|
|
208
|
+
|
|
209
|
+
const clear = useCallback(() => {
|
|
210
|
+
for (const ctrl of controllersRef.current.values()) ctrl.abort()
|
|
211
|
+
controllersRef.current.clear()
|
|
212
|
+
setAttachments([])
|
|
213
|
+
}, [])
|
|
214
|
+
|
|
215
|
+
const uploadOne = useCallback(
|
|
216
|
+
async (att: StagedAttachment) => {
|
|
217
|
+
const ctrl = controllersRef.current.get(att.id)
|
|
218
|
+
try {
|
|
219
|
+
// 1. Magic-byte sniff. The `file` ARG is a Blob; `fileTypeFromBlob`
|
|
220
|
+
// reads the first few KB. Rejects extension spoofs.
|
|
221
|
+
let sniffed: { mime: string } | undefined
|
|
222
|
+
try {
|
|
223
|
+
sniffed = await fileTypeFromBlob(att.file)
|
|
224
|
+
} catch (err) {
|
|
225
|
+
if (ctrl?.signal.aborted) return
|
|
226
|
+
throw new Error(
|
|
227
|
+
`Could not read file content: ${err instanceof Error ? err.message : String(err)}`,
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
if (ctrl?.signal.aborted) return
|
|
231
|
+
if (!sniffed) {
|
|
232
|
+
throw new Error('Unrecognized file format')
|
|
233
|
+
}
|
|
234
|
+
const actualMime = sniffed.mime
|
|
235
|
+
if (!(CHAT_ATTACHMENT_MIME_TYPES as readonly string[]).includes(actualMime)) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`File type "${actualMime}" is not allowed. ` +
|
|
238
|
+
`Allowed: ${CHAT_ATTACHMENT_MIME_TYPES.join(', ')}`,
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 2. Move to 'uploading' and POST the URL-mint request with
|
|
243
|
+
// bearer-act-as headers (if present).
|
|
244
|
+
updateOne(att.id, { status: 'uploading', progress: 5 })
|
|
245
|
+
|
|
246
|
+
const urlResp = await chatAuthedFetch(uploadUrlEndpoint, {
|
|
247
|
+
method: 'POST',
|
|
248
|
+
headers: { 'Content-Type': 'application/json' },
|
|
249
|
+
signal: ctrl?.signal,
|
|
250
|
+
body: JSON.stringify({
|
|
251
|
+
fileName: att.file.name,
|
|
252
|
+
// Pass the SNIFFED MIME (not `file.type`, which can be a
|
|
253
|
+
// user-supplied lie) so the server's MIME pre-validation
|
|
254
|
+
// matches reality.
|
|
255
|
+
fileType: actualMime,
|
|
256
|
+
bucket: CHAT_ATTACHMENTS_BUCKET,
|
|
257
|
+
folder: CHAT_ATTACHMENTS_FOLDER,
|
|
258
|
+
}),
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
if (!urlResp.ok) {
|
|
262
|
+
const errBody = await urlResp.json().catch(() => ({}))
|
|
263
|
+
throw new Error(errBody.error || `Failed to mint upload URL: ${urlResp.statusText}`)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const mintData = (await urlResp.json()) as UploadUrlResponse
|
|
267
|
+
if (!mintData.viewToken) {
|
|
268
|
+
throw new Error('Server did not return a viewToken for the chat-attachments bucket')
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 3. PUT the bytes via inline `uploadWithProgress`.
|
|
272
|
+
await uploadWithProgress(
|
|
273
|
+
mintData.uploadUrl,
|
|
274
|
+
att.file,
|
|
275
|
+
mintData.token,
|
|
276
|
+
(pct) => {
|
|
277
|
+
updateOne(att.id, { progress: pct })
|
|
278
|
+
},
|
|
279
|
+
ctrl?.signal,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
if (ctrl?.signal.aborted) return
|
|
283
|
+
|
|
284
|
+
// 4. Mark ready.
|
|
285
|
+
updateOne(att.id, {
|
|
286
|
+
status: 'ready',
|
|
287
|
+
progress: 100,
|
|
288
|
+
storagePath: mintData.filePath,
|
|
289
|
+
viewToken: mintData.viewToken,
|
|
290
|
+
})
|
|
291
|
+
} catch (err) {
|
|
292
|
+
if (ctrl?.signal.aborted) return
|
|
293
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
294
|
+
updateOne(att.id, { status: 'error', progress: 0, errorMessage: message })
|
|
295
|
+
} finally {
|
|
296
|
+
controllersRef.current.delete(att.id)
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
[uploadUrlEndpoint, updateOne],
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
// `attachmentsRef` mirrors the staged set for SYNCHRONOUS reads
|
|
303
|
+
// during `addFiles`. Two assignment sites keep it in lockstep:
|
|
304
|
+
// (a) `attachmentsRef.current = attachments` after every render —
|
|
305
|
+
// picks up state mutations from `updateOne` / `removeAttachment`
|
|
306
|
+
// / `clear`.
|
|
307
|
+
// (b) `attachmentsRef.current = [...attachmentsRef.current,
|
|
308
|
+
// ...additions]` INSIDE `addFiles` BEFORE `setAttachments`
|
|
309
|
+
// commits — guarantees a SECOND `addFiles` call in the same
|
|
310
|
+
// tick sees the first call's additions for dedup.
|
|
311
|
+
const attachmentsRef = useRef<StagedAttachment[]>([])
|
|
312
|
+
attachmentsRef.current = attachments
|
|
313
|
+
|
|
314
|
+
const addFiles = useCallback(
|
|
315
|
+
(files: FileList | File[]) => {
|
|
316
|
+
const arr = Array.from(files)
|
|
317
|
+
if (arr.length === 0) return
|
|
318
|
+
|
|
319
|
+
// Compute additions OUTSIDE the setState updater — the updater
|
|
320
|
+
// must be a pure function. React 18 StrictMode invokes updaters
|
|
321
|
+
// TWICE in dev; concurrent transitions can do the same in prod.
|
|
322
|
+
// If side effects (UUID gen, controllerRef.set, uploadOne) lived
|
|
323
|
+
// inside, each render double-fired the upload.
|
|
324
|
+
const prev = attachmentsRef.current
|
|
325
|
+
const seen = new Set(
|
|
326
|
+
prev.map((a) => `${a.file.name}|${a.file.size}|${a.file.lastModified}`),
|
|
327
|
+
)
|
|
328
|
+
const additions: StagedAttachment[] = []
|
|
329
|
+
// `error`-status chips don't count toward the cap — they're
|
|
330
|
+
// visual diagnostics, not active uploads. Without this filter
|
|
331
|
+
// a single oversized-file mistake would block the user from
|
|
332
|
+
// adding further files until they manually `×` the error chip.
|
|
333
|
+
const remainingSlots = Math.max(
|
|
334
|
+
0,
|
|
335
|
+
CHAT_ATTACHMENT_CONCURRENT_UPLOADS_PER_USER -
|
|
336
|
+
prev.filter((a) => a.status !== 'error').length,
|
|
337
|
+
)
|
|
338
|
+
for (const file of arr) {
|
|
339
|
+
if (additions.length >= remainingSlots) break
|
|
340
|
+
const key = `${file.name}|${file.size}|${file.lastModified}`
|
|
341
|
+
if (seen.has(key)) continue
|
|
342
|
+
|
|
343
|
+
// Size pre-check — server-side bucket policy is the
|
|
344
|
+
// authoritative gate, but rejecting here saves the user a
|
|
345
|
+
// round-trip on obviously-too-big files.
|
|
346
|
+
if (file.size > CHAT_ATTACHMENT_MAX_SIZE_BYTES) {
|
|
347
|
+
additions.push({
|
|
348
|
+
id: crypto.randomUUID(),
|
|
349
|
+
file,
|
|
350
|
+
status: 'error',
|
|
351
|
+
progress: 0,
|
|
352
|
+
errorMessage: `File too large (${(file.size / 1024 / 1024).toFixed(1)} MB; max 25 MB)`,
|
|
353
|
+
})
|
|
354
|
+
seen.add(key)
|
|
355
|
+
continue
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
additions.push({
|
|
359
|
+
id: crypto.randomUUID(),
|
|
360
|
+
file,
|
|
361
|
+
status: 'sniffing',
|
|
362
|
+
progress: 0,
|
|
363
|
+
})
|
|
364
|
+
seen.add(key)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (additions.length === 0) return
|
|
368
|
+
|
|
369
|
+
// SYNCHRONOUSLY update the ref BEFORE the React commit. Without
|
|
370
|
+
// this, a second `addFiles` call in the SAME tick (paste-then-drop
|
|
371
|
+
// browser event sequence; touch-then-mouse on iOS hybrid; double
|
|
372
|
+
// user clicks on the chip-strip's hidden file input) would read
|
|
373
|
+
// the same stale `attachmentsRef.current` and bypass dedup.
|
|
374
|
+
attachmentsRef.current = [...prev, ...additions]
|
|
375
|
+
|
|
376
|
+
// Register AbortControllers + kick off uploads BEFORE the state
|
|
377
|
+
// commit. The XHR doesn't depend on React state; `uploadOne`'s
|
|
378
|
+
// `updateOne` calls are queued behind this `setAttachments`,
|
|
379
|
+
// so by the time the first progress callback fires React has
|
|
380
|
+
// already rendered the new chips.
|
|
381
|
+
for (const a of additions) {
|
|
382
|
+
if (a.status === 'sniffing') {
|
|
383
|
+
const ctrl = new AbortController()
|
|
384
|
+
controllersRef.current.set(a.id, ctrl)
|
|
385
|
+
void uploadOne(a)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Pure updater — no side effects, idempotent under StrictMode
|
|
390
|
+
// double-invocation.
|
|
391
|
+
setAttachments((p) => [...p, ...additions])
|
|
392
|
+
},
|
|
393
|
+
[uploadOne],
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
// Memoized so `handleSend`'s `useCallback([readyAttachments, ...])`
|
|
397
|
+
// doesn't re-create the handler (and re-render `ChatInput`) on every
|
|
398
|
+
// unrelated re-render. Stable reference while the underlying
|
|
399
|
+
// attachments array is unchanged.
|
|
400
|
+
const readyAttachments: ChatAttachment[] = useMemo(
|
|
401
|
+
() =>
|
|
402
|
+
attachments
|
|
403
|
+
.filter(
|
|
404
|
+
(a): a is StagedAttachment & { storagePath: string; viewToken: string } =>
|
|
405
|
+
a.status === 'ready' && !!a.storagePath && !!a.viewToken,
|
|
406
|
+
)
|
|
407
|
+
.map((a) => ({
|
|
408
|
+
storagePath: a.storagePath,
|
|
409
|
+
viewToken: a.viewToken,
|
|
410
|
+
contentType: a.file.type,
|
|
411
|
+
fileName: a.file.name,
|
|
412
|
+
size: a.file.size,
|
|
413
|
+
})),
|
|
414
|
+
[attachments],
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
const hasInflightUploads = attachments.some(
|
|
418
|
+
(a) => a.status === 'sniffing' || a.status === 'uploading',
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
attachments,
|
|
423
|
+
readyAttachments,
|
|
424
|
+
hasInflightUploads,
|
|
425
|
+
addFiles,
|
|
426
|
+
removeAttachment,
|
|
427
|
+
clear,
|
|
428
|
+
}
|
|
429
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useChatCardItem — fetches the full mapped item for an inline chat
|
|
5
|
+
* card via the SAME list-API endpoint the public pages use.
|
|
6
|
+
*
|
|
7
|
+
* Single source of truth: no parallel `buildImageUrl` / `buildMetadata`
|
|
8
|
+
* synthesis. The chat renders the same shape, with the same joins
|
|
9
|
+
* (author / categories / platforms / hosts / etc.), as `/blog`,
|
|
10
|
+
* `/case-studies`, `/podcasts`, `/roadmap`, etc.
|
|
11
|
+
*
|
|
12
|
+
* Batching: every compact card with the same `(type, id)` shares one
|
|
13
|
+
* TanStack-Query entry; multiple refs of the same type in the same
|
|
14
|
+
* chat message produce ONE network request (TanStack dedups by
|
|
15
|
+
* queryKey). 5-minute staleTime matches `RelatedContentSection`.
|
|
16
|
+
*
|
|
17
|
+
* URL builder is read from `runtime.endpoints.buildListUrl` so the
|
|
18
|
+
* embedded app can supply a per-type URL builder against the reverse
|
|
19
|
+
* proxy; the hub wires the registry-driven builder directly.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { useQuery } from '@tanstack/react-query'
|
|
23
|
+
import { useRequiredChatRuntime } from '../../../contexts/chat-runtime-context'
|
|
24
|
+
|
|
25
|
+
export interface UseChatCardItemResult<T = unknown> {
|
|
26
|
+
item: T | undefined
|
|
27
|
+
isLoading: boolean
|
|
28
|
+
isError: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Extract the items array from each endpoint's response shape (some
|
|
32
|
+
* return `{ items }`, others `{ posts }`, etc.). Single normalization
|
|
33
|
+
* point — callers always read `Item[]`. */
|
|
34
|
+
function extractItems(data: unknown): unknown[] {
|
|
35
|
+
if (!data || typeof data !== 'object') return []
|
|
36
|
+
const obj = data as Record<string, unknown>
|
|
37
|
+
if (Array.isArray(obj.items)) return obj.items
|
|
38
|
+
if (Array.isArray(obj.posts)) return obj.posts
|
|
39
|
+
if (Array.isArray(obj.campaigns)) return obj.campaigns
|
|
40
|
+
if (Array.isArray(obj.completed) || Array.isArray(obj.inProgress)) {
|
|
41
|
+
// Delivery endpoint splits into completed/inProgress arrays — flatten.
|
|
42
|
+
const completed = Array.isArray(obj.completed) ? obj.completed : []
|
|
43
|
+
const inProgress = Array.isArray(obj.inProgress) ? obj.inProgress : []
|
|
44
|
+
return [...completed, ...inProgress]
|
|
45
|
+
}
|
|
46
|
+
if (Array.isArray(obj.data)) return obj.data
|
|
47
|
+
if (Array.isArray(data)) return data
|
|
48
|
+
return []
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Extract a stable id from a fetched item — different shapes use
|
|
52
|
+
* `id` vs `external_id`. Used by the chat loader to match the fetched
|
|
53
|
+
* array element back to the marker's id. */
|
|
54
|
+
function extractItemId(type: string, item: unknown): string | null {
|
|
55
|
+
if (!item || typeof item !== 'object') return null
|
|
56
|
+
const obj = item as Record<string, unknown>
|
|
57
|
+
if (type === 'roadmap_item' || type === 'delivery_item' || type === 'internal_task') {
|
|
58
|
+
const ext = obj.external_id
|
|
59
|
+
if (typeof ext === 'string') return ext
|
|
60
|
+
}
|
|
61
|
+
// RoadmapItem-shaped responses use `id` to carry the external_id; the
|
|
62
|
+
// mapper renames external_id→id at the API boundary. Treat both id and
|
|
63
|
+
// external_id as primary-key candidates for these clickup-backed types.
|
|
64
|
+
const id = obj.id
|
|
65
|
+
if (typeof id === 'string') return id
|
|
66
|
+
if (typeof id === 'number') return String(id)
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function useChatCardItem<T = unknown>(
|
|
71
|
+
type: string,
|
|
72
|
+
id: string,
|
|
73
|
+
): UseChatCardItemResult<T> {
|
|
74
|
+
// Read the list-URL builder from the chat runtime — hub uses the
|
|
75
|
+
// rag-table-config registry directly; embedded apps supply a per-type
|
|
76
|
+
// URL builder against the reverse proxy.
|
|
77
|
+
const runtime = useRequiredChatRuntime()
|
|
78
|
+
const url = runtime.endpoints.buildListUrl(type, [id])
|
|
79
|
+
const query = useQuery({
|
|
80
|
+
queryKey: ['chat-card-item', type, id],
|
|
81
|
+
queryFn: async (): Promise<T | null> => {
|
|
82
|
+
if (!url) return null
|
|
83
|
+
const res = await fetch(url)
|
|
84
|
+
if (!res.ok) return null
|
|
85
|
+
const data = await res.json()
|
|
86
|
+
const items = extractItems(data)
|
|
87
|
+
const match = items.find((it) => extractItemId(type, it) === id)
|
|
88
|
+
return (match ?? null) as T | null
|
|
89
|
+
},
|
|
90
|
+
enabled: !!url && id.length > 0,
|
|
91
|
+
// Don't refetch on window focus inside the chat — the chat panel
|
|
92
|
+
// is opened and closed frequently, and re-fetching every time the
|
|
93
|
+
// user toggles back to the tab is wasteful (the row's id-keyed
|
|
94
|
+
// payload is effectively immutable).
|
|
95
|
+
refetchOnWindowFocus: false,
|
|
96
|
+
})
|
|
97
|
+
return {
|
|
98
|
+
item: (query.data ?? undefined) as T | undefined,
|
|
99
|
+
isLoading: query.isLoading,
|
|
100
|
+
isError: query.isError,
|
|
101
|
+
}
|
|
102
|
+
}
|