analytica-frontend-lib 1.7.21 → 1.7.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 (119) hide show
  1. package/dist/ActivitiesHistory/index.js +7 -7
  2. package/dist/ActivitiesHistory/index.mjs +7 -7
  3. package/dist/ActivityCardQuestionBanks/index.js +7 -7
  4. package/dist/ActivityCardQuestionBanks/index.mjs +7 -7
  5. package/dist/ActivityCardQuestionPreview/index.js +7 -7
  6. package/dist/ActivityCardQuestionPreview/index.mjs +7 -7
  7. package/dist/ActivityCreate/ActivityCreateContent/index.js +7 -7
  8. package/dist/ActivityCreate/ActivityCreateContent/index.mjs +7 -7
  9. package/dist/ActivityCreate/index.js +7 -7
  10. package/dist/ActivityCreate/index.mjs +7 -7
  11. package/dist/ActivityDetails/index.js +7 -7
  12. package/dist/ActivityDetails/index.mjs +7 -7
  13. package/dist/ActivityFilters/index.js +7 -7
  14. package/dist/ActivityFilters/index.mjs +7 -7
  15. package/dist/ActivityPageLayout/index.js +7 -7
  16. package/dist/ActivityPageLayout/index.mjs +7 -7
  17. package/dist/ActivityPreview/index.js +7 -7
  18. package/dist/ActivityPreview/index.mjs +7 -7
  19. package/dist/AlertManager/index.js +7 -7
  20. package/dist/AlertManager/index.mjs +7 -7
  21. package/dist/CheckBoxGroup/index.js +7 -7
  22. package/dist/CheckBoxGroup/index.mjs +7 -7
  23. package/dist/EnemMoment/index.js +7 -7
  24. package/dist/EnemMoment/index.mjs +7 -7
  25. package/dist/EnemMomentStudentModal/index.js +7 -7
  26. package/dist/EnemMomentStudentModal/index.mjs +7 -7
  27. package/dist/ExamDetailsLayout/ExamDetailsPage/index.js +7 -7
  28. package/dist/ExamDetailsLayout/ExamDetailsPage/index.mjs +7 -7
  29. package/dist/ExamPageLayout/index.js +7 -7
  30. package/dist/ExamPageLayout/index.mjs +7 -7
  31. package/dist/KnowledgePerformanceCard/index.js +7 -7
  32. package/dist/KnowledgePerformanceCard/index.mjs +7 -7
  33. package/dist/LessonCardPreview/index.js +7 -7
  34. package/dist/LessonCardPreview/index.mjs +7 -7
  35. package/dist/LessonMediaSections/index.js +7 -7
  36. package/dist/LessonMediaSections/index.mjs +7 -7
  37. package/dist/LessonsCatalog/index.js +7 -7
  38. package/dist/LessonsCatalog/index.mjs +7 -7
  39. package/dist/PrintableUsersTable/index.js +7 -7
  40. package/dist/PrintableUsersTable/index.mjs +7 -7
  41. package/dist/QuestionsPerformanceCard/index.js +7 -7
  42. package/dist/QuestionsPerformanceCard/index.mjs +7 -7
  43. package/dist/RecommendedLessonCreate/index.js +7 -7
  44. package/dist/RecommendedLessonCreate/index.mjs +7 -7
  45. package/dist/RecommendedLessonsHistory/index.js +7 -7
  46. package/dist/RecommendedLessonsHistory/index.mjs +7 -7
  47. package/dist/SendActivityModal/SendActivityModal.js +7 -7
  48. package/dist/SendActivityModal/SendActivityModal.mjs +7 -7
  49. package/dist/SendActivityModal/index.js +7 -7
  50. package/dist/SendActivityModal/index.mjs +7 -7
  51. package/dist/SendLessonModal/index.js +7 -7
  52. package/dist/SendLessonModal/index.mjs +7 -7
  53. package/dist/SimulatedPerformance/SimulatedPerformanceView/index.js +7 -7
  54. package/dist/SimulatedPerformance/SimulatedPerformanceView/index.mjs +7 -7
  55. package/dist/SimulationsPage/index.js +7 -7
  56. package/dist/SimulationsPage/index.mjs +7 -7
  57. package/dist/SupportChatComposer/index.js +3 -3
  58. package/dist/SupportChatComposer/index.mjs +2 -2
  59. package/dist/SupportChatEvent/index.js +3 -3
  60. package/dist/SupportChatEvent/index.mjs +2 -2
  61. package/dist/SupportChatMessage/index.js +3 -3
  62. package/dist/SupportChatMessage/index.mjs +2 -2
  63. package/dist/SupportChatWidget/index.js +3 -3
  64. package/dist/SupportChatWidget/index.mjs +2 -2
  65. package/dist/SupportConversationMessages/index.js +5 -5
  66. package/dist/SupportConversationMessages/index.mjs +4 -4
  67. package/dist/SupportTicketConversation/index.js +7 -7
  68. package/dist/SupportTicketConversation/index.mjs +6 -6
  69. package/dist/TableProvider/index.js +7 -7
  70. package/dist/TableProvider/index.mjs +7 -7
  71. package/dist/UnifiedDraftModelPage/index.js +7 -7
  72. package/dist/UnifiedDraftModelPage/index.mjs +7 -7
  73. package/dist/UnifiedHistoryPage/index.js +7 -7
  74. package/dist/UnifiedHistoryPage/index.mjs +7 -7
  75. package/dist/{chunk-I6EAPPBL.mjs → chunk-25SRDJZ6.mjs} +3 -3
  76. package/dist/{chunk-CYSODZGX.js → chunk-27Y6V3GM.js} +169 -66
  77. package/dist/chunk-27Y6V3GM.js.map +1 -0
  78. package/dist/{chunk-KSUPPAKH.js → chunk-3IXK6AR7.js} +5 -5
  79. package/dist/{chunk-KSUPPAKH.js.map → chunk-3IXK6AR7.js.map} +1 -1
  80. package/dist/{chunk-POT3ALY3.js → chunk-5DINXU6W.js} +1 -1
  81. package/dist/{chunk-POT3ALY3.js.map → chunk-5DINXU6W.js.map} +1 -1
  82. package/dist/{chunk-EMWX6IKP.js → chunk-7VJP5D26.js} +13 -13
  83. package/dist/{chunk-EMWX6IKP.js.map → chunk-7VJP5D26.js.map} +1 -1
  84. package/dist/{chunk-7ZB3YHF2.mjs → chunk-C6LC5LD5.mjs} +1 -1
  85. package/dist/{chunk-7ZB3YHF2.mjs.map → chunk-C6LC5LD5.mjs.map} +1 -1
  86. package/dist/{chunk-OES54VU6.mjs → chunk-CFV2OZ3P.mjs} +116 -13
  87. package/dist/chunk-CFV2OZ3P.mjs.map +1 -0
  88. package/dist/{chunk-I4QGQMXI.mjs → chunk-EXYXZFNK.mjs} +2 -2
  89. package/dist/{chunk-FU23WOTY.js → chunk-HSV5VZ4Z.js} +6 -6
  90. package/dist/{chunk-FU23WOTY.js.map → chunk-HSV5VZ4Z.js.map} +1 -1
  91. package/dist/{chunk-KWXOYHYK.mjs → chunk-P7MORXSE.mjs} +2 -2
  92. package/dist/{chunk-YG4IZ5Q3.mjs → chunk-Q2OSEROT.mjs} +2 -2
  93. package/dist/{chunk-K45PTE7F.mjs → chunk-RBKQXT73.mjs} +4 -4
  94. package/dist/{chunk-VSVLIRO2.js → chunk-RXEU77VN.js} +15 -15
  95. package/dist/{chunk-VSVLIRO2.js.map → chunk-RXEU77VN.js.map} +1 -1
  96. package/dist/{chunk-ZB6HDDE4.js → chunk-TQO37PVW.js} +14 -14
  97. package/dist/{chunk-ZB6HDDE4.js.map → chunk-TQO37PVW.js.map} +1 -1
  98. package/dist/components/SupportChatWidget/guestSupportChatAdapter.d.ts +6 -1
  99. package/dist/components/SupportChatWidget/guestSupportChatAdapter.d.ts.map +1 -1
  100. package/dist/hooks/useAppContent/index.js +7 -7
  101. package/dist/hooks/useAppContent/index.mjs +7 -7
  102. package/dist/hooks/useSupportChat.d.ts +2 -0
  103. package/dist/hooks/useSupportChat.d.ts.map +1 -1
  104. package/dist/index.js +8 -8
  105. package/dist/index.mjs +7 -7
  106. package/dist/types/supportChat/index.d.ts +14 -0
  107. package/dist/types/supportChat/index.d.ts.map +1 -1
  108. package/dist/types/supportChat/index.js +2 -2
  109. package/dist/types/supportChat/index.mjs +1 -1
  110. package/dist/types/supportChat.d.ts +14 -0
  111. package/dist/types/supportChat.d.ts.map +1 -1
  112. package/package.json +1 -1
  113. package/dist/chunk-CYSODZGX.js.map +0 -1
  114. package/dist/chunk-OES54VU6.mjs.map +0 -1
  115. /package/dist/{chunk-I6EAPPBL.mjs.map → chunk-25SRDJZ6.mjs.map} +0 -0
  116. /package/dist/{chunk-I4QGQMXI.mjs.map → chunk-EXYXZFNK.mjs.map} +0 -0
  117. /package/dist/{chunk-KWXOYHYK.mjs.map → chunk-P7MORXSE.mjs.map} +0 -0
  118. /package/dist/{chunk-YG4IZ5Q3.mjs.map → chunk-Q2OSEROT.mjs.map} +0 -0
  119. /package/dist/{chunk-K45PTE7F.mjs.map → chunk-RBKQXT73.mjs.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-27Y6V3GM.js","../src/components/SupportChatWidget/SupportChatWidget.tsx","../src/hooks/useSupportChat.ts","../src/components/SupportChatWidget/SupportChatContactForm.tsx","../src/components/SupportChatWidget/SupportChatArticleView.tsx","../src/components/SupportChatWidget/supportChatContent.tsx","../src/components/SupportChatWidget/NativeSupportChat.tsx","../src/components/SupportChatWidget/guestSupportChatAdapter.ts","../src/components/SupportChatWidget/mockSupportChatAdapter.ts"],"names":["useState","jsxs","jsx","useCallback","useMemo"],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-27Y6V3GM.js","sourcesContent":[null,"import {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ChangeEvent,\n type KeyboardEvent,\n} from 'react';\nimport {\n ChatCircleDotsIcon,\n MinusIcon,\n PaperclipIcon,\n PaperPlaneTiltIcon,\n XIcon,\n} from '@phosphor-icons/react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport { useSupportChat } from '../../hooks/useSupportChat';\nimport { SupportChatContactForm } from './SupportChatContactForm';\nimport { SupportChatArticleView } from './SupportChatArticleView';\nimport {\n AttachmentList,\n MessageText,\n PendingAttachment,\n SUPPORT_CHAT_ACCEPT,\n pickAttachments,\n} from './supportChatContent';\nimport { SupportType } from '../../types/support';\nimport {\n SUPPORT_CHAT_AUTHOR,\n SUPPORT_CHAT_MAX_ATTACHMENTS,\n type SupportChatAdapter,\n type SupportChatFlags,\n type SupportChatMessage,\n type SupportChatTheme,\n type SupportChatUser,\n} from '../../types/supportChat';\nimport { cn } from '../../utils/utils';\n\n/** Visibility of the chat window */\nexport type SupportChatWindowState = 'closed' | 'open' | 'minimized';\n\nexport interface SupportChatWidgetProps {\n /** Data layer (createMockSupportChatAdapter for now) */\n adapter: SupportChatAdapter;\n /** App the widget runs in, sent with the ticket (e.g. \"login\", \"backoffice\") */\n channel: string;\n institutionId?: string;\n /** Logged user; omit for visitors */\n user?: SupportChatUser;\n flags?: SupportChatFlags;\n theme?: SupportChatTheme;\n onOpen?: () => void;\n onSend?: (message: SupportChatMessage) => void;\n /**\n * Called when the window is closed with X. Closing only hides it: the\n * conversation stays connected and new replies count as unread.\n */\n onClose?: () => void;\n /**\n * Link of a FAQ article, e.g. `(id) => '/artigo/' + id`. With it, the\n * \"Baseado na FAQ\" line under an AI answer opens the article; without it\n * the line only names the article.\n */\n faqArticleUrl?: (articleId: string) => string;\n}\n\n/** Label read by screen readers on the unread badge of the launcher */\nconst formatNewMessagesLabel = (count: number): string =>\n count === 1 ? '1 nova mensagem' : `${count} novas mensagens`;\n\n/** The requester side of the conversation (user or visitor) */\nconst isOwnMessage = (message: SupportChatMessage) =>\n message.senderRole === SUPPORT_CHAT_AUTHOR.USER ||\n message.senderRole === SUPPORT_CHAT_AUTHOR.GUEST;\n\nconst formatTime = (createdAt: string) =>\n new Date(createdAt).toLocaleTimeString('pt-BR', {\n hour: '2-digit',\n minute: '2-digit',\n });\n\n/** \"Baseado na FAQ: <title>\" under an AI answer, linked when possible */\nconst FaqReference = ({\n faq,\n faqArticleUrl,\n onOpenArticle,\n}: {\n faq: { id: string; title: string };\n faqArticleUrl?: (articleId: string) => string;\n onOpenArticle?: (article: { id: string; title: string }) => void;\n}) => {\n const linked = !!onOpenArticle || !!faqArticleUrl;\n const label = (\n <Text\n as=\"span\"\n size=\"xs\"\n color=\"text-primary-700\"\n className={cn(linked && 'underline')}\n >\n Baseado na FAQ: {faq.title}\n </Text>\n );\n let content = label;\n if (onOpenArticle) {\n // Opens inside the chat, like the Zendesk messenger (no new tab)\n content = (\n <a\n href={faqArticleUrl?.(faq.id) ?? '#'}\n onClick={(event) => {\n event.preventDefault();\n onOpenArticle(faq);\n }}\n >\n {label}\n </a>\n );\n } else if (faqArticleUrl) {\n content = (\n <a href={faqArticleUrl(faq.id)} target=\"_blank\" rel=\"noreferrer\">\n {label}\n </a>\n );\n }\n return (\n <div data-testid=\"support-chat-faq\" className=\"mt-1 max-w-[80%]\">\n {content}\n </div>\n );\n};\n\nconst MessageBubble = ({\n message,\n faqArticleUrl,\n onOpenArticle,\n}: {\n message: SupportChatMessage;\n faqArticleUrl?: (articleId: string) => string;\n onOpenArticle?: (article: { id: string; title?: string }) => void;\n}) => {\n const own = isOwnMessage(message);\n return (\n <div\n className={cn(\n 'flex flex-col mb-3 transition-opacity',\n own ? 'items-end' : 'items-start',\n message.pending && 'opacity-60'\n )}\n aria-busy={message.pending || undefined}\n >\n {!own && (\n <Text size=\"xs\" className=\"text-text-500 mb-1\">\n {message.senderName}\n </Text>\n )}\n <div\n className={cn(\n 'max-w-[80%] rounded-2xl whitespace-pre-wrap break-words',\n // Image-only messages show the image without the colored bubble\n message.content\n ? cn(\n 'px-4 py-2',\n own\n ? 'bg-primary-500 text-white rounded-br-md'\n : 'bg-background-100 text-text-900 rounded-bl-md'\n )\n : 'text-text-900'\n )}\n >\n {message.content && (\n <MessageText\n text={message.content}\n onOpenArticle={\n onOpenArticle ? (id) => onOpenArticle({ id }) : undefined\n }\n />\n )}\n {message.attachments.length > 0 && (\n <AttachmentList items={message.attachments} />\n )}\n </div>\n {message.faq && (\n <FaqReference\n faq={message.faq}\n faqArticleUrl={faqArticleUrl}\n onOpenArticle={onOpenArticle}\n />\n )}\n <Text size=\"xs\" className=\"text-text-400 mt-1\">\n {message.pending ? 'Enviando…' : formatTime(message.createdAt)}\n </Text>\n </div>\n );\n};\n\n/**\n * Floating support chat (native support, the Zendesk replacement).\n * Renders nothing when disabled or when the institution uses Zendesk.\n */\nexport const SupportChatWidget = ({\n flags,\n ...props\n}: SupportChatWidgetProps) => {\n const hidden =\n flags?.enabled === false ||\n flags?.chat === false ||\n flags?.supportType === SupportType.ZENDESK;\n\n if (hidden) return null;\n return <SupportChatWidgetContent {...props} />;\n};\n\nconst SupportChatWidgetContent = ({\n adapter,\n channel,\n institutionId,\n user,\n theme,\n onOpen,\n onSend,\n onClose,\n faqArticleUrl,\n}: Omit<SupportChatWidgetProps, 'flags'>) => {\n const [windowState, setWindowState] =\n useState<SupportChatWindowState>('closed');\n const [draft, setDraft] = useState('');\n const [files, setFiles] = useState<File[]>([]);\n const [fileError, setFileError] = useState<string | null>(null);\n const [unread, setUnread] = useState(0);\n // FAQ article open inside the chat (the conversation stays as it was)\n const [article, setArticle] = useState<{ id: string; title?: string } | null>(\n null\n );\n const [expanded, setExpanded] = useState(false);\n const listRef = useRef<HTMLDivElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const seenCount = useRef(0);\n\n const {\n messages,\n sending,\n error,\n closed,\n restored,\n send,\n updateContact,\n reset,\n } = useSupportChat({\n adapter,\n channel,\n institutionId,\n user,\n });\n const [savedContactRequests, setSavedContactRequests] = useState<string[]>(\n []\n );\n\n // Latest AI request for name/e-mail: the form goes under it, and the\n // composer waits until it is sent\n const contactRequest = updateContact\n ? [...messages].reverse().find((message) => message.contactRequest?.length)\n : undefined;\n const awaitingContact =\n !!contactRequest && !savedContactRequests.includes(contactRequest.id);\n\n const isOpen = windowState === 'open';\n\n const canOpenArticle = !!adapter.getArticle && !!institutionId;\n const loadArticle = useCallback(\n (articleId: string) => {\n if (!adapter.getArticle || !institutionId) {\n return Promise.reject(new Error('Artigo indisponível'));\n }\n // Called on the adapter so a class-based adapter keeps its `this`\n return adapter.getArticle(institutionId, articleId);\n },\n [adapter, institutionId]\n );\n const openArticle = canOpenArticle\n ? (target: { id: string; title?: string }) => {\n setArticle(target);\n setExpanded(true);\n }\n : undefined;\n const closeArticle = () => {\n setArticle(null);\n setExpanded(false);\n };\n const title = theme?.title ?? 'Suporte';\n const left = theme?.position === 'bottom-left';\n const accentStyle = theme?.primaryColor\n ? { backgroundColor: theme.primaryColor }\n : undefined;\n\n // Count the support replies that arrive while the window is not visible\n useEffect(() => {\n const fresh = messages.slice(seenCount.current);\n seenCount.current = messages.length;\n // A resumed conversation was already read before the reload\n if (isOpen || (restored && fresh.length === messages.length)) return;\n const replies = fresh.filter((message) => !isOwnMessage(message)).length;\n if (replies) setUnread((count) => count + replies);\n }, [messages, isOpen, restored]);\n\n useEffect(() => {\n if (!isOpen || article) return;\n setUnread(0);\n listRef.current?.scrollTo?.({ top: listRef.current.scrollHeight });\n }, [isOpen, messages, article]);\n\n const open = () => {\n setWindowState('open');\n onOpen?.();\n };\n\n const close = () => {\n setWindowState('closed');\n onClose?.();\n };\n\n /** Drop a finished conversation so the next message opens a new ticket */\n const startOver = () => {\n setDraft('');\n setFiles([]);\n setFileError(null);\n setUnread(0);\n seenCount.current = 0;\n setSavedContactRequests([]);\n reset();\n };\n\n const submit = async () => {\n const content = draft;\n const pending = files;\n if (!content.trim() && pending.length === 0) return;\n setDraft('');\n setFiles([]);\n setFileError(null);\n const message = await send(content, pending);\n if (message) {\n onSend?.(message);\n } else {\n setDraft(content);\n setFiles(pending);\n }\n };\n\n const handleFiles = (event: ChangeEvent<HTMLInputElement>) => {\n const { accepted, problem } = pickAttachments(\n files,\n Array.from(event.target.files ?? [])\n );\n setFiles(accepted);\n setFileError(problem);\n event.target.value = '';\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n void submit();\n }\n };\n\n return (\n <div\n className={cn(\n 'fixed bottom-4 z-50 flex flex-col gap-3',\n left ? 'left-4 items-start' : 'right-4 items-end'\n )}\n >\n {isOpen && (\n <section\n role=\"dialog\"\n aria-label={title}\n className={cn(\n 'max-w-[calc(100vw-2rem)] max-h-[calc(100vh-6rem)] flex flex-col bg-background rounded-2xl shadow-lg border border-border-100 overflow-hidden transition-[width,height]',\n expanded ? 'w-[720px] h-[615px]' : 'w-[360px] h-[520px]'\n )}\n >\n {article ? (\n <SupportChatArticleView\n articleId={article.id}\n initialTitle={article.title}\n loadArticle={loadArticle}\n expanded={expanded}\n onToggleExpanded={() => setExpanded((value) => !value)}\n onBack={closeArticle}\n externalUrl={faqArticleUrl?.(article.id)}\n accentStyle={accentStyle}\n />\n ) : (\n <>\n <header\n className=\"flex items-center justify-between px-4 py-3 bg-primary-500 text-white\"\n style={accentStyle}\n >\n <Text size=\"md\" weight=\"semibold\" color=\"\">\n {title}\n </Text>\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"raw\"\n aria-label=\"Minimizar\"\n onClick={() => setWindowState('minimized')}\n className=\"p-1 rounded hover:bg-white/20\"\n >\n <MinusIcon size={18} />\n </Button>\n <Button\n variant=\"raw\"\n aria-label=\"Fechar\"\n onClick={close}\n className=\"p-1 rounded hover:bg-white/20\"\n >\n <XIcon size={18} />\n </Button>\n </div>\n </header>\n\n <div ref={listRef} className=\"flex-1 overflow-y-auto px-4 py-3\">\n {messages.length === 0 ? (\n <Text size=\"sm\" className=\"text-text-500 text-center mt-8\">\n Olá! Como podemos te ajudar?\n </Text>\n ) : (\n messages.map((message) => (\n <div key={message.id}>\n <MessageBubble\n message={message}\n faqArticleUrl={faqArticleUrl}\n onOpenArticle={openArticle}\n />\n {updateContact &&\n message.id === contactRequest?.id &&\n message.contactRequest && (\n <SupportChatContactForm\n fields={message.contactRequest}\n accentColor={theme?.primaryColor}\n onSubmit={async (contact) => {\n const ok = await updateContact(contact);\n if (ok) {\n setSavedContactRequests((ids) => [\n ...ids,\n message.id,\n ]);\n }\n return ok;\n }}\n />\n )}\n </div>\n ))\n )}\n </div>\n\n {(fileError || error) && (\n <Text\n size=\"xs\"\n role=\"alert\"\n className=\"px-4 py-2 text-error-700 bg-error-50\"\n >\n {fileError ?? error}\n </Text>\n )}\n\n {closed ? (\n <div className=\"flex flex-col items-center gap-2 px-4 py-3 border-t border-border-100\">\n <Text size=\"sm\" color=\"text-text-500\" className=\"text-center\">\n Este atendimento foi encerrado.\n </Text>\n <Button variant=\"link\" size=\"small\" onClick={startOver}>\n Iniciar nova conversa\n </Button>\n </div>\n ) : (\n <form\n className=\"flex flex-col gap-2 px-3 py-3 border-t border-border-100\"\n onSubmit={(event) => {\n event.preventDefault();\n void submit();\n }}\n >\n {files.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {files.map((file, index) => (\n <PendingAttachment\n key={`${file.name}-${index}`}\n file={file}\n onRemove={() =>\n setFiles((list) =>\n list.filter((_, i) => i !== index)\n )\n }\n />\n ))}\n </div>\n )}\n <div className=\"flex items-end gap-2\">\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={SUPPORT_CHAT_ACCEPT}\n multiple\n hidden\n data-testid=\"support-chat-file-input\"\n onChange={handleFiles}\n />\n <Button\n variant=\"raw\"\n aria-label=\"Anexar arquivo\"\n disabled={\n sending ||\n awaitingContact ||\n files.length >= SUPPORT_CHAT_MAX_ATTACHMENTS\n }\n onClick={() => fileInputRef.current?.click()}\n className=\"p-2 rounded-full text-text-600 hover:bg-background-100 disabled:opacity-50\"\n >\n <PaperclipIcon size={20} />\n </Button>\n <textarea\n aria-label=\"Mensagem\"\n placeholder={\n awaitingContact\n ? 'Preencha seus dados acima'\n : 'Digite sua mensagem'\n }\n disabled={awaitingContact}\n rows={1}\n value={draft}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={handleKeyDown}\n className=\"flex-1 resize-none rounded-xl border border-border-300 px-3 py-2 text-sm text-text-900 focus:outline-none focus:border-primary-500 max-h-28\"\n />\n <Button\n variant=\"raw\"\n type=\"submit\"\n aria-label=\"Enviar\"\n disabled={\n sending ||\n awaitingContact ||\n (!draft.trim() && files.length === 0)\n }\n className=\"p-2 rounded-full bg-primary-500 text-white disabled:opacity-50\"\n style={accentStyle}\n >\n <PaperPlaneTiltIcon size={18} />\n </Button>\n </div>\n </form>\n )}\n </>\n )}\n </section>\n )}\n\n {!isOpen && (\n <Button\n variant=\"raw\"\n aria-label={`Abrir ${title.toLowerCase()}`}\n onClick={open}\n className=\"relative w-14 h-14 rounded-full bg-primary-500 text-white shadow-lg flex items-center justify-center hover:brightness-110\"\n style={accentStyle}\n >\n <ChatCircleDotsIcon size={28} />\n {unread > 0 && (\n <Text\n as=\"span\"\n size=\"xs\"\n color=\"text-white\"\n aria-label={formatNewMessagesLabel(unread)}\n className=\"absolute -top-1 -right-1 min-w-5 h-5 px-1 rounded-full bg-error-500 flex items-center justify-center\"\n >\n {unread}\n </Text>\n )}\n </Button>\n )}\n </div>\n );\n};\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n SUPPORT_CHAT_AUTHOR,\n SUPPORT_CHAT_GUEST_SENDER_ID,\n SUPPORT_CHAT_STATUS,\n type SupportChatAdapter,\n type SupportChatContact,\n type SupportChatEvent,\n type SupportChatMessage,\n type SupportChatTicket,\n type SupportChatUser,\n} from '../types/supportChat';\n\nexport interface UseSupportChatConfig {\n adapter: SupportChatAdapter;\n channel: string;\n institutionId?: string;\n user?: SupportChatUser;\n}\n\nexport interface UseSupportChatReturn {\n ticket: SupportChatTicket | null;\n messages: SupportChatMessage[];\n sending: boolean;\n error: string | null;\n /** Ticket resolved: no more messages can be sent */\n closed: boolean;\n /** The conversation was resumed from an earlier page load */\n restored: boolean;\n /** Send a message (text and/or files); the first one opens the ticket */\n send: (content: string, files?: File[]) => Promise<SupportChatMessage | null>;\n /**\n * Save the contact typed in the form shown under the AI request. Resolves\n * false (and sets `error`) when it fails. Undefined when the adapter cannot\n * save it.\n */\n updateContact?: (contact: SupportChatContact) => Promise<boolean>;\n /** Forget the current conversation */\n reset: () => void;\n}\n\nlet pendingSequence = 0;\n\n/**\n * Message shown right away while it is being sent (optimistic rendering).\n * Images use local object URLs, released when the real message arrives.\n */\nconst buildPendingMessage = (\n content: string,\n files: File[],\n user?: SupportChatUser\n): SupportChatMessage => {\n pendingSequence += 1;\n return {\n id: `pending-${pendingSequence}`,\n senderId: SUPPORT_CHAT_GUEST_SENDER_ID,\n senderName: user?.name || 'Você',\n senderPhoto: null,\n senderRole: user?.email\n ? SUPPORT_CHAT_AUTHOR.USER\n : SUPPORT_CHAT_AUTHOR.GUEST,\n content,\n messageType: 'text',\n createdAt: new Date().toISOString(),\n attachments: files.map((file, index) => ({\n id: `pending-${pendingSequence}-${index}`,\n url: URL.createObjectURL(file),\n fileName: file.name,\n mimeType: file.type,\n size: file.size,\n })),\n pending: true,\n };\n};\n\nconst releasePending = (message: SupportChatMessage) =>\n message.attachments.forEach((item) => URL.revokeObjectURL(item.url));\n\nconst getErrorMessage = (error: unknown) =>\n error instanceof Error ? error.message : 'Não foi possível enviar a mensagem';\n\n/** Add or replace a message, keeping the list ordered by arrival */\nconst upsertMessage = (\n list: SupportChatMessage[],\n message: SupportChatMessage\n): SupportChatMessage[] =>\n list.some((item) => item.id === message.id)\n ? list.map((item) => (item.id === message.id ? message : item))\n : [...list, message];\n\n/**\n * State of a support conversation on top of a SupportChatAdapter.\n * The REST-like calls write; the subscription only applies the events,\n * so a message received both ways is stored once.\n */\nexport const useSupportChat = ({\n adapter,\n channel,\n institutionId,\n user,\n}: UseSupportChatConfig): UseSupportChatReturn => {\n const [ticket, setTicket] = useState<SupportChatTicket | null>(null);\n const [messages, setMessages] = useState<SupportChatMessage[]>([]);\n const [sending, setSending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [restored, setRestored] = useState(false);\n const sendingRef = useRef(false);\n\n const ticketId = ticket?.id;\n const userEmail = user?.email;\n // Read by the restore, which must not replace a conversation started meanwhile\n const ticketRef = useRef(ticket);\n ticketRef.current = ticket;\n\n // Resume the unresolved conversation of an earlier page load, unless the\n // person already started a new one meanwhile\n useEffect(() => {\n if (!adapter.restoreConversation) return;\n let active = true;\n adapter\n .restoreConversation({ email: userEmail })\n .then((resumed) => {\n if (!active || !resumed) return;\n if (ticketRef.current || sendingRef.current) return;\n setTicket(resumed.ticket);\n setMessages(resumed.messages);\n setRestored(true);\n })\n .catch(() => {\n // Not resumed: the next message opens a new ticket\n });\n return () => {\n active = false;\n };\n }, [adapter, userEmail]);\n\n const closed = ticket?.status === SUPPORT_CHAT_STATUS.FECHADO;\n\n // A resolved conversation is not resumed on the next load\n useEffect(() => {\n if (closed) adapter.forgetConversation?.();\n }, [adapter, closed]);\n\n useEffect(() => {\n if (!ticketId) return;\n\n const handleEvent = (event: SupportChatEvent) => {\n switch (event.type) {\n case 'new_message':\n setMessages((list) => upsertMessage(list, event.payload.message));\n break;\n case 'message_updated':\n setMessages((list) =>\n list.map((item) =>\n item.id === event.payload.messageId\n ? { ...item, content: event.payload.content }\n : item\n )\n );\n break;\n case 'ticket_updated': {\n const { ticketId: id, ...rest } = event.payload;\n setTicket((current) =>\n current?.id === id ? { ...current, ...rest } : current\n );\n break;\n }\n case 'error':\n setError(event.payload.message);\n break;\n }\n };\n\n return adapter.subscribe(ticketId, handleEvent);\n }, [adapter, ticketId]);\n\n const send = useCallback(\n async (content: string, files: File[] = []) => {\n const text = content.trim();\n if ((!text && files.length === 0) || sendingRef.current) return null;\n\n sendingRef.current = true;\n setSending(true);\n setError(null);\n\n const pending = buildPendingMessage(text, files, user);\n setMessages((list) => [...list, pending]);\n // Swap the optimistic message for the confirmed ones\n const confirm = (confirmed: SupportChatMessage[]) =>\n setMessages((list) =>\n confirmed.reduce(\n upsertMessage,\n list.filter((item) => item.id !== pending.id)\n )\n );\n\n try {\n if (ticketId) {\n const message = await adapter.sendMessage(ticketId, text, files);\n confirm([message]);\n return message;\n }\n\n const started = await adapter.startConversation({\n message: text,\n channel,\n institutionId,\n requesterName: user?.name,\n email: user?.email,\n files,\n });\n setTicket(started.ticket);\n confirm(started.messages);\n return started.messages[started.messages.length - 1] ?? null;\n } catch (sendError) {\n setMessages((list) => list.filter((item) => item.id !== pending.id));\n setError(getErrorMessage(sendError));\n return null;\n } finally {\n releasePending(pending);\n sendingRef.current = false;\n setSending(false);\n }\n },\n [adapter, channel, institutionId, ticketId, user]\n );\n\n const updateContact = useCallback(\n async (contact: SupportChatContact) => {\n if (!ticketId || !adapter.updateContact) return false;\n setError(null);\n try {\n await adapter.updateContact(ticketId, contact);\n return true;\n } catch (contactError) {\n setError(\n (contactError as { response?: { status?: number } } | null)?.response\n ?.status === 409\n ? 'O e-mail deste atendimento já foi informado'\n : 'Não foi possível salvar seus dados. Tente de novo.'\n );\n return false;\n }\n },\n [adapter, ticketId]\n );\n\n const reset = useCallback(() => {\n adapter.forgetConversation?.();\n setTicket(null);\n setMessages([]);\n setError(null);\n setRestored(false);\n }, [adapter]);\n\n return {\n ticket,\n messages,\n sending,\n error,\n closed,\n restored,\n send,\n updateContact: adapter.updateContact ? updateContact : undefined,\n reset,\n };\n};\n","import { useState, type FormEvent } from 'react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport Input from '../Input/Input';\nimport type {\n SupportChatContact,\n SupportChatContactField,\n} from '../../types/supportChat';\n\nexport interface SupportChatContactFormProps {\n /** Fields the AI asked for, in order (one step each) */\n fields: SupportChatContactField[];\n /** Saves the contact; resolves true when it worked */\n onSubmit: (contact: SupportChatContact) => Promise<boolean>;\n /** Accent color of the theme, for the button */\n accentColor?: string;\n}\n\nconst STEPS: Record<\n SupportChatContactField,\n { label: string; type: string; autoComplete: string; placeholder: string }\n> = {\n name: {\n label: 'Seu nome',\n type: 'text',\n autoComplete: 'name',\n placeholder: 'Como podemos te chamar?',\n },\n email: {\n label: 'Seu e-mail',\n type: 'email',\n autoComplete: 'email',\n placeholder: 'nome@exemplo.com',\n },\n};\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Problem with a typed value, or null when it can be sent */\nconst validate = (\n field: SupportChatContactField,\n value: string\n): string | null => {\n if (field === 'name') {\n return value.length >= 2 ? null : 'Digite seu nome';\n }\n return EMAIL_PATTERN.test(value) ? null : 'Digite um e-mail válido';\n};\n\n/**\n * Step-by-step form for the contact the AI asks on the first contact (name,\n * then e-mail), shown under the AI message like the Zendesk pre-chat form.\n * The values go to the backend as fields, not as a chat message. Once sent,\n * it shows what was saved.\n */\nexport const SupportChatContactForm = ({\n fields,\n onSubmit,\n accentColor,\n}: SupportChatContactFormProps) => {\n const [step, setStep] = useState(0);\n const [values, setValues] = useState<Record<string, string>>({});\n const [problem, setProblem] = useState<string | null>(null);\n const [saving, setSaving] = useState(false);\n const [saved, setSaved] = useState(false);\n\n const field = fields[step];\n const config = STEPS[field];\n const value = values[field] ?? '';\n const isLast = step === fields.length - 1;\n\n if (saved) {\n return (\n <div\n data-testid=\"support-chat-contact-saved\"\n className=\"mb-3 ml-1 flex max-w-[85%] flex-col gap-1 rounded-2xl border border-border-100 px-4 py-3\"\n >\n {fields.map((item) => (\n <Text key={item} size=\"xs\" color=\"text-text-600\">\n {STEPS[item].label}:{' '}\n <Text as=\"span\" size=\"xs\" weight=\"semibold\" color=\"text-text-900\">\n {values[item]}\n </Text>\n </Text>\n ))}\n </div>\n );\n }\n\n const advance = async (event: FormEvent) => {\n event.preventDefault();\n const typed = field === 'email' ? value.trim().toLowerCase() : value.trim();\n const error = validate(field, typed);\n setProblem(error);\n if (error) return;\n\n const next = { ...values, [field]: typed };\n setValues(next);\n if (!isLast) {\n setStep(step + 1);\n return;\n }\n\n setSaving(true);\n const ok = await onSubmit({\n ...(next.name ? { requesterName: next.name } : {}),\n ...(next.email ? { email: next.email } : {}),\n });\n setSaving(false);\n if (ok) setSaved(true);\n };\n\n const inputId = `support-chat-contact-${field}`;\n\n return (\n <form\n aria-label=\"Seus dados\"\n noValidate\n onSubmit={(event) => void advance(event)}\n className=\"mb-3 ml-1 flex max-w-[85%] flex-col gap-3 rounded-2xl border border-border-100 px-4 py-3\"\n >\n <Input\n id={inputId}\n key={field}\n label={config.label}\n variant=\"rounded\"\n size=\"small\"\n type={config.type}\n autoComplete={config.autoComplete}\n placeholder={config.placeholder}\n value={value}\n autoFocus\n disabled={saving}\n errorMessage={problem ?? undefined}\n onChange={(event) =>\n setValues((current) => ({ ...current, [field]: event.target.value }))\n }\n />\n <div className=\"flex items-center justify-between\">\n <Text size=\"xs\" color=\"text-text-500\">\n {fields.length > 1 ? `${step + 1} de ${fields.length}` : ''}\n </Text>\n <Button\n type=\"submit\"\n size=\"small\"\n disabled={saving}\n style={accentColor ? { backgroundColor: accentColor } : undefined}\n >\n {isLast ? 'Enviar' : 'Avançar'}\n </Button>\n </div>\n </form>\n );\n};\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport {\n ArrowLeftIcon,\n ArrowSquareOutIcon,\n ArrowsInSimpleIcon,\n ArrowsOutSimpleIcon,\n} from '@phosphor-icons/react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport { ArticleContent } from '../ArticleContent/ArticleContent';\nimport { embedVideoLinks } from '../../utils/videoEmbed';\nimport type { SupportChatArticle } from '../../types/supportChat';\nimport { cn } from '../../utils/utils';\n\nexport interface SupportChatArticleViewProps {\n /** Article to show */\n articleId: string;\n /** Title known from the chat, shown while the article loads */\n initialTitle?: string;\n /** Loads the article (adapter.getArticle bound to the institution) */\n loadArticle: (articleId: string) => Promise<SupportChatArticle>;\n /** Whether the chat window is in the wide mode */\n expanded: boolean;\n onToggleExpanded: () => void;\n /** Back to the conversation (it is kept as it was) */\n onBack: () => void;\n /** Page of the article in the help center, for \"Abrir em nova aba\" */\n externalUrl?: string;\n /** Header background (theme) */\n accentStyle?: { backgroundColor: string };\n}\n\nconst formatUpdatedAt = (iso: string) =>\n new Date(iso).toLocaleDateString('pt-BR', {\n day: '2-digit',\n month: '2-digit',\n year: 'numeric',\n });\n\n/**\n * FAQ article read inside the support chat, like the Zendesk messenger: the\n * conversation is replaced by the article (no new tab), with back,\n * expand/reduce and open-in-new-tab controls. The big title moves to the\n * header once it scrolls out of view. YouTube/Vimeo links of the article\n * become players.\n */\nexport const SupportChatArticleView = ({\n articleId,\n initialTitle,\n loadArticle,\n expanded,\n onToggleExpanded,\n onBack,\n externalUrl,\n accentStyle,\n}: SupportChatArticleViewProps) => {\n const [article, setArticle] = useState<SupportChatArticle | null>(null);\n const [failed, setFailed] = useState(false);\n const [titleHidden, setTitleHidden] = useState(false);\n const titleRef = useRef<HTMLHeadingElement>(null);\n\n useEffect(() => {\n let active = true;\n setArticle(null);\n setFailed(false);\n loadArticle(articleId)\n .then((loaded) => {\n if (active) setArticle(loaded);\n })\n .catch(() => {\n if (active) setFailed(true);\n });\n return () => {\n active = false;\n };\n }, [articleId, loadArticle]);\n\n // The big title leaves the screen → show it in the header\n useEffect(() => {\n const title = titleRef.current;\n if (!title || typeof IntersectionObserver === 'undefined') {\n return undefined;\n }\n const observer = new IntersectionObserver(([entry]) =>\n setTitleHidden(!entry.isIntersecting)\n );\n observer.observe(title);\n return () => observer.disconnect();\n }, [article]);\n\n const html = useMemo(\n () => (article ? embedVideoLinks(article.body) : ''),\n [article]\n );\n const title = article?.title ?? initialTitle ?? 'Artigo';\n\n return (\n <div\n data-testid=\"support-chat-article\"\n className=\"flex min-h-0 flex-1 flex-col\"\n >\n <header\n className=\"flex items-center gap-1 px-2 py-2 bg-primary-500 text-white\"\n style={accentStyle}\n >\n <Button\n variant=\"raw\"\n aria-label=\"Voltar para a janela de mensagens\"\n onClick={onBack}\n className=\"p-1 rounded hover:bg-white/20\"\n >\n <ArrowLeftIcon size={18} />\n </Button>\n <Text\n as=\"span\"\n size=\"sm\"\n weight=\"semibold\"\n color=\"\"\n aria-hidden={!titleHidden || undefined}\n className={cn(\n 'min-w-0 flex-1 truncate transition-opacity',\n titleHidden ? 'opacity-100' : 'opacity-0'\n )}\n >\n {title}\n </Text>\n <Button\n variant=\"raw\"\n aria-label={expanded ? 'Reduzir artigo' : 'Expandir artigo'}\n onClick={onToggleExpanded}\n className=\"p-1 rounded hover:bg-white/20\"\n >\n {expanded ? (\n <ArrowsInSimpleIcon size={18} />\n ) : (\n <ArrowsOutSimpleIcon size={18} />\n )}\n </Button>\n {externalUrl && (\n <a\n href={externalUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n aria-label=\"Abrir em nova aba\"\n className=\"p-1 rounded hover:bg-white/20\"\n >\n <ArrowSquareOutIcon size={18} />\n </a>\n )}\n </header>\n\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n <Text\n as=\"h2\"\n ref={titleRef}\n size=\"xl\"\n weight=\"bold\"\n color=\"text-text-950\"\n >\n {title}\n </Text>\n {article?.updatedAt && (\n <Text size=\"xs\" color=\"text-text-500\" className=\"mt-1\">\n Atualizado em {formatUpdatedAt(article.updatedAt)}\n </Text>\n )}\n\n <div className=\"mt-4\">\n {failed && (\n <Text size=\"sm\" role=\"alert\" color=\"text-error-700\">\n Não foi possível abrir o artigo.\n </Text>\n )}\n {!failed && !article && (\n <Text size=\"sm\" color=\"text-text-500\">\n Carregando…\n </Text>\n )}\n {article && <ArticleContent html={html} />}\n </div>\n </div>\n </div>\n );\n};\n","import { useEffect, useState, type ReactNode } from 'react';\nimport {\n FileIcon,\n FilePdfIcon,\n FileVideoIcon,\n XIcon,\n} from '@phosphor-icons/react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport {\n SUPPORT_CHAT_ATTACHMENT_RULES,\n SUPPORT_CHAT_MAX_ATTACHMENTS,\n type SupportChatAttachment,\n} from '../../types/supportChat';\n\n/** Value of the `accept` attribute of the file picker */\nexport const SUPPORT_CHAT_ACCEPT = Object.keys(\n SUPPORT_CHAT_ATTACHMENT_RULES\n).join(',');\n\nconst isImage = (mimeType?: string) => Boolean(mimeType?.startsWith('image/'));\nconst isVideo = (mimeType?: string) => Boolean(mimeType?.startsWith('video/'));\n\nconst formatSize = (bytes: number) => `${Math.round(bytes / (1024 * 1024))} MB`;\n\n/**\n * Keep the files the backend accepts (type, size and count) and say why the\n * others were left out.\n * @param current - Files already picked\n * @param picked - Files just chosen\n * @returns The accepted files and the last problem found, if any\n */\nexport const pickAttachments = (current: File[], picked: File[]) => {\n const accepted = [...current];\n let problem: string | null = null;\n for (const file of picked) {\n const maxBytes = SUPPORT_CHAT_ATTACHMENT_RULES[file.type];\n if (!maxBytes) {\n problem =\n 'Envie imagens (JPG, PNG, GIF, WEBP), PDF ou vídeo (MP4, WEBM, MOV)';\n } else if (file.size > maxBytes) {\n problem = `${file.name} passa de ${formatSize(maxBytes)}`;\n } else if (accepted.length >= SUPPORT_CHAT_MAX_ATTACHMENTS) {\n problem = `Máximo de ${SUPPORT_CHAT_MAX_ATTACHMENTS} arquivos por mensagem`;\n } else {\n accepted.push(file);\n }\n }\n return { accepted, problem };\n};\n\nconst FileTypeIcon = ({ mimeType }: { mimeType?: string }) => {\n if (mimeType === 'application/pdf') return <FilePdfIcon size={18} />;\n if (isVideo(mimeType)) return <FileVideoIcon size={18} />;\n return <FileIcon size={18} />;\n};\n\n/** Files of a message: images and videos inline, other files as links */\nexport const AttachmentList = ({\n items,\n}: {\n items: SupportChatAttachment[];\n}) => (\n <div className=\"flex flex-wrap gap-2 first:mt-0 mt-1\">\n {items.map((item) => {\n if (isImage(item.mimeType)) {\n return (\n <a key={item.id} href={item.url} target=\"_blank\" rel=\"noreferrer\">\n <img\n src={item.url}\n alt={item.fileName}\n className=\"max-w-[200px] max-h-[200px] rounded-lg object-cover\"\n />\n </a>\n );\n }\n if (isVideo(item.mimeType)) {\n return (\n <video\n key={item.id}\n src={item.url}\n controls\n preload=\"metadata\"\n aria-label={item.fileName}\n className=\"max-w-[240px] max-h-[200px] rounded-lg bg-black\"\n />\n );\n }\n return (\n <a\n key={item.id}\n href={item.url}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"flex items-center gap-1 underline\"\n >\n <FileTypeIcon mimeType={item.mimeType} />\n <Text as=\"span\" size=\"sm\" color=\"\">\n {item.fileName}\n </Text>\n </a>\n );\n })}\n </div>\n);\n\n/** A file picked but not sent yet: thumbnail for images, name for the rest */\nexport const PendingAttachment = ({\n file,\n onRemove,\n}: {\n file: File;\n onRemove: () => void;\n}) => {\n const image = isImage(file.type);\n const [url, setUrl] = useState('');\n useEffect(() => {\n if (!image) return undefined;\n const objectUrl = URL.createObjectURL(file);\n setUrl(objectUrl);\n return () => URL.revokeObjectURL(objectUrl);\n }, [file, image]);\n\n return (\n <div className=\"relative\">\n {image ? (\n url && (\n <img\n src={url}\n alt={file.name}\n className=\"w-14 h-14 rounded-lg object-cover border border-border-100\"\n />\n )\n ) : (\n <div className=\"flex h-14 max-w-[160px] items-center gap-1 rounded-lg border border-border-100 px-2 text-text-700\">\n <FileTypeIcon mimeType={file.type} />\n <Text as=\"span\" size=\"xs\" className=\"truncate\">\n {file.name}\n </Text>\n </div>\n )}\n <Button\n variant=\"raw\"\n aria-label={`Remover ${file.name}`}\n onClick={onRemove}\n className=\"absolute -top-1 -right-1 w-5 h-5 rounded-full bg-text-700 text-white flex items-center justify-center\"\n >\n <XIcon size={12} />\n </Button>\n </div>\n );\n};\n\n/** http(s) addresses inside a message */\nconst URL_PATTERN = /https?:\\/\\/[^\\s<>\"]+/g;\n\n/** Punctuation that ends a sentence, not the address */\nconst TRAILING_PUNCTUATION = /[.,;:!?)\\]]+$/;\n\n/** A FAQ article page: `/artigo/<uuid>` */\nconst ARTICLE_PATH =\n /\\/artigo\\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;\n\n/**\n * Message text with its addresses as links. A link to a FAQ article opens in\n * the chat (when `onOpenArticle` is given); the others open a new tab.\n */\nexport const MessageText = ({\n text,\n onOpenArticle,\n}: {\n text: string;\n onOpenArticle?: (articleId: string) => void;\n}) => {\n const parts: ReactNode[] = [];\n let cursor = 0;\n for (const match of text.matchAll(URL_PATTERN)) {\n const raw = match[0];\n const url = raw.replace(TRAILING_PUNCTUATION, '');\n const start = match.index ?? 0;\n if (start > cursor) parts.push(text.slice(cursor, start));\n\n const articleId = onOpenArticle ? ARTICLE_PATH.exec(url)?.[1] : undefined;\n parts.push(\n <a\n key={`${start}-${url}`}\n href={url}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"underline break-all\"\n onClick={\n articleId\n ? (event) => {\n event.preventDefault();\n onOpenArticle?.(articleId);\n }\n : undefined\n }\n >\n {url}\n </a>\n );\n cursor = start + url.length;\n }\n if (cursor < text.length) parts.push(text.slice(cursor));\n\n return (\n <Text size=\"sm\" color=\"\" className=\"whitespace-pre-wrap\">\n {parts}\n </Text>\n );\n};\n","import { useMemo } from 'react';\nimport { SupportChatWidget } from './SupportChatWidget';\nimport { createGuestSupportChatAdapter } from './guestSupportChatAdapter';\nimport { useSupportFeatureFlag } from '../../hooks/useSupportFeatureFlag';\nimport { useAppStore } from '../../store/appStore';\nimport { useAuthStore } from '../../store/authStore';\nimport { SupportType, type SupportApiClient } from '../../types/support';\nimport type {\n SupportChatHttpClient,\n SupportChatTheme,\n} from '../../types/supportChat';\n\nexport interface NativeSupportChatProps {\n /** Client of the backend API (the app axios instance fits as it is) */\n apiClient: SupportApiClient & SupportChatHttpClient;\n /** App the chat runs in, sent with the ticket (e.g. \"aluno\", \"login\") */\n channel: string;\n /**\n * Extra condition of the app to show the chat, on top of the SUPPORT flag\n * (e.g. logged in and with the support module). Default: true.\n */\n enabled?: boolean;\n /**\n * Link of a FAQ article. Default: the FAQ address of the SUPPORT flag\n * (`<faqUrl>/artigo/<id>`).\n */\n faqArticleUrl?: (articleId: string) => string;\n /** Support WebSocket; without it the chat polls the REST API */\n wsUrl?: string;\n theme?: SupportChatTheme;\n}\n\n/**\n * Native support chat ready to drop in an app: reads the SUPPORT feature flag\n * of the institution (from the app store), the logged user (from the auth\n * store) and talks to the support API by itself.\n *\n * Shown only when the institution uses the native support with the internal\n * support and its chat switched on in the backoffice, once the flag is read,\n * and when `enabled`. With Zendesk it renders nothing (the app loads the\n * Zendesk widget). A logged user opens the ticket with their name and e-mail;\n * a visitor gives them to the AI on the first contact.\n *\n * @example\n * <NativeSupportChat apiClient={api} channel=\"aluno\" enabled={hasSupport} />\n */\nexport const NativeSupportChat = ({\n apiClient,\n channel,\n enabled = true,\n faqArticleUrl,\n wsUrl,\n theme,\n}: NativeSupportChatProps) => {\n const { institutionId } = useAppStore();\n const userName = useAuthStore(\n (state) => state.user?.name ?? state.sessionInfo?.userName\n );\n const userEmail = useAuthStore((state) => state.user?.email);\n const flag = useSupportFeatureFlag({ apiClient });\n const adapter = useMemo(\n () => createGuestSupportChatAdapter({ apiClient, wsUrl }),\n [apiClient, wsUrl]\n );\n\n const user =\n userName || userEmail ? { name: userName, email: userEmail } : undefined;\n\n return (\n <SupportChatWidget\n adapter={adapter}\n channel={channel}\n institutionId={institutionId || undefined}\n user={user}\n theme={theme}\n faqArticleUrl={faqArticleUrl ?? flag.faqArticleUrl}\n flags={{\n enabled: enabled && !flag.loading && flag.isNative && flag.chatEnabled,\n supportType: flag.isNative ? SupportType.NATIVE : SupportType.ZENDESK,\n }}\n />\n );\n};\n","import { isSupportTicketOrigin } from '../../types/support';\nimport {\n SUPPORT_CHAT_AUTHOR,\n SUPPORT_CHAT_GUEST_SENDER_ID,\n SUPPORT_CHAT_STATUS,\n type SupportChatAdapter,\n type SupportChatAnswerApi,\n type SupportChatArticle,\n type SupportChatEvent,\n type SupportChatHttpClient,\n type SupportChatMessage,\n type SupportChatTicket,\n} from '../../types/supportChat';\n\nexport interface GuestSupportChatAdapterOptions {\n /** Client of the backend API (e.g. the app axios instance) */\n apiClient: SupportChatHttpClient;\n /**\n * Support WebSocket, including ?channel=support (e.g.\n * wss://bff-hml-chat.analyticaensino.com.br?channel=support).\n * Without it the adapter only polls the REST API.\n */\n wsUrl?: string;\n /** Polling interval while the socket is not connected, in ms */\n pollIntervalMs?: number;\n /**\n * localStorage key of the open conversation, kept so a reload resumes it.\n * Default: `analytica:support-chat`.\n */\n storageKey?: string;\n}\n\n/** Open conversation kept across page loads */\ninterface StoredSupportChatSession {\n ticketId: string;\n token: string;\n name: string;\n /** E-mail of the logged user who opened it; null for a visitor */\n userEmail: string | null;\n /** Opening text the chat does not show (image-only start) */\n hiddenId?: string;\n}\n\nconst DEFAULT_STORAGE_KEY = 'analytica:support-chat';\n\n/** The browser may block storage (private mode, policies): never throw */\nconst readStoredSession = (key: string): StoredSupportChatSession | null => {\n try {\n const raw = globalThis.localStorage?.getItem(key);\n if (!raw) return null;\n const session = JSON.parse(raw) as Partial<StoredSupportChatSession>;\n return session.ticketId && session.token\n ? (session as StoredSupportChatSession)\n : null;\n } catch {\n return null;\n }\n};\n\nconst writeStoredSession = (key: string, session: StoredSupportChatSession) => {\n try {\n globalThis.localStorage?.setItem(key, JSON.stringify(session));\n } catch {\n // Not kept: a reload starts a new conversation, as before\n }\n};\n\nconst clearStoredSession = (key: string) => {\n try {\n globalThis.localStorage?.removeItem(key);\n } catch {\n // Nothing kept to clear\n }\n};\n\nconst statusOf = (error: unknown) =>\n (error as { response?: { status?: number } } | null)?.response?.status;\n\nconst GUEST_TOKEN_HEADER = 'x-support-guest-token';\n\nconst senderNameFor = (role: string, requesterName: string) => {\n if (role === SUPPORT_CHAT_AUTHOR.AGENT) return 'Suporte';\n if (role === SUPPORT_CHAT_AUTHOR.AI) return 'Assistente';\n return requesterName;\n};\n\n/** REST answer → widget message */\nexport const fromSupportAnswer = (\n answer: SupportChatAnswerApi,\n requesterName: string\n): SupportChatMessage => ({\n id: answer.id,\n senderId:\n answer.authorType === SUPPORT_CHAT_AUTHOR.GUEST\n ? SUPPORT_CHAT_GUEST_SENDER_ID\n : answer.authorType.toLowerCase(),\n senderName: senderNameFor(answer.authorType, requesterName),\n senderPhoto: null,\n senderRole: answer.authorType,\n content: answer.answer,\n messageType: 'text',\n createdAt: answer.createdAt,\n attachments: answer.attachments ?? [],\n faq:\n answer.faqArticleId && answer.faqArticleTitle\n ? { id: answer.faqArticleId, title: answer.faqArticleTitle }\n : null,\n contactRequest: answer.contactRequest ?? null,\n});\n\n/**\n * Support chat adapter for visitors (no login), on top of the support API:\n * - POST /support/external opens the ticket and returns the guest token;\n * - GET/POST /support/guest/:id/answer read and send, with the token header;\n * - the chat WebSocket (?channel=support&roomId=&token=) pushes the events,\n * and REST polling covers the time the socket is down.\n */\nexport const createGuestSupportChatAdapter = ({\n apiClient,\n wsUrl,\n pollIntervalMs = 3000,\n storageKey = DEFAULT_STORAGE_KEY,\n}: GuestSupportChatAdapterOptions): SupportChatAdapter => {\n const sessions = new Map<string, { token: string; name: string }>();\n /**\n * Ticket opening text written by the adapter (an image-only start still\n * needs a description). It is the ticket description for the agents, not\n * something the visitor typed, so the chat does not show it.\n */\n const hiddenIds = new Set<string>();\n const isVisible = (message: SupportChatMessage) => !hiddenIds.has(message.id);\n\n const headersFor = (ticketId: string) => ({\n [GUEST_TOKEN_HEADER]: sessions.get(ticketId)?.token ?? '',\n });\n const nameFor = (ticketId: string) =>\n sessions.get(ticketId)?.name ?? 'Visitante';\n\n const listMessages = async (ticketId: string) => {\n const { data } = await apiClient.get<{ data: SupportChatAnswerApi[] }>(\n `/support/guest/${ticketId}/answer`,\n { headers: headersFor(ticketId) }\n );\n return data.data\n .map((answer) => fromSupportAnswer(answer, nameFor(ticketId)))\n .filter(isVisible);\n };\n\n /**\n * Upload each file straight to the storage: ask the backend for a signed\n * URL, then PUT the file there. Returns what the message needs.\n */\n const uploadFiles = (ticketId: string, files: File[]) =>\n Promise.all(\n files.map(async (file) => {\n const { data } = await apiClient.post<{\n data: { uploadUrl: string; key: string; contentType: string };\n }>(\n `/support/guest/${ticketId}/upload-url`,\n { fileName: file.name, fileSize: file.size, mimeType: file.type },\n { headers: headersFor(ticketId) }\n );\n const { uploadUrl, key, contentType } = data.data;\n const response = await fetch(uploadUrl, {\n method: 'PUT',\n headers: { 'Content-Type': contentType },\n body: file,\n });\n if (!response.ok) {\n throw new Error(`Não foi possível enviar ${file.name}`);\n }\n return {\n key,\n fileName: file.name,\n mimeType: contentType,\n size: file.size,\n };\n })\n );\n\n const sendMessage = async (\n ticketId: string,\n content: string,\n files: File[] = []\n ) => {\n const attachments = await uploadFiles(ticketId, files);\n const { data } = await apiClient.post<{ data: SupportChatAnswerApi }>(\n `/support/guest/${ticketId}/answer`,\n { answer: content, attachments },\n { headers: headersFor(ticketId) }\n );\n return fromSupportAnswer(data.data, nameFor(ticketId));\n };\n\n return {\n async startConversation({\n message,\n channel,\n institutionId,\n requesterName,\n email,\n files = [],\n }) {\n // The ticket needs a description; an image-only start uses the names\n const description = message || files.map((file) => file.name).join(', ');\n const { data } = await apiClient.post<{\n data: { id: string; guestToken: string };\n }>('/support/external', {\n description,\n ...(institutionId ? { institutionId } : {}),\n ...(requesterName ? { requesterName } : {}),\n ...(email ? { email } : {}),\n // The app the chat runs in, when it is one the API knows\n ...(isSupportTicketOrigin(channel) ? { origin: channel } : {}),\n });\n const ticketId = data.data.id;\n const name = requesterName || 'Visitante';\n sessions.set(ticketId, { token: data.data.guestToken, name });\n\n // The first message is saved with the ticket; read it back for its id.\n // /support/external takes no files, so they go as the next message.\n let messages = await listMessages(ticketId);\n let hiddenId: string | undefined;\n if (!message && messages[0]) {\n hiddenId = messages[0].id;\n hiddenIds.add(hiddenId);\n messages = messages.slice(1);\n }\n writeStoredSession(storageKey, {\n ticketId,\n token: data.data.guestToken,\n name,\n userEmail: email ?? null,\n hiddenId,\n });\n if (files.length) messages.push(await sendMessage(ticketId, '', files));\n return {\n ticket: {\n id: ticketId,\n status: SUPPORT_CHAT_STATUS.ABERTO,\n handlerType: null,\n problemType: null,\n },\n messages,\n };\n },\n\n sendMessage,\n\n async getArticle(institutionId, articleId) {\n const { data } = await apiClient.get<{\n data: { article: SupportChatArticle };\n }>(`/help-center/${institutionId}/articles/${articleId}`);\n const { id, title, body, updatedAt } = data.data.article;\n return { id, title, body, updatedAt: updatedAt ?? null };\n },\n\n async updateContact(ticketId, contact) {\n if (!apiClient.put) {\n throw new Error('O cliente da API não envia PUT');\n }\n await apiClient.put(`/support/guest/${ticketId}`, contact, {\n headers: headersFor(ticketId),\n });\n const session = sessions.get(ticketId);\n if (session && contact.requesterName) {\n session.name = contact.requesterName;\n const stored = readStoredSession(storageKey);\n if (stored?.ticketId === ticketId) {\n writeStoredSession(storageKey, {\n ...stored,\n name: contact.requesterName,\n });\n }\n }\n },\n\n async restoreConversation({ email }) {\n const stored = readStoredSession(storageKey);\n if (!stored) return null;\n // Another account on this browser: that conversation is not theirs\n if (stored.userEmail !== (email ?? null)) {\n clearStoredSession(storageKey);\n return null;\n }\n\n const { ticketId } = stored;\n sessions.set(ticketId, { token: stored.token, name: stored.name });\n if (stored.hiddenId) hiddenIds.add(stored.hiddenId);\n try {\n const { data } = await apiClient.get<{ data: SupportChatTicket }>(\n `/support/guest/${ticketId}`,\n { headers: headersFor(ticketId) }\n );\n if (data.data.status === SUPPORT_CHAT_STATUS.FECHADO) {\n clearStoredSession(storageKey);\n return null;\n }\n return { ticket: data.data, messages: await listMessages(ticketId) };\n } catch (error) {\n // Ticket gone or token expired: it cannot be resumed. Any other\n // failure (offline) keeps it for the next load.\n if (statusOf(error) === 404) clearStoredSession(storageKey);\n sessions.delete(ticketId);\n return null;\n }\n },\n\n forgetConversation() {\n clearStoredSession(storageKey);\n },\n\n listMessages,\n\n subscribe(ticketId, listener) {\n // id → what the chat shows besides the text; a message seen again with\n // more of it (a stale WebSocket history may lack contactRequest/faq) is\n // delivered again so the chat replaces it\n const seen = new Map<string, string>();\n const extrasOf = (message: SupportChatMessage) =>\n JSON.stringify([message.contactRequest ?? null, message.faq ?? null]);\n const NO_EXTRAS = JSON.stringify([null, null]);\n let socket: WebSocket | null = null;\n let pollTimer: ReturnType<typeof setInterval> | null = null;\n let active = true;\n\n const deliver = (message: SupportChatMessage) => {\n if (!isVisible(message)) return;\n const extras = extrasOf(message);\n const previous = seen.get(message.id);\n // Same as before, or a poorer copy: nothing new to show\n if (previous === extras || (previous && extras === NO_EXTRAS)) return;\n seen.set(message.id, extras);\n listener({ type: 'new_message', payload: { message } });\n };\n\n const poll = async () => {\n try {\n (await listMessages(ticketId)).forEach(deliver);\n } catch {\n // Next tick tries again\n }\n };\n const startPolling = () => {\n if (pollTimer || !active) return;\n void poll();\n pollTimer = setInterval(poll, pollIntervalMs);\n };\n const stopPolling = () => {\n if (pollTimer) clearInterval(pollTimer);\n pollTimer = null;\n };\n\n const handleFrame = (raw: string) => {\n let event: { type?: string; payload?: Record<string, unknown> };\n try {\n event = JSON.parse(raw);\n } catch {\n return;\n }\n const payload = event.payload ?? {};\n if (event.type === 'history') {\n (payload.messages as SupportChatMessage[]).forEach((message) =>\n deliver({ ...message, attachments: message.attachments ?? [] })\n );\n } else if (event.type === 'new_message') {\n const message = payload.message as SupportChatMessage;\n deliver({ ...message, attachments: message.attachments ?? [] });\n } else if (\n event.type === 'message_updated' ||\n event.type === 'ticket_updated'\n ) {\n listener(event as SupportChatEvent);\n } else if (event.type === 'error') {\n listener({\n type: 'error',\n payload: {\n message: String(payload.message_text ?? payload.message ?? ''),\n },\n });\n }\n };\n\n const token = sessions.get(ticketId)?.token;\n if (wsUrl && token) {\n const url = new URL(wsUrl);\n url.searchParams.set('channel', 'support');\n url.searchParams.set('roomId', ticketId);\n url.searchParams.set('token', token);\n socket = new WebSocket(url.toString());\n socket.onopen = () => {\n stopPolling();\n socket?.send(JSON.stringify({ type: 'getInitialData' }));\n // The REST API is the source of truth: a WebSocket deployed apart\n // from it may send a history without contactRequest/faq\n void poll();\n };\n socket.onmessage = (event) => handleFrame(String(event.data));\n socket.onclose = startPolling;\n socket.onerror = startPolling;\n } else {\n startPolling();\n }\n\n return () => {\n active = false;\n stopPolling();\n if (socket) {\n socket.onclose = null;\n socket.close();\n }\n };\n },\n };\n};\n","import {\n SUPPORT_CHAT_AUTHOR,\n SUPPORT_CHAT_GUEST_SENDER_ID,\n SUPPORT_CHAT_HANDLER,\n SUPPORT_CHAT_STATUS,\n type SupportChatAdapter,\n type SupportChatEvent,\n type SupportChatMessage,\n type SupportChatTicket,\n} from '../../types/supportChat';\n\nexport interface MockSupportChatAdapterOptions {\n /** Latency of every call, in ms (default 300) */\n delayMs?: number;\n /** Delay of the automatic agent reply, in ms (default 1500) */\n replyDelayMs?: number;\n /** Text of the agent reply; null disables it */\n replyText?: string | null;\n}\n\nconst DEFAULT_REPLY =\n 'Olá! Recebemos sua mensagem e já vamos te ajudar. Este é um atendimento de demonstração.';\n\n/**\n * In-memory adapter for the support chat widget. It mirrors the backend\n * contract (statuses, authors, events) so the widget can be built and tested\n * before the real API + WebSocket adapter exists.\n */\nexport const createMockSupportChatAdapter = ({\n delayMs = 300,\n replyDelayMs = 1500,\n replyText = DEFAULT_REPLY,\n}: MockSupportChatAdapterOptions = {}): SupportChatAdapter => {\n const tickets = new Map<string, SupportChatTicket>();\n const messages = new Map<string, SupportChatMessage[]>();\n const requesters = new Map<string, { name: string; logged: boolean }>();\n const listeners = new Map<string, Set<(event: SupportChatEvent) => void>>();\n let sequence = 0;\n\n const wait = () => new Promise((resolve) => setTimeout(resolve, delayMs));\n const nextId = (prefix: string) => `${prefix}-${++sequence}`;\n\n const emit = (ticketId: string, event: SupportChatEvent) => {\n listeners.get(ticketId)?.forEach((listener) => listener(event));\n };\n\n const append = (ticketId: string, message: SupportChatMessage) => {\n messages.set(ticketId, [...(messages.get(ticketId) ?? []), message]);\n };\n\n const toAttachments = (files: File[]) =>\n files.map((file) => ({\n id: nextId('file'),\n url: URL.createObjectURL(file),\n fileName: file.name,\n mimeType: file.type,\n size: file.size,\n }));\n\n const buildRequesterMessage = (\n ticketId: string,\n content: string,\n files: File[] = []\n ): SupportChatMessage => {\n const requester = requesters.get(ticketId);\n return {\n id: nextId('msg'),\n senderId: requester?.logged ? 'user' : SUPPORT_CHAT_GUEST_SENDER_ID,\n senderName: requester?.name ?? 'Visitante',\n senderPhoto: null,\n senderRole: requester?.logged\n ? SUPPORT_CHAT_AUTHOR.USER\n : SUPPORT_CHAT_AUTHOR.GUEST,\n content,\n messageType: 'text',\n createdAt: new Date().toISOString(),\n attachments: toAttachments(files),\n };\n };\n\n const scheduleReply = (ticketId: string) => {\n if (replyText === null) return;\n setTimeout(() => {\n const ticket = tickets.get(ticketId);\n if (!ticket) return;\n\n const reply: SupportChatMessage = {\n id: nextId('msg'),\n senderId: 'agent',\n senderName: 'Suporte',\n senderPhoto: null,\n senderRole: SUPPORT_CHAT_AUTHOR.AGENT,\n content: replyText,\n messageType: 'text',\n createdAt: new Date().toISOString(),\n attachments: [],\n };\n append(ticketId, reply);\n\n const updated: SupportChatTicket = {\n ...ticket,\n status: SUPPORT_CHAT_STATUS.EM_ANDAMENTO,\n handlerType: SUPPORT_CHAT_HANDLER.HUMANO,\n };\n tickets.set(ticketId, updated);\n\n emit(ticketId, { type: 'new_message', payload: { message: reply } });\n emit(ticketId, {\n type: 'ticket_updated',\n payload: {\n ticketId,\n status: updated.status,\n handlerType: updated.handlerType,\n problemType: updated.problemType,\n },\n });\n }, replyDelayMs);\n };\n\n return {\n async getArticle(_institutionId, articleId) {\n await wait();\n return {\n id: articleId,\n title: 'Como gerar e localizar simulados',\n body:\n '<p>Monte um simulado com as matérias que quiser e acompanhe o resultado. 📝</p>' +\n '<h2>Passo a passo</h2>' +\n '<ol><li><p>No menu, abra <strong>Simulados</strong>.</p></li>' +\n '<li><p>Clique em <strong>Gerar simulado</strong> e escolha as áreas.</p></li>' +\n '<li><p>Responda e veja o resultado em <strong>Histórico</strong>.</p></li></ol>' +\n '<p><a href=\"https://www.youtube.com/watch?v=dQw4w9WgXcQ\">Assista ao vídeo do passo a passo</a></p>' +\n '<blockquote><p>💡 Dica: refaça as questões que errou.</p></blockquote>',\n updatedAt: '2026-10-21T12:00:00.000Z',\n };\n },\n\n async startConversation({ message, requesterName, email, files }) {\n await wait();\n const ticket: SupportChatTicket = {\n id: nextId('ticket'),\n status: SUPPORT_CHAT_STATUS.ABERTO,\n handlerType: null,\n problemType: null,\n };\n tickets.set(ticket.id, ticket);\n requesters.set(ticket.id, {\n name: requesterName || 'Visitante',\n logged: Boolean(email),\n });\n\n const first = buildRequesterMessage(ticket.id, message, files);\n append(ticket.id, first);\n scheduleReply(ticket.id);\n return { ticket, messages: [first] };\n },\n\n async sendMessage(ticketId, content, files) {\n await wait();\n const ticket = tickets.get(ticketId);\n if (!ticket) throw new Error('Atendimento não encontrado');\n if (ticket.status === SUPPORT_CHAT_STATUS.FECHADO) {\n throw new Error('Este atendimento já foi encerrado');\n }\n\n const message = buildRequesterMessage(ticketId, content, files);\n append(ticketId, message);\n scheduleReply(ticketId);\n return message;\n },\n\n async listMessages(ticketId) {\n await wait();\n return [...(messages.get(ticketId) ?? [])];\n },\n\n subscribe(ticketId, listener) {\n const set = listeners.get(ticketId) ?? new Set();\n set.add(listener);\n listeners.set(ticketId, set);\n return () => {\n set.delete(listener);\n };\n },\n };\n};\n"]}
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkFU23WOTYjs = require('./chunk-FU23WOTY.js');
3
+ var _chunkHSV5VZ4Zjs = require('./chunk-HSV5VZ4Z.js');
4
4
 
5
5
 
6
6
  var _chunkJPO6L6NEjs = require('./chunk-JPO6L6NE.js');
@@ -9,7 +9,7 @@ var _chunkJPO6L6NEjs = require('./chunk-JPO6L6NE.js');
9
9
  var _chunkSKM7NFPYjs = require('./chunk-SKM7NFPY.js');
10
10
 
11
11
 
12
- var _chunkVSVLIRO2js = require('./chunk-VSVLIRO2.js');
12
+ var _chunkRXEU77VNjs = require('./chunk-RXEU77VN.js');
13
13
 
14
14
 
15
15
  var _chunk4OVIHZ3Ijs = require('./chunk-4OVIHZ3I.js');
@@ -60,7 +60,7 @@ function SupportTicketConversation({
60
60
  children: [
61
61
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkSKM7NFPYjs.SupportConversationHeader, { ...header, actions }),
62
62
  banner && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "px-6 pt-4", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJPO6L6NEjs.SupportStatusBanner, { ...banner }) }),
63
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVSVLIRO2js.SupportConversationMessages, { ...messages }),
63
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkRXEU77VNjs.SupportConversationMessages, { ...messages }),
64
64
  alert && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
65
65
  _chunkANT66KVKjs.Text_default,
66
66
  {
@@ -89,7 +89,7 @@ function SupportTicketConversation({
89
89
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "sm", color: "text-text-600", children: lockedMessage })
90
90
  ]
91
91
  }
92
- ) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkFU23WOTYjs.SupportChatComposer_default, { ...composer }) }),
92
+ ) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkHSV5VZ4Zjs.SupportChatComposer_default, { ...composer }) }),
93
93
  conclude && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
94
94
  _chunk4OVIHZ3Ijs.SupportConcludeModal,
95
95
  {
@@ -106,4 +106,4 @@ function SupportTicketConversation({
106
106
 
107
107
 
108
108
  exports.SupportTicketConversation = SupportTicketConversation;
109
- //# sourceMappingURL=chunk-KSUPPAKH.js.map
109
+ //# sourceMappingURL=chunk-3IXK6AR7.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-KSUPPAKH.js","../src/components/SupportConversation/SupportTicketConversation.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-KSUPPAKH.js","sourcesContent":[null,"import { useState } from 'react';\nimport { LockSimpleIcon } from '@phosphor-icons/react/dist/csr/LockSimple';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport SupportConcludeModal, {\n type SupportConcludeModalProps,\n} from '../SupportConcludeModal/SupportConcludeModal';\nimport SupportChatComposer, {\n type SupportChatComposerProps,\n} from '../SupportChat/SupportChatComposer';\nimport SupportConversationHeader, {\n type SupportConversationHeaderProps,\n} from './SupportConversationHeader';\nimport SupportStatusBanner, {\n type SupportStatusBannerProps,\n} from './SupportStatusBanner';\nimport SupportConversationMessages, {\n type SupportConversationMessagesProps,\n} from './SupportConversationMessages';\nimport { cn } from '../../utils/utils';\n\n/**\n * Props of the SupportTicketConversation component\n */\nexport interface SupportTicketConversationProps {\n /** Header: code, name, status, \"Atendido por\", channel and details toggle */\n header: SupportConversationHeaderProps;\n /** Fixed notice on top of the timeline; omit or null to hide it */\n banner?: SupportStatusBannerProps | null;\n /** Timeline: items, loading and empty text */\n messages: SupportConversationMessagesProps;\n /** Composer of the agent reply */\n composer: SupportChatComposerProps;\n /**\n * Replaces the composer with a locked footer, e.g. \"O assistente virtual\n * está atendendo. Clique em 'Responder como humano' para escrever.\" or\n * \"Atendimento concluído. O histórico está disponível somente para leitura.\"\n */\n lockedMessage?: string | null;\n /** Message shown above the composer (e.g. a send error) */\n alert?: string | null;\n /**\n * Shows the \"Concluir\" button in the header, which opens the\n * SupportConcludeModal with these props; omit or null to hide it\n */\n conclude?: Omit<SupportConcludeModalProps, 'isOpen' | 'onClose'> | null;\n className?: string;\n}\n\n/**\n * Central area of the agent's ticket screen: header, status banner, message\n * timeline and composer, stacked in a full-height column. Only the timeline\n * scrolls; the header, the banner and the footer stay fixed.\n *\n * Presentational: data comes in through props and every action goes out\n * through callbacks. The details panel (SupportUserPanel) is laid out by the\n * parent, which owns `isDetailsOpen` and passes `onToggleDetails` in the\n * header props.\n *\n * @example\n * ```tsx\n * <div className=\"flex h-full\">\n * <SupportTicketConversation\n * header={{ ticketCode: '#4819', name: 'Helena Martins', status,\n * isDetailsOpen, onToggleDetails: () => setOpen((o) => !o) }}\n * banner={{ variant: 'waiting', title: 'Aguardando resposta do backoffice' }}\n * messages={{ items }}\n * composer={{ onSend }}\n * />\n * {isDetailsOpen && <SupportUserPanel {...panel} />}\n * </div>\n * ```\n */\nexport default function SupportTicketConversation({\n header,\n banner,\n messages,\n composer,\n lockedMessage,\n alert,\n conclude,\n className,\n}: Readonly<SupportTicketConversationProps>) {\n const [isConcludeOpen, setConcludeOpen] = useState(false);\n\n const actions = conclude ? (\n <>\n <Button\n variant=\"outline\"\n action=\"positive\"\n size=\"small\"\n onClick={() => setConcludeOpen(true)}\n >\n Concluir\n </Button>\n {header.actions}\n </>\n ) : (\n header.actions\n );\n\n return (\n <section\n aria-label=\"Conversa do atendimento\"\n data-testid=\"support-ticket-conversation\"\n className={cn('flex h-full min-h-0 min-w-0 flex-1 flex-col', className)}\n >\n <SupportConversationHeader {...header} actions={actions} />\n\n {banner && (\n <div className=\"px-6 pt-4\">\n <SupportStatusBanner {...banner} />\n </div>\n )}\n\n <SupportConversationMessages {...messages} />\n\n {alert && (\n <Text\n as=\"p\"\n size=\"sm\"\n role=\"alert\"\n color=\"text-error-700\"\n className=\"bg-error-50 px-6 py-2\"\n >\n {alert}\n </Text>\n )}\n\n <footer className=\"border-t border-border-100 px-6 py-4\">\n {lockedMessage ? (\n <div\n data-testid=\"support-ticket-conversation-locked\"\n className=\"flex items-center gap-2 rounded-lg bg-background-50 px-4 py-3\"\n >\n <LockSimpleIcon\n size={18}\n aria-hidden=\"true\"\n className=\"shrink-0 text-text-500\"\n />\n <Text size=\"sm\" color=\"text-text-600\">\n {lockedMessage}\n </Text>\n </div>\n ) : (\n <SupportChatComposer {...composer} />\n )}\n </footer>\n\n {conclude && (\n <SupportConcludeModal\n {...conclude}\n isOpen={isConcludeOpen}\n onClose={() => setConcludeOpen(false)}\n />\n )}\n </section>\n );\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-3IXK6AR7.js","../src/components/SupportConversation/SupportTicketConversation.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-3IXK6AR7.js","sourcesContent":[null,"import { useState } from 'react';\nimport { LockSimpleIcon } from '@phosphor-icons/react/dist/csr/LockSimple';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport SupportConcludeModal, {\n type SupportConcludeModalProps,\n} from '../SupportConcludeModal/SupportConcludeModal';\nimport SupportChatComposer, {\n type SupportChatComposerProps,\n} from '../SupportChat/SupportChatComposer';\nimport SupportConversationHeader, {\n type SupportConversationHeaderProps,\n} from './SupportConversationHeader';\nimport SupportStatusBanner, {\n type SupportStatusBannerProps,\n} from './SupportStatusBanner';\nimport SupportConversationMessages, {\n type SupportConversationMessagesProps,\n} from './SupportConversationMessages';\nimport { cn } from '../../utils/utils';\n\n/**\n * Props of the SupportTicketConversation component\n */\nexport interface SupportTicketConversationProps {\n /** Header: code, name, status, \"Atendido por\", channel and details toggle */\n header: SupportConversationHeaderProps;\n /** Fixed notice on top of the timeline; omit or null to hide it */\n banner?: SupportStatusBannerProps | null;\n /** Timeline: items, loading and empty text */\n messages: SupportConversationMessagesProps;\n /** Composer of the agent reply */\n composer: SupportChatComposerProps;\n /**\n * Replaces the composer with a locked footer, e.g. \"O assistente virtual\n * está atendendo. Clique em 'Responder como humano' para escrever.\" or\n * \"Atendimento concluído. O histórico está disponível somente para leitura.\"\n */\n lockedMessage?: string | null;\n /** Message shown above the composer (e.g. a send error) */\n alert?: string | null;\n /**\n * Shows the \"Concluir\" button in the header, which opens the\n * SupportConcludeModal with these props; omit or null to hide it\n */\n conclude?: Omit<SupportConcludeModalProps, 'isOpen' | 'onClose'> | null;\n className?: string;\n}\n\n/**\n * Central area of the agent's ticket screen: header, status banner, message\n * timeline and composer, stacked in a full-height column. Only the timeline\n * scrolls; the header, the banner and the footer stay fixed.\n *\n * Presentational: data comes in through props and every action goes out\n * through callbacks. The details panel (SupportUserPanel) is laid out by the\n * parent, which owns `isDetailsOpen` and passes `onToggleDetails` in the\n * header props.\n *\n * @example\n * ```tsx\n * <div className=\"flex h-full\">\n * <SupportTicketConversation\n * header={{ ticketCode: '#4819', name: 'Helena Martins', status,\n * isDetailsOpen, onToggleDetails: () => setOpen((o) => !o) }}\n * banner={{ variant: 'waiting', title: 'Aguardando resposta do backoffice' }}\n * messages={{ items }}\n * composer={{ onSend }}\n * />\n * {isDetailsOpen && <SupportUserPanel {...panel} />}\n * </div>\n * ```\n */\nexport default function SupportTicketConversation({\n header,\n banner,\n messages,\n composer,\n lockedMessage,\n alert,\n conclude,\n className,\n}: Readonly<SupportTicketConversationProps>) {\n const [isConcludeOpen, setConcludeOpen] = useState(false);\n\n const actions = conclude ? (\n <>\n <Button\n variant=\"outline\"\n action=\"positive\"\n size=\"small\"\n onClick={() => setConcludeOpen(true)}\n >\n Concluir\n </Button>\n {header.actions}\n </>\n ) : (\n header.actions\n );\n\n return (\n <section\n aria-label=\"Conversa do atendimento\"\n data-testid=\"support-ticket-conversation\"\n className={cn('flex h-full min-h-0 min-w-0 flex-1 flex-col', className)}\n >\n <SupportConversationHeader {...header} actions={actions} />\n\n {banner && (\n <div className=\"px-6 pt-4\">\n <SupportStatusBanner {...banner} />\n </div>\n )}\n\n <SupportConversationMessages {...messages} />\n\n {alert && (\n <Text\n as=\"p\"\n size=\"sm\"\n role=\"alert\"\n color=\"text-error-700\"\n className=\"bg-error-50 px-6 py-2\"\n >\n {alert}\n </Text>\n )}\n\n <footer className=\"border-t border-border-100 px-6 py-4\">\n {lockedMessage ? (\n <div\n data-testid=\"support-ticket-conversation-locked\"\n className=\"flex items-center gap-2 rounded-lg bg-background-50 px-4 py-3\"\n >\n <LockSimpleIcon\n size={18}\n aria-hidden=\"true\"\n className=\"shrink-0 text-text-500\"\n />\n <Text size=\"sm\" color=\"text-text-600\">\n {lockedMessage}\n </Text>\n </div>\n ) : (\n <SupportChatComposer {...composer} />\n )}\n </footer>\n\n {conclude && (\n <SupportConcludeModal\n {...conclude}\n isOpen={isConcludeOpen}\n onClose={() => setConcludeOpen(false)}\n />\n )}\n </section>\n );\n}\n"]}
@@ -73,4 +73,4 @@ var SUPPORT_CHAT_ATTACHMENT_RULES = {
73
73
 
74
74
 
75
75
  exports.SUPPORT_CHAT_MESSAGE_TYPE = SUPPORT_CHAT_MESSAGE_TYPE; exports.SUPPORT_CHAT_SEND_STATUS = SUPPORT_CHAT_SEND_STATUS; exports.SUPPORT_CHAT_REPLY_MODE = SUPPORT_CHAT_REPLY_MODE; exports.SUPPORT_CHAT_EVENT_TYPE = SUPPORT_CHAT_EVENT_TYPE; exports.SUPPORT_CHAT_STATUS = SUPPORT_CHAT_STATUS; exports.SUPPORT_CHAT_HANDLER = SUPPORT_CHAT_HANDLER; exports.SUPPORT_CHAT_AUTHOR = SUPPORT_CHAT_AUTHOR; exports.SUPPORT_CHAT_GUEST_SENDER_ID = SUPPORT_CHAT_GUEST_SENDER_ID; exports.SUPPORT_CHAT_IMAGE_TYPES = SUPPORT_CHAT_IMAGE_TYPES; exports.SUPPORT_CHAT_MAX_IMAGE_BYTES = SUPPORT_CHAT_MAX_IMAGE_BYTES; exports.SUPPORT_CHAT_MAX_ATTACHMENTS = SUPPORT_CHAT_MAX_ATTACHMENTS; exports.SUPPORT_CHAT_ATTACHMENT_RULES = SUPPORT_CHAT_ATTACHMENT_RULES;
76
- //# sourceMappingURL=chunk-POT3ALY3.js.map
76
+ //# sourceMappingURL=chunk-5DINXU6W.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-POT3ALY3.js","../src/types/supportChat.ts"],"names":[],"mappings":"AAAA;ACWO,IAAM,0BAAA,EAA4B;AAAA,EACvC,IAAA,EAAM,MAAA;AAAA,EACN,KAAA,EAAO,OAAA;AAAA,EACP,EAAA,EAAI,IAAA;AAAA,EACJ,KAAA,EAAO,OAAA;AAAA,EACP,aAAA,EAAe;AACjB,CAAA;AAQO,IAAM,yBAAA,EAA2B;AAAA,EACtC,IAAA,EAAM,MAAA;AAAA,EACN,OAAA,EAAS,SAAA;AAAA,EACT,MAAA,EAAQ;AACV,CAAA;AAsBO,IAAM,wBAAA,EAA0B;AAAA,EACrC,IAAA,EAAM,MAAA;AAAA,EACN,aAAA,EAAe;AACjB,CAAA;AAQO,IAAM,wBAAA,EAA0B;AAAA,EACrC,OAAA,EAAS,SAAA;AAAA,EACT,QAAA,EAAU,UAAA;AAAA,EACV,EAAA,EAAI,IAAA;AAAA,EACJ,OAAA,EAAS,SAAA;AAAA,EACT,IAAA,EAAM;AACR,CAAA;AA+BO,IAAM,oBAAA,EAAsB;AAAA,EACjC,MAAA,EAAQ,QAAA;AAAA,EACR,QAAA,EAAU,UAAA;AAAA,EACV,YAAA,EAAc,cAAA;AAAA,EACd,OAAA,EAAS;AACX,CAAA;AAQO,IAAM,qBAAA,EAAuB;AAAA,EAClC,EAAA,EAAI,IAAA;AAAA,EACJ,MAAA,EAAQ;AACV,CAAA;AAQO,IAAM,oBAAA,EAAsB;AAAA,EACjC,IAAA,EAAM,MAAA;AAAA,EACN,KAAA,EAAO,OAAA;AAAA,EACP,KAAA,EAAO,OAAA;AAAA,EACP,EAAA,EAAI;AACN,CAAA;AAMO,IAAM,6BAAA,EAA+B,OAAA;AAGrC,IAAM,yBAAA,EAA2B;AAAA,EACtC,YAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA;AAGO,IAAM,6BAAA,EAA+B,EAAA,EAAI,KAAA,EAAO,IAAA;AAGhD,IAAM,6BAAA,EAA+B,CAAA;AAE5C,IAAM,GAAA,EAAK,KAAA,EAAO,IAAA;AAMX,IAAM,8BAAA,EAAwD;AAAA,EACnE,YAAA,EAAc,EAAA,EAAI,EAAA;AAAA,EAClB,WAAA,EAAa,EAAA,EAAI,EAAA;AAAA,EACjB,WAAA,EAAa,EAAA,EAAI,EAAA;AAAA,EACjB,YAAA,EAAc,EAAA,EAAI,EAAA;AAAA,EAClB,iBAAA,EAAmB,GAAA,EAAK,EAAA;AAAA,EACxB,WAAA,EAAa,GAAA,EAAK,EAAA;AAAA,EAClB,YAAA,EAAc,GAAA,EAAK,EAAA;AAAA,EACnB,iBAAA,EAAmB,GAAA,EAAK;AAC1B,CAAA;ADzGA;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,+tBAAC","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-POT3ALY3.js","sourcesContent":[null,"import type { SupportType } from './support';\n\n/**\n * Support (SAC) chat types\n */\n\n/**\n * Who authored a support chat message. `USER`, `GUEST`, `AI` and `AGENT`\n * match the backend `senderRole`; `GUEST` (logged-out visitor) renders like `USER`.\n * `INTERNAL_NOTE` is only visible in the backoffice.\n */\nexport const SUPPORT_CHAT_MESSAGE_TYPE = {\n USER: 'USER',\n GUEST: 'GUEST',\n AI: 'AI',\n AGENT: 'AGENT',\n INTERNAL_NOTE: 'INTERNAL_NOTE',\n} as const;\n\nexport type SupportChatMessageType =\n (typeof SUPPORT_CHAT_MESSAGE_TYPE)[keyof typeof SUPPORT_CHAT_MESSAGE_TYPE];\n\n/**\n * Delivery status of a message sent by the current user/agent\n */\nexport const SUPPORT_CHAT_SEND_STATUS = {\n SENT: 'SENT',\n SENDING: 'SENDING',\n FAILED: 'FAILED',\n} as const;\n\nexport type SupportChatSendStatus =\n (typeof SUPPORT_CHAT_SEND_STATUS)[keyof typeof SUPPORT_CHAT_SEND_STATUS];\n\n/**\n * File attached to a support chat message (same shape as the backend attachment)\n */\nexport interface SupportChatAttachment {\n id: string;\n fileName: string;\n url: string;\n /** MIME type; `image/*` renders a thumbnail */\n mimeType?: string;\n /** File size in bytes */\n size?: number;\n}\n\n/**\n * Where an agent reply goes: to the requester (`USER`) or only to the team\n * (`INTERNAL_NOTE`, same value as the message type shown in the backoffice)\n */\nexport const SUPPORT_CHAT_REPLY_MODE = {\n USER: 'USER',\n INTERNAL_NOTE: 'INTERNAL_NOTE',\n} as const;\n\nexport type SupportChatReplyMode =\n (typeof SUPPORT_CHAT_REPLY_MODE)[keyof typeof SUPPORT_CHAT_REPLY_MODE];\n\n/**\n * Kind of event shown between chat messages\n */\nexport const SUPPORT_CHAT_EVENT_TYPE = {\n NEUTRAL: 'NEUTRAL',\n TRANSFER: 'TRANSFER',\n AI: 'AI',\n SUCCESS: 'SUCCESS',\n DATE: 'DATE',\n} as const;\n\nexport type SupportChatEventType =\n (typeof SUPPORT_CHAT_EVENT_TYPE)[keyof typeof SUPPORT_CHAT_EVENT_TYPE];\n\n/**\n * Author displayed next to a support chat message\n */\nexport interface SupportChatAuthor {\n name: string;\n /** Overrides the initials computed from `name` */\n initials?: string;\n /** Tailwind classes overriding the avatar background color */\n avatarClassName?: string;\n}\n\n/**\n * FAQ entry the AI answer was based on\n */\nexport interface SupportChatFaqReference {\n title: string;\n onClick?: () => void;\n}\n\n/*\n * Support chat widget (floating chat on top of a SupportChatAdapter)\n */\n\n/**\n * Ticket status, as sent by the backend (sup_support.status)\n */\nexport const SUPPORT_CHAT_STATUS = {\n ABERTO: 'ABERTO',\n PENDENTE: 'PENDENTE',\n EM_ANDAMENTO: 'EM_ANDAMENTO',\n FECHADO: 'FECHADO',\n} as const;\n\nexport type SupportChatStatus =\n (typeof SUPPORT_CHAT_STATUS)[keyof typeof SUPPORT_CHAT_STATUS];\n\n/**\n * Who is handling the ticket\n */\nexport const SUPPORT_CHAT_HANDLER = {\n IA: 'IA',\n HUMANO: 'HUMANO',\n} as const;\n\nexport type SupportChatHandler =\n (typeof SUPPORT_CHAT_HANDLER)[keyof typeof SUPPORT_CHAT_HANDLER];\n\n/**\n * Author of a message (sent by the backend as senderRole)\n */\nexport const SUPPORT_CHAT_AUTHOR = {\n USER: 'USER',\n GUEST: 'GUEST',\n AGENT: 'AGENT',\n AI: 'AI',\n} as const;\n\nexport type SupportChatAuthorRole =\n (typeof SUPPORT_CHAT_AUTHOR)[keyof typeof SUPPORT_CHAT_AUTHOR];\n\n/** senderId of the messages written by a visitor (not logged in) */\nexport const SUPPORT_CHAT_GUEST_SENDER_ID = 'guest';\n\n/** Images accepted by the backend */\nexport const SUPPORT_CHAT_IMAGE_TYPES = [\n 'image/jpeg',\n 'image/png',\n 'image/gif',\n 'image/webp',\n];\n\n/** Largest image the backend accepts (5 MB) */\nexport const SUPPORT_CHAT_MAX_IMAGE_BYTES = 5 * 1024 * 1024;\n\n/** Most attachments in one message */\nexport const SUPPORT_CHAT_MAX_ATTACHMENTS = 5;\n\nconst MB = 1024 * 1024;\n\n/**\n * Files the backend accepts in the support chat (mime type → largest size).\n * Mirrors SUPPORT_ATTACHMENT_RULES of the API: images, PDF and short videos.\n */\nexport const SUPPORT_CHAT_ATTACHMENT_RULES: Record<string, number> = {\n 'image/jpeg': 5 * MB,\n 'image/png': 5 * MB,\n 'image/gif': 5 * MB,\n 'image/webp': 5 * MB,\n 'application/pdf': 10 * MB,\n 'video/mp4': 50 * MB,\n 'video/webm': 50 * MB,\n 'video/quicktime': 50 * MB,\n};\n\n/**\n * FAQ article opened inside the chat (GET /help-center/:institutionId/articles/:id)\n */\nexport interface SupportChatArticle {\n id: string;\n title: string;\n /** Article HTML as saved by the help-center editor */\n body: string;\n /** ISO date of the last update, when known */\n updatedAt: string | null;\n}\n\n/**\n * Chat message (same shape as the platform chat, plus attachments)\n */\nexport interface SupportChatMessage {\n id: string;\n senderId: string;\n senderName: string;\n senderPhoto: string | null;\n senderRole: SupportChatAuthorRole;\n content: string;\n messageType: string;\n createdAt: string;\n attachments: SupportChatAttachment[];\n /** FAQ article an AI message is based on */\n faq?: { id: string; title: string } | null;\n /**\n * Contact fields an AI message asks for: the chat shows a form for them\n * under the message\n */\n contactRequest?: SupportChatContactField[] | null;\n /** Local only: shown before the server confirms it (optimistic) */\n pending?: boolean;\n}\n\n/**\n * Ticket the conversation belongs to\n */\nexport interface SupportChatTicket {\n id: string;\n status: SupportChatStatus;\n handlerType: SupportChatHandler | null;\n problemType: string | null;\n}\n\n/**\n * Realtime events pushed to the conversation (WebSocket in the real adapter)\n */\nexport type SupportChatEvent =\n | { type: 'new_message'; payload: { message: SupportChatMessage } }\n | {\n type: 'message_updated';\n payload: { messageId: string; content: string; read: boolean };\n }\n | {\n type: 'ticket_updated';\n payload: { ticketId: string } & Omit<SupportChatTicket, 'id'>;\n }\n | { type: 'error'; payload: { message: string } };\n\n/**\n * Data needed to open a conversation (first message creates the ticket)\n */\nexport interface StartSupportChatInput {\n message: string;\n channel: string;\n institutionId?: string;\n requesterName?: string;\n email?: string;\n /** Files sent with the first message */\n files?: File[];\n}\n\n/**\n * Data layer of the widget. The mock adapter implements it in memory; the\n * real one will use the support REST API and the chat WebSocket.\n */\nexport interface SupportChatAdapter {\n /**\n * Create the ticket with its first message. With files the API may store\n * them as a second message, hence the list.\n */\n startConversation(input: StartSupportChatInput): Promise<{\n ticket: SupportChatTicket;\n messages: SupportChatMessage[];\n }>;\n /** Send a message (text and/or files) on an existing ticket */\n sendMessage(\n ticketId: string,\n content: string,\n files?: File[]\n ): Promise<SupportChatMessage>;\n /** Messages of the ticket, oldest first */\n listMessages(ticketId: string): Promise<SupportChatMessage[]>;\n /** Listen to realtime events of the ticket; returns the unsubscribe */\n subscribe(\n ticketId: string,\n listener: (event: SupportChatEvent) => void\n ): () => void;\n /**\n * Save the name and/or e-mail the visitor typed in the contact form.\n * Without it the chat does not show the form (the AI request stays a text).\n */\n updateContact?(ticketId: string, contact: SupportChatContact): Promise<void>;\n /**\n * Load a FAQ article of the institution. With it, the article an AI answer\n * is based on opens inside the chat instead of a new tab.\n */\n getArticle?(\n institutionId: string,\n articleId: string\n ): Promise<SupportChatArticle>;\n}\n\n/** Contact field the AI asks for */\nexport type SupportChatContactField = 'name' | 'email';\n\n/** Contact sent by the chat form */\nexport interface SupportChatContact {\n requesterName?: string;\n email?: string;\n}\n\n/**\n * Feature flags of the widget\n */\nexport interface SupportChatFlags {\n /** Widget available at all (default true) */\n enabled?: boolean;\n /** Chat with the support (default true) */\n chat?: boolean;\n /** FAQ (reserved, not rendered yet) */\n faq?: boolean;\n /** AI first answer (reserved, not rendered yet) */\n ai?: boolean;\n /** ZENDESK hides the widget (the Zendesk messenger is used instead) */\n supportType?: SupportType;\n}\n\n/**\n * Visual customization of the widget\n */\nexport interface SupportChatTheme {\n /** Background of the button and the header (any CSS color) */\n primaryColor?: string;\n /** Corner of the screen (default bottom-right) */\n position?: 'bottom-right' | 'bottom-left';\n /** Header title (default \"Suporte\") */\n title?: string;\n}\n\n/**\n * Logged user (optional; a visitor has none)\n */\nexport interface SupportChatUser {\n name?: string;\n email?: string;\n}\n\n/**\n * HTTP client the API adapter needs (axios instances fit as they are)\n */\nexport interface SupportChatHttpClient {\n get: <T>(\n url: string,\n config?: { headers?: Record<string, string> }\n ) => Promise<{ data: T }>;\n post: <T>(\n url: string,\n data?: unknown,\n config?: { headers?: Record<string, string> }\n ) => Promise<{ data: T }>;\n /** Used to save the visitor contact (PUT /support/guest/:id) */\n put?: <T>(\n url: string,\n data?: unknown,\n config?: { headers?: Record<string, string> }\n ) => Promise<{ data: T }>;\n}\n\n/**\n * Message as returned by the support REST routes\n */\nexport interface SupportChatAnswerApi {\n id: string;\n authorType: SupportChatAuthorRole;\n answer: string;\n createdAt: string;\n attachments?: SupportChatAttachment[];\n /** FAQ article an AI message is based on */\n faqArticleId?: string | null;\n faqArticleTitle?: string | null;\n /** Contact fields an AI message asks for */\n contactRequest?: SupportChatContactField[] | null;\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-5DINXU6W.js","../src/types/supportChat.ts"],"names":[],"mappings":"AAAA;ACWO,IAAM,0BAAA,EAA4B;AAAA,EACvC,IAAA,EAAM,MAAA;AAAA,EACN,KAAA,EAAO,OAAA;AAAA,EACP,EAAA,EAAI,IAAA;AAAA,EACJ,KAAA,EAAO,OAAA;AAAA,EACP,aAAA,EAAe;AACjB,CAAA;AAQO,IAAM,yBAAA,EAA2B;AAAA,EACtC,IAAA,EAAM,MAAA;AAAA,EACN,OAAA,EAAS,SAAA;AAAA,EACT,MAAA,EAAQ;AACV,CAAA;AAsBO,IAAM,wBAAA,EAA0B;AAAA,EACrC,IAAA,EAAM,MAAA;AAAA,EACN,aAAA,EAAe;AACjB,CAAA;AAQO,IAAM,wBAAA,EAA0B;AAAA,EACrC,OAAA,EAAS,SAAA;AAAA,EACT,QAAA,EAAU,UAAA;AAAA,EACV,EAAA,EAAI,IAAA;AAAA,EACJ,OAAA,EAAS,SAAA;AAAA,EACT,IAAA,EAAM;AACR,CAAA;AA+BO,IAAM,oBAAA,EAAsB;AAAA,EACjC,MAAA,EAAQ,QAAA;AAAA,EACR,QAAA,EAAU,UAAA;AAAA,EACV,YAAA,EAAc,cAAA;AAAA,EACd,OAAA,EAAS;AACX,CAAA;AAQO,IAAM,qBAAA,EAAuB;AAAA,EAClC,EAAA,EAAI,IAAA;AAAA,EACJ,MAAA,EAAQ;AACV,CAAA;AAQO,IAAM,oBAAA,EAAsB;AAAA,EACjC,IAAA,EAAM,MAAA;AAAA,EACN,KAAA,EAAO,OAAA;AAAA,EACP,KAAA,EAAO,OAAA;AAAA,EACP,EAAA,EAAI;AACN,CAAA;AAMO,IAAM,6BAAA,EAA+B,OAAA;AAGrC,IAAM,yBAAA,EAA2B;AAAA,EACtC,YAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA;AAGO,IAAM,6BAAA,EAA+B,EAAA,EAAI,KAAA,EAAO,IAAA;AAGhD,IAAM,6BAAA,EAA+B,CAAA;AAE5C,IAAM,GAAA,EAAK,KAAA,EAAO,IAAA;AAMX,IAAM,8BAAA,EAAwD;AAAA,EACnE,YAAA,EAAc,EAAA,EAAI,EAAA;AAAA,EAClB,WAAA,EAAa,EAAA,EAAI,EAAA;AAAA,EACjB,WAAA,EAAa,EAAA,EAAI,EAAA;AAAA,EACjB,YAAA,EAAc,EAAA,EAAI,EAAA;AAAA,EAClB,iBAAA,EAAmB,GAAA,EAAK,EAAA;AAAA,EACxB,WAAA,EAAa,GAAA,EAAK,EAAA;AAAA,EAClB,YAAA,EAAc,GAAA,EAAK,EAAA;AAAA,EACnB,iBAAA,EAAmB,GAAA,EAAK;AAC1B,CAAA;ADzGA;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,+tBAAC","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-5DINXU6W.js","sourcesContent":[null,"import type { SupportType } from './support';\n\n/**\n * Support (SAC) chat types\n */\n\n/**\n * Who authored a support chat message. `USER`, `GUEST`, `AI` and `AGENT`\n * match the backend `senderRole`; `GUEST` (logged-out visitor) renders like `USER`.\n * `INTERNAL_NOTE` is only visible in the backoffice.\n */\nexport const SUPPORT_CHAT_MESSAGE_TYPE = {\n USER: 'USER',\n GUEST: 'GUEST',\n AI: 'AI',\n AGENT: 'AGENT',\n INTERNAL_NOTE: 'INTERNAL_NOTE',\n} as const;\n\nexport type SupportChatMessageType =\n (typeof SUPPORT_CHAT_MESSAGE_TYPE)[keyof typeof SUPPORT_CHAT_MESSAGE_TYPE];\n\n/**\n * Delivery status of a message sent by the current user/agent\n */\nexport const SUPPORT_CHAT_SEND_STATUS = {\n SENT: 'SENT',\n SENDING: 'SENDING',\n FAILED: 'FAILED',\n} as const;\n\nexport type SupportChatSendStatus =\n (typeof SUPPORT_CHAT_SEND_STATUS)[keyof typeof SUPPORT_CHAT_SEND_STATUS];\n\n/**\n * File attached to a support chat message (same shape as the backend attachment)\n */\nexport interface SupportChatAttachment {\n id: string;\n fileName: string;\n url: string;\n /** MIME type; `image/*` renders a thumbnail */\n mimeType?: string;\n /** File size in bytes */\n size?: number;\n}\n\n/**\n * Where an agent reply goes: to the requester (`USER`) or only to the team\n * (`INTERNAL_NOTE`, same value as the message type shown in the backoffice)\n */\nexport const SUPPORT_CHAT_REPLY_MODE = {\n USER: 'USER',\n INTERNAL_NOTE: 'INTERNAL_NOTE',\n} as const;\n\nexport type SupportChatReplyMode =\n (typeof SUPPORT_CHAT_REPLY_MODE)[keyof typeof SUPPORT_CHAT_REPLY_MODE];\n\n/**\n * Kind of event shown between chat messages\n */\nexport const SUPPORT_CHAT_EVENT_TYPE = {\n NEUTRAL: 'NEUTRAL',\n TRANSFER: 'TRANSFER',\n AI: 'AI',\n SUCCESS: 'SUCCESS',\n DATE: 'DATE',\n} as const;\n\nexport type SupportChatEventType =\n (typeof SUPPORT_CHAT_EVENT_TYPE)[keyof typeof SUPPORT_CHAT_EVENT_TYPE];\n\n/**\n * Author displayed next to a support chat message\n */\nexport interface SupportChatAuthor {\n name: string;\n /** Overrides the initials computed from `name` */\n initials?: string;\n /** Tailwind classes overriding the avatar background color */\n avatarClassName?: string;\n}\n\n/**\n * FAQ entry the AI answer was based on\n */\nexport interface SupportChatFaqReference {\n title: string;\n onClick?: () => void;\n}\n\n/*\n * Support chat widget (floating chat on top of a SupportChatAdapter)\n */\n\n/**\n * Ticket status, as sent by the backend (sup_support.status)\n */\nexport const SUPPORT_CHAT_STATUS = {\n ABERTO: 'ABERTO',\n PENDENTE: 'PENDENTE',\n EM_ANDAMENTO: 'EM_ANDAMENTO',\n FECHADO: 'FECHADO',\n} as const;\n\nexport type SupportChatStatus =\n (typeof SUPPORT_CHAT_STATUS)[keyof typeof SUPPORT_CHAT_STATUS];\n\n/**\n * Who is handling the ticket\n */\nexport const SUPPORT_CHAT_HANDLER = {\n IA: 'IA',\n HUMANO: 'HUMANO',\n} as const;\n\nexport type SupportChatHandler =\n (typeof SUPPORT_CHAT_HANDLER)[keyof typeof SUPPORT_CHAT_HANDLER];\n\n/**\n * Author of a message (sent by the backend as senderRole)\n */\nexport const SUPPORT_CHAT_AUTHOR = {\n USER: 'USER',\n GUEST: 'GUEST',\n AGENT: 'AGENT',\n AI: 'AI',\n} as const;\n\nexport type SupportChatAuthorRole =\n (typeof SUPPORT_CHAT_AUTHOR)[keyof typeof SUPPORT_CHAT_AUTHOR];\n\n/** senderId of the messages written by a visitor (not logged in) */\nexport const SUPPORT_CHAT_GUEST_SENDER_ID = 'guest';\n\n/** Images accepted by the backend */\nexport const SUPPORT_CHAT_IMAGE_TYPES = [\n 'image/jpeg',\n 'image/png',\n 'image/gif',\n 'image/webp',\n];\n\n/** Largest image the backend accepts (5 MB) */\nexport const SUPPORT_CHAT_MAX_IMAGE_BYTES = 5 * 1024 * 1024;\n\n/** Most attachments in one message */\nexport const SUPPORT_CHAT_MAX_ATTACHMENTS = 5;\n\nconst MB = 1024 * 1024;\n\n/**\n * Files the backend accepts in the support chat (mime type → largest size).\n * Mirrors SUPPORT_ATTACHMENT_RULES of the API: images, PDF and short videos.\n */\nexport const SUPPORT_CHAT_ATTACHMENT_RULES: Record<string, number> = {\n 'image/jpeg': 5 * MB,\n 'image/png': 5 * MB,\n 'image/gif': 5 * MB,\n 'image/webp': 5 * MB,\n 'application/pdf': 10 * MB,\n 'video/mp4': 50 * MB,\n 'video/webm': 50 * MB,\n 'video/quicktime': 50 * MB,\n};\n\n/**\n * FAQ article opened inside the chat (GET /help-center/:institutionId/articles/:id)\n */\nexport interface SupportChatArticle {\n id: string;\n title: string;\n /** Article HTML as saved by the help-center editor */\n body: string;\n /** ISO date of the last update, when known */\n updatedAt: string | null;\n}\n\n/**\n * Chat message (same shape as the platform chat, plus attachments)\n */\nexport interface SupportChatMessage {\n id: string;\n senderId: string;\n senderName: string;\n senderPhoto: string | null;\n senderRole: SupportChatAuthorRole;\n content: string;\n messageType: string;\n createdAt: string;\n attachments: SupportChatAttachment[];\n /** FAQ article an AI message is based on */\n faq?: { id: string; title: string } | null;\n /**\n * Contact fields an AI message asks for: the chat shows a form for them\n * under the message\n */\n contactRequest?: SupportChatContactField[] | null;\n /** Local only: shown before the server confirms it (optimistic) */\n pending?: boolean;\n}\n\n/**\n * Ticket the conversation belongs to\n */\nexport interface SupportChatTicket {\n id: string;\n status: SupportChatStatus;\n handlerType: SupportChatHandler | null;\n problemType: string | null;\n}\n\n/**\n * Realtime events pushed to the conversation (WebSocket in the real adapter)\n */\nexport type SupportChatEvent =\n | { type: 'new_message'; payload: { message: SupportChatMessage } }\n | {\n type: 'message_updated';\n payload: { messageId: string; content: string; read: boolean };\n }\n | {\n type: 'ticket_updated';\n payload: { ticketId: string } & Omit<SupportChatTicket, 'id'>;\n }\n | { type: 'error'; payload: { message: string } };\n\n/**\n * Data needed to open a conversation (first message creates the ticket)\n */\nexport interface StartSupportChatInput {\n message: string;\n channel: string;\n institutionId?: string;\n requesterName?: string;\n email?: string;\n /** Files sent with the first message */\n files?: File[];\n}\n\n/**\n * Data layer of the widget. The mock adapter implements it in memory; the\n * real one will use the support REST API and the chat WebSocket.\n */\nexport interface SupportChatAdapter {\n /**\n * Create the ticket with its first message. With files the API may store\n * them as a second message, hence the list.\n */\n startConversation(input: StartSupportChatInput): Promise<{\n ticket: SupportChatTicket;\n messages: SupportChatMessage[];\n }>;\n /** Send a message (text and/or files) on an existing ticket */\n sendMessage(\n ticketId: string,\n content: string,\n files?: File[]\n ): Promise<SupportChatMessage>;\n /** Messages of the ticket, oldest first */\n listMessages(ticketId: string): Promise<SupportChatMessage[]>;\n /** Listen to realtime events of the ticket; returns the unsubscribe */\n subscribe(\n ticketId: string,\n listener: (event: SupportChatEvent) => void\n ): () => void;\n /**\n * Save the name and/or e-mail the visitor typed in the contact form.\n * Without it the chat does not show the form (the AI request stays a text).\n */\n updateContact?(ticketId: string, contact: SupportChatContact): Promise<void>;\n /**\n * Conversation kept from an earlier page load that is still unresolved, so\n * a reload does not drop it. Null when there is none, when it was resolved\n * or when it belongs to another user (`email` of the logged user; omit for\n * a visitor).\n */\n restoreConversation?(input: { email?: string }): Promise<{\n ticket: SupportChatTicket;\n messages: SupportChatMessage[];\n } | null>;\n /** Stop keeping the conversation (resolved, or the chat started over) */\n forgetConversation?(): void;\n /**\n * Load a FAQ article of the institution. With it, the article an AI answer\n * is based on opens inside the chat instead of a new tab.\n */\n getArticle?(\n institutionId: string,\n articleId: string\n ): Promise<SupportChatArticle>;\n}\n\n/** Contact field the AI asks for */\nexport type SupportChatContactField = 'name' | 'email';\n\n/** Contact sent by the chat form */\nexport interface SupportChatContact {\n requesterName?: string;\n email?: string;\n}\n\n/**\n * Feature flags of the widget\n */\nexport interface SupportChatFlags {\n /** Widget available at all (default true) */\n enabled?: boolean;\n /** Chat with the support (default true) */\n chat?: boolean;\n /** FAQ (reserved, not rendered yet) */\n faq?: boolean;\n /** AI first answer (reserved, not rendered yet) */\n ai?: boolean;\n /** ZENDESK hides the widget (the Zendesk messenger is used instead) */\n supportType?: SupportType;\n}\n\n/**\n * Visual customization of the widget\n */\nexport interface SupportChatTheme {\n /** Background of the button and the header (any CSS color) */\n primaryColor?: string;\n /** Corner of the screen (default bottom-right) */\n position?: 'bottom-right' | 'bottom-left';\n /** Header title (default \"Suporte\") */\n title?: string;\n}\n\n/**\n * Logged user (optional; a visitor has none)\n */\nexport interface SupportChatUser {\n name?: string;\n email?: string;\n}\n\n/**\n * HTTP client the API adapter needs (axios instances fit as they are)\n */\nexport interface SupportChatHttpClient {\n get: <T>(\n url: string,\n config?: { headers?: Record<string, string> }\n ) => Promise<{ data: T }>;\n post: <T>(\n url: string,\n data?: unknown,\n config?: { headers?: Record<string, string> }\n ) => Promise<{ data: T }>;\n /** Used to save the visitor contact (PUT /support/guest/:id) */\n put?: <T>(\n url: string,\n data?: unknown,\n config?: { headers?: Record<string, string> }\n ) => Promise<{ data: T }>;\n}\n\n/**\n * Message as returned by the support REST routes\n */\nexport interface SupportChatAnswerApi {\n id: string;\n authorType: SupportChatAuthorRole;\n answer: string;\n createdAt: string;\n attachments?: SupportChatAttachment[];\n /** FAQ article an AI message is based on */\n faqArticleId?: string | null;\n faqArticleTitle?: string | null;\n /** Contact fields an AI message asks for */\n contactRequest?: SupportChatContactField[] | null;\n}\n"]}
@@ -4,7 +4,7 @@ var _chunkXQL6H446js = require('./chunk-XQL6H446.js');
4
4
 
5
5
 
6
6
 
7
- var _chunkPOT3ALY3js = require('./chunk-POT3ALY3.js');
7
+ var _chunk5DINXU6Wjs = require('./chunk-5DINXU6W.js');
8
8
 
9
9
 
10
10
  var _chunkXN2HPVVHjs = require('./chunk-XN2HPVVH.js');
@@ -48,11 +48,11 @@ var Sparkle_default = Sparkle;
48
48
  // src/components/SupportChat/SupportChatMessage.tsx
49
49
 
50
50
  var BUBBLE_CLASSES = {
51
- [_chunkPOT3ALY3js.SUPPORT_CHAT_MESSAGE_TYPE.USER]: "bg-background-100",
52
- [_chunkPOT3ALY3js.SUPPORT_CHAT_MESSAGE_TYPE.GUEST]: "bg-background-100",
53
- [_chunkPOT3ALY3js.SUPPORT_CHAT_MESSAGE_TYPE.AI]: "bg-primary-50/60 border border-primary-100",
54
- [_chunkPOT3ALY3js.SUPPORT_CHAT_MESSAGE_TYPE.AGENT]: "bg-primary-950",
55
- [_chunkPOT3ALY3js.SUPPORT_CHAT_MESSAGE_TYPE.INTERNAL_NOTE]: "bg-exam-3 border border-warning-200"
51
+ [_chunk5DINXU6Wjs.SUPPORT_CHAT_MESSAGE_TYPE.USER]: "bg-background-100",
52
+ [_chunk5DINXU6Wjs.SUPPORT_CHAT_MESSAGE_TYPE.GUEST]: "bg-background-100",
53
+ [_chunk5DINXU6Wjs.SUPPORT_CHAT_MESSAGE_TYPE.AI]: "bg-primary-50/60 border border-primary-100",
54
+ [_chunk5DINXU6Wjs.SUPPORT_CHAT_MESSAGE_TYPE.AGENT]: "bg-primary-950",
55
+ [_chunk5DINXU6Wjs.SUPPORT_CHAT_MESSAGE_TYPE.INTERNAL_NOTE]: "bg-exam-3 border border-warning-200"
56
56
  };
57
57
  function AiAvatar() {
58
58
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -136,16 +136,16 @@ function SupportChatMessage({
136
136
  author,
137
137
  faq,
138
138
  attachments,
139
- status = _chunkPOT3ALY3js.SUPPORT_CHAT_SEND_STATUS.SENT,
139
+ status = _chunk5DINXU6Wjs.SUPPORT_CHAT_SEND_STATUS.SENT,
140
140
  onRetry,
141
141
  className
142
142
  }) {
143
- const isAi = type === _chunkPOT3ALY3js.SUPPORT_CHAT_MESSAGE_TYPE.AI;
144
- const isAgent = type === _chunkPOT3ALY3js.SUPPORT_CHAT_MESSAGE_TYPE.AGENT;
145
- const isNote = type === _chunkPOT3ALY3js.SUPPORT_CHAT_MESSAGE_TYPE.INTERNAL_NOTE;
143
+ const isAi = type === _chunk5DINXU6Wjs.SUPPORT_CHAT_MESSAGE_TYPE.AI;
144
+ const isAgent = type === _chunk5DINXU6Wjs.SUPPORT_CHAT_MESSAGE_TYPE.AGENT;
145
+ const isNote = type === _chunk5DINXU6Wjs.SUPPORT_CHAT_MESSAGE_TYPE.INTERNAL_NOTE;
146
146
  const isRight = isAgent || isNote;
147
- const isSending = status === _chunkPOT3ALY3js.SUPPORT_CHAT_SEND_STATUS.SENDING;
148
- const isFailed = status === _chunkPOT3ALY3js.SUPPORT_CHAT_SEND_STATUS.FAILED;
147
+ const isSending = status === _chunk5DINXU6Wjs.SUPPORT_CHAT_SEND_STATUS.SENDING;
148
+ const isFailed = status === _chunk5DINXU6Wjs.SUPPORT_CHAT_SEND_STATUS.FAILED;
149
149
  const avatar = isAi ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, AiAvatar, {}) : author && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, InitialsAvatar, { author });
150
150
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
151
151
  "div",
@@ -270,4 +270,4 @@ function SupportChatMessage({
270
270
 
271
271
 
272
272
  exports.SupportChatMessage = SupportChatMessage;
273
- //# sourceMappingURL=chunk-EMWX6IKP.js.map
273
+ //# sourceMappingURL=chunk-7VJP5D26.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-EMWX6IKP.js","../src/components/SupportChat/SupportChatMessage.tsx","../src/assets/icons/Sparkle.tsx"],"names":["jsx"],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACA;ACjBA,2DAAyB;AACzB,6EAAkC;ADmBlC;AACA;AELM,+CAAA;AAVN,IAAM,QAAA,EAAU,CAAC,KAAA,EAAA,GAAmC;AAClD,EAAA,uBACE,6BAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAM,IAAA;AAAA,MACN,MAAA,EAAO,IAAA;AAAA,MACP,OAAA,EAAQ,WAAA;AAAA,MACR,IAAA,EAAK,MAAA;AAAA,MACL,KAAA,EAAM,4BAAA;AAAA,MACL,GAAG,KAAA;AAAA,MAEJ,QAAA,kBAAA,6BAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UACC,CAAA,EAAE,kHAAA;AAAA,UACF,MAAA,EAAO,cAAA;AAAA,UACP,WAAA,EAAY,GAAA;AAAA,UACZ,cAAA,EAAe;AAAA,QAAA;AAAA,MACjB;AAAA,IAAA;AAAA,EACF,CAAA;AAEJ,CAAA;AAEA,IAAO,gBAAA,EAAQ,OAAA;AFoBf;AACA;ACcM;AAnBN,IAAM,eAAA,EAAyD;AAAA,EAC7D,CAAC,0CAAA,CAA0B,IAAI,CAAA,EAAG,mBAAA;AAAA,EAClC,CAAC,0CAAA,CAA0B,KAAK,CAAA,EAAG,mBAAA;AAAA,EACnC,CAAC,0CAAA,CAA0B,EAAE,CAAA,EAAG,4CAAA;AAAA,EAChC,CAAC,0CAAA,CAA0B,KAAK,CAAA,EAAG,gBAAA;AAAA,EACnC,CAAC,0CAAA,CAA0B,aAAa,CAAA,EACtC;AACJ,CAAA;AAKA,SAAS,QAAA,CAAA,EAAW;AAClB,EAAA,uBACEA,6BAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAK,KAAA;AAAA,MACL,YAAA,EAAW,oBAAA;AAAA,MACX,SAAA,EAAU,wHAAA;AAAA,MAEV,QAAA,kBAAAA,6BAAAA;AAAA,QAAC,eAAA;AAAA,QAAA;AAAA,UACC,KAAA,EAAO,EAAA;AAAA,UACP,MAAA,EAAQ,EAAA;AAAA,UACR,aAAA,EAAY,MAAA;AAAA,UACZ,aAAA,EAAY;AAAA,QAAA;AAAA,MACd;AAAA,IAAA;AAAA,EACF,CAAA;AAEJ;AAKA,SAAS,cAAA,CAAe,EAAE,OAAO,CAAA,EAA4C;AAC3E,EAAA,uBACEA,6BAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAK,KAAA;AAAA,MACL,YAAA,EAAY,MAAA,CAAO,IAAA;AAAA,MACnB,SAAA,EAAW,iCAAA;AAAA,QACT,8EAAA;AAAA,QACA,MAAA,CAAO;AAAA,MACT,CAAA;AAAA,MAEA,QAAA,kBAAAA,6BAAAA,6BAAC,EAAA,EAAK,IAAA,EAAK,KAAA,EAAM,MAAA,EAAO,MAAA,EAAO,KAAA,EAAM,YAAA,EAAa,aAAA,EAAY,MAAA,EAC3D,QAAA,mBAAA,MAAA,CAAO,QAAA,UAAY,0CAAA,MAAY,CAAO,IAAI,IAAA,CAC7C;AAAA,IAAA;AAAA,EACF,CAAA;AAEJ;AAKA,SAAS,cAAA,CAAe;AAAA,EACtB,UAAA;AAAA,EACA;AACF,CAAA,EAAqE;AACnE,EAAA,MAAM,QAAA,mCAAU,UAAA,mBAAW,QAAA,6BAAU,UAAA,mBAAW,QAAQ,GAAA,UAAK,OAAA;AAC7D,EAAA,uBACE,8BAAA;AAAA,IAAC,GAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAM,UAAA,CAAW,GAAA;AAAA,MACjB,MAAA,EAAO,QAAA;AAAA,MACP,GAAA,EAAI,qBAAA;AAAA,MACJ,SAAA,EAAU,oCAAA;AAAA,MAET,QAAA,EAAA;AAAA,QAAA,QAAA,kBACCA,6BAAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,GAAA,EAAK,UAAA,CAAW,GAAA;AAAA,YAChB,GAAA,EAAK,UAAA,CAAW,QAAA;AAAA,YAChB,SAAA,EAAU;AAAA,UAAA;AAAA,QACZ,EAAA,kBAEAA,6BAAAA;AAAA,UAAC,cAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAM,EAAA;AAAA,YACN,aAAA,EAAY,MAAA;AAAA,YACZ,SAAA,EAAW,iCAAA,UAAG,EAAY,OAAA,EAAS,aAAA,EAAe,eAAe;AAAA,UAAA;AAAA,QACnE,CAAA;AAAA,wBAEFA,6BAAAA;AAAA,UAAC,6BAAA;AAAA,UAAA;AAAA,YACC,EAAA,EAAG,MAAA;AAAA,YACH,IAAA,EAAK,IAAA;AAAA,YACL,KAAA,EAAO,OAAA,EAAS,aAAA,EAAe,eAAA;AAAA,YAC/B,SAAA,EAAU,oBAAA;AAAA,YAET,QAAA,EAAA,UAAA,CAAW;AAAA,UAAA;AAAA,QACd;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,CAAA;AAEJ;AAUe,SAAR,kBAAA,CAAoC;AAAA,EACzC,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,GAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA,EAAS,yCAAA,CAAyB,IAAA;AAAA,EAClC,OAAA;AAAA,EACA;AACF,CAAA,EAAsC;AACpC,EAAA,MAAM,KAAA,EAAO,KAAA,IAAS,0CAAA,CAA0B,EAAA;AAChD,EAAA,MAAM,QAAA,EAAU,KAAA,IAAS,0CAAA,CAA0B,KAAA;AACnD,EAAA,MAAM,OAAA,EAAS,KAAA,IAAS,0CAAA,CAA0B,aAAA;AAClD,EAAA,MAAM,QAAA,EAAU,QAAA,GAAW,MAAA;AAC3B,EAAA,MAAM,UAAA,EAAY,OAAA,IAAW,yCAAA,CAAyB,OAAA;AACtD,EAAA,MAAM,SAAA,EAAW,OAAA,IAAW,yCAAA,CAAyB,MAAA;AAErD,EAAA,MAAM,OAAA,EAAS,KAAA,kBACbA,6BAAAA,QAAC,EAAA,CAAA,CAAS,EAAA,EAEV,OAAA,mBAAUA,6BAAAA,cAAC,EAAA,EAAe,OAAA,CAAgB,CAAA;AAG5C,EAAA,uBACE,8BAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,iCAAA;AAAA,QACT,6BAAA;AAAA,QACA,QAAA,EAAU,cAAA,EAAgB,eAAA;AAAA,QAC1B;AAAA,MACF,CAAA;AAAA,MAEC,QAAA,EAAA;AAAA,QAAA,CAAC,QAAA,GAAW,MAAA;AAAA,wBAEb,8BAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAW,iCAAA;AAAA,cACT,0DAAA;AAAA,cACA,QAAA,EAAU,YAAA,EAAc;AAAA,YAC1B,CAAA;AAAA,YAEA,QAAA,EAAA;AAAA,8BAAA,8BAAA;AAAA,gBAAC,KAAA;AAAA,gBAAA;AAAA,kBACC,aAAA,EAAY,6BAAA;AAAA,kBACZ,SAAA,EAAW,iCAAA;AAAA,oBACT,2DAAA;AAAA,oBACA,cAAA,CAAe,IAAI,CAAA;AAAA,oBACnB,UAAA,GAAa;AAAA,kBACf,CAAA;AAAA,kBAEC,QAAA,EAAA;AAAA,oBAAA,KAAA,mBACCA,6BAAAA;AAAA,sBAAC,6BAAA;AAAA,sBAAA;AAAA,wBACC,IAAA,EAAK,KAAA;AAAA,wBACL,MAAA,EAAO,MAAA;AAAA,wBACP,KAAA,EAAM,kBAAA;AAAA,wBACN,SAAA,EAAU,0BAAA;AAAA,wBACX,QAAA,EAAA;AAAA,sBAAA;AAAA,oBAED,CAAA;AAAA,oBAGD,OAAA,mBACCA,6BAAAA;AAAA,sBAAC,6BAAA;AAAA,sBAAA;AAAA,wBACC,IAAA,EAAK,KAAA;AAAA,wBACL,MAAA,EAAO,MAAA;AAAA,wBACP,KAAA,EAAM,kBAAA;AAAA,wBACN,SAAA,EAAU,0BAAA;AAAA,wBAET,QAAA,EAAA,OAAA,EAAS,CAAA,kBAAA,EAAkB,MAAA,CAAO,IAAI,CAAA,EAAA;AAAK,sBAAA;AAC9C,oBAAA;AAIC,oBAAA;AAKHA,oCAAAA;AAAC,sBAAA;AAAA,sBAAA;AACM,wBAAA;AAC2B,wBAAA;AACtB,wBAAA;AAET,wBAAA;AAAA,sBAAA;AACH,oBAAA;AAGE,oBAAA;AAEK,sBAAA;AAAA,sBAAA;AAEC,wBAAA;AACQ,wBAAA;AAAA,sBAAA;AAFQ,sBAAA;AAKtB,oBAAA;AAIe,oBAAA;AAEV,sBAAA;AAAA,sBAAA;AACS,wBAAA;AACH,wBAAA;AACQ,wBAAA;AACH,wBAAA;AACX,wBAAA;AAAA,0BAAA;AACsB,0BAAA;AAAA,wBAAA;AAAA,sBAAA;AAGP,oBAAA;AAAyB,sBAAA;AAClB,sBAAA;AAG3B,oBAAA;AAAA,kBAAA;AAAA,gBAAA;AAEJ,cAAA;AAEe,8BAAA;AAGT,gBAAA;AAAAA,kCAAAA;AAAC,oBAAA;AAAA,oBAAA;AACO,sBAAA;AACM,sBAAA;AACF,sBAAA;AAAA,oBAAA;AACZ,kBAAA;AACqB,kCAAA;AAInBA,kBAAAA;AAAC,oBAAA;AAAA,oBAAA;AACS,sBAAA;AACD,sBAAA;AACF,sBAAA;AACI,sBAAA;AACC,sBAAA;AACX,sBAAA;AAAA,oBAAA;AAED,kBAAA;AAEJ,gBAAA;AAGS,gBAAA;AAIU,gCAAA;AAGvB,cAAA;AAAA,YAAA;AAAA,UAAA;AACF,QAAA;AAEY,QAAA;AAAA,MAAA;AAAA,IAAA;AACd,EAAA;AAEJ;ADzB4E;AACA;AACA;AACA","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-EMWX6IKP.js","sourcesContent":[null,"import { FileIcon } from '@phosphor-icons/react/dist/csr/File';\nimport { WarningCircleIcon } from '@phosphor-icons/react/dist/csr/WarningCircle';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport Sparkle from '../../assets/icons/Sparkle';\nimport { cn } from '../../utils/utils';\nimport { getInitials } from '../../utils/getInitials';\nimport {\n SUPPORT_CHAT_MESSAGE_TYPE,\n SUPPORT_CHAT_SEND_STATUS,\n type SupportChatAttachment,\n type SupportChatAuthor,\n type SupportChatFaqReference,\n type SupportChatMessageType,\n type SupportChatSendStatus,\n} from '../../types/supportChat';\n\n/**\n * Props for a support (SAC) chat message\n */\nexport interface SupportChatMessageProps {\n /** Who authored the message (user, AI assistant, agent or internal note) */\n type: SupportChatMessageType;\n /** Message body; line breaks are preserved */\n text: string;\n /** Already formatted time, e.g. \"13:52\" */\n time: string;\n /** Author shown in the avatar (and as the name for agents/notes) */\n author?: SupportChatAuthor;\n /** FAQ the AI answer was based on (AI messages only) */\n faq?: SupportChatFaqReference;\n /** Files attached to the message */\n attachments?: SupportChatAttachment[];\n /** Delivery status; defaults to SENT */\n status?: SupportChatSendStatus;\n /** Called when the user clicks \"Tentar novamente\" on a failed message */\n onRetry?: () => void;\n /** Extra classes for the root element */\n className?: string;\n}\n\n/** Bubble classes for each message type */\nconst BUBBLE_CLASSES: Record<SupportChatMessageType, string> = {\n [SUPPORT_CHAT_MESSAGE_TYPE.USER]: 'bg-background-100',\n [SUPPORT_CHAT_MESSAGE_TYPE.GUEST]: 'bg-background-100',\n [SUPPORT_CHAT_MESSAGE_TYPE.AI]: 'bg-primary-50/60 border border-primary-100',\n [SUPPORT_CHAT_MESSAGE_TYPE.AGENT]: 'bg-primary-950',\n [SUPPORT_CHAT_MESSAGE_TYPE.INTERNAL_NOTE]:\n 'bg-exam-3 border border-warning-200',\n};\n\n/**\n * Avatar for the virtual assistant (sparkle icon)\n */\nfunction AiAvatar() {\n return (\n <div\n role=\"img\"\n aria-label=\"Assistente virtual\"\n className=\"flex size-6 shrink-0 items-center justify-center rounded-full border border-primary-100 bg-primary-50 text-primary-800\"\n >\n <Sparkle\n width={12}\n height={12}\n aria-hidden=\"true\"\n data-testid=\"support-chat-ai-sparkle\"\n />\n </div>\n );\n}\n\n/**\n * Avatar with the author's initials\n */\nfunction InitialsAvatar({ author }: Readonly<{ author: SupportChatAuthor }>) {\n return (\n <div\n role=\"img\"\n aria-label={author.name}\n className={cn(\n 'flex size-6 shrink-0 items-center justify-center rounded-full bg-primary-800',\n author.avatarClassName\n )}\n >\n <Text size=\"2xs\" weight=\"bold\" color=\"text-white\" aria-hidden=\"true\">\n {author.initials ?? getInitials(author.name)}\n </Text>\n </div>\n );\n}\n\n/**\n * Single attachment: image thumbnail or generic file icon, plus the file name\n */\nfunction AttachmentItem({\n attachment,\n isDark,\n}: Readonly<{ attachment: SupportChatAttachment; isDark: boolean }>) {\n const isImage = attachment.mimeType?.startsWith('image/') ?? false;\n return (\n <a\n href={attachment.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex max-w-full items-center gap-2\"\n >\n {isImage ? (\n <img\n src={attachment.url}\n alt={attachment.fileName}\n className=\"size-16 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <FileIcon\n size={20}\n aria-hidden=\"true\"\n className={cn('shrink-0', isDark ? 'text-white' : 'text-text-700')}\n />\n )}\n <Text\n as=\"span\"\n size=\"xs\"\n color={isDark ? 'text-white' : 'text-text-700'}\n className=\"truncate underline\"\n >\n {attachment.fileName}\n </Text>\n </a>\n );\n}\n\n/**\n * Support (SAC) chat message bubble.\n *\n * - USER: light gray bubble on the left with an initials avatar\n * - AI: light blue bubble on the left labeled \"ASSISTENTE VIRTUAL\", with an optional FAQ reference\n * - AGENT: dark blue bubble on the right with the agent name and avatar\n * - INTERNAL_NOTE: yellow bubble on the right, visible only in the backoffice\n */\nexport default function SupportChatMessage({\n type,\n text,\n time,\n author,\n faq,\n attachments,\n status = SUPPORT_CHAT_SEND_STATUS.SENT,\n onRetry,\n className,\n}: Readonly<SupportChatMessageProps>) {\n const isAi = type === SUPPORT_CHAT_MESSAGE_TYPE.AI;\n const isAgent = type === SUPPORT_CHAT_MESSAGE_TYPE.AGENT;\n const isNote = type === SUPPORT_CHAT_MESSAGE_TYPE.INTERNAL_NOTE;\n const isRight = isAgent || isNote;\n const isSending = status === SUPPORT_CHAT_SEND_STATUS.SENDING;\n const isFailed = status === SUPPORT_CHAT_SEND_STATUS.FAILED;\n\n const avatar = isAi ? (\n <AiAvatar />\n ) : (\n author && <InitialsAvatar author={author} />\n );\n\n return (\n <div\n className={cn(\n 'flex w-full items-end gap-2',\n isRight ? 'justify-end' : 'justify-start',\n className\n )}\n >\n {!isRight && avatar}\n\n <div\n className={cn(\n 'flex min-w-0 max-w-[85%] flex-col gap-1 sm:max-w-[498px]',\n isRight ? 'items-end' : 'items-start'\n )}\n >\n <div\n data-testid=\"support-chat-message-bubble\"\n className={cn(\n 'flex max-w-full flex-col gap-1.5 rounded-xl px-3.5 py-2.5',\n BUBBLE_CLASSES[type],\n isSending && 'opacity-60'\n )}\n >\n {isAi && (\n <Text\n size=\"2xs\"\n weight=\"bold\"\n color=\"text-primary-800\"\n className=\"uppercase tracking-wider\"\n >\n Assistente virtual\n </Text>\n )}\n\n {isNote && (\n <Text\n size=\"2xs\"\n weight=\"bold\"\n color=\"text-warning-700\"\n className=\"uppercase tracking-wider\"\n >\n {author ? `Nota interna · ${author.name}` : 'Nota interna'}\n </Text>\n )}\n\n {isAgent && author && (\n <Text size=\"2xs\" weight=\"bold\" color=\"text-primary-100\">\n {author.name}\n </Text>\n )}\n\n <Text\n size=\"sm\"\n color={isAgent ? 'text-white' : 'text-text-950'}\n className=\"whitespace-pre-wrap break-words\"\n >\n {text}\n </Text>\n\n {Array.isArray(attachments) && attachments.length > 0 && (\n <div className=\"flex flex-col gap-2\">\n {attachments.map((attachment) => (\n <AttachmentItem\n key={attachment.id}\n attachment={attachment}\n isDark={isAgent}\n />\n ))}\n </div>\n )}\n\n {isAi && faq && (\n <div className=\"self-stretch border-t border-primary-100 pt-1.5\">\n {faq.onClick ? (\n <Button\n variant=\"link\"\n size=\"extra-small\"\n onClick={faq.onClick}\n className=\"p-0 text-left font-normal text-primary-800\"\n >\n Baseado na FAQ: {faq.title}\n </Button>\n ) : (\n <Text size=\"xs\" color=\"text-primary-800\">\n Baseado na FAQ: {faq.title}\n </Text>\n )}\n </div>\n )}\n </div>\n\n <div className=\"flex items-center gap-2\">\n {isFailed && (\n <>\n <WarningCircleIcon\n size={14}\n aria-hidden=\"true\"\n className=\"text-error-600\"\n />\n <Text as=\"span\" size=\"2xs\" color=\"text-error-600\">\n Falha no envio\n </Text>\n {onRetry && (\n <Button\n variant=\"link\"\n action=\"negative\"\n size=\"extra-small\"\n onClick={onRetry}\n className=\"p-0\"\n >\n Tentar novamente\n </Button>\n )}\n </>\n )}\n {isSending && (\n <Text as=\"span\" size=\"2xs\" color=\"text-text-400\">\n Enviando…\n </Text>\n )}\n <Text as=\"span\" size=\"2xs\" color=\"text-text-400\">\n {time}\n </Text>\n </div>\n </div>\n\n {isRight && avatar}\n </div>\n );\n}\n","import { SVGProps } from 'react';\n\n/**\n * Outlined four-point sparkle icon (Figma \"icon/sparkle\").\n * 24x24 grid, 2px stroke; color follows `currentColor`.\n */\nconst Sparkle = (props: SVGProps<SVGSVGElement>) => {\n return (\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n d=\"M12 3C12.6 7.4 15.6 10.1 20 10.75C15.6 11.4 12.6 14.1 12 18.5C11.4 14.1 8.4 11.4 4 10.75C8.4 10.1 11.4 7.4 12 3Z\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n};\n\nexport default Sparkle;\n"]}
1
+ {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-7VJP5D26.js","../src/components/SupportChat/SupportChatMessage.tsx","../src/assets/icons/Sparkle.tsx"],"names":["jsx"],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-7VJP5D26.js","sourcesContent":[null,"import { FileIcon } from '@phosphor-icons/react/dist/csr/File';\nimport { WarningCircleIcon } from '@phosphor-icons/react/dist/csr/WarningCircle';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport Sparkle from '../../assets/icons/Sparkle';\nimport { cn } from '../../utils/utils';\nimport { getInitials } from '../../utils/getInitials';\nimport {\n SUPPORT_CHAT_MESSAGE_TYPE,\n SUPPORT_CHAT_SEND_STATUS,\n type SupportChatAttachment,\n type SupportChatAuthor,\n type SupportChatFaqReference,\n type SupportChatMessageType,\n type SupportChatSendStatus,\n} from '../../types/supportChat';\n\n/**\n * Props for a support (SAC) chat message\n */\nexport interface SupportChatMessageProps {\n /** Who authored the message (user, AI assistant, agent or internal note) */\n type: SupportChatMessageType;\n /** Message body; line breaks are preserved */\n text: string;\n /** Already formatted time, e.g. \"13:52\" */\n time: string;\n /** Author shown in the avatar (and as the name for agents/notes) */\n author?: SupportChatAuthor;\n /** FAQ the AI answer was based on (AI messages only) */\n faq?: SupportChatFaqReference;\n /** Files attached to the message */\n attachments?: SupportChatAttachment[];\n /** Delivery status; defaults to SENT */\n status?: SupportChatSendStatus;\n /** Called when the user clicks \"Tentar novamente\" on a failed message */\n onRetry?: () => void;\n /** Extra classes for the root element */\n className?: string;\n}\n\n/** Bubble classes for each message type */\nconst BUBBLE_CLASSES: Record<SupportChatMessageType, string> = {\n [SUPPORT_CHAT_MESSAGE_TYPE.USER]: 'bg-background-100',\n [SUPPORT_CHAT_MESSAGE_TYPE.GUEST]: 'bg-background-100',\n [SUPPORT_CHAT_MESSAGE_TYPE.AI]: 'bg-primary-50/60 border border-primary-100',\n [SUPPORT_CHAT_MESSAGE_TYPE.AGENT]: 'bg-primary-950',\n [SUPPORT_CHAT_MESSAGE_TYPE.INTERNAL_NOTE]:\n 'bg-exam-3 border border-warning-200',\n};\n\n/**\n * Avatar for the virtual assistant (sparkle icon)\n */\nfunction AiAvatar() {\n return (\n <div\n role=\"img\"\n aria-label=\"Assistente virtual\"\n className=\"flex size-6 shrink-0 items-center justify-center rounded-full border border-primary-100 bg-primary-50 text-primary-800\"\n >\n <Sparkle\n width={12}\n height={12}\n aria-hidden=\"true\"\n data-testid=\"support-chat-ai-sparkle\"\n />\n </div>\n );\n}\n\n/**\n * Avatar with the author's initials\n */\nfunction InitialsAvatar({ author }: Readonly<{ author: SupportChatAuthor }>) {\n return (\n <div\n role=\"img\"\n aria-label={author.name}\n className={cn(\n 'flex size-6 shrink-0 items-center justify-center rounded-full bg-primary-800',\n author.avatarClassName\n )}\n >\n <Text size=\"2xs\" weight=\"bold\" color=\"text-white\" aria-hidden=\"true\">\n {author.initials ?? getInitials(author.name)}\n </Text>\n </div>\n );\n}\n\n/**\n * Single attachment: image thumbnail or generic file icon, plus the file name\n */\nfunction AttachmentItem({\n attachment,\n isDark,\n}: Readonly<{ attachment: SupportChatAttachment; isDark: boolean }>) {\n const isImage = attachment.mimeType?.startsWith('image/') ?? false;\n return (\n <a\n href={attachment.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex max-w-full items-center gap-2\"\n >\n {isImage ? (\n <img\n src={attachment.url}\n alt={attachment.fileName}\n className=\"size-16 shrink-0 rounded-lg object-cover\"\n />\n ) : (\n <FileIcon\n size={20}\n aria-hidden=\"true\"\n className={cn('shrink-0', isDark ? 'text-white' : 'text-text-700')}\n />\n )}\n <Text\n as=\"span\"\n size=\"xs\"\n color={isDark ? 'text-white' : 'text-text-700'}\n className=\"truncate underline\"\n >\n {attachment.fileName}\n </Text>\n </a>\n );\n}\n\n/**\n * Support (SAC) chat message bubble.\n *\n * - USER: light gray bubble on the left with an initials avatar\n * - AI: light blue bubble on the left labeled \"ASSISTENTE VIRTUAL\", with an optional FAQ reference\n * - AGENT: dark blue bubble on the right with the agent name and avatar\n * - INTERNAL_NOTE: yellow bubble on the right, visible only in the backoffice\n */\nexport default function SupportChatMessage({\n type,\n text,\n time,\n author,\n faq,\n attachments,\n status = SUPPORT_CHAT_SEND_STATUS.SENT,\n onRetry,\n className,\n}: Readonly<SupportChatMessageProps>) {\n const isAi = type === SUPPORT_CHAT_MESSAGE_TYPE.AI;\n const isAgent = type === SUPPORT_CHAT_MESSAGE_TYPE.AGENT;\n const isNote = type === SUPPORT_CHAT_MESSAGE_TYPE.INTERNAL_NOTE;\n const isRight = isAgent || isNote;\n const isSending = status === SUPPORT_CHAT_SEND_STATUS.SENDING;\n const isFailed = status === SUPPORT_CHAT_SEND_STATUS.FAILED;\n\n const avatar = isAi ? (\n <AiAvatar />\n ) : (\n author && <InitialsAvatar author={author} />\n );\n\n return (\n <div\n className={cn(\n 'flex w-full items-end gap-2',\n isRight ? 'justify-end' : 'justify-start',\n className\n )}\n >\n {!isRight && avatar}\n\n <div\n className={cn(\n 'flex min-w-0 max-w-[85%] flex-col gap-1 sm:max-w-[498px]',\n isRight ? 'items-end' : 'items-start'\n )}\n >\n <div\n data-testid=\"support-chat-message-bubble\"\n className={cn(\n 'flex max-w-full flex-col gap-1.5 rounded-xl px-3.5 py-2.5',\n BUBBLE_CLASSES[type],\n isSending && 'opacity-60'\n )}\n >\n {isAi && (\n <Text\n size=\"2xs\"\n weight=\"bold\"\n color=\"text-primary-800\"\n className=\"uppercase tracking-wider\"\n >\n Assistente virtual\n </Text>\n )}\n\n {isNote && (\n <Text\n size=\"2xs\"\n weight=\"bold\"\n color=\"text-warning-700\"\n className=\"uppercase tracking-wider\"\n >\n {author ? `Nota interna · ${author.name}` : 'Nota interna'}\n </Text>\n )}\n\n {isAgent && author && (\n <Text size=\"2xs\" weight=\"bold\" color=\"text-primary-100\">\n {author.name}\n </Text>\n )}\n\n <Text\n size=\"sm\"\n color={isAgent ? 'text-white' : 'text-text-950'}\n className=\"whitespace-pre-wrap break-words\"\n >\n {text}\n </Text>\n\n {Array.isArray(attachments) && attachments.length > 0 && (\n <div className=\"flex flex-col gap-2\">\n {attachments.map((attachment) => (\n <AttachmentItem\n key={attachment.id}\n attachment={attachment}\n isDark={isAgent}\n />\n ))}\n </div>\n )}\n\n {isAi && faq && (\n <div className=\"self-stretch border-t border-primary-100 pt-1.5\">\n {faq.onClick ? (\n <Button\n variant=\"link\"\n size=\"extra-small\"\n onClick={faq.onClick}\n className=\"p-0 text-left font-normal text-primary-800\"\n >\n Baseado na FAQ: {faq.title}\n </Button>\n ) : (\n <Text size=\"xs\" color=\"text-primary-800\">\n Baseado na FAQ: {faq.title}\n </Text>\n )}\n </div>\n )}\n </div>\n\n <div className=\"flex items-center gap-2\">\n {isFailed && (\n <>\n <WarningCircleIcon\n size={14}\n aria-hidden=\"true\"\n className=\"text-error-600\"\n />\n <Text as=\"span\" size=\"2xs\" color=\"text-error-600\">\n Falha no envio\n </Text>\n {onRetry && (\n <Button\n variant=\"link\"\n action=\"negative\"\n size=\"extra-small\"\n onClick={onRetry}\n className=\"p-0\"\n >\n Tentar novamente\n </Button>\n )}\n </>\n )}\n {isSending && (\n <Text as=\"span\" size=\"2xs\" color=\"text-text-400\">\n Enviando…\n </Text>\n )}\n <Text as=\"span\" size=\"2xs\" color=\"text-text-400\">\n {time}\n </Text>\n </div>\n </div>\n\n {isRight && avatar}\n </div>\n );\n}\n","import { SVGProps } from 'react';\n\n/**\n * Outlined four-point sparkle icon (Figma \"icon/sparkle\").\n * 24x24 grid, 2px stroke; color follows `currentColor`.\n */\nconst Sparkle = (props: SVGProps<SVGSVGElement>) => {\n return (\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n d=\"M12 3C12.6 7.4 15.6 10.1 20 10.75C15.6 11.4 12.6 14.1 12 18.5C11.4 14.1 8.4 11.4 4 10.75C8.4 10.1 11.4 7.4 12 3Z\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n};\n\nexport default Sparkle;\n"]}
@@ -73,4 +73,4 @@ export {
73
73
  SUPPORT_CHAT_MAX_ATTACHMENTS,
74
74
  SUPPORT_CHAT_ATTACHMENT_RULES
75
75
  };
76
- //# sourceMappingURL=chunk-7ZB3YHF2.mjs.map
76
+ //# sourceMappingURL=chunk-C6LC5LD5.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types/supportChat.ts"],"sourcesContent":["import type { SupportType } from './support';\n\n/**\n * Support (SAC) chat types\n */\n\n/**\n * Who authored a support chat message. `USER`, `GUEST`, `AI` and `AGENT`\n * match the backend `senderRole`; `GUEST` (logged-out visitor) renders like `USER`.\n * `INTERNAL_NOTE` is only visible in the backoffice.\n */\nexport const SUPPORT_CHAT_MESSAGE_TYPE = {\n USER: 'USER',\n GUEST: 'GUEST',\n AI: 'AI',\n AGENT: 'AGENT',\n INTERNAL_NOTE: 'INTERNAL_NOTE',\n} as const;\n\nexport type SupportChatMessageType =\n (typeof SUPPORT_CHAT_MESSAGE_TYPE)[keyof typeof SUPPORT_CHAT_MESSAGE_TYPE];\n\n/**\n * Delivery status of a message sent by the current user/agent\n */\nexport const SUPPORT_CHAT_SEND_STATUS = {\n SENT: 'SENT',\n SENDING: 'SENDING',\n FAILED: 'FAILED',\n} as const;\n\nexport type SupportChatSendStatus =\n (typeof SUPPORT_CHAT_SEND_STATUS)[keyof typeof SUPPORT_CHAT_SEND_STATUS];\n\n/**\n * File attached to a support chat message (same shape as the backend attachment)\n */\nexport interface SupportChatAttachment {\n id: string;\n fileName: string;\n url: string;\n /** MIME type; `image/*` renders a thumbnail */\n mimeType?: string;\n /** File size in bytes */\n size?: number;\n}\n\n/**\n * Where an agent reply goes: to the requester (`USER`) or only to the team\n * (`INTERNAL_NOTE`, same value as the message type shown in the backoffice)\n */\nexport const SUPPORT_CHAT_REPLY_MODE = {\n USER: 'USER',\n INTERNAL_NOTE: 'INTERNAL_NOTE',\n} as const;\n\nexport type SupportChatReplyMode =\n (typeof SUPPORT_CHAT_REPLY_MODE)[keyof typeof SUPPORT_CHAT_REPLY_MODE];\n\n/**\n * Kind of event shown between chat messages\n */\nexport const SUPPORT_CHAT_EVENT_TYPE = {\n NEUTRAL: 'NEUTRAL',\n TRANSFER: 'TRANSFER',\n AI: 'AI',\n SUCCESS: 'SUCCESS',\n DATE: 'DATE',\n} as const;\n\nexport type SupportChatEventType =\n (typeof SUPPORT_CHAT_EVENT_TYPE)[keyof typeof SUPPORT_CHAT_EVENT_TYPE];\n\n/**\n * Author displayed next to a support chat message\n */\nexport interface SupportChatAuthor {\n name: string;\n /** Overrides the initials computed from `name` */\n initials?: string;\n /** Tailwind classes overriding the avatar background color */\n avatarClassName?: string;\n}\n\n/**\n * FAQ entry the AI answer was based on\n */\nexport interface SupportChatFaqReference {\n title: string;\n onClick?: () => void;\n}\n\n/*\n * Support chat widget (floating chat on top of a SupportChatAdapter)\n */\n\n/**\n * Ticket status, as sent by the backend (sup_support.status)\n */\nexport const SUPPORT_CHAT_STATUS = {\n ABERTO: 'ABERTO',\n PENDENTE: 'PENDENTE',\n EM_ANDAMENTO: 'EM_ANDAMENTO',\n FECHADO: 'FECHADO',\n} as const;\n\nexport type SupportChatStatus =\n (typeof SUPPORT_CHAT_STATUS)[keyof typeof SUPPORT_CHAT_STATUS];\n\n/**\n * Who is handling the ticket\n */\nexport const SUPPORT_CHAT_HANDLER = {\n IA: 'IA',\n HUMANO: 'HUMANO',\n} as const;\n\nexport type SupportChatHandler =\n (typeof SUPPORT_CHAT_HANDLER)[keyof typeof SUPPORT_CHAT_HANDLER];\n\n/**\n * Author of a message (sent by the backend as senderRole)\n */\nexport const SUPPORT_CHAT_AUTHOR = {\n USER: 'USER',\n GUEST: 'GUEST',\n AGENT: 'AGENT',\n AI: 'AI',\n} as const;\n\nexport type SupportChatAuthorRole =\n (typeof SUPPORT_CHAT_AUTHOR)[keyof typeof SUPPORT_CHAT_AUTHOR];\n\n/** senderId of the messages written by a visitor (not logged in) */\nexport const SUPPORT_CHAT_GUEST_SENDER_ID = 'guest';\n\n/** Images accepted by the backend */\nexport const SUPPORT_CHAT_IMAGE_TYPES = [\n 'image/jpeg',\n 'image/png',\n 'image/gif',\n 'image/webp',\n];\n\n/** Largest image the backend accepts (5 MB) */\nexport const SUPPORT_CHAT_MAX_IMAGE_BYTES = 5 * 1024 * 1024;\n\n/** Most attachments in one message */\nexport const SUPPORT_CHAT_MAX_ATTACHMENTS = 5;\n\nconst MB = 1024 * 1024;\n\n/**\n * Files the backend accepts in the support chat (mime type → largest size).\n * Mirrors SUPPORT_ATTACHMENT_RULES of the API: images, PDF and short videos.\n */\nexport const SUPPORT_CHAT_ATTACHMENT_RULES: Record<string, number> = {\n 'image/jpeg': 5 * MB,\n 'image/png': 5 * MB,\n 'image/gif': 5 * MB,\n 'image/webp': 5 * MB,\n 'application/pdf': 10 * MB,\n 'video/mp4': 50 * MB,\n 'video/webm': 50 * MB,\n 'video/quicktime': 50 * MB,\n};\n\n/**\n * FAQ article opened inside the chat (GET /help-center/:institutionId/articles/:id)\n */\nexport interface SupportChatArticle {\n id: string;\n title: string;\n /** Article HTML as saved by the help-center editor */\n body: string;\n /** ISO date of the last update, when known */\n updatedAt: string | null;\n}\n\n/**\n * Chat message (same shape as the platform chat, plus attachments)\n */\nexport interface SupportChatMessage {\n id: string;\n senderId: string;\n senderName: string;\n senderPhoto: string | null;\n senderRole: SupportChatAuthorRole;\n content: string;\n messageType: string;\n createdAt: string;\n attachments: SupportChatAttachment[];\n /** FAQ article an AI message is based on */\n faq?: { id: string; title: string } | null;\n /**\n * Contact fields an AI message asks for: the chat shows a form for them\n * under the message\n */\n contactRequest?: SupportChatContactField[] | null;\n /** Local only: shown before the server confirms it (optimistic) */\n pending?: boolean;\n}\n\n/**\n * Ticket the conversation belongs to\n */\nexport interface SupportChatTicket {\n id: string;\n status: SupportChatStatus;\n handlerType: SupportChatHandler | null;\n problemType: string | null;\n}\n\n/**\n * Realtime events pushed to the conversation (WebSocket in the real adapter)\n */\nexport type SupportChatEvent =\n | { type: 'new_message'; payload: { message: SupportChatMessage } }\n | {\n type: 'message_updated';\n payload: { messageId: string; content: string; read: boolean };\n }\n | {\n type: 'ticket_updated';\n payload: { ticketId: string } & Omit<SupportChatTicket, 'id'>;\n }\n | { type: 'error'; payload: { message: string } };\n\n/**\n * Data needed to open a conversation (first message creates the ticket)\n */\nexport interface StartSupportChatInput {\n message: string;\n channel: string;\n institutionId?: string;\n requesterName?: string;\n email?: string;\n /** Files sent with the first message */\n files?: File[];\n}\n\n/**\n * Data layer of the widget. The mock adapter implements it in memory; the\n * real one will use the support REST API and the chat WebSocket.\n */\nexport interface SupportChatAdapter {\n /**\n * Create the ticket with its first message. With files the API may store\n * them as a second message, hence the list.\n */\n startConversation(input: StartSupportChatInput): Promise<{\n ticket: SupportChatTicket;\n messages: SupportChatMessage[];\n }>;\n /** Send a message (text and/or files) on an existing ticket */\n sendMessage(\n ticketId: string,\n content: string,\n files?: File[]\n ): Promise<SupportChatMessage>;\n /** Messages of the ticket, oldest first */\n listMessages(ticketId: string): Promise<SupportChatMessage[]>;\n /** Listen to realtime events of the ticket; returns the unsubscribe */\n subscribe(\n ticketId: string,\n listener: (event: SupportChatEvent) => void\n ): () => void;\n /**\n * Save the name and/or e-mail the visitor typed in the contact form.\n * Without it the chat does not show the form (the AI request stays a text).\n */\n updateContact?(ticketId: string, contact: SupportChatContact): Promise<void>;\n /**\n * Load a FAQ article of the institution. With it, the article an AI answer\n * is based on opens inside the chat instead of a new tab.\n */\n getArticle?(\n institutionId: string,\n articleId: string\n ): Promise<SupportChatArticle>;\n}\n\n/** Contact field the AI asks for */\nexport type SupportChatContactField = 'name' | 'email';\n\n/** Contact sent by the chat form */\nexport interface SupportChatContact {\n requesterName?: string;\n email?: string;\n}\n\n/**\n * Feature flags of the widget\n */\nexport interface SupportChatFlags {\n /** Widget available at all (default true) */\n enabled?: boolean;\n /** Chat with the support (default true) */\n chat?: boolean;\n /** FAQ (reserved, not rendered yet) */\n faq?: boolean;\n /** AI first answer (reserved, not rendered yet) */\n ai?: boolean;\n /** ZENDESK hides the widget (the Zendesk messenger is used instead) */\n supportType?: SupportType;\n}\n\n/**\n * Visual customization of the widget\n */\nexport interface SupportChatTheme {\n /** Background of the button and the header (any CSS color) */\n primaryColor?: string;\n /** Corner of the screen (default bottom-right) */\n position?: 'bottom-right' | 'bottom-left';\n /** Header title (default \"Suporte\") */\n title?: string;\n}\n\n/**\n * Logged user (optional; a visitor has none)\n */\nexport interface SupportChatUser {\n name?: string;\n email?: string;\n}\n\n/**\n * HTTP client the API adapter needs (axios instances fit as they are)\n */\nexport interface SupportChatHttpClient {\n get: <T>(\n url: string,\n config?: { headers?: Record<string, string> }\n ) => Promise<{ data: T }>;\n post: <T>(\n url: string,\n data?: unknown,\n config?: { headers?: Record<string, string> }\n ) => Promise<{ data: T }>;\n /** Used to save the visitor contact (PUT /support/guest/:id) */\n put?: <T>(\n url: string,\n data?: unknown,\n config?: { headers?: Record<string, string> }\n ) => Promise<{ data: T }>;\n}\n\n/**\n * Message as returned by the support REST routes\n */\nexport interface SupportChatAnswerApi {\n id: string;\n authorType: SupportChatAuthorRole;\n answer: string;\n createdAt: string;\n attachments?: SupportChatAttachment[];\n /** FAQ article an AI message is based on */\n faqArticleId?: string | null;\n faqArticleTitle?: string | null;\n /** Contact fields an AI message asks for */\n contactRequest?: SupportChatContactField[] | null;\n}\n"],"mappings":";AAWO,IAAM,4BAA4B;AAAA,EACvC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,eAAe;AACjB;AAQO,IAAM,2BAA2B;AAAA,EACtC,MAAM;AAAA,EACN,SAAS;AAAA,EACT,QAAQ;AACV;AAsBO,IAAM,0BAA0B;AAAA,EACrC,MAAM;AAAA,EACN,eAAe;AACjB;AAQO,IAAM,0BAA0B;AAAA,EACrC,SAAS;AAAA,EACT,UAAU;AAAA,EACV,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,MAAM;AACR;AA+BO,IAAM,sBAAsB;AAAA,EACjC,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,cAAc;AAAA,EACd,SAAS;AACX;AAQO,IAAM,uBAAuB;AAAA,EAClC,IAAI;AAAA,EACJ,QAAQ;AACV;AAQO,IAAM,sBAAsB;AAAA,EACjC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,IAAI;AACN;AAMO,IAAM,+BAA+B;AAGrC,IAAM,2BAA2B;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,+BAA+B,IAAI,OAAO;AAGhD,IAAM,+BAA+B;AAE5C,IAAM,KAAK,OAAO;AAMX,IAAM,gCAAwD;AAAA,EACnE,cAAc,IAAI;AAAA,EAClB,aAAa,IAAI;AAAA,EACjB,aAAa,IAAI;AAAA,EACjB,cAAc,IAAI;AAAA,EAClB,mBAAmB,KAAK;AAAA,EACxB,aAAa,KAAK;AAAA,EAClB,cAAc,KAAK;AAAA,EACnB,mBAAmB,KAAK;AAC1B;","names":[]}
1
+ {"version":3,"sources":["../src/types/supportChat.ts"],"sourcesContent":["import type { SupportType } from './support';\n\n/**\n * Support (SAC) chat types\n */\n\n/**\n * Who authored a support chat message. `USER`, `GUEST`, `AI` and `AGENT`\n * match the backend `senderRole`; `GUEST` (logged-out visitor) renders like `USER`.\n * `INTERNAL_NOTE` is only visible in the backoffice.\n */\nexport const SUPPORT_CHAT_MESSAGE_TYPE = {\n USER: 'USER',\n GUEST: 'GUEST',\n AI: 'AI',\n AGENT: 'AGENT',\n INTERNAL_NOTE: 'INTERNAL_NOTE',\n} as const;\n\nexport type SupportChatMessageType =\n (typeof SUPPORT_CHAT_MESSAGE_TYPE)[keyof typeof SUPPORT_CHAT_MESSAGE_TYPE];\n\n/**\n * Delivery status of a message sent by the current user/agent\n */\nexport const SUPPORT_CHAT_SEND_STATUS = {\n SENT: 'SENT',\n SENDING: 'SENDING',\n FAILED: 'FAILED',\n} as const;\n\nexport type SupportChatSendStatus =\n (typeof SUPPORT_CHAT_SEND_STATUS)[keyof typeof SUPPORT_CHAT_SEND_STATUS];\n\n/**\n * File attached to a support chat message (same shape as the backend attachment)\n */\nexport interface SupportChatAttachment {\n id: string;\n fileName: string;\n url: string;\n /** MIME type; `image/*` renders a thumbnail */\n mimeType?: string;\n /** File size in bytes */\n size?: number;\n}\n\n/**\n * Where an agent reply goes: to the requester (`USER`) or only to the team\n * (`INTERNAL_NOTE`, same value as the message type shown in the backoffice)\n */\nexport const SUPPORT_CHAT_REPLY_MODE = {\n USER: 'USER',\n INTERNAL_NOTE: 'INTERNAL_NOTE',\n} as const;\n\nexport type SupportChatReplyMode =\n (typeof SUPPORT_CHAT_REPLY_MODE)[keyof typeof SUPPORT_CHAT_REPLY_MODE];\n\n/**\n * Kind of event shown between chat messages\n */\nexport const SUPPORT_CHAT_EVENT_TYPE = {\n NEUTRAL: 'NEUTRAL',\n TRANSFER: 'TRANSFER',\n AI: 'AI',\n SUCCESS: 'SUCCESS',\n DATE: 'DATE',\n} as const;\n\nexport type SupportChatEventType =\n (typeof SUPPORT_CHAT_EVENT_TYPE)[keyof typeof SUPPORT_CHAT_EVENT_TYPE];\n\n/**\n * Author displayed next to a support chat message\n */\nexport interface SupportChatAuthor {\n name: string;\n /** Overrides the initials computed from `name` */\n initials?: string;\n /** Tailwind classes overriding the avatar background color */\n avatarClassName?: string;\n}\n\n/**\n * FAQ entry the AI answer was based on\n */\nexport interface SupportChatFaqReference {\n title: string;\n onClick?: () => void;\n}\n\n/*\n * Support chat widget (floating chat on top of a SupportChatAdapter)\n */\n\n/**\n * Ticket status, as sent by the backend (sup_support.status)\n */\nexport const SUPPORT_CHAT_STATUS = {\n ABERTO: 'ABERTO',\n PENDENTE: 'PENDENTE',\n EM_ANDAMENTO: 'EM_ANDAMENTO',\n FECHADO: 'FECHADO',\n} as const;\n\nexport type SupportChatStatus =\n (typeof SUPPORT_CHAT_STATUS)[keyof typeof SUPPORT_CHAT_STATUS];\n\n/**\n * Who is handling the ticket\n */\nexport const SUPPORT_CHAT_HANDLER = {\n IA: 'IA',\n HUMANO: 'HUMANO',\n} as const;\n\nexport type SupportChatHandler =\n (typeof SUPPORT_CHAT_HANDLER)[keyof typeof SUPPORT_CHAT_HANDLER];\n\n/**\n * Author of a message (sent by the backend as senderRole)\n */\nexport const SUPPORT_CHAT_AUTHOR = {\n USER: 'USER',\n GUEST: 'GUEST',\n AGENT: 'AGENT',\n AI: 'AI',\n} as const;\n\nexport type SupportChatAuthorRole =\n (typeof SUPPORT_CHAT_AUTHOR)[keyof typeof SUPPORT_CHAT_AUTHOR];\n\n/** senderId of the messages written by a visitor (not logged in) */\nexport const SUPPORT_CHAT_GUEST_SENDER_ID = 'guest';\n\n/** Images accepted by the backend */\nexport const SUPPORT_CHAT_IMAGE_TYPES = [\n 'image/jpeg',\n 'image/png',\n 'image/gif',\n 'image/webp',\n];\n\n/** Largest image the backend accepts (5 MB) */\nexport const SUPPORT_CHAT_MAX_IMAGE_BYTES = 5 * 1024 * 1024;\n\n/** Most attachments in one message */\nexport const SUPPORT_CHAT_MAX_ATTACHMENTS = 5;\n\nconst MB = 1024 * 1024;\n\n/**\n * Files the backend accepts in the support chat (mime type → largest size).\n * Mirrors SUPPORT_ATTACHMENT_RULES of the API: images, PDF and short videos.\n */\nexport const SUPPORT_CHAT_ATTACHMENT_RULES: Record<string, number> = {\n 'image/jpeg': 5 * MB,\n 'image/png': 5 * MB,\n 'image/gif': 5 * MB,\n 'image/webp': 5 * MB,\n 'application/pdf': 10 * MB,\n 'video/mp4': 50 * MB,\n 'video/webm': 50 * MB,\n 'video/quicktime': 50 * MB,\n};\n\n/**\n * FAQ article opened inside the chat (GET /help-center/:institutionId/articles/:id)\n */\nexport interface SupportChatArticle {\n id: string;\n title: string;\n /** Article HTML as saved by the help-center editor */\n body: string;\n /** ISO date of the last update, when known */\n updatedAt: string | null;\n}\n\n/**\n * Chat message (same shape as the platform chat, plus attachments)\n */\nexport interface SupportChatMessage {\n id: string;\n senderId: string;\n senderName: string;\n senderPhoto: string | null;\n senderRole: SupportChatAuthorRole;\n content: string;\n messageType: string;\n createdAt: string;\n attachments: SupportChatAttachment[];\n /** FAQ article an AI message is based on */\n faq?: { id: string; title: string } | null;\n /**\n * Contact fields an AI message asks for: the chat shows a form for them\n * under the message\n */\n contactRequest?: SupportChatContactField[] | null;\n /** Local only: shown before the server confirms it (optimistic) */\n pending?: boolean;\n}\n\n/**\n * Ticket the conversation belongs to\n */\nexport interface SupportChatTicket {\n id: string;\n status: SupportChatStatus;\n handlerType: SupportChatHandler | null;\n problemType: string | null;\n}\n\n/**\n * Realtime events pushed to the conversation (WebSocket in the real adapter)\n */\nexport type SupportChatEvent =\n | { type: 'new_message'; payload: { message: SupportChatMessage } }\n | {\n type: 'message_updated';\n payload: { messageId: string; content: string; read: boolean };\n }\n | {\n type: 'ticket_updated';\n payload: { ticketId: string } & Omit<SupportChatTicket, 'id'>;\n }\n | { type: 'error'; payload: { message: string } };\n\n/**\n * Data needed to open a conversation (first message creates the ticket)\n */\nexport interface StartSupportChatInput {\n message: string;\n channel: string;\n institutionId?: string;\n requesterName?: string;\n email?: string;\n /** Files sent with the first message */\n files?: File[];\n}\n\n/**\n * Data layer of the widget. The mock adapter implements it in memory; the\n * real one will use the support REST API and the chat WebSocket.\n */\nexport interface SupportChatAdapter {\n /**\n * Create the ticket with its first message. With files the API may store\n * them as a second message, hence the list.\n */\n startConversation(input: StartSupportChatInput): Promise<{\n ticket: SupportChatTicket;\n messages: SupportChatMessage[];\n }>;\n /** Send a message (text and/or files) on an existing ticket */\n sendMessage(\n ticketId: string,\n content: string,\n files?: File[]\n ): Promise<SupportChatMessage>;\n /** Messages of the ticket, oldest first */\n listMessages(ticketId: string): Promise<SupportChatMessage[]>;\n /** Listen to realtime events of the ticket; returns the unsubscribe */\n subscribe(\n ticketId: string,\n listener: (event: SupportChatEvent) => void\n ): () => void;\n /**\n * Save the name and/or e-mail the visitor typed in the contact form.\n * Without it the chat does not show the form (the AI request stays a text).\n */\n updateContact?(ticketId: string, contact: SupportChatContact): Promise<void>;\n /**\n * Conversation kept from an earlier page load that is still unresolved, so\n * a reload does not drop it. Null when there is none, when it was resolved\n * or when it belongs to another user (`email` of the logged user; omit for\n * a visitor).\n */\n restoreConversation?(input: { email?: string }): Promise<{\n ticket: SupportChatTicket;\n messages: SupportChatMessage[];\n } | null>;\n /** Stop keeping the conversation (resolved, or the chat started over) */\n forgetConversation?(): void;\n /**\n * Load a FAQ article of the institution. With it, the article an AI answer\n * is based on opens inside the chat instead of a new tab.\n */\n getArticle?(\n institutionId: string,\n articleId: string\n ): Promise<SupportChatArticle>;\n}\n\n/** Contact field the AI asks for */\nexport type SupportChatContactField = 'name' | 'email';\n\n/** Contact sent by the chat form */\nexport interface SupportChatContact {\n requesterName?: string;\n email?: string;\n}\n\n/**\n * Feature flags of the widget\n */\nexport interface SupportChatFlags {\n /** Widget available at all (default true) */\n enabled?: boolean;\n /** Chat with the support (default true) */\n chat?: boolean;\n /** FAQ (reserved, not rendered yet) */\n faq?: boolean;\n /** AI first answer (reserved, not rendered yet) */\n ai?: boolean;\n /** ZENDESK hides the widget (the Zendesk messenger is used instead) */\n supportType?: SupportType;\n}\n\n/**\n * Visual customization of the widget\n */\nexport interface SupportChatTheme {\n /** Background of the button and the header (any CSS color) */\n primaryColor?: string;\n /** Corner of the screen (default bottom-right) */\n position?: 'bottom-right' | 'bottom-left';\n /** Header title (default \"Suporte\") */\n title?: string;\n}\n\n/**\n * Logged user (optional; a visitor has none)\n */\nexport interface SupportChatUser {\n name?: string;\n email?: string;\n}\n\n/**\n * HTTP client the API adapter needs (axios instances fit as they are)\n */\nexport interface SupportChatHttpClient {\n get: <T>(\n url: string,\n config?: { headers?: Record<string, string> }\n ) => Promise<{ data: T }>;\n post: <T>(\n url: string,\n data?: unknown,\n config?: { headers?: Record<string, string> }\n ) => Promise<{ data: T }>;\n /** Used to save the visitor contact (PUT /support/guest/:id) */\n put?: <T>(\n url: string,\n data?: unknown,\n config?: { headers?: Record<string, string> }\n ) => Promise<{ data: T }>;\n}\n\n/**\n * Message as returned by the support REST routes\n */\nexport interface SupportChatAnswerApi {\n id: string;\n authorType: SupportChatAuthorRole;\n answer: string;\n createdAt: string;\n attachments?: SupportChatAttachment[];\n /** FAQ article an AI message is based on */\n faqArticleId?: string | null;\n faqArticleTitle?: string | null;\n /** Contact fields an AI message asks for */\n contactRequest?: SupportChatContactField[] | null;\n}\n"],"mappings":";AAWO,IAAM,4BAA4B;AAAA,EACvC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,eAAe;AACjB;AAQO,IAAM,2BAA2B;AAAA,EACtC,MAAM;AAAA,EACN,SAAS;AAAA,EACT,QAAQ;AACV;AAsBO,IAAM,0BAA0B;AAAA,EACrC,MAAM;AAAA,EACN,eAAe;AACjB;AAQO,IAAM,0BAA0B;AAAA,EACrC,SAAS;AAAA,EACT,UAAU;AAAA,EACV,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,MAAM;AACR;AA+BO,IAAM,sBAAsB;AAAA,EACjC,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,cAAc;AAAA,EACd,SAAS;AACX;AAQO,IAAM,uBAAuB;AAAA,EAClC,IAAI;AAAA,EACJ,QAAQ;AACV;AAQO,IAAM,sBAAsB;AAAA,EACjC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,IAAI;AACN;AAMO,IAAM,+BAA+B;AAGrC,IAAM,2BAA2B;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,+BAA+B,IAAI,OAAO;AAGhD,IAAM,+BAA+B;AAE5C,IAAM,KAAK,OAAO;AAMX,IAAM,gCAAwD;AAAA,EACnE,cAAc,IAAI;AAAA,EAClB,aAAa,IAAI;AAAA,EACjB,aAAa,IAAI;AAAA,EACjB,cAAc,IAAI;AAAA,EAClB,mBAAmB,KAAK;AAAA,EACxB,aAAa,KAAK;AAAA,EAClB,cAAc,KAAK;AAAA,EACnB,mBAAmB,KAAK;AAC1B;","names":[]}