@cossistant/react 0.0.20 → 0.0.22
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/conversation.d.ts +28 -0
- package/conversation.d.ts.map +1 -1
- package/hooks/index.d.ts +4 -1
- package/hooks/index.js +4 -1
- package/hooks/private/use-grouped-messages.d.ts.map +1 -1
- package/hooks/private/use-grouped-messages.js +3 -17
- package/hooks/private/use-grouped-messages.js.map +1 -1
- package/hooks/use-conversation-auto-seen.d.ts +1 -1
- package/hooks/use-conversation-auto-seen.js +30 -46
- package/hooks/use-conversation-auto-seen.js.map +1 -1
- package/hooks/use-conversation-seen.d.ts.map +1 -1
- package/hooks/use-conversation-seen.js +7 -3
- package/hooks/use-conversation-seen.js.map +1 -1
- package/hooks/use-new-message-sound.d.ts +23 -0
- package/hooks/use-new-message-sound.d.ts.map +1 -0
- package/hooks/use-new-message-sound.js +34 -0
- package/hooks/use-new-message-sound.js.map +1 -0
- package/hooks/use-sound-effect.d.ts +30 -0
- package/hooks/use-sound-effect.d.ts.map +1 -0
- package/hooks/use-sound-effect.js +104 -0
- package/hooks/use-sound-effect.js.map +1 -0
- package/hooks/use-typing-sound.d.ts +18 -0
- package/hooks/use-typing-sound.d.ts.map +1 -0
- package/hooks/use-typing-sound.js +38 -0
- package/hooks/use-typing-sound.js.map +1 -0
- package/index.d.ts +5 -2
- package/index.js +8 -6
- package/package.json +3 -3
- package/primitives/bubble.js +1 -1
- package/primitives/index.d.ts +3 -5
- package/primitives/index.js +3 -9
- package/primitives/index.parts.d.ts +2 -4
- package/primitives/index.parts.js +2 -4
- package/primitives/router.d.ts +19 -20
- package/primitives/router.d.ts.map +1 -1
- package/primitives/router.js +17 -11
- package/primitives/router.js.map +1 -1
- package/realtime/index.js +1 -1
- package/realtime/provider.js +1 -1
- package/realtime-events.d.ts +14 -0
- package/realtime-events.d.ts.map +1 -1
- package/schemas3.d.ts +7 -0
- package/schemas3.d.ts.map +1 -1
- package/support/components/bubble.d.ts.map +1 -1
- package/support/components/bubble.js +27 -4
- package/support/components/bubble.js.map +1 -1
- package/support/components/button.d.ts +1 -1
- package/support/components/conversation-event.js +1 -1
- package/support/components/conversation-event.js.map +1 -1
- package/support/components/conversation-timeline.d.ts.map +1 -1
- package/support/components/conversation-timeline.js +5 -0
- package/support/components/conversation-timeline.js.map +1 -1
- package/support/components/support-content.d.ts +2 -0
- package/support/components/support-content.d.ts.map +1 -1
- package/support/components/support-content.js +5 -2
- package/support/components/support-content.js.map +1 -1
- package/support/components/timeline-message-group.js +2 -2
- package/support/components/timeline-message-group.js.map +1 -1
- package/support/components/timeline-message-item.js +2 -2
- package/support/components/timeline-message-item.js.map +1 -1
- package/support/components/typing-indicator.d.ts.map +1 -1
- package/support/index.d.ts +12 -7
- package/support/index.d.ts.map +1 -1
- package/support/index.js +28 -29
- package/support/index.js.map +1 -1
- package/support/pages/conversation.d.ts.map +1 -1
- package/support/pages/conversation.js +19 -1
- package/support/pages/conversation.js.map +1 -1
- package/support/router.d.ts +19 -9
- package/support/router.d.ts.map +1 -1
- package/support/router.js +31 -30
- package/support/router.js.map +1 -1
- package/timeline-item.d.ts +14 -0
- package/timeline-item.d.ts.map +1 -1
- package/utils/use-render-element.d.ts.map +1 -1
- package/primitives/page-registry.d.ts +0 -30
- package/primitives/page-registry.d.ts.map +0 -1
- package/primitives/page-registry.js +0 -45
- package/primitives/page-registry.js.map +0 -1
- package/primitives/page.d.ts +0 -21
- package/primitives/page.d.ts.map +0 -1
- package/primitives/page.js +0 -18
- package/primitives/page.js.map +0 -1
|
@@ -5,13 +5,28 @@ import { cn } from "../utils/index.js";
|
|
|
5
5
|
import { BouncingDots } from "./typing-indicator.js";
|
|
6
6
|
import { SupportBubble } from "../../primitives/bubble.js";
|
|
7
7
|
import icons_default from "./icons.js";
|
|
8
|
+
import { useNewMessageSound } from "../../hooks/use-new-message-sound.js";
|
|
9
|
+
import { useTypingSound } from "../../hooks/use-typing-sound.js";
|
|
10
|
+
import { useEffect, useRef } from "react";
|
|
8
11
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
12
|
import { AnimatePresence, motion } from "motion/react";
|
|
10
13
|
|
|
11
14
|
//#region src/support/components/bubble.tsx
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
const BubbleContent = ({ isOpen, unreadCount, isTyping }) => {
|
|
16
|
+
const playNewMessageSound = useNewMessageSound({
|
|
17
|
+
volume: .7,
|
|
18
|
+
playbackRate: 1
|
|
19
|
+
});
|
|
20
|
+
const previousUnreadCountRef = useRef(0);
|
|
21
|
+
useTypingSound(!isOpen && isTyping, {
|
|
22
|
+
volume: 1,
|
|
23
|
+
playbackRate: 1.3
|
|
24
|
+
});
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (unreadCount > previousUnreadCountRef.current) playNewMessageSound();
|
|
27
|
+
previousUnreadCountRef.current = unreadCount;
|
|
28
|
+
}, [unreadCount, playNewMessageSound]);
|
|
29
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AnimatePresence, {
|
|
15
30
|
mode: "wait",
|
|
16
31
|
children: isOpen ? /* @__PURE__ */ jsx(motion.div, {
|
|
17
32
|
animate: {
|
|
@@ -110,7 +125,15 @@ const Bubble = ({ className }) => /* @__PURE__ */ jsx(SupportBubble, {
|
|
|
110
125
|
scale: 0,
|
|
111
126
|
opacity: 0
|
|
112
127
|
}
|
|
113
|
-
})] })
|
|
128
|
+
})] });
|
|
129
|
+
};
|
|
130
|
+
const Bubble = ({ className }) => /* @__PURE__ */ jsx(SupportBubble, {
|
|
131
|
+
className: cn("relative flex size-12 cursor-pointer items-center justify-center rounded-full bg-co-primary text-co-primary-foreground transition-colors hover:bg-co-primary/90 data-[open=true]:bg-co-primary/90", className),
|
|
132
|
+
children: ({ isOpen, unreadCount, isTyping }) => /* @__PURE__ */ jsx(BubbleContent, {
|
|
133
|
+
isOpen,
|
|
134
|
+
isTyping,
|
|
135
|
+
unreadCount
|
|
136
|
+
})
|
|
114
137
|
});
|
|
115
138
|
|
|
116
139
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bubble.js","names":["Bubble: React.FC<BubbleProps>","Primitive.Bubble"
|
|
1
|
+
{"version":3,"file":"bubble.js","names":["BubbleContent: React.FC<BubbleContentProps>","Icon","Bubble: React.FC<BubbleProps>","Primitive.Bubble"],"sources":["../../../src/support/components/bubble.tsx"],"sourcesContent":["\"use client\";\n\nimport { AnimatePresence, motion } from \"motion/react\";\nimport type React from \"react\";\nimport { useEffect, useRef } from \"react\";\nimport { useNewMessageSound } from \"../../hooks/use-new-message-sound\";\nimport { useTypingSound } from \"../../hooks/use-typing-sound\";\nimport * as Primitive from \"../../primitives\";\nimport { cn } from \"../utils\";\nimport Icon from \"./icons\";\nimport { BouncingDots } from \"./typing-indicator\";\n\ntype BubbleContentProps = {\n\tisOpen: boolean;\n\tunreadCount: number;\n\tisTyping: boolean;\n};\n\nconst BubbleContent: React.FC<BubbleContentProps> = ({\n\tisOpen,\n\tunreadCount,\n\tisTyping,\n}) => {\n\t// Customize playback settings here:\n\t// - volume: 0.0 to 1.0+ (default varies by sound)\n\t// - playbackRate: 0.5 to 2.0 (1.0 is normal speed, higher = faster)\n\tconst playNewMessageSound = useNewMessageSound({\n\t\tvolume: 0.7,\n\t\tplaybackRate: 1.0,\n\t});\n\tconst previousUnreadCountRef = useRef(0);\n\n\t// Play typing sound when widget is closed and someone is typing\n\tuseTypingSound(!isOpen && isTyping, {\n\t\tvolume: 1,\n\t\tplaybackRate: 1.3,\n\t});\n\n\t// Play new message sound when unread count increases\n\tuseEffect(() => {\n\t\tif (unreadCount > previousUnreadCountRef.current) {\n\t\t\tplayNewMessageSound();\n\t\t}\n\t\tpreviousUnreadCountRef.current = unreadCount;\n\t}, [unreadCount, playNewMessageSound]);\n\n\treturn (\n\t\t<>\n\t\t\t<AnimatePresence mode=\"wait\">\n\t\t\t\t{isOpen ? (\n\t\t\t\t\t<motion.div\n\t\t\t\t\t\tanimate={{\n\t\t\t\t\t\t\tscale: 1,\n\t\t\t\t\t\t\trotate: 0,\n\t\t\t\t\t\t\topacity: 1,\n\t\t\t\t\t\t\ttransition: { duration: 0.2, ease: \"easeOut\" },\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tclassName=\"flex items-center justify-center\"\n\t\t\t\t\t\texit={{\n\t\t\t\t\t\t\tscale: 0.9,\n\t\t\t\t\t\t\trotate: -45,\n\t\t\t\t\t\t\topacity: 0,\n\t\t\t\t\t\t\ttransition: { duration: 0.1, ease: \"easeIn\" },\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tinitial={{ scale: 0.9, rotate: 45, opacity: 0 }}\n\t\t\t\t\t\tkey=\"chevron\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<Icon className=\"h-5 w-5\" name=\"chevron-down\" />\n\t\t\t\t\t</motion.div>\n\t\t\t\t) : isTyping ? (\n\t\t\t\t\t<motion.span\n\t\t\t\t\t\tanimate={{\n\t\t\t\t\t\t\topacity: 1,\n\t\t\t\t\t\t\tscale: 1,\n\t\t\t\t\t\t\ttransition: {\n\t\t\t\t\t\t\t\tduration: 0.2,\n\t\t\t\t\t\t\t\tease: \"easeOut\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tclassName=\"pointer-events-none flex items-center rounded-full text-co-primary\"\n\t\t\t\t\t\texit={{\n\t\t\t\t\t\t\topacity: 0,\n\t\t\t\t\t\t\tscale: 0.9,\n\t\t\t\t\t\t\ttransition: {\n\t\t\t\t\t\t\t\tduration: 0.1,\n\t\t\t\t\t\t\t\tease: \"easeIn\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tinitial={{ opacity: 0, scale: 0.9 }}\n\t\t\t\t\t\tkey=\"typing-indicator\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<BouncingDots className=\"bg-co-primary-foreground\" />\n\t\t\t\t\t</motion.span>\n\t\t\t\t) : (\n\t\t\t\t\t<motion.div\n\t\t\t\t\t\tanimate={{\n\t\t\t\t\t\t\tscale: 1,\n\t\t\t\t\t\t\trotate: 0,\n\t\t\t\t\t\t\topacity: 1,\n\t\t\t\t\t\t\ttransition: { duration: 0.2, ease: \"easeOut\" },\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tclassName=\"flex items-center justify-center\"\n\t\t\t\t\t\texit={{\n\t\t\t\t\t\t\tscale: 0.9,\n\t\t\t\t\t\t\trotate: 45,\n\t\t\t\t\t\t\topacity: 0,\n\t\t\t\t\t\t\ttransition: { duration: 0.1, ease: \"easeIn\" },\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tinitial={{ scale: 0.9, rotate: -45, opacity: 0 }}\n\t\t\t\t\t\tkey=\"chat\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<Icon className=\"h-6 w-6\" name=\"chat\" variant=\"filled\" />\n\t\t\t\t\t</motion.div>\n\t\t\t\t)}\n\t\t\t</AnimatePresence>\n\n\t\t\t{unreadCount > 0 && (\n\t\t\t\t<motion.div\n\t\t\t\t\tanimate={{ scale: 1, opacity: 1 }}\n\t\t\t\t\tclassName=\"absolute top-0.5 right-0.5 flex size-2 items-center justify-center rounded-full bg-co-destructive font-medium text-[10px] text-co-destructive-foreground text-white text-xs\"\n\t\t\t\t\texit={{ scale: 0, opacity: 0 }}\n\t\t\t\t\tinitial={{ scale: 0, opacity: 0 }}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</>\n\t);\n};\n\nexport type BubbleProps = {\n\tclassName?: string;\n};\n\nexport const Bubble: React.FC<BubbleProps> = ({ className }) => (\n\t<Primitive.Bubble\n\t\tclassName={cn(\n\t\t\t\"relative flex size-12 cursor-pointer items-center justify-center rounded-full bg-co-primary text-co-primary-foreground transition-colors hover:bg-co-primary/90 data-[open=true]:bg-co-primary/90\",\n\t\t\tclassName\n\t\t)}\n\t>\n\t\t{({ isOpen, unreadCount, isTyping }) => (\n\t\t\t<BubbleContent\n\t\t\t\tisOpen={isOpen}\n\t\t\t\tisTyping={isTyping}\n\t\t\t\tunreadCount={unreadCount}\n\t\t\t/>\n\t\t)}\n\t</Primitive.Bubble>\n);\n"],"mappings":";;;;;;;;;;;;;;AAkBA,MAAMA,iBAA+C,EACpD,QACA,aACA,eACK;CAIL,MAAM,sBAAsB,mBAAmB;EAC9C,QAAQ;EACR,cAAc;EACd,CAAC;CACF,MAAM,yBAAyB,OAAO,EAAE;AAGxC,gBAAe,CAAC,UAAU,UAAU;EACnC,QAAQ;EACR,cAAc;EACd,CAAC;AAGF,iBAAgB;AACf,MAAI,cAAc,uBAAuB,QACxC,sBAAqB;AAEtB,yBAAuB,UAAU;IAC/B,CAAC,aAAa,oBAAoB,CAAC;AAEtC,QACC,4CACC,oBAAC;EAAgB,MAAK;YACpB,SACA,oBAAC,OAAO;GACP,SAAS;IACR,OAAO;IACP,QAAQ;IACR,SAAS;IACT,YAAY;KAAE,UAAU;KAAK,MAAM;KAAW;IAC9C;GACD,WAAU;GACV,MAAM;IACL,OAAO;IACP,QAAQ;IACR,SAAS;IACT,YAAY;KAAE,UAAU;KAAK,MAAM;KAAU;IAC7C;GACD,SAAS;IAAE,OAAO;IAAK,QAAQ;IAAI,SAAS;IAAG;aAG/C,oBAACC;IAAK,WAAU;IAAU,MAAK;KAAiB;KAF5C,UAGQ,GACV,WACH,oBAAC,OAAO;GACP,SAAS;IACR,SAAS;IACT,OAAO;IACP,YAAY;KACX,UAAU;KACV,MAAM;KACN;IACD;GACD,WAAU;GACV,MAAM;IACL,SAAS;IACT,OAAO;IACP,YAAY;KACX,UAAU;KACV,MAAM;KACN;IACD;GACD,SAAS;IAAE,SAAS;IAAG,OAAO;IAAK;aAGnC,oBAAC,gBAAa,WAAU,6BAA6B;KAFjD,mBAGS,GAEd,oBAAC,OAAO;GACP,SAAS;IACR,OAAO;IACP,QAAQ;IACR,SAAS;IACT,YAAY;KAAE,UAAU;KAAK,MAAM;KAAW;IAC9C;GACD,WAAU;GACV,MAAM;IACL,OAAO;IACP,QAAQ;IACR,SAAS;IACT,YAAY;KAAE,UAAU;KAAK,MAAM;KAAU;IAC7C;GACD,SAAS;IAAE,OAAO;IAAK,QAAQ;IAAK,SAAS;IAAG;aAGhD,oBAACA;IAAK,WAAU;IAAU,MAAK;IAAO,SAAQ;KAAW;KAFrD,OAGQ;GAEG,EAEjB,cAAc,KACd,oBAAC,OAAO;EACP,SAAS;GAAE,OAAO;GAAG,SAAS;GAAG;EACjC,WAAU;EACV,MAAM;GAAE,OAAO;GAAG,SAAS;GAAG;EAC9B,SAAS;GAAE,OAAO;GAAG,SAAS;GAAG;GAChC,IAED;;AAQL,MAAaC,UAAiC,EAAE,gBAC/C,oBAACC;CACA,WAAW,GACV,qMACA,UACA;YAEC,EAAE,QAAQ,aAAa,eACxB,oBAAC;EACQ;EACE;EACG;GACZ;EAEe"}
|
|
@@ -4,7 +4,7 @@ import * as class_variance_authority_dist_types0 from "class-variance-authority/
|
|
|
4
4
|
|
|
5
5
|
//#region src/support/components/button.d.ts
|
|
6
6
|
declare const coButtonVariants: (props?: ({
|
|
7
|
-
variant?: "
|
|
7
|
+
variant?: "default" | "tab" | "secondary" | "ghost" | "outline" | "tab-selected" | null | undefined;
|
|
8
8
|
size?: "default" | "large" | "icon" | null | undefined;
|
|
9
9
|
} & class_variance_authority_dist_types0.ClassProp) | undefined) => string;
|
|
10
10
|
type CossistantButtonProps = React$1.ComponentProps<"button"> & VariantProps<typeof coButtonVariants>;
|
|
@@ -35,7 +35,7 @@ const ConversationEvent = ({ event, availableAIAgents, createdAt, availableHuman
|
|
|
35
35
|
opacity: 1,
|
|
36
36
|
scale: 1
|
|
37
37
|
},
|
|
38
|
-
className: "flex items-center justify-center
|
|
38
|
+
className: "flex items-center justify-center pt-4 pb-8",
|
|
39
39
|
initial: {
|
|
40
40
|
opacity: 0,
|
|
41
41
|
scale: .95
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-event.js","names":["ConversationEvent: React.FC<ConversationEventProps>"],"sources":["../../../src/support/components/conversation-event.tsx"],"sourcesContent":["import type {\n\tAvailableAIAgent,\n\tAvailableHumanAgent,\n\tTimelinePartEvent,\n} from \"@cossistant/types\";\nimport { motion } from \"motion/react\";\nimport type React from \"react\";\nimport { useSupportText } from \"../text\";\nimport { Avatar } from \"./avatar\";\nimport { CossistantLogo } from \"./cossistant-branding\";\n\nexport type ConversationEventProps = {\n\tevent: TimelinePartEvent;\n\tavailableAIAgents: AvailableAIAgent[];\n\tavailableHumanAgents: AvailableHumanAgent[];\n\tcreatedAt?: string;\n};\n\nexport const ConversationEvent: React.FC<ConversationEventProps> = ({\n\tevent,\n\tavailableAIAgents,\n\tcreatedAt,\n\tavailableHumanAgents,\n}) => {\n\tconst text = useSupportText();\n\tconst isAI = event.actorAiAgentId !== null;\n\tconst humanAgent = availableHumanAgents.find(\n\t\t(agent) => agent.id === event.actorUserId\n\t);\n\tconst aiAgent = availableAIAgents.find(\n\t\t(agent) => agent.id === event.actorAiAgentId\n\t);\n\n\t// Get the actor name\n\tconst actorName = isAI\n\t\t? aiAgent?.name || text(\"common.fallbacks.cossistant\")\n\t\t: humanAgent?.name || text(\"common.fallbacks.someone\");\n\n\t// Convert event type to plain English\n\tconst getEventText = () => {\n\t\tswitch (event.eventType) {\n\t\t\tcase \"assigned\":\n\t\t\t\treturn text(\"component.conversationEvent.assigned\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"unassigned\":\n\t\t\t\treturn text(\"component.conversationEvent.unassigned\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"participant_requested\":\n\t\t\t\treturn text(\"component.conversationEvent.participantRequested\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"participant_joined\":\n\t\t\t\treturn text(\"component.conversationEvent.participantJoined\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"participant_left\":\n\t\t\t\treturn text(\"component.conversationEvent.participantLeft\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"status_changed\":\n\t\t\t\treturn text(\"component.conversationEvent.statusChanged\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"priority_changed\":\n\t\t\t\treturn text(\"component.conversationEvent.priorityChanged\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"tag_added\":\n\t\t\t\treturn text(\"component.conversationEvent.tagAdded\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"tag_removed\":\n\t\t\t\treturn text(\"component.conversationEvent.tagRemoved\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"resolved\":\n\t\t\t\treturn text(\"component.conversationEvent.resolved\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"reopened\":\n\t\t\t\treturn text(\"component.conversationEvent.reopened\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"visitor_blocked\":\n\t\t\t\treturn text(\"component.conversationEvent.visitorBlocked\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"visitor_unblocked\":\n\t\t\t\treturn text(\"component.conversationEvent.visitorUnblocked\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"visitor_identified\":\n\t\t\t\treturn text(\"component.conversationEvent.visitorIdentified\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tdefault:\n\t\t\t\treturn text(\"component.conversationEvent.default\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t}\n\t};\n\n\treturn (\n\t\t<motion.div\n\t\t\tanimate={{ opacity: 1, scale: 1 }}\n\t\t\tclassName=\"flex items-center justify-center
|
|
1
|
+
{"version":3,"file":"conversation-event.js","names":["ConversationEvent: React.FC<ConversationEventProps>"],"sources":["../../../src/support/components/conversation-event.tsx"],"sourcesContent":["import type {\n\tAvailableAIAgent,\n\tAvailableHumanAgent,\n\tTimelinePartEvent,\n} from \"@cossistant/types\";\nimport { motion } from \"motion/react\";\nimport type React from \"react\";\nimport { useSupportText } from \"../text\";\nimport { Avatar } from \"./avatar\";\nimport { CossistantLogo } from \"./cossistant-branding\";\n\nexport type ConversationEventProps = {\n\tevent: TimelinePartEvent;\n\tavailableAIAgents: AvailableAIAgent[];\n\tavailableHumanAgents: AvailableHumanAgent[];\n\tcreatedAt?: string;\n};\n\nexport const ConversationEvent: React.FC<ConversationEventProps> = ({\n\tevent,\n\tavailableAIAgents,\n\tcreatedAt,\n\tavailableHumanAgents,\n}) => {\n\tconst text = useSupportText();\n\tconst isAI = event.actorAiAgentId !== null;\n\tconst humanAgent = availableHumanAgents.find(\n\t\t(agent) => agent.id === event.actorUserId\n\t);\n\tconst aiAgent = availableAIAgents.find(\n\t\t(agent) => agent.id === event.actorAiAgentId\n\t);\n\n\t// Get the actor name\n\tconst actorName = isAI\n\t\t? aiAgent?.name || text(\"common.fallbacks.cossistant\")\n\t\t: humanAgent?.name || text(\"common.fallbacks.someone\");\n\n\t// Convert event type to plain English\n\tconst getEventText = () => {\n\t\tswitch (event.eventType) {\n\t\t\tcase \"assigned\":\n\t\t\t\treturn text(\"component.conversationEvent.assigned\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"unassigned\":\n\t\t\t\treturn text(\"component.conversationEvent.unassigned\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"participant_requested\":\n\t\t\t\treturn text(\"component.conversationEvent.participantRequested\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"participant_joined\":\n\t\t\t\treturn text(\"component.conversationEvent.participantJoined\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"participant_left\":\n\t\t\t\treturn text(\"component.conversationEvent.participantLeft\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"status_changed\":\n\t\t\t\treturn text(\"component.conversationEvent.statusChanged\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"priority_changed\":\n\t\t\t\treturn text(\"component.conversationEvent.priorityChanged\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"tag_added\":\n\t\t\t\treturn text(\"component.conversationEvent.tagAdded\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"tag_removed\":\n\t\t\t\treturn text(\"component.conversationEvent.tagRemoved\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"resolved\":\n\t\t\t\treturn text(\"component.conversationEvent.resolved\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"reopened\":\n\t\t\t\treturn text(\"component.conversationEvent.reopened\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"visitor_blocked\":\n\t\t\t\treturn text(\"component.conversationEvent.visitorBlocked\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"visitor_unblocked\":\n\t\t\t\treturn text(\"component.conversationEvent.visitorUnblocked\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tcase \"visitor_identified\":\n\t\t\t\treturn text(\"component.conversationEvent.visitorIdentified\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t\tdefault:\n\t\t\t\treturn text(\"component.conversationEvent.default\", {\n\t\t\t\t\tactorName,\n\t\t\t\t});\n\t\t}\n\t};\n\n\treturn (\n\t\t<motion.div\n\t\t\tanimate={{ opacity: 1, scale: 1 }}\n\t\t\tclassName=\"flex items-center justify-center pt-4 pb-8\"\n\t\t\tinitial={{ opacity: 0, scale: 0.95 }}\n\t\t\ttransition={{ duration: 0.3, ease: \"easeOut\" }}\n\t\t>\n\t\t\t<div className=\"flex items-center gap-2 text-co-muted-foreground text-xs\">\n\t\t\t\t<div className=\"flex flex-col justify-end\">\n\t\t\t\t\t{isAI ? (\n\t\t\t\t\t\t<div className=\"flex size-5 items-center justify-center rounded-full bg-co-primary/10\">\n\t\t\t\t\t\t\t<CossistantLogo className=\"h-3 w-3 text-co-primary\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<Avatar\n\t\t\t\t\t\t\tclassName=\"size-5 flex-shrink-0 overflow-clip rounded-full\"\n\t\t\t\t\t\t\timage={humanAgent?.image}\n\t\t\t\t\t\t\tname={humanAgent?.name || text(\"common.fallbacks.someone\")}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t\t<span className=\"px-2\">{getEventText()}</span>\n\t\t\t\t{createdAt && (\n\t\t\t\t\t<time className=\"text-[10px]\">\n\t\t\t\t\t\t{new Date(createdAt).toLocaleTimeString([], {\n\t\t\t\t\t\t\thour: \"2-digit\",\n\t\t\t\t\t\t\tminute: \"2-digit\",\n\t\t\t\t\t\t})}\n\t\t\t\t\t</time>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</motion.div>\n\t);\n};\n\nConversationEvent.displayName = \"ConversationEvent\";\n"],"mappings":";;;;;;;AAkBA,MAAaA,qBAAuD,EACnE,OACA,mBACA,WACA,2BACK;CACL,MAAM,OAAO,gBAAgB;CAC7B,MAAM,OAAO,MAAM,mBAAmB;CACtC,MAAM,aAAa,qBAAqB,MACtC,UAAU,MAAM,OAAO,MAAM,YAC9B;CACD,MAAM,UAAU,kBAAkB,MAChC,UAAU,MAAM,OAAO,MAAM,eAC9B;CAGD,MAAM,YAAY,OACf,SAAS,QAAQ,KAAK,8BAA8B,GACpD,YAAY,QAAQ,KAAK,2BAA2B;CAGvD,MAAM,qBAAqB;AAC1B,UAAQ,MAAM,WAAd;GACC,KAAK,WACJ,QAAO,KAAK,wCAAwC,EACnD,WACA,CAAC;GACH,KAAK,aACJ,QAAO,KAAK,0CAA0C,EACrD,WACA,CAAC;GACH,KAAK,wBACJ,QAAO,KAAK,oDAAoD,EAC/D,WACA,CAAC;GACH,KAAK,qBACJ,QAAO,KAAK,iDAAiD,EAC5D,WACA,CAAC;GACH,KAAK,mBACJ,QAAO,KAAK,+CAA+C,EAC1D,WACA,CAAC;GACH,KAAK,iBACJ,QAAO,KAAK,6CAA6C,EACxD,WACA,CAAC;GACH,KAAK,mBACJ,QAAO,KAAK,+CAA+C,EAC1D,WACA,CAAC;GACH,KAAK,YACJ,QAAO,KAAK,wCAAwC,EACnD,WACA,CAAC;GACH,KAAK,cACJ,QAAO,KAAK,0CAA0C,EACrD,WACA,CAAC;GACH,KAAK,WACJ,QAAO,KAAK,wCAAwC,EACnD,WACA,CAAC;GACH,KAAK,WACJ,QAAO,KAAK,wCAAwC,EACnD,WACA,CAAC;GACH,KAAK,kBACJ,QAAO,KAAK,8CAA8C,EACzD,WACA,CAAC;GACH,KAAK,oBACJ,QAAO,KAAK,gDAAgD,EAC3D,WACA,CAAC;GACH,KAAK,qBACJ,QAAO,KAAK,iDAAiD,EAC5D,WACA,CAAC;GACH,QACC,QAAO,KAAK,uCAAuC,EAClD,WACA,CAAC;;;AAIL,QACC,oBAAC,OAAO;EACP,SAAS;GAAE,SAAS;GAAG,OAAO;GAAG;EACjC,WAAU;EACV,SAAS;GAAE,SAAS;GAAG,OAAO;GAAM;EACpC,YAAY;GAAE,UAAU;GAAK,MAAM;GAAW;YAE9C,qBAAC;GAAI,WAAU;;IACd,oBAAC;KAAI,WAAU;eACb,OACA,oBAAC;MAAI,WAAU;gBACd,oBAAC,kBAAe,WAAU,4BAA4B;OACjD,GAEN,oBAAC;MACA,WAAU;MACV,OAAO,YAAY;MACnB,MAAM,YAAY,QAAQ,KAAK,2BAA2B;OACzD;MAEE;IACN,oBAAC;KAAK,WAAU;eAAQ,cAAc;MAAQ;IAC7C,aACA,oBAAC;KAAK,WAAU;eACd,IAAI,KAAK,UAAU,CAAC,mBAAmB,EAAE,EAAE;MAC3C,MAAM;MACN,QAAQ;MACR,CAAC;MACI;;IAEH;GACM;;AAIf,kBAAkB,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-timeline.d.ts","names":[],"sources":["../../../src/support/components/conversation-timeline.tsx"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"conversation-timeline.d.ts","names":[],"sources":["../../../src/support/components/conversation-timeline.tsx"],"sourcesContent":[],"mappings":";;;;;KAkCY,6BAAA;QACL;EADK,cAAA,EAAA,MAAA;AAKZ,CAAA;AAIY,KAJA,kCAAA,GAMX;EAGW,SAAA,EARA,KAAA,CAAM,aAQmB,CARL,6BAQK,CAAA;CAE7B;AAEY,KATR,yBAAA,GAA4B,MASpB,CAAA,MAAA,EAPnB,kCAOmB,CAAA;AACG,KALX,yBAAA,GAKW;EAEd,cAAA,EAAA,MAAA;EAAyB,KAAA,EAL1B,YAK0B,EAAA;EAGrB,SAAA,CAAA,EAAA,MAAA;qBANO;wBACG;;UAEd;;cAGI,0BAA0B,KAAA,CAAM,GAAG"}
|
|
@@ -2,6 +2,7 @@ import { cn } from "../utils/index.js";
|
|
|
2
2
|
import { TypingIndicator } from "./typing-indicator.js";
|
|
3
3
|
import { ConversationTimeline, ConversationTimelineContainer } from "../../primitives/conversation-timeline.js";
|
|
4
4
|
import { useConversationTimeline } from "../../hooks/use-conversation-timeline.js";
|
|
5
|
+
import { useTypingSound } from "../../hooks/use-typing-sound.js";
|
|
5
6
|
import { ConversationEvent } from "./conversation-event.js";
|
|
6
7
|
import { TimelineMessageGroup } from "./timeline-message-group.js";
|
|
7
8
|
import { useCallback, useMemo } from "react";
|
|
@@ -24,6 +25,10 @@ const ConversationTimelineList = ({ conversationId, items: timelineItems, classN
|
|
|
24
25
|
id: participant.id,
|
|
25
26
|
type: participant.type
|
|
26
27
|
})), [timeline.typingParticipants]);
|
|
28
|
+
useTypingSound(typingIndicatorParticipants.length > 0, {
|
|
29
|
+
volume: 1,
|
|
30
|
+
playbackRate: 1.3
|
|
31
|
+
});
|
|
27
32
|
const seenNameLookup = useMemo(() => {
|
|
28
33
|
const map = /* @__PURE__ */ new Map();
|
|
29
34
|
for (const agent of availableHumanAgents) if (agent.name) map.set(agent.id, agent.name);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-timeline.js","names":["EMPTY_SEEN_BY_IDS: readonly string[]","EMPTY_SEEN_BY_NAMES: readonly string[]","ConversationTimelineList: React.FC<ConversationTimelineProps>","names: string[]","PrimitiveConversationTimeline"],"sources":["../../../src/support/components/conversation-timeline.tsx"],"sourcesContent":["import type { AvailableAIAgent, AvailableHumanAgent } from \"@cossistant/types\";\nimport type {\n\tTimelineItem,\n\tTimelinePartEvent,\n} from \"@cossistant/types/api/timeline-item\";\nimport type React from \"react\";\nimport { useCallback, useMemo } from \"react\";\nimport { useConversationTimeline } from \"../../hooks/use-conversation-timeline\";\nimport {\n\tConversationTimelineContainer,\n\tConversationTimeline as PrimitiveConversationTimeline,\n} from \"../../primitives/conversation-timeline\";\nimport { cn } from \"../utils\";\nimport { ConversationEvent } from \"./conversation-event\";\nimport { TimelineMessageGroup } from \"./timeline-message-group\";\nimport { TypingIndicator, type TypingParticipant } from \"./typing-indicator\";\n\n// Helper to extract event part from timeline item\nfunction extractEventPart(item: TimelineItem): TimelinePartEvent | null {\n\tif (item.type !== \"event\") {\n\t\treturn null;\n\t}\n\n\tconst eventPart = item.parts.find(\n\t\t(part): part is TimelinePartEvent => part.type === \"event\"\n\t);\n\n\treturn eventPart || null;\n}\n\nconst EMPTY_SEEN_BY_IDS: readonly string[] = Object.freeze([]);\nconst EMPTY_SEEN_BY_NAMES: readonly string[] = Object.freeze([]);\n\nexport type ConversationTimelineToolProps = {\n\titem: TimelineItem;\n\tconversationId: string;\n};\n\nexport type ConversationTimelineToolDefinition = {\n\tcomponent: React.ComponentType<ConversationTimelineToolProps>;\n};\n\nexport type ConversationTimelineTools = Record<\n\tstring,\n\tConversationTimelineToolDefinition\n>;\n\nexport type ConversationTimelineProps = {\n\tconversationId: string;\n\titems: TimelineItem[];\n\tclassName?: string;\n\tavailableAIAgents: AvailableAIAgent[];\n\tavailableHumanAgents: AvailableHumanAgent[];\n\tcurrentVisitorId?: string;\n\ttools?: ConversationTimelineTools;\n};\n\nexport const ConversationTimelineList: React.FC<ConversationTimelineProps> = ({\n\tconversationId,\n\titems: timelineItems,\n\tclassName,\n\tavailableAIAgents = [],\n\tavailableHumanAgents = [],\n\tcurrentVisitorId,\n\ttools,\n}) => {\n\tconst timeline = useConversationTimeline({\n\t\tconversationId,\n\t\titems: timelineItems,\n\t\tcurrentVisitorId,\n\t});\n\n\tconst typingIndicatorParticipants = useMemo(\n\t\t() =>\n\t\t\ttimeline.typingParticipants.map<TypingParticipant>((participant) => ({\n\t\t\t\tid: participant.id,\n\t\t\t\ttype: participant.type,\n\t\t\t})),\n\t\t[timeline.typingParticipants]\n\t);\n\n\tconst seenNameLookup = useMemo(() => {\n\t\tconst map = new Map<string, string>();\n\n\t\tfor (const agent of availableHumanAgents) {\n\t\t\tif (agent.name) {\n\t\t\t\tmap.set(agent.id, agent.name);\n\t\t\t}\n\t\t}\n\n\t\tfor (const agent of availableAIAgents) {\n\t\t\tif (agent.name) {\n\t\t\t\tmap.set(agent.id, agent.name);\n\t\t\t}\n\t\t}\n\n\t\treturn map;\n\t}, [availableHumanAgents, availableAIAgents]);\n\n\tconst getSeenByNames = useCallback(\n\t\t(ids: readonly string[] = EMPTY_SEEN_BY_IDS): readonly string[] => {\n\t\t\tif (ids.length === 0 || seenNameLookup.size === 0) {\n\t\t\t\treturn EMPTY_SEEN_BY_NAMES;\n\t\t\t}\n\n\t\t\tconst uniqueNames = new Set<string>();\n\t\t\tconst names: string[] = [];\n\n\t\t\tfor (const id of ids) {\n\t\t\t\tconst name = seenNameLookup.get(id);\n\t\t\t\tif (!name || uniqueNames.has(name)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tuniqueNames.add(name);\n\t\t\t\tnames.push(name);\n\t\t\t}\n\n\t\t\tif (names.length === 0) {\n\t\t\t\treturn EMPTY_SEEN_BY_NAMES;\n\t\t\t}\n\n\t\t\treturn Object.freeze(names);\n\t\t},\n\t\t[seenNameLookup]\n\t);\n\n\treturn (\n\t\t<PrimitiveConversationTimeline\n\t\t\tautoScroll={true}\n\t\t\tclassName={cn(\n\t\t\t\t\"overflow-y-scroll scroll-smooth px-3 py-6\",\n\t\t\t\t\"co-scrollbar-thin\",\n\t\t\t\t\"h-full w-full\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\tid=\"conversation-timeline\"\n\t\t\titems={timelineItems}\n\t\t>\n\t\t\t<ConversationTimelineContainer className=\"flex min-h-full w-full flex-col gap-3\">\n\t\t\t\t{timeline.groupedMessages.items.map((item, index) => {\n\t\t\t\t\tif (item.type === \"timeline_event\") {\n\t\t\t\t\t\t// Extract event data from parts\n\t\t\t\t\t\tconst eventPart = extractEventPart(item.item);\n\n\t\t\t\t\t\t// Only render if we have valid event data\n\t\t\t\t\t\tif (!eventPart) {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<ConversationEvent\n\t\t\t\t\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\t\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\t\t\t\t\tcreatedAt={item.item.createdAt}\n\t\t\t\t\t\t\t\tevent={eventPart}\n\t\t\t\t\t\t\t\tkey={item.item.id ?? `timeline-event-${item.item.createdAt}`}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (item.type === \"timeline_tool\") {\n\t\t\t\t\t\tconst toolName = item.tool ?? item.item.tool ?? item.item.type;\n\t\t\t\t\t\tconst toolDefinition = toolName ? tools?.[toolName] : undefined;\n\n\t\t\t\t\t\tif (!toolDefinition) {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst ToolComponent = toolDefinition.component;\n\n\t\t\t\t\t\tconst toolKey =\n\t\t\t\t\t\t\titem.item.id ?? `${toolName}-${item.item.createdAt}-${index}`;\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<ToolComponent\n\t\t\t\t\t\t\t\tconversationId={conversationId}\n\t\t\t\t\t\t\t\titem={item.item}\n\t\t\t\t\t\t\t\tkey={toolKey}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only show seen indicator on the LAST message group sent by the visitor\n\t\t\t\t\tconst isLastVisitorGroup =\n\t\t\t\t\t\tindex === timeline.lastVisitorMessageGroupIndex;\n\t\t\t\t\tconst seenByIds =\n\t\t\t\t\t\tisLastVisitorGroup && item.lastMessageId\n\t\t\t\t\t\t\t? timeline.groupedMessages.getMessageSeenBy(item.lastMessageId)\n\t\t\t\t\t\t\t: EMPTY_SEEN_BY_IDS;\n\t\t\t\t\tconst seenByNames =\n\t\t\t\t\t\tseenByIds.length > 0\n\t\t\t\t\t\t\t? getSeenByNames(seenByIds)\n\t\t\t\t\t\t\t: EMPTY_SEEN_BY_NAMES;\n\n\t\t\t\t\t// Use first timeline item ID as stable key\n\t\t\t\t\tconst groupKey =\n\t\t\t\t\t\titem.lastMessageId ??\n\t\t\t\t\t\titem.items?.[0]?.id ??\n\t\t\t\t\t\t`group-${item.items?.[0]?.createdAt ?? index}`;\n\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<TimelineMessageGroup\n\t\t\t\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\t\t\t\tcurrentVisitorId={currentVisitorId}\n\t\t\t\t\t\t\titems={item.items || []}\n\t\t\t\t\t\t\tkey={groupKey}\n\t\t\t\t\t\t\tseenByIds={seenByIds}\n\t\t\t\t\t\t\tseenByNames={seenByNames}\n\t\t\t\t\t\t/>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t\t<div className=\"h-6 w-full\">\n\t\t\t\t\t{typingIndicatorParticipants.length > 0 ? (\n\t\t\t\t\t\t<TypingIndicator\n\t\t\t\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\t\t\t\tclassName=\"mt-2\"\n\t\t\t\t\t\t\tparticipants={typingIndicatorParticipants}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t</ConversationTimelineContainer>\n\t\t</PrimitiveConversationTimeline>\n\t);\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"conversation-timeline.js","names":["EMPTY_SEEN_BY_IDS: readonly string[]","EMPTY_SEEN_BY_NAMES: readonly string[]","ConversationTimelineList: React.FC<ConversationTimelineProps>","names: string[]","PrimitiveConversationTimeline"],"sources":["../../../src/support/components/conversation-timeline.tsx"],"sourcesContent":["import type { AvailableAIAgent, AvailableHumanAgent } from \"@cossistant/types\";\nimport type {\n\tTimelineItem,\n\tTimelinePartEvent,\n} from \"@cossistant/types/api/timeline-item\";\nimport type React from \"react\";\nimport { useCallback, useMemo } from \"react\";\nimport { useConversationTimeline } from \"../../hooks/use-conversation-timeline\";\nimport { useTypingSound } from \"../../hooks/use-typing-sound\";\nimport {\n\tConversationTimelineContainer,\n\tConversationTimeline as PrimitiveConversationTimeline,\n} from \"../../primitives/conversation-timeline\";\nimport { cn } from \"../utils\";\nimport { ConversationEvent } from \"./conversation-event\";\nimport { TimelineMessageGroup } from \"./timeline-message-group\";\nimport { TypingIndicator, type TypingParticipant } from \"./typing-indicator\";\n\n// Helper to extract event part from timeline item\nfunction extractEventPart(item: TimelineItem): TimelinePartEvent | null {\n\tif (item.type !== \"event\") {\n\t\treturn null;\n\t}\n\n\tconst eventPart = item.parts.find(\n\t\t(part): part is TimelinePartEvent => part.type === \"event\"\n\t);\n\n\treturn eventPart || null;\n}\n\nconst EMPTY_SEEN_BY_IDS: readonly string[] = Object.freeze([]);\nconst EMPTY_SEEN_BY_NAMES: readonly string[] = Object.freeze([]);\n\nexport type ConversationTimelineToolProps = {\n\titem: TimelineItem;\n\tconversationId: string;\n};\n\nexport type ConversationTimelineToolDefinition = {\n\tcomponent: React.ComponentType<ConversationTimelineToolProps>;\n};\n\nexport type ConversationTimelineTools = Record<\n\tstring,\n\tConversationTimelineToolDefinition\n>;\n\nexport type ConversationTimelineProps = {\n\tconversationId: string;\n\titems: TimelineItem[];\n\tclassName?: string;\n\tavailableAIAgents: AvailableAIAgent[];\n\tavailableHumanAgents: AvailableHumanAgent[];\n\tcurrentVisitorId?: string;\n\ttools?: ConversationTimelineTools;\n};\n\nexport const ConversationTimelineList: React.FC<ConversationTimelineProps> = ({\n\tconversationId,\n\titems: timelineItems,\n\tclassName,\n\tavailableAIAgents = [],\n\tavailableHumanAgents = [],\n\tcurrentVisitorId,\n\ttools,\n}) => {\n\tconst timeline = useConversationTimeline({\n\t\tconversationId,\n\t\titems: timelineItems,\n\t\tcurrentVisitorId,\n\t});\n\n\tconst typingIndicatorParticipants = useMemo(\n\t\t() =>\n\t\t\ttimeline.typingParticipants.map<TypingParticipant>((participant) => ({\n\t\t\t\tid: participant.id,\n\t\t\t\ttype: participant.type,\n\t\t\t})),\n\t\t[timeline.typingParticipants]\n\t);\n\n\t// Play typing sound when someone is typing\n\tuseTypingSound(typingIndicatorParticipants.length > 0, {\n\t\tvolume: 1,\n\t\tplaybackRate: 1.3,\n\t});\n\n\tconst seenNameLookup = useMemo(() => {\n\t\tconst map = new Map<string, string>();\n\n\t\tfor (const agent of availableHumanAgents) {\n\t\t\tif (agent.name) {\n\t\t\t\tmap.set(agent.id, agent.name);\n\t\t\t}\n\t\t}\n\n\t\tfor (const agent of availableAIAgents) {\n\t\t\tif (agent.name) {\n\t\t\t\tmap.set(agent.id, agent.name);\n\t\t\t}\n\t\t}\n\n\t\treturn map;\n\t}, [availableHumanAgents, availableAIAgents]);\n\n\tconst getSeenByNames = useCallback(\n\t\t(ids: readonly string[] = EMPTY_SEEN_BY_IDS): readonly string[] => {\n\t\t\tif (ids.length === 0 || seenNameLookup.size === 0) {\n\t\t\t\treturn EMPTY_SEEN_BY_NAMES;\n\t\t\t}\n\n\t\t\tconst uniqueNames = new Set<string>();\n\t\t\tconst names: string[] = [];\n\n\t\t\tfor (const id of ids) {\n\t\t\t\tconst name = seenNameLookup.get(id);\n\t\t\t\tif (!name || uniqueNames.has(name)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tuniqueNames.add(name);\n\t\t\t\tnames.push(name);\n\t\t\t}\n\n\t\t\tif (names.length === 0) {\n\t\t\t\treturn EMPTY_SEEN_BY_NAMES;\n\t\t\t}\n\n\t\t\treturn Object.freeze(names);\n\t\t},\n\t\t[seenNameLookup]\n\t);\n\n\treturn (\n\t\t<PrimitiveConversationTimeline\n\t\t\tautoScroll={true}\n\t\t\tclassName={cn(\n\t\t\t\t\"overflow-y-scroll scroll-smooth px-3 py-6\",\n\t\t\t\t\"co-scrollbar-thin\",\n\t\t\t\t\"h-full w-full\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\tid=\"conversation-timeline\"\n\t\t\titems={timelineItems}\n\t\t>\n\t\t\t<ConversationTimelineContainer className=\"flex min-h-full w-full flex-col gap-3\">\n\t\t\t\t{timeline.groupedMessages.items.map((item, index) => {\n\t\t\t\t\tif (item.type === \"timeline_event\") {\n\t\t\t\t\t\t// Extract event data from parts\n\t\t\t\t\t\tconst eventPart = extractEventPart(item.item);\n\n\t\t\t\t\t\t// Only render if we have valid event data\n\t\t\t\t\t\tif (!eventPart) {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<ConversationEvent\n\t\t\t\t\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\t\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\t\t\t\t\tcreatedAt={item.item.createdAt}\n\t\t\t\t\t\t\t\tevent={eventPart}\n\t\t\t\t\t\t\t\tkey={item.item.id ?? `timeline-event-${item.item.createdAt}`}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (item.type === \"timeline_tool\") {\n\t\t\t\t\t\tconst toolName = item.tool ?? item.item.tool ?? item.item.type;\n\t\t\t\t\t\tconst toolDefinition = toolName ? tools?.[toolName] : undefined;\n\n\t\t\t\t\t\tif (!toolDefinition) {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst ToolComponent = toolDefinition.component;\n\n\t\t\t\t\t\tconst toolKey =\n\t\t\t\t\t\t\titem.item.id ?? `${toolName}-${item.item.createdAt}-${index}`;\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<ToolComponent\n\t\t\t\t\t\t\t\tconversationId={conversationId}\n\t\t\t\t\t\t\t\titem={item.item}\n\t\t\t\t\t\t\t\tkey={toolKey}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only show seen indicator on the LAST message group sent by the visitor\n\t\t\t\t\tconst isLastVisitorGroup =\n\t\t\t\t\t\tindex === timeline.lastVisitorMessageGroupIndex;\n\t\t\t\t\tconst seenByIds =\n\t\t\t\t\t\tisLastVisitorGroup && item.lastMessageId\n\t\t\t\t\t\t\t? timeline.groupedMessages.getMessageSeenBy(item.lastMessageId)\n\t\t\t\t\t\t\t: EMPTY_SEEN_BY_IDS;\n\t\t\t\t\tconst seenByNames =\n\t\t\t\t\t\tseenByIds.length > 0\n\t\t\t\t\t\t\t? getSeenByNames(seenByIds)\n\t\t\t\t\t\t\t: EMPTY_SEEN_BY_NAMES;\n\n\t\t\t\t\t// Use first timeline item ID as stable key\n\t\t\t\t\tconst groupKey =\n\t\t\t\t\t\titem.lastMessageId ??\n\t\t\t\t\t\titem.items?.[0]?.id ??\n\t\t\t\t\t\t`group-${item.items?.[0]?.createdAt ?? index}`;\n\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<TimelineMessageGroup\n\t\t\t\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\t\t\t\tcurrentVisitorId={currentVisitorId}\n\t\t\t\t\t\t\titems={item.items || []}\n\t\t\t\t\t\t\tkey={groupKey}\n\t\t\t\t\t\t\tseenByIds={seenByIds}\n\t\t\t\t\t\t\tseenByNames={seenByNames}\n\t\t\t\t\t\t/>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t\t<div className=\"h-6 w-full\">\n\t\t\t\t\t{typingIndicatorParticipants.length > 0 ? (\n\t\t\t\t\t\t<TypingIndicator\n\t\t\t\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\t\t\t\tclassName=\"mt-2\"\n\t\t\t\t\t\t\tparticipants={typingIndicatorParticipants}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t</ConversationTimelineContainer>\n\t\t</PrimitiveConversationTimeline>\n\t);\n};\n"],"mappings":";;;;;;;;;;;AAmBA,SAAS,iBAAiB,MAA8C;AACvE,KAAI,KAAK,SAAS,QACjB,QAAO;AAOR,QAJkB,KAAK,MAAM,MAC3B,SAAoC,KAAK,SAAS,QACnD,IAEmB;;AAGrB,MAAMA,oBAAuC,OAAO,OAAO,EAAE,CAAC;AAC9D,MAAMC,sBAAyC,OAAO,OAAO,EAAE,CAAC;AA0BhE,MAAaC,4BAAiE,EAC7E,gBACA,OAAO,eACP,WACA,oBAAoB,EAAE,EACtB,uBAAuB,EAAE,EACzB,kBACA,YACK;CACL,MAAM,WAAW,wBAAwB;EACxC;EACA,OAAO;EACP;EACA,CAAC;CAEF,MAAM,8BAA8B,cAElC,SAAS,mBAAmB,KAAwB,iBAAiB;EACpE,IAAI,YAAY;EAChB,MAAM,YAAY;EAClB,EAAE,EACJ,CAAC,SAAS,mBAAmB,CAC7B;AAGD,gBAAe,4BAA4B,SAAS,GAAG;EACtD,QAAQ;EACR,cAAc;EACd,CAAC;CAEF,MAAM,iBAAiB,cAAc;EACpC,MAAM,sBAAM,IAAI,KAAqB;AAErC,OAAK,MAAM,SAAS,qBACnB,KAAI,MAAM,KACT,KAAI,IAAI,MAAM,IAAI,MAAM,KAAK;AAI/B,OAAK,MAAM,SAAS,kBACnB,KAAI,MAAM,KACT,KAAI,IAAI,MAAM,IAAI,MAAM,KAAK;AAI/B,SAAO;IACL,CAAC,sBAAsB,kBAAkB,CAAC;CAE7C,MAAM,iBAAiB,aACrB,MAAyB,sBAAyC;AAClE,MAAI,IAAI,WAAW,KAAK,eAAe,SAAS,EAC/C,QAAO;EAGR,MAAM,8BAAc,IAAI,KAAa;EACrC,MAAMC,QAAkB,EAAE;AAE1B,OAAK,MAAM,MAAM,KAAK;GACrB,MAAM,OAAO,eAAe,IAAI,GAAG;AACnC,OAAI,CAAC,QAAQ,YAAY,IAAI,KAAK,CACjC;AAGD,eAAY,IAAI,KAAK;AACrB,SAAM,KAAK,KAAK;;AAGjB,MAAI,MAAM,WAAW,EACpB,QAAO;AAGR,SAAO,OAAO,OAAO,MAAM;IAE5B,CAAC,eAAe,CAChB;AAED,QACC,oBAACC;EACA,YAAY;EACZ,WAAW,GACV,6CACA,qBACA,iBACA,UACA;EACD,IAAG;EACH,OAAO;YAEP,qBAAC;GAA8B,WAAU;cACvC,SAAS,gBAAgB,MAAM,KAAK,MAAM,UAAU;AACpD,QAAI,KAAK,SAAS,kBAAkB;KAEnC,MAAM,YAAY,iBAAiB,KAAK,KAAK;AAG7C,SAAI,CAAC,UACJ,QAAO;AAGR,YACC,oBAAC;MACmB;MACG;MACtB,WAAW,KAAK,KAAK;MACrB,OAAO;QACF,KAAK,KAAK,MAAM,kBAAkB,KAAK,KAAK,YAChD;;AAIJ,QAAI,KAAK,SAAS,iBAAiB;KAClC,MAAM,WAAW,KAAK,QAAQ,KAAK,KAAK,QAAQ,KAAK,KAAK;KAC1D,MAAM,iBAAiB,WAAW,QAAQ,YAAY;AAEtD,SAAI,CAAC,eACJ,QAAO;KAGR,MAAM,gBAAgB,eAAe;KAErC,MAAM,UACL,KAAK,KAAK,MAAM,GAAG,SAAS,GAAG,KAAK,KAAK,UAAU,GAAG;AAEvD,YACC,oBAAC;MACgB;MAChB,MAAM,KAAK;QACN,QACJ;;IAOJ,MAAM,YADL,UAAU,SAAS,gCAEG,KAAK,gBACxB,SAAS,gBAAgB,iBAAiB,KAAK,cAAc,GAC7D;IACJ,MAAM,cACL,UAAU,SAAS,IAChB,eAAe,UAAU,GACzB;IAGJ,MAAM,WACL,KAAK,iBACL,KAAK,QAAQ,IAAI,MACjB,SAAS,KAAK,QAAQ,IAAI,aAAa;AAExC,WACC,oBAAC;KACmB;KACG;KACJ;KAClB,OAAO,KAAK,SAAS,EAAE;KAEZ;KACE;OAFR,SAGJ;KAEF,EACF,oBAAC;IAAI,WAAU;cACb,4BAA4B,SAAS,IACrC,oBAAC;KACmB;KACG;KACtB,WAAU;KACV,cAAc;MACb,GACC;KACC;IACyB;GACD"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CustomPage } from "../router.js";
|
|
1
2
|
import { SupportProps } from "../index.js";
|
|
2
3
|
import React from "react";
|
|
3
4
|
|
|
@@ -9,6 +10,7 @@ type SupportContentProps = {
|
|
|
9
10
|
positioning?: "fixed" | "absolute";
|
|
10
11
|
slots?: SupportProps["slots"];
|
|
11
12
|
classNames?: SupportProps["classNames"];
|
|
13
|
+
customPages?: CustomPage[];
|
|
12
14
|
children?: React.ReactNode;
|
|
13
15
|
};
|
|
14
16
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"support-content.d.ts","names":[],"sources":["../../../src/support/components/support-content.tsx"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"support-content.d.ts","names":[],"sources":["../../../src/support/components/support-content.tsx"],"sourcesContent":[],"mappings":";;;;;KAYK,mBAAA;;EAAA,QAAA,CAAA,EAAA,KAAA,GAAA,QAAmB;EAKf,KAAA,CAAA,EAAA,OAAA,GAAA,MAAA;EACK,WAAA,CAAA,EAAA,OAAA,GAAA,UAAA;EACC,KAAA,CAAA,EAFN,YAEM,CAAA,OAAA,CAAA;EACH,UAAM,CAAA,EAFJ,YAEI,CAAA,YAAA,CAAA;EAAS,WAAA,CAAA,EADZ,UACY,EAAA;EAMd,QAAA,CAAA,EAND,KAAA,CAAM,SAyDjB;;;;;cAnDY,gBAAgB,KAAA,CAAM,GAAG"}
|
|
@@ -12,7 +12,7 @@ import { motion } from "motion/react";
|
|
|
12
12
|
/**
|
|
13
13
|
* Main support widget container handling layout, positioning, and routing.
|
|
14
14
|
*/
|
|
15
|
-
const SupportContent = ({ className, position = "bottom", align = "right", positioning = "fixed", slots = {}, classNames = {}, children }) => {
|
|
15
|
+
const SupportContent = ({ className, position = "bottom", align = "right", positioning = "fixed", slots = {}, classNames = {}, customPages, children }) => {
|
|
16
16
|
const BubbleComponent = slots.bubble ?? Bubble;
|
|
17
17
|
const ContainerComponent = slots.container ?? Container;
|
|
18
18
|
const RouterComponent = slots.router ?? SupportRouter;
|
|
@@ -35,7 +35,10 @@ const SupportContent = ({ className, position = "bottom", align = "right", posit
|
|
|
35
35
|
align,
|
|
36
36
|
className: classNames.container,
|
|
37
37
|
position,
|
|
38
|
-
children: /* @__PURE__ */ jsx(RouterComponent, {
|
|
38
|
+
children: /* @__PURE__ */ jsx(RouterComponent, {
|
|
39
|
+
customPages,
|
|
40
|
+
children
|
|
41
|
+
})
|
|
39
42
|
})]
|
|
40
43
|
});
|
|
41
44
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"support-content.js","names":["SupportContent: React.FC<SupportContentProps>"],"sources":["../../../src/support/components/support-content.tsx"],"sourcesContent":["\"use client\";\n\nimport { motion } from \"motion/react\";\nimport type React from \"react\";\n\nimport type { SupportProps } from \"../index\";\nimport { SupportRouter } from \"../router\";\nimport { cn } from \"../utils\";\nimport { Bubble } from \"./bubble\";\nimport { Container } from \"./container\";\n\ntype SupportContentProps = {\n\tclassName?: string;\n\tposition?: \"top\" | \"bottom\";\n\talign?: \"right\" | \"left\";\n\tpositioning?: \"fixed\" | \"absolute\";\n\tslots?: SupportProps[\"slots\"];\n\tclassNames?: SupportProps[\"classNames\"];\n\tchildren?: React.ReactNode;\n};\n\n/**\n * Main support widget container handling layout, positioning, and routing.\n */\nexport const SupportContent: React.FC<SupportContentProps> = ({\n\tclassName,\n\tposition = \"bottom\",\n\talign = \"right\",\n\tpositioning = \"fixed\",\n\tslots = {},\n\tclassNames = {},\n\tchildren,\n}) => {\n\t// Use custom components if provided, otherwise use defaults\n\tconst BubbleComponent = slots.bubble ?? Bubble;\n\tconst ContainerComponent = slots.container ?? Container;\n\tconst RouterComponent = slots.router ?? SupportRouter;\n\n\tconst containerClasses = cn(\n\t\t\"cossistant z-[9999]\",\n\t\tpositioning === \"fixed\" ? \"fixed\" : \"absolute\",\n\t\t{\n\t\t\t\"bottom-4\": position === \"bottom\",\n\t\t\t\"top-4\": position === \"top\",\n\t\t\t\"right-4\": align === \"right\",\n\t\t\t\"left-4\": align === \"left\",\n\t\t},\n\t\tclassNames.root,\n\t\tclassName\n\t);\n\n\treturn (\n\t\t<motion.div\n\t\t\tanimate={{ opacity: 1 }}\n\t\t\tclassName={containerClasses}\n\t\t\tinitial={{ opacity: 0 }}\n\t\t\tlayout=\"position\"\n\t\t\ttransition={{\n\t\t\t\tdefault: { ease: \"anticipate\" },\n\t\t\t\tlayout: { duration: 0.3 },\n\t\t\t}}\n\t\t>\n\t\t\t<BubbleComponent\n\t\t\t\tclassName={cn(\"z-[1000] md:z-[9999]\", classNames.bubble)}\n\t\t\t/>\n\t\t\t<ContainerComponent\n\t\t\t\talign={align}\n\t\t\t\tclassName={classNames.container}\n\t\t\t\tposition={position}\n\t\t\t>\n\t\t\t\t<RouterComponent>{children}</RouterComponent>\n\t\t\t</ContainerComponent>\n\t\t</motion.div>\n\t);\n};\n"],"mappings":";;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"support-content.js","names":["SupportContent: React.FC<SupportContentProps>"],"sources":["../../../src/support/components/support-content.tsx"],"sourcesContent":["\"use client\";\n\nimport { motion } from \"motion/react\";\nimport type React from \"react\";\n\nimport type { SupportProps } from \"../index\";\nimport type { CustomPage } from \"../router\";\nimport { SupportRouter } from \"../router\";\nimport { cn } from \"../utils\";\nimport { Bubble } from \"./bubble\";\nimport { Container } from \"./container\";\n\ntype SupportContentProps = {\n\tclassName?: string;\n\tposition?: \"top\" | \"bottom\";\n\talign?: \"right\" | \"left\";\n\tpositioning?: \"fixed\" | \"absolute\";\n\tslots?: SupportProps[\"slots\"];\n\tclassNames?: SupportProps[\"classNames\"];\n\tcustomPages?: CustomPage[];\n\tchildren?: React.ReactNode;\n};\n\n/**\n * Main support widget container handling layout, positioning, and routing.\n */\nexport const SupportContent: React.FC<SupportContentProps> = ({\n\tclassName,\n\tposition = \"bottom\",\n\talign = \"right\",\n\tpositioning = \"fixed\",\n\tslots = {},\n\tclassNames = {},\n\tcustomPages,\n\tchildren,\n}) => {\n\t// Use custom components if provided, otherwise use defaults\n\tconst BubbleComponent = slots.bubble ?? Bubble;\n\tconst ContainerComponent = slots.container ?? Container;\n\tconst RouterComponent = slots.router ?? SupportRouter;\n\n\tconst containerClasses = cn(\n\t\t\"cossistant z-[9999]\",\n\t\tpositioning === \"fixed\" ? \"fixed\" : \"absolute\",\n\t\t{\n\t\t\t\"bottom-4\": position === \"bottom\",\n\t\t\t\"top-4\": position === \"top\",\n\t\t\t\"right-4\": align === \"right\",\n\t\t\t\"left-4\": align === \"left\",\n\t\t},\n\t\tclassNames.root,\n\t\tclassName\n\t);\n\n\treturn (\n\t\t<motion.div\n\t\t\tanimate={{ opacity: 1 }}\n\t\t\tclassName={containerClasses}\n\t\t\tinitial={{ opacity: 0 }}\n\t\t\tlayout=\"position\"\n\t\t\ttransition={{\n\t\t\t\tdefault: { ease: \"anticipate\" },\n\t\t\t\tlayout: { duration: 0.3 },\n\t\t\t}}\n\t\t>\n\t\t\t<BubbleComponent\n\t\t\t\tclassName={cn(\"z-[1000] md:z-[9999]\", classNames.bubble)}\n\t\t\t/>\n\t\t\t<ContainerComponent\n\t\t\t\talign={align}\n\t\t\t\tclassName={classNames.container}\n\t\t\t\tposition={position}\n\t\t\t>\n\t\t\t\t<RouterComponent customPages={customPages}>{children}</RouterComponent>\n\t\t\t</ContainerComponent>\n\t\t</motion.div>\n\t);\n};\n"],"mappings":";;;;;;;;;;;;;;AA0BA,MAAaA,kBAAiD,EAC7D,WACA,WAAW,UACX,QAAQ,SACR,cAAc,SACd,QAAQ,EAAE,EACV,aAAa,EAAE,EACf,aACA,eACK;CAEL,MAAM,kBAAkB,MAAM,UAAU;CACxC,MAAM,qBAAqB,MAAM,aAAa;CAC9C,MAAM,kBAAkB,MAAM,UAAU;CAExC,MAAM,mBAAmB,GACxB,uBACA,gBAAgB,UAAU,UAAU,YACpC;EACC,YAAY,aAAa;EACzB,SAAS,aAAa;EACtB,WAAW,UAAU;EACrB,UAAU,UAAU;EACpB,EACD,WAAW,MACX,UACA;AAED,QACC,qBAAC,OAAO;EACP,SAAS,EAAE,SAAS,GAAG;EACvB,WAAW;EACX,SAAS,EAAE,SAAS,GAAG;EACvB,QAAO;EACP,YAAY;GACX,SAAS,EAAE,MAAM,cAAc;GAC/B,QAAQ,EAAE,UAAU,IAAK;GACzB;aAED,oBAAC,mBACA,WAAW,GAAG,wBAAwB,WAAW,OAAO,GACvD,EACF,oBAAC;GACO;GACP,WAAW,WAAW;GACZ;aAEV,oBAAC;IAA6B;IAAc;KAA2B;IACnD;GACT"}
|
|
@@ -43,7 +43,7 @@ const TimelineMessageGroup = ({ items, availableAIAgents, availableHumanAgents,
|
|
|
43
43
|
const humanAgent = availableHumanAgents.find((agent) => agent.id === firstItem?.userId);
|
|
44
44
|
const aiAgent = availableAIAgents.find((agent) => agent.id === firstItem?.aiAgentId);
|
|
45
45
|
if (items.length === 0) return null;
|
|
46
|
-
const hasSeenIndicator = seenByIds.length > 0
|
|
46
|
+
const hasSeenIndicator = seenByIds.length > 0;
|
|
47
47
|
return /* @__PURE__ */ jsx(TimelineItemGroup, {
|
|
48
48
|
items,
|
|
49
49
|
seenByIds,
|
|
@@ -85,7 +85,7 @@ const TimelineMessageGroup = ({ items, availableAIAgents, availableHumanAgents,
|
|
|
85
85
|
children: /* @__PURE__ */ jsx(TimelineItemGroupSeenIndicator, {
|
|
86
86
|
className: "px-1 text-co-muted-foreground text-xs",
|
|
87
87
|
seenByIds,
|
|
88
|
-
children: () => `Seen by ${seenByNames.join(", ")}`
|
|
88
|
+
children: () => seenByNames.length > 0 ? `Seen by ${seenByNames.join(", ")}` : "Seen"
|
|
89
89
|
})
|
|
90
90
|
}, "seen-indicator")
|
|
91
91
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-message-group.js","names":["EMPTY_SEEN_BY_IDS: readonly string[]","EMPTY_SEEN_BY_NAMES: readonly string[]","TimelineMessageGroup: React.FC<TimelineMessageGroupProps>","PrimitiveTimelineItemGroup"],"sources":["../../../src/support/components/timeline-message-group.tsx"],"sourcesContent":["import type { AvailableAIAgent, AvailableHumanAgent } from \"@cossistant/types\";\nimport { SenderType } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { motion } from \"motion/react\";\nimport type React from \"react\";\nimport {\n\tTimelineItemGroup as PrimitiveTimelineItemGroup,\n\tTimelineItemGroupAvatar,\n\tTimelineItemGroupContent,\n\tTimelineItemGroupHeader,\n\tTimelineItemGroupSeenIndicator,\n} from \"../../primitives/timeline-item-group\";\nimport { cn } from \"../utils\";\nimport { Avatar } from \"./avatar\";\nimport { CossistantLogo } from \"./cossistant-branding\";\nimport { TimelineMessageItem } from \"./timeline-message-item\";\n\nconst MESSAGE_ANIMATION = {\n\tinitial: { opacity: 0, y: 6 },\n\tanimate: { opacity: 1, y: 0 },\n\texit: { opacity: 0 },\n\ttransition: {\n\t\tduration: 0.1,\n\t\tease: [0.25, 0.46, 0.45, 0.94] as const, // easeOutCubic\n\t},\n} as const;\n\nconst SEEN_ANIMATION = {\n\tinitial: { opacity: 0 },\n\tanimate: { opacity: 1 },\n\ttransition: {\n\t\tduration: 0.1,\n\t\tease: \"easeOut\" as const,\n\t},\n} as const;\n\nexport type TimelineMessageGroupProps = {\n\titems: TimelineItem[];\n\tavailableAIAgents: AvailableAIAgent[];\n\tavailableHumanAgents: AvailableHumanAgent[];\n\tcurrentVisitorId?: string;\n\tseenByIds?: readonly string[];\n\tseenByNames?: readonly string[];\n};\n\nconst EMPTY_SEEN_BY_IDS: readonly string[] = Object.freeze([]);\nconst EMPTY_SEEN_BY_NAMES: readonly string[] = Object.freeze([]);\n\nexport const TimelineMessageGroup: React.FC<TimelineMessageGroupProps> = ({\n\titems,\n\tavailableAIAgents,\n\tavailableHumanAgents,\n\tcurrentVisitorId,\n\tseenByIds = EMPTY_SEEN_BY_IDS,\n\tseenByNames = EMPTY_SEEN_BY_NAMES,\n}) => {\n\t// Get agent info for the sender\n\tconst firstItem = items[0];\n\tconst humanAgent = availableHumanAgents.find(\n\t\t(agent) => agent.id === firstItem?.userId\n\t);\n\tconst aiAgent = availableAIAgents.find(\n\t\t(agent) => agent.id === firstItem?.aiAgentId\n\t);\n\n\tif (items.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst hasSeenIndicator = seenByIds.length > 0
|
|
1
|
+
{"version":3,"file":"timeline-message-group.js","names":["EMPTY_SEEN_BY_IDS: readonly string[]","EMPTY_SEEN_BY_NAMES: readonly string[]","TimelineMessageGroup: React.FC<TimelineMessageGroupProps>","PrimitiveTimelineItemGroup"],"sources":["../../../src/support/components/timeline-message-group.tsx"],"sourcesContent":["import type { AvailableAIAgent, AvailableHumanAgent } from \"@cossistant/types\";\nimport { SenderType } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { motion } from \"motion/react\";\nimport type React from \"react\";\nimport {\n\tTimelineItemGroup as PrimitiveTimelineItemGroup,\n\tTimelineItemGroupAvatar,\n\tTimelineItemGroupContent,\n\tTimelineItemGroupHeader,\n\tTimelineItemGroupSeenIndicator,\n} from \"../../primitives/timeline-item-group\";\nimport { cn } from \"../utils\";\nimport { Avatar } from \"./avatar\";\nimport { CossistantLogo } from \"./cossistant-branding\";\nimport { TimelineMessageItem } from \"./timeline-message-item\";\n\nconst MESSAGE_ANIMATION = {\n\tinitial: { opacity: 0, y: 6 },\n\tanimate: { opacity: 1, y: 0 },\n\texit: { opacity: 0 },\n\ttransition: {\n\t\tduration: 0.1,\n\t\tease: [0.25, 0.46, 0.45, 0.94] as const, // easeOutCubic\n\t},\n} as const;\n\nconst SEEN_ANIMATION = {\n\tinitial: { opacity: 0 },\n\tanimate: { opacity: 1 },\n\ttransition: {\n\t\tduration: 0.1,\n\t\tease: \"easeOut\" as const,\n\t},\n} as const;\n\nexport type TimelineMessageGroupProps = {\n\titems: TimelineItem[];\n\tavailableAIAgents: AvailableAIAgent[];\n\tavailableHumanAgents: AvailableHumanAgent[];\n\tcurrentVisitorId?: string;\n\tseenByIds?: readonly string[];\n\tseenByNames?: readonly string[];\n};\n\nconst EMPTY_SEEN_BY_IDS: readonly string[] = Object.freeze([]);\nconst EMPTY_SEEN_BY_NAMES: readonly string[] = Object.freeze([]);\n\nexport const TimelineMessageGroup: React.FC<TimelineMessageGroupProps> = ({\n\titems,\n\tavailableAIAgents,\n\tavailableHumanAgents,\n\tcurrentVisitorId,\n\tseenByIds = EMPTY_SEEN_BY_IDS,\n\tseenByNames = EMPTY_SEEN_BY_NAMES,\n}) => {\n\t// Get agent info for the sender\n\tconst firstItem = items[0];\n\tconst humanAgent = availableHumanAgents.find(\n\t\t(agent) => agent.id === firstItem?.userId\n\t);\n\tconst aiAgent = availableAIAgents.find(\n\t\t(agent) => agent.id === firstItem?.aiAgentId\n\t);\n\n\tif (items.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst hasSeenIndicator = seenByIds.length > 0;\n\n\treturn (\n\t\t<PrimitiveTimelineItemGroup\n\t\t\titems={items}\n\t\t\tseenByIds={seenByIds}\n\t\t\tviewerId={currentVisitorId}\n\t\t\tviewerType={SenderType.VISITOR}\n\t\t>\n\t\t\t{({\n\t\t\t\tisSentByViewer,\n\t\t\t\tisReceivedByViewer,\n\t\t\t\tisVisitor,\n\t\t\t\tisAI,\n\t\t\t\tisTeamMember,\n\t\t\t}) => (\n\t\t\t\t<div\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"flex w-full gap-2\",\n\t\t\t\t\t\t// Support widget POV: visitor messages are sent (right side)\n\t\t\t\t\t\t// Agent messages are received (left side)\n\t\t\t\t\t\tisSentByViewer && \"flex-row-reverse\",\n\t\t\t\t\t\tisReceivedByViewer && \"flex-row\"\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t{/* Avatar - only show for received messages (agents) */}\n\t\t\t\t\t{isReceivedByViewer && (\n\t\t\t\t\t\t<TimelineItemGroupAvatar className=\"flex flex-shrink-0 flex-col justify-end\">\n\t\t\t\t\t\t\t{isAI ? (\n\t\t\t\t\t\t\t\t<div className=\"flex size-6 items-center justify-center rounded-full bg-co-primary/10\">\n\t\t\t\t\t\t\t\t\t<CossistantLogo className=\"h-4 w-4 text-co-primary\" />\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<Avatar\n\t\t\t\t\t\t\t\t\tclassName=\"size-6\"\n\t\t\t\t\t\t\t\t\timage={humanAgent?.image}\n\t\t\t\t\t\t\t\t\tname={humanAgent?.name || \"Support\"}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</TimelineItemGroupAvatar>\n\t\t\t\t\t)}\n\n\t\t\t\t\t<TimelineItemGroupContent\n\t\t\t\t\t\tclassName={cn(\"flex flex-col gap-1\", isSentByViewer && \"items-end\")}\n\t\t\t\t\t>\n\t\t\t\t\t\t{/* Header - show sender name for received messages (agents) */}\n\t\t\t\t\t\t{isReceivedByViewer && (\n\t\t\t\t\t\t\t<TimelineItemGroupHeader className=\"px-1 text-co-muted-foreground text-xs\">\n\t\t\t\t\t\t\t\t{isAI\n\t\t\t\t\t\t\t\t\t? aiAgent?.name || \"AI Assistant\"\n\t\t\t\t\t\t\t\t\t: humanAgent?.name || \"Support\"}\n\t\t\t\t\t\t\t</TimelineItemGroupHeader>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{items.map((item, index) => (\n\t\t\t\t\t\t\t<motion.div key={item.id} {...MESSAGE_ANIMATION}>\n\t\t\t\t\t\t\t\t<TimelineMessageItem\n\t\t\t\t\t\t\t\t\tisLast={index === items.length - 1}\n\t\t\t\t\t\t\t\t\tisSentByViewer={isSentByViewer}\n\t\t\t\t\t\t\t\t\titem={item}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</motion.div>\n\t\t\t\t\t\t))}\n\n\t\t\t\t\t\t{isSentByViewer && (\n\t\t\t\t\t\t\t<div className={cn(\"\", hasSeenIndicator && \"mt-2\")}>\n\t\t\t\t\t\t\t\t<div className=\"min-h-[1.25rem]\">\n\t\t\t\t\t\t\t\t\t{hasSeenIndicator && (\n\t\t\t\t\t\t\t\t\t\t<motion.div key=\"seen-indicator\" {...SEEN_ANIMATION}>\n\t\t\t\t\t\t\t\t\t\t\t<TimelineItemGroupSeenIndicator\n\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"px-1 text-co-muted-foreground text-xs\"\n\t\t\t\t\t\t\t\t\t\t\t\tseenByIds={seenByIds}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t{() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\tseenByNames.length > 0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t? `Seen by ${seenByNames.join(\", \")}`\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t: \"Seen\"\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t</TimelineItemGroupSeenIndicator>\n\t\t\t\t\t\t\t\t\t\t</motion.div>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</TimelineItemGroupContent>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</PrimitiveTimelineItemGroup>\n\t);\n};\n\nTimelineMessageGroup.displayName = \"TimelineMessageGroup\";\n"],"mappings":";;;;;;;;;;AAiBA,MAAM,oBAAoB;CACzB,SAAS;EAAE,SAAS;EAAG,GAAG;EAAG;CAC7B,SAAS;EAAE,SAAS;EAAG,GAAG;EAAG;CAC7B,MAAM,EAAE,SAAS,GAAG;CACpB,YAAY;EACX,UAAU;EACV,MAAM;GAAC;GAAM;GAAM;GAAM;GAAK;EAC9B;CACD;AAED,MAAM,iBAAiB;CACtB,SAAS,EAAE,SAAS,GAAG;CACvB,SAAS,EAAE,SAAS,GAAG;CACvB,YAAY;EACX,UAAU;EACV,MAAM;EACN;CACD;AAWD,MAAMA,oBAAuC,OAAO,OAAO,EAAE,CAAC;AAC9D,MAAMC,sBAAyC,OAAO,OAAO,EAAE,CAAC;AAEhE,MAAaC,wBAA6D,EACzE,OACA,mBACA,sBACA,kBACA,YAAY,mBACZ,cAAc,0BACT;CAEL,MAAM,YAAY,MAAM;CACxB,MAAM,aAAa,qBAAqB,MACtC,UAAU,MAAM,OAAO,WAAW,OACnC;CACD,MAAM,UAAU,kBAAkB,MAChC,UAAU,MAAM,OAAO,WAAW,UACnC;AAED,KAAI,MAAM,WAAW,EACpB,QAAO;CAGR,MAAM,mBAAmB,UAAU,SAAS;AAE5C,QACC,oBAACC;EACO;EACI;EACX,UAAU;EACV,YAAY,WAAW;aAErB,EACD,gBACA,oBACA,WACA,MACA,mBAEA,qBAAC;GACA,WAAW,GACV,qBAGA,kBAAkB,oBAClB,sBAAsB,WACtB;cAGA,sBACA,oBAAC;IAAwB,WAAU;cACjC,OACA,oBAAC;KAAI,WAAU;eACd,oBAAC,kBAAe,WAAU,4BAA4B;MACjD,GAEN,oBAAC;KACA,WAAU;KACV,OAAO,YAAY;KACnB,MAAM,YAAY,QAAQ;MACzB;KAEsB,EAG3B,qBAAC;IACA,WAAW,GAAG,uBAAuB,kBAAkB,YAAY;;KAGlE,sBACA,oBAAC;MAAwB,WAAU;gBACjC,OACE,SAAS,QAAQ,iBACjB,YAAY,QAAQ;OACE;KAG1B,MAAM,KAAK,MAAM,UACjB,oBAAC,OAAO;MAAkB,GAAI;gBAC7B,oBAAC;OACA,QAAQ,UAAU,MAAM,SAAS;OACjB;OACV;QACL;QALc,KAAK,GAMT,CACZ;KAED,kBACA,oBAAC;MAAI,WAAW,GAAG,IAAI,oBAAoB,OAAO;gBACjD,oBAAC;OAAI,WAAU;iBACb,oBACA,oBAAC,OAAO;QAAyB,GAAI;kBACpC,oBAAC;SACA,WAAU;SACC;yBAGV,YAAY,SAAS,IAClB,WAAW,YAAY,KAAK,KAAK,KACjC;UAE4B;UAVlB,iBAWH;QAET;OACD;;KAEmB;IACtB;GAEqB;;AAI/B,qBAAqB,cAAc"}
|
|
@@ -17,9 +17,9 @@ function TimelineMessageItem({ item, isLast = false, isSentByViewer = false }) {
|
|
|
17
17
|
return /* @__PURE__ */ jsx("div", {
|
|
18
18
|
className: cn("flex w-full gap-2", isSentByViewerFinal && "flex-row-reverse", !isSentByViewerFinal && "flex-row"),
|
|
19
19
|
children: /* @__PURE__ */ jsxs("div", {
|
|
20
|
-
className: cn("flex w-full flex-1 flex-col gap-1", isSentByViewerFinal && "items-end"),
|
|
20
|
+
className: cn("flex w-full min-w-0 flex-1 flex-col gap-1", isSentByViewerFinal && "items-end"),
|
|
21
21
|
children: [/* @__PURE__ */ jsx(TimelineItemContent, {
|
|
22
|
-
className: cn("block w-
|
|
22
|
+
className: cn("block min-w-0 max-w-[300px] break-all rounded-lg px-3.5 py-2.5 text-sm", {
|
|
23
23
|
"bg-co-background-300 text-co-foreground dark:bg-co-background-600": !isSentByViewerFinal,
|
|
24
24
|
"bg-co-primary text-co-primary-foreground": isSentByViewerFinal,
|
|
25
25
|
"rounded-br-sm": isLast && isSentByViewerFinal,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-message-item.js","names":["PrimitiveTimelineItem"],"sources":["../../../src/support/components/timeline-message-item.tsx"],"sourcesContent":["import type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport type React from \"react\";\nimport {\n\tTimelineItem as PrimitiveTimelineItem,\n\tTimelineItemContent,\n\tTimelineItemTimestamp,\n} from \"../../primitives/timeline-item\";\nimport { useSupportText } from \"../text\";\nimport { cn } from \"../utils\";\n\nexport type TimelineMessageItemProps = {\n\titem: TimelineItem;\n\tisLast?: boolean;\n\tisSentByViewer?: boolean;\n};\n\n/**\n * Message bubble renderer that adapts layout depending on whether the visitor\n * or an agent sent the message.\n */\nexport function TimelineMessageItem({\n\titem,\n\tisLast = false,\n\tisSentByViewer = false,\n}: TimelineMessageItemProps): React.ReactElement {\n\tconst text = useSupportText();\n\treturn (\n\t\t<PrimitiveTimelineItem item={item}>\n\t\t\t{({ isAI, timestamp }) => {\n\t\t\t\t// isSentByViewer defaults to false, meaning messages are treated as received\n\t\t\t\t// (left side with background) unless explicitly marked as sent by viewer\n\t\t\t\tconst isSentByViewerFinal = isSentByViewer;\n\n\t\t\t\treturn (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"flex w-full gap-2\",\n\t\t\t\t\t\t\tisSentByViewerFinal && \"flex-row-reverse\",\n\t\t\t\t\t\t\t!isSentByViewerFinal && \"flex-row\"\n\t\t\t\t\t\t)}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\"flex w-full flex-1 flex-col gap-1\",\n\t\t\t\t\t\t\t\tisSentByViewerFinal && \"items-end\"\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<TimelineItemContent\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"block w-
|
|
1
|
+
{"version":3,"file":"timeline-message-item.js","names":["PrimitiveTimelineItem"],"sources":["../../../src/support/components/timeline-message-item.tsx"],"sourcesContent":["import type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport type React from \"react\";\nimport {\n\tTimelineItem as PrimitiveTimelineItem,\n\tTimelineItemContent,\n\tTimelineItemTimestamp,\n} from \"../../primitives/timeline-item\";\nimport { useSupportText } from \"../text\";\nimport { cn } from \"../utils\";\n\nexport type TimelineMessageItemProps = {\n\titem: TimelineItem;\n\tisLast?: boolean;\n\tisSentByViewer?: boolean;\n};\n\n/**\n * Message bubble renderer that adapts layout depending on whether the visitor\n * or an agent sent the message.\n */\nexport function TimelineMessageItem({\n\titem,\n\tisLast = false,\n\tisSentByViewer = false,\n}: TimelineMessageItemProps): React.ReactElement {\n\tconst text = useSupportText();\n\treturn (\n\t\t<PrimitiveTimelineItem item={item}>\n\t\t\t{({ isAI, timestamp }) => {\n\t\t\t\t// isSentByViewer defaults to false, meaning messages are treated as received\n\t\t\t\t// (left side with background) unless explicitly marked as sent by viewer\n\t\t\t\tconst isSentByViewerFinal = isSentByViewer;\n\n\t\t\t\treturn (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"flex w-full gap-2\",\n\t\t\t\t\t\t\tisSentByViewerFinal && \"flex-row-reverse\",\n\t\t\t\t\t\t\t!isSentByViewerFinal && \"flex-row\"\n\t\t\t\t\t\t)}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\"flex w-full min-w-0 flex-1 flex-col gap-1\",\n\t\t\t\t\t\t\t\tisSentByViewerFinal && \"items-end\"\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<TimelineItemContent\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"block min-w-0 max-w-[300px] break-all rounded-lg px-3.5 py-2.5 text-sm\",\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"bg-co-background-300 text-co-foreground dark:bg-co-background-600\":\n\t\t\t\t\t\t\t\t\t\t\t!isSentByViewerFinal,\n\t\t\t\t\t\t\t\t\t\t\"bg-co-primary text-co-primary-foreground\":\n\t\t\t\t\t\t\t\t\t\t\tisSentByViewerFinal,\n\t\t\t\t\t\t\t\t\t\t\"rounded-br-sm\": isLast && isSentByViewerFinal,\n\t\t\t\t\t\t\t\t\t\t\"rounded-bl-sm\": isLast && !isSentByViewerFinal,\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\trenderMarkdown\n\t\t\t\t\t\t\t\ttext={item.text}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t{isLast && (\n\t\t\t\t\t\t\t\t<TimelineItemTimestamp\n\t\t\t\t\t\t\t\t\tclassName=\"px-1 text-co-muted-foreground text-xs\"\n\t\t\t\t\t\t\t\t\ttimestamp={timestamp}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{() => (\n\t\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t\t{timestamp.toLocaleTimeString([], {\n\t\t\t\t\t\t\t\t\t\t\t\thour: \"2-digit\",\n\t\t\t\t\t\t\t\t\t\t\t\tminute: \"2-digit\",\n\t\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t\t\t{isAI &&\n\t\t\t\t\t\t\t\t\t\t\t\t` ${text(\"component.message.timestamp.aiIndicator\")}`}\n\t\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</TimelineItemTimestamp>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t);\n\t\t\t}}\n\t\t</PrimitiveTimelineItem>\n\t);\n}\n"],"mappings":";;;;;;;;;;AAoBA,SAAgB,oBAAoB,EACnC,MACA,SAAS,OACT,iBAAiB,SAC+B;CAChD,MAAM,OAAO,gBAAgB;AAC7B,QACC,oBAACA;EAA4B;aAC1B,EAAE,MAAM,gBAAgB;GAGzB,MAAM,sBAAsB;AAE5B,UACC,oBAAC;IACA,WAAW,GACV,qBACA,uBAAuB,oBACvB,CAAC,uBAAuB,WACxB;cAED,qBAAC;KACA,WAAW,GACV,6CACA,uBAAuB,YACvB;gBAED,oBAAC;MACA,WAAW,GACV,0EACA;OACC,qEACC,CAAC;OACF,4CACC;OACD,iBAAiB,UAAU;OAC3B,iBAAiB,UAAU,CAAC;OAC5B,CACD;MACD;MACA,MAAM,KAAK;OACV,EACD,UACA,oBAAC;MACA,WAAU;MACC;sBAGV,4CACE,UAAU,mBAAmB,EAAE,EAAE;OACjC,MAAM;OACN,QAAQ;OACR,CAAC,EACD,QACA,IAAI,KAAK,0CAA0C,MAClD;OAEmB;MAEpB;KACD;;GAGe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typing-indicator.d.ts","names":[],"sources":["../../../src/support/components/typing-indicator.tsx"],"sourcesContent":[],"mappings":";;;;KAKY,qBAAA;KAEA,iBAAA;EAFA,EAAA,EAAA,MAAA;EAEA,IAAA,EAEL,qBAFsB;AAK7B,CAAA;AAAwD,KAA5C,oBAAA,GAAuB,OAAA,CAAM,cAAe,CAAA,cAAA,CAAA,GAAA;EAArB,YAAM,EAC1B,iBAD0B,EAAA;EAC1B,iBAAA,CAAA,EACM,gBADN,EAAA;EACM,oBAAA,CAAA,EACG,mBADH,EAAA;EACG,WAAA,CAAA,EAAA,OAAA;CAAmB;AAI9B,cAAA,YAAgB,EAAA,CAAA;EAAA;
|
|
1
|
+
{"version":3,"file":"typing-indicator.d.ts","names":[],"sources":["../../../src/support/components/typing-indicator.tsx"],"sourcesContent":[],"mappings":";;;;KAKY,qBAAA;KAEA,iBAAA;EAFA,EAAA,EAAA,MAAA;EAEA,IAAA,EAEL,qBAFsB;AAK7B,CAAA;AAAwD,KAA5C,oBAAA,GAAuB,OAAA,CAAM,cAAe,CAAA,cAAA,CAAA,GAAA;EAArB,YAAM,EAC1B,iBAD0B,EAAA;EAC1B,iBAAA,CAAA,EACM,gBADN,EAAA;EACM,oBAAA,CAAA,EACG,mBADH,EAAA;EACG,WAAA,CAAA,EAAA,OAAA;CAAmB;AAI9B,cAAA,YAAgB,EAAA,CAAA;EAAA;CAInB,EAAA;EAuBG,SAAA,CAAA,EAAA,MAuDZ;CAvD2B,EAAA,GAvBxB,OAAA,CAAM,YAuBkB;AAAA,cAAf,eAAe,EAAA,OAAA,CAAA,yBAAA,CAAA,OAAA,CAAA,cAAA,CAAA,cAAA,CAAA,GAAA;EAjCb,YAAA,EAAA,iBAAA,EAAA;EACM,iBAAA,CAAA,EAAA,gBAAA,EAAA;EACG,oBAAA,CAAA,EAAA,mBAAA,EAAA"}
|
package/support/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { SupportLocale, SupportTextContentOverrides } from "./text/locales/keys.js";
|
|
2
|
-
import {
|
|
3
|
-
import "../primitives/index.js";
|
|
2
|
+
import { CustomPage } from "./router.js";
|
|
4
3
|
import { Text, useSupportText } from "./text/index.js";
|
|
5
4
|
import { BubbleSlotProps, ContainerSlotProps, RouterSlotProps } from "./types.js";
|
|
6
5
|
import { CoButton } from "./components/button.js";
|
|
@@ -34,6 +33,7 @@ type SupportProps<Locale extends string = SupportLocale> = {
|
|
|
34
33
|
bubble?: string;
|
|
35
34
|
container?: string;
|
|
36
35
|
};
|
|
36
|
+
customPages?: CustomPage[];
|
|
37
37
|
children?: React.ReactNode;
|
|
38
38
|
};
|
|
39
39
|
/**
|
|
@@ -44,10 +44,14 @@ type SupportProps<Locale extends string = SupportLocale> = {
|
|
|
44
44
|
* <Support />
|
|
45
45
|
*
|
|
46
46
|
* @example
|
|
47
|
-
* // With customization
|
|
48
|
-
* <Support
|
|
49
|
-
*
|
|
50
|
-
*
|
|
47
|
+
* // With customization and custom pages
|
|
48
|
+
* <Support
|
|
49
|
+
* theme="dark"
|
|
50
|
+
* classNames={{ bubble: "bg-purple-600" }}
|
|
51
|
+
* customPages={[
|
|
52
|
+
* { name: "FAQ", component: FAQPage }
|
|
53
|
+
* ]}
|
|
54
|
+
* />
|
|
51
55
|
*/
|
|
52
56
|
declare function Support<Locale extends string = SupportLocale>({
|
|
53
57
|
className,
|
|
@@ -62,8 +66,9 @@ declare function Support<Locale extends string = SupportLocale>({
|
|
|
62
66
|
theme,
|
|
63
67
|
slots,
|
|
64
68
|
classNames,
|
|
69
|
+
customPages,
|
|
65
70
|
children
|
|
66
71
|
}: SupportProps<Locale>): ReactElement | null;
|
|
67
72
|
//#endregion
|
|
68
|
-
export { type BubbleSlotProps, CoButton as Button, type ContainerSlotProps, type
|
|
73
|
+
export { type BubbleSlotProps, CoButton as Button, type ContainerSlotProps, type CustomPage, type DefaultRoutes, Header, type NavigationState, type RouteRegistry, type RouterSlotProps, Support, Support as default, type SupportLocale, type SupportPage, SupportProps, type SupportTextContentOverrides, Text, type WebSocketContextValue, WebSocketProvider, useSupportConfig, useSupportNavigation, useSupportStore, useSupportText, useWebSocket };
|
|
69
74
|
//# sourceMappingURL=index.d.ts.map
|
package/support/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/support/index.tsx"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/support/index.tsx"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;KAmBY,qCAAqC;;;;;;oBAO9B;;EAPP,MAAA,CAAA,EASF,MATc;EAAyB,OAAA,CAAA,EAUtC,2BAVsC,CAUV,MAVU,CAAA;EAO9B,KAAA,CAAA,EAAA,OAAA,GAAA,MAAA;EAET,KAAA,CAAA,EAAA;IAC6B,MAAA,CAAA,EAO5B,KAAA,CAAM,aAPsB,CAOR,eAPQ,CAAA;IAA5B,SAAA,CAAA,EAQG,KAAA,CAAM,aART,CAQuB,kBARvB,CAAA;IAOoB,MAAA,CAAA,EAEpB,KAAA,CAAM,aAFc,CAEA,eAFA,CAAA;EAApB,CAAA;EACuB,UAAA,CAAA,EAAA;IAAd,IAAA,CAAA,EAAA,MAAA;IACW,MAAA,CAAA,EAAA,MAAA;IAAd,SAAA,CAAA,EAAA,MAAA;EAWF,CAAA;EAEH,WAAM,CAAA,EAFH,UAEG,EAAA;EAAS,QAAA,CAAA,EAAf,KAAA,CAAM,SAAS;AAoB3B,CAAA;;;;;;;;;;;;;;;;;;AAe0B,iBAfV,OAeU,CAAA,eAAA,MAAA,GAfsB,aAetB,CAAA,CAAA;EAAA,SAAA;EAAA,QAAA;EAAA,KAAA;EAAA,WAAA;EAAA,YAAA;EAAA,eAAA;EAAA,WAAA;EAAA,MAAA;EAAA,OAAA;EAAA,KAAA;EAAA,KAAA;EAAA,UAAA;EAAA,WAAA;EAAA;AAAA,CAAA,EAAvB,YAAuB,CAAV,MAAU,CAAA,CAAA,EAAA,YAAA,GAAA,IAAA"}
|
package/support/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import "./support.js";
|
|
2
|
+
import { SupportRealtimeProvider } from "../realtime/support-provider.js";
|
|
2
3
|
import { SupportConfig } from "../support-config.js";
|
|
3
|
-
import { PageRegistryProvider } from "../primitives/page-registry.js";
|
|
4
|
-
import { Page } from "../primitives/page.js";
|
|
5
4
|
import { initializeSupportStore, useSupportConfig, useSupportNavigation, useSupportStore } from "./store/support-store.js";
|
|
6
|
-
import { SupportRealtimeProvider } from "../realtime/support-provider.js";
|
|
7
5
|
import { CoButton } from "./components/button.js";
|
|
8
6
|
import { Header } from "./components/header.js";
|
|
9
7
|
import { SupportTextProvider, Text, useSupportText } from "./text/index.js";
|
|
@@ -23,44 +21,45 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
23
21
|
* <Support />
|
|
24
22
|
*
|
|
25
23
|
* @example
|
|
26
|
-
* // With customization
|
|
27
|
-
* <Support
|
|
28
|
-
*
|
|
29
|
-
*
|
|
24
|
+
* // With customization and custom pages
|
|
25
|
+
* <Support
|
|
26
|
+
* theme="dark"
|
|
27
|
+
* classNames={{ bubble: "bg-purple-600" }}
|
|
28
|
+
* customPages={[
|
|
29
|
+
* { name: "FAQ", component: FAQPage }
|
|
30
|
+
* ]}
|
|
31
|
+
* />
|
|
30
32
|
*/
|
|
31
|
-
function Support({ className, position = "bottom", align = "right", positioning = "fixed", quickOptions, defaultMessages, defaultOpen, locale, content, theme, slots, classNames, children }) {
|
|
33
|
+
function Support({ className, position = "bottom", align = "right", positioning = "fixed", quickOptions, defaultMessages, defaultOpen, locale, content, theme, slots, classNames, customPages, children }) {
|
|
32
34
|
const { website } = useSupport();
|
|
33
35
|
const isVisitorBlocked = website?.visitor?.isBlocked ?? false;
|
|
34
36
|
React.useEffect(() => {
|
|
35
37
|
if (defaultOpen !== void 0) initializeSupportStore({ defaultOpen });
|
|
36
38
|
}, [defaultOpen]);
|
|
37
39
|
if (!website || isVisitorBlocked) return null;
|
|
38
|
-
return /* @__PURE__ */
|
|
40
|
+
return /* @__PURE__ */ jsxs(ThemeWrapper, {
|
|
39
41
|
theme,
|
|
40
|
-
children: /* @__PURE__ */
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
children
|
|
53
|
-
})
|
|
54
|
-
}) }),
|
|
55
|
-
/* @__PURE__ */ jsx(SupportConfig, {
|
|
56
|
-
defaultMessages,
|
|
57
|
-
quickOptions
|
|
42
|
+
children: [/* @__PURE__ */ jsx(SupportRealtimeProvider, { children: /* @__PURE__ */ jsx(SupportTextProvider, {
|
|
43
|
+
content,
|
|
44
|
+
locale,
|
|
45
|
+
children: /* @__PURE__ */ jsx(SupportContent, {
|
|
46
|
+
align,
|
|
47
|
+
className,
|
|
48
|
+
classNames,
|
|
49
|
+
customPages,
|
|
50
|
+
position,
|
|
51
|
+
positioning,
|
|
52
|
+
slots,
|
|
53
|
+
children
|
|
58
54
|
})
|
|
59
|
-
|
|
55
|
+
}) }), /* @__PURE__ */ jsx(SupportConfig, {
|
|
56
|
+
defaultMessages,
|
|
57
|
+
quickOptions
|
|
58
|
+
})]
|
|
60
59
|
});
|
|
61
60
|
}
|
|
62
61
|
var support_default = Support;
|
|
63
62
|
|
|
64
63
|
//#endregion
|
|
65
|
-
export { CoButton as Button, Header,
|
|
64
|
+
export { CoButton as Button, Header, Support, Text, WebSocketProvider, support_default as default, useSupportConfig, useSupportNavigation, useSupportStore, useSupportText, useWebSocket };
|
|
66
65
|
//# sourceMappingURL=index.js.map
|
package/support/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/support/index.tsx"],"sourcesContent":["import \"./support.css\";\n\nimport type { DefaultMessage } from \"@cossistant/types\";\nimport React, { type ReactElement } from \"react\";\nimport {
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/support/index.tsx"],"sourcesContent":["import \"./support.css\";\n\nimport type { DefaultMessage } from \"@cossistant/types\";\nimport React, { type ReactElement } from \"react\";\nimport { useSupport } from \"../provider\";\nimport { SupportRealtimeProvider } from \"../realtime\";\nimport { SupportConfig } from \"../support-config\";\nimport { SupportContent } from \"./components/support-content\";\nimport { ThemeWrapper } from \"./components/theme-wrapper\";\nimport type { CustomPage } from \"./router\";\nimport { initializeSupportStore } from \"./store/support-store\";\nimport type { SupportLocale, SupportTextContentOverrides } from \"./text\";\nimport { SupportTextProvider } from \"./text\";\nimport type {\n\tBubbleSlotProps,\n\tContainerSlotProps,\n\tRouterSlotProps,\n} from \"./types\";\n\nexport type SupportProps<Locale extends string = SupportLocale> = {\n\t// Existing props\n\tclassName?: string;\n\tposition?: \"top\" | \"bottom\";\n\talign?: \"right\" | \"left\";\n\tpositioning?: \"fixed\" | \"absolute\";\n\tquickOptions?: string[];\n\tdefaultMessages?: DefaultMessage[];\n\tdefaultOpen?: boolean;\n\tlocale?: Locale;\n\tcontent?: SupportTextContentOverrides<Locale>;\n\n\t// NEW: Theme control\n\ttheme?: \"light\" | \"dark\";\n\n\t// NEW: Slot customization\n\tslots?: {\n\t\tbubble?: React.ComponentType<BubbleSlotProps>;\n\t\tcontainer?: React.ComponentType<ContainerSlotProps>;\n\t\trouter?: React.ComponentType<RouterSlotProps>;\n\t};\n\n\t// NEW: Granular className overrides\n\tclassNames?: {\n\t\troot?: string;\n\t\tbubble?: string;\n\t\tcontainer?: string;\n\t};\n\n\t// NEW: Type-safe custom pages\n\tcustomPages?: CustomPage[];\n\n\tchildren?: React.ReactNode;\n};\n\n/**\n * Complete support widget with chat, routing, and real-time features.\n *\n * @example\n * // Zero config\n * <Support />\n *\n * @example\n * // With customization and custom pages\n * <Support\n * theme=\"dark\"\n * classNames={{ bubble: \"bg-purple-600\" }}\n * customPages={[\n * { name: \"FAQ\", component: FAQPage }\n * ]}\n * />\n */\nexport function Support<Locale extends string = SupportLocale>({\n\tclassName,\n\tposition = \"bottom\",\n\talign = \"right\",\n\tpositioning = \"fixed\",\n\tquickOptions,\n\tdefaultMessages,\n\tdefaultOpen,\n\tlocale,\n\tcontent,\n\ttheme,\n\tslots,\n\tclassNames,\n\tcustomPages,\n\tchildren,\n}: SupportProps<Locale>): ReactElement | null {\n\tconst { website } = useSupport();\n\tconst isVisitorBlocked = website?.visitor?.isBlocked ?? false;\n\n\t// Initialize support store with defaultOpen when component mounts or prop changes\n\tReact.useEffect(() => {\n\t\tif (defaultOpen !== undefined) {\n\t\t\tinitializeSupportStore({ defaultOpen });\n\t\t}\n\t}, [defaultOpen]);\n\n\tif (!website || isVisitorBlocked) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<ThemeWrapper theme={theme}>\n\t\t\t<SupportRealtimeProvider>\n\t\t\t\t<SupportTextProvider content={content} locale={locale}>\n\t\t\t\t\t<SupportContent\n\t\t\t\t\t\talign={align}\n\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t\tclassNames={classNames}\n\t\t\t\t\t\tcustomPages={customPages}\n\t\t\t\t\t\tposition={position}\n\t\t\t\t\t\tpositioning={positioning}\n\t\t\t\t\t\tslots={slots}\n\t\t\t\t\t>\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</SupportContent>\n\t\t\t\t</SupportTextProvider>\n\t\t\t</SupportRealtimeProvider>\n\t\t\t<SupportConfig\n\t\t\t\tdefaultMessages={defaultMessages}\n\t\t\t\tquickOptions={quickOptions}\n\t\t\t/>\n\t\t</ThemeWrapper>\n\t);\n}\n\nexport default Support;\n\n// Type exports from core\nexport type {\n\tDefaultRoutes,\n\tNavigationState,\n\tRouteRegistry,\n\tSupportPage,\n} from \"@cossistant/core\";\nexport { CoButton as Button } from \"./components/button\";\n// UI components for building custom pages\nexport { Header } from \"./components/header\";\n// WebSocket context\nexport type { WebSocketContextValue } from \"./context/websocket\";\nexport { useWebSocket, WebSocketProvider } from \"./context/websocket\";\n// Custom page type for type-safe routing\nexport type { CustomPage } from \"./router\";\n// Navigation hooks and store\nexport {\n\tuseSupportConfig,\n\tuseSupportNavigation,\n\tuseSupportStore,\n} from \"./store\";\n// Text and localization\nexport type { SupportLocale, SupportTextContentOverrides } from \"./text\";\nexport { Text, useSupportText } from \"./text\";\n\n// Slot prop types\nexport type {\n\tBubbleSlotProps,\n\tContainerSlotProps,\n\tRouterSlotProps,\n} from \"./types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,SAAgB,QAA+C,EAC9D,WACA,WAAW,UACX,QAAQ,SACR,cAAc,SACd,cACA,iBACA,aACA,QACA,SACA,OACA,OACA,YACA,aACA,YAC6C;CAC7C,MAAM,EAAE,YAAY,YAAY;CAChC,MAAM,mBAAmB,SAAS,SAAS,aAAa;AAGxD,OAAM,gBAAgB;AACrB,MAAI,gBAAgB,OACnB,wBAAuB,EAAE,aAAa,CAAC;IAEtC,CAAC,YAAY,CAAC;AAEjB,KAAI,CAAC,WAAW,iBACf,QAAO;AAGR,QACC,qBAAC;EAAoB;aACpB,oBAAC,qCACA,oBAAC;GAA6B;GAAiB;aAC9C,oBAAC;IACO;IACI;IACC;IACC;IACH;IACG;IACN;IAEN;KACe;IACI,GACG,EAC1B,oBAAC;GACiB;GACH;IACb;GACY;;AAIjB,sBAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation.d.ts","names":[],"sources":["../../../src/support/pages/conversation.tsx"],"sourcesContent":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"conversation.d.ts","names":[],"sources":["../../../src/support/pages/conversation.tsx"],"sourcesContent":[],"mappings":";;;;KAeK,qBAAA;;AAbiE;AAqCjD;EAQR,MAAA,CAAA,EAAA;;;;;;;;;;;;YAdH;;;;UAMD;;;;;KAMJ,yBAAA,WAAoC,0BAA0B;cAEtD,kBAAkB"}
|
|
@@ -4,11 +4,12 @@ import { Header } from "../components/header.js";
|
|
|
4
4
|
import { Text, useSupportText } from "../text/index.js";
|
|
5
5
|
import { useConversation } from "../../hooks/use-conversation.js";
|
|
6
6
|
import { useConversationPage } from "../../hooks/use-conversation-page.js";
|
|
7
|
+
import { useNewMessageSound } from "../../hooks/use-new-message-sound.js";
|
|
7
8
|
import { ConversationTimelineList } from "../components/conversation-timeline.js";
|
|
8
9
|
import { MultimodalInput } from "../components/multimodal-input.js";
|
|
9
10
|
import { IdentificationTimelineTool } from "../components/timeline-identification-tool.js";
|
|
10
11
|
import { useSupport } from "../../provider.js";
|
|
11
|
-
import { useMemo } from "react";
|
|
12
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
12
13
|
import { ConversationStatus } from "@cossistant/types";
|
|
13
14
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
15
|
|
|
@@ -21,6 +22,11 @@ const ConversationPage = ({ params, conversationId: legacyConversationId, initia
|
|
|
21
22
|
const { navigate, replace, goBack, canGoBack } = useSupportNavigation();
|
|
22
23
|
const { isOpen } = useSupportConfig();
|
|
23
24
|
const text = useSupportText();
|
|
25
|
+
const playNewMessageSound = useNewMessageSound({
|
|
26
|
+
volume: .7,
|
|
27
|
+
playbackRate: 1
|
|
28
|
+
});
|
|
29
|
+
const previousItemsRef = useRef([]);
|
|
24
30
|
const timelineTools = useMemo(() => ({ identification: { component: IdentificationTimelineTool } }), []);
|
|
25
31
|
const conversation = useConversationPage({
|
|
26
32
|
conversationId: initialConversationId,
|
|
@@ -40,6 +46,18 @@ const ConversationPage = ({ params, conversationId: legacyConversationId, initia
|
|
|
40
46
|
if (canGoBack) goBack();
|
|
41
47
|
else navigate({ page: "HOME" });
|
|
42
48
|
};
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const currentItems = conversation.items;
|
|
51
|
+
const previousItems = previousItemsRef.current;
|
|
52
|
+
if (currentItems.length > previousItems.length) {
|
|
53
|
+
const newItems = currentItems.slice(previousItems.length);
|
|
54
|
+
for (const item of newItems) if (item.type === "message" && !item.visitorId) {
|
|
55
|
+
playNewMessageSound();
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
previousItemsRef.current = currentItems;
|
|
60
|
+
}, [conversation.items, playNewMessageSound]);
|
|
43
61
|
return /* @__PURE__ */ jsxs("div", {
|
|
44
62
|
className: "flex h-full flex-col gap-0 overflow-hidden",
|
|
45
63
|
children: [
|