@cossistant/react 0.0.19 → 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.
Files changed (101) hide show
  1. package/conversation.d.ts +28 -0
  2. package/conversation.d.ts.map +1 -1
  3. package/hooks/index.d.ts +4 -1
  4. package/hooks/index.js +4 -1
  5. package/hooks/private/use-default-messages.js +1 -1
  6. package/hooks/private/use-default-messages.js.map +1 -1
  7. package/hooks/private/use-grouped-messages.d.ts.map +1 -1
  8. package/hooks/private/use-grouped-messages.js +4 -18
  9. package/hooks/private/use-grouped-messages.js.map +1 -1
  10. package/hooks/private/use-rest-client.js +1 -1
  11. package/hooks/private/use-rest-client.js.map +1 -1
  12. package/hooks/private/use-visitor-typing-reporter.js +3 -3
  13. package/hooks/private/use-visitor-typing-reporter.js.map +1 -1
  14. package/hooks/use-conversation-auto-seen.d.ts +1 -1
  15. package/hooks/use-conversation-auto-seen.js +30 -46
  16. package/hooks/use-conversation-auto-seen.js.map +1 -1
  17. package/hooks/use-conversation-page.js +1 -1
  18. package/hooks/use-conversation-page.js.map +1 -1
  19. package/hooks/use-conversation-seen.d.ts.map +1 -1
  20. package/hooks/use-conversation-seen.js +7 -3
  21. package/hooks/use-conversation-seen.js.map +1 -1
  22. package/hooks/use-new-message-sound.d.ts +23 -0
  23. package/hooks/use-new-message-sound.d.ts.map +1 -0
  24. package/hooks/use-new-message-sound.js +34 -0
  25. package/hooks/use-new-message-sound.js.map +1 -0
  26. package/hooks/use-send-message.js +1 -1
  27. package/hooks/use-send-message.js.map +1 -1
  28. package/hooks/use-sound-effect.d.ts +30 -0
  29. package/hooks/use-sound-effect.d.ts.map +1 -0
  30. package/hooks/use-sound-effect.js +104 -0
  31. package/hooks/use-sound-effect.js.map +1 -0
  32. package/hooks/use-typing-sound.d.ts +18 -0
  33. package/hooks/use-typing-sound.d.ts.map +1 -0
  34. package/hooks/use-typing-sound.js +38 -0
  35. package/hooks/use-typing-sound.js.map +1 -0
  36. package/index.d.ts +5 -2
  37. package/index.js +8 -6
  38. package/package.json +3 -3
  39. package/primitives/avatar/image.d.ts +1 -1
  40. package/primitives/bubble.js +1 -1
  41. package/primitives/index.d.ts +3 -5
  42. package/primitives/index.js +3 -9
  43. package/primitives/index.parts.d.ts +2 -4
  44. package/primitives/index.parts.js +2 -4
  45. package/primitives/router.d.ts +19 -20
  46. package/primitives/router.d.ts.map +1 -1
  47. package/primitives/router.js +17 -11
  48. package/primitives/router.js.map +1 -1
  49. package/realtime/index.js +1 -1
  50. package/realtime/provider.d.ts +1 -0
  51. package/realtime/provider.d.ts.map +1 -1
  52. package/realtime/provider.js +59 -10
  53. package/realtime/provider.js.map +1 -1
  54. package/realtime-events.d.ts +14 -0
  55. package/realtime-events.d.ts.map +1 -1
  56. package/schemas3.d.ts +7 -0
  57. package/schemas3.d.ts.map +1 -1
  58. package/support/components/bubble.d.ts.map +1 -1
  59. package/support/components/bubble.js +27 -4
  60. package/support/components/bubble.js.map +1 -1
  61. package/support/components/conversation-event.js +1 -1
  62. package/support/components/conversation-event.js.map +1 -1
  63. package/support/components/conversation-timeline.d.ts.map +1 -1
  64. package/support/components/conversation-timeline.js +5 -0
  65. package/support/components/conversation-timeline.js.map +1 -1
  66. package/support/components/support-content.d.ts +2 -0
  67. package/support/components/support-content.d.ts.map +1 -1
  68. package/support/components/support-content.js +5 -2
  69. package/support/components/support-content.js.map +1 -1
  70. package/support/components/timeline-message-group.js +2 -2
  71. package/support/components/timeline-message-group.js.map +1 -1
  72. package/support/components/timeline-message-item.js +2 -2
  73. package/support/components/timeline-message-item.js.map +1 -1
  74. package/support/components/typing-indicator.d.ts.map +1 -1
  75. package/support/index.d.ts +12 -7
  76. package/support/index.d.ts.map +1 -1
  77. package/support/index.js +28 -29
  78. package/support/index.js.map +1 -1
  79. package/support/pages/conversation.d.ts.map +1 -1
  80. package/support/pages/conversation.js +19 -1
  81. package/support/pages/conversation.js.map +1 -1
  82. package/support/router.d.ts +19 -9
  83. package/support/router.d.ts.map +1 -1
  84. package/support/router.js +31 -30
  85. package/support/router.js.map +1 -1
  86. package/support/text/runtime.js +1 -1
  87. package/support/text/runtime.js.map +1 -1
  88. package/support/utils/time.d.ts +1 -0
  89. package/support/utils/time.d.ts.map +1 -1
  90. package/support/utils/time.js +2 -0
  91. package/support/utils/time.js.map +1 -1
  92. package/timeline-item.d.ts +14 -0
  93. package/timeline-item.d.ts.map +1 -1
  94. package/primitives/page-registry.d.ts +0 -30
  95. package/primitives/page-registry.d.ts.map +0 -1
  96. package/primitives/page-registry.js +0 -45
  97. package/primitives/page-registry.js.map +0 -1
  98. package/primitives/page.d.ts +0 -21
  99. package/primitives/page.d.ts.map +0 -1
  100. package/primitives/page.js +0 -18
  101. package/primitives/page.js.map +0 -1
@@ -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 py-4\"\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
+ {"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":";;;;;KAiCY,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"}
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":";;;;;;;;;;AAkBA,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;CAED,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
+ {"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":";;;;KAWK,mBAAA;;EAAA,QAAA,CAAA,EAAA,KAAA,GAAA,QAAmB;EAKf,KAAA,CAAA,EAAA,OAAA,GAAA,MAAA;EACK,WAAA,CAAA,EAAA,OAAA,GAAA,UAAA;EACF,KAAM,CAAA,EAFT,YAES,CAAA,OAAA,CAAA;EAAS,UAAA,CAAA,EADb,YACa,CAAA,YAAA,CAAA;EAMd,QAAA,CAAA,EAND,KAAA,CAAM,SAwDjB;;;;;cAlDY,gBAAgB,KAAA,CAAM,GAAG"}
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, { children })
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":";;;;;;;;;;;;;;AAwBA,MAAaA,kBAAiD,EAC7D,WACA,WAAW,UACX,QAAQ,SACR,cAAc,SACd,QAAQ,EAAE,EACV,aAAa,EAAE,EACf,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,mBAAiB,WAA2B;IACzB;GACT"}
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 && seenByNames.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 && seenByNames.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{() => `Seen by ${seenByNames.join(\", \")}`}\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,KAAK,YAAY,SAAS;AAEtE,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;yBAEJ,WAAW,YAAY,KAAK,KAAK;UACR;UANlB,iBAOH;QAET;OACD;;KAEmB;IACtB;GAEqB;;AAI/B,qBAAqB,cAAc"}
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-max max-w-[300px] rounded-lg px-3.5 py-2.5 text-sm", {
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-max max-w-[300px] 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,qCACA,uBAAuB,YACvB;gBAED,oBAAC;MACA,WAAW,GACV,8DACA;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
+ {"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;CAIzB,EAAM;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"}
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"}
@@ -1,6 +1,5 @@
1
1
  import { SupportLocale, SupportTextContentOverrides } from "./text/locales/keys.js";
2
- import { Page, PageProps } from "../primitives/page.js";
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 theme="dark" classNames={{ bubble: "bg-purple-600" }}>
49
- * <Page name="FAQ" component={FAQPage} />
50
- * </Support>
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 DefaultRoutes, Header, type NavigationState, Page, type PageProps, type RouteRegistry, type RouterSlotProps, Support, Support as default, type SupportLocale, type SupportPage, SupportProps, type SupportTextContentOverrides, Text, type WebSocketContextValue, WebSocketProvider, useSupportConfig, useSupportNavigation, useSupportStore, useSupportText, useWebSocket };
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/support/index.tsx"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;KAmBY,qCAAqC;;;;;;oBAO9B;;WAET;YACC,4BAA4B;EAV3B,KAAA,CAAA,EAAA,OAAY,GAAA,MAAA;EAAyB,KAAA,CAAA,EAAA;IAO9B,MAAA,CAAA,EAUR,KAAA,CAAM,aAVE,CAUY,eAVZ,CAAA;IAET,SAAA,CAAA,EASI,KAAA,CAAM,aATV,CASwB,kBATxB,CAAA;IAC6B,MAAA,CAAA,EAS5B,KAAA,CAAM,aATsB,CASR,eATQ,CAAA;EAA5B,CAAA;EAOoB,UAAA,CAAA,EAAA;IAAd,IAAA,CAAA,EAAA,MAAA;IACiB,MAAA,CAAA,EAAA,MAAA;IAAd,SAAA,CAAA,EAAA,MAAA;EACW,CAAA;EAApB,QAAM,CAAA,EAWL,KAAA,CAAM,SAXD;CAWL;;AAgBZ;;;;;;;;;;;;AAWC,iBAXe,OAWf,CAAA,eAAA,MAAA,GAX+C,aAW/C,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;AAAA,CAAA,EAGE,YAHF,CAGe,MAHf,CAAA,CAAA,EAGyB,YAHzB,GAAA,IAAA"}
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 theme="dark" classNames={{ bubble: "bg-purple-600" }}>
28
- * <Page name="FAQ" component={FAQPage} />
29
- * </Support>
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__ */ jsx(ThemeWrapper, {
40
+ return /* @__PURE__ */ jsxs(ThemeWrapper, {
39
41
  theme,
40
- children: /* @__PURE__ */ jsxs(PageRegistryProvider, { children: [
41
- children,
42
- /* @__PURE__ */ jsx(SupportRealtimeProvider, { children: /* @__PURE__ */ jsx(SupportTextProvider, {
43
- content,
44
- locale,
45
- children: /* @__PURE__ */ jsx(SupportContent, {
46
- align,
47
- className,
48
- classNames,
49
- position,
50
- positioning,
51
- slots,
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, Page, Support, Text, WebSocketProvider, support_default as default, useSupportConfig, useSupportNavigation, useSupportStore, useSupportText, useWebSocket };
64
+ export { CoButton as Button, Header, Support, Text, WebSocketProvider, support_default as default, useSupportConfig, useSupportNavigation, useSupportStore, useSupportText, useWebSocket };
66
65
  //# sourceMappingURL=index.js.map
@@ -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 { PageRegistryProvider } from \"../primitives\";\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 { 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: Allow declarative children (Page components)\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\n * <Support theme=\"dark\" classNames={{ bubble: \"bg-purple-600\" }}>\n * <Page name=\"FAQ\" component={FAQPage} />\n * </Support>\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\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<PageRegistryProvider>\n\t\t\t\t{children}\n\t\t\t\t<SupportRealtimeProvider>\n\t\t\t\t\t<SupportTextProvider content={content} locale={locale}>\n\t\t\t\t\t\t<SupportContent\n\t\t\t\t\t\t\talign={align}\n\t\t\t\t\t\t\tclassName={className}\n\t\t\t\t\t\t\tclassNames={classNames}\n\t\t\t\t\t\t\tposition={position}\n\t\t\t\t\t\t\tpositioning={positioning}\n\t\t\t\t\t\t\tslots={slots}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t</SupportContent>\n\t\t\t\t\t</SupportTextProvider>\n\t\t\t\t</SupportRealtimeProvider>\n\t\t\t\t<SupportConfig\n\t\t\t\t\tdefaultMessages={defaultMessages}\n\t\t\t\t\tquickOptions={quickOptions}\n\t\t\t\t/>\n\t\t\t</PageRegistryProvider>\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 type { PageProps } from \"../primitives\";\n// Page component for declarative routing (re-exported from primitives)\nexport { Page } from \"../primitives\";\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// 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEA,SAAgB,QAA+C,EAC9D,WACA,WAAW,UACX,QAAQ,SACR,cAAc,SACd,cACA,iBACA,aACA,QACA,SACA,OACA,OACA,YACA,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,oBAAC;EAAoB;YACpB,qBAAC;GACC;GACD,oBAAC,qCACA,oBAAC;IAA6B;IAAiB;cAC9C,oBAAC;KACO;KACI;KACC;KACF;KACG;KACN;KAEN;MACe;KACI,GACG;GAC1B,oBAAC;IACiB;IACH;KACb;MACoB;GACT;;AAIjB,sBAAe"}
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":";;;;KAcK,qBAAA;;AAZ8C;AAoC9B;EAQR,MAAA,CAAA,EAAA;;;;;;;;;;;;YAdH;;;;UAMD;;;;;KAMJ,yBAAA,WAAoC,0BAA0B;cAEtD,kBAAkB"}
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: [
@@ -1 +1 @@
1
- {"version":3,"file":"conversation.js","names":["ConversationPage: ConversationPageComponent"],"sources":["../../../src/support/pages/conversation.tsx"],"sourcesContent":["import { ConversationStatus } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { type ReactElement, useMemo } from \"react\";\nimport { useConversation } from \"../../hooks/use-conversation\";\nimport { useConversationPage } from \"../../hooks/use-conversation-page\";\nimport { useSupport } from \"../../provider\";\nimport { AvatarStack } from \"../components/avatar-stack\";\nimport { ConversationTimelineList } from \"../components/conversation-timeline\";\nimport { Header } from \"../components/header\";\nimport { MultimodalInput } from \"../components/multimodal-input\";\nimport { IdentificationTimelineTool } from \"../components/timeline-identification-tool\";\nimport { useSupportConfig, useSupportNavigation } from \"../store\";\nimport { Text, useSupportText } from \"../text\";\n\ntype ConversationPageProps = {\n\t/**\n\t * Page params object (for compatibility with Page component)\n\t */\n\tparams?: {\n\t\t/**\n\t\t * The conversation ID to display (can be PENDING_CONVERSATION_ID or a real ID).\n\t\t */\n\t\tconversationId: string;\n\n\t\t/**\n\t\t * Optional initial message to send when opening the conversation.\n\t\t */\n\t\tinitialMessage?: string;\n\n\t\t/**\n\t\t * Optional timeline items to display (for optimistic updates or initial state).\n\t\t */\n\t\titems?: TimelineItem[];\n\t};\n\n\t// Legacy direct props support (deprecated but maintained for backward compatibility)\n\tconversationId?: string;\n\tinitialMessage?: string;\n\titems?: TimelineItem[];\n};\n\n/**\n * Conversation page with message timeline and input composer.\n */\ntype ConversationPageComponent = (props: ConversationPageProps) => ReactElement;\n\nexport const ConversationPage: ConversationPageComponent = ({\n\tparams,\n\tconversationId: legacyConversationId,\n\tinitialMessage: legacyInitialMessage,\n\titems: legacyItems,\n}: ConversationPageProps) => {\n\t// Support both params object (new) and direct props (legacy)\n\tconst initialConversationId =\n\t\tparams?.conversationId ?? legacyConversationId ?? \"\";\n\tconst initialMessage = params?.initialMessage ?? legacyInitialMessage;\n\tconst passedItems = params?.items ?? legacyItems ?? [];\n\tconst { website, availableAIAgents, availableHumanAgents, visitor } =\n\t\tuseSupport();\n\tconst { navigate, replace, goBack, canGoBack } = useSupportNavigation();\n\tconst { isOpen } = useSupportConfig();\n\tconst text = useSupportText();\n\n\tconst timelineTools = useMemo(\n\t\t() => ({\n\t\t\tidentification: { component: IdentificationTimelineTool },\n\t\t}),\n\t\t[]\n\t);\n\n\t// Main conversation hook - handles all logic\n\tconst conversation = useConversationPage({\n\t\tconversationId: initialConversationId,\n\t\titems: passedItems,\n\t\tinitialMessage,\n\t\tautoSeenEnabled: isOpen,\n\t\tonConversationIdChange: (newConversationId) => {\n\t\t\t// Update navigation when conversation is created\n\t\t\treplace({\n\t\t\t\tpage: \"CONVERSATION\",\n\t\t\t\tparams: { conversationId: newConversationId },\n\t\t\t});\n\t\t},\n\t});\n\n\tconst { conversation: activeConversation } = useConversation(\n\t\tconversation.isPending ? null : conversation.conversationId,\n\t\t{ enabled: !conversation.isPending }\n\t);\n\n\tconst isConversationClosed = Boolean(\n\t\tactiveConversation &&\n\t\t\t(activeConversation.status === ConversationStatus.RESOLVED ||\n\t\t\t\tactiveConversation.status === ConversationStatus.SPAM ||\n\t\t\t\tactiveConversation.deletedAt)\n\t);\n\n\tconst handleGoBack = () => {\n\t\tif (canGoBack) {\n\t\t\tgoBack();\n\t\t} else {\n\t\t\tnavigate({ page: \"HOME\" });\n\t\t}\n\t};\n\n\treturn (\n\t\t<div className=\"flex h-full flex-col gap-0 overflow-hidden\">\n\t\t\t<Header onGoBack={handleGoBack}>\n\t\t\t\t<div className=\"flex w-full items-center justify-between gap-2 py-3\">\n\t\t\t\t\t<div className=\"flex flex-col\">\n\t\t\t\t\t\t<p className=\"font-medium text-co-primary text-sm\">\n\t\t\t\t\t\t\t{website?.name}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<Text\n\t\t\t\t\t\t\tas=\"p\"\n\t\t\t\t\t\t\tclassName=\"text-co-muted-foreground text-sm\"\n\t\t\t\t\t\t\ttextKey=\"common.labels.supportOnline\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<AvatarStack\n\t\t\t\t\t\taiAgents={availableAIAgents}\n\t\t\t\t\t\tgapWidth={2}\n\t\t\t\t\t\thumanAgents={availableHumanAgents}\n\t\t\t\t\t\tsize={32}\n\t\t\t\t\t\tspacing={28}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</Header>\n\n\t\t\t<ConversationTimelineList\n\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\tclassName=\"min-h-0 flex-1 px-4\"\n\t\t\t\tconversationId={conversation.conversationId}\n\t\t\t\tcurrentVisitorId={visitor?.id}\n\t\t\t\titems={conversation.items}\n\t\t\t\ttools={timelineTools}\n\t\t\t/>\n\n\t\t\t{isConversationClosed ? (\n\t\t\t\t<div className=\"m-4 flex items-center justify-center text-balance px-4 pb-6 text-center font-medium text-co-muted-foreground text-sm\">\n\t\t\t\t\t<Text as=\"p\" textKey=\"component.conversationPage.closedMessage\" />\n\t\t\t\t</div>\n\t\t\t) : (\n\t\t\t\t<div className=\"flex-shrink-0 p-1\">\n\t\t\t\t\t<MultimodalInput\n\t\t\t\t\t\tdisabled={conversation.composer.isSubmitting}\n\t\t\t\t\t\terror={conversation.error}\n\t\t\t\t\t\tfiles={conversation.composer.files}\n\t\t\t\t\t\tisSubmitting={conversation.composer.isSubmitting}\n\t\t\t\t\t\tonChange={conversation.composer.setMessage}\n\t\t\t\t\t\tonFileSelect={conversation.composer.addFiles}\n\t\t\t\t\t\tonRemoveFile={conversation.composer.removeFile}\n\t\t\t\t\t\tonSubmit={conversation.composer.submit}\n\t\t\t\t\t\tplaceholder={text(\"component.multimodalInput.placeholder\")}\n\t\t\t\t\t\tvalue={conversation.composer.message}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n};\n"],"mappings":";;;;;;;;;;;;;;;AA8CA,MAAaA,oBAA+C,EAC3D,QACA,gBAAgB,sBAChB,gBAAgB,sBAChB,OAAO,kBACqB;CAE5B,MAAM,wBACL,QAAQ,kBAAkB,wBAAwB;CACnD,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,cAAc,QAAQ,SAAS,eAAe,EAAE;CACtD,MAAM,EAAE,SAAS,mBAAmB,sBAAsB,YACzD,YAAY;CACb,MAAM,EAAE,UAAU,SAAS,QAAQ,cAAc,sBAAsB;CACvE,MAAM,EAAE,WAAW,kBAAkB;CACrC,MAAM,OAAO,gBAAgB;CAE7B,MAAM,gBAAgB,eACd,EACN,gBAAgB,EAAE,WAAW,4BAA4B,EACzD,GACD,EAAE,CACF;CAGD,MAAM,eAAe,oBAAoB;EACxC,gBAAgB;EAChB,OAAO;EACP;EACA,iBAAiB;EACjB,yBAAyB,sBAAsB;AAE9C,WAAQ;IACP,MAAM;IACN,QAAQ,EAAE,gBAAgB,mBAAmB;IAC7C,CAAC;;EAEH,CAAC;CAEF,MAAM,EAAE,cAAc,uBAAuB,gBAC5C,aAAa,YAAY,OAAO,aAAa,gBAC7C,EAAE,SAAS,CAAC,aAAa,WAAW,CACpC;CAED,MAAM,uBAAuB,QAC5B,uBACE,mBAAmB,WAAW,mBAAmB,YACjD,mBAAmB,WAAW,mBAAmB,QACjD,mBAAmB,WACrB;CAED,MAAM,qBAAqB;AAC1B,MAAI,UACH,SAAQ;MAER,UAAS,EAAE,MAAM,QAAQ,CAAC;;AAI5B,QACC,qBAAC;EAAI,WAAU;;GACd,oBAAC;IAAO,UAAU;cACjB,qBAAC;KAAI,WAAU;gBACd,qBAAC;MAAI,WAAU;iBACd,oBAAC;OAAE,WAAU;iBACX,SAAS;QACP,EACJ,oBAAC;OACA,IAAG;OACH,WAAU;OACV,SAAQ;QACP;OACG,EACN,oBAAC;MACA,UAAU;MACV,UAAU;MACV,aAAa;MACb,MAAM;MACN,SAAS;OACR;MACG;KACE;GAET,oBAAC;IACmB;IACG;IACtB,WAAU;IACV,gBAAgB,aAAa;IAC7B,kBAAkB,SAAS;IAC3B,OAAO,aAAa;IACpB,OAAO;KACN;GAED,uBACA,oBAAC;IAAI,WAAU;cACd,oBAAC;KAAK,IAAG;KAAI,SAAQ;MAA6C;KAC7D,GAEN,oBAAC;IAAI,WAAU;cACd,oBAAC;KACA,UAAU,aAAa,SAAS;KAChC,OAAO,aAAa;KACpB,OAAO,aAAa,SAAS;KAC7B,cAAc,aAAa,SAAS;KACpC,UAAU,aAAa,SAAS;KAChC,cAAc,aAAa,SAAS;KACpC,cAAc,aAAa,SAAS;KACpC,UAAU,aAAa,SAAS;KAChC,aAAa,KAAK,wCAAwC;KAC1D,OAAO,aAAa,SAAS;MAC5B;KACG;;GAEF"}
1
+ {"version":3,"file":"conversation.js","names":["ConversationPage: ConversationPageComponent"],"sources":["../../../src/support/pages/conversation.tsx"],"sourcesContent":["import { ConversationStatus } from \"@cossistant/types\";\nimport type { TimelineItem } from \"@cossistant/types/api/timeline-item\";\nimport { type ReactElement, useEffect, useMemo, useRef } from \"react\";\nimport { useConversation } from \"../../hooks/use-conversation\";\nimport { useConversationPage } from \"../../hooks/use-conversation-page\";\nimport { useNewMessageSound } from \"../../hooks/use-new-message-sound\";\nimport { useSupport } from \"../../provider\";\nimport { AvatarStack } from \"../components/avatar-stack\";\nimport { ConversationTimelineList } from \"../components/conversation-timeline\";\nimport { Header } from \"../components/header\";\nimport { MultimodalInput } from \"../components/multimodal-input\";\nimport { IdentificationTimelineTool } from \"../components/timeline-identification-tool\";\nimport { useSupportConfig, useSupportNavigation } from \"../store\";\nimport { Text, useSupportText } from \"../text\";\n\ntype ConversationPageProps = {\n\t/**\n\t * Page params object (for compatibility with Page component)\n\t */\n\tparams?: {\n\t\t/**\n\t\t * The conversation ID to display (can be PENDING_CONVERSATION_ID or a real ID).\n\t\t */\n\t\tconversationId: string;\n\n\t\t/**\n\t\t * Optional initial message to send when opening the conversation.\n\t\t */\n\t\tinitialMessage?: string;\n\n\t\t/**\n\t\t * Optional timeline items to display (for optimistic updates or initial state).\n\t\t */\n\t\titems?: TimelineItem[];\n\t};\n\n\t// Legacy direct props support (deprecated but maintained for backward compatibility)\n\tconversationId?: string;\n\tinitialMessage?: string;\n\titems?: TimelineItem[];\n};\n\n/**\n * Conversation page with message timeline and input composer.\n */\ntype ConversationPageComponent = (props: ConversationPageProps) => ReactElement;\n\nexport const ConversationPage: ConversationPageComponent = ({\n\tparams,\n\tconversationId: legacyConversationId,\n\tinitialMessage: legacyInitialMessage,\n\titems: legacyItems,\n}: ConversationPageProps) => {\n\t// Support both params object (new) and direct props (legacy)\n\tconst initialConversationId =\n\t\tparams?.conversationId ?? legacyConversationId ?? \"\";\n\tconst initialMessage = params?.initialMessage ?? legacyInitialMessage;\n\tconst passedItems = params?.items ?? legacyItems ?? [];\n\tconst { website, availableAIAgents, availableHumanAgents, visitor } =\n\t\tuseSupport();\n\tconst { navigate, replace, goBack, canGoBack } = useSupportNavigation();\n\tconst { isOpen } = useSupportConfig();\n\tconst text = useSupportText();\n\tconst playNewMessageSound = useNewMessageSound({\n\t\tvolume: 0.7,\n\t\tplaybackRate: 1.0,\n\t});\n\tconst previousItemsRef = useRef<TimelineItem[]>([]);\n\n\tconst timelineTools = useMemo(\n\t\t() => ({\n\t\t\tidentification: { component: IdentificationTimelineTool },\n\t\t}),\n\t\t[]\n\t);\n\n\t// Main conversation hook - handles all logic\n\tconst conversation = useConversationPage({\n\t\tconversationId: initialConversationId,\n\t\titems: passedItems,\n\t\tinitialMessage,\n\t\tautoSeenEnabled: isOpen,\n\t\tonConversationIdChange: (newConversationId) => {\n\t\t\t// Update navigation when conversation is created\n\t\t\treplace({\n\t\t\t\tpage: \"CONVERSATION\",\n\t\t\t\tparams: { conversationId: newConversationId },\n\t\t\t});\n\t\t},\n\t});\n\n\tconst { conversation: activeConversation } = useConversation(\n\t\tconversation.isPending ? null : conversation.conversationId,\n\t\t{ enabled: !conversation.isPending }\n\t);\n\n\tconst isConversationClosed = Boolean(\n\t\tactiveConversation &&\n\t\t\t(activeConversation.status === ConversationStatus.RESOLVED ||\n\t\t\t\tactiveConversation.status === ConversationStatus.SPAM ||\n\t\t\t\tactiveConversation.deletedAt)\n\t);\n\n\tconst handleGoBack = () => {\n\t\tif (canGoBack) {\n\t\t\tgoBack();\n\t\t} else {\n\t\t\tnavigate({ page: \"HOME\" });\n\t\t}\n\t};\n\n\t// Play sound when new messages arrive from agents (not visitor)\n\tuseEffect(() => {\n\t\tconst currentItems = conversation.items;\n\t\tconst previousItems = previousItemsRef.current;\n\n\t\t// Check if there are new items\n\t\tif (currentItems.length > previousItems.length) {\n\t\t\t// Find the new items\n\t\t\tconst newItems = currentItems.slice(previousItems.length);\n\n\t\t\t// Play sound only if new message is from agent (not visitor)\n\t\t\tfor (const item of newItems) {\n\t\t\t\tif (item.type === \"message\" && !item.visitorId) {\n\t\t\t\t\tplayNewMessageSound();\n\t\t\t\t\tbreak; // Only play once per batch\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Update the ref\n\t\tpreviousItemsRef.current = currentItems;\n\t}, [conversation.items, playNewMessageSound]);\n\n\treturn (\n\t\t<div className=\"flex h-full flex-col gap-0 overflow-hidden\">\n\t\t\t<Header onGoBack={handleGoBack}>\n\t\t\t\t<div className=\"flex w-full items-center justify-between gap-2 py-3\">\n\t\t\t\t\t<div className=\"flex flex-col\">\n\t\t\t\t\t\t<p className=\"font-medium text-co-primary text-sm\">\n\t\t\t\t\t\t\t{website?.name}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<Text\n\t\t\t\t\t\t\tas=\"p\"\n\t\t\t\t\t\t\tclassName=\"text-co-muted-foreground text-sm\"\n\t\t\t\t\t\t\ttextKey=\"common.labels.supportOnline\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<AvatarStack\n\t\t\t\t\t\taiAgents={availableAIAgents}\n\t\t\t\t\t\tgapWidth={2}\n\t\t\t\t\t\thumanAgents={availableHumanAgents}\n\t\t\t\t\t\tsize={32}\n\t\t\t\t\t\tspacing={28}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</Header>\n\n\t\t\t<ConversationTimelineList\n\t\t\t\tavailableAIAgents={availableAIAgents}\n\t\t\t\tavailableHumanAgents={availableHumanAgents}\n\t\t\t\tclassName=\"min-h-0 flex-1 px-4\"\n\t\t\t\tconversationId={conversation.conversationId}\n\t\t\t\tcurrentVisitorId={visitor?.id}\n\t\t\t\titems={conversation.items}\n\t\t\t\ttools={timelineTools}\n\t\t\t/>\n\n\t\t\t{isConversationClosed ? (\n\t\t\t\t<div className=\"m-4 flex items-center justify-center text-balance px-4 pb-6 text-center font-medium text-co-muted-foreground text-sm\">\n\t\t\t\t\t<Text as=\"p\" textKey=\"component.conversationPage.closedMessage\" />\n\t\t\t\t</div>\n\t\t\t) : (\n\t\t\t\t<div className=\"flex-shrink-0 p-1\">\n\t\t\t\t\t<MultimodalInput\n\t\t\t\t\t\tdisabled={conversation.composer.isSubmitting}\n\t\t\t\t\t\terror={conversation.error}\n\t\t\t\t\t\tfiles={conversation.composer.files}\n\t\t\t\t\t\tisSubmitting={conversation.composer.isSubmitting}\n\t\t\t\t\t\tonChange={conversation.composer.setMessage}\n\t\t\t\t\t\tonFileSelect={conversation.composer.addFiles}\n\t\t\t\t\t\tonRemoveFile={conversation.composer.removeFile}\n\t\t\t\t\t\tonSubmit={conversation.composer.submit}\n\t\t\t\t\t\tplaceholder={text(\"component.multimodalInput.placeholder\")}\n\t\t\t\t\t\tvalue={conversation.composer.message}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n};\n"],"mappings":";;;;;;;;;;;;;;;;AA+CA,MAAaA,oBAA+C,EAC3D,QACA,gBAAgB,sBAChB,gBAAgB,sBAChB,OAAO,kBACqB;CAE5B,MAAM,wBACL,QAAQ,kBAAkB,wBAAwB;CACnD,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,cAAc,QAAQ,SAAS,eAAe,EAAE;CACtD,MAAM,EAAE,SAAS,mBAAmB,sBAAsB,YACzD,YAAY;CACb,MAAM,EAAE,UAAU,SAAS,QAAQ,cAAc,sBAAsB;CACvE,MAAM,EAAE,WAAW,kBAAkB;CACrC,MAAM,OAAO,gBAAgB;CAC7B,MAAM,sBAAsB,mBAAmB;EAC9C,QAAQ;EACR,cAAc;EACd,CAAC;CACF,MAAM,mBAAmB,OAAuB,EAAE,CAAC;CAEnD,MAAM,gBAAgB,eACd,EACN,gBAAgB,EAAE,WAAW,4BAA4B,EACzD,GACD,EAAE,CACF;CAGD,MAAM,eAAe,oBAAoB;EACxC,gBAAgB;EAChB,OAAO;EACP;EACA,iBAAiB;EACjB,yBAAyB,sBAAsB;AAE9C,WAAQ;IACP,MAAM;IACN,QAAQ,EAAE,gBAAgB,mBAAmB;IAC7C,CAAC;;EAEH,CAAC;CAEF,MAAM,EAAE,cAAc,uBAAuB,gBAC5C,aAAa,YAAY,OAAO,aAAa,gBAC7C,EAAE,SAAS,CAAC,aAAa,WAAW,CACpC;CAED,MAAM,uBAAuB,QAC5B,uBACE,mBAAmB,WAAW,mBAAmB,YACjD,mBAAmB,WAAW,mBAAmB,QACjD,mBAAmB,WACrB;CAED,MAAM,qBAAqB;AAC1B,MAAI,UACH,SAAQ;MAER,UAAS,EAAE,MAAM,QAAQ,CAAC;;AAK5B,iBAAgB;EACf,MAAM,eAAe,aAAa;EAClC,MAAM,gBAAgB,iBAAiB;AAGvC,MAAI,aAAa,SAAS,cAAc,QAAQ;GAE/C,MAAM,WAAW,aAAa,MAAM,cAAc,OAAO;AAGzD,QAAK,MAAM,QAAQ,SAClB,KAAI,KAAK,SAAS,aAAa,CAAC,KAAK,WAAW;AAC/C,yBAAqB;AACrB;;;AAMH,mBAAiB,UAAU;IACzB,CAAC,aAAa,OAAO,oBAAoB,CAAC;AAE7C,QACC,qBAAC;EAAI,WAAU;;GACd,oBAAC;IAAO,UAAU;cACjB,qBAAC;KAAI,WAAU;gBACd,qBAAC;MAAI,WAAU;iBACd,oBAAC;OAAE,WAAU;iBACX,SAAS;QACP,EACJ,oBAAC;OACA,IAAG;OACH,WAAU;OACV,SAAQ;QACP;OACG,EACN,oBAAC;MACA,UAAU;MACV,UAAU;MACV,aAAa;MACb,MAAM;MACN,SAAS;OACR;MACG;KACE;GAET,oBAAC;IACmB;IACG;IACtB,WAAU;IACV,gBAAgB,aAAa;IAC7B,kBAAkB,SAAS;IAC3B,OAAO,aAAa;IACpB,OAAO;KACN;GAED,uBACA,oBAAC;IAAI,WAAU;cACd,oBAAC;KAAK,IAAG;KAAI,SAAQ;MAA6C;KAC7D,GAEN,oBAAC;IAAI,WAAU;cACd,oBAAC;KACA,UAAU,aAAa,SAAS;KAChC,OAAO,aAAa;KACpB,OAAO,aAAa,SAAS;KAC7B,cAAc,aAAa,SAAS;KACpC,UAAU,aAAa,SAAS;KAChC,cAAc,aAAa,SAAS;KACpC,cAAc,aAAa,SAAS;KACpC,UAAU,aAAa,SAAS;KAChC,aAAa,KAAK,wCAAwC;KAC1D,OAAO,aAAa,SAAS;MAC5B;KACG;;GAEF"}
@@ -1,19 +1,29 @@
1
1
  import React from "react";
2
+ import { RouteRegistry } from "@cossistant/core";
2
3
 
3
4
  //#region src/support/router.d.ts
4
-
5
+ type CustomPage<K extends keyof RouteRegistry = keyof RouteRegistry> = {
6
+ name: K;
7
+ component: React.ComponentType<{
8
+ params?: RouteRegistry[K];
9
+ }>;
10
+ };
11
+ type SupportRouterProps = {
12
+ customPages?: CustomPage[];
13
+ children?: React.ReactNode;
14
+ };
5
15
  /**
6
16
  * Router with default support pages (HOME, ARTICLES, CONVERSATION, CONVERSATION_HISTORY).
7
- * Add custom pages as children.
17
+ * Add custom pages via customPages prop.
8
18
  *
9
19
  * @example
10
- * <SupportRouter>
11
- * <Page name="SETTINGS" component={SettingsPage} />
12
- * </SupportRouter>
20
+ * <SupportRouter
21
+ * customPages={[
22
+ * { name: "SETTINGS", component: SettingsPage }
23
+ * ]}
24
+ * />
13
25
  */
14
- declare const SupportRouter: React.FC<{
15
- children?: React.ReactNode;
16
- }>;
26
+ declare const SupportRouter: React.FC<SupportRouterProps>;
17
27
  //#endregion
18
- export { SupportRouter };
28
+ export { CustomPage, SupportRouter, SupportRouterProps };
19
29
  //# sourceMappingURL=router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","names":[],"sources":["../../src/support/router.tsx"],"sourcesContent":[],"mappings":";;;;;;AAiBA;;;;;;;cAAa,eAAe,KAAA,CAAM;aAAgB,KAAA,CAAM"}
1
+ {"version":3,"file":"router.d.ts","names":[],"sources":["../../src/support/router.tsx"],"sourcesContent":[],"mappings":";;;;KAWY,2BAA2B,sBAAsB;QACtD;EADK,SAAA,EAEA,KAAA,CAAM,aAFI,CAAA;IAAiB,MAAA,CAAA,EAEI,aAFJ,CAEkB,CAFlB,CAAA;EAAsB,CAAA,CAAA;CACtD;AACoC,KAG/B,kBAAA,GAH+B;EAAc,WAAA,CAAA,EAI1C,UAJ0C,EAAA;EAA7C,QAAM,CAAA,EAKN,KAAA,CAAM,SALA;CAAa;AAG/B;AAgBA;;;;;;;;;;cAAa,eAAe,KAAA,CAAM,GAAG"}