@athenaintel/react 0.22.3 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +394 -4
- package/dist/chat/AthenaChat.cjs +389 -240
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.d.ts +26 -8
- package/dist/chat/AthenaChat.js +394 -245
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +2 -1
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.js +2 -1
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/StatewireClientToolBridge.cjs +1 -0
- package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
- package/dist/chat/StatewireClientToolBridge.js +1 -0
- package/dist/chat/StatewireClientToolBridge.js.map +1 -1
- package/dist/chat/StatewireQueuedMessages.cjs +1 -0
- package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
- package/dist/chat/StatewireQueuedMessages.js +1 -0
- package/dist/chat/StatewireQueuedMessages.js.map +1 -1
- package/dist/chat/WaitingOnYouBanner.cjs +100 -0
- package/dist/chat/WaitingOnYouBanner.cjs.map +1 -0
- package/dist/chat/WaitingOnYouBanner.d.ts +29 -0
- package/dist/chat/WaitingOnYouBanner.js +100 -0
- package/dist/chat/WaitingOnYouBanner.js.map +1 -0
- package/dist/chat/athena-chat-welcome.cjs +148 -0
- package/dist/chat/athena-chat-welcome.cjs.map +1 -0
- package/dist/chat/athena-chat-welcome.d.ts +54 -0
- package/dist/chat/athena-chat-welcome.js +148 -0
- package/dist/chat/athena-chat-welcome.js.map +1 -0
- package/dist/chat/athena-composer-controls.cjs +76 -0
- package/dist/chat/athena-composer-controls.cjs.map +1 -0
- package/dist/chat/athena-composer-controls.d.ts +21 -0
- package/dist/chat/athena-composer-controls.js +76 -0
- package/dist/chat/athena-composer-controls.js.map +1 -0
- package/dist/chat/composer-keyboard-hint.cjs +61 -0
- package/dist/chat/composer-keyboard-hint.cjs.map +1 -0
- package/dist/chat/composer-keyboard-hint.d.ts +33 -0
- package/dist/chat/composer-keyboard-hint.js +61 -0
- package/dist/chat/composer-keyboard-hint.js.map +1 -0
- package/dist/chat/message-time.cjs +64 -0
- package/dist/chat/message-time.cjs.map +1 -0
- package/dist/chat/message-time.d.ts +45 -0
- package/dist/chat/message-time.js +64 -0
- package/dist/chat/message-time.js.map +1 -0
- package/dist/chat/run-activity.cjs +38 -7
- package/dist/chat/run-activity.cjs.map +1 -1
- package/dist/chat/run-activity.d.ts +14 -3
- package/dist/chat/run-activity.js +39 -8
- package/dist/chat/run-activity.js.map +1 -1
- package/dist/chat/statewire-client-tools.cjs +2 -1
- package/dist/chat/statewire-client-tools.cjs.map +1 -1
- package/dist/chat/statewire-client-tools.js +2 -1
- package/dist/chat/statewire-client-tools.js.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.cjs +2 -1
- package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.js +2 -1
- package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
- package/dist/chat/thread-scroll-to-bottom.cjs +24 -0
- package/dist/chat/thread-scroll-to-bottom.cjs.map +1 -0
- package/dist/chat/thread-scroll-to-bottom.d.ts +11 -0
- package/dist/chat/thread-scroll-to-bottom.js +24 -0
- package/dist/chat/thread-scroll-to-bottom.js.map +1 -0
- package/dist/chat/tiptap-text.cjs +26 -99
- package/dist/chat/tiptap-text.cjs.map +1 -1
- package/dist/chat/tiptap-text.d.ts +13 -0
- package/dist/chat/tiptap-text.js +26 -99
- package/dist/chat/tiptap-text.js.map +1 -1
- package/dist/chat/use-athena-run-live.cjs +2 -1
- package/dist/chat/use-athena-run-live.cjs.map +1 -1
- package/dist/chat/use-athena-run-live.js +2 -1
- package/dist/chat/use-athena-run-live.js.map +1 -1
- package/dist/chat/use-statewire-approval-request.cjs +1 -0
- package/dist/chat/use-statewire-approval-request.cjs.map +1 -1
- package/dist/chat/use-statewire-approval-request.js +1 -0
- package/dist/chat/use-statewire-approval-request.js.map +1 -1
- package/dist/chat-ui/composer/tiptap-composer.cjs +39 -4
- package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
- package/dist/chat-ui/composer/tiptap-composer.d.ts +21 -0
- package/dist/chat-ui/composer/tiptap-composer.js +39 -4
- package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
- package/dist/chat-ui/index.cjs +15 -1
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +6 -3
- package/dist/chat-ui/index.js +16 -2
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/mentions/mention-chip.cjs +31 -4
- package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
- package/dist/chat-ui/mentions/mention-chip.js +31 -4
- package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
- package/dist/chat-ui/messages/message-components.cjs +19 -1
- package/dist/chat-ui/messages/message-components.cjs.map +1 -1
- package/dist/chat-ui/messages/message-components.d.ts +13 -0
- package/dist/chat-ui/messages/message-components.js +19 -1
- package/dist/chat-ui/messages/message-components.js.map +1 -1
- package/dist/chat-ui/messages/runtime-message.cjs +2 -1
- package/dist/chat-ui/messages/runtime-message.cjs.map +1 -1
- package/dist/chat-ui/messages/runtime-message.js +2 -1
- package/dist/chat-ui/messages/runtime-message.js.map +1 -1
- package/dist/chat-ui/messages/statewire-continue-run.cjs +1 -0
- package/dist/chat-ui/messages/statewire-continue-run.cjs.map +1 -1
- package/dist/chat-ui/messages/statewire-continue-run.js +1 -0
- package/dist/chat-ui/messages/statewire-continue-run.js.map +1 -1
- package/dist/chat-ui/messages/user-edit-composer.cjs +3 -1
- package/dist/chat-ui/messages/user-edit-composer.cjs.map +1 -1
- package/dist/chat-ui/messages/user-edit-composer.js +3 -1
- package/dist/chat-ui/messages/user-edit-composer.js.map +1 -1
- package/dist/chat-ui/messages/wake-marker.cjs +84 -0
- package/dist/chat-ui/messages/wake-marker.cjs.map +1 -0
- package/dist/chat-ui/messages/wake-marker.d.ts +16 -0
- package/dist/chat-ui/messages/wake-marker.js +84 -0
- package/dist/chat-ui/messages/wake-marker.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-blocks.cjs +177 -0
- package/dist/chat-ui/rich-text/chat-blocks.cjs.map +1 -0
- package/dist/chat-ui/rich-text/chat-blocks.d.ts +58 -0
- package/dist/chat-ui/rich-text/chat-blocks.js +177 -0
- package/dist/chat-ui/rich-text/chat-blocks.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs +69 -0
- package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -0
- package/dist/chat-ui/rich-text/chat-link.d.ts +24 -0
- package/dist/chat-ui/rich-text/chat-link.js +69 -0
- package/dist/chat-ui/rich-text/chat-link.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-message-body.cjs +144 -18
- package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.d.ts +10 -1
- package/dist/chat-ui/rich-text/chat-message-body.js +149 -23
- package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
- package/dist/chat-ui/rich-text/chat-node-views.cjs +84 -4
- package/dist/chat-ui/rich-text/chat-node-views.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-node-views.d.ts +12 -1
- package/dist/chat-ui/rich-text/chat-node-views.js +85 -5
- package/dist/chat-ui/rich-text/chat-node-views.js.map +1 -1
- package/dist/chat-ui/rich-text/chat-text-schema.cjs +21 -3
- package/dist/chat-ui/rich-text/chat-text-schema.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-text-schema.d.ts +17 -2
- package/dist/chat-ui/rich-text/chat-text-schema.js +21 -3
- package/dist/chat-ui/rich-text/chat-text-schema.js.map +1 -1
- package/dist/chat-ui/rich-text/citation-anchors.cjs +139 -0
- package/dist/chat-ui/rich-text/citation-anchors.cjs.map +1 -0
- package/dist/chat-ui/rich-text/citation-anchors.d.ts +74 -0
- package/dist/chat-ui/rich-text/citation-anchors.js +139 -0
- package/dist/chat-ui/rich-text/citation-anchors.js.map +1 -0
- package/dist/chat-ui/statewire.cjs +8 -1
- package/dist/chat-ui/statewire.cjs.map +1 -1
- package/dist/chat-ui/statewire.d.ts +2 -0
- package/dist/chat-ui/statewire.js +8 -1
- package/dist/chat-ui/statewire.js.map +1 -1
- package/dist/chat-ui/thread/chat-composer-dock.cjs +27 -6
- package/dist/chat-ui/thread/chat-composer-dock.cjs.map +1 -1
- package/dist/chat-ui/thread/chat-composer-dock.d.ts +21 -1
- package/dist/chat-ui/thread/chat-composer-dock.js +28 -7
- package/dist/chat-ui/thread/chat-composer-dock.js.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.cjs +12 -13
- package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.d.ts +11 -10
- package/dist/chat-ui/thread/statewire-history-edge.js +13 -14
- package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
- package/dist/chat-ui/thread/thread-loading-overlay.cjs +31 -0
- package/dist/chat-ui/thread/thread-loading-overlay.cjs.map +1 -0
- package/dist/chat-ui/thread/thread-loading-overlay.d.ts +14 -0
- package/dist/chat-ui/thread/thread-loading-overlay.js +31 -0
- package/dist/chat-ui/thread/thread-loading-overlay.js.map +1 -0
- package/dist/composer/ComposerDropZone.cjs +8 -1
- package/dist/composer/ComposerDropZone.cjs.map +1 -1
- package/dist/composer/ComposerDropZone.d.ts +6 -0
- package/dist/composer/ComposerDropZone.js +9 -2
- package/dist/composer/ComposerDropZone.js.map +1 -1
- package/dist/diagnostics/errors.cjs +20 -0
- package/dist/diagnostics/errors.cjs.map +1 -1
- package/dist/diagnostics/errors.d.ts +20 -0
- package/dist/diagnostics/errors.js +20 -0
- package/dist/diagnostics/errors.js.map +1 -1
- package/dist/index.cjs +47 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +26 -7
- package/dist/index.js +51 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/use-prefers-reduced-motion.cjs +29 -0
- package/dist/lib/use-prefers-reduced-motion.cjs.map +1 -0
- package/dist/lib/use-prefers-reduced-motion.d.ts +6 -0
- package/dist/lib/use-prefers-reduced-motion.js +29 -0
- package/dist/lib/use-prefers-reduced-motion.js.map +1 -0
- package/dist/packages/agent-runtime/src/conversation-index.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/conversation-index.js.map +1 -1
- package/dist/packages/agent-runtime/src/conversation-list.cjs +59 -0
- package/dist/packages/agent-runtime/src/conversation-list.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/conversation-list.js +60 -1
- package/dist/packages/agent-runtime/src/conversation-list.js.map +1 -1
- package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.cjs +5 -3
- package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.js +5 -3
- package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.js.map +1 -1
- package/dist/packages/agent-runtime/src/edit-command.cjs +21 -17
- package/dist/packages/agent-runtime/src/edit-command.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/edit-command.js +22 -18
- package/dist/packages/agent-runtime/src/edit-command.js.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +9 -11
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +9 -11
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
- package/dist/packages/agent-runtime/src/github-wake-event.cjs +86 -0
- package/dist/packages/agent-runtime/src/github-wake-event.cjs.map +1 -0
- package/dist/packages/agent-runtime/src/github-wake-event.js +86 -0
- package/dist/packages/agent-runtime/src/github-wake-event.js.map +1 -0
- package/dist/packages/agent-runtime/src/message-window.cjs +150 -42
- package/dist/packages/agent-runtime/src/message-window.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/message-window.js +151 -43
- package/dist/packages/agent-runtime/src/message-window.js.map +1 -1
- package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs +13 -9
- package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js +13 -9
- package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
- package/dist/packages/agent-runtime/src/statewire-suspended.cjs +1 -3
- package/dist/packages/agent-runtime/src/statewire-suspended.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/statewire-suspended.js +1 -3
- package/dist/packages/agent-runtime/src/statewire-suspended.js.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.js.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs +61 -30
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.js +59 -28
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.cjs +56 -36
- package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.js +59 -39
- package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
- package/dist/packages/react/package.json.cjs +1 -1
- package/dist/packages/react/package.json.js +1 -1
- package/dist/provider/AthenaContext.cjs.map +1 -1
- package/dist/provider/AthenaContext.d.ts +3 -0
- package/dist/provider/AthenaContext.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +93 -45
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +6 -1
- package/dist/provider/AthenaProvider.js +97 -49
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/runtime/statewire-lifecycle.cjs +2 -1
- package/dist/runtime/statewire-lifecycle.cjs.map +1 -1
- package/dist/runtime/statewire-lifecycle.js +2 -1
- package/dist/runtime/statewire-lifecycle.js.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.cjs +3 -2
- package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.d.ts +8 -12
- package/dist/runtime/useAthenaStatewireRuntime.js +3 -2
- package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
- package/dist/search/useAthenaRepoSearch.cjs +94 -0
- package/dist/search/useAthenaRepoSearch.cjs.map +1 -0
- package/dist/search/useAthenaRepoSearch.d.ts +95 -0
- package/dist/search/useAthenaRepoSearch.js +94 -0
- package/dist/search/useAthenaRepoSearch.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/threads/SessionList.cjs +486 -0
- package/dist/threads/SessionList.cjs.map +1 -0
- package/dist/threads/SessionList.d.ts +60 -0
- package/dist/threads/SessionList.js +486 -0
- package/dist/threads/SessionList.js.map +1 -0
- package/dist/threads/api.cjs +112 -4
- package/dist/threads/api.cjs.map +1 -1
- package/dist/threads/api.d.ts +128 -1
- package/dist/threads/api.js +113 -5
- package/dist/threads/api.js.map +1 -1
- package/dist/threads/conversation-list-fetcher.cjs +27 -3
- package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
- package/dist/threads/conversation-list-fetcher.d.ts +16 -3
- package/dist/threads/conversation-list-fetcher.js +27 -3
- package/dist/threads/conversation-list-fetcher.js.map +1 -1
- package/dist/threads/index.d.ts +5 -1
- package/dist/threads/session-list-model.cjs +130 -0
- package/dist/threads/session-list-model.cjs.map +1 -0
- package/dist/threads/session-list-model.d.ts +62 -0
- package/dist/threads/session-list-model.js +130 -0
- package/dist/threads/session-list-model.js.map +1 -0
- package/dist/threads/statewire-thread-list.cjs +220 -38
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.d.ts +152 -2
- package/dist/threads/statewire-thread-list.js +222 -40
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/thread-prefetcher.cjs +63 -0
- package/dist/threads/thread-prefetcher.cjs.map +1 -0
- package/dist/threads/thread-prefetcher.d.ts +54 -0
- package/dist/threads/thread-prefetcher.js +63 -0
- package/dist/threads/thread-prefetcher.js.map +1 -0
- package/dist/threads/use-athena-session-attention.cjs +22 -0
- package/dist/threads/use-athena-session-attention.cjs.map +1 -0
- package/dist/threads/use-athena-session-attention.d.ts +24 -0
- package/dist/threads/use-athena-session-attention.js +22 -0
- package/dist/threads/use-athena-session-attention.js.map +1 -0
- package/dist/threads/use-rest-conversation-history.cjs +25 -2
- package/dist/threads/use-rest-conversation-history.cjs.map +1 -1
- package/dist/threads/use-rest-conversation-history.d.ts +8 -1
- package/dist/threads/use-rest-conversation-history.js +25 -2
- package/dist/threads/use-rest-conversation-history.js.map +1 -1
- package/dist/threads/use-statewire-conversation-history.cjs +40 -16
- package/dist/threads/use-statewire-conversation-history.cjs.map +1 -1
- package/dist/threads/use-statewire-conversation-history.d.ts +13 -0
- package/dist/threads/use-statewire-conversation-history.js +40 -16
- package/dist/threads/use-statewire-conversation-history.js.map +1 -1
- package/dist/threads/use-thread-archive.cjs +128 -0
- package/dist/threads/use-thread-archive.cjs.map +1 -0
- package/dist/threads/use-thread-archive.d.ts +26 -0
- package/dist/threads/use-thread-archive.js +128 -0
- package/dist/threads/use-thread-archive.js.map +1 -0
- package/dist/threads/use-thread-favorite.cjs +53 -0
- package/dist/threads/use-thread-favorite.cjs.map +1 -0
- package/dist/threads/use-thread-favorite.d.ts +49 -0
- package/dist/threads/use-thread-favorite.js +53 -0
- package/dist/threads/use-thread-favorite.js.map +1 -0
- package/dist/threads/use-thread-field-override.cjs +184 -0
- package/dist/threads/use-thread-field-override.cjs.map +1 -0
- package/dist/threads/use-thread-field-override.d.ts +55 -0
- package/dist/threads/use-thread-field-override.js +184 -0
- package/dist/threads/use-thread-field-override.js.map +1 -0
- package/dist/threads/use-thread-rename.cjs +53 -0
- package/dist/threads/use-thread-rename.cjs.map +1 -0
- package/dist/threads/use-thread-rename.d.ts +48 -0
- package/dist/threads/use-thread-rename.js +53 -0
- package/dist/threads/use-thread-rename.js.map +1 -0
- package/dist/tools/index.cjs +5 -1
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +7 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/misc-tool-uis.cjs +2 -1
- package/dist/tools/misc-tool-uis.cjs.map +1 -1
- package/dist/tools/misc-tool-uis.js +2 -1
- package/dist/tools/misc-tool-uis.js.map +1 -1
- package/dist/tools/primitives.cjs +25 -2
- package/dist/tools/primitives.cjs.map +1 -1
- package/dist/tools/primitives.d.ts +9 -2
- package/dist/tools/primitives.js +26 -3
- package/dist/tools/primitives.js.map +1 -1
- package/dist/tools/tool-appearance.cjs +11 -0
- package/dist/tools/tool-appearance.cjs.map +1 -0
- package/dist/tools/tool-appearance.d.ts +29 -0
- package/dist/tools/tool-appearance.js +11 -0
- package/dist/tools/tool-appearance.js.map +1 -0
- package/dist/tools/tool-fallback.cjs +211 -13
- package/dist/tools/tool-fallback.cjs.map +1 -1
- package/dist/tools/tool-fallback.d.ts +31 -4
- package/dist/tools/tool-fallback.js +214 -16
- package/dist/tools/tool-fallback.js.map +1 -1
- package/dist/tools/use-elapsed-seconds.cjs +24 -0
- package/dist/tools/use-elapsed-seconds.cjs.map +1 -0
- package/dist/tools/use-elapsed-seconds.d.ts +10 -0
- package/dist/tools/use-elapsed-seconds.js +24 -0
- package/dist/tools/use-elapsed-seconds.js.map +1 -0
- package/dist/tools/voice-update-tool-ui.cjs +2 -1
- package/dist/tools/voice-update-tool-ui.cjs.map +1 -1
- package/dist/tools/voice-update-tool-ui.js +2 -1
- package/dist/tools/voice-update-tool-ui.js.map +1 -1
- package/package.json +1 -1
- package/src/chat-ui/tailwind-preset.js +14 -1
|
@@ -51,6 +51,12 @@ export declare function useStatewireConversationHistory({ backendUrl, conversati
|
|
|
51
51
|
messageCount: number | null;
|
|
52
52
|
sessionStatus: string | null;
|
|
53
53
|
runStatus: string | null;
|
|
54
|
+
attention?: import("@athenaintel/agent-runtime").ConversationAttention | null;
|
|
55
|
+
nextAction?: string | null;
|
|
56
|
+
attentionSource?: import("@athenaintel/agent-runtime").ConversationAttentionSource | null;
|
|
57
|
+
isFavorited?: boolean;
|
|
58
|
+
lastWake?: import("@athenaintel/agent-runtime").ConversationWake | null;
|
|
59
|
+
pullRequests?: import("@athenaintel/agent-runtime").ConversationPullRequest[] | null;
|
|
54
60
|
isUnread: boolean;
|
|
55
61
|
}[];
|
|
56
62
|
totalCount: number | null;
|
|
@@ -72,5 +78,12 @@ export declare function useStatewireConversationHistory({ backendUrl, conversati
|
|
|
72
78
|
threadId: string;
|
|
73
79
|
}>;
|
|
74
80
|
readStatePending: boolean;
|
|
81
|
+
/** Pin or unpin a conversation; read-only on a backend without the route (`favoriteToggleAvailable`). */
|
|
82
|
+
setConversationFavorited: (target: import("./use-thread-favorite").ThreadFavoriteTarget) => void;
|
|
83
|
+
favoritePending: boolean;
|
|
84
|
+
/** The conversations whose pin toggle is in flight. */
|
|
85
|
+
favoritePendingThreadIds: ReadonlySet<string>;
|
|
86
|
+
/** `false` once the backend answered 404 / 405 to the favorite route. */
|
|
87
|
+
favoriteToggleAvailable: boolean;
|
|
75
88
|
connection: import("statewire").StatewireClient.Connection;
|
|
76
89
|
};
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import "../packages/agent-runtime/src/generated/runconfig-contract.js";
|
|
2
2
|
import { useState, useEffect, useMemo, useCallback } from "react";
|
|
3
3
|
import { useConversationIndex } from "../packages/agent-runtime/src/conversation-index.js";
|
|
4
|
+
import "../packages/agent-runtime/src/conversation-list.js";
|
|
4
5
|
import { useMutation } from "@tanstack/react-query";
|
|
5
6
|
import "../packages/agent-runtime/src/statewire-state.js";
|
|
6
7
|
import "../packages/agent-runtime/src/messages.js";
|
|
7
|
-
import "
|
|
8
|
+
import "statewire";
|
|
8
9
|
import "@assistant-ui/store";
|
|
9
10
|
import "../packages/agent-runtime/src/quota-safe-local-storage-session.js";
|
|
10
11
|
import "../packages/agent-runtime/src/statewire-suspended.js";
|
|
11
12
|
import "@assistant-ui/tap";
|
|
12
13
|
import "../packages/agent-runtime/src/statewire-attach-observer.js";
|
|
13
|
-
import "statewire";
|
|
14
14
|
import "../packages/agent-runtime/src/thread-status.js";
|
|
15
|
+
import "@assistant-ui/react-statewire";
|
|
15
16
|
import "@assistant-ui/core";
|
|
16
17
|
import "../packages/agent-runtime/src/thread-handoff.js";
|
|
17
18
|
import { archiveThread, setThreadReadState } from "./api.js";
|
|
18
19
|
import { useRestConversationHistory } from "./use-rest-conversation-history.js";
|
|
20
|
+
import { useThreadFavorite, withFavoriteOverrides } from "./use-thread-favorite.js";
|
|
19
21
|
function conversationSummaryFromIndexEntry(entry) {
|
|
20
22
|
return {
|
|
21
23
|
threadId: entry.thread_id,
|
|
@@ -114,24 +116,39 @@ function useStatewireConversationHistory({
|
|
|
114
116
|
return next.size === current.size ? current : next;
|
|
115
117
|
});
|
|
116
118
|
}, [archivedThreadIds.size, index.entries]);
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
+
const sourceConversations = useMemo(
|
|
120
|
+
() => conversationHistorySource({
|
|
119
121
|
indexEntries: index.entries,
|
|
120
122
|
restConversations: legacyHistory.conversations,
|
|
121
123
|
supportsStatewirePaging,
|
|
122
124
|
hasSearch
|
|
123
|
-
}).filter((conversation) => !archivedThreadIds.has(conversation.threadId))
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
125
|
+
}).filter((conversation) => !archivedThreadIds.has(conversation.threadId)),
|
|
126
|
+
[
|
|
127
|
+
archivedThreadIds,
|
|
128
|
+
index.entries,
|
|
129
|
+
legacyHistory.conversations,
|
|
130
|
+
hasSearch,
|
|
131
|
+
supportsStatewirePaging
|
|
132
|
+
]
|
|
133
|
+
);
|
|
134
|
+
const patchLegacyConversation = legacyHistory.patchConversation;
|
|
135
|
+
const favorite = useThreadFavorite({
|
|
136
|
+
backendUrl,
|
|
137
|
+
principalId,
|
|
138
|
+
getAuth,
|
|
139
|
+
rows: sourceConversations,
|
|
140
|
+
onServerTruth: ({ threadId, isFavorited }) => patchLegacyConversation(threadId, { isFavorited })
|
|
141
|
+
});
|
|
142
|
+
const favoriteOverrides = favorite.overrides;
|
|
143
|
+
const conversations = useMemo(
|
|
144
|
+
() => withFavoriteOverrides(sourceConversations, favoriteOverrides).map(
|
|
145
|
+
(conversation) => ({
|
|
146
|
+
...conversation,
|
|
147
|
+
isUnread: readOverrides[conversation.threadId] ?? conversation.isUnread
|
|
148
|
+
})
|
|
149
|
+
),
|
|
150
|
+
[sourceConversations, favoriteOverrides, readOverrides]
|
|
151
|
+
);
|
|
135
152
|
const refreshMutation = useMutation({
|
|
136
153
|
mutationKey: [
|
|
137
154
|
"conversation-index",
|
|
@@ -234,6 +251,13 @@ function useStatewireConversationHistory({
|
|
|
234
251
|
archivePending: archive.isPending,
|
|
235
252
|
setConversationReadState: readState.mutate,
|
|
236
253
|
readStatePending: readState.isPending,
|
|
254
|
+
/** Pin or unpin a conversation; read-only on a backend without the route (`favoriteToggleAvailable`). */
|
|
255
|
+
setConversationFavorited: favorite.setFavorited,
|
|
256
|
+
favoritePending: favorite.isPending,
|
|
257
|
+
/** The conversations whose pin toggle is in flight. */
|
|
258
|
+
favoritePendingThreadIds: favorite.pendingThreadIds,
|
|
259
|
+
/** `false` once the backend answered 404 / 405 to the favorite route. */
|
|
260
|
+
favoriteToggleAvailable: favorite.isAvailable,
|
|
237
261
|
connection: index.connection
|
|
238
262
|
};
|
|
239
263
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-statewire-conversation-history.js","sources":["../../src/threads/use-statewire-conversation-history.ts"],"sourcesContent":["import {\n\ttype ConversationIndexEntry,\n\ttype ConversationSummary,\n\ttype UseConversationIndexOptions,\n\tuseConversationIndex,\n} from \"@athenaintel/agent-runtime\";\nimport { useMutation } from \"@tanstack/react-query\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { archiveThread, setThreadReadState } from \"./api\";\nimport {\n\ttype RestConversationAuth,\n\tuseRestConversationHistory,\n} from \"./use-rest-conversation-history\";\n\nexport interface UseStatewireConversationHistoryOptions {\n\tbackendUrl: string;\n\tconversationIndexBaseUrl: string;\n\t/** The workspace being viewed; the index is one instance per (workspace, user). */\n\tworkspaceId: string;\n\tprincipalId: string;\n\tgetAuth: () => RestConversationAuth | Promise<RestConversationAuth>;\n\theaders: UseConversationIndexOptions[\"headers\"];\n\tsearch?: string;\n\tenabled?: boolean;\n\tsse?: boolean;\n}\n\n/** Convert the replicated wire row into the shared cross-surface contract. */\nexport function conversationSummaryFromIndexEntry(\n\tentry: ConversationIndexEntry,\n): ConversationSummary {\n\treturn {\n\t\tthreadId: entry.thread_id,\n\t\ttitle: entry.title,\n\t\tupdatedAt: entry.updated_at,\n\t\tcreatedAt: null,\n\t\tstate: entry.state,\n\t\truntime: entry.runtime,\n\t\tsourceChannel: entry.start_channel,\n\t\tassetType: entry.asset_type,\n\t\tassetId: entry.asset_id ?? null,\n\t\tappId: entry.app_id ?? null,\n\t\ttriggerType: entry.trigger_type ?? null,\n\t\tpreview: entry.last_message_preview ?? null,\n\t\tmessageCount: entry.message_count ?? null,\n\t\tisUnread: entry.is_unread ?? false,\n\t\tsessionStatus: entry.session_status ?? null,\n\t\trunStatus: entry.run_status ?? null,\n\t};\n}\n\nexport function conversationHistorySource({\n\tindexEntries,\n\trestConversations,\n\tsupportsStatewirePaging,\n\thasSearch,\n}: {\n\tindexEntries: readonly ConversationIndexEntry[] | null;\n\trestConversations: readonly ConversationSummary[];\n\tsupportsStatewirePaging: boolean;\n\thasSearch: boolean;\n}): ConversationSummary[] {\n\t// Search is server-backed so it covers the user's entire authorized history,\n\t// including rows older than the Statewire live window. An empty query uses\n\t// Statewire as the render source; REST only supplements rolling old hosts.\n\tif (hasSearch) return [...restConversations];\n\tconst statewireItems = (indexEntries ?? []).map(\n\t\tconversationSummaryFromIndexEntry,\n\t);\n\tif (supportsStatewirePaging) return statewireItems;\n\tconst statewireIds = new Set(statewireItems.map((item) => item.threadId));\n\treturn [\n\t\t...statewireItems,\n\t\t...restConversations.filter(\n\t\t\t(conversation) => !statewireIds.has(conversation.threadId),\n\t\t),\n\t];\n}\n\nexport function shouldUseRestConversationHistory({\n\thasSearch,\n\tindexEntries,\n\tsupportsStatewirePaging,\n\tconnectionStatus,\n}: {\n\thasSearch: boolean;\n\tindexEntries: readonly ConversationIndexEntry[] | null;\n\tsupportsStatewirePaging: boolean;\n\tconnectionStatus: string;\n}): boolean {\n\tif (hasSearch) return true;\n\tif (supportsStatewirePaging) return false;\n\t// An old host is identifiable once its snapshot arrives. A transport that\n\t// fails or is parked before any snapshot also needs the independent REST\n\t// path; otherwise entries stays null and the history panel loads forever.\n\treturn (\n\t\tindexEntries !== null ||\n\t\tconnectionStatus === \"reconnecting\" ||\n\t\tconnectionStatus === \"gone\"\n\t);\n}\n\n/**\n * Inclusive satellite conversation history backed by the user's Pinned\n * Statewire instance. The replicated window is the render source; refreshes,\n * pagination, thread creation, lifecycle changes, and title changes arrive on\n * that same channel. A non-empty search deliberately uses the authorized REST\n * list endpoint so matches are not limited to the replicated newest window.\n */\nexport function useStatewireConversationHistory({\n\tbackendUrl,\n\tconversationIndexBaseUrl,\n\tworkspaceId,\n\tprincipalId,\n\tgetAuth,\n\theaders,\n\tsearch,\n\tenabled = true,\n\tsse = false,\n}: UseStatewireConversationHistoryOptions) {\n\tconst index = useConversationIndex({\n\t\tbaseUrl: conversationIndexBaseUrl,\n\t\tworkspaceId: workspaceId || \"pending-workspace\",\n\t\tuserId: principalId || \"pending-user\",\n\t\theaders,\n\t\tenabled: enabled && workspaceId.length > 0 && principalId.length > 0,\n\t\tsse,\n\t});\n\t// Rolling deploy compatibility: the pre-paging index snapshot has no\n\t// total_count. It still renders live rows immediately, while the old REST\n\t// pager remains available only until the upgraded host activates.\n\tconst supportsStatewirePaging = index.totalCount !== null;\n\tconst normalizedSearch = search?.trim().toLowerCase() ?? \"\";\n\tconst hasSearch = normalizedSearch.length > 0;\n\tconst usesRestHistory = shouldUseRestConversationHistory({\n\t\thasSearch,\n\t\tindexEntries: index.entries,\n\t\tsupportsStatewirePaging,\n\t\tconnectionStatus: index.connection.status,\n\t});\n\tconst legacyHistory = useRestConversationHistory({\n\t\tbackendUrl,\n\t\tprincipalId,\n\t\tgetAuth,\n\t\tsearch,\n\t\tenabled: enabled && principalId.length > 0 && usesRestHistory,\n\t});\n\tconst [readOverrides, setReadOverrides] = useState<Record<string, boolean>>(\n\t\t{},\n\t);\n\tconst [archivedThreadIds, setArchivedThreadIds] = useState<\n\t\tReadonlySet<string>\n\t>(() => new Set());\n\n\tuseEffect(() => {\n\t\tif (index.entries === null || archivedThreadIds.size === 0) return;\n\t\tconst visibleIds = new Set(index.entries.map((entry) => entry.thread_id));\n\t\tsetArchivedThreadIds((current) => {\n\t\t\tconst next = new Set(\n\t\t\t\t[...current].filter((threadId) => visibleIds.has(threadId)),\n\t\t\t);\n\t\t\treturn next.size === current.size ? current : next;\n\t\t});\n\t}, [archivedThreadIds.size, index.entries]);\n\n\tconst conversations = useMemo(() => {\n\t\treturn conversationHistorySource({\n\t\t\tindexEntries: index.entries,\n\t\t\trestConversations: legacyHistory.conversations,\n\t\t\tsupportsStatewirePaging,\n\t\t\thasSearch,\n\t\t})\n\t\t\t.filter((conversation) => !archivedThreadIds.has(conversation.threadId))\n\t\t\t.map((conversation) => ({\n\t\t\t\t...conversation,\n\t\t\t\tisUnread: readOverrides[conversation.threadId] ?? conversation.isUnread,\n\t\t\t}));\n\t}, [\n\t\tarchivedThreadIds,\n\t\tindex.entries,\n\t\tlegacyHistory.conversations,\n\t\thasSearch,\n\t\treadOverrides,\n\t\tsupportsStatewirePaging,\n\t]);\n\n\tconst refreshMutation = useMutation({\n\t\tmutationKey: [\n\t\t\t\"conversation-index\",\n\t\t\t\"refresh\",\n\t\t\tconversationIndexBaseUrl,\n\t\t\tprincipalId,\n\t\t],\n\t\tmutationFn: () =>\n\t\t\tusesRestHistory ? legacyHistory.refresh() : index.refresh(),\n\t});\n\tconst loadMoreMutation = useMutation({\n\t\tmutationKey: [\n\t\t\t\"conversation-index\",\n\t\t\t\"load-more\",\n\t\t\tconversationIndexBaseUrl,\n\t\t\tprincipalId,\n\t\t],\n\t\tmutationFn: () =>\n\t\t\tusesRestHistory ? legacyHistory.fetchNextPage() : index.loadMore(),\n\t});\n\tconst archive = useMutation({\n\t\tmutationKey: [\"conversation-history\", \"archive\", backendUrl, principalId],\n\t\tmutationFn: async (threadId: string) => {\n\t\t\tawait archiveThread(backendUrl, await getAuth(), threadId);\n\t\t\treturn threadId;\n\t\t},\n\t\tonMutate: (threadId) => {\n\t\t\tsetArchivedThreadIds((current) => new Set(current).add(threadId));\n\t\t},\n\t\tonError: (_error, threadId) => {\n\t\t\tsetArchivedThreadIds((current) => {\n\t\t\t\tconst next = new Set(current);\n\t\t\t\tnext.delete(threadId);\n\t\t\t\treturn next;\n\t\t\t});\n\t\t},\n\t\tonSuccess: () => {\n\t\t\trefreshMutation.mutate();\n\t\t},\n\t});\n\tconst readState = useMutation({\n\t\tmutationKey: [\n\t\t\t\"conversation-history\",\n\t\t\t\"read-state\",\n\t\t\tbackendUrl,\n\t\t\tprincipalId,\n\t\t],\n\t\tmutationFn: async ({\n\t\t\tthreadId,\n\t\t\tisUnread,\n\t\t}: {\n\t\t\tthreadId: string;\n\t\t\tisUnread: boolean;\n\t\t}) =>\n\t\t\tsetThreadReadState(backendUrl, await getAuth(), { threadId, isUnread }),\n\t\tonMutate: ({ threadId, isUnread }) => {\n\t\t\tconst previous = readOverrides[threadId];\n\t\t\tsetReadOverrides((current) => ({ ...current, [threadId]: isUnread }));\n\t\t\treturn { previous, threadId };\n\t\t},\n\t\tonError: (_error, _variables, context) => {\n\t\t\tif (!context) return;\n\t\t\tsetReadOverrides((current) => {\n\t\t\t\tconst next = { ...current };\n\t\t\t\tif (context.previous === undefined) delete next[context.threadId];\n\t\t\t\telse next[context.threadId] = context.previous;\n\t\t\t\treturn next;\n\t\t\t});\n\t\t},\n\t\tonSuccess: (result) => {\n\t\t\tsetReadOverrides((current) => ({\n\t\t\t\t...current,\n\t\t\t\t[result.thread_id]: result.is_unread,\n\t\t\t}));\n\t\t\trefreshMutation.mutate(undefined, {\n\t\t\t\tonSettled: () => {\n\t\t\t\t\tsetReadOverrides((current) => {\n\t\t\t\t\t\tconst next = { ...current };\n\t\t\t\t\t\tdelete next[result.thread_id];\n\t\t\t\t\t\treturn next;\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t});\n\n\tconst refresh = useCallback(\n\t\t() => refreshMutation.mutateAsync(),\n\t\t[refreshMutation.mutateAsync],\n\t);\n\tconst fetchNextPage = useCallback(\n\t\t() => loadMoreMutation.mutateAsync(),\n\t\t[loadMoreMutation.mutateAsync],\n\t);\n\n\treturn {\n\t\tconversations,\n\t\ttotalCount: usesRestHistory ? legacyHistory.totalCount : index.totalCount,\n\t\tisLoading:\n\t\t\tenabled &&\n\t\t\t(usesRestHistory\n\t\t\t\t? !legacyHistory.hasResolvedFirstPage\n\t\t\t\t: index.entries === null),\n\t\tisFetching:\n\t\t\trefreshMutation.isPending ||\n\t\t\tloadMoreMutation.isPending ||\n\t\t\tlegacyHistory.isFetching,\n\t\tisRefreshing: refreshMutation.isPending,\n\t\thasResolvedFirstPage: usesRestHistory\n\t\t\t? legacyHistory.hasResolvedFirstPage\n\t\t\t: index.entries !== null,\n\t\thasNextPage: usesRestHistory\n\t\t\t? legacyHistory.hasNextPage\n\t\t\t: index.hasNextPage,\n\t\tisFetchingNextPage: loadMoreMutation.isPending,\n\t\tfetchNextPage,\n\t\trefresh,\n\t\tarchiveConversation: archive.mutate,\n\t\tarchivePending: archive.isPending,\n\t\tsetConversationReadState: readState.mutate,\n\t\treadStatePending: readState.isPending,\n\t\tconnection: index.connection,\n\t};\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-statewire-conversation-history.js","sources":["../../src/threads/use-statewire-conversation-history.ts"],"sourcesContent":["import {\n\ttype ConversationIndexEntry,\n\ttype ConversationSummary,\n\ttype UseConversationIndexOptions,\n\tuseConversationIndex,\n} from \"@athenaintel/agent-runtime\";\nimport { useMutation } from \"@tanstack/react-query\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { archiveThread, setThreadReadState } from \"./api\";\nimport {\n\ttype RestConversationAuth,\n\tuseRestConversationHistory,\n} from \"./use-rest-conversation-history\";\nimport { useThreadFavorite, withFavoriteOverrides } from \"./use-thread-favorite\";\n\nexport interface UseStatewireConversationHistoryOptions {\n\tbackendUrl: string;\n\tconversationIndexBaseUrl: string;\n\t/** The workspace being viewed; the index is one instance per (workspace, user). */\n\tworkspaceId: string;\n\tprincipalId: string;\n\tgetAuth: () => RestConversationAuth | Promise<RestConversationAuth>;\n\theaders: UseConversationIndexOptions[\"headers\"];\n\tsearch?: string;\n\tenabled?: boolean;\n\tsse?: boolean;\n}\n\n/** Convert the replicated wire row into the shared cross-surface contract. */\nexport function conversationSummaryFromIndexEntry(\n\tentry: ConversationIndexEntry,\n): ConversationSummary {\n\treturn {\n\t\tthreadId: entry.thread_id,\n\t\ttitle: entry.title,\n\t\tupdatedAt: entry.updated_at,\n\t\tcreatedAt: null,\n\t\tstate: entry.state,\n\t\truntime: entry.runtime,\n\t\tsourceChannel: entry.start_channel,\n\t\tassetType: entry.asset_type,\n\t\tassetId: entry.asset_id ?? null,\n\t\tappId: entry.app_id ?? null,\n\t\ttriggerType: entry.trigger_type ?? null,\n\t\tpreview: entry.last_message_preview ?? null,\n\t\tmessageCount: entry.message_count ?? null,\n\t\tisUnread: entry.is_unread ?? false,\n\t\tsessionStatus: entry.session_status ?? null,\n\t\trunStatus: entry.run_status ?? null,\n\t};\n}\n\nexport function conversationHistorySource({\n\tindexEntries,\n\trestConversations,\n\tsupportsStatewirePaging,\n\thasSearch,\n}: {\n\tindexEntries: readonly ConversationIndexEntry[] | null;\n\trestConversations: readonly ConversationSummary[];\n\tsupportsStatewirePaging: boolean;\n\thasSearch: boolean;\n}): ConversationSummary[] {\n\t// Search is server-backed so it covers the user's entire authorized history,\n\t// including rows older than the Statewire live window. An empty query uses\n\t// Statewire as the render source; REST only supplements rolling old hosts.\n\tif (hasSearch) return [...restConversations];\n\tconst statewireItems = (indexEntries ?? []).map(\n\t\tconversationSummaryFromIndexEntry,\n\t);\n\tif (supportsStatewirePaging) return statewireItems;\n\tconst statewireIds = new Set(statewireItems.map((item) => item.threadId));\n\treturn [\n\t\t...statewireItems,\n\t\t...restConversations.filter(\n\t\t\t(conversation) => !statewireIds.has(conversation.threadId),\n\t\t),\n\t];\n}\n\nexport function shouldUseRestConversationHistory({\n\thasSearch,\n\tindexEntries,\n\tsupportsStatewirePaging,\n\tconnectionStatus,\n}: {\n\thasSearch: boolean;\n\tindexEntries: readonly ConversationIndexEntry[] | null;\n\tsupportsStatewirePaging: boolean;\n\tconnectionStatus: string;\n}): boolean {\n\tif (hasSearch) return true;\n\tif (supportsStatewirePaging) return false;\n\t// An old host is identifiable once its snapshot arrives. A transport that\n\t// fails or is parked before any snapshot also needs the independent REST\n\t// path; otherwise entries stays null and the history panel loads forever.\n\treturn (\n\t\tindexEntries !== null ||\n\t\tconnectionStatus === \"reconnecting\" ||\n\t\tconnectionStatus === \"gone\"\n\t);\n}\n\n/**\n * Inclusive satellite conversation history backed by the user's Pinned\n * Statewire instance. The replicated window is the render source; refreshes,\n * pagination, thread creation, lifecycle changes, and title changes arrive on\n * that same channel. A non-empty search deliberately uses the authorized REST\n * list endpoint so matches are not limited to the replicated newest window.\n */\nexport function useStatewireConversationHistory({\n\tbackendUrl,\n\tconversationIndexBaseUrl,\n\tworkspaceId,\n\tprincipalId,\n\tgetAuth,\n\theaders,\n\tsearch,\n\tenabled = true,\n\tsse = false,\n}: UseStatewireConversationHistoryOptions) {\n\tconst index = useConversationIndex({\n\t\tbaseUrl: conversationIndexBaseUrl,\n\t\tworkspaceId: workspaceId || \"pending-workspace\",\n\t\tuserId: principalId || \"pending-user\",\n\t\theaders,\n\t\tenabled: enabled && workspaceId.length > 0 && principalId.length > 0,\n\t\tsse,\n\t});\n\t// Rolling deploy compatibility: the pre-paging index snapshot has no\n\t// total_count. It still renders live rows immediately, while the old REST\n\t// pager remains available only until the upgraded host activates.\n\tconst supportsStatewirePaging = index.totalCount !== null;\n\tconst normalizedSearch = search?.trim().toLowerCase() ?? \"\";\n\tconst hasSearch = normalizedSearch.length > 0;\n\tconst usesRestHistory = shouldUseRestConversationHistory({\n\t\thasSearch,\n\t\tindexEntries: index.entries,\n\t\tsupportsStatewirePaging,\n\t\tconnectionStatus: index.connection.status,\n\t});\n\tconst legacyHistory = useRestConversationHistory({\n\t\tbackendUrl,\n\t\tprincipalId,\n\t\tgetAuth,\n\t\tsearch,\n\t\tenabled: enabled && principalId.length > 0 && usesRestHistory,\n\t});\n\tconst [readOverrides, setReadOverrides] = useState<Record<string, boolean>>(\n\t\t{},\n\t);\n\tconst [archivedThreadIds, setArchivedThreadIds] = useState<\n\t\tReadonlySet<string>\n\t>(() => new Set());\n\n\tuseEffect(() => {\n\t\tif (index.entries === null || archivedThreadIds.size === 0) return;\n\t\tconst visibleIds = new Set(index.entries.map((entry) => entry.thread_id));\n\t\tsetArchivedThreadIds((current) => {\n\t\t\tconst next = new Set(\n\t\t\t\t[...current].filter((threadId) => visibleIds.has(threadId)),\n\t\t\t);\n\t\t\treturn next.size === current.size ? current : next;\n\t\t});\n\t}, [archivedThreadIds.size, index.entries]);\n\n\tconst sourceConversations = useMemo(\n\t\t() =>\n\t\t\tconversationHistorySource({\n\t\t\t\tindexEntries: index.entries,\n\t\t\t\trestConversations: legacyHistory.conversations,\n\t\t\t\tsupportsStatewirePaging,\n\t\t\t\thasSearch,\n\t\t\t}).filter((conversation) => !archivedThreadIds.has(conversation.threadId)),\n\t\t[\n\t\t\tarchivedThreadIds,\n\t\t\tindex.entries,\n\t\t\tlegacyHistory.conversations,\n\t\t\thasSearch,\n\t\t\tsupportsStatewirePaging,\n\t\t],\n\t);\n\t// The pin flips optimistically over both sources; the Statewire index\n\t// pushes the confirmed value, the REST page is patched with it, and the\n\t// override drops once the rendered source reports it.\n\tconst patchLegacyConversation = legacyHistory.patchConversation;\n\tconst favorite = useThreadFavorite({\n\t\tbackendUrl,\n\t\tprincipalId,\n\t\tgetAuth,\n\t\trows: sourceConversations,\n\t\tonServerTruth: ({ threadId, isFavorited }) =>\n\t\t\tpatchLegacyConversation(threadId, { isFavorited }),\n\t});\n\tconst favoriteOverrides = favorite.overrides;\n\n\tconst conversations = useMemo(\n\t\t() =>\n\t\t\twithFavoriteOverrides(sourceConversations, favoriteOverrides).map(\n\t\t\t\t(conversation) => ({\n\t\t\t\t\t...conversation,\n\t\t\t\t\tisUnread:\n\t\t\t\t\t\treadOverrides[conversation.threadId] ?? conversation.isUnread,\n\t\t\t\t}),\n\t\t\t),\n\t\t[sourceConversations, favoriteOverrides, readOverrides],\n\t);\n\n\tconst refreshMutation = useMutation({\n\t\tmutationKey: [\n\t\t\t\"conversation-index\",\n\t\t\t\"refresh\",\n\t\t\tconversationIndexBaseUrl,\n\t\t\tprincipalId,\n\t\t],\n\t\tmutationFn: () =>\n\t\t\tusesRestHistory ? legacyHistory.refresh() : index.refresh(),\n\t});\n\tconst loadMoreMutation = useMutation({\n\t\tmutationKey: [\n\t\t\t\"conversation-index\",\n\t\t\t\"load-more\",\n\t\t\tconversationIndexBaseUrl,\n\t\t\tprincipalId,\n\t\t],\n\t\tmutationFn: () =>\n\t\t\tusesRestHistory ? legacyHistory.fetchNextPage() : index.loadMore(),\n\t});\n\tconst archive = useMutation({\n\t\tmutationKey: [\"conversation-history\", \"archive\", backendUrl, principalId],\n\t\tmutationFn: async (threadId: string) => {\n\t\t\tawait archiveThread(backendUrl, await getAuth(), threadId);\n\t\t\treturn threadId;\n\t\t},\n\t\tonMutate: (threadId) => {\n\t\t\tsetArchivedThreadIds((current) => new Set(current).add(threadId));\n\t\t},\n\t\tonError: (_error, threadId) => {\n\t\t\tsetArchivedThreadIds((current) => {\n\t\t\t\tconst next = new Set(current);\n\t\t\t\tnext.delete(threadId);\n\t\t\t\treturn next;\n\t\t\t});\n\t\t},\n\t\tonSuccess: () => {\n\t\t\trefreshMutation.mutate();\n\t\t},\n\t});\n\tconst readState = useMutation({\n\t\tmutationKey: [\n\t\t\t\"conversation-history\",\n\t\t\t\"read-state\",\n\t\t\tbackendUrl,\n\t\t\tprincipalId,\n\t\t],\n\t\tmutationFn: async ({\n\t\t\tthreadId,\n\t\t\tisUnread,\n\t\t}: {\n\t\t\tthreadId: string;\n\t\t\tisUnread: boolean;\n\t\t}) =>\n\t\t\tsetThreadReadState(backendUrl, await getAuth(), { threadId, isUnread }),\n\t\tonMutate: ({ threadId, isUnread }) => {\n\t\t\tconst previous = readOverrides[threadId];\n\t\t\tsetReadOverrides((current) => ({ ...current, [threadId]: isUnread }));\n\t\t\treturn { previous, threadId };\n\t\t},\n\t\tonError: (_error, _variables, context) => {\n\t\t\tif (!context) return;\n\t\t\tsetReadOverrides((current) => {\n\t\t\t\tconst next = { ...current };\n\t\t\t\tif (context.previous === undefined) delete next[context.threadId];\n\t\t\t\telse next[context.threadId] = context.previous;\n\t\t\t\treturn next;\n\t\t\t});\n\t\t},\n\t\tonSuccess: (result) => {\n\t\t\tsetReadOverrides((current) => ({\n\t\t\t\t...current,\n\t\t\t\t[result.thread_id]: result.is_unread,\n\t\t\t}));\n\t\t\trefreshMutation.mutate(undefined, {\n\t\t\t\tonSettled: () => {\n\t\t\t\t\tsetReadOverrides((current) => {\n\t\t\t\t\t\tconst next = { ...current };\n\t\t\t\t\t\tdelete next[result.thread_id];\n\t\t\t\t\t\treturn next;\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t});\n\n\tconst refresh = useCallback(\n\t\t() => refreshMutation.mutateAsync(),\n\t\t[refreshMutation.mutateAsync],\n\t);\n\tconst fetchNextPage = useCallback(\n\t\t() => loadMoreMutation.mutateAsync(),\n\t\t[loadMoreMutation.mutateAsync],\n\t);\n\n\treturn {\n\t\tconversations,\n\t\ttotalCount: usesRestHistory ? legacyHistory.totalCount : index.totalCount,\n\t\tisLoading:\n\t\t\tenabled &&\n\t\t\t(usesRestHistory\n\t\t\t\t? !legacyHistory.hasResolvedFirstPage\n\t\t\t\t: index.entries === null),\n\t\tisFetching:\n\t\t\trefreshMutation.isPending ||\n\t\t\tloadMoreMutation.isPending ||\n\t\t\tlegacyHistory.isFetching,\n\t\tisRefreshing: refreshMutation.isPending,\n\t\thasResolvedFirstPage: usesRestHistory\n\t\t\t? legacyHistory.hasResolvedFirstPage\n\t\t\t: index.entries !== null,\n\t\thasNextPage: usesRestHistory\n\t\t\t? legacyHistory.hasNextPage\n\t\t\t: index.hasNextPage,\n\t\tisFetchingNextPage: loadMoreMutation.isPending,\n\t\tfetchNextPage,\n\t\trefresh,\n\t\tarchiveConversation: archive.mutate,\n\t\tarchivePending: archive.isPending,\n\t\tsetConversationReadState: readState.mutate,\n\t\treadStatePending: readState.isPending,\n\t\t/** Pin or unpin a conversation; read-only on a backend without the route (`favoriteToggleAvailable`). */\n\t\tsetConversationFavorited: favorite.setFavorited,\n\t\tfavoritePending: favorite.isPending,\n\t\t/** The conversations whose pin toggle is in flight. */\n\t\tfavoritePendingThreadIds: favorite.pendingThreadIds,\n\t\t/** `false` once the backend answered 404 / 405 to the favorite route. */\n\t\tfavoriteToggleAvailable: favorite.isAvailable,\n\t\tconnection: index.connection,\n\t};\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const reactQuery = require("@tanstack/react-query");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const api = require("./api.cjs");
|
|
6
|
+
const EMPTY_IDS = /* @__PURE__ */ new Set();
|
|
7
|
+
function archiveScope(backendUrl, principalId) {
|
|
8
|
+
return `${backendUrl}::${principalId}`;
|
|
9
|
+
}
|
|
10
|
+
function withId(ids, threadId, present) {
|
|
11
|
+
if (ids.has(threadId) === present) return ids;
|
|
12
|
+
const next = new Set(ids);
|
|
13
|
+
if (present) next.add(threadId);
|
|
14
|
+
else next.delete(threadId);
|
|
15
|
+
return next;
|
|
16
|
+
}
|
|
17
|
+
const warnedUnavailableBackends = /* @__PURE__ */ new Set();
|
|
18
|
+
function useThreadArchive({
|
|
19
|
+
backendUrl,
|
|
20
|
+
principalId,
|
|
21
|
+
getAuth,
|
|
22
|
+
onArchived
|
|
23
|
+
}) {
|
|
24
|
+
const scope = archiveScope(backendUrl, principalId);
|
|
25
|
+
const [state, setState] = react.useState(() => ({
|
|
26
|
+
scope,
|
|
27
|
+
hidden: EMPTY_IDS,
|
|
28
|
+
pending: EMPTY_IDS
|
|
29
|
+
}));
|
|
30
|
+
if (state.scope !== scope) {
|
|
31
|
+
setState({ scope, hidden: EMPTY_IDS, pending: EMPTY_IDS });
|
|
32
|
+
}
|
|
33
|
+
const hiddenThreadIds = state.scope === scope ? state.hidden : EMPTY_IDS;
|
|
34
|
+
const pendingThreadIds = state.scope === scope ? state.pending : EMPTY_IDS;
|
|
35
|
+
const [unavailableBackend, setUnavailableBackend] = react.useState(null);
|
|
36
|
+
const scopeRef = react.useRef(scope);
|
|
37
|
+
scopeRef.current = scope;
|
|
38
|
+
const onArchivedRef = react.useRef(onArchived);
|
|
39
|
+
onArchivedRef.current = onArchived;
|
|
40
|
+
const inFlightRef = react.useRef(/* @__PURE__ */ new Set());
|
|
41
|
+
const writeKey = (write) => `${write.scope}::${write.threadId}`;
|
|
42
|
+
const update = react.useCallback(
|
|
43
|
+
(forScope, patch) => {
|
|
44
|
+
setState((current) => {
|
|
45
|
+
const base = current.scope === forScope ? current : { scope: forScope, hidden: EMPTY_IDS, pending: EMPTY_IDS };
|
|
46
|
+
const next = patch(base);
|
|
47
|
+
return next === base && current.scope === forScope ? current : next;
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
[]
|
|
51
|
+
);
|
|
52
|
+
const mutation = reactQuery.useMutation({
|
|
53
|
+
mutationKey: ["conversation-history", "archive", backendUrl, principalId],
|
|
54
|
+
mutationFn: async (write) => {
|
|
55
|
+
await api.archiveThread(backendUrl, await getAuth(), write.threadId);
|
|
56
|
+
},
|
|
57
|
+
onSuccess: (_result, write) => {
|
|
58
|
+
var _a;
|
|
59
|
+
inFlightRef.current.delete(writeKey(write));
|
|
60
|
+
if (scopeRef.current !== write.scope) return;
|
|
61
|
+
update(write.scope, (current) => ({
|
|
62
|
+
...current,
|
|
63
|
+
pending: withId(current.pending, write.threadId, false)
|
|
64
|
+
}));
|
|
65
|
+
(_a = onArchivedRef.current) == null ? void 0 : _a.call(onArchivedRef, write.threadId);
|
|
66
|
+
},
|
|
67
|
+
onError: (error, write) => {
|
|
68
|
+
inFlightRef.current.delete(writeKey(write));
|
|
69
|
+
if (api.isThreadArchiveUnavailableError(error)) {
|
|
70
|
+
setUnavailableBackend(backendUrl);
|
|
71
|
+
if (!warnedUnavailableBackends.has(backendUrl)) {
|
|
72
|
+
warnedUnavailableBackends.add(backendUrl);
|
|
73
|
+
console.warn(
|
|
74
|
+
"[AthenaSDK] This backend has no POST /api/conversations/threads/archive route (older agora, or no checkpointer); sessions cannot be archived from here."
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (scopeRef.current !== write.scope) return;
|
|
79
|
+
update(write.scope, (current) => ({
|
|
80
|
+
...current,
|
|
81
|
+
pending: withId(current.pending, write.threadId, false),
|
|
82
|
+
hidden: withId(current.hidden, write.threadId, false)
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const mutate = mutation.mutate;
|
|
87
|
+
const archive = react.useCallback(
|
|
88
|
+
(threadId) => {
|
|
89
|
+
if (!threadId) return;
|
|
90
|
+
const write = { threadId, scope: scopeRef.current };
|
|
91
|
+
const key = writeKey(write);
|
|
92
|
+
if (inFlightRef.current.has(key)) return;
|
|
93
|
+
inFlightRef.current.add(key);
|
|
94
|
+
update(write.scope, (current) => ({
|
|
95
|
+
...current,
|
|
96
|
+
pending: withId(current.pending, threadId, true),
|
|
97
|
+
hidden: withId(current.hidden, threadId, true)
|
|
98
|
+
}));
|
|
99
|
+
mutate(write);
|
|
100
|
+
},
|
|
101
|
+
[mutate, update]
|
|
102
|
+
);
|
|
103
|
+
const release = react.useCallback(
|
|
104
|
+
(threadIds) => {
|
|
105
|
+
if (threadIds.length === 0) return;
|
|
106
|
+
update(scopeRef.current, (current) => {
|
|
107
|
+
const releasable = threadIds.filter(
|
|
108
|
+
(threadId) => current.hidden.has(threadId) && !current.pending.has(threadId)
|
|
109
|
+
);
|
|
110
|
+
if (releasable.length === 0) return current;
|
|
111
|
+
const hidden = new Set(current.hidden);
|
|
112
|
+
for (const threadId of releasable) hidden.delete(threadId);
|
|
113
|
+
return { ...current, hidden };
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
[update]
|
|
117
|
+
);
|
|
118
|
+
return {
|
|
119
|
+
hiddenThreadIds,
|
|
120
|
+
archive,
|
|
121
|
+
release,
|
|
122
|
+
isPending: pendingThreadIds.size > 0,
|
|
123
|
+
pendingThreadIds,
|
|
124
|
+
isAvailable: unavailableBackend !== backendUrl
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
exports.useThreadArchive = useThreadArchive;
|
|
128
|
+
//# sourceMappingURL=use-thread-archive.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-thread-archive.cjs","sources":["../../src/threads/use-thread-archive.ts"],"sourcesContent":["/**\n * The archive action behind `SessionList`: one TanStack mutation over\n * `POST /api/conversations/threads/archive` with an optimistic hidden set.\n *\n * The row leaves the list the moment the user asks and stays hidden until\n * the caller reports that neither list source (the fetched page, the pushed\n * conversation index) lists it any more (`release`) — the page is patched on\n * the server's answer, but the index may still carry the row until the\n * publish that follows the commit lands. A failure drops the id and the row\n * comes back from the still-cached page. A 405, or a 404 that does not name\n * the thread, marks the route unavailable — the backend predates it, or has\n * no checkpointer — so the UI withdraws the action; a 404 for a thread that\n * is gone is a per-row failure (`isThreadArchiveUnavailableError`).\n *\n * One write per thread at a time: a second `archive` for a thread whose\n * write is in flight is a no-op (the guard is synchronous, so two calls in\n * one tick send one request). Everything is scoped to `(backendUrl,\n * principalId)`: a credential swap discards the hidden and pending sets, and\n * an answer for the previous identity is ignored when it lands, so a same-id\n * row under the new identity is never removed and the new user is never\n * moved off their active thread by the old user's archive.\n */\nimport { useMutation } from '@tanstack/react-query';\nimport { useCallback, useRef, useState } from 'react';\nimport { archiveThread, isThreadArchiveUnavailableError } from './api';\nimport type { RestConversationAuth } from './use-rest-conversation-history';\n\nexport interface UseThreadArchiveOptions {\n backendUrl: string;\n /** Non-secret identity in the mutation key (as the other thread writes key). */\n principalId: string;\n getAuth: () => RestConversationAuth | Promise<RestConversationAuth>;\n /** The server confirmed the archive: patch the caches, leave the thread if it is active. */\n onArchived?: (threadId: string) => void;\n}\n\nexport interface ThreadArchive {\n /** Threads hidden from the rows: asked to archive, not yet forgotten by every source. */\n hiddenThreadIds: ReadonlySet<string>;\n /** Archive a thread; a no-op while that thread's write is in flight. */\n archive: (threadId: string) => void;\n /** Drop hidden ids the caller has seen every source forget (never while their write is in flight). */\n release: (threadIds: readonly string[]) => void;\n /** True while any archive is in flight. */\n isPending: boolean;\n /** The threads whose archive is in flight — the action disables itself for these. */\n pendingThreadIds: ReadonlySet<string>;\n /** `false` once the backend answered that it has no archive route. */\n isAvailable: boolean;\n}\n\ninterface ScopedArchiveState {\n scope: string;\n hidden: ReadonlySet<string>;\n pending: ReadonlySet<string>;\n}\n\ninterface ArchiveWrite {\n threadId: string;\n /** The identity the write was issued for; an answer for another one is ignored. */\n scope: string;\n}\n\nconst EMPTY_IDS: ReadonlySet<string> = new Set();\n\nfunction archiveScope(backendUrl: string, principalId: string): string {\n return `${backendUrl}::${principalId}`;\n}\n\nfunction withId(ids: ReadonlySet<string>, threadId: string, present: boolean): ReadonlySet<string> {\n if (ids.has(threadId) === present) return ids;\n const next = new Set(ids);\n if (present) next.add(threadId);\n else next.delete(threadId);\n return next;\n}\n\n/** Backends already warned about, so an older agora logs one line per page load, not one per click. */\nconst warnedUnavailableBackends = new Set<string>();\n\n/** Test seam: forget which backends were warned about. */\nexport function resetThreadArchiveUnavailableWarnings(): void {\n warnedUnavailableBackends.clear();\n}\n\nexport function useThreadArchive({\n backendUrl,\n principalId,\n getAuth,\n onArchived,\n}: UseThreadArchiveOptions): ThreadArchive {\n const scope = archiveScope(backendUrl, principalId);\n const [state, setState] = useState<ScopedArchiveState>(() => ({\n scope,\n hidden: EMPTY_IDS,\n pending: EMPTY_IDS,\n }));\n // An identity swap discards everything held for the previous identity\n // (derived-state reset during render; see `useThreadFieldOverride`).\n if (state.scope !== scope) {\n setState({ scope, hidden: EMPTY_IDS, pending: EMPTY_IDS });\n }\n const hiddenThreadIds = state.scope === scope ? state.hidden : EMPTY_IDS;\n const pendingThreadIds = state.scope === scope ? state.pending : EMPTY_IDS;\n // The route is a property of the backend, not of who is asking.\n const [unavailableBackend, setUnavailableBackend] = useState<string | null>(null);\n\n const scopeRef = useRef(scope);\n scopeRef.current = scope;\n const onArchivedRef = useRef(onArchived);\n onArchivedRef.current = onArchived;\n // Writes in flight, keyed by identity and thread: the synchronous guard\n // behind the one-write-per-thread rule. A previous identity's entries are\n // left to expire with their answers (which the scope guard drops).\n const inFlightRef = useRef<Set<string>>(new Set());\n const writeKey = (write: ArchiveWrite) => `${write.scope}::${write.threadId}`;\n\n const update = useCallback(\n (forScope: string, patch: (current: ScopedArchiveState) => ScopedArchiveState) => {\n setState((current) => {\n const base: ScopedArchiveState =\n current.scope === forScope ? current : { scope: forScope, hidden: EMPTY_IDS, pending: EMPTY_IDS };\n const next = patch(base);\n return next === base && current.scope === forScope ? current : next;\n });\n },\n [],\n );\n\n const mutation = useMutation({\n mutationKey: ['conversation-history', 'archive', backendUrl, principalId],\n mutationFn: async (write: ArchiveWrite) => {\n await archiveThread(backendUrl, await getAuth(), write.threadId);\n },\n onSuccess: (_result, write) => {\n inFlightRef.current.delete(writeKey(write));\n if (scopeRef.current !== write.scope) return;\n update(write.scope, (current) => ({\n ...current,\n pending: withId(current.pending, write.threadId, false),\n }));\n onArchivedRef.current?.(write.threadId);\n },\n onError: (error, write) => {\n inFlightRef.current.delete(writeKey(write));\n if (isThreadArchiveUnavailableError(error)) {\n setUnavailableBackend(backendUrl);\n if (!warnedUnavailableBackends.has(backendUrl)) {\n warnedUnavailableBackends.add(backendUrl);\n console.warn(\n '[AthenaSDK] This backend has no POST /api/conversations/threads/archive route (older agora, or no checkpointer); sessions cannot be archived from here.',\n );\n }\n }\n if (scopeRef.current !== write.scope) return;\n // The row comes back from the still-cached page.\n update(write.scope, (current) => ({\n ...current,\n pending: withId(current.pending, write.threadId, false),\n hidden: withId(current.hidden, write.threadId, false),\n }));\n },\n });\n const mutate = mutation.mutate;\n\n const archive = useCallback(\n (threadId: string) => {\n if (!threadId) return;\n const write: ArchiveWrite = { threadId, scope: scopeRef.current };\n const key = writeKey(write);\n if (inFlightRef.current.has(key)) return;\n inFlightRef.current.add(key);\n update(write.scope, (current) => ({\n ...current,\n pending: withId(current.pending, threadId, true),\n hidden: withId(current.hidden, threadId, true),\n }));\n mutate(write);\n },\n [mutate, update],\n );\n\n const release = useCallback(\n (threadIds: readonly string[]) => {\n if (threadIds.length === 0) return;\n update(scopeRef.current, (current) => {\n // A hidden row with a write in flight stays hidden whatever the\n // sources say: they have not seen the archive yet.\n const releasable = threadIds.filter(\n (threadId) => current.hidden.has(threadId) && !current.pending.has(threadId),\n );\n if (releasable.length === 0) return current;\n const hidden = new Set(current.hidden);\n for (const threadId of releasable) hidden.delete(threadId);\n return { ...current, hidden };\n });\n },\n [update],\n );\n\n return {\n hiddenThreadIds,\n archive,\n release,\n isPending: pendingThreadIds.size > 0,\n pendingThreadIds,\n isAvailable: unavailableBackend !== backendUrl,\n };\n}\n"],"names":["useState","useRef","useCallback","useMutation","archiveThread","isThreadArchiveUnavailableError"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { RestConversationAuth } from './use-rest-conversation-history';
|
|
2
|
+
export interface UseThreadArchiveOptions {
|
|
3
|
+
backendUrl: string;
|
|
4
|
+
/** Non-secret identity in the mutation key (as the other thread writes key). */
|
|
5
|
+
principalId: string;
|
|
6
|
+
getAuth: () => RestConversationAuth | Promise<RestConversationAuth>;
|
|
7
|
+
/** The server confirmed the archive: patch the caches, leave the thread if it is active. */
|
|
8
|
+
onArchived?: (threadId: string) => void;
|
|
9
|
+
}
|
|
10
|
+
export interface ThreadArchive {
|
|
11
|
+
/** Threads hidden from the rows: asked to archive, not yet forgotten by every source. */
|
|
12
|
+
hiddenThreadIds: ReadonlySet<string>;
|
|
13
|
+
/** Archive a thread; a no-op while that thread's write is in flight. */
|
|
14
|
+
archive: (threadId: string) => void;
|
|
15
|
+
/** Drop hidden ids the caller has seen every source forget (never while their write is in flight). */
|
|
16
|
+
release: (threadIds: readonly string[]) => void;
|
|
17
|
+
/** True while any archive is in flight. */
|
|
18
|
+
isPending: boolean;
|
|
19
|
+
/** The threads whose archive is in flight — the action disables itself for these. */
|
|
20
|
+
pendingThreadIds: ReadonlySet<string>;
|
|
21
|
+
/** `false` once the backend answered that it has no archive route. */
|
|
22
|
+
isAvailable: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Test seam: forget which backends were warned about. */
|
|
25
|
+
export declare function resetThreadArchiveUnavailableWarnings(): void;
|
|
26
|
+
export declare function useThreadArchive({ backendUrl, principalId, getAuth, onArchived, }: UseThreadArchiveOptions): ThreadArchive;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { useMutation } from "@tanstack/react-query";
|
|
2
|
+
import { useState, useRef, useCallback } from "react";
|
|
3
|
+
import { isThreadArchiveUnavailableError, archiveThread } from "./api.js";
|
|
4
|
+
const EMPTY_IDS = /* @__PURE__ */ new Set();
|
|
5
|
+
function archiveScope(backendUrl, principalId) {
|
|
6
|
+
return `${backendUrl}::${principalId}`;
|
|
7
|
+
}
|
|
8
|
+
function withId(ids, threadId, present) {
|
|
9
|
+
if (ids.has(threadId) === present) return ids;
|
|
10
|
+
const next = new Set(ids);
|
|
11
|
+
if (present) next.add(threadId);
|
|
12
|
+
else next.delete(threadId);
|
|
13
|
+
return next;
|
|
14
|
+
}
|
|
15
|
+
const warnedUnavailableBackends = /* @__PURE__ */ new Set();
|
|
16
|
+
function useThreadArchive({
|
|
17
|
+
backendUrl,
|
|
18
|
+
principalId,
|
|
19
|
+
getAuth,
|
|
20
|
+
onArchived
|
|
21
|
+
}) {
|
|
22
|
+
const scope = archiveScope(backendUrl, principalId);
|
|
23
|
+
const [state, setState] = useState(() => ({
|
|
24
|
+
scope,
|
|
25
|
+
hidden: EMPTY_IDS,
|
|
26
|
+
pending: EMPTY_IDS
|
|
27
|
+
}));
|
|
28
|
+
if (state.scope !== scope) {
|
|
29
|
+
setState({ scope, hidden: EMPTY_IDS, pending: EMPTY_IDS });
|
|
30
|
+
}
|
|
31
|
+
const hiddenThreadIds = state.scope === scope ? state.hidden : EMPTY_IDS;
|
|
32
|
+
const pendingThreadIds = state.scope === scope ? state.pending : EMPTY_IDS;
|
|
33
|
+
const [unavailableBackend, setUnavailableBackend] = useState(null);
|
|
34
|
+
const scopeRef = useRef(scope);
|
|
35
|
+
scopeRef.current = scope;
|
|
36
|
+
const onArchivedRef = useRef(onArchived);
|
|
37
|
+
onArchivedRef.current = onArchived;
|
|
38
|
+
const inFlightRef = useRef(/* @__PURE__ */ new Set());
|
|
39
|
+
const writeKey = (write) => `${write.scope}::${write.threadId}`;
|
|
40
|
+
const update = useCallback(
|
|
41
|
+
(forScope, patch) => {
|
|
42
|
+
setState((current) => {
|
|
43
|
+
const base = current.scope === forScope ? current : { scope: forScope, hidden: EMPTY_IDS, pending: EMPTY_IDS };
|
|
44
|
+
const next = patch(base);
|
|
45
|
+
return next === base && current.scope === forScope ? current : next;
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
[]
|
|
49
|
+
);
|
|
50
|
+
const mutation = useMutation({
|
|
51
|
+
mutationKey: ["conversation-history", "archive", backendUrl, principalId],
|
|
52
|
+
mutationFn: async (write) => {
|
|
53
|
+
await archiveThread(backendUrl, await getAuth(), write.threadId);
|
|
54
|
+
},
|
|
55
|
+
onSuccess: (_result, write) => {
|
|
56
|
+
var _a;
|
|
57
|
+
inFlightRef.current.delete(writeKey(write));
|
|
58
|
+
if (scopeRef.current !== write.scope) return;
|
|
59
|
+
update(write.scope, (current) => ({
|
|
60
|
+
...current,
|
|
61
|
+
pending: withId(current.pending, write.threadId, false)
|
|
62
|
+
}));
|
|
63
|
+
(_a = onArchivedRef.current) == null ? void 0 : _a.call(onArchivedRef, write.threadId);
|
|
64
|
+
},
|
|
65
|
+
onError: (error, write) => {
|
|
66
|
+
inFlightRef.current.delete(writeKey(write));
|
|
67
|
+
if (isThreadArchiveUnavailableError(error)) {
|
|
68
|
+
setUnavailableBackend(backendUrl);
|
|
69
|
+
if (!warnedUnavailableBackends.has(backendUrl)) {
|
|
70
|
+
warnedUnavailableBackends.add(backendUrl);
|
|
71
|
+
console.warn(
|
|
72
|
+
"[AthenaSDK] This backend has no POST /api/conversations/threads/archive route (older agora, or no checkpointer); sessions cannot be archived from here."
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (scopeRef.current !== write.scope) return;
|
|
77
|
+
update(write.scope, (current) => ({
|
|
78
|
+
...current,
|
|
79
|
+
pending: withId(current.pending, write.threadId, false),
|
|
80
|
+
hidden: withId(current.hidden, write.threadId, false)
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
const mutate = mutation.mutate;
|
|
85
|
+
const archive = useCallback(
|
|
86
|
+
(threadId) => {
|
|
87
|
+
if (!threadId) return;
|
|
88
|
+
const write = { threadId, scope: scopeRef.current };
|
|
89
|
+
const key = writeKey(write);
|
|
90
|
+
if (inFlightRef.current.has(key)) return;
|
|
91
|
+
inFlightRef.current.add(key);
|
|
92
|
+
update(write.scope, (current) => ({
|
|
93
|
+
...current,
|
|
94
|
+
pending: withId(current.pending, threadId, true),
|
|
95
|
+
hidden: withId(current.hidden, threadId, true)
|
|
96
|
+
}));
|
|
97
|
+
mutate(write);
|
|
98
|
+
},
|
|
99
|
+
[mutate, update]
|
|
100
|
+
);
|
|
101
|
+
const release = useCallback(
|
|
102
|
+
(threadIds) => {
|
|
103
|
+
if (threadIds.length === 0) return;
|
|
104
|
+
update(scopeRef.current, (current) => {
|
|
105
|
+
const releasable = threadIds.filter(
|
|
106
|
+
(threadId) => current.hidden.has(threadId) && !current.pending.has(threadId)
|
|
107
|
+
);
|
|
108
|
+
if (releasable.length === 0) return current;
|
|
109
|
+
const hidden = new Set(current.hidden);
|
|
110
|
+
for (const threadId of releasable) hidden.delete(threadId);
|
|
111
|
+
return { ...current, hidden };
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
[update]
|
|
115
|
+
);
|
|
116
|
+
return {
|
|
117
|
+
hiddenThreadIds,
|
|
118
|
+
archive,
|
|
119
|
+
release,
|
|
120
|
+
isPending: pendingThreadIds.size > 0,
|
|
121
|
+
pendingThreadIds,
|
|
122
|
+
isAvailable: unavailableBackend !== backendUrl
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
useThreadArchive
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=use-thread-archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-thread-archive.js","sources":["../../src/threads/use-thread-archive.ts"],"sourcesContent":["/**\n * The archive action behind `SessionList`: one TanStack mutation over\n * `POST /api/conversations/threads/archive` with an optimistic hidden set.\n *\n * The row leaves the list the moment the user asks and stays hidden until\n * the caller reports that neither list source (the fetched page, the pushed\n * conversation index) lists it any more (`release`) — the page is patched on\n * the server's answer, but the index may still carry the row until the\n * publish that follows the commit lands. A failure drops the id and the row\n * comes back from the still-cached page. A 405, or a 404 that does not name\n * the thread, marks the route unavailable — the backend predates it, or has\n * no checkpointer — so the UI withdraws the action; a 404 for a thread that\n * is gone is a per-row failure (`isThreadArchiveUnavailableError`).\n *\n * One write per thread at a time: a second `archive` for a thread whose\n * write is in flight is a no-op (the guard is synchronous, so two calls in\n * one tick send one request). Everything is scoped to `(backendUrl,\n * principalId)`: a credential swap discards the hidden and pending sets, and\n * an answer for the previous identity is ignored when it lands, so a same-id\n * row under the new identity is never removed and the new user is never\n * moved off their active thread by the old user's archive.\n */\nimport { useMutation } from '@tanstack/react-query';\nimport { useCallback, useRef, useState } from 'react';\nimport { archiveThread, isThreadArchiveUnavailableError } from './api';\nimport type { RestConversationAuth } from './use-rest-conversation-history';\n\nexport interface UseThreadArchiveOptions {\n backendUrl: string;\n /** Non-secret identity in the mutation key (as the other thread writes key). */\n principalId: string;\n getAuth: () => RestConversationAuth | Promise<RestConversationAuth>;\n /** The server confirmed the archive: patch the caches, leave the thread if it is active. */\n onArchived?: (threadId: string) => void;\n}\n\nexport interface ThreadArchive {\n /** Threads hidden from the rows: asked to archive, not yet forgotten by every source. */\n hiddenThreadIds: ReadonlySet<string>;\n /** Archive a thread; a no-op while that thread's write is in flight. */\n archive: (threadId: string) => void;\n /** Drop hidden ids the caller has seen every source forget (never while their write is in flight). */\n release: (threadIds: readonly string[]) => void;\n /** True while any archive is in flight. */\n isPending: boolean;\n /** The threads whose archive is in flight — the action disables itself for these. */\n pendingThreadIds: ReadonlySet<string>;\n /** `false` once the backend answered that it has no archive route. */\n isAvailable: boolean;\n}\n\ninterface ScopedArchiveState {\n scope: string;\n hidden: ReadonlySet<string>;\n pending: ReadonlySet<string>;\n}\n\ninterface ArchiveWrite {\n threadId: string;\n /** The identity the write was issued for; an answer for another one is ignored. */\n scope: string;\n}\n\nconst EMPTY_IDS: ReadonlySet<string> = new Set();\n\nfunction archiveScope(backendUrl: string, principalId: string): string {\n return `${backendUrl}::${principalId}`;\n}\n\nfunction withId(ids: ReadonlySet<string>, threadId: string, present: boolean): ReadonlySet<string> {\n if (ids.has(threadId) === present) return ids;\n const next = new Set(ids);\n if (present) next.add(threadId);\n else next.delete(threadId);\n return next;\n}\n\n/** Backends already warned about, so an older agora logs one line per page load, not one per click. */\nconst warnedUnavailableBackends = new Set<string>();\n\n/** Test seam: forget which backends were warned about. */\nexport function resetThreadArchiveUnavailableWarnings(): void {\n warnedUnavailableBackends.clear();\n}\n\nexport function useThreadArchive({\n backendUrl,\n principalId,\n getAuth,\n onArchived,\n}: UseThreadArchiveOptions): ThreadArchive {\n const scope = archiveScope(backendUrl, principalId);\n const [state, setState] = useState<ScopedArchiveState>(() => ({\n scope,\n hidden: EMPTY_IDS,\n pending: EMPTY_IDS,\n }));\n // An identity swap discards everything held for the previous identity\n // (derived-state reset during render; see `useThreadFieldOverride`).\n if (state.scope !== scope) {\n setState({ scope, hidden: EMPTY_IDS, pending: EMPTY_IDS });\n }\n const hiddenThreadIds = state.scope === scope ? state.hidden : EMPTY_IDS;\n const pendingThreadIds = state.scope === scope ? state.pending : EMPTY_IDS;\n // The route is a property of the backend, not of who is asking.\n const [unavailableBackend, setUnavailableBackend] = useState<string | null>(null);\n\n const scopeRef = useRef(scope);\n scopeRef.current = scope;\n const onArchivedRef = useRef(onArchived);\n onArchivedRef.current = onArchived;\n // Writes in flight, keyed by identity and thread: the synchronous guard\n // behind the one-write-per-thread rule. A previous identity's entries are\n // left to expire with their answers (which the scope guard drops).\n const inFlightRef = useRef<Set<string>>(new Set());\n const writeKey = (write: ArchiveWrite) => `${write.scope}::${write.threadId}`;\n\n const update = useCallback(\n (forScope: string, patch: (current: ScopedArchiveState) => ScopedArchiveState) => {\n setState((current) => {\n const base: ScopedArchiveState =\n current.scope === forScope ? current : { scope: forScope, hidden: EMPTY_IDS, pending: EMPTY_IDS };\n const next = patch(base);\n return next === base && current.scope === forScope ? current : next;\n });\n },\n [],\n );\n\n const mutation = useMutation({\n mutationKey: ['conversation-history', 'archive', backendUrl, principalId],\n mutationFn: async (write: ArchiveWrite) => {\n await archiveThread(backendUrl, await getAuth(), write.threadId);\n },\n onSuccess: (_result, write) => {\n inFlightRef.current.delete(writeKey(write));\n if (scopeRef.current !== write.scope) return;\n update(write.scope, (current) => ({\n ...current,\n pending: withId(current.pending, write.threadId, false),\n }));\n onArchivedRef.current?.(write.threadId);\n },\n onError: (error, write) => {\n inFlightRef.current.delete(writeKey(write));\n if (isThreadArchiveUnavailableError(error)) {\n setUnavailableBackend(backendUrl);\n if (!warnedUnavailableBackends.has(backendUrl)) {\n warnedUnavailableBackends.add(backendUrl);\n console.warn(\n '[AthenaSDK] This backend has no POST /api/conversations/threads/archive route (older agora, or no checkpointer); sessions cannot be archived from here.',\n );\n }\n }\n if (scopeRef.current !== write.scope) return;\n // The row comes back from the still-cached page.\n update(write.scope, (current) => ({\n ...current,\n pending: withId(current.pending, write.threadId, false),\n hidden: withId(current.hidden, write.threadId, false),\n }));\n },\n });\n const mutate = mutation.mutate;\n\n const archive = useCallback(\n (threadId: string) => {\n if (!threadId) return;\n const write: ArchiveWrite = { threadId, scope: scopeRef.current };\n const key = writeKey(write);\n if (inFlightRef.current.has(key)) return;\n inFlightRef.current.add(key);\n update(write.scope, (current) => ({\n ...current,\n pending: withId(current.pending, threadId, true),\n hidden: withId(current.hidden, threadId, true),\n }));\n mutate(write);\n },\n [mutate, update],\n );\n\n const release = useCallback(\n (threadIds: readonly string[]) => {\n if (threadIds.length === 0) return;\n update(scopeRef.current, (current) => {\n // A hidden row with a write in flight stays hidden whatever the\n // sources say: they have not seen the archive yet.\n const releasable = threadIds.filter(\n (threadId) => current.hidden.has(threadId) && !current.pending.has(threadId),\n );\n if (releasable.length === 0) return current;\n const hidden = new Set(current.hidden);\n for (const threadId of releasable) hidden.delete(threadId);\n return { ...current, hidden };\n });\n },\n [update],\n );\n\n return {\n hiddenThreadIds,\n archive,\n release,\n isPending: pendingThreadIds.size > 0,\n pendingThreadIds,\n isAvailable: unavailableBackend !== backendUrl,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const react = require("react");
|
|
4
|
+
const api = require("./api.cjs");
|
|
5
|
+
const useThreadFieldOverride = require("./use-thread-field-override.cjs");
|
|
6
|
+
const FAVORITE_UNAVAILABLE_WARNING = "[AthenaSDK] This backend has no POST /api/conversations/threads/favorite route yet; session pins stay read-only until it deploys.";
|
|
7
|
+
const readFavorite = (row) => row.isFavorited ?? false;
|
|
8
|
+
function useThreadFavorite({
|
|
9
|
+
backendUrl,
|
|
10
|
+
principalId,
|
|
11
|
+
getAuth,
|
|
12
|
+
rows,
|
|
13
|
+
onServerTruth
|
|
14
|
+
}) {
|
|
15
|
+
const field = useThreadFieldOverride.useThreadFieldOverride({
|
|
16
|
+
field: "favorite",
|
|
17
|
+
backendUrl,
|
|
18
|
+
principalId,
|
|
19
|
+
getAuth,
|
|
20
|
+
rows,
|
|
21
|
+
readRow: readFavorite,
|
|
22
|
+
write: async (auth, { threadId, value }) => {
|
|
23
|
+
const result = await api.setThreadFavorite(backendUrl, auth, { threadId, isFavorited: value });
|
|
24
|
+
return { threadId: result.thread_id, value: result.is_favorited };
|
|
25
|
+
},
|
|
26
|
+
isUnavailableError: api.isThreadFavoriteUnavailableError,
|
|
27
|
+
unavailableWarning: FAVORITE_UNAVAILABLE_WARNING,
|
|
28
|
+
onServerTruth: onServerTruth ? ({ threadId, value }) => onServerTruth({ threadId, isFavorited: value }) : void 0
|
|
29
|
+
});
|
|
30
|
+
const set = field.set;
|
|
31
|
+
const setFavorited = react.useCallback(
|
|
32
|
+
({ threadId, isFavorited }) => set({ threadId, value: isFavorited }),
|
|
33
|
+
[set]
|
|
34
|
+
);
|
|
35
|
+
return {
|
|
36
|
+
overrides: field.overrides,
|
|
37
|
+
setFavorited,
|
|
38
|
+
isPending: field.isPending,
|
|
39
|
+
pendingThreadIds: field.pendingThreadIds,
|
|
40
|
+
isAvailable: field.isAvailable
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function withFavoriteOverride(row, overrides) {
|
|
44
|
+
const override = overrides[row.threadId];
|
|
45
|
+
return override === void 0 || override === row.isFavorited ? row : { ...row, isFavorited: override };
|
|
46
|
+
}
|
|
47
|
+
function withFavoriteOverrides(rows, overrides) {
|
|
48
|
+
return rows.map((row) => withFavoriteOverride(row, overrides));
|
|
49
|
+
}
|
|
50
|
+
exports.useThreadFavorite = useThreadFavorite;
|
|
51
|
+
exports.withFavoriteOverride = withFavoriteOverride;
|
|
52
|
+
exports.withFavoriteOverrides = withFavoriteOverrides;
|
|
53
|
+
//# sourceMappingURL=use-thread-favorite.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-thread-favorite.cjs","sources":["../../src/threads/use-thread-favorite.ts"],"sourcesContent":["/**\n * The pin toggle behind `SessionList` and the conversation-history hooks:\n * `useThreadFieldOverride` over `POST /api/conversations/threads/favorite`\n * (see that module for the optimistic-override, rollback, serialization and\n * identity-scoping contract). A 404 / 405 marks the toggle unavailable — the\n * backend predates the route — so the UI falls back to the read-only pin\n * instead of failing on every click.\n */\nimport { useCallback } from 'react';\nimport { isThreadFavoriteUnavailableError, setThreadFavorite } from './api';\nimport type { RestConversationAuth } from './use-rest-conversation-history';\nimport {\n resetThreadFieldUnavailableWarnings,\n type ThreadFieldTarget,\n useThreadFieldOverride,\n} from './use-thread-field-override';\n\nexport interface ThreadFavoriteTarget {\n threadId: string;\n isFavorited: boolean;\n}\n\n/** The two fields the override layer reads and writes on a list row. */\nexport interface FavoriteRow {\n threadId: string;\n isFavorited?: boolean;\n}\n\nexport interface UseThreadFavoriteOptions {\n backendUrl: string;\n /** Non-secret identity in the mutation key (as the read-state mutation keys). */\n principalId: string;\n getAuth: () => RestConversationAuth | Promise<RestConversationAuth>;\n /**\n * The rows as the list sources currently report them, BEFORE overrides. An\n * override is dropped once its row reports the same value.\n */\n rows: readonly FavoriteRow[];\n /** Write the server's answer into the list caches (`patchConversation`). */\n onServerTruth?: (result: ThreadFavoriteTarget) => void;\n}\n\nexport interface ThreadFavoriteToggle {\n /** Optimistic, then server-confirmed, values layered over the list sources, keyed by thread id. */\n overrides: Readonly<Record<string, boolean>>;\n /**\n * Pin or unpin a thread. Flips the row at once; while that thread's write\n * is in flight the call only records the desired value (see module doc).\n */\n setFavorited: (target: ThreadFavoriteTarget) => void;\n /** True while any toggle is in flight. */\n isPending: boolean;\n /** The threads whose toggle is in flight — a pin control disables itself for these. */\n pendingThreadIds: ReadonlySet<string>;\n /**\n * `false` once the backend answered 404 / 405: the route is not deployed\n * there, and the UI shows the read-only pin instead of a toggle.\n */\n isAvailable: boolean;\n}\n\n/** Test seam: forget which backends were warned about (shared with the other field writes). */\nexport const resetThreadFavoriteUnavailableWarnings = resetThreadFieldUnavailableWarnings;\n\nconst FAVORITE_UNAVAILABLE_WARNING =\n '[AthenaSDK] This backend has no POST /api/conversations/threads/favorite route yet; session pins stay read-only until it deploys.';\n\nconst readFavorite = (row: FavoriteRow): boolean => row.isFavorited ?? false;\n\nexport function useThreadFavorite({\n backendUrl,\n principalId,\n getAuth,\n rows,\n onServerTruth,\n}: UseThreadFavoriteOptions): ThreadFavoriteToggle {\n const field = useThreadFieldOverride<boolean, FavoriteRow>({\n field: 'favorite',\n backendUrl,\n principalId,\n getAuth,\n rows,\n readRow: readFavorite,\n write: async (auth, { threadId, value }) => {\n const result = await setThreadFavorite(backendUrl, auth, { threadId, isFavorited: value });\n return { threadId: result.thread_id, value: result.is_favorited };\n },\n isUnavailableError: isThreadFavoriteUnavailableError,\n unavailableWarning: FAVORITE_UNAVAILABLE_WARNING,\n onServerTruth: onServerTruth\n ? ({ threadId, value }: ThreadFieldTarget<boolean>) =>\n onServerTruth({ threadId, isFavorited: value })\n : undefined,\n });\n const set = field.set;\n const setFavorited = useCallback(\n ({ threadId, isFavorited }: ThreadFavoriteTarget) => set({ threadId, value: isFavorited }),\n [set],\n );\n return {\n overrides: field.overrides,\n setFavorited,\n isPending: field.isPending,\n pendingThreadIds: field.pendingThreadIds,\n isAvailable: field.isAvailable,\n };\n}\n\n/** The row with its override applied, or the row itself when it has none. */\nexport function withFavoriteOverride<T extends FavoriteRow>(\n row: T,\n overrides: Readonly<Record<string, boolean>>,\n): T {\n const override = overrides[row.threadId];\n return override === undefined || override === row.isFavorited\n ? row\n : { ...row, isFavorited: override };\n}\n\n/** Every row with its override applied. */\nexport function withFavoriteOverrides<T extends FavoriteRow>(\n rows: readonly T[],\n overrides: Readonly<Record<string, boolean>>,\n): T[] {\n return rows.map((row) => withFavoriteOverride(row, overrides));\n}\n"],"names":["useThreadFieldOverride","setThreadFavorite","isThreadFavoriteUnavailableError","useCallback"],"mappings":";;;;;AAgEA,MAAM,+BACJ;AAEF,MAAM,eAAe,CAAC,QAA8B,IAAI,eAAe;AAEhE,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAmD;AACjD,QAAM,QAAQA,uBAAAA,uBAA6C;AAAA,IACzD,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT,OAAO,OAAO,MAAM,EAAE,UAAU,YAAY;AAC1C,YAAM,SAAS,MAAMC,IAAAA,kBAAkB,YAAY,MAAM,EAAE,UAAU,aAAa,OAAO;AACzF,aAAO,EAAE,UAAU,OAAO,WAAW,OAAO,OAAO,aAAA;AAAA,IACrD;AAAA,IACA,oBAAoBC,IAAAA;AAAAA,IACpB,oBAAoB;AAAA,IACpB,eAAe,gBACX,CAAC,EAAE,UAAU,MAAA,MACX,cAAc,EAAE,UAAU,aAAa,MAAA,CAAO,IAChD;AAAA,EAAA,CACL;AACD,QAAM,MAAM,MAAM;AAClB,QAAM,eAAeC,MAAAA;AAAAA,IACnB,CAAC,EAAE,UAAU,kBAAwC,IAAI,EAAE,UAAU,OAAO,aAAa;AAAA,IACzF,CAAC,GAAG;AAAA,EAAA;AAEN,SAAO;AAAA,IACL,WAAW,MAAM;AAAA,IACjB;AAAA,IACA,WAAW,MAAM;AAAA,IACjB,kBAAkB,MAAM;AAAA,IACxB,aAAa,MAAM;AAAA,EAAA;AAEvB;AAGO,SAAS,qBACd,KACA,WACG;AACH,QAAM,WAAW,UAAU,IAAI,QAAQ;AACvC,SAAO,aAAa,UAAa,aAAa,IAAI,cAC9C,MACA,EAAE,GAAG,KAAK,aAAa,SAAA;AAC7B;AAGO,SAAS,sBACd,MACA,WACK;AACL,SAAO,KAAK,IAAI,CAAC,QAAQ,qBAAqB,KAAK,SAAS,CAAC;AAC/D;;;;"}
|