@cossistant/react 0.0.4 → 0.0.5
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/_virtual/rolldown_runtime.js +19 -0
- package/conversation.d.ts +26 -3
- package/conversation.d.ts.map +1 -1
- package/hooks/index.d.ts +5 -3
- package/hooks/index.js +7 -5
- package/hooks/private/store/use-conversations-store.d.ts +8 -0
- package/hooks/private/store/use-conversations-store.d.ts.map +1 -1
- package/hooks/private/store/use-conversations-store.js +8 -0
- package/hooks/private/store/use-conversations-store.js.map +1 -1
- package/hooks/private/store/use-store-selector.d.ts +4 -0
- package/hooks/private/store/use-store-selector.d.ts.map +1 -1
- package/hooks/private/store/use-store-selector.js +5 -2
- package/hooks/private/store/use-store-selector.js.map +1 -1
- package/hooks/private/store/use-website-store.d.ts +4 -0
- package/hooks/private/store/use-website-store.d.ts.map +1 -1
- package/hooks/private/store/use-website-store.js +6 -3
- package/hooks/private/store/use-website-store.js.map +1 -1
- package/hooks/private/typing.d.ts +35 -0
- package/hooks/private/typing.d.ts.map +1 -0
- package/hooks/private/typing.js +49 -0
- package/hooks/private/typing.js.map +1 -0
- package/hooks/private/use-client-query.d.ts +5 -0
- package/hooks/private/use-client-query.d.ts.map +1 -1
- package/hooks/private/use-client-query.js +5 -0
- package/hooks/private/use-client-query.js.map +1 -1
- package/hooks/private/use-grouped-messages.d.ts +10 -4
- package/hooks/private/use-grouped-messages.d.ts.map +1 -1
- package/hooks/private/use-grouped-messages.js +24 -4
- package/hooks/private/use-grouped-messages.js.map +1 -1
- package/hooks/private/use-multimodal-input.d.ts.map +1 -1
- package/hooks/private/use-rest-client.d.ts.map +1 -1
- package/hooks/private/use-visitor-typing-reporter.d.ts +6 -0
- package/hooks/private/use-visitor-typing-reporter.d.ts.map +1 -1
- package/hooks/private/use-visitor-typing-reporter.js +6 -0
- package/hooks/private/use-visitor-typing-reporter.js.map +1 -1
- package/hooks/use-composer-refocus.d.ts.map +1 -1
- package/hooks/use-conversation-auto-seen.d.ts +9 -0
- package/hooks/use-conversation-auto-seen.d.ts.map +1 -1
- package/hooks/use-conversation-auto-seen.js +44 -3
- package/hooks/use-conversation-auto-seen.js.map +1 -1
- package/hooks/use-conversation-history-page.d.ts.map +1 -1
- package/hooks/use-conversation-history-page.js +16 -18
- package/hooks/use-conversation-history-page.js.map +1 -1
- package/hooks/use-conversation-lifecycle.d.ts.map +1 -1
- package/hooks/use-conversation-lifecycle.js +2 -4
- package/hooks/use-conversation-lifecycle.js.map +1 -1
- package/hooks/use-conversation-page.d.ts +6 -0
- package/hooks/use-conversation-page.d.ts.map +1 -1
- package/hooks/use-conversation-page.js +41 -3
- package/hooks/use-conversation-page.js.map +1 -1
- package/hooks/use-conversation-preview.d.ts +61 -0
- package/hooks/use-conversation-preview.d.ts.map +1 -0
- package/hooks/use-conversation-preview.js +173 -0
- package/hooks/use-conversation-preview.js.map +1 -0
- package/hooks/use-conversation-seen.d.ts +4 -0
- package/hooks/use-conversation-seen.d.ts.map +1 -1
- package/hooks/use-conversation-seen.js +4 -0
- package/hooks/use-conversation-seen.js.map +1 -1
- package/hooks/use-conversation-timeline-items.d.ts +4 -0
- package/hooks/use-conversation-timeline-items.d.ts.map +1 -1
- package/hooks/use-conversation-timeline-items.js +4 -0
- package/hooks/use-conversation-timeline-items.js.map +1 -1
- package/hooks/use-conversation-timeline.d.ts +32 -0
- package/hooks/use-conversation-timeline.d.ts.map +1 -0
- package/hooks/use-conversation-timeline.js +41 -0
- package/hooks/use-conversation-timeline.js.map +1 -0
- package/hooks/use-conversation-typing.d.ts +4 -0
- package/hooks/use-conversation-typing.d.ts.map +1 -1
- package/hooks/use-conversation-typing.js +4 -0
- package/hooks/use-conversation-typing.js.map +1 -1
- package/hooks/use-conversation.d.ts +11 -0
- package/hooks/use-conversation.d.ts.map +1 -1
- package/hooks/use-conversation.js +11 -0
- package/hooks/use-conversation.js.map +1 -1
- package/hooks/use-conversations.d.ts +12 -0
- package/hooks/use-conversations.d.ts.map +1 -1
- package/hooks/use-conversations.js +12 -0
- package/hooks/use-conversations.js.map +1 -1
- package/hooks/use-create-conversation.d.ts +5 -0
- package/hooks/use-create-conversation.d.ts.map +1 -1
- package/hooks/use-create-conversation.js +12 -9
- package/hooks/use-create-conversation.js.map +1 -1
- package/hooks/use-home-page.d.ts.map +1 -1
- package/hooks/use-home-page.js +6 -4
- package/hooks/use-home-page.js.map +1 -1
- package/hooks/use-message-composer.d.ts.map +1 -1
- package/hooks/use-realtime-support.d.ts.map +1 -1
- package/hooks/use-send-message.d.ts +9 -0
- package/hooks/use-send-message.d.ts.map +1 -1
- package/hooks/use-send-message.js +15 -13
- package/hooks/use-send-message.js.map +1 -1
- package/hooks/use-visitor.d.ts.map +1 -1
- package/hooks/use-visitor.js +28 -30
- package/hooks/use-visitor.js.map +1 -1
- package/hooks/use-window-visibility-focus.d.ts +4 -0
- package/hooks/use-window-visibility-focus.d.ts.map +1 -1
- package/hooks/use-window-visibility-focus.js +5 -2
- package/hooks/use-window-visibility-focus.js.map +1 -1
- package/identify-visitor.d.ts +12 -3
- package/identify-visitor.d.ts.map +1 -1
- package/identify-visitor.js +58 -9
- package/identify-visitor.js.map +1 -1
- package/index.d.ts +10 -7
- package/index.js +10 -9
- package/package.json +11 -16
- package/primitives/avatar/avatar.d.ts.map +1 -1
- package/primitives/avatar/fallback.d.ts.map +1 -1
- package/primitives/avatar/fallback.js +1 -3
- package/primitives/avatar/fallback.js.map +1 -1
- package/primitives/avatar/image.d.ts.map +1 -1
- package/primitives/avatar/index.d.ts +1 -0
- package/primitives/bubble.d.ts +2 -0
- package/primitives/bubble.d.ts.map +1 -1
- package/primitives/bubble.js +8 -2
- package/primitives/bubble.js.map +1 -1
- package/primitives/button.d.ts.map +1 -1
- package/primitives/conversation-timeline.d.ts.map +1 -1
- package/primitives/conversation-timeline.js +58 -5
- package/primitives/conversation-timeline.js.map +1 -1
- package/primitives/index.d.ts +1 -0
- package/primitives/index.parts.d.ts +1 -0
- package/primitives/multimodal-input.d.ts.map +1 -1
- package/primitives/timeline-item-group.d.ts +7 -7
- package/primitives/timeline-item-group.d.ts.map +1 -1
- package/primitives/timeline-item-group.js.map +1 -1
- package/primitives/timeline-item.d.ts +1 -1
- package/primitives/timeline-item.d.ts.map +1 -1
- package/primitives/timeline-item.js +7 -1
- package/primitives/timeline-item.js.map +1 -1
- package/primitives/window.d.ts +1 -1
- package/primitives/window.d.ts.map +1 -1
- package/primitives/window.js +4 -4
- package/primitives/window.js.map +1 -1
- package/provider.d.ts +23 -43
- package/provider.d.ts.map +1 -1
- package/provider.js +152 -49
- package/provider.js.map +1 -1
- package/realtime/event-filter.d.ts +4 -0
- package/realtime/event-filter.d.ts.map +1 -1
- package/realtime/event-filter.js +4 -0
- package/realtime/event-filter.js.map +1 -1
- package/realtime/index.js +1 -1
- package/realtime/provider.d.ts +7 -2
- package/realtime/provider.d.ts.map +1 -1
- package/realtime/provider.js +23 -1
- package/realtime/provider.js.map +1 -1
- package/realtime/seen-store.d.ts +13 -0
- package/realtime/seen-store.d.ts.map +1 -1
- package/realtime/seen-store.js +14 -2
- package/realtime/seen-store.js.map +1 -1
- package/realtime/support-provider.d.ts +1 -2
- package/realtime/support-provider.d.ts.map +1 -1
- package/realtime/support-provider.js +19 -20
- package/realtime/support-provider.js.map +1 -1
- package/realtime/typing-store.d.ts +18 -0
- package/realtime/typing-store.d.ts.map +1 -1
- package/realtime/typing-store.js +19 -2
- package/realtime/typing-store.js.map +1 -1
- package/realtime/use-realtime.d.ts +8 -4
- package/realtime/use-realtime.d.ts.map +1 -1
- package/realtime/use-realtime.js +4 -0
- package/realtime/use-realtime.js.map +1 -1
- package/realtime-events.d.ts +17 -3
- package/realtime-events.d.ts.map +1 -1
- package/schemas.d.ts +7 -1
- package/schemas.d.ts.map +1 -1
- package/support/components/avatar-stack.d.ts +8 -4
- package/support/components/avatar-stack.d.ts.map +1 -1
- package/support/components/avatar-stack.js +4 -0
- package/support/components/avatar-stack.js.map +1 -1
- package/support/components/avatar.d.ts +11 -6
- package/support/components/avatar.d.ts.map +1 -1
- package/support/components/avatar.js +4 -0
- package/support/components/avatar.js.map +1 -1
- package/support/components/bubble.d.ts.map +1 -1
- package/support/components/bubble.js +29 -6
- package/support/components/bubble.js.map +1 -1
- package/support/components/button.d.ts +8 -5
- package/support/components/button.d.ts.map +1 -1
- package/support/components/button.js +5 -1
- package/support/components/button.js.map +1 -1
- package/support/components/container.d.ts +0 -1
- package/support/components/container.d.ts.map +1 -1
- package/support/components/container.js +2 -8
- package/support/components/container.js.map +1 -1
- package/support/components/conversation-button-link.d.ts +8 -21
- package/support/components/conversation-button-link.d.ts.map +1 -1
- package/support/components/conversation-button-link.js +62 -178
- package/support/components/conversation-button-link.js.map +1 -1
- package/support/components/conversation-event.d.ts.map +1 -1
- package/support/components/conversation-event.js +4 -0
- package/support/components/conversation-event.js.map +1 -1
- package/support/components/conversation-timeline.d.ts +10 -1
- package/support/components/conversation-timeline.d.ts.map +1 -1
- package/support/components/conversation-timeline.js +63 -57
- package/support/components/conversation-timeline.js.map +1 -1
- package/support/components/cossistant-branding.d.ts +5 -2
- package/support/components/cossistant-branding.d.ts.map +1 -1
- package/support/components/cossistant-branding.js +3 -0
- package/support/components/cossistant-branding.js.map +1 -1
- package/support/components/header.d.ts.map +1 -1
- package/support/components/header.js +2 -2
- package/support/components/header.js.map +1 -1
- package/support/components/icons.d.ts.map +1 -1
- package/support/components/multimodal-input.d.ts.map +1 -1
- package/support/components/multimodal-input.js +5 -24
- package/support/components/multimodal-input.js.map +1 -1
- package/support/components/navigation-tab.d.ts +7 -2
- package/support/components/navigation-tab.d.ts.map +1 -1
- package/support/components/navigation-tab.js +4 -0
- package/support/components/navigation-tab.js.map +1 -1
- package/support/components/support-content.d.ts +1 -1
- package/support/components/support-content.d.ts.map +1 -1
- package/support/components/support-content.js +7 -10
- package/support/components/support-content.js.map +1 -1
- package/support/components/text-effect.d.ts +5 -2
- package/support/components/text-effect.d.ts.map +1 -1
- package/support/components/text-effect.js +4 -0
- package/support/components/text-effect.js.map +1 -1
- package/support/components/timeline-identification-tool.d.ts +7 -0
- package/support/components/timeline-identification-tool.d.ts.map +1 -0
- package/support/components/timeline-identification-tool.js +139 -0
- package/support/components/timeline-identification-tool.js.map +1 -0
- package/support/components/timeline-message-group.d.ts +2 -1
- package/support/components/timeline-message-group.d.ts.map +1 -1
- package/support/components/timeline-message-group.js +4 -19
- package/support/components/timeline-message-group.js.map +1 -1
- package/support/components/timeline-message-item.d.ts +6 -2
- package/support/components/timeline-message-item.d.ts.map +1 -1
- package/support/components/timeline-message-item.js +8 -4
- package/support/components/timeline-message-item.js.map +1 -1
- package/support/components/typing-indicator.d.ts +5 -2
- package/support/components/typing-indicator.d.ts.map +1 -1
- package/support/components/typing-indicator.js +4 -4
- package/support/components/typing-indicator.js.map +1 -1
- package/support/components/watermark.d.ts.map +1 -1
- package/support/context/websocket.d.ts +8 -0
- package/support/context/websocket.d.ts.map +1 -1
- package/support/context/websocket.js +12 -6
- package/support/context/websocket.js.map +1 -1
- package/support/index.d.ts +8 -8
- package/support/index.d.ts.map +1 -1
- package/support/index.js +18 -18
- package/support/index.js.map +1 -1
- package/support/pages/conversation-history.js +46 -54
- package/support/pages/conversation-history.js.map +1 -1
- package/support/pages/conversation.d.ts +3 -6
- package/support/pages/conversation.d.ts.map +1 -1
- package/support/pages/conversation.js +19 -9
- package/support/pages/conversation.js.map +1 -1
- package/support/pages/home.d.ts +2 -2
- package/support/pages/home.d.ts.map +1 -1
- package/support/pages/home.js +64 -77
- package/support/pages/home.js.map +1 -1
- package/support/store/support-store.d.ts +18 -2
- package/support/store/support-store.d.ts.map +1 -1
- package/support/store/support-store.js +20 -5
- package/support/store/support-store.js.map +1 -1
- package/support/{support-CMoDLQoC.css → support-Ck4jy29i.css} +1 -2
- package/support/support-Ck4jy29i.css.map +1 -0
- package/support/text/index.d.ts +15 -2
- package/support/text/index.d.ts.map +1 -1
- package/support/text/index.js +15 -2
- package/support/text/index.js.map +1 -1
- package/support/text/locales/en.js +22 -4
- package/support/text/locales/en.js.map +1 -1
- package/support/text/locales/es.js +18 -0
- package/support/text/locales/es.js.map +1 -1
- package/support/text/locales/fr.js +18 -0
- package/support/text/locales/fr.js.map +1 -1
- package/support/text/locales/keys.d.ts +69 -9
- package/support/text/locales/keys.d.ts.map +1 -1
- package/support/text/locales/keys.js +18 -0
- package/support/text/locales/keys.js.map +1 -1
- package/support/text/runtime.d.ts +21 -0
- package/support/text/runtime.d.ts.map +1 -1
- package/support/text/runtime.js +21 -0
- package/support/text/runtime.js.map +1 -1
- package/support/utils/index.d.ts +4 -0
- package/support/utils/index.d.ts.map +1 -1
- package/support/utils/index.js +4 -1
- package/support/utils/index.js.map +1 -1
- package/support/utils/time.d.ts +3 -0
- package/support/utils/time.d.ts.map +1 -1
- package/support/utils/time.js +3 -0
- package/support/utils/time.js.map +1 -1
- package/support-config.d.ts +2 -1
- package/support-config.d.ts.map +1 -1
- package/support-config.js.map +1 -1
- package/support.css +2 -2
- package/timeline-item.d.ts +10 -0
- package/timeline-item.d.ts.map +1 -1
- package/utils/conversation.d.ts +7 -0
- package/utils/conversation.d.ts.map +1 -0
- package/utils/conversation.js +18 -0
- package/utils/conversation.js.map +1 -0
- package/utils/id.d.ts +3 -0
- package/utils/id.d.ts.map +1 -1
- package/utils/id.js +3 -0
- package/utils/id.js.map +1 -1
- package/utils/index.d.ts +2 -1
- package/utils/index.js +2 -1
- package/utils/metadata-hash.d.ts +12 -0
- package/utils/metadata-hash.d.ts.map +1 -0
- package/utils/metadata-hash.js +26 -0
- package/utils/metadata-hash.js.map +1 -0
- package/utils/text.d.ts +3 -0
- package/utils/text.d.ts.map +1 -1
- package/utils/text.js +3 -0
- package/utils/text.js.map +1 -1
- package/utils/use-render-element.d.ts +3 -0
- package/utils/use-render-element.d.ts.map +1 -1
- package/utils/use-render-element.js +3 -0
- package/utils/use-render-element.js.map +1 -1
- package/support/context/config.d.ts +0 -32
- package/support/context/config.d.ts.map +0 -1
- package/support/context/config.js +0 -27
- package/support/context/config.js.map +0 -1
- package/support/support-CMoDLQoC.css.map +0 -1
|
@@ -5,6 +5,7 @@ import { useMessageComposer } from "./use-message-composer.js";
|
|
|
5
5
|
import { useSupport } from "../provider.js";
|
|
6
6
|
import { useDefaultMessages } from "./private/use-default-messages.js";
|
|
7
7
|
import { useEffect, useMemo, useRef } from "react";
|
|
8
|
+
import { ConversationTimelineType, TimelineItemVisibility } from "@cossistant/types/enums";
|
|
8
9
|
|
|
9
10
|
//#region src/hooks/use-conversation-page.ts
|
|
10
11
|
/**
|
|
@@ -44,7 +45,7 @@ import { useEffect, useMemo, useRef } from "react";
|
|
|
44
45
|
* ```
|
|
45
46
|
*/
|
|
46
47
|
function useConversationPage(options) {
|
|
47
|
-
const { conversationId: initialConversationId, initialMessage, onConversationIdChange, items: passedItems = [] } = options;
|
|
48
|
+
const { conversationId: initialConversationId, initialMessage, onConversationIdChange, items: passedItems = [], autoSeenEnabled = true } = options;
|
|
48
49
|
const { client, visitor } = useSupport();
|
|
49
50
|
const trimmedInitialMessage = initialMessage?.trim() ?? "";
|
|
50
51
|
const hasInitialMessage = trimmedInitialMessage.length > 0;
|
|
@@ -55,7 +56,7 @@ function useConversationPage(options) {
|
|
|
55
56
|
const defaultTimelineItems = useDefaultMessages({ conversationId: lifecycle.conversationId });
|
|
56
57
|
const effectiveDefaultTimelineItems = hasInitialMessage ? [] : defaultTimelineItems;
|
|
57
58
|
const timelineQuery = useConversationTimelineItems(lifecycle.conversationId, { enabled: !lifecycle.isPending });
|
|
58
|
-
const
|
|
59
|
+
const baseItems = useMemo(() => {
|
|
59
60
|
if (timelineQuery.items.length > 0) return timelineQuery.items;
|
|
60
61
|
if (lifecycle.isPending && effectiveDefaultTimelineItems.length > 0) return effectiveDefaultTimelineItems;
|
|
61
62
|
if (passedItems.length > 0) return passedItems;
|
|
@@ -66,6 +67,41 @@ function useConversationPage(options) {
|
|
|
66
67
|
effectiveDefaultTimelineItems,
|
|
67
68
|
passedItems
|
|
68
69
|
]);
|
|
70
|
+
const shouldShowIdentificationTool = useMemo(() => {
|
|
71
|
+
if (lifecycle.isPending) return false;
|
|
72
|
+
if (visitor?.contact) return false;
|
|
73
|
+
return !baseItems.some((item) => item.type === ConversationTimelineType.IDENTIFICATION);
|
|
74
|
+
}, [
|
|
75
|
+
baseItems,
|
|
76
|
+
lifecycle.isPending,
|
|
77
|
+
visitor?.contact
|
|
78
|
+
]);
|
|
79
|
+
const displayItems = useMemo(() => {
|
|
80
|
+
if (!shouldShowIdentificationTool) return baseItems;
|
|
81
|
+
const organizationId = baseItems.at(-1)?.organizationId ?? client.getConfiguration().organizationId ?? "";
|
|
82
|
+
const identificationItem = {
|
|
83
|
+
id: `identification-${lifecycle.conversationId}`,
|
|
84
|
+
conversationId: lifecycle.conversationId,
|
|
85
|
+
organizationId,
|
|
86
|
+
visibility: TimelineItemVisibility.PUBLIC,
|
|
87
|
+
type: ConversationTimelineType.IDENTIFICATION,
|
|
88
|
+
text: null,
|
|
89
|
+
tool: "identification",
|
|
90
|
+
parts: [],
|
|
91
|
+
userId: null,
|
|
92
|
+
visitorId: visitor?.id ?? null,
|
|
93
|
+
aiAgentId: null,
|
|
94
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
95
|
+
deletedAt: null
|
|
96
|
+
};
|
|
97
|
+
return [...baseItems, identificationItem];
|
|
98
|
+
}, [
|
|
99
|
+
baseItems,
|
|
100
|
+
client,
|
|
101
|
+
lifecycle.conversationId,
|
|
102
|
+
shouldShowIdentificationTool,
|
|
103
|
+
visitor?.id
|
|
104
|
+
]);
|
|
69
105
|
const lastTimelineItem = useMemo(() => displayItems.at(-1) ?? null, [displayItems]);
|
|
70
106
|
const composer = useMessageComposer({
|
|
71
107
|
client,
|
|
@@ -110,7 +146,9 @@ function useConversationPage(options) {
|
|
|
110
146
|
client,
|
|
111
147
|
conversationId: lifecycle.realConversationId,
|
|
112
148
|
visitorId: visitor?.id,
|
|
113
|
-
lastTimelineItem
|
|
149
|
+
lastTimelineItem,
|
|
150
|
+
enabled: autoSeenEnabled,
|
|
151
|
+
isWidgetOpen: autoSeenEnabled
|
|
114
152
|
});
|
|
115
153
|
return {
|
|
116
154
|
conversationId: lifecycle.conversationId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-page.js","names":[],"sources":["../../src/hooks/use-conversation-page.ts"],"sourcesContent":["import type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { useSupport } from \"../provider\";\nimport { useDefaultMessages } from \"./private/use-default-messages\";\nimport { useConversationAutoSeen } from \"./use-conversation-auto-seen\";\nimport { useConversationLifecycle } from \"./use-conversation-lifecycle\";\nimport { useConversationTimelineItems } from \"./use-conversation-timeline-items\";\nimport { useMessageComposer } from \"./use-message-composer\";\n\nexport type UseConversationPageOptions = {\n\t/**\n\t * Initial conversation ID (from URL params, navigation state, etc.)\n\t * Can be PENDING_CONVERSATION_ID or a real ID.\n\t */\n\tconversationId: string;\n\n\t/**\n\t * Optional initial message to send when the conversation opens.\n\t */\n\tinitialMessage?: string;\n\n\t/**\n\t * Callback when conversation ID changes (e.g., after creation).\n\t * Use this to update navigation state or URL.\n\t */\n\tonConversationIdChange?: (conversationId: string) => void;\n\n\t/**\n\t * Optional timeline items to pass through (e.g., optimistic updates).\n\t */\n\titems?: TimelineItem[];\n};\n\nexport type UseConversationPageReturn = {\n\t// Conversation state\n\tconversationId: string;\n\tisPending: boolean;\n\titems: TimelineItem[];\n\tisLoading: boolean;\n\terror: Error | null;\n\n\t// Message composer\n\tcomposer: {\n\t\tmessage: string;\n\t\tfiles: File[];\n\t\tisSubmitting: boolean;\n\t\tcanSubmit: boolean;\n\t\tsetMessage: (message: string) => void;\n\t\taddFiles: (files: File[]) => void;\n\t\tremoveFile: (index: number) => void;\n\t\tsubmit: () => void;\n\t};\n\n\t// UI helpers\n\thasItems: boolean;\n\tlastTimelineItem: TimelineItem | null;\n};\n\n/**\n * Main orchestrator hook for the conversation page.\n *\n * This hook combines all conversation-related logic:\n * - Lifecycle management (pending → real conversation)\n * - Message fetching and display\n * - Message composition and sending\n * - Automatic seen tracking\n * - Default/welcome messages before conversation is created\n *\n * It provides a clean, simple API for building conversation UIs.\n *\n * @example\n * ```tsx\n * export function ConversationPage({ conversationId: initialId }) {\n * const conversation = useConversationPage({\n * conversationId: initialId,\n * onConversationIdChange: (newId) => {\n * // Update URL or navigation state\n * navigate(`/conversation/${newId}`);\n * },\n * });\n *\n * return (\n * <>\n * <MessageList messages={conversation.messages} />\n * <MessageInput\n * value={conversation.composer.message}\n * onChange={conversation.composer.setMessage}\n * onSubmit={conversation.composer.submit}\n * />\n * </>\n * );\n * }\n * ```\n */\nexport function useConversationPage(\n\toptions: UseConversationPageOptions\n): UseConversationPageReturn {\n\tconst {\n\t\tconversationId: initialConversationId,\n\t\tinitialMessage,\n\t\tonConversationIdChange,\n\t\titems: passedItems = [],\n\t} = options;\n\n\tconst { client, visitor } = useSupport();\n\n\tconst trimmedInitialMessage = initialMessage?.trim() ?? \"\";\n\tconst hasInitialMessage = trimmedInitialMessage.length > 0;\n\n\t// 1. Manage conversation lifecycle (pending vs real)\n\tconst lifecycle = useConversationLifecycle({\n\t\tinitialConversationId,\n\t\tonConversationCreated: onConversationIdChange,\n\t});\n\n\t// 2. Get default timeline items for pending state\n\tconst defaultTimelineItems = useDefaultMessages({\n\t\tconversationId: lifecycle.conversationId,\n\t});\n\n\tconst effectiveDefaultTimelineItems = hasInitialMessage\n\t\t? []\n\t\t: defaultTimelineItems;\n\n\t// 3. Fetch timeline items from backend if real conversation exists\n\tconst timelineQuery = useConversationTimelineItems(lifecycle.conversationId, {\n\t\tenabled: !lifecycle.isPending,\n\t});\n\n\t// 4. Determine which items to display\n\tconst displayItems = useMemo(() => {\n\t\t// If we have fetched timeline items, use them\n\t\tif (timelineQuery.items.length > 0) {\n\t\t\treturn timelineQuery.items;\n\t\t}\n\n\t\t// If pending, use default timeline items\n\t\tif (lifecycle.isPending && effectiveDefaultTimelineItems.length > 0) {\n\t\t\treturn effectiveDefaultTimelineItems;\n\t\t}\n\n\t\t// Use passed items as fallback\n\t\tif (passedItems.length > 0) {\n\t\t\treturn passedItems;\n\t\t}\n\n\t\treturn [];\n\t}, [\n\t\ttimelineQuery.items,\n\t\tlifecycle.isPending,\n\t\teffectiveDefaultTimelineItems,\n\t\tpassedItems,\n\t]);\n\n\tconst lastTimelineItem = useMemo(\n\t\t() => displayItems.at(-1) ?? null,\n\t\t[displayItems]\n\t);\n\n\t// 5. Set up message composer\n\tconst composer = useMessageComposer({\n\t\tclient,\n\t\tconversationId: lifecycle.realConversationId,\n\t\tdefaultTimelineItems: effectiveDefaultTimelineItems,\n\t\tvisitorId: visitor?.id,\n\t\tonMessageSent: (newConversationId) => {\n\t\t\t// Transition from pending to real conversation\n\t\t\tif (lifecycle.isPending) {\n\t\t\t\tlifecycle.setConversationId(newConversationId);\n\t\t\t}\n\t\t},\n\t});\n\n\tconst initialMessageSubmittedRef = useRef(false);\n\tconst lastInitialMessageRef = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!hasInitialMessage) {\n\t\t\tinitialMessageSubmittedRef.current = false;\n\t\t\tlastInitialMessageRef.current = null;\n\t\t\treturn;\n\t\t}\n\n\t\tif (lastInitialMessageRef.current !== trimmedInitialMessage) {\n\t\t\tinitialMessageSubmittedRef.current = false;\n\t\t\tlastInitialMessageRef.current = trimmedInitialMessage;\n\t\t}\n\n\t\tif (!lifecycle.isPending) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (composer.message !== trimmedInitialMessage) {\n\t\t\tcomposer.setMessage(trimmedInitialMessage);\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\tinitialMessageSubmittedRef.current ||\n\t\t\tcomposer.isSubmitting ||\n\t\t\t!composer.canSubmit\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tinitialMessageSubmittedRef.current = true;\n\t\tcomposer.submit();\n\t}, [\n\t\thasInitialMessage,\n\t\tlifecycle.isPending,\n\t\tcomposer.message,\n\t\tcomposer.setMessage,\n\t\tcomposer.isSubmitting,\n\t\tcomposer.canSubmit,\n\t\tcomposer.submit,\n\t\ttrimmedInitialMessage,\n\t]);\n\n\t// 6. Auto-mark messages as seen\n\tuseConversationAutoSeen({\n\t\tclient,\n\t\tconversationId: lifecycle.realConversationId,\n\t\tvisitorId: visitor?.id,\n\t\tlastTimelineItem,\n\t});\n\n\treturn {\n\t\tconversationId: lifecycle.conversationId,\n\t\tisPending: lifecycle.isPending,\n\t\titems: displayItems,\n\t\tisLoading: timelineQuery.isLoading,\n\t\terror: timelineQuery.error || composer.error,\n\t\tcomposer: {\n\t\t\tmessage: composer.message,\n\t\t\tfiles: composer.files,\n\t\t\tisSubmitting: composer.isSubmitting,\n\t\t\tcanSubmit: composer.canSubmit,\n\t\t\tsetMessage: composer.setMessage,\n\t\t\taddFiles: composer.addFiles,\n\t\t\tremoveFile: composer.removeFile,\n\t\t\tsubmit: composer.submit,\n\t\t},\n\t\thasItems: displayItems.length > 0,\n\t\tlastTimelineItem,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8FA,SAAgB,oBACf,SAC4B;CAC5B,MAAM,EACL,gBAAgB,uBAChB,gBACA,wBACA,OAAO,cAAc,EAAE,KACpB;CAEJ,MAAM,EAAE,QAAQ,YAAY,YAAY;CAExC,MAAM,wBAAwB,gBAAgB,MAAM,IAAI;CACxD,MAAM,oBAAoB,sBAAsB,SAAS;CAGzD,MAAM,YAAY,yBAAyB;EAC1C;EACA,uBAAuB;EACvB,CAAC;CAGF,MAAM,uBAAuB,mBAAmB,EAC/C,gBAAgB,UAAU,gBAC1B,CAAC;CAEF,MAAM,gCAAgC,oBACnC,EAAE,GACF;CAGH,MAAM,gBAAgB,6BAA6B,UAAU,gBAAgB,EAC5E,SAAS,CAAC,UAAU,WACpB,CAAC;CAGF,MAAM,eAAe,cAAc;AAElC,MAAI,cAAc,MAAM,SAAS,EAChC,QAAO,cAAc;AAItB,MAAI,UAAU,aAAa,8BAA8B,SAAS,EACjE,QAAO;AAIR,MAAI,YAAY,SAAS,EACxB,QAAO;AAGR,SAAO,EAAE;IACP;EACF,cAAc;EACd,UAAU;EACV;EACA;EACA,CAAC;CAEF,MAAM,mBAAmB,cAClB,aAAa,GAAG,GAAG,IAAI,MAC7B,CAAC,aAAa,CACd;CAGD,MAAM,WAAW,mBAAmB;EACnC;EACA,gBAAgB,UAAU;EAC1B,sBAAsB;EACtB,WAAW,SAAS;EACpB,gBAAgB,sBAAsB;AAErC,OAAI,UAAU,UACb,WAAU,kBAAkB,kBAAkB;;EAGhD,CAAC;CAEF,MAAM,6BAA6B,OAAO,MAAM;CAChD,MAAM,wBAAwB,OAAsB,KAAK;AAEzD,iBAAgB;AACf,MAAI,CAAC,mBAAmB;AACvB,8BAA2B,UAAU;AACrC,yBAAsB,UAAU;AAChC;;AAGD,MAAI,sBAAsB,YAAY,uBAAuB;AAC5D,8BAA2B,UAAU;AACrC,yBAAsB,UAAU;;AAGjC,MAAI,CAAC,UAAU,UACd;AAGD,MAAI,SAAS,YAAY,uBAAuB;AAC/C,YAAS,WAAW,sBAAsB;AAC1C;;AAGD,MACC,2BAA2B,WAC3B,SAAS,gBACT,CAAC,SAAS,UAEV;AAGD,6BAA2B,UAAU;AACrC,WAAS,QAAQ;IACf;EACF;EACA,UAAU;EACV,SAAS;EACT,SAAS;EACT,SAAS;EACT,SAAS;EACT,SAAS;EACT;EACA,CAAC;AAGF,yBAAwB;EACvB;EACA,gBAAgB,UAAU;EAC1B,WAAW,SAAS;EACpB;EACA,CAAC;AAEF,QAAO;EACN,gBAAgB,UAAU;EAC1B,WAAW,UAAU;EACrB,OAAO;EACP,WAAW,cAAc;EACzB,OAAO,cAAc,SAAS,SAAS;EACvC,UAAU;GACT,SAAS,SAAS;GAClB,OAAO,SAAS;GAChB,cAAc,SAAS;GACvB,WAAW,SAAS;GACpB,YAAY,SAAS;GACrB,UAAU,SAAS;GACnB,YAAY,SAAS;GACrB,QAAQ,SAAS;GACjB;EACD,UAAU,aAAa,SAAS;EAChC;EACA"}
|
|
1
|
+
{"version":3,"file":"use-conversation-page.js","names":["identificationItem: TimelineItem"],"sources":["../../src/hooks/use-conversation-page.ts"],"sourcesContent":["import type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport {\n\tConversationTimelineType,\n\tTimelineItemVisibility,\n} from \"@cossistant/types/enums\";\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { useSupport } from \"../provider\";\nimport { useDefaultMessages } from \"./private/use-default-messages\";\nimport { useConversationAutoSeen } from \"./use-conversation-auto-seen\";\nimport { useConversationLifecycle } from \"./use-conversation-lifecycle\";\nimport { useConversationTimelineItems } from \"./use-conversation-timeline-items\";\nimport { useMessageComposer } from \"./use-message-composer\";\n\nexport type UseConversationPageOptions = {\n\t/**\n\t * Initial conversation ID (from URL params, navigation state, etc.)\n\t * Can be PENDING_CONVERSATION_ID or a real ID.\n\t */\n\tconversationId: string;\n\n\t/**\n\t * Optional initial message to send when the conversation opens.\n\t */\n\tinitialMessage?: string;\n\n\t/**\n\t * Callback when conversation ID changes (e.g., after creation).\n\t * Use this to update navigation state or URL.\n\t */\n\tonConversationIdChange?: (conversationId: string) => void;\n\n\t/**\n\t * Optional timeline items to pass through (e.g., optimistic updates).\n\t */\n\titems?: TimelineItem[];\n\n\t/**\n\t * Whether automatic \"seen\" tracking should be enabled.\n\t * Set to false when the conversation isn't visible (e.g. widget closed).\n\t * Default: true\n\t */\n\tautoSeenEnabled?: boolean;\n};\n\nexport type UseConversationPageReturn = {\n\t// Conversation state\n\tconversationId: string;\n\tisPending: boolean;\n\titems: TimelineItem[];\n\tisLoading: boolean;\n\terror: Error | null;\n\n\t// Message composer\n\tcomposer: {\n\t\tmessage: string;\n\t\tfiles: File[];\n\t\tisSubmitting: boolean;\n\t\tcanSubmit: boolean;\n\t\tsetMessage: (message: string) => void;\n\t\taddFiles: (files: File[]) => void;\n\t\tremoveFile: (index: number) => void;\n\t\tsubmit: () => void;\n\t};\n\n\t// UI helpers\n\thasItems: boolean;\n\tlastTimelineItem: TimelineItem | null;\n};\n\n/**\n * Main orchestrator hook for the conversation page.\n *\n * This hook combines all conversation-related logic:\n * - Lifecycle management (pending → real conversation)\n * - Message fetching and display\n * - Message composition and sending\n * - Automatic seen tracking\n * - Default/welcome messages before conversation is created\n *\n * It provides a clean, simple API for building conversation UIs.\n *\n * @example\n * ```tsx\n * export function ConversationPage({ conversationId: initialId }) {\n * const conversation = useConversationPage({\n * conversationId: initialId,\n * onConversationIdChange: (newId) => {\n * // Update URL or navigation state\n * navigate(`/conversation/${newId}`);\n * },\n * });\n *\n * return (\n * <>\n * <MessageList messages={conversation.messages} />\n * <MessageInput\n * value={conversation.composer.message}\n * onChange={conversation.composer.setMessage}\n * onSubmit={conversation.composer.submit}\n * />\n * </>\n * );\n * }\n * ```\n */\nexport function useConversationPage(\n\toptions: UseConversationPageOptions\n): UseConversationPageReturn {\n\tconst {\n\t\tconversationId: initialConversationId,\n\t\tinitialMessage,\n\t\tonConversationIdChange,\n\t\titems: passedItems = [],\n\t\tautoSeenEnabled = true,\n\t} = options;\n\n\tconst { client, visitor } = useSupport();\n\n\tconst trimmedInitialMessage = initialMessage?.trim() ?? \"\";\n\tconst hasInitialMessage = trimmedInitialMessage.length > 0;\n\n\t// 1. Manage conversation lifecycle (pending vs real)\n\tconst lifecycle = useConversationLifecycle({\n\t\tinitialConversationId,\n\t\tonConversationCreated: onConversationIdChange,\n\t});\n\n\t// 2. Get default timeline items for pending state\n\tconst defaultTimelineItems = useDefaultMessages({\n\t\tconversationId: lifecycle.conversationId,\n\t});\n\n\tconst effectiveDefaultTimelineItems = hasInitialMessage\n\t\t? []\n\t\t: defaultTimelineItems;\n\n\t// 3. Fetch timeline items from backend if real conversation exists\n\tconst timelineQuery = useConversationTimelineItems(lifecycle.conversationId, {\n\t\tenabled: !lifecycle.isPending,\n\t});\n\n\t// 4. Determine which items to display\n\tconst baseItems = useMemo(() => {\n\t\t// If we have fetched timeline items, use them\n\t\tif (timelineQuery.items.length > 0) {\n\t\t\treturn timelineQuery.items;\n\t\t}\n\n\t\t// If pending, use default timeline items\n\t\tif (lifecycle.isPending && effectiveDefaultTimelineItems.length > 0) {\n\t\t\treturn effectiveDefaultTimelineItems;\n\t\t}\n\n\t\t// Use passed items as fallback\n\t\tif (passedItems.length > 0) {\n\t\t\treturn passedItems;\n\t\t}\n\n\t\treturn [];\n\t}, [\n\t\ttimelineQuery.items,\n\t\tlifecycle.isPending,\n\t\teffectiveDefaultTimelineItems,\n\t\tpassedItems,\n\t]);\n\n\tconst shouldShowIdentificationTool = useMemo(() => {\n\t\tif (lifecycle.isPending) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (visitor?.contact) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn !baseItems.some(\n\t\t\t(item) => item.type === ConversationTimelineType.IDENTIFICATION\n\t\t);\n\t}, [baseItems, lifecycle.isPending, visitor?.contact]);\n\n\tconst displayItems = useMemo(() => {\n\t\tif (!shouldShowIdentificationTool) {\n\t\t\treturn baseItems;\n\t\t}\n\n\t\tconst organizationId =\n\t\t\tbaseItems.at(-1)?.organizationId ??\n\t\t\tclient.getConfiguration().organizationId ??\n\t\t\t\"\";\n\n\t\tconst identificationItem: TimelineItem = {\n\t\t\tid: `identification-${lifecycle.conversationId}`,\n\t\t\tconversationId: lifecycle.conversationId,\n\t\t\torganizationId,\n\t\t\tvisibility: TimelineItemVisibility.PUBLIC,\n\t\t\ttype: ConversationTimelineType.IDENTIFICATION,\n\t\t\ttext: null,\n\t\t\ttool: \"identification\",\n\t\t\tparts: [],\n\t\t\tuserId: null,\n\t\t\tvisitorId: visitor?.id ?? null,\n\t\t\taiAgentId: null,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\tdeletedAt: null,\n\t\t};\n\n\t\treturn [...baseItems, identificationItem];\n\t}, [\n\t\tbaseItems,\n\t\tclient,\n\t\tlifecycle.conversationId,\n\t\tshouldShowIdentificationTool,\n\t\tvisitor?.id,\n\t]);\n\n\tconst lastTimelineItem = useMemo(\n\t\t() => displayItems.at(-1) ?? null,\n\t\t[displayItems]\n\t);\n\n\t// 5. Set up message composer\n\tconst composer = useMessageComposer({\n\t\tclient,\n\t\tconversationId: lifecycle.realConversationId,\n\t\tdefaultTimelineItems: effectiveDefaultTimelineItems,\n\t\tvisitorId: visitor?.id,\n\t\tonMessageSent: (newConversationId) => {\n\t\t\t// Transition from pending to real conversation\n\t\t\tif (lifecycle.isPending) {\n\t\t\t\tlifecycle.setConversationId(newConversationId);\n\t\t\t}\n\t\t},\n\t});\n\n\tconst initialMessageSubmittedRef = useRef(false);\n\tconst lastInitialMessageRef = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!hasInitialMessage) {\n\t\t\tinitialMessageSubmittedRef.current = false;\n\t\t\tlastInitialMessageRef.current = null;\n\t\t\treturn;\n\t\t}\n\n\t\tif (lastInitialMessageRef.current !== trimmedInitialMessage) {\n\t\t\tinitialMessageSubmittedRef.current = false;\n\t\t\tlastInitialMessageRef.current = trimmedInitialMessage;\n\t\t}\n\n\t\tif (!lifecycle.isPending) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (composer.message !== trimmedInitialMessage) {\n\t\t\tcomposer.setMessage(trimmedInitialMessage);\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\tinitialMessageSubmittedRef.current ||\n\t\t\tcomposer.isSubmitting ||\n\t\t\t!composer.canSubmit\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tinitialMessageSubmittedRef.current = true;\n\t\tcomposer.submit();\n\t}, [\n\t\thasInitialMessage,\n\t\tlifecycle.isPending,\n\t\tcomposer.message,\n\t\tcomposer.setMessage,\n\t\tcomposer.isSubmitting,\n\t\tcomposer.canSubmit,\n\t\tcomposer.submit,\n\t\ttrimmedInitialMessage,\n\t]);\n\n\t// 6. Auto-mark messages as seen\n\tuseConversationAutoSeen({\n\t\tclient,\n\t\tconversationId: lifecycle.realConversationId,\n\t\tvisitorId: visitor?.id,\n\t\tlastTimelineItem,\n\t\tenabled: autoSeenEnabled,\n\t\tisWidgetOpen: autoSeenEnabled,\n\t});\n\n\treturn {\n\t\tconversationId: lifecycle.conversationId,\n\t\tisPending: lifecycle.isPending,\n\t\titems: displayItems,\n\t\tisLoading: timelineQuery.isLoading,\n\t\terror: timelineQuery.error || composer.error,\n\t\tcomposer: {\n\t\t\tmessage: composer.message,\n\t\t\tfiles: composer.files,\n\t\t\tisSubmitting: composer.isSubmitting,\n\t\t\tcanSubmit: composer.canSubmit,\n\t\t\tsetMessage: composer.setMessage,\n\t\t\taddFiles: composer.addFiles,\n\t\t\tremoveFile: composer.removeFile,\n\t\t\tsubmit: composer.submit,\n\t\t},\n\t\thasItems: displayItems.length > 0,\n\t\tlastTimelineItem,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { TimelineItem } from "../timeline-item.js";
|
|
2
|
+
import { PreviewTypingParticipant } from "./private/typing.js";
|
|
3
|
+
import { useConversationTimelineItems } from "./use-conversation-timeline-items.js";
|
|
4
|
+
import { Conversation } from "@cossistant/types";
|
|
5
|
+
|
|
6
|
+
//#region src/hooks/use-conversation-preview.d.ts
|
|
7
|
+
type ConversationPreviewLastMessage = {
|
|
8
|
+
content: string;
|
|
9
|
+
time: string;
|
|
10
|
+
isFromVisitor: boolean;
|
|
11
|
+
senderName?: string;
|
|
12
|
+
senderImage?: string | null;
|
|
13
|
+
};
|
|
14
|
+
type ConversationPreviewAssignedAgent = {
|
|
15
|
+
name: string;
|
|
16
|
+
image: string | null;
|
|
17
|
+
type: "human" | "ai" | "fallback";
|
|
18
|
+
};
|
|
19
|
+
type ConversationPreviewTypingParticipant = PreviewTypingParticipant;
|
|
20
|
+
type ConversationPreviewTypingState = {
|
|
21
|
+
participants: ConversationPreviewTypingParticipant[];
|
|
22
|
+
primaryParticipant: ConversationPreviewTypingParticipant | null;
|
|
23
|
+
label: string | null;
|
|
24
|
+
isTyping: boolean;
|
|
25
|
+
};
|
|
26
|
+
type UseConversationPreviewOptions = {
|
|
27
|
+
conversation: Conversation;
|
|
28
|
+
/**
|
|
29
|
+
* Whether the hook should fetch timeline items for the conversation.
|
|
30
|
+
* Enabled by default.
|
|
31
|
+
*/
|
|
32
|
+
includeTimelineItems?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Optional timeline items to merge with the live ones (e.g. optimistic items).
|
|
35
|
+
*/
|
|
36
|
+
initialTimelineItems?: TimelineItem[];
|
|
37
|
+
/**
|
|
38
|
+
* Typing state configuration (mainly exclusions for the current visitor).
|
|
39
|
+
*/
|
|
40
|
+
typing?: {
|
|
41
|
+
excludeVisitorId?: string | null;
|
|
42
|
+
excludeUserId?: string | null;
|
|
43
|
+
excludeAiAgentId?: string | null;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
type UseConversationPreviewReturn = {
|
|
47
|
+
conversation: Conversation;
|
|
48
|
+
title: string;
|
|
49
|
+
lastMessage: ConversationPreviewLastMessage | null;
|
|
50
|
+
assignedAgent: ConversationPreviewAssignedAgent;
|
|
51
|
+
typing: ConversationPreviewTypingState;
|
|
52
|
+
timeline: ReturnType<typeof useConversationTimelineItems>;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Composes conversation metadata including derived titles, last message
|
|
56
|
+
* snippets and typing state for use in lists.
|
|
57
|
+
*/
|
|
58
|
+
declare function useConversationPreview(options: UseConversationPreviewOptions): UseConversationPreviewReturn;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { ConversationPreviewAssignedAgent, ConversationPreviewLastMessage, ConversationPreviewTypingParticipant, ConversationPreviewTypingState, UseConversationPreviewOptions, UseConversationPreviewReturn, useConversationPreview };
|
|
61
|
+
//# sourceMappingURL=use-conversation-preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-conversation-preview.d.ts","names":[],"sources":["../../src/hooks/use-conversation-preview.ts"],"sourcesContent":[],"mappings":";;;;;;KAcY,8BAAA;;EAAA,IAAA,EAAA,MAAA;EAQA,aAAA,EAAA,OAAA;EAMA,UAAA,CAAA,EAAA,MAAA;EAEA,WAAA,CAAA,EAAA,MAAA,GAAA,IAAA;AAOZ,CAAA;AAqBY,KApCA,gCAAA,GAoC4B;EACzB,IAAA,EAAA,MAAA;EAED,KAAA,EAAA,MAAA,GAAA,IAAA;EACE,IAAA,EAAA,OAAA,GAAA,IAAA,GAAA,UAAA;CACP;AACoB,KApCjB,oCAAA,GAAuC,wBAoCtB;AAAlB,KAlCC,8BAAA,GAkCD;EAAU,YAAA,EAjCN,oCAiCM,EAAA;EA0BL,kBAAA,EA1DK,oCA2DX,GAAA,IAAA;;;;KAtDE,6BAAA;gBACG;;;;;;;;;yBASS;;;;;;;;;;KAWZ,4BAAA;gBACG;;eAED;iBACE;UACP;YACE,kBAAkB;;;;;;iBA0Bb,sBAAA,UACN,gCACP"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { useSupportText } from "../support/text/index.js";
|
|
2
|
+
import { useConversationTimelineItems } from "./use-conversation-timeline-items.js";
|
|
3
|
+
import { mapTypingEntriesToPreviewParticipants } from "./private/typing.js";
|
|
4
|
+
import { useConversationTyping } from "./use-conversation-typing.js";
|
|
5
|
+
import { formatTimeAgo } from "../support/utils/time.js";
|
|
6
|
+
import { useSupport } from "../provider.js";
|
|
7
|
+
import { useMemo } from "react";
|
|
8
|
+
|
|
9
|
+
//#region src/hooks/use-conversation-preview.ts
|
|
10
|
+
function resolveLastTimelineMessage(items, fallback) {
|
|
11
|
+
for (let index = items.length - 1; index >= 0; index--) {
|
|
12
|
+
const item = items[index];
|
|
13
|
+
if (item?.type === "message") return item;
|
|
14
|
+
}
|
|
15
|
+
if (fallback?.type === "message") return fallback;
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Composes conversation metadata including derived titles, last message
|
|
20
|
+
* snippets and typing state for use in lists.
|
|
21
|
+
*/
|
|
22
|
+
function useConversationPreview(options) {
|
|
23
|
+
const { conversation, includeTimelineItems = true, initialTimelineItems = [], typing } = options;
|
|
24
|
+
const { availableHumanAgents, availableAIAgents, visitor } = useSupport();
|
|
25
|
+
const text = useSupportText();
|
|
26
|
+
const timeline = useConversationTimelineItems(conversation.id, { enabled: includeTimelineItems });
|
|
27
|
+
const mergedTimelineItems = useMemo(() => {
|
|
28
|
+
if (timeline.items.length > 0) return timeline.items;
|
|
29
|
+
if (initialTimelineItems.length > 0) return initialTimelineItems;
|
|
30
|
+
return [];
|
|
31
|
+
}, [timeline.items, initialTimelineItems]);
|
|
32
|
+
const knownTimelineItems = useMemo(() => {
|
|
33
|
+
const items = [...mergedTimelineItems];
|
|
34
|
+
if (conversation.lastTimelineItem && !items.some((item) => item.id === conversation.lastTimelineItem?.id)) items.push(conversation.lastTimelineItem);
|
|
35
|
+
return items;
|
|
36
|
+
}, [mergedTimelineItems, conversation.lastTimelineItem]);
|
|
37
|
+
const lastTimelineMessage = useMemo(() => resolveLastTimelineMessage(mergedTimelineItems, conversation.lastTimelineItem ?? null), [mergedTimelineItems, conversation.lastTimelineItem]);
|
|
38
|
+
const lastMessage = useMemo(() => {
|
|
39
|
+
if (!lastTimelineMessage) return null;
|
|
40
|
+
const isFromVisitor = lastTimelineMessage.visitorId !== null;
|
|
41
|
+
let senderName = text("common.fallbacks.unknown");
|
|
42
|
+
let senderImage = null;
|
|
43
|
+
if (isFromVisitor) senderName = text("common.fallbacks.you");
|
|
44
|
+
else if (lastTimelineMessage.userId) {
|
|
45
|
+
const agent = availableHumanAgents.find((a) => a.id === lastTimelineMessage.userId);
|
|
46
|
+
if (agent) {
|
|
47
|
+
senderName = agent.name;
|
|
48
|
+
senderImage = agent.image;
|
|
49
|
+
} else senderName = text("common.fallbacks.supportTeam");
|
|
50
|
+
} else if (lastTimelineMessage.aiAgentId) {
|
|
51
|
+
const aiAgent = availableAIAgents.find((agent) => agent.id === lastTimelineMessage.aiAgentId);
|
|
52
|
+
if (aiAgent) {
|
|
53
|
+
senderName = aiAgent.name;
|
|
54
|
+
senderImage = aiAgent.image;
|
|
55
|
+
} else senderName = text("common.fallbacks.aiAssistant");
|
|
56
|
+
} else senderName = text("common.fallbacks.supportTeam");
|
|
57
|
+
return {
|
|
58
|
+
content: lastTimelineMessage.text || "",
|
|
59
|
+
time: formatTimeAgo(lastTimelineMessage.createdAt),
|
|
60
|
+
isFromVisitor,
|
|
61
|
+
senderName,
|
|
62
|
+
senderImage
|
|
63
|
+
};
|
|
64
|
+
}, [
|
|
65
|
+
lastTimelineMessage,
|
|
66
|
+
availableHumanAgents,
|
|
67
|
+
availableAIAgents,
|
|
68
|
+
text
|
|
69
|
+
]);
|
|
70
|
+
const assignedAgent = useMemo(() => {
|
|
71
|
+
const supportFallbackName = text("common.fallbacks.supportTeam");
|
|
72
|
+
const aiFallbackName = text("common.fallbacks.aiAssistant");
|
|
73
|
+
const lastAgentItem = [...knownTimelineItems].reverse().find((item) => item.userId !== null || item.aiAgentId !== null);
|
|
74
|
+
if (lastAgentItem?.userId) {
|
|
75
|
+
const human = availableHumanAgents.find((agent) => agent.id === lastAgentItem.userId);
|
|
76
|
+
if (human) return {
|
|
77
|
+
type: "human",
|
|
78
|
+
name: human.name,
|
|
79
|
+
image: human.image ?? null
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
type: "human",
|
|
83
|
+
name: supportFallbackName,
|
|
84
|
+
image: null
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (lastAgentItem?.aiAgentId) {
|
|
88
|
+
const ai = availableAIAgents.find((agent) => agent.id === lastAgentItem.aiAgentId);
|
|
89
|
+
if (ai) return {
|
|
90
|
+
type: "ai",
|
|
91
|
+
name: ai.name,
|
|
92
|
+
image: ai.image ?? null
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
type: "ai",
|
|
96
|
+
name: aiFallbackName,
|
|
97
|
+
image: null
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const fallbackHuman = availableHumanAgents[0];
|
|
101
|
+
if (fallbackHuman) return {
|
|
102
|
+
type: "human",
|
|
103
|
+
name: fallbackHuman.name,
|
|
104
|
+
image: fallbackHuman.image ?? null
|
|
105
|
+
};
|
|
106
|
+
const fallbackAi = availableAIAgents[0];
|
|
107
|
+
if (fallbackAi) return {
|
|
108
|
+
type: "ai",
|
|
109
|
+
name: fallbackAi.name,
|
|
110
|
+
image: fallbackAi.image ?? null
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
type: "fallback",
|
|
114
|
+
name: supportFallbackName,
|
|
115
|
+
image: null
|
|
116
|
+
};
|
|
117
|
+
}, [
|
|
118
|
+
knownTimelineItems,
|
|
119
|
+
availableHumanAgents,
|
|
120
|
+
availableAIAgents,
|
|
121
|
+
text
|
|
122
|
+
]);
|
|
123
|
+
const typingEntries = useConversationTyping(conversation.id, {
|
|
124
|
+
excludeVisitorId: typing?.excludeVisitorId ?? visitor?.id ?? null,
|
|
125
|
+
excludeUserId: typing?.excludeUserId ?? null,
|
|
126
|
+
excludeAiAgentId: typing?.excludeAiAgentId ?? null
|
|
127
|
+
});
|
|
128
|
+
const typingParticipants = useMemo(() => mapTypingEntriesToPreviewParticipants(typingEntries, {
|
|
129
|
+
availableHumanAgents,
|
|
130
|
+
availableAIAgents,
|
|
131
|
+
text
|
|
132
|
+
}), [
|
|
133
|
+
typingEntries,
|
|
134
|
+
availableHumanAgents,
|
|
135
|
+
availableAIAgents,
|
|
136
|
+
text
|
|
137
|
+
]);
|
|
138
|
+
const primaryTypingParticipant = typingParticipants[0] ?? null;
|
|
139
|
+
const typingLabel = useMemo(() => {
|
|
140
|
+
if (!primaryTypingParticipant) return null;
|
|
141
|
+
return text("component.conversationButtonLink.typing", { name: primaryTypingParticipant.name });
|
|
142
|
+
}, [primaryTypingParticipant, text]);
|
|
143
|
+
const typingState = useMemo(() => ({
|
|
144
|
+
participants: typingParticipants,
|
|
145
|
+
primaryParticipant: primaryTypingParticipant,
|
|
146
|
+
label: typingLabel,
|
|
147
|
+
isTyping: typingParticipants.length > 0
|
|
148
|
+
}), [
|
|
149
|
+
typingParticipants,
|
|
150
|
+
primaryTypingParticipant,
|
|
151
|
+
typingLabel
|
|
152
|
+
]);
|
|
153
|
+
return {
|
|
154
|
+
conversation,
|
|
155
|
+
title: useMemo(() => {
|
|
156
|
+
if (conversation.title) return conversation.title;
|
|
157
|
+
if (lastMessage?.content) return lastMessage.content;
|
|
158
|
+
return text("component.conversationButtonLink.fallbackTitle");
|
|
159
|
+
}, [
|
|
160
|
+
conversation.title,
|
|
161
|
+
lastMessage?.content,
|
|
162
|
+
text
|
|
163
|
+
]),
|
|
164
|
+
lastMessage,
|
|
165
|
+
assignedAgent,
|
|
166
|
+
typing: typingState,
|
|
167
|
+
timeline
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
export { useConversationPreview };
|
|
173
|
+
//# sourceMappingURL=use-conversation-preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-conversation-preview.js","names":["senderImage: string | null","typingState: ConversationPreviewTypingState"],"sources":["../../src/hooks/use-conversation-preview.ts"],"sourcesContent":["import type { Conversation } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { useMemo } from \"react\";\n\nimport { useSupport } from \"../provider\";\nimport { useSupportText } from \"../support/text\";\nimport { formatTimeAgo } from \"../support/utils/time\";\nimport {\n\tmapTypingEntriesToPreviewParticipants,\n\ttype PreviewTypingParticipant,\n} from \"./private/typing\";\nimport { useConversationTimelineItems } from \"./use-conversation-timeline-items\";\nimport { useConversationTyping } from \"./use-conversation-typing\";\n\nexport type ConversationPreviewLastMessage = {\n\tcontent: string;\n\ttime: string;\n\tisFromVisitor: boolean;\n\tsenderName?: string;\n\tsenderImage?: string | null;\n};\n\nexport type ConversationPreviewAssignedAgent = {\n\tname: string;\n\timage: string | null;\n\ttype: \"human\" | \"ai\" | \"fallback\";\n};\n\nexport type ConversationPreviewTypingParticipant = PreviewTypingParticipant;\n\nexport type ConversationPreviewTypingState = {\n\tparticipants: ConversationPreviewTypingParticipant[];\n\tprimaryParticipant: ConversationPreviewTypingParticipant | null;\n\tlabel: string | null;\n\tisTyping: boolean;\n};\n\nexport type UseConversationPreviewOptions = {\n\tconversation: Conversation;\n\t/**\n\t * Whether the hook should fetch timeline items for the conversation.\n\t * Enabled by default.\n\t */\n\tincludeTimelineItems?: boolean;\n\t/**\n\t * Optional timeline items to merge with the live ones (e.g. optimistic items).\n\t */\n\tinitialTimelineItems?: TimelineItem[];\n\t/**\n\t * Typing state configuration (mainly exclusions for the current visitor).\n\t */\n\ttyping?: {\n\t\texcludeVisitorId?: string | null;\n\t\texcludeUserId?: string | null;\n\t\texcludeAiAgentId?: string | null;\n\t};\n};\n\nexport type UseConversationPreviewReturn = {\n\tconversation: Conversation;\n\ttitle: string;\n\tlastMessage: ConversationPreviewLastMessage | null;\n\tassignedAgent: ConversationPreviewAssignedAgent;\n\ttyping: ConversationPreviewTypingState;\n\ttimeline: ReturnType<typeof useConversationTimelineItems>;\n};\n\nfunction resolveLastTimelineMessage(\n\titems: TimelineItem[],\n\tfallback: TimelineItem | null\n) {\n\tfor (let index = items.length - 1; index >= 0; index--) {\n\t\tconst item = items[index];\n\n\t\tif (item?.type === \"message\") {\n\t\t\treturn item;\n\t\t}\n\t}\n\n\tif (fallback?.type === \"message\") {\n\t\treturn fallback;\n\t}\n\n\treturn null;\n}\n\n/**\n * Composes conversation metadata including derived titles, last message\n * snippets and typing state for use in lists.\n */\nexport function useConversationPreview(\n\toptions: UseConversationPreviewOptions\n): UseConversationPreviewReturn {\n\tconst {\n\t\tconversation,\n\t\tincludeTimelineItems = true,\n\t\tinitialTimelineItems = [],\n\t\ttyping,\n\t} = options;\n\tconst { availableHumanAgents, availableAIAgents, visitor } = useSupport();\n\tconst text = useSupportText();\n\n\tconst timeline = useConversationTimelineItems(conversation.id, {\n\t\tenabled: includeTimelineItems,\n\t});\n\n\tconst mergedTimelineItems = useMemo(() => {\n\t\tif (timeline.items.length > 0) {\n\t\t\treturn timeline.items;\n\t\t}\n\n\t\tif (initialTimelineItems.length > 0) {\n\t\t\treturn initialTimelineItems;\n\t\t}\n\n\t\treturn [] as TimelineItem[];\n\t}, [timeline.items, initialTimelineItems]);\n\n\tconst knownTimelineItems = useMemo(() => {\n\t\tconst items = [...mergedTimelineItems];\n\n\t\tif (\n\t\t\tconversation.lastTimelineItem &&\n\t\t\t!items.some((item) => item.id === conversation.lastTimelineItem?.id)\n\t\t) {\n\t\t\titems.push(conversation.lastTimelineItem);\n\t\t}\n\n\t\treturn items;\n\t}, [mergedTimelineItems, conversation.lastTimelineItem]);\n\n\tconst lastTimelineMessage = useMemo(\n\t\t() =>\n\t\t\tresolveLastTimelineMessage(\n\t\t\t\tmergedTimelineItems,\n\t\t\t\tconversation.lastTimelineItem ?? null\n\t\t\t),\n\t\t[mergedTimelineItems, conversation.lastTimelineItem]\n\t);\n\n\tconst lastMessage = useMemo<ConversationPreviewLastMessage | null>(() => {\n\t\tif (!lastTimelineMessage) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst isFromVisitor = lastTimelineMessage.visitorId !== null;\n\n\t\tlet senderName = text(\"common.fallbacks.unknown\");\n\t\tlet senderImage: string | null = null;\n\n\t\tif (isFromVisitor) {\n\t\t\tsenderName = text(\"common.fallbacks.you\");\n\t\t} else if (lastTimelineMessage.userId) {\n\t\t\tconst agent = availableHumanAgents.find(\n\t\t\t\t(a) => a.id === lastTimelineMessage.userId\n\t\t\t);\n\t\t\tif (agent) {\n\t\t\t\tsenderName = agent.name;\n\t\t\t\tsenderImage = agent.image;\n\t\t\t} else {\n\t\t\t\tsenderName = text(\"common.fallbacks.supportTeam\");\n\t\t\t}\n\t\t} else if (lastTimelineMessage.aiAgentId) {\n\t\t\tconst aiAgent = availableAIAgents.find(\n\t\t\t\t(agent) => agent.id === lastTimelineMessage.aiAgentId\n\t\t\t);\n\t\t\tif (aiAgent) {\n\t\t\t\tsenderName = aiAgent.name;\n\t\t\t\tsenderImage = aiAgent.image;\n\t\t\t} else {\n\t\t\t\tsenderName = text(\"common.fallbacks.aiAssistant\");\n\t\t\t}\n\t\t} else {\n\t\t\tsenderName = text(\"common.fallbacks.supportTeam\");\n\t\t}\n\n\t\treturn {\n\t\t\tcontent: lastTimelineMessage.text || \"\",\n\t\t\ttime: formatTimeAgo(lastTimelineMessage.createdAt),\n\t\t\tisFromVisitor,\n\t\t\tsenderName,\n\t\t\tsenderImage,\n\t\t};\n\t}, [lastTimelineMessage, availableHumanAgents, availableAIAgents, text]);\n\n\tconst assignedAgent = useMemo<ConversationPreviewAssignedAgent>(() => {\n\t\tconst supportFallbackName = text(\"common.fallbacks.supportTeam\");\n\t\tconst aiFallbackName = text(\"common.fallbacks.aiAssistant\");\n\n\t\tconst lastAgentItem = [...knownTimelineItems]\n\t\t\t.reverse()\n\t\t\t.find((item) => item.userId !== null || item.aiAgentId !== null);\n\n\t\tif (lastAgentItem?.userId) {\n\t\t\tconst human = availableHumanAgents.find(\n\t\t\t\t(agent) => agent.id === lastAgentItem.userId\n\t\t\t);\n\n\t\t\tif (human) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"human\" as const,\n\t\t\t\t\tname: human.name,\n\t\t\t\t\timage: human.image ?? null,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\ttype: \"human\" as const,\n\t\t\t\tname: supportFallbackName,\n\t\t\t\timage: null,\n\t\t\t};\n\t\t}\n\n\t\tif (lastAgentItem?.aiAgentId) {\n\t\t\tconst ai = availableAIAgents.find(\n\t\t\t\t(agent) => agent.id === lastAgentItem.aiAgentId\n\t\t\t);\n\n\t\t\tif (ai) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"ai\" as const,\n\t\t\t\t\tname: ai.name,\n\t\t\t\t\timage: ai.image ?? null,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\ttype: \"ai\" as const,\n\t\t\t\tname: aiFallbackName,\n\t\t\t\timage: null,\n\t\t\t};\n\t\t}\n\n\t\tconst fallbackHuman = availableHumanAgents[0];\n\t\tif (fallbackHuman) {\n\t\t\treturn {\n\t\t\t\ttype: \"human\" as const,\n\t\t\t\tname: fallbackHuman.name,\n\t\t\t\timage: fallbackHuman.image ?? null,\n\t\t\t};\n\t\t}\n\n\t\tconst fallbackAi = availableAIAgents[0];\n\t\tif (fallbackAi) {\n\t\t\treturn {\n\t\t\t\ttype: \"ai\" as const,\n\t\t\t\tname: fallbackAi.name,\n\t\t\t\timage: fallbackAi.image ?? null,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\ttype: \"fallback\" as const,\n\t\t\tname: supportFallbackName,\n\t\t\timage: null,\n\t\t};\n\t}, [knownTimelineItems, availableHumanAgents, availableAIAgents, text]);\n\n\tconst typingEntries = useConversationTyping(conversation.id, {\n\t\texcludeVisitorId: typing?.excludeVisitorId ?? visitor?.id ?? null,\n\t\texcludeUserId: typing?.excludeUserId ?? null,\n\t\texcludeAiAgentId: typing?.excludeAiAgentId ?? null,\n\t});\n\n\tconst typingParticipants = useMemo(\n\t\t() =>\n\t\t\tmapTypingEntriesToPreviewParticipants(typingEntries, {\n\t\t\t\tavailableHumanAgents,\n\t\t\t\tavailableAIAgents,\n\t\t\t\ttext,\n\t\t\t}),\n\t\t[typingEntries, availableHumanAgents, availableAIAgents, text]\n\t);\n\n\tconst primaryTypingParticipant = typingParticipants[0] ?? null;\n\n\tconst typingLabel = useMemo(() => {\n\t\tif (!primaryTypingParticipant) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn text(\"component.conversationButtonLink.typing\", {\n\t\t\tname: primaryTypingParticipant.name,\n\t\t});\n\t}, [primaryTypingParticipant, text]);\n\n\tconst typingState: ConversationPreviewTypingState = useMemo(\n\t\t() => ({\n\t\t\tparticipants: typingParticipants,\n\t\t\tprimaryParticipant: primaryTypingParticipant,\n\t\t\tlabel: typingLabel,\n\t\t\tisTyping: typingParticipants.length > 0,\n\t\t}),\n\t\t[typingParticipants, primaryTypingParticipant, typingLabel]\n\t);\n\n\tconst title = useMemo(() => {\n\t\tif (conversation.title) {\n\t\t\treturn conversation.title;\n\t\t}\n\n\t\tif (lastMessage?.content) {\n\t\t\treturn lastMessage.content;\n\t\t}\n\n\t\treturn text(\"component.conversationButtonLink.fallbackTitle\");\n\t}, [conversation.title, lastMessage?.content, text]);\n\n\treturn {\n\t\tconversation,\n\t\ttitle,\n\t\tlastMessage,\n\t\tassignedAgent,\n\t\ttyping: typingState,\n\t\ttimeline,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -4,6 +4,10 @@ import { ConversationSeen } from "../schemas.js";
|
|
|
4
4
|
type UseConversationSeenOptions = {
|
|
5
5
|
initialData?: ConversationSeen[];
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* Reads the conversation seen store and optionally hydrates it with SSR
|
|
9
|
+
* payloads.
|
|
10
|
+
*/
|
|
7
11
|
declare function useConversationSeen(conversationId: string | null | undefined, options?: UseConversationSeenOptions): ConversationSeen[];
|
|
8
12
|
/**
|
|
9
13
|
* Debounced version of useConversationSeen that delays updates by 500ms
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-seen.d.ts","names":[],"sources":["../../src/hooks/use-conversation-seen.ts"],"sourcesContent":[],"mappings":";;;KAIK,0BAAA;gBACU;AALmD,CAAA;
|
|
1
|
+
{"version":3,"file":"use-conversation-seen.d.ts","names":[],"sources":["../../src/hooks/use-conversation-seen.ts"],"sourcesContent":[],"mappings":";;;KAIK,0BAAA;gBACU;AALmD,CAAA;AAoBlE;AAwDA;;;iBAxDgB,mBAAA,sDAEN,6BACP;;;;;;;iBAqDa,4BAAA,sDAEN,6CAEP"}
|
|
@@ -5,6 +5,10 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
5
5
|
function buildSeenId(conversationId, actorType, actorId) {
|
|
6
6
|
return `${conversationId}-${actorType}-${actorId}`;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Reads the conversation seen store and optionally hydrates it with SSR
|
|
10
|
+
* payloads.
|
|
11
|
+
*/
|
|
8
12
|
function useConversationSeen(conversationId, options = {}) {
|
|
9
13
|
const { initialData } = options;
|
|
10
14
|
const hydratedKeyRef = useRef(null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-seen.js","names":[],"sources":["../../src/hooks/use-conversation-seen.ts"],"sourcesContent":["import type { ConversationSeen } from \"@cossistant/types/schemas\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { hydrateConversationSeen, useSeenStore } from \"../realtime/seen-store\";\n\ntype UseConversationSeenOptions = {\n\tinitialData?: ConversationSeen[];\n};\n\nfunction buildSeenId(\n\tconversationId: string,\n\tactorType: string,\n\tactorId: string\n) {\n\treturn `${conversationId}-${actorType}-${actorId}`;\n}\n\nexport function useConversationSeen(\n\tconversationId: string | null | undefined,\n\toptions: UseConversationSeenOptions = {}\n): ConversationSeen[] {\n\tconst { initialData } = options;\n\tconst hydratedKeyRef = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!(conversationId && initialData) || initialData.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst hydrationKey = `${conversationId}:${initialData\n\t\t\t.map((entry) => `${entry.id}:${new Date(entry.updatedAt).getTime()}`)\n\t\t\t.join(\"|\")}`;\n\n\t\tif (hydratedKeyRef.current === hydrationKey) {\n\t\t\treturn;\n\t\t}\n\n\t\thydrateConversationSeen(conversationId, initialData);\n\t\thydratedKeyRef.current = hydrationKey;\n\t}, [conversationId, initialData]);\n\n\tconst conversationSeen = useSeenStore((state) =>\n\t\tconversationId ? (state.conversations[conversationId] ?? null) : null\n\t);\n\n\treturn useMemo(() => {\n\t\tif (!(conversationId && conversationSeen)) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn Object.values(conversationSeen).map(\n\t\t\t(entry) =>\n\t\t\t\t({\n\t\t\t\t\tid: buildSeenId(conversationId, entry.actorType, entry.actorId),\n\t\t\t\t\tconversationId,\n\t\t\t\t\tuserId: entry.actorType === \"user\" ? entry.actorId : null,\n\t\t\t\t\tvisitorId: entry.actorType === \"visitor\" ? entry.actorId : null,\n\t\t\t\t\taiAgentId: entry.actorType === \"ai_agent\" ? entry.actorId : null,\n\t\t\t\t\tlastSeenAt: entry.lastSeenAt,\n\t\t\t\t\tcreatedAt: entry.lastSeenAt,\n\t\t\t\t\tupdatedAt: entry.lastSeenAt,\n\t\t\t\t\tdeletedAt: null,\n\t\t\t\t}) satisfies ConversationSeen\n\t\t);\n\t}, [conversationId, conversationSeen]);\n}\n\n/**\n * Debounced version of useConversationSeen that delays updates by 500ms\n * to prevent animation conflicts when messages are sent and immediately seen.\n *\n * Use this in UI components where smooth animations are critical.\n */\nexport function useDebouncedConversationSeen(\n\tconversationId: string | null | undefined,\n\toptions: UseConversationSeenOptions = {},\n\tdelay = 500\n): ConversationSeen[] {\n\tconst seenData = useConversationSeen(conversationId, options);\n\tconst [debouncedSeenData, setDebouncedSeenData] =\n\t\tuseState<ConversationSeen[]>(seenData);\n\tconst timeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n\tuseEffect(() => {\n\t\t// Clear any pending timeout\n\t\tif (timeoutRef.current) {\n\t\t\tclearTimeout(timeoutRef.current);\n\t\t}\n\n\t\t// Set new timeout to update after delay\n\t\ttimeoutRef.current = setTimeout(() => {\n\t\t\tsetDebouncedSeenData(seenData);\n\t\t}, delay);\n\n\t\t// Cleanup on unmount or when seenData changes\n\t\treturn () => {\n\t\t\tif (timeoutRef.current) {\n\t\t\t\tclearTimeout(timeoutRef.current);\n\t\t\t}\n\t\t};\n\t}, [seenData, delay]);\n\n\treturn debouncedSeenData;\n}\n"],"mappings":";;;;AAQA,SAAS,YACR,gBACA,WACA,SACC;AACD,QAAO,GAAG,eAAe,GAAG,UAAU,GAAG
|
|
1
|
+
{"version":3,"file":"use-conversation-seen.js","names":[],"sources":["../../src/hooks/use-conversation-seen.ts"],"sourcesContent":["import type { ConversationSeen } from \"@cossistant/types/schemas\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { hydrateConversationSeen, useSeenStore } from \"../realtime/seen-store\";\n\ntype UseConversationSeenOptions = {\n\tinitialData?: ConversationSeen[];\n};\n\nfunction buildSeenId(\n\tconversationId: string,\n\tactorType: string,\n\tactorId: string\n) {\n\treturn `${conversationId}-${actorType}-${actorId}`;\n}\n\n/**\n * Reads the conversation seen store and optionally hydrates it with SSR\n * payloads.\n */\nexport function useConversationSeen(\n\tconversationId: string | null | undefined,\n\toptions: UseConversationSeenOptions = {}\n): ConversationSeen[] {\n\tconst { initialData } = options;\n\tconst hydratedKeyRef = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!(conversationId && initialData) || initialData.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst hydrationKey = `${conversationId}:${initialData\n\t\t\t.map((entry) => `${entry.id}:${new Date(entry.updatedAt).getTime()}`)\n\t\t\t.join(\"|\")}`;\n\n\t\tif (hydratedKeyRef.current === hydrationKey) {\n\t\t\treturn;\n\t\t}\n\n\t\thydrateConversationSeen(conversationId, initialData);\n\t\thydratedKeyRef.current = hydrationKey;\n\t}, [conversationId, initialData]);\n\n\tconst conversationSeen = useSeenStore((state) =>\n\t\tconversationId ? (state.conversations[conversationId] ?? null) : null\n\t);\n\n\treturn useMemo(() => {\n\t\tif (!(conversationId && conversationSeen)) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn Object.values(conversationSeen).map(\n\t\t\t(entry) =>\n\t\t\t\t({\n\t\t\t\t\tid: buildSeenId(conversationId, entry.actorType, entry.actorId),\n\t\t\t\t\tconversationId,\n\t\t\t\t\tuserId: entry.actorType === \"user\" ? entry.actorId : null,\n\t\t\t\t\tvisitorId: entry.actorType === \"visitor\" ? entry.actorId : null,\n\t\t\t\t\taiAgentId: entry.actorType === \"ai_agent\" ? entry.actorId : null,\n\t\t\t\t\tlastSeenAt: entry.lastSeenAt,\n\t\t\t\t\tcreatedAt: entry.lastSeenAt,\n\t\t\t\t\tupdatedAt: entry.lastSeenAt,\n\t\t\t\t\tdeletedAt: null,\n\t\t\t\t}) satisfies ConversationSeen\n\t\t);\n\t}, [conversationId, conversationSeen]);\n}\n\n/**\n * Debounced version of useConversationSeen that delays updates by 500ms\n * to prevent animation conflicts when messages are sent and immediately seen.\n *\n * Use this in UI components where smooth animations are critical.\n */\nexport function useDebouncedConversationSeen(\n\tconversationId: string | null | undefined,\n\toptions: UseConversationSeenOptions = {},\n\tdelay = 500\n): ConversationSeen[] {\n\tconst seenData = useConversationSeen(conversationId, options);\n\tconst [debouncedSeenData, setDebouncedSeenData] =\n\t\tuseState<ConversationSeen[]>(seenData);\n\tconst timeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n\tuseEffect(() => {\n\t\t// Clear any pending timeout\n\t\tif (timeoutRef.current) {\n\t\t\tclearTimeout(timeoutRef.current);\n\t\t}\n\n\t\t// Set new timeout to update after delay\n\t\ttimeoutRef.current = setTimeout(() => {\n\t\t\tsetDebouncedSeenData(seenData);\n\t\t}, delay);\n\n\t\t// Cleanup on unmount or when seenData changes\n\t\treturn () => {\n\t\t\tif (timeoutRef.current) {\n\t\t\t\tclearTimeout(timeoutRef.current);\n\t\t\t}\n\t\t};\n\t}, [seenData, delay]);\n\n\treturn debouncedSeenData;\n}\n"],"mappings":";;;;AAQA,SAAS,YACR,gBACA,WACA,SACC;AACD,QAAO,GAAG,eAAe,GAAG,UAAU,GAAG;;;;;;AAO1C,SAAgB,oBACf,gBACA,UAAsC,EAAE,EACnB;CACrB,MAAM,EAAE,gBAAgB;CACxB,MAAM,iBAAiB,OAAsB,KAAK;AAElD,iBAAgB;AACf,MAAI,EAAE,kBAAkB,gBAAgB,YAAY,WAAW,EAC9D;EAGD,MAAM,eAAe,GAAG,eAAe,GAAG,YACxC,KAAK,UAAU,GAAG,MAAM,GAAG,GAAG,IAAI,KAAK,MAAM,UAAU,CAAC,SAAS,GAAG,CACpE,KAAK,IAAI;AAEX,MAAI,eAAe,YAAY,aAC9B;AAGD,0BAAwB,gBAAgB,YAAY;AACpD,iBAAe,UAAU;IACvB,CAAC,gBAAgB,YAAY,CAAC;CAEjC,MAAM,mBAAmB,cAAc,UACtC,iBAAkB,MAAM,cAAc,mBAAmB,OAAQ,KACjE;AAED,QAAO,cAAc;AACpB,MAAI,EAAE,kBAAkB,kBACvB,QAAO,EAAE;AAGV,SAAO,OAAO,OAAO,iBAAiB,CAAC,KACrC,WACC;GACA,IAAI,YAAY,gBAAgB,MAAM,WAAW,MAAM,QAAQ;GAC/D;GACA,QAAQ,MAAM,cAAc,SAAS,MAAM,UAAU;GACrD,WAAW,MAAM,cAAc,YAAY,MAAM,UAAU;GAC3D,WAAW,MAAM,cAAc,aAAa,MAAM,UAAU;GAC5D,YAAY,MAAM;GAClB,WAAW,MAAM;GACjB,WAAW,MAAM;GACjB,WAAW;GACX,EACF;IACC,CAAC,gBAAgB,iBAAiB,CAAC;;;;;;;;AASvC,SAAgB,6BACf,gBACA,UAAsC,EAAE,EACxC,QAAQ,KACa;CACrB,MAAM,WAAW,oBAAoB,gBAAgB,QAAQ;CAC7D,MAAM,CAAC,mBAAmB,wBACzB,SAA6B,SAAS;CACvC,MAAM,aAAa,OAA8B,KAAK;AAEtD,iBAAgB;AAEf,MAAI,WAAW,QACd,cAAa,WAAW,QAAQ;AAIjC,aAAW,UAAU,iBAAiB;AACrC,wBAAqB,SAAS;KAC5B,MAAM;AAGT,eAAa;AACZ,OAAI,WAAW,QACd,cAAa,WAAW,QAAQ;;IAGhC,CAAC,UAAU,MAAM,CAAC;AAErB,QAAO"}
|
|
@@ -15,6 +15,10 @@ type UseConversationTimelineItemsResult = ConversationTimelineItemsState & {
|
|
|
15
15
|
refetch: (args?: Pick<GetConversationTimelineItemsRequest, "cursor" | "limit">) => Promise<GetConversationTimelineItemsResponse | undefined>;
|
|
16
16
|
fetchNextPage: () => Promise<GetConversationTimelineItemsResponse | undefined>;
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Fetches timeline items for a conversation and keeps the local store in sync
|
|
20
|
+
* with pagination helpers.
|
|
21
|
+
*/
|
|
18
22
|
declare function useConversationTimelineItems(conversationId: string | null | undefined, options?: UseConversationTimelineItemsOptions): UseConversationTimelineItemsResult;
|
|
19
23
|
//#endregion
|
|
20
24
|
export { UseConversationTimelineItemsOptions, UseConversationTimelineItemsResult, useConversationTimelineItems };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-timeline-items.d.ts","names":[],"sources":["../../src/hooks/use-conversation-timeline-items.ts"],"sourcesContent":[],"mappings":";;;;KAoBY,mCAAA;;EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EAQA,OAAA,CAAA,EAAA,OAAA;
|
|
1
|
+
{"version":3,"file":"use-conversation-timeline-items.d.ts","names":[],"sources":["../../src/hooks/use-conversation-timeline-items.ts"],"sourcesContent":[],"mappings":";;;;KAoBY,mCAAA;;EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EAQA,OAAA,CAAA,EAAA,OAAA;EACX,eAAA,CAAA,EAAA,MAAA,GAAA,KAAA;EAEQ,oBAAA,CAAA,EAAA,OAAA;CAEM;AAAL,KALE,kCAAA,GACX,8BAIS,GAAA;EACK,SAAA,EAAA,OAAA;EAAR,KAAA,EAHE,KAGF,GAAA,IAAA;EAEJ,OAAA,EAAA,CAAA,IAAA,CAAA,EAHO,IAGP,CAHY,mCAGZ,EAAA,QAAA,GAAA,OAAA,CAAA,EAAA,GAFI,OAEJ,CAFY,oCAEZ,GAAA,SAAA,CAAA;EADoB,aAAA,EAAA,GAAA,GAAA,OAAA,CACpB,oCADoB,GAAA,SAAA,CAAA;CAAO;AAS9B;;;;iBAAgB,4BAAA,sDAEN,sCACP"}
|
|
@@ -11,6 +11,10 @@ const EMPTY_STATE = {
|
|
|
11
11
|
};
|
|
12
12
|
const DEFAULT_LIMIT = 50;
|
|
13
13
|
const NO_CONVERSATION_ID = "__no_conversation__";
|
|
14
|
+
/**
|
|
15
|
+
* Fetches timeline items for a conversation and keeps the local store in sync
|
|
16
|
+
* with pagination helpers.
|
|
17
|
+
*/
|
|
14
18
|
function useConversationTimelineItems(conversationId, options = {}) {
|
|
15
19
|
const { client } = useSupport();
|
|
16
20
|
const store = client.timelineItemsStore;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-timeline-items.js","names":["EMPTY_STATE: ConversationTimelineItemsState"],"sources":["../../src/hooks/use-conversation-timeline-items.ts"],"sourcesContent":["import type { ConversationTimelineItemsState } from \"@cossistant/core\";\nimport type {\n\tGetConversationTimelineItemsRequest,\n\tGetConversationTimelineItemsResponse,\n} from \"@cossistant/types/api/timeline-item\";\nimport { useCallback, useMemo } from \"react\";\nimport { useSupport } from \"../provider\";\nimport { useStoreSelector } from \"./private/store/use-store-selector\";\nimport { useClientQuery } from \"./private/use-client-query\";\n\nconst EMPTY_STATE: ConversationTimelineItemsState = {\n\titems: [],\n\thasNextPage: false,\n\tnextCursor: undefined,\n};\n\nconst DEFAULT_LIMIT = 50;\n\nconst NO_CONVERSATION_ID = \"__no_conversation__\" as const;\n\nexport type UseConversationTimelineItemsOptions = {\n\tlimit?: number;\n\tcursor?: string | null;\n\tenabled?: boolean;\n\trefetchInterval?: number | false;\n\trefetchOnWindowFocus?: boolean;\n};\n\nexport type UseConversationTimelineItemsResult =\n\tConversationTimelineItemsState & {\n\t\tisLoading: boolean;\n\t\terror: Error | null;\n\t\trefetch: (\n\t\t\targs?: Pick<GetConversationTimelineItemsRequest, \"cursor\" | \"limit\">\n\t\t) => Promise<GetConversationTimelineItemsResponse | undefined>;\n\t\tfetchNextPage: () => Promise<\n\t\t\tGetConversationTimelineItemsResponse | undefined\n\t\t>;\n\t};\n\nexport function useConversationTimelineItems(\n\tconversationId: string | null | undefined,\n\toptions: UseConversationTimelineItemsOptions = {}\n): UseConversationTimelineItemsResult {\n\tconst { client } = useSupport();\n\tconst store = client.timelineItemsStore;\n\n\tif (!store) {\n\t\tthrow new Error(\n\t\t\t\"Timeline items store is not available on the client instance\"\n\t\t);\n\t}\n\n\tconst stableConversationId = conversationId ?? NO_CONVERSATION_ID;\n\n\tconst selection = useStoreSelector(\n\t\tstore,\n\t\t(state) => state.conversations[stableConversationId] ?? EMPTY_STATE\n\t);\n\n\tconst baseArgs = useMemo(\n\t\t() =>\n\t\t\t({\n\t\t\t\tlimit: options.limit ?? DEFAULT_LIMIT,\n\t\t\t\tcursor: options.cursor ?? undefined,\n\t\t\t}) satisfies Pick<\n\t\t\t\tGetConversationTimelineItemsRequest,\n\t\t\t\t\"limit\" | \"cursor\"\n\t\t\t>,\n\t\t[options.cursor, options.limit]\n\t);\n\n\tconst {\n\t\trefetch: queryRefetch,\n\t\tisLoading: queryLoading,\n\t\terror,\n\t} = useClientQuery<\n\t\tGetConversationTimelineItemsResponse,\n\t\tPick<GetConversationTimelineItemsRequest, \"cursor\" | \"limit\">\n\t>({\n\t\tclient,\n\t\tqueryFn: (instance, args) => {\n\t\t\tif (!conversationId) {\n\t\t\t\treturn Promise.resolve({\n\t\t\t\t\titems: [],\n\t\t\t\t\thasNextPage: false,\n\t\t\t\t\tnextCursor: null,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn instance.getConversationTimelineItems({\n\t\t\t\tconversationId,\n\t\t\t\tlimit: args?.limit ?? baseArgs.limit,\n\t\t\t\tcursor: args?.cursor ?? baseArgs.cursor ?? undefined,\n\t\t\t});\n\t\t},\n\t\tenabled: Boolean(conversationId) && (options.enabled ?? true),\n\t\trefetchInterval: options.refetchInterval ?? false,\n\t\trefetchOnWindowFocus: options.refetchOnWindowFocus ?? true,\n\t\trefetchOnMount: selection.items.length === 0,\n\t\tinitialArgs: baseArgs,\n\t\tdependencies: [\n\t\t\tstableConversationId,\n\t\t\tbaseArgs.limit,\n\t\t\tbaseArgs.cursor ?? null,\n\t\t],\n\t});\n\n\tconst refetch = useCallback(\n\t\t(args?: Pick<GetConversationTimelineItemsRequest, \"cursor\" | \"limit\">) => {\n\t\t\tif (!conversationId) {\n\t\t\t\treturn Promise.resolve(undefined);\n\t\t\t}\n\n\t\t\treturn queryRefetch({\n\t\t\t\tlimit: baseArgs.limit,\n\t\t\t\tcursor: baseArgs.cursor,\n\t\t\t\t...args,\n\t\t\t});\n\t\t},\n\t\t[queryRefetch, baseArgs, conversationId]\n\t);\n\n\tconst fetchNextPage = useCallback(() => {\n\t\tif (!(selection.hasNextPage && selection.nextCursor)) {\n\t\t\treturn Promise.resolve(undefined);\n\t\t}\n\n\t\treturn refetch({ cursor: selection.nextCursor, limit: baseArgs.limit });\n\t}, [selection.hasNextPage, selection.nextCursor, refetch, baseArgs.limit]);\n\n\tconst isInitialLoad = selection.items.length === 0;\n\tconst isLoading = isInitialLoad ? queryLoading : false;\n\n\treturn {\n\t\titems: selection.items,\n\t\thasNextPage: selection.hasNextPage,\n\t\tnextCursor: selection.nextCursor,\n\t\tisLoading,\n\t\terror,\n\t\trefetch,\n\t\tfetchNextPage,\n\t};\n}\n"],"mappings":";;;;;;AAUA,MAAMA,cAA8C;CACnD,OAAO,EAAE;CACT,aAAa;CACb,YAAY;CACZ;AAED,MAAM,gBAAgB;AAEtB,MAAM,qBAAqB
|
|
1
|
+
{"version":3,"file":"use-conversation-timeline-items.js","names":["EMPTY_STATE: ConversationTimelineItemsState"],"sources":["../../src/hooks/use-conversation-timeline-items.ts"],"sourcesContent":["import type { ConversationTimelineItemsState } from \"@cossistant/core\";\nimport type {\n\tGetConversationTimelineItemsRequest,\n\tGetConversationTimelineItemsResponse,\n} from \"@cossistant/types/api/timeline-item\";\nimport { useCallback, useMemo } from \"react\";\nimport { useSupport } from \"../provider\";\nimport { useStoreSelector } from \"./private/store/use-store-selector\";\nimport { useClientQuery } from \"./private/use-client-query\";\n\nconst EMPTY_STATE: ConversationTimelineItemsState = {\n\titems: [],\n\thasNextPage: false,\n\tnextCursor: undefined,\n};\n\nconst DEFAULT_LIMIT = 50;\n\nconst NO_CONVERSATION_ID = \"__no_conversation__\" as const;\n\nexport type UseConversationTimelineItemsOptions = {\n\tlimit?: number;\n\tcursor?: string | null;\n\tenabled?: boolean;\n\trefetchInterval?: number | false;\n\trefetchOnWindowFocus?: boolean;\n};\n\nexport type UseConversationTimelineItemsResult =\n\tConversationTimelineItemsState & {\n\t\tisLoading: boolean;\n\t\terror: Error | null;\n\t\trefetch: (\n\t\t\targs?: Pick<GetConversationTimelineItemsRequest, \"cursor\" | \"limit\">\n\t\t) => Promise<GetConversationTimelineItemsResponse | undefined>;\n\t\tfetchNextPage: () => Promise<\n\t\t\tGetConversationTimelineItemsResponse | undefined\n\t\t>;\n\t};\n\n/**\n * Fetches timeline items for a conversation and keeps the local store in sync\n * with pagination helpers.\n */\nexport function useConversationTimelineItems(\n\tconversationId: string | null | undefined,\n\toptions: UseConversationTimelineItemsOptions = {}\n): UseConversationTimelineItemsResult {\n\tconst { client } = useSupport();\n\tconst store = client.timelineItemsStore;\n\n\tif (!store) {\n\t\tthrow new Error(\n\t\t\t\"Timeline items store is not available on the client instance\"\n\t\t);\n\t}\n\n\tconst stableConversationId = conversationId ?? NO_CONVERSATION_ID;\n\n\tconst selection = useStoreSelector(\n\t\tstore,\n\t\t(state) => state.conversations[stableConversationId] ?? EMPTY_STATE\n\t);\n\n\tconst baseArgs = useMemo(\n\t\t() =>\n\t\t\t({\n\t\t\t\tlimit: options.limit ?? DEFAULT_LIMIT,\n\t\t\t\tcursor: options.cursor ?? undefined,\n\t\t\t}) satisfies Pick<\n\t\t\t\tGetConversationTimelineItemsRequest,\n\t\t\t\t\"limit\" | \"cursor\"\n\t\t\t>,\n\t\t[options.cursor, options.limit]\n\t);\n\n\tconst {\n\t\trefetch: queryRefetch,\n\t\tisLoading: queryLoading,\n\t\terror,\n\t} = useClientQuery<\n\t\tGetConversationTimelineItemsResponse,\n\t\tPick<GetConversationTimelineItemsRequest, \"cursor\" | \"limit\">\n\t>({\n\t\tclient,\n\t\tqueryFn: (instance, args) => {\n\t\t\tif (!conversationId) {\n\t\t\t\treturn Promise.resolve({\n\t\t\t\t\titems: [],\n\t\t\t\t\thasNextPage: false,\n\t\t\t\t\tnextCursor: null,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn instance.getConversationTimelineItems({\n\t\t\t\tconversationId,\n\t\t\t\tlimit: args?.limit ?? baseArgs.limit,\n\t\t\t\tcursor: args?.cursor ?? baseArgs.cursor ?? undefined,\n\t\t\t});\n\t\t},\n\t\tenabled: Boolean(conversationId) && (options.enabled ?? true),\n\t\trefetchInterval: options.refetchInterval ?? false,\n\t\trefetchOnWindowFocus: options.refetchOnWindowFocus ?? true,\n\t\trefetchOnMount: selection.items.length === 0,\n\t\tinitialArgs: baseArgs,\n\t\tdependencies: [\n\t\t\tstableConversationId,\n\t\t\tbaseArgs.limit,\n\t\t\tbaseArgs.cursor ?? null,\n\t\t],\n\t});\n\n\tconst refetch = useCallback(\n\t\t(args?: Pick<GetConversationTimelineItemsRequest, \"cursor\" | \"limit\">) => {\n\t\t\tif (!conversationId) {\n\t\t\t\treturn Promise.resolve(undefined);\n\t\t\t}\n\n\t\t\treturn queryRefetch({\n\t\t\t\tlimit: baseArgs.limit,\n\t\t\t\tcursor: baseArgs.cursor,\n\t\t\t\t...args,\n\t\t\t});\n\t\t},\n\t\t[queryRefetch, baseArgs, conversationId]\n\t);\n\n\tconst fetchNextPage = useCallback(() => {\n\t\tif (!(selection.hasNextPage && selection.nextCursor)) {\n\t\t\treturn Promise.resolve(undefined);\n\t\t}\n\n\t\treturn refetch({ cursor: selection.nextCursor, limit: baseArgs.limit });\n\t}, [selection.hasNextPage, selection.nextCursor, refetch, baseArgs.limit]);\n\n\tconst isInitialLoad = selection.items.length === 0;\n\tconst isLoading = isInitialLoad ? queryLoading : false;\n\n\treturn {\n\t\titems: selection.items,\n\t\thasNextPage: selection.hasNextPage,\n\t\tnextCursor: selection.nextCursor,\n\t\tisLoading,\n\t\terror,\n\t\trefetch,\n\t\tfetchNextPage,\n\t};\n}\n"],"mappings":";;;;;;AAUA,MAAMA,cAA8C;CACnD,OAAO,EAAE;CACT,aAAa;CACb,YAAY;CACZ;AAED,MAAM,gBAAgB;AAEtB,MAAM,qBAAqB;;;;;AA0B3B,SAAgB,6BACf,gBACA,UAA+C,EAAE,EACZ;CACrC,MAAM,EAAE,WAAW,YAAY;CAC/B,MAAM,QAAQ,OAAO;AAErB,KAAI,CAAC,MACJ,OAAM,IAAI,MACT,+DACA;CAGF,MAAM,uBAAuB,kBAAkB;CAE/C,MAAM,YAAY,iBACjB,QACC,UAAU,MAAM,cAAc,yBAAyB,YACxD;CAED,MAAM,WAAW,eAEd;EACA,OAAO,QAAQ,SAAS;EACxB,QAAQ,QAAQ,UAAU;EAC1B,GAIF,CAAC,QAAQ,QAAQ,QAAQ,MAAM,CAC/B;CAED,MAAM,EACL,SAAS,cACT,WAAW,cACX,UACG,eAGF;EACD;EACA,UAAU,UAAU,SAAS;AAC5B,OAAI,CAAC,eACJ,QAAO,QAAQ,QAAQ;IACtB,OAAO,EAAE;IACT,aAAa;IACb,YAAY;IACZ,CAAC;AAGH,UAAO,SAAS,6BAA6B;IAC5C;IACA,OAAO,MAAM,SAAS,SAAS;IAC/B,QAAQ,MAAM,UAAU,SAAS,UAAU;IAC3C,CAAC;;EAEH,SAAS,QAAQ,eAAe,KAAK,QAAQ,WAAW;EACxD,iBAAiB,QAAQ,mBAAmB;EAC5C,sBAAsB,QAAQ,wBAAwB;EACtD,gBAAgB,UAAU,MAAM,WAAW;EAC3C,aAAa;EACb,cAAc;GACb;GACA,SAAS;GACT,SAAS,UAAU;GACnB;EACD,CAAC;CAEF,MAAM,UAAU,aACd,SAAyE;AACzE,MAAI,CAAC,eACJ,QAAO,QAAQ,QAAQ,OAAU;AAGlC,SAAO,aAAa;GACnB,OAAO,SAAS;GAChB,QAAQ,SAAS;GACjB,GAAG;GACH,CAAC;IAEH;EAAC;EAAc;EAAU;EAAe,CACxC;CAED,MAAM,gBAAgB,kBAAkB;AACvC,MAAI,EAAE,UAAU,eAAe,UAAU,YACxC,QAAO,QAAQ,QAAQ,OAAU;AAGlC,SAAO,QAAQ;GAAE,QAAQ,UAAU;GAAY,OAAO,SAAS;GAAO,CAAC;IACrE;EAAC,UAAU;EAAa,UAAU;EAAY;EAAS,SAAS;EAAM,CAAC;CAG1E,MAAM,YADgB,UAAU,MAAM,WAAW,IACf,eAAe;AAEjD,QAAO;EACN,OAAO,UAAU;EACjB,aAAa,UAAU;EACvB,YAAY,UAAU;EACtB;EACA;EACA;EACA;EACA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { TimelineItem } from "../timeline-item.js";
|
|
2
|
+
import { useGroupedMessages } from "./private/use-grouped-messages.js";
|
|
3
|
+
import { TimelineTypingParticipant } from "./private/typing.js";
|
|
4
|
+
import { useDebouncedConversationSeen } from "./use-conversation-seen.js";
|
|
5
|
+
import { useConversationTyping } from "./use-conversation-typing.js";
|
|
6
|
+
|
|
7
|
+
//#region src/hooks/use-conversation-timeline.d.ts
|
|
8
|
+
type ConversationTimelineTypingParticipant = TimelineTypingParticipant;
|
|
9
|
+
type UseConversationTimelineOptions = {
|
|
10
|
+
conversationId: string;
|
|
11
|
+
items: TimelineItem[];
|
|
12
|
+
currentVisitorId?: string;
|
|
13
|
+
};
|
|
14
|
+
type UseConversationTimelineReturn = {
|
|
15
|
+
groupedMessages: ReturnType<typeof useGroupedMessages>;
|
|
16
|
+
seenData: ReturnType<typeof useDebouncedConversationSeen>;
|
|
17
|
+
typingEntries: ReturnType<typeof useConversationTyping>;
|
|
18
|
+
typingParticipants: ConversationTimelineTypingParticipant[];
|
|
19
|
+
lastVisitorMessageGroupIndex: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Produces grouped timeline items, seen data and typing state suitable for the
|
|
23
|
+
* conversation detail view.
|
|
24
|
+
*/
|
|
25
|
+
declare function useConversationTimeline({
|
|
26
|
+
conversationId,
|
|
27
|
+
items: timelineItems,
|
|
28
|
+
currentVisitorId
|
|
29
|
+
}: UseConversationTimelineOptions): UseConversationTimelineReturn;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { ConversationTimelineTypingParticipant, UseConversationTimelineOptions, UseConversationTimelineReturn, useConversationTimeline };
|
|
32
|
+
//# sourceMappingURL=use-conversation-timeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-conversation-timeline.d.ts","names":[],"sources":["../../src/hooks/use-conversation-timeline.ts"],"sourcesContent":[],"mappings":";;;;;;;KAYY,qCAAA,GAAwC;KAExC,8BAAA;EAFA,cAAA,EAAA,MAAA;EAEA,KAAA,EAEJ,YAFI,EAAA;EAMA,gBAAA,CAAA,EAAA,MAAA;CACwB;AAAlB,KADN,6BAAA,GACM;EACW,eAAA,EADX,UACW,CAAA,OADO,kBACP,CAAA;EAAlB,QAAA,EAAA,UAAA,CAAA,OAAkB,4BAAlB,CAAA;EACuB,aAAA,EAAlB,UAAkB,CAAA,OAAA,qBAAA,CAAA;EAAlB,kBAAA,EACK,qCADL,EAAA;EACK,4BAAA,EAAA,MAAA;CAAqC;AAQ1D;;;;AAIG,iBAJa,uBAAA,CAIb;EAAA,cAAA;EAAA,KAAA,EAFK,aAEL;EAAA;AAAA,CAAA,EAAA,8BAAA,CAAA,EAAiC,6BAAjC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { mapTypingEntriesToParticipants } from "./private/typing.js";
|
|
2
|
+
import { useGroupedMessages } from "./private/use-grouped-messages.js";
|
|
3
|
+
import { useDebouncedConversationSeen } from "./use-conversation-seen.js";
|
|
4
|
+
import { useConversationTyping } from "./use-conversation-typing.js";
|
|
5
|
+
import { useMemo } from "react";
|
|
6
|
+
import { SenderType } from "@cossistant/types";
|
|
7
|
+
|
|
8
|
+
//#region src/hooks/use-conversation-timeline.ts
|
|
9
|
+
/**
|
|
10
|
+
* Produces grouped timeline items, seen data and typing state suitable for the
|
|
11
|
+
* conversation detail view.
|
|
12
|
+
*/
|
|
13
|
+
function useConversationTimeline({ conversationId, items: timelineItems, currentVisitorId }) {
|
|
14
|
+
const seenData = useDebouncedConversationSeen(conversationId);
|
|
15
|
+
const typingEntries = useConversationTyping(conversationId, { excludeVisitorId: currentVisitorId ?? null });
|
|
16
|
+
const groupedMessages = useGroupedMessages({
|
|
17
|
+
items: timelineItems,
|
|
18
|
+
seenData,
|
|
19
|
+
currentViewerId: currentVisitorId,
|
|
20
|
+
viewerType: SenderType.VISITOR
|
|
21
|
+
});
|
|
22
|
+
const lastVisitorMessageGroupIndex = useMemo(() => {
|
|
23
|
+
for (let index = groupedMessages.items.length - 1; index >= 0; index--) {
|
|
24
|
+
const item = groupedMessages.items[index];
|
|
25
|
+
if (!item || item.type !== "message_group") continue;
|
|
26
|
+
if ((item.items?.[0])?.visitorId === currentVisitorId) return index;
|
|
27
|
+
}
|
|
28
|
+
return -1;
|
|
29
|
+
}, [groupedMessages.items, currentVisitorId]);
|
|
30
|
+
return {
|
|
31
|
+
groupedMessages,
|
|
32
|
+
seenData,
|
|
33
|
+
typingEntries,
|
|
34
|
+
typingParticipants: useMemo(() => mapTypingEntriesToParticipants(typingEntries), [typingEntries]),
|
|
35
|
+
lastVisitorMessageGroupIndex
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { useConversationTimeline };
|
|
41
|
+
//# sourceMappingURL=use-conversation-timeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-conversation-timeline.js","names":[],"sources":["../../src/hooks/use-conversation-timeline.ts"],"sourcesContent":["import { SenderType } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { useMemo } from \"react\";\n\nimport {\n\tmapTypingEntriesToParticipants,\n\ttype TimelineTypingParticipant,\n} from \"./private/typing\";\nimport { useGroupedMessages } from \"./private/use-grouped-messages\";\nimport { useDebouncedConversationSeen } from \"./use-conversation-seen\";\nimport { useConversationTyping } from \"./use-conversation-typing\";\n\nexport type ConversationTimelineTypingParticipant = TimelineTypingParticipant;\n\nexport type UseConversationTimelineOptions = {\n\tconversationId: string;\n\titems: TimelineItem[];\n\tcurrentVisitorId?: string;\n};\n\nexport type UseConversationTimelineReturn = {\n\tgroupedMessages: ReturnType<typeof useGroupedMessages>;\n\tseenData: ReturnType<typeof useDebouncedConversationSeen>;\n\ttypingEntries: ReturnType<typeof useConversationTyping>;\n\ttypingParticipants: ConversationTimelineTypingParticipant[];\n\tlastVisitorMessageGroupIndex: number;\n};\n\n/**\n * Produces grouped timeline items, seen data and typing state suitable for the\n * conversation detail view.\n */\nexport function useConversationTimeline({\n\tconversationId,\n\titems: timelineItems,\n\tcurrentVisitorId,\n}: UseConversationTimelineOptions): UseConversationTimelineReturn {\n\tconst seenData = useDebouncedConversationSeen(conversationId);\n\tconst typingEntries = useConversationTyping(conversationId, {\n\t\texcludeVisitorId: currentVisitorId ?? null,\n\t});\n\n\tconst groupedMessages = useGroupedMessages({\n\t\titems: timelineItems,\n\t\tseenData,\n\t\tcurrentViewerId: currentVisitorId,\n\t\tviewerType: SenderType.VISITOR,\n\t});\n\n\tconst lastVisitorMessageGroupIndex = useMemo(() => {\n\t\tfor (let index = groupedMessages.items.length - 1; index >= 0; index--) {\n\t\t\tconst item = groupedMessages.items[index];\n\n\t\t\tif (!item || item.type !== \"message_group\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst firstMessage = item.items?.[0];\n\t\t\tif (firstMessage?.visitorId === currentVisitorId) {\n\t\t\t\treturn index;\n\t\t\t}\n\t\t}\n\n\t\treturn -1;\n\t}, [groupedMessages.items, currentVisitorId]);\n\n\tconst typingParticipants = useMemo(\n\t\t() => mapTypingEntriesToParticipants(typingEntries),\n\t\t[typingEntries]\n\t);\n\n\treturn {\n\t\tgroupedMessages,\n\t\tseenData,\n\t\ttypingEntries,\n\t\ttypingParticipants,\n\t\tlastVisitorMessageGroupIndex,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;AAgCA,SAAgB,wBAAwB,EACvC,gBACA,OAAO,eACP,oBACiE;CACjE,MAAM,WAAW,6BAA6B,eAAe;CAC7D,MAAM,gBAAgB,sBAAsB,gBAAgB,EAC3D,kBAAkB,oBAAoB,MACtC,CAAC;CAEF,MAAM,kBAAkB,mBAAmB;EAC1C,OAAO;EACP;EACA,iBAAiB;EACjB,YAAY,WAAW;EACvB,CAAC;CAEF,MAAM,+BAA+B,cAAc;AAClD,OAAK,IAAI,QAAQ,gBAAgB,MAAM,SAAS,GAAG,SAAS,GAAG,SAAS;GACvE,MAAM,OAAO,gBAAgB,MAAM;AAEnC,OAAI,CAAC,QAAQ,KAAK,SAAS,gBAC1B;AAID,QADqB,KAAK,QAAQ,KAChB,cAAc,iBAC/B,QAAO;;AAIT,SAAO;IACL,CAAC,gBAAgB,OAAO,iBAAiB,CAAC;AAO7C,QAAO;EACN;EACA;EACA;EACA,oBAT0B,cACpB,+BAA+B,cAAc,EACnD,CAAC,cAAc,CACf;EAOA;EACA"}
|
|
@@ -7,6 +7,10 @@ type UseConversationTypingOptions = {
|
|
|
7
7
|
excludeUserId?: string | null;
|
|
8
8
|
excludeAiAgentId?: string | null;
|
|
9
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* Selects typing participants for a conversation while letting consumers omit
|
|
12
|
+
* their own identities.
|
|
13
|
+
*/
|
|
10
14
|
declare function useConversationTyping(conversationId: string | null | undefined, options?: UseConversationTypingOptions): ConversationTypingParticipant[];
|
|
11
15
|
//#endregion
|
|
12
16
|
export { ConversationTypingParticipant, useConversationTyping };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-typing.d.ts","names":[],"sources":["../../src/hooks/use-conversation-typing.ts"],"sourcesContent":[],"mappings":";;;KAIY,6BAAA,GAAgC;KAEvC,4BAAA;EAFO,gBAAA,CAAA,EAAA,MAAA,GAAA,IAA6B;EAEpC,aAAA,CAAA,EAAA,MAAA,GAAA,IAAA;
|
|
1
|
+
{"version":3,"file":"use-conversation-typing.d.ts","names":[],"sources":["../../src/hooks/use-conversation-typing.ts"],"sourcesContent":[],"mappings":";;;KAIY,6BAAA,GAAgC;KAEvC,4BAAA;EAFO,gBAAA,CAAA,EAAA,MAAA,GAAA,IAA6B;EAEpC,aAAA,CAAA,EAAA,MAAA,GAAA,IAAA;EA6BW,gBAAA,CAAA,EAAA,MAAqB,GAAA,IAAA;;;;;;iBAArB,qBAAA,sDAEN,+BACP"}
|
|
@@ -8,6 +8,10 @@ function shouldExclude(entry, options) {
|
|
|
8
8
|
if (entry.actorType === "ai_agent" && options.excludeAiAgentId) return entry.actorId === options.excludeAiAgentId;
|
|
9
9
|
return false;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Selects typing participants for a conversation while letting consumers omit
|
|
13
|
+
* their own identities.
|
|
14
|
+
*/
|
|
11
15
|
function useConversationTyping(conversationId, options = {}) {
|
|
12
16
|
const conversationTyping = useTypingStore((state) => conversationId ? state.conversations[conversationId] ?? null : null);
|
|
13
17
|
return useMemo(() => {
|