@cossistant/react 0.0.30 → 0.0.31
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 +3 -1
- package/hooks/private/use-grouped-messages.d.ts.map +1 -1
- package/hooks/private/use-grouped-messages.js +34 -10
- package/hooks/private/use-grouped-messages.js.map +1 -1
- package/hooks/use-conversation-page.js +4 -2
- package/hooks/use-conversation-page.js.map +1 -1
- package/hooks/use-conversation-preview.d.ts.map +1 -1
- package/hooks/use-conversation-preview.js +2 -1
- package/hooks/use-conversation-preview.js.map +1 -1
- package/package.json +5 -4
- package/packages/types/src/api/conversation.d.ts +21 -15
- package/packages/types/src/api/conversation.d.ts.map +1 -1
- package/packages/types/src/api/timeline-item.d.ts +9 -9
- package/packages/types/src/realtime-events.d.ts +19 -15
- package/packages/types/src/realtime-events.d.ts.map +1 -1
- package/packages/types/src/schemas.d.ts +6 -4
- package/packages/types/src/schemas.d.ts.map +1 -1
- package/primitives/multimodal-input.d.ts +2 -2
- package/primitives/multimodal-input.d.ts.map +1 -1
- package/primitives/timeline-item.d.ts.map +1 -1
- package/primitives/timeline-item.js +22 -7
- package/primitives/timeline-item.js.map +1 -1
- package/support/components/button.d.ts +1 -1
- package/support/components/conversation-resolved-feedback.d.ts +21 -0
- package/support/components/conversation-resolved-feedback.d.ts.map +1 -0
- package/support/components/conversation-resolved-feedback.js +59 -0
- package/support/components/conversation-resolved-feedback.js.map +1 -0
- package/support/components/conversation-timeline-utils.d.ts +5 -0
- package/support/components/conversation-timeline-utils.d.ts.map +1 -0
- package/support/components/conversation-timeline-utils.js +10 -0
- package/support/components/conversation-timeline-utils.js.map +1 -0
- package/support/components/conversation-timeline.d.ts.map +1 -1
- package/support/components/conversation-timeline.js +2 -1
- package/support/components/conversation-timeline.js.map +1 -1
- package/support/components/icons.d.ts +1 -1
- package/support/components/icons.d.ts.map +1 -1
- package/support/components/icons.js +6 -2
- package/support/components/icons.js.map +1 -1
- package/support/components/index.d.ts +2 -1
- package/support/components/index.js +2 -1
- package/support/pages/conversation.d.ts.map +1 -1
- package/support/pages/conversation.js +33 -7
- package/support/pages/conversation.js.map +1 -1
- package/support/text/locales/en.js +9 -0
- package/support/text/locales/en.js.map +1 -1
- package/support/text/locales/es.js +9 -0
- package/support/text/locales/es.js.map +1 -1
- package/support/text/locales/fr.js +9 -0
- package/support/text/locales/fr.js.map +1 -1
- package/support/text/locales/keys.d.ts +11 -0
- package/support/text/locales/keys.d.ts.map +1 -1
- package/support/text/locales/keys.js +3 -0
- package/support/text/locales/keys.js.map +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ export function Dashboard({
|
|
|
84
84
|
defaultMessages={[
|
|
85
85
|
{
|
|
86
86
|
content:
|
|
87
|
-
"Welcome
|
|
87
|
+
"Welcome to your dashboard. If you need any help, I'm here!",
|
|
88
88
|
senderType: SenderType.TeamMember,
|
|
89
89
|
},
|
|
90
90
|
]}
|
|
@@ -94,6 +94,8 @@ export function Dashboard({
|
|
|
94
94
|
}
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
Make sure `IdentifySupportVisitor` and `SupportConfig` are rendered inside `SupportProvider`, and keep `<Support />` mounted somewhere in that tree.
|
|
98
|
+
|
|
97
99
|
## Need help or spot a typo?
|
|
98
100
|
|
|
99
101
|
Open an issue in the main repository or start a discussion so we can improve the docs together. Screenshots, reproduction steps, and suggestions are welcome.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-grouped-messages.d.ts","names":[],"sources":["../../../src/hooks/private/use-grouped-messages.ts"],"sourcesContent":[],"mappings":";;;;;KAKY,cAAA;;EAAA,QAAA,EAAA,MAAA;EAGC,UAAA,EAAA,UAAA;EACL,KAAA,EAAA,YAAA,EAAA;EAGW,cAAA,EAAA,MAAA;EACD,aAAA,EAAA,MAAA;EAAI,gBAAA,EADH,IACG;EAGV,eAAA,EAHM,IAGW;AAM7B,CAAA;AAOY,KAbA,iBAAA,GAagB;EAMhB,IAAA,EAAA,gBAAgB;EACzB,IAAA,EAlBI,YAkBJ;EACA,SAAA,EAlBS,IAkBT;CACA;AACA,KAjBS,gBAAA,GAiBT;EAAgB,IAAA,EAAA,eAAA;EAEP,IAAA,EAjBL,YAiBK;EAMA,IAAA,EAAA,MAAA,GAAA,IAAA;
|
|
1
|
+
{"version":3,"file":"use-grouped-messages.d.ts","names":[],"sources":["../../../src/hooks/private/use-grouped-messages.ts"],"sourcesContent":[],"mappings":";;;;;KAKY,cAAA;;EAAA,QAAA,EAAA,MAAA;EAGC,UAAA,EAAA,UAAA;EACL,KAAA,EAAA,YAAA,EAAA;EAGW,cAAA,EAAA,MAAA;EACD,aAAA,EAAA,MAAA;EAAI,gBAAA,EADH,IACG;EAGV,eAAA,EAHM,IAGW;AAM7B,CAAA;AAOY,KAbA,iBAAA,GAagB;EAMhB,IAAA,EAAA,gBAAgB;EACzB,IAAA,EAlBI,YAkBJ;EACA,SAAA,EAlBS,IAkBT;CACA;AACA,KAjBS,gBAAA,GAiBT;EAAgB,IAAA,EAAA,eAAA;EAEP,IAAA,EAjBL,YAiBK;EAMA,IAAA,EAAA,MAAA,GAAA,IAAA;EA6OC,SAAA,EAlQD,IAkQC;CAAsB;AAAA,KA/PvB,gBAAA,GA+PuB;EAAA,IAAA,EAAA,eAAA;EAIhC,IAAA,EAjQI,IAiQJ;;;KA7PS,gBAAA,GACT,iBACA,oBACA,mBACA;KAES,yBAAA;SACJ;aACI;;;KAIA,uBAAA,GAA0B;;;;;;;cA6OzB;;;;GAIV"}
|
|
@@ -107,7 +107,7 @@ const groupTimelineItems = (items) => {
|
|
|
107
107
|
if (currentGroup) result.push(currentGroup);
|
|
108
108
|
return result;
|
|
109
109
|
};
|
|
110
|
-
const buildTimelineReadReceiptData = (seenData, items, sortedMessageItems) => {
|
|
110
|
+
const buildTimelineReadReceiptData = (seenData, items, sortedMessageItems, sortedMessageTimes) => {
|
|
111
111
|
const seenByMap = /* @__PURE__ */ new Map();
|
|
112
112
|
const lastReadMessageMap = /* @__PURE__ */ new Map();
|
|
113
113
|
const unreadCountMap = /* @__PURE__ */ new Map();
|
|
@@ -118,13 +118,17 @@ const buildTimelineReadReceiptData = (seenData, items, sortedMessageItems) => {
|
|
|
118
118
|
if (!viewerId) continue;
|
|
119
119
|
let lastReadItem = null;
|
|
120
120
|
let unreadCount = 0;
|
|
121
|
-
for (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
for (let index = 0; index < sortedMessageItems.length; index++) {
|
|
122
|
+
const item = sortedMessageItems[index];
|
|
123
|
+
if (!item) continue;
|
|
124
|
+
if ((sortedMessageTimes[index] ?? getTimestamp(item.createdAt)) <= seenTime) {
|
|
125
|
+
if (item.id) {
|
|
126
|
+
const seenBy = seenByMap.get(item.id);
|
|
127
|
+
if (seenBy) seenBy.add(viewerId);
|
|
128
|
+
}
|
|
129
|
+
lastReadItem = item;
|
|
130
|
+
} else unreadCount++;
|
|
131
|
+
}
|
|
128
132
|
if (lastReadItem?.id) lastReadMessageMap.set(viewerId, lastReadItem.id);
|
|
129
133
|
unreadCountMap.set(viewerId, unreadCount);
|
|
130
134
|
}
|
|
@@ -143,13 +147,33 @@ const buildTimelineReadReceiptData = (seenData, items, sortedMessageItems) => {
|
|
|
143
147
|
const useGroupedMessages = ({ items, seenData = [], currentViewerId }) => {
|
|
144
148
|
return useMemo(() => {
|
|
145
149
|
const groupedItems = groupTimelineItems(items);
|
|
146
|
-
const
|
|
150
|
+
const messageItems = items.filter((item) => item.type === "message");
|
|
151
|
+
let sortedMessageItems = messageItems;
|
|
152
|
+
let sortedMessageTimes = messageItems.map((item) => getTimestamp(item.createdAt));
|
|
153
|
+
let isSorted = true;
|
|
154
|
+
for (let index = 1; index < sortedMessageTimes.length; index++) {
|
|
155
|
+
const currentTime = sortedMessageTimes[index];
|
|
156
|
+
const previousTime = sortedMessageTimes[index - 1];
|
|
157
|
+
if (currentTime !== void 0 && previousTime !== void 0 && currentTime < previousTime) {
|
|
158
|
+
isSorted = false;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!isSorted) {
|
|
163
|
+
const itemsWithTimes = messageItems.map((item, index) => ({
|
|
164
|
+
item,
|
|
165
|
+
time: sortedMessageTimes[index] ?? 0
|
|
166
|
+
}));
|
|
167
|
+
itemsWithTimes.sort((a, b) => a.time - b.time);
|
|
168
|
+
sortedMessageItems = itemsWithTimes.map((entry) => entry.item);
|
|
169
|
+
sortedMessageTimes = itemsWithTimes.map((entry) => entry.time);
|
|
170
|
+
}
|
|
147
171
|
const messageIndexMap = /* @__PURE__ */ new Map();
|
|
148
172
|
for (let i = 0; i < sortedMessageItems.length; i++) {
|
|
149
173
|
const item = sortedMessageItems[i];
|
|
150
174
|
if (item?.id) messageIndexMap.set(item.id, i);
|
|
151
175
|
}
|
|
152
|
-
const { seenByMap, lastReadMessageMap, unreadCountMap } = buildTimelineReadReceiptData(seenData, items, sortedMessageItems);
|
|
176
|
+
const { seenByMap, lastReadMessageMap, unreadCountMap } = buildTimelineReadReceiptData(seenData, items, sortedMessageItems, sortedMessageTimes);
|
|
153
177
|
const seenByArrayCache = /* @__PURE__ */ new Map();
|
|
154
178
|
return {
|
|
155
179
|
items: groupedItems,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-grouped-messages.js","names":["EMPTY_STRING_ARRAY: readonly string[]","result: ConversationItem[]","currentGroup: GroupedMessage | null","currentDayString: string | null","lastReadItem: TimelineItem | null"],"sources":["../../../src/hooks/private/use-grouped-messages.ts"],"sourcesContent":["import { SenderType } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport type { ConversationSeen } from \"@cossistant/types/schemas\";\nimport { useMemo } from \"react\";\n\nexport type GroupedMessage = {\n\ttype: \"message_group\";\n\tsenderId: string;\n\tsenderType: SenderType;\n\titems: TimelineItem[];\n\tfirstMessageId: string;\n\tlastMessageId: string;\n\tfirstMessageTime: Date;\n\tlastMessageTime: Date;\n};\n\nexport type TimelineEventItem = {\n\ttype: \"timeline_event\";\n\titem: TimelineItem;\n\ttimestamp: Date;\n};\n\nexport type TimelineToolItem = {\n\ttype: \"timeline_tool\";\n\titem: TimelineItem;\n\ttool: string | null;\n\ttimestamp: Date;\n};\n\nexport type DaySeparatorItem = {\n\ttype: \"day_separator\";\n\tdate: Date;\n\tdateString: string; // ISO date string (YYYY-MM-DD) for stable keys\n};\n\nexport type ConversationItem =\n\t| GroupedMessage\n\t| TimelineEventItem\n\t| TimelineToolItem\n\t| DaySeparatorItem;\n\nexport type UseGroupedMessagesOptions = {\n\titems: TimelineItem[];\n\tseenData?: ConversationSeen[];\n\tcurrentViewerId?: string; // The ID of the current viewer (visitor, user, or AI agent)\n};\n\nexport type UseGroupedMessagesProps = UseGroupedMessagesOptions;\n\n// Helper function to safely get timestamp from Date or string\nconst getTimestamp = (date: Date | string | null | undefined): number => {\n\tif (!date) {\n\t\treturn 0;\n\t}\n\tif (typeof date === \"string\") {\n\t\treturn new Date(date).getTime();\n\t}\n\treturn date.getTime();\n};\n\n// Helper function to safely convert to Date\nconst toDate = (date: Date | string | null | undefined): Date => {\n\tif (!date) {\n\t\treturn typeof window !== \"undefined\" ? new Date() : new Date(0);\n\t}\n\tif (typeof date === \"string\") {\n\t\treturn new Date(date);\n\t}\n\treturn date;\n};\n\n// Helper to extract the date string (YYYY-MM-DD) from a Date for day comparison\nconst getDateString = (date: Date): string => {\n\tconst year = date.getFullYear();\n\tconst month = String(date.getMonth() + 1).padStart(2, \"0\");\n\tconst day = String(date.getDate()).padStart(2, \"0\");\n\treturn `${year}-${month}-${day}`;\n};\n\n// Helper to create a Date at midnight for a given date string\nconst createDayDate = (dateString: string): Date => {\n\tconst [year, month, day] = dateString.split(\"-\").map(Number);\n\treturn new Date(year ?? 0, (month ?? 1) - 1, day ?? 1, 0, 0, 0, 0);\n};\n\n// Helper to determine sender ID and type from a timeline item\nconst getSenderIdAndTypeFromTimelineItem = (\n\titem: TimelineItem\n): { senderId: string; senderType: SenderType } => {\n\tif (item.visitorId) {\n\t\treturn { senderId: item.visitorId, senderType: SenderType.VISITOR };\n\t}\n\tif (item.aiAgentId) {\n\t\treturn { senderId: item.aiAgentId, senderType: SenderType.AI };\n\t}\n\tif (item.userId) {\n\t\treturn { senderId: item.userId, senderType: SenderType.TEAM_MEMBER };\n\t}\n\n\t// Fallback\n\treturn {\n\t\tsenderId: item.id || \"default-sender\",\n\t\tsenderType: SenderType.TEAM_MEMBER,\n\t};\n};\n\nconst EMPTY_STRING_ARRAY: readonly string[] = Object.freeze([]);\n\n// Helper function to group timeline items (messages only, events stay separate)\n// Also inserts day separators when the day changes between items\nconst groupTimelineItems = (items: TimelineItem[]): ConversationItem[] => {\n\tconst result: ConversationItem[] = [];\n\tlet currentGroup: GroupedMessage | null = null;\n\tlet currentDayString: string | null = null;\n\n\tconst maybeInsertDaySeparator = (itemDate: Date): void => {\n\t\tconst itemDayString = getDateString(itemDate);\n\n\t\tif (currentDayString !== itemDayString) {\n\t\t\t// Finalize any existing group before inserting day separator\n\t\t\tif (currentGroup) {\n\t\t\t\tresult.push(currentGroup);\n\t\t\t\tcurrentGroup = null;\n\t\t\t}\n\n\t\t\t// Insert day separator\n\t\t\tresult.push({\n\t\t\t\ttype: \"day_separator\",\n\t\t\t\tdate: createDayDate(itemDayString),\n\t\t\t\tdateString: itemDayString,\n\t\t\t});\n\n\t\t\tcurrentDayString = itemDayString;\n\t\t}\n\t};\n\n\tfor (const item of items) {\n\t\tconst itemDate = toDate(item.createdAt);\n\n\t\t// Check for day boundary before processing any item\n\t\tmaybeInsertDaySeparator(itemDate);\n\n\t\t// Events don't get grouped\n\t\tif (item.type === \"event\") {\n\t\t\t// Finalize any existing group\n\t\t\tif (currentGroup) {\n\t\t\t\tresult.push(currentGroup);\n\t\t\t\tcurrentGroup = null;\n\t\t\t}\n\n\t\t\t// Add event as standalone item\n\t\t\tresult.push({\n\t\t\t\ttype: \"timeline_event\",\n\t\t\t\titem,\n\t\t\t\ttimestamp: itemDate,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (item.type === \"identification\") {\n\t\t\t// Finalize any existing group\n\t\t\tif (currentGroup) {\n\t\t\t\tresult.push(currentGroup);\n\t\t\t\tcurrentGroup = null;\n\t\t\t}\n\n\t\t\t// Add tool item as standalone entry\n\t\t\tresult.push({\n\t\t\t\ttype: \"timeline_tool\",\n\t\t\t\titem,\n\t\t\t\ttool: item.tool ?? null,\n\t\t\t\ttimestamp: itemDate,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Group messages by sender\n\t\tconst { senderId, senderType } = getSenderIdAndTypeFromTimelineItem(item);\n\n\t\tif (currentGroup && currentGroup.senderId === senderId) {\n\t\t\t// Add to existing group (day boundary already handled above)\n\t\t\tcurrentGroup.items.push(item);\n\t\t\tcurrentGroup.lastMessageId = item.id || currentGroup.lastMessageId;\n\t\t\tcurrentGroup.lastMessageTime = itemDate;\n\t\t} else {\n\t\t\t// Finalize previous group if exists\n\t\t\tif (currentGroup) {\n\t\t\t\tresult.push(currentGroup);\n\t\t\t}\n\n\t\t\t// Start new group\n\t\t\tcurrentGroup = {\n\t\t\t\ttype: \"message_group\",\n\t\t\t\tsenderId,\n\t\t\t\tsenderType,\n\t\t\t\titems: [item],\n\t\t\t\tfirstMessageId: item.id || \"\",\n\t\t\t\tlastMessageId: item.id || \"\",\n\t\t\t\tfirstMessageTime: itemDate,\n\t\t\t\tlastMessageTime: itemDate,\n\t\t\t};\n\t\t}\n\t}\n\n\tif (currentGroup) {\n\t\tresult.push(currentGroup);\n\t}\n\n\treturn result;\n};\n\n// Build read receipt data for timeline items\n// Accepts pre-sorted message items for performance\nconst buildTimelineReadReceiptData = (\n\tseenData: ConversationSeen[],\n\titems: TimelineItem[],\n\tsortedMessageItems: TimelineItem[]\n) => {\n\tconst seenByMap = new Map<string, Set<string>>();\n\tconst lastReadMessageMap = new Map<string, string>();\n\tconst unreadCountMap = new Map<string, number>();\n\n\t// Initialize map for all message-type timeline items\n\tfor (const item of items) {\n\t\tif (item.type === \"message\" && item.id) {\n\t\t\tseenByMap.set(item.id, new Set());\n\t\t}\n\t}\n\n\t// Process seen data for each viewer\n\tfor (const seen of seenData) {\n\t\tconst seenTime = getTimestamp(seen.lastSeenAt);\n\t\tconst viewerId = seen.userId || seen.visitorId || seen.aiAgentId;\n\t\tif (!viewerId) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet lastReadItem: TimelineItem | null = null;\n\t\tlet unreadCount = 0;\n\n\t\t// Process items in chronological order (using pre-sorted array)\n\t\tfor (const item of sortedMessageItems) {\n\t\t\tconst itemTime = getTimestamp(item.createdAt);\n\n\t\t\tif (itemTime <= seenTime) {\n\t\t\t\t// This item has been seen\n\t\t\t\tif (item.id) {\n\t\t\t\t\tconst seenBy = seenByMap.get(item.id);\n\t\t\t\t\tif (seenBy) {\n\t\t\t\t\t\tseenBy.add(viewerId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlastReadItem = item;\n\t\t\t} else {\n\t\t\t\t// This item is unread\n\t\t\t\tunreadCount++;\n\t\t\t}\n\t\t}\n\n\t\t// Store the last read item for this viewer\n\t\tif (lastReadItem?.id) {\n\t\t\tlastReadMessageMap.set(viewerId, lastReadItem.id);\n\t\t}\n\n\t\t// Store unread count\n\t\tunreadCountMap.set(viewerId, unreadCount);\n\t}\n\n\treturn { seenByMap, lastReadMessageMap, unreadCountMap };\n};\n\n/**\n * Batches sequential timeline items from the same sender into groups and enriches\n * them with read-receipt helpers so UIs can render conversation timelines with\n * minimal effort. Seen data is normalised into quick lookup maps for unread\n * indicators.\n */\nexport const useGroupedMessages = ({\n\titems,\n\tseenData = [],\n\tcurrentViewerId,\n}: UseGroupedMessagesOptions) => {\n\treturn useMemo(() => {\n\t\tconst groupedItems = groupTimelineItems(items);\n\n\t\t// Pre-sort message items once for reuse (performance optimization)\n\t\tconst sortedMessageItems = items\n\t\t\t.filter((item) => item.type === \"message\")\n\t\t\t.sort((a, b) => getTimestamp(a.createdAt) - getTimestamp(b.createdAt));\n\n\t\t// Build index map from sorted items for O(1) chronological lookups\n\t\t// Must use sortedMessageItems (not raw items) to ensure indices reflect time order\n\t\tconst messageIndexMap = new Map<string, number>();\n\t\tfor (let i = 0; i < sortedMessageItems.length; i++) {\n\t\t\tconst item = sortedMessageItems[i];\n\t\t\tif (item?.id) {\n\t\t\t\tmessageIndexMap.set(item.id, i);\n\t\t\t}\n\t\t}\n\n\t\t// Build read receipt data with pre-sorted items\n\t\tconst { seenByMap, lastReadMessageMap, unreadCountMap } =\n\t\t\tbuildTimelineReadReceiptData(seenData, items, sortedMessageItems);\n\n\t\t// Cache for turning seen sets into stable arrays across renders\n\t\tconst seenByArrayCache = new Map<string, readonly string[]>();\n\n\t\treturn {\n\t\t\titems: groupedItems,\n\t\t\tseenByMap,\n\t\t\tlastReadMessageMap,\n\t\t\tunreadCountMap,\n\n\t\t\tisMessageSeenByViewer: (messageId: string): boolean => {\n\t\t\t\tif (!currentViewerId) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst seenBy = seenByMap.get(messageId);\n\t\t\t\treturn seenBy ? seenBy.has(currentViewerId) : false;\n\t\t\t},\n\n\t\t\tgetMessageSeenBy: (messageId: string): readonly string[] => {\n\t\t\t\tif (seenByArrayCache.has(messageId)) {\n\t\t\t\t\treturn seenByArrayCache.get(messageId) ?? EMPTY_STRING_ARRAY;\n\t\t\t\t}\n\n\t\t\t\tconst seenBy = seenByMap.get(messageId);\n\t\t\t\tif (!seenBy || seenBy.size === 0) {\n\t\t\t\t\tseenByArrayCache.set(messageId, EMPTY_STRING_ARRAY);\n\t\t\t\t\treturn EMPTY_STRING_ARRAY;\n\t\t\t\t}\n\n\t\t\t\tconst result = Object.freeze(Array.from(seenBy)) as readonly string[];\n\t\t\t\tseenByArrayCache.set(messageId, result);\n\t\t\t\treturn result;\n\t\t\t},\n\n\t\t\tgetLastReadMessageId: (userId: string): string | undefined =>\n\t\t\t\tlastReadMessageMap.get(userId),\n\n\t\t\tisLastReadMessage: (messageId: string, userId: string): boolean =>\n\t\t\t\tlastReadMessageMap.get(userId) === messageId,\n\n\t\t\tgetUnreadCount: (userId: string): number =>\n\t\t\t\tunreadCountMap.get(userId) || 0,\n\n\t\t\thasUnreadAfter: (messageId: string, userId: string): boolean => {\n\t\t\t\tconst lastRead = lastReadMessageMap.get(userId);\n\t\t\t\tif (!lastRead) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\t// Use index map for O(1) lookups instead of findIndex O(n)\n\t\t\t\tconst messageIndex = messageIndexMap.get(messageId);\n\t\t\t\tconst lastReadIndex = messageIndexMap.get(lastRead);\n\n\t\t\t\tif (messageIndex === undefined || lastReadIndex === undefined) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\treturn messageIndex < lastReadIndex;\n\t\t\t},\n\t\t};\n\t}, [items, seenData, currentViewerId]);\n};\n"],"mappings":";;;;AAkDA,MAAM,gBAAgB,SAAmD;AACxE,KAAI,CAAC,KACJ,QAAO;AAER,KAAI,OAAO,SAAS,SACnB,QAAO,IAAI,KAAK,KAAK,CAAC,SAAS;AAEhC,QAAO,KAAK,SAAS;;AAItB,MAAM,UAAU,SAAiD;AAChE,KAAI,CAAC,KACJ,QAAO,OAAO,WAAW,8BAAc,IAAI,MAAM,mBAAG,IAAI,KAAK,EAAE;AAEhE,KAAI,OAAO,SAAS,SACnB,QAAO,IAAI,KAAK,KAAK;AAEtB,QAAO;;AAIR,MAAM,iBAAiB,SAAuB;AAI7C,QAAO,GAHM,KAAK,aAAa,CAGhB,GAFD,OAAO,KAAK,UAAU,GAAG,EAAE,CAAC,SAAS,GAAG,IAAI,CAElC,GADZ,OAAO,KAAK,SAAS,CAAC,CAAC,SAAS,GAAG,IAAI;;AAKpD,MAAM,iBAAiB,eAA6B;CACnD,MAAM,CAAC,MAAM,OAAO,OAAO,WAAW,MAAM,IAAI,CAAC,IAAI,OAAO;AAC5D,QAAO,IAAI,KAAK,QAAQ,IAAI,SAAS,KAAK,GAAG,OAAO,GAAG,GAAG,GAAG,GAAG,EAAE;;AAInE,MAAM,sCACL,SACkD;AAClD,KAAI,KAAK,UACR,QAAO;EAAE,UAAU,KAAK;EAAW,YAAY,WAAW;EAAS;AAEpE,KAAI,KAAK,UACR,QAAO;EAAE,UAAU,KAAK;EAAW,YAAY,WAAW;EAAI;AAE/D,KAAI,KAAK,OACR,QAAO;EAAE,UAAU,KAAK;EAAQ,YAAY,WAAW;EAAa;AAIrE,QAAO;EACN,UAAU,KAAK,MAAM;EACrB,YAAY,WAAW;EACvB;;AAGF,MAAMA,qBAAwC,OAAO,OAAO,EAAE,CAAC;AAI/D,MAAM,sBAAsB,UAA8C;CACzE,MAAMC,SAA6B,EAAE;CACrC,IAAIC,eAAsC;CAC1C,IAAIC,mBAAkC;CAEtC,MAAM,2BAA2B,aAAyB;EACzD,MAAM,gBAAgB,cAAc,SAAS;AAE7C,MAAI,qBAAqB,eAAe;AAEvC,OAAI,cAAc;AACjB,WAAO,KAAK,aAAa;AACzB,mBAAe;;AAIhB,UAAO,KAAK;IACX,MAAM;IACN,MAAM,cAAc,cAAc;IAClC,YAAY;IACZ,CAAC;AAEF,sBAAmB;;;AAIrB,MAAK,MAAM,QAAQ,OAAO;EACzB,MAAM,WAAW,OAAO,KAAK,UAAU;AAGvC,0BAAwB,SAAS;AAGjC,MAAI,KAAK,SAAS,SAAS;AAE1B,OAAI,cAAc;AACjB,WAAO,KAAK,aAAa;AACzB,mBAAe;;AAIhB,UAAO,KAAK;IACX,MAAM;IACN;IACA,WAAW;IACX,CAAC;AACF;;AAGD,MAAI,KAAK,SAAS,kBAAkB;AAEnC,OAAI,cAAc;AACjB,WAAO,KAAK,aAAa;AACzB,mBAAe;;AAIhB,UAAO,KAAK;IACX,MAAM;IACN;IACA,MAAM,KAAK,QAAQ;IACnB,WAAW;IACX,CAAC;AACF;;EAID,MAAM,EAAE,UAAU,eAAe,mCAAmC,KAAK;AAEzE,MAAI,gBAAgB,aAAa,aAAa,UAAU;AAEvD,gBAAa,MAAM,KAAK,KAAK;AAC7B,gBAAa,gBAAgB,KAAK,MAAM,aAAa;AACrD,gBAAa,kBAAkB;SACzB;AAEN,OAAI,aACH,QAAO,KAAK,aAAa;AAI1B,kBAAe;IACd,MAAM;IACN;IACA;IACA,OAAO,CAAC,KAAK;IACb,gBAAgB,KAAK,MAAM;IAC3B,eAAe,KAAK,MAAM;IAC1B,kBAAkB;IAClB,iBAAiB;IACjB;;;AAIH,KAAI,aACH,QAAO,KAAK,aAAa;AAG1B,QAAO;;AAKR,MAAM,gCACL,UACA,OACA,uBACI;CACJ,MAAM,4BAAY,IAAI,KAA0B;CAChD,MAAM,qCAAqB,IAAI,KAAqB;CACpD,MAAM,iCAAiB,IAAI,KAAqB;AAGhD,MAAK,MAAM,QAAQ,MAClB,KAAI,KAAK,SAAS,aAAa,KAAK,GACnC,WAAU,IAAI,KAAK,oBAAI,IAAI,KAAK,CAAC;AAKnC,MAAK,MAAM,QAAQ,UAAU;EAC5B,MAAM,WAAW,aAAa,KAAK,WAAW;EAC9C,MAAM,WAAW,KAAK,UAAU,KAAK,aAAa,KAAK;AACvD,MAAI,CAAC,SACJ;EAGD,IAAIC,eAAoC;EACxC,IAAI,cAAc;AAGlB,OAAK,MAAM,QAAQ,mBAGlB,KAFiB,aAAa,KAAK,UAAU,IAE7B,UAAU;AAEzB,OAAI,KAAK,IAAI;IACZ,MAAM,SAAS,UAAU,IAAI,KAAK,GAAG;AACrC,QAAI,OACH,QAAO,IAAI,SAAS;;AAGtB,kBAAe;QAGf;AAKF,MAAI,cAAc,GACjB,oBAAmB,IAAI,UAAU,aAAa,GAAG;AAIlD,iBAAe,IAAI,UAAU,YAAY;;AAG1C,QAAO;EAAE;EAAW;EAAoB;EAAgB;;;;;;;;AASzD,MAAa,sBAAsB,EAClC,OACA,WAAW,EAAE,EACb,sBACgC;AAChC,QAAO,cAAc;EACpB,MAAM,eAAe,mBAAmB,MAAM;EAG9C,MAAM,qBAAqB,MACzB,QAAQ,SAAS,KAAK,SAAS,UAAU,CACzC,MAAM,GAAG,MAAM,aAAa,EAAE,UAAU,GAAG,aAAa,EAAE,UAAU,CAAC;EAIvE,MAAM,kCAAkB,IAAI,KAAqB;AACjD,OAAK,IAAI,IAAI,GAAG,IAAI,mBAAmB,QAAQ,KAAK;GACnD,MAAM,OAAO,mBAAmB;AAChC,OAAI,MAAM,GACT,iBAAgB,IAAI,KAAK,IAAI,EAAE;;EAKjC,MAAM,EAAE,WAAW,oBAAoB,mBACtC,6BAA6B,UAAU,OAAO,mBAAmB;EAGlE,MAAM,mCAAmB,IAAI,KAAgC;AAE7D,SAAO;GACN,OAAO;GACP;GACA;GACA;GAEA,wBAAwB,cAA+B;AACtD,QAAI,CAAC,gBACJ,QAAO;IAER,MAAM,SAAS,UAAU,IAAI,UAAU;AACvC,WAAO,SAAS,OAAO,IAAI,gBAAgB,GAAG;;GAG/C,mBAAmB,cAAyC;AAC3D,QAAI,iBAAiB,IAAI,UAAU,CAClC,QAAO,iBAAiB,IAAI,UAAU,IAAI;IAG3C,MAAM,SAAS,UAAU,IAAI,UAAU;AACvC,QAAI,CAAC,UAAU,OAAO,SAAS,GAAG;AACjC,sBAAiB,IAAI,WAAW,mBAAmB;AACnD,YAAO;;IAGR,MAAM,SAAS,OAAO,OAAO,MAAM,KAAK,OAAO,CAAC;AAChD,qBAAiB,IAAI,WAAW,OAAO;AACvC,WAAO;;GAGR,uBAAuB,WACtB,mBAAmB,IAAI,OAAO;GAE/B,oBAAoB,WAAmB,WACtC,mBAAmB,IAAI,OAAO,KAAK;GAEpC,iBAAiB,WAChB,eAAe,IAAI,OAAO,IAAI;GAE/B,iBAAiB,WAAmB,WAA4B;IAC/D,MAAM,WAAW,mBAAmB,IAAI,OAAO;AAC/C,QAAI,CAAC,SACJ,QAAO;IAIR,MAAM,eAAe,gBAAgB,IAAI,UAAU;IACnD,MAAM,gBAAgB,gBAAgB,IAAI,SAAS;AAEnD,QAAI,iBAAiB,UAAa,kBAAkB,OACnD,QAAO;AAGR,WAAO,eAAe;;GAEvB;IACC;EAAC;EAAO;EAAU;EAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"use-grouped-messages.js","names":["EMPTY_STRING_ARRAY: readonly string[]","result: ConversationItem[]","currentGroup: GroupedMessage | null","currentDayString: string | null","lastReadItem: TimelineItem | null"],"sources":["../../../src/hooks/private/use-grouped-messages.ts"],"sourcesContent":["import { SenderType } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport type { ConversationSeen } from \"@cossistant/types/schemas\";\nimport { useMemo } from \"react\";\n\nexport type GroupedMessage = {\n\ttype: \"message_group\";\n\tsenderId: string;\n\tsenderType: SenderType;\n\titems: TimelineItem[];\n\tfirstMessageId: string;\n\tlastMessageId: string;\n\tfirstMessageTime: Date;\n\tlastMessageTime: Date;\n};\n\nexport type TimelineEventItem = {\n\ttype: \"timeline_event\";\n\titem: TimelineItem;\n\ttimestamp: Date;\n};\n\nexport type TimelineToolItem = {\n\ttype: \"timeline_tool\";\n\titem: TimelineItem;\n\ttool: string | null;\n\ttimestamp: Date;\n};\n\nexport type DaySeparatorItem = {\n\ttype: \"day_separator\";\n\tdate: Date;\n\tdateString: string; // ISO date string (YYYY-MM-DD) for stable keys\n};\n\nexport type ConversationItem =\n\t| GroupedMessage\n\t| TimelineEventItem\n\t| TimelineToolItem\n\t| DaySeparatorItem;\n\nexport type UseGroupedMessagesOptions = {\n\titems: TimelineItem[];\n\tseenData?: ConversationSeen[];\n\tcurrentViewerId?: string; // The ID of the current viewer (visitor, user, or AI agent)\n};\n\nexport type UseGroupedMessagesProps = UseGroupedMessagesOptions;\n\n// Helper function to safely get timestamp from Date or string\nconst getTimestamp = (date: Date | string | null | undefined): number => {\n\tif (!date) {\n\t\treturn 0;\n\t}\n\tif (typeof date === \"string\") {\n\t\treturn new Date(date).getTime();\n\t}\n\treturn date.getTime();\n};\n\n// Helper function to safely convert to Date\nconst toDate = (date: Date | string | null | undefined): Date => {\n\tif (!date) {\n\t\treturn typeof window !== \"undefined\" ? new Date() : new Date(0);\n\t}\n\tif (typeof date === \"string\") {\n\t\treturn new Date(date);\n\t}\n\treturn date;\n};\n\n// Helper to extract the date string (YYYY-MM-DD) from a Date for day comparison\nconst getDateString = (date: Date): string => {\n\tconst year = date.getFullYear();\n\tconst month = String(date.getMonth() + 1).padStart(2, \"0\");\n\tconst day = String(date.getDate()).padStart(2, \"0\");\n\treturn `${year}-${month}-${day}`;\n};\n\n// Helper to create a Date at midnight for a given date string\nconst createDayDate = (dateString: string): Date => {\n\tconst [year, month, day] = dateString.split(\"-\").map(Number);\n\treturn new Date(year ?? 0, (month ?? 1) - 1, day ?? 1, 0, 0, 0, 0);\n};\n\n// Helper to determine sender ID and type from a timeline item\nconst getSenderIdAndTypeFromTimelineItem = (\n\titem: TimelineItem\n): { senderId: string; senderType: SenderType } => {\n\tif (item.visitorId) {\n\t\treturn { senderId: item.visitorId, senderType: SenderType.VISITOR };\n\t}\n\tif (item.aiAgentId) {\n\t\treturn { senderId: item.aiAgentId, senderType: SenderType.AI };\n\t}\n\tif (item.userId) {\n\t\treturn { senderId: item.userId, senderType: SenderType.TEAM_MEMBER };\n\t}\n\n\t// Fallback\n\treturn {\n\t\tsenderId: item.id || \"default-sender\",\n\t\tsenderType: SenderType.TEAM_MEMBER,\n\t};\n};\n\nconst EMPTY_STRING_ARRAY: readonly string[] = Object.freeze([]);\n\n// Helper function to group timeline items (messages only, events stay separate)\n// Also inserts day separators when the day changes between items\nconst groupTimelineItems = (items: TimelineItem[]): ConversationItem[] => {\n\tconst result: ConversationItem[] = [];\n\tlet currentGroup: GroupedMessage | null = null;\n\tlet currentDayString: string | null = null;\n\n\tconst maybeInsertDaySeparator = (itemDate: Date): void => {\n\t\tconst itemDayString = getDateString(itemDate);\n\n\t\tif (currentDayString !== itemDayString) {\n\t\t\t// Finalize any existing group before inserting day separator\n\t\t\tif (currentGroup) {\n\t\t\t\tresult.push(currentGroup);\n\t\t\t\tcurrentGroup = null;\n\t\t\t}\n\n\t\t\t// Insert day separator\n\t\t\tresult.push({\n\t\t\t\ttype: \"day_separator\",\n\t\t\t\tdate: createDayDate(itemDayString),\n\t\t\t\tdateString: itemDayString,\n\t\t\t});\n\n\t\t\tcurrentDayString = itemDayString;\n\t\t}\n\t};\n\n\tfor (const item of items) {\n\t\tconst itemDate = toDate(item.createdAt);\n\n\t\t// Check for day boundary before processing any item\n\t\tmaybeInsertDaySeparator(itemDate);\n\n\t\t// Events don't get grouped\n\t\tif (item.type === \"event\") {\n\t\t\t// Finalize any existing group\n\t\t\tif (currentGroup) {\n\t\t\t\tresult.push(currentGroup);\n\t\t\t\tcurrentGroup = null;\n\t\t\t}\n\n\t\t\t// Add event as standalone item\n\t\t\tresult.push({\n\t\t\t\ttype: \"timeline_event\",\n\t\t\t\titem,\n\t\t\t\ttimestamp: itemDate,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (item.type === \"identification\") {\n\t\t\t// Finalize any existing group\n\t\t\tif (currentGroup) {\n\t\t\t\tresult.push(currentGroup);\n\t\t\t\tcurrentGroup = null;\n\t\t\t}\n\n\t\t\t// Add tool item as standalone entry\n\t\t\tresult.push({\n\t\t\t\ttype: \"timeline_tool\",\n\t\t\t\titem,\n\t\t\t\ttool: item.tool ?? null,\n\t\t\t\ttimestamp: itemDate,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Group messages by sender\n\t\tconst { senderId, senderType } = getSenderIdAndTypeFromTimelineItem(item);\n\n\t\tif (currentGroup && currentGroup.senderId === senderId) {\n\t\t\t// Add to existing group (day boundary already handled above)\n\t\t\tcurrentGroup.items.push(item);\n\t\t\tcurrentGroup.lastMessageId = item.id || currentGroup.lastMessageId;\n\t\t\tcurrentGroup.lastMessageTime = itemDate;\n\t\t} else {\n\t\t\t// Finalize previous group if exists\n\t\t\tif (currentGroup) {\n\t\t\t\tresult.push(currentGroup);\n\t\t\t}\n\n\t\t\t// Start new group\n\t\t\tcurrentGroup = {\n\t\t\t\ttype: \"message_group\",\n\t\t\t\tsenderId,\n\t\t\t\tsenderType,\n\t\t\t\titems: [item],\n\t\t\t\tfirstMessageId: item.id || \"\",\n\t\t\t\tlastMessageId: item.id || \"\",\n\t\t\t\tfirstMessageTime: itemDate,\n\t\t\t\tlastMessageTime: itemDate,\n\t\t\t};\n\t\t}\n\t}\n\n\tif (currentGroup) {\n\t\tresult.push(currentGroup);\n\t}\n\n\treturn result;\n};\n\n// Build read receipt data for timeline items\n// Accepts pre-sorted message items for performance\nconst buildTimelineReadReceiptData = (\n\tseenData: ConversationSeen[],\n\titems: TimelineItem[],\n\tsortedMessageItems: TimelineItem[],\n\tsortedMessageTimes: number[]\n) => {\n\tconst seenByMap = new Map<string, Set<string>>();\n\tconst lastReadMessageMap = new Map<string, string>();\n\tconst unreadCountMap = new Map<string, number>();\n\n\t// Initialize map for all message-type timeline items\n\tfor (const item of items) {\n\t\tif (item.type === \"message\" && item.id) {\n\t\t\tseenByMap.set(item.id, new Set());\n\t\t}\n\t}\n\n\t// Process seen data for each viewer\n\tfor (const seen of seenData) {\n\t\tconst seenTime = getTimestamp(seen.lastSeenAt);\n\t\tconst viewerId = seen.userId || seen.visitorId || seen.aiAgentId;\n\t\tif (!viewerId) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet lastReadItem: TimelineItem | null = null;\n\t\tlet unreadCount = 0;\n\n\t\t// Process items in chronological order (using pre-sorted array)\n\t\tfor (let index = 0; index < sortedMessageItems.length; index++) {\n\t\t\tconst item = sortedMessageItems[index];\n\t\t\tif (!item) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst itemTime =\n\t\t\t\tsortedMessageTimes[index] ?? getTimestamp(item.createdAt);\n\n\t\t\tif (itemTime <= seenTime) {\n\t\t\t\t// This item has been seen\n\t\t\t\tif (item.id) {\n\t\t\t\t\tconst seenBy = seenByMap.get(item.id);\n\t\t\t\t\tif (seenBy) {\n\t\t\t\t\t\tseenBy.add(viewerId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlastReadItem = item;\n\t\t\t} else {\n\t\t\t\t// This item is unread\n\t\t\t\tunreadCount++;\n\t\t\t}\n\t\t}\n\n\t\t// Store the last read item for this viewer\n\t\tif (lastReadItem?.id) {\n\t\t\tlastReadMessageMap.set(viewerId, lastReadItem.id);\n\t\t}\n\n\t\t// Store unread count\n\t\tunreadCountMap.set(viewerId, unreadCount);\n\t}\n\n\treturn { seenByMap, lastReadMessageMap, unreadCountMap };\n};\n\n/**\n * Batches sequential timeline items from the same sender into groups and enriches\n * them with read-receipt helpers so UIs can render conversation timelines with\n * minimal effort. Seen data is normalised into quick lookup maps for unread\n * indicators.\n */\nexport const useGroupedMessages = ({\n\titems,\n\tseenData = [],\n\tcurrentViewerId,\n}: UseGroupedMessagesOptions) => {\n\treturn useMemo(() => {\n\t\tconst groupedItems = groupTimelineItems(items);\n\n\t\t// Pre-compute message items and timestamps once for reuse\n\t\tconst messageItems = items.filter((item) => item.type === \"message\");\n\t\tlet sortedMessageItems = messageItems;\n\t\tlet sortedMessageTimes = messageItems.map((item) =>\n\t\t\tgetTimestamp(item.createdAt)\n\t\t);\n\n\t\t// Avoid sorting if items are already in chronological order\n\t\tlet isSorted = true;\n\t\tfor (let index = 1; index < sortedMessageTimes.length; index++) {\n\t\t\tconst currentTime = sortedMessageTimes[index];\n\t\t\tconst previousTime = sortedMessageTimes[index - 1];\n\t\t\tif (\n\t\t\t\tcurrentTime !== undefined &&\n\t\t\t\tpreviousTime !== undefined &&\n\t\t\t\tcurrentTime < previousTime\n\t\t\t) {\n\t\t\t\tisSorted = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!isSorted) {\n\t\t\tconst itemsWithTimes = messageItems.map((item, index) => ({\n\t\t\t\titem,\n\t\t\t\ttime: sortedMessageTimes[index] ?? 0,\n\t\t\t}));\n\n\t\t\titemsWithTimes.sort((a, b) => a.time - b.time);\n\n\t\t\tsortedMessageItems = itemsWithTimes.map((entry) => entry.item);\n\t\t\tsortedMessageTimes = itemsWithTimes.map((entry) => entry.time);\n\t\t}\n\n\t\t// Build index map from sorted items for O(1) chronological lookups\n\t\t// Must use sortedMessageItems (not raw items) to ensure indices reflect time order\n\t\tconst messageIndexMap = new Map<string, number>();\n\t\tfor (let i = 0; i < sortedMessageItems.length; i++) {\n\t\t\tconst item = sortedMessageItems[i];\n\t\t\tif (item?.id) {\n\t\t\t\tmessageIndexMap.set(item.id, i);\n\t\t\t}\n\t\t}\n\n\t\t// Build read receipt data with pre-sorted items\n\t\tconst { seenByMap, lastReadMessageMap, unreadCountMap } =\n\t\t\tbuildTimelineReadReceiptData(\n\t\t\t\tseenData,\n\t\t\t\titems,\n\t\t\t\tsortedMessageItems,\n\t\t\t\tsortedMessageTimes\n\t\t\t);\n\n\t\t// Cache for turning seen sets into stable arrays across renders\n\t\tconst seenByArrayCache = new Map<string, readonly string[]>();\n\n\t\treturn {\n\t\t\titems: groupedItems,\n\t\t\tseenByMap,\n\t\t\tlastReadMessageMap,\n\t\t\tunreadCountMap,\n\n\t\t\tisMessageSeenByViewer: (messageId: string): boolean => {\n\t\t\t\tif (!currentViewerId) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tconst seenBy = seenByMap.get(messageId);\n\t\t\t\treturn seenBy ? seenBy.has(currentViewerId) : false;\n\t\t\t},\n\n\t\t\tgetMessageSeenBy: (messageId: string): readonly string[] => {\n\t\t\t\tif (seenByArrayCache.has(messageId)) {\n\t\t\t\t\treturn seenByArrayCache.get(messageId) ?? EMPTY_STRING_ARRAY;\n\t\t\t\t}\n\n\t\t\t\tconst seenBy = seenByMap.get(messageId);\n\t\t\t\tif (!seenBy || seenBy.size === 0) {\n\t\t\t\t\tseenByArrayCache.set(messageId, EMPTY_STRING_ARRAY);\n\t\t\t\t\treturn EMPTY_STRING_ARRAY;\n\t\t\t\t}\n\n\t\t\t\tconst result = Object.freeze(Array.from(seenBy)) as readonly string[];\n\t\t\t\tseenByArrayCache.set(messageId, result);\n\t\t\t\treturn result;\n\t\t\t},\n\n\t\t\tgetLastReadMessageId: (userId: string): string | undefined =>\n\t\t\t\tlastReadMessageMap.get(userId),\n\n\t\t\tisLastReadMessage: (messageId: string, userId: string): boolean =>\n\t\t\t\tlastReadMessageMap.get(userId) === messageId,\n\n\t\t\tgetUnreadCount: (userId: string): number =>\n\t\t\t\tunreadCountMap.get(userId) || 0,\n\n\t\t\thasUnreadAfter: (messageId: string, userId: string): boolean => {\n\t\t\t\tconst lastRead = lastReadMessageMap.get(userId);\n\t\t\t\tif (!lastRead) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\t// Use index map for O(1) lookups instead of findIndex O(n)\n\t\t\t\tconst messageIndex = messageIndexMap.get(messageId);\n\t\t\t\tconst lastReadIndex = messageIndexMap.get(lastRead);\n\n\t\t\t\tif (messageIndex === undefined || lastReadIndex === undefined) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\treturn messageIndex < lastReadIndex;\n\t\t\t},\n\t\t};\n\t}, [items, seenData, currentViewerId]);\n};\n"],"mappings":";;;;AAkDA,MAAM,gBAAgB,SAAmD;AACxE,KAAI,CAAC,KACJ,QAAO;AAER,KAAI,OAAO,SAAS,SACnB,QAAO,IAAI,KAAK,KAAK,CAAC,SAAS;AAEhC,QAAO,KAAK,SAAS;;AAItB,MAAM,UAAU,SAAiD;AAChE,KAAI,CAAC,KACJ,QAAO,OAAO,WAAW,8BAAc,IAAI,MAAM,mBAAG,IAAI,KAAK,EAAE;AAEhE,KAAI,OAAO,SAAS,SACnB,QAAO,IAAI,KAAK,KAAK;AAEtB,QAAO;;AAIR,MAAM,iBAAiB,SAAuB;AAI7C,QAAO,GAHM,KAAK,aAAa,CAGhB,GAFD,OAAO,KAAK,UAAU,GAAG,EAAE,CAAC,SAAS,GAAG,IAAI,CAElC,GADZ,OAAO,KAAK,SAAS,CAAC,CAAC,SAAS,GAAG,IAAI;;AAKpD,MAAM,iBAAiB,eAA6B;CACnD,MAAM,CAAC,MAAM,OAAO,OAAO,WAAW,MAAM,IAAI,CAAC,IAAI,OAAO;AAC5D,QAAO,IAAI,KAAK,QAAQ,IAAI,SAAS,KAAK,GAAG,OAAO,GAAG,GAAG,GAAG,GAAG,EAAE;;AAInE,MAAM,sCACL,SACkD;AAClD,KAAI,KAAK,UACR,QAAO;EAAE,UAAU,KAAK;EAAW,YAAY,WAAW;EAAS;AAEpE,KAAI,KAAK,UACR,QAAO;EAAE,UAAU,KAAK;EAAW,YAAY,WAAW;EAAI;AAE/D,KAAI,KAAK,OACR,QAAO;EAAE,UAAU,KAAK;EAAQ,YAAY,WAAW;EAAa;AAIrE,QAAO;EACN,UAAU,KAAK,MAAM;EACrB,YAAY,WAAW;EACvB;;AAGF,MAAMA,qBAAwC,OAAO,OAAO,EAAE,CAAC;AAI/D,MAAM,sBAAsB,UAA8C;CACzE,MAAMC,SAA6B,EAAE;CACrC,IAAIC,eAAsC;CAC1C,IAAIC,mBAAkC;CAEtC,MAAM,2BAA2B,aAAyB;EACzD,MAAM,gBAAgB,cAAc,SAAS;AAE7C,MAAI,qBAAqB,eAAe;AAEvC,OAAI,cAAc;AACjB,WAAO,KAAK,aAAa;AACzB,mBAAe;;AAIhB,UAAO,KAAK;IACX,MAAM;IACN,MAAM,cAAc,cAAc;IAClC,YAAY;IACZ,CAAC;AAEF,sBAAmB;;;AAIrB,MAAK,MAAM,QAAQ,OAAO;EACzB,MAAM,WAAW,OAAO,KAAK,UAAU;AAGvC,0BAAwB,SAAS;AAGjC,MAAI,KAAK,SAAS,SAAS;AAE1B,OAAI,cAAc;AACjB,WAAO,KAAK,aAAa;AACzB,mBAAe;;AAIhB,UAAO,KAAK;IACX,MAAM;IACN;IACA,WAAW;IACX,CAAC;AACF;;AAGD,MAAI,KAAK,SAAS,kBAAkB;AAEnC,OAAI,cAAc;AACjB,WAAO,KAAK,aAAa;AACzB,mBAAe;;AAIhB,UAAO,KAAK;IACX,MAAM;IACN;IACA,MAAM,KAAK,QAAQ;IACnB,WAAW;IACX,CAAC;AACF;;EAID,MAAM,EAAE,UAAU,eAAe,mCAAmC,KAAK;AAEzE,MAAI,gBAAgB,aAAa,aAAa,UAAU;AAEvD,gBAAa,MAAM,KAAK,KAAK;AAC7B,gBAAa,gBAAgB,KAAK,MAAM,aAAa;AACrD,gBAAa,kBAAkB;SACzB;AAEN,OAAI,aACH,QAAO,KAAK,aAAa;AAI1B,kBAAe;IACd,MAAM;IACN;IACA;IACA,OAAO,CAAC,KAAK;IACb,gBAAgB,KAAK,MAAM;IAC3B,eAAe,KAAK,MAAM;IAC1B,kBAAkB;IAClB,iBAAiB;IACjB;;;AAIH,KAAI,aACH,QAAO,KAAK,aAAa;AAG1B,QAAO;;AAKR,MAAM,gCACL,UACA,OACA,oBACA,uBACI;CACJ,MAAM,4BAAY,IAAI,KAA0B;CAChD,MAAM,qCAAqB,IAAI,KAAqB;CACpD,MAAM,iCAAiB,IAAI,KAAqB;AAGhD,MAAK,MAAM,QAAQ,MAClB,KAAI,KAAK,SAAS,aAAa,KAAK,GACnC,WAAU,IAAI,KAAK,oBAAI,IAAI,KAAK,CAAC;AAKnC,MAAK,MAAM,QAAQ,UAAU;EAC5B,MAAM,WAAW,aAAa,KAAK,WAAW;EAC9C,MAAM,WAAW,KAAK,UAAU,KAAK,aAAa,KAAK;AACvD,MAAI,CAAC,SACJ;EAGD,IAAIC,eAAoC;EACxC,IAAI,cAAc;AAGlB,OAAK,IAAI,QAAQ,GAAG,QAAQ,mBAAmB,QAAQ,SAAS;GAC/D,MAAM,OAAO,mBAAmB;AAChC,OAAI,CAAC,KACJ;AAMD,QAFC,mBAAmB,UAAU,aAAa,KAAK,UAAU,KAE1C,UAAU;AAEzB,QAAI,KAAK,IAAI;KACZ,MAAM,SAAS,UAAU,IAAI,KAAK,GAAG;AACrC,SAAI,OACH,QAAO,IAAI,SAAS;;AAGtB,mBAAe;SAGf;;AAKF,MAAI,cAAc,GACjB,oBAAmB,IAAI,UAAU,aAAa,GAAG;AAIlD,iBAAe,IAAI,UAAU,YAAY;;AAG1C,QAAO;EAAE;EAAW;EAAoB;EAAgB;;;;;;;;AASzD,MAAa,sBAAsB,EAClC,OACA,WAAW,EAAE,EACb,sBACgC;AAChC,QAAO,cAAc;EACpB,MAAM,eAAe,mBAAmB,MAAM;EAG9C,MAAM,eAAe,MAAM,QAAQ,SAAS,KAAK,SAAS,UAAU;EACpE,IAAI,qBAAqB;EACzB,IAAI,qBAAqB,aAAa,KAAK,SAC1C,aAAa,KAAK,UAAU,CAC5B;EAGD,IAAI,WAAW;AACf,OAAK,IAAI,QAAQ,GAAG,QAAQ,mBAAmB,QAAQ,SAAS;GAC/D,MAAM,cAAc,mBAAmB;GACvC,MAAM,eAAe,mBAAmB,QAAQ;AAChD,OACC,gBAAgB,UAChB,iBAAiB,UACjB,cAAc,cACb;AACD,eAAW;AACX;;;AAIF,MAAI,CAAC,UAAU;GACd,MAAM,iBAAiB,aAAa,KAAK,MAAM,WAAW;IACzD;IACA,MAAM,mBAAmB,UAAU;IACnC,EAAE;AAEH,kBAAe,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK;AAE9C,wBAAqB,eAAe,KAAK,UAAU,MAAM,KAAK;AAC9D,wBAAqB,eAAe,KAAK,UAAU,MAAM,KAAK;;EAK/D,MAAM,kCAAkB,IAAI,KAAqB;AACjD,OAAK,IAAI,IAAI,GAAG,IAAI,mBAAmB,QAAQ,KAAK;GACnD,MAAM,OAAO,mBAAmB;AAChC,OAAI,MAAM,GACT,iBAAgB,IAAI,KAAK,IAAI,EAAE;;EAKjC,MAAM,EAAE,WAAW,oBAAoB,mBACtC,6BACC,UACA,OACA,oBACA,mBACA;EAGF,MAAM,mCAAmB,IAAI,KAAgC;AAE7D,SAAO;GACN,OAAO;GACP;GACA;GACA;GAEA,wBAAwB,cAA+B;AACtD,QAAI,CAAC,gBACJ,QAAO;IAER,MAAM,SAAS,UAAU,IAAI,UAAU;AACvC,WAAO,SAAS,OAAO,IAAI,gBAAgB,GAAG;;GAG/C,mBAAmB,cAAyC;AAC3D,QAAI,iBAAiB,IAAI,UAAU,CAClC,QAAO,iBAAiB,IAAI,UAAU,IAAI;IAG3C,MAAM,SAAS,UAAU,IAAI,UAAU;AACvC,QAAI,CAAC,UAAU,OAAO,SAAS,GAAG;AACjC,sBAAiB,IAAI,WAAW,mBAAmB;AACnD,YAAO;;IAGR,MAAM,SAAS,OAAO,OAAO,MAAM,KAAK,OAAO,CAAC;AAChD,qBAAiB,IAAI,WAAW,OAAO;AACvC,WAAO;;GAGR,uBAAuB,WACtB,mBAAmB,IAAI,OAAO;GAE/B,oBAAoB,WAAmB,WACtC,mBAAmB,IAAI,OAAO,KAAK;GAEpC,iBAAiB,WAChB,eAAe,IAAI,OAAO,IAAI;GAE/B,iBAAiB,WAAmB,WAA4B;IAC/D,MAAM,WAAW,mBAAmB,IAAI,OAAO;AAC/C,QAAI,CAAC,SACJ,QAAO;IAIR,MAAM,eAAe,gBAAgB,IAAI,UAAU;IACnD,MAAM,gBAAgB,gBAAgB,IAAI,SAAS;AAEnD,QAAI,iBAAiB,UAAa,kBAAkB,OACnD,QAAO;AAGR,WAAO,eAAe;;GAEvB;IACC;EAAC;EAAO;EAAU;EAAgB,CAAC"}
|
|
@@ -48,7 +48,7 @@ import { ConversationTimelineType, TimelineItemVisibility } from "@cossistant/ty
|
|
|
48
48
|
*/
|
|
49
49
|
function useConversationPage(options) {
|
|
50
50
|
const { conversationId: initialConversationId, initialMessage, onConversationIdChange, items: passedItems = [], autoSeenEnabled = true } = options;
|
|
51
|
-
const { client, visitor } = useSupport();
|
|
51
|
+
const { client, visitor, availableAIAgents } = useSupport();
|
|
52
52
|
const websocket = useWebSocketSafe();
|
|
53
53
|
const identificationState = useIdentificationState();
|
|
54
54
|
const trimmedInitialMessage = initialMessage?.trim() ?? "";
|
|
@@ -73,6 +73,7 @@ function useConversationPage(options) {
|
|
|
73
73
|
]);
|
|
74
74
|
const shouldShowIdentificationTool = useMemo(() => {
|
|
75
75
|
if (lifecycle.isPending) return false;
|
|
76
|
+
if (availableAIAgents.length > 0) return false;
|
|
76
77
|
if (identificationState?.isIdentifying) return false;
|
|
77
78
|
if (visitor?.contact) return false;
|
|
78
79
|
return !baseItems.some((item) => item.type === ConversationTimelineType.IDENTIFICATION);
|
|
@@ -80,7 +81,8 @@ function useConversationPage(options) {
|
|
|
80
81
|
baseItems,
|
|
81
82
|
lifecycle.isPending,
|
|
82
83
|
visitor?.contact,
|
|
83
|
-
identificationState?.isIdentifying
|
|
84
|
+
identificationState?.isIdentifying,
|
|
85
|
+
availableAIAgents.length
|
|
84
86
|
]);
|
|
85
87
|
const displayItems = useMemo(() => {
|
|
86
88
|
if (!shouldShowIdentificationTool) return baseItems;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-page.js","names":["identificationItem: TimelineItem"],"sources":["../../src/hooks/use-conversation-page.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport 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 { useIdentificationState } from \"../support/context/identification\";\nimport { useWebSocketSafe } from \"../support/context/websocket\";\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\tisUploading: 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\tconst websocket = useWebSocketSafe();\n\tconst identificationState = useIdentificationState();\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\t// Don't show identification form while identification is in progress\n\t\t// This prevents the form from flashing when an authenticated user opens the widget\n\t\tif (identificationState?.isIdentifying) {\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}, [\n\t\tbaseItems,\n\t\tlifecycle.isPending,\n\t\tvisitor?.contact,\n\t\tidentificationState?.isIdentifying,\n\t]);\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: typeof window !== \"undefined\" ? 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: client ?? undefined,\n\t\tconversationId: lifecycle.realConversationId,\n\t\tdefaultTimelineItems: effectiveDefaultTimelineItems,\n\t\tvisitorId: visitor?.id,\n\t\tonConversationInitiated: (newConversationId) => {\n\t\t\t// Immediately switch to new conversation ID for optimistic updates\n\t\t\t// This happens BEFORE the API call, so the UI starts reading from\n\t\t\t// the correct store key right away\n\t\t\tif (lifecycle.isPending) {\n\t\t\t\tlifecycle.setConversationId(newConversationId);\n\t\t\t}\n\t\t},\n\t\tonMessageSent: (newConversationId) => {\n\t\t\t// Also handle this for completeness (API call completed)\n\t\t\tif (lifecycle.isPending) {\n\t\t\t\tlifecycle.setConversationId(newConversationId);\n\t\t\t}\n\t\t},\n\t\t// Pass WebSocket connection for real-time typing events\n\t\trealtimeSend: websocket?.send ?? null,\n\t\tisRealtimeConnected: websocket?.isConnected ?? false,\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\tisUploading: composer.isUploading,\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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"}
|
|
1
|
+
{"version":3,"file":"use-conversation-page.js","names":["identificationItem: TimelineItem"],"sources":["../../src/hooks/use-conversation-page.ts"],"sourcesContent":["import type { CossistantClient } from \"@cossistant/core\";\nimport 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 { useIdentificationState } from \"../support/context/identification\";\nimport { useWebSocketSafe } from \"../support/context/websocket\";\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\tisUploading: 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, availableAIAgents } = useSupport();\n\tconst websocket = useWebSocketSafe();\n\tconst identificationState = useIdentificationState();\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\t// Hide identification form when an AI agent is available\n\t\tif (availableAIAgents.length > 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Don't show identification form while identification is in progress\n\t\t// This prevents the form from flashing when an authenticated user opens the widget\n\t\tif (identificationState?.isIdentifying) {\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}, [\n\t\tbaseItems,\n\t\tlifecycle.isPending,\n\t\tvisitor?.contact,\n\t\tidentificationState?.isIdentifying,\n\t\tavailableAIAgents.length,\n\t]);\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: typeof window !== \"undefined\" ? 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: client ?? undefined,\n\t\tconversationId: lifecycle.realConversationId,\n\t\tdefaultTimelineItems: effectiveDefaultTimelineItems,\n\t\tvisitorId: visitor?.id,\n\t\tonConversationInitiated: (newConversationId) => {\n\t\t\t// Immediately switch to new conversation ID for optimistic updates\n\t\t\t// This happens BEFORE the API call, so the UI starts reading from\n\t\t\t// the correct store key right away\n\t\t\tif (lifecycle.isPending) {\n\t\t\t\tlifecycle.setConversationId(newConversationId);\n\t\t\t}\n\t\t},\n\t\tonMessageSent: (newConversationId) => {\n\t\t\t// Also handle this for completeness (API call completed)\n\t\t\tif (lifecycle.isPending) {\n\t\t\t\tlifecycle.setConversationId(newConversationId);\n\t\t\t}\n\t\t},\n\t\t// Pass WebSocket connection for real-time typing events\n\t\trealtimeSend: websocket?.send ?? null,\n\t\tisRealtimeConnected: websocket?.isConnected ?? false,\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\tisUploading: composer.isUploading,\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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-preview.d.ts","names":[],"sources":["../../src/hooks/use-conversation-preview.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"use-conversation-preview.d.ts","names":[],"sources":["../../src/hooks/use-conversation-preview.ts"],"sourcesContent":[],"mappings":";;;;;;KAeY,8BAAA;;EAAA,IAAA,EAAA,MAAA;EAQA,aAAA,EAAA,OAAA;EAQA,UAAA,CAAA,EAAA,MAAA;EAEA,WAAA,CAAA,EAAA,MAAA,GAAA,IAAA;AAOZ,CAAA;AAsBY,KAvCA,gCAAA,GAuC4B;EACzB,IAAA,EAAA,MAAA;EAED,KAAA,EAAA,MAAA,GAAA,IAAA;EACE,IAAA,EAAA,OAAA,GAAA,IAAA,GAAA,UAAA;EACP;EACoB,UAAA,CAAA,EAAA,MAAA,GAAA,IAAA;CAAlB;AAAU,KArCT,oCAAA,GAAuC,wBAqC9B;AA0BL,KA7DJ,8BAAA,GA8DF;gBA7DK;sBACM;;;;KAKT,6BAAA;gBACG;;;;;;;;;;yBAUS;;;;;;;;;;KAWZ,4BAAA;gBACG;;eAED;iBACE;UACP;YACE,kBAAkB;;;;;;iBA0Bb,sBAAA,UACN,gCACP"}
|
|
@@ -5,6 +5,7 @@ import { useConversationTyping } from "./use-conversation-typing.js";
|
|
|
5
5
|
import { formatTimeAgo } from "../support/utils/time.js";
|
|
6
6
|
import { useSupport } from "../provider.js";
|
|
7
7
|
import { useMemo } from "react";
|
|
8
|
+
import { formatMessagePreview } from "@cossistant/tiny-markdown/utils";
|
|
8
9
|
|
|
9
10
|
//#region src/hooks/use-conversation-preview.ts
|
|
10
11
|
function resolveLastTimelineMessage(items, fallback) {
|
|
@@ -55,7 +56,7 @@ function useConversationPreview(options) {
|
|
|
55
56
|
} else senderName = text("common.fallbacks.aiAssistant");
|
|
56
57
|
} else senderName = text("common.fallbacks.supportTeam");
|
|
57
58
|
return {
|
|
58
|
-
content: lastTimelineMessage.text || "",
|
|
59
|
+
content: formatMessagePreview(lastTimelineMessage.text || ""),
|
|
59
60
|
time: formatTimeAgo(lastTimelineMessage.createdAt),
|
|
60
61
|
isFromVisitor,
|
|
61
62
|
senderName,
|
|
@@ -1 +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\t/** Last seen timestamp for human agents, used for online status indicator */\n\tlastSeenAt?: string | null;\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 * Disabled by default to reduce API calls - conversation.lastTimelineItem\n\t * is typically sufficient for previews.\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 = false,\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\tlastSeenAt: human.lastSeenAt ?? 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\tlastSeenAt: 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\tlastSeenAt: fallbackHuman.lastSeenAt ?? 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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"}
|
|
1
|
+
{"version":3,"file":"use-conversation-preview.js","names":["senderImage: string | null","typingState: ConversationPreviewTypingState"],"sources":["../../src/hooks/use-conversation-preview.ts"],"sourcesContent":["import { formatMessagePreview } from \"@cossistant/tiny-markdown/utils\";\nimport 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\t/** Last seen timestamp for human agents, used for online status indicator */\n\tlastSeenAt?: string | null;\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 * Disabled by default to reduce API calls - conversation.lastTimelineItem\n\t * is typically sufficient for previews.\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 = false,\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: formatMessagePreview(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\tlastSeenAt: human.lastSeenAt ?? 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\tlastSeenAt: 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\tlastSeenAt: fallbackHuman.lastSeenAt ?? 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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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cossistant/react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.31",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Cossistant team",
|
|
7
7
|
"description": "Headless React SDK for building AI-powered support/chat widgets. Hooks + primitives, WS-driven, TypeScript-first. Next.js-ready, Tailwind optional.",
|
|
@@ -88,9 +88,10 @@
|
|
|
88
88
|
"*.css"
|
|
89
89
|
],
|
|
90
90
|
"dependencies": {
|
|
91
|
-
"@cossistant/core": "0.0.
|
|
92
|
-
"@cossistant/
|
|
93
|
-
"
|
|
91
|
+
"@cossistant/core": "0.0.31",
|
|
92
|
+
"@cossistant/tiny-markdown": "0.0.1",
|
|
93
|
+
"@cossistant/types": "0.0.31",
|
|
94
|
+
"facehash": "0.0.6",
|
|
94
95
|
"@floating-ui/react": "^0.27.16",
|
|
95
96
|
"class-variance-authority": "^0.7.1",
|
|
96
97
|
"clsx": "^2.1.1",
|
|
@@ -26,15 +26,15 @@ declare const createConversationResponseSchema: ZodObject<{
|
|
|
26
26
|
type: ZodLiteral<"text">;
|
|
27
27
|
text: ZodString;
|
|
28
28
|
state: ZodOptional<ZodEnum<{
|
|
29
|
-
done: "done";
|
|
30
29
|
streaming: "streaming";
|
|
30
|
+
done: "done";
|
|
31
31
|
}>>;
|
|
32
32
|
}, $strip>, ZodObject<{
|
|
33
33
|
type: ZodLiteral<"reasoning">;
|
|
34
34
|
text: ZodString;
|
|
35
35
|
state: ZodOptional<ZodEnum<{
|
|
36
|
-
done: "done";
|
|
37
36
|
streaming: "streaming";
|
|
37
|
+
done: "done";
|
|
38
38
|
}>>;
|
|
39
39
|
providerMetadata: ZodOptional<ZodObject<{
|
|
40
40
|
cossistant: ZodOptional<ZodObject<{
|
|
@@ -54,8 +54,8 @@ declare const createConversationResponseSchema: ZodObject<{
|
|
|
54
54
|
output: ZodOptional<ZodUnknown>;
|
|
55
55
|
state: ZodEnum<{
|
|
56
56
|
error: "error";
|
|
57
|
-
result: "result";
|
|
58
57
|
partial: "partial";
|
|
58
|
+
result: "result";
|
|
59
59
|
}>;
|
|
60
60
|
errorText: ZodOptional<ZodString>;
|
|
61
61
|
providerMetadata: ZodOptional<ZodObject<{
|
|
@@ -160,10 +160,12 @@ declare const createConversationResponseSchema: ZodObject<{
|
|
|
160
160
|
visitorId: ZodString;
|
|
161
161
|
websiteId: ZodString;
|
|
162
162
|
status: ZodDefault<ZodEnum<{
|
|
163
|
-
open: "open";
|
|
164
163
|
resolved: "resolved";
|
|
164
|
+
open: "open";
|
|
165
165
|
spam: "spam";
|
|
166
166
|
}>>;
|
|
167
|
+
visitorRating: ZodOptional<ZodNullable<ZodNumber>>;
|
|
168
|
+
visitorRatingAt: ZodOptional<ZodNullable<ZodString>>;
|
|
167
169
|
deletedAt: ZodDefault<ZodNullable<ZodString>>;
|
|
168
170
|
visitorLastSeenAt: ZodOptional<ZodNullable<ZodString>>;
|
|
169
171
|
lastTimelineItem: ZodOptional<ZodObject<{
|
|
@@ -185,15 +187,15 @@ declare const createConversationResponseSchema: ZodObject<{
|
|
|
185
187
|
type: ZodLiteral<"text">;
|
|
186
188
|
text: ZodString;
|
|
187
189
|
state: ZodOptional<ZodEnum<{
|
|
188
|
-
done: "done";
|
|
189
190
|
streaming: "streaming";
|
|
191
|
+
done: "done";
|
|
190
192
|
}>>;
|
|
191
193
|
}, $strip>, ZodObject<{
|
|
192
194
|
type: ZodLiteral<"reasoning">;
|
|
193
195
|
text: ZodString;
|
|
194
196
|
state: ZodOptional<ZodEnum<{
|
|
195
|
-
done: "done";
|
|
196
197
|
streaming: "streaming";
|
|
198
|
+
done: "done";
|
|
197
199
|
}>>;
|
|
198
200
|
providerMetadata: ZodOptional<ZodObject<{
|
|
199
201
|
cossistant: ZodOptional<ZodObject<{
|
|
@@ -213,8 +215,8 @@ declare const createConversationResponseSchema: ZodObject<{
|
|
|
213
215
|
output: ZodOptional<ZodUnknown>;
|
|
214
216
|
state: ZodEnum<{
|
|
215
217
|
error: "error";
|
|
216
|
-
result: "result";
|
|
217
218
|
partial: "partial";
|
|
219
|
+
result: "result";
|
|
218
220
|
}>;
|
|
219
221
|
errorText: ZodOptional<ZodString>;
|
|
220
222
|
providerMetadata: ZodOptional<ZodObject<{
|
|
@@ -341,10 +343,12 @@ declare const listConversationsResponseSchema: ZodObject<{
|
|
|
341
343
|
visitorId: ZodString;
|
|
342
344
|
websiteId: ZodString;
|
|
343
345
|
status: ZodDefault<ZodEnum<{
|
|
344
|
-
open: "open";
|
|
345
346
|
resolved: "resolved";
|
|
347
|
+
open: "open";
|
|
346
348
|
spam: "spam";
|
|
347
349
|
}>>;
|
|
350
|
+
visitorRating: ZodOptional<ZodNullable<ZodNumber>>;
|
|
351
|
+
visitorRatingAt: ZodOptional<ZodNullable<ZodString>>;
|
|
348
352
|
deletedAt: ZodDefault<ZodNullable<ZodString>>;
|
|
349
353
|
visitorLastSeenAt: ZodOptional<ZodNullable<ZodString>>;
|
|
350
354
|
lastTimelineItem: ZodOptional<ZodObject<{
|
|
@@ -366,15 +370,15 @@ declare const listConversationsResponseSchema: ZodObject<{
|
|
|
366
370
|
type: ZodLiteral<"text">;
|
|
367
371
|
text: ZodString;
|
|
368
372
|
state: ZodOptional<ZodEnum<{
|
|
369
|
-
done: "done";
|
|
370
373
|
streaming: "streaming";
|
|
374
|
+
done: "done";
|
|
371
375
|
}>>;
|
|
372
376
|
}, $strip>, ZodObject<{
|
|
373
377
|
type: ZodLiteral<"reasoning">;
|
|
374
378
|
text: ZodString;
|
|
375
379
|
state: ZodOptional<ZodEnum<{
|
|
376
|
-
done: "done";
|
|
377
380
|
streaming: "streaming";
|
|
381
|
+
done: "done";
|
|
378
382
|
}>>;
|
|
379
383
|
providerMetadata: ZodOptional<ZodObject<{
|
|
380
384
|
cossistant: ZodOptional<ZodObject<{
|
|
@@ -394,8 +398,8 @@ declare const listConversationsResponseSchema: ZodObject<{
|
|
|
394
398
|
output: ZodOptional<ZodUnknown>;
|
|
395
399
|
state: ZodEnum<{
|
|
396
400
|
error: "error";
|
|
397
|
-
result: "result";
|
|
398
401
|
partial: "partial";
|
|
402
|
+
result: "result";
|
|
399
403
|
}>;
|
|
400
404
|
errorText: ZodOptional<ZodString>;
|
|
401
405
|
providerMetadata: ZodOptional<ZodObject<{
|
|
@@ -515,10 +519,12 @@ declare const getConversationResponseSchema: ZodObject<{
|
|
|
515
519
|
visitorId: ZodString;
|
|
516
520
|
websiteId: ZodString;
|
|
517
521
|
status: ZodDefault<ZodEnum<{
|
|
518
|
-
open: "open";
|
|
519
522
|
resolved: "resolved";
|
|
523
|
+
open: "open";
|
|
520
524
|
spam: "spam";
|
|
521
525
|
}>>;
|
|
526
|
+
visitorRating: ZodOptional<ZodNullable<ZodNumber>>;
|
|
527
|
+
visitorRatingAt: ZodOptional<ZodNullable<ZodString>>;
|
|
522
528
|
deletedAt: ZodDefault<ZodNullable<ZodString>>;
|
|
523
529
|
visitorLastSeenAt: ZodOptional<ZodNullable<ZodString>>;
|
|
524
530
|
lastTimelineItem: ZodOptional<ZodObject<{
|
|
@@ -540,15 +546,15 @@ declare const getConversationResponseSchema: ZodObject<{
|
|
|
540
546
|
type: ZodLiteral<"text">;
|
|
541
547
|
text: ZodString;
|
|
542
548
|
state: ZodOptional<ZodEnum<{
|
|
543
|
-
done: "done";
|
|
544
549
|
streaming: "streaming";
|
|
550
|
+
done: "done";
|
|
545
551
|
}>>;
|
|
546
552
|
}, $strip>, ZodObject<{
|
|
547
553
|
type: ZodLiteral<"reasoning">;
|
|
548
554
|
text: ZodString;
|
|
549
555
|
state: ZodOptional<ZodEnum<{
|
|
550
|
-
done: "done";
|
|
551
556
|
streaming: "streaming";
|
|
557
|
+
done: "done";
|
|
552
558
|
}>>;
|
|
553
559
|
providerMetadata: ZodOptional<ZodObject<{
|
|
554
560
|
cossistant: ZodOptional<ZodObject<{
|
|
@@ -568,8 +574,8 @@ declare const getConversationResponseSchema: ZodObject<{
|
|
|
568
574
|
output: ZodOptional<ZodUnknown>;
|
|
569
575
|
state: ZodEnum<{
|
|
570
576
|
error: "error";
|
|
571
|
-
result: "result";
|
|
572
577
|
partial: "partial";
|
|
578
|
+
result: "result";
|
|
573
579
|
}>;
|
|
574
580
|
errorText: ZodOptional<ZodString>;
|
|
575
581
|
providerMetadata: ZodOptional<ZodObject<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation.d.ts","names":[],"sources":["../../../../../../types/src/api/conversation.ts"],"sourcesContent":[],"mappings":";;;;;;;;cA8Ba,kCAAgC
|
|
1
|
+
{"version":3,"file":"conversation.d.ts","names":[],"sources":["../../../../../../types/src/api/conversation.ts"],"sourcesContent":[],"mappings":";;;;;;;;cA8Ba,kCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAAA,CAAA,CAAA,CAAA;YAAA,eAAA,aAAA,UAAA,CAAA;YASjC,WAAA,aACJ,UAAA,CAAA;UAGK,CAAA,QAAA,CA2BV,CAAA;;;;;;;;;;;;;;QA3BwC,gBAAA,aAAA,UAAA,CAAA;UAAA,UAAA,aAAA,UAAA,CAAA;YA6B/B,UAAwB,aAC5B,QAAA,CAAA;cAGK,MAAA,EAAA,QAaV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAlDS,8BAAA,GAAiC,cACrC;cAGK,gCAA8B;;;;;;;;;;;;;;;;;KA6B/B,wBAAA,GAA2B,cAC/B;cAGK,iCAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAA,IAAA,YAAA,CAAA,YAAA,CAAA;QAAA,QAAA,WAAA;QAehC,GAAA,WAAyB;QAIxB,KAAA,aAQV,UAAA,CAAA;;;YARsC,UAAA,aAAA,QAAA,CAAA;cAAA,MAAA,EAAA,QAAA;cAU7B,OAAsB,EAAA,SAC1B;YAGK,CAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAlBD,yBAAA,GAA4B,cAChC;cAGK,8BAA4B;;;KAU7B,sBAAA,GAAyB,cAC7B;cAGK,+BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAA,QAAA,WAAA;QAAA,SAAA,WAAA;QAQ9B,KAAA,WAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAAvB,uBAAA,GAA0B,cAC9B"}
|
|
@@ -26,15 +26,15 @@ declare const timelineItemPartsSchema: ZodArray<ZodUnion<readonly [ZodObject<{
|
|
|
26
26
|
type: ZodLiteral<"text">;
|
|
27
27
|
text: ZodString;
|
|
28
28
|
state: ZodOptional<ZodEnum<{
|
|
29
|
-
done: "done";
|
|
30
29
|
streaming: "streaming";
|
|
30
|
+
done: "done";
|
|
31
31
|
}>>;
|
|
32
32
|
}, $strip>, ZodObject<{
|
|
33
33
|
type: ZodLiteral<"reasoning">;
|
|
34
34
|
text: ZodString;
|
|
35
35
|
state: ZodOptional<ZodEnum<{
|
|
36
|
-
done: "done";
|
|
37
36
|
streaming: "streaming";
|
|
37
|
+
done: "done";
|
|
38
38
|
}>>;
|
|
39
39
|
providerMetadata: ZodOptional<ZodObject<{
|
|
40
40
|
cossistant: ZodOptional<ZodObject<{
|
|
@@ -54,8 +54,8 @@ declare const timelineItemPartsSchema: ZodArray<ZodUnion<readonly [ZodObject<{
|
|
|
54
54
|
output: ZodOptional<ZodUnknown>;
|
|
55
55
|
state: ZodEnum<{
|
|
56
56
|
error: "error";
|
|
57
|
-
result: "result";
|
|
58
57
|
partial: "partial";
|
|
58
|
+
result: "result";
|
|
59
59
|
}>;
|
|
60
60
|
errorText: ZodOptional<ZodString>;
|
|
61
61
|
providerMetadata: ZodOptional<ZodObject<{
|
|
@@ -165,15 +165,15 @@ declare const timelineItemSchema: ZodObject<{
|
|
|
165
165
|
type: ZodLiteral<"text">;
|
|
166
166
|
text: ZodString;
|
|
167
167
|
state: ZodOptional<ZodEnum<{
|
|
168
|
-
done: "done";
|
|
169
168
|
streaming: "streaming";
|
|
169
|
+
done: "done";
|
|
170
170
|
}>>;
|
|
171
171
|
}, $strip>, ZodObject<{
|
|
172
172
|
type: ZodLiteral<"reasoning">;
|
|
173
173
|
text: ZodString;
|
|
174
174
|
state: ZodOptional<ZodEnum<{
|
|
175
|
-
done: "done";
|
|
176
175
|
streaming: "streaming";
|
|
176
|
+
done: "done";
|
|
177
177
|
}>>;
|
|
178
178
|
providerMetadata: ZodOptional<ZodObject<{
|
|
179
179
|
cossistant: ZodOptional<ZodObject<{
|
|
@@ -193,8 +193,8 @@ declare const timelineItemSchema: ZodObject<{
|
|
|
193
193
|
output: ZodOptional<ZodUnknown>;
|
|
194
194
|
state: ZodEnum<{
|
|
195
195
|
error: "error";
|
|
196
|
-
result: "result";
|
|
197
196
|
partial: "partial";
|
|
197
|
+
result: "result";
|
|
198
198
|
}>;
|
|
199
199
|
errorText: ZodOptional<ZodString>;
|
|
200
200
|
providerMetadata: ZodOptional<ZodObject<{
|
|
@@ -325,15 +325,15 @@ declare const getConversationTimelineItemsResponseSchema: ZodObject<{
|
|
|
325
325
|
type: ZodLiteral<"text">;
|
|
326
326
|
text: ZodString;
|
|
327
327
|
state: ZodOptional<ZodEnum<{
|
|
328
|
-
done: "done";
|
|
329
328
|
streaming: "streaming";
|
|
329
|
+
done: "done";
|
|
330
330
|
}>>;
|
|
331
331
|
}, $strip>, ZodObject<{
|
|
332
332
|
type: ZodLiteral<"reasoning">;
|
|
333
333
|
text: ZodString;
|
|
334
334
|
state: ZodOptional<ZodEnum<{
|
|
335
|
-
done: "done";
|
|
336
335
|
streaming: "streaming";
|
|
336
|
+
done: "done";
|
|
337
337
|
}>>;
|
|
338
338
|
providerMetadata: ZodOptional<ZodObject<{
|
|
339
339
|
cossistant: ZodOptional<ZodObject<{
|
|
@@ -353,8 +353,8 @@ declare const getConversationTimelineItemsResponseSchema: ZodObject<{
|
|
|
353
353
|
output: ZodOptional<ZodUnknown>;
|
|
354
354
|
state: ZodEnum<{
|
|
355
355
|
error: "error";
|
|
356
|
-
result: "result";
|
|
357
356
|
partial: "partial";
|
|
357
|
+
result: "result";
|
|
358
358
|
}>;
|
|
359
359
|
errorText: ZodOptional<ZodString>;
|
|
360
360
|
providerMetadata: ZodOptional<ZodObject<{
|