@adatechnology/conversations-ui 0.1.0-rc.3 → 0.1.0-rc.30

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 (143) hide show
  1. package/dist/chunk-DXPSPUWF.js +110 -0
  2. package/dist/chunk-GY472G6E.js +2316 -0
  3. package/dist/{chunk-OGRRHQQW.js → chunk-WCBDXZ3X.js} +68 -4
  4. package/dist/flows/index.d.ts +311 -4
  5. package/dist/flows/index.js +1322 -55
  6. package/dist/index.d.ts +1074 -42
  7. package/dist/index.js +3571 -742
  8. package/dist/preview/index.d.ts +328 -8
  9. package/dist/preview/index.js +902 -115
  10. package/dist/styles.css +819 -0
  11. package/dist/types-De5aN-E_.d.ts +502 -0
  12. package/package.json +3 -3
  13. package/src/AudioPlayer.tsx +8 -0
  14. package/src/AudioRecorderButton.test.tsx +30 -0
  15. package/src/AudioRecorderButton.tsx +248 -0
  16. package/src/AudioTranscription.test.tsx +115 -0
  17. package/src/AudioTranscription.tsx +252 -0
  18. package/src/Avatar.tsx +14 -3
  19. package/src/ConversationContextPanel.tsx +218 -44
  20. package/src/ConversationDocumentsPanel.tsx +347 -24
  21. package/src/ConversationHeader.test.tsx +66 -0
  22. package/src/ConversationHeader.tsx +163 -45
  23. package/src/ConversationListItem.tsx +19 -2
  24. package/src/ConversationLocalesProvider.tsx +42 -0
  25. package/src/ConversationRow.tsx +31 -7
  26. package/src/DocumentsLibrary.tsx +382 -0
  27. package/src/EmojiPicker.tsx +70 -55
  28. package/src/FileIcon.test.ts +83 -0
  29. package/src/FileIcon.tsx +88 -11
  30. package/src/InteractiveMessage.test.tsx +41 -0
  31. package/src/InteractiveMessage.tsx +146 -0
  32. package/src/Lightbox.tsx +18 -3
  33. package/src/MediaRenderer.tsx +98 -22
  34. package/src/MessageBubble.test.tsx +41 -0
  35. package/src/MessageBubble.tsx +75 -6
  36. package/src/MessageComposer.test.tsx +35 -0
  37. package/src/MessageComposer.tsx +155 -19
  38. package/src/RichMessageComposer.test.tsx +113 -0
  39. package/src/RichMessageComposer.tsx +551 -0
  40. package/src/SimpleEmojiPicker.tsx +5 -3
  41. package/src/StatusTicks.tsx +1 -1
  42. package/src/Toast.tsx +4 -0
  43. package/src/Tooltip.test.ts +42 -0
  44. package/src/Tooltip.tsx +164 -0
  45. package/src/Wallpaper.test.tsx +21 -0
  46. package/src/Wallpaper.tsx +67 -7
  47. package/src/WhatsAppMessageEditor.tsx +28 -4
  48. package/src/WindowExpiredNotice.tsx +12 -4
  49. package/src/audioRecorderFormat.test.ts +67 -0
  50. package/src/buildOutput.test.ts +79 -0
  51. package/src/composer.constant.ts +33 -0
  52. package/src/conversationTranscript.test.ts +57 -0
  53. package/src/conversationTranscript.ts +29 -4
  54. package/src/conversationWindow.ts +7 -5
  55. package/src/documentTypeLabel.test.ts +57 -0
  56. package/src/documents/DocumentsWorkspace.tsx +543 -0
  57. package/src/documents/index.ts +8 -0
  58. package/src/documents/labels.ts +92 -0
  59. package/src/emojiCatalog.test.ts +35 -0
  60. package/src/emojiCatalog.ts +189 -0
  61. package/src/flows/FlowGroupHeader.tsx +12 -2
  62. package/src/flows/FlowMapCanvas.tsx +15 -12
  63. package/src/flows/FlowMapNode.tsx +4 -1
  64. package/src/flows/FlowNodeCard.tsx +35 -8
  65. package/src/flows/FlowNodePanel.tsx +149 -38
  66. package/src/flows/FlowPalette.tsx +13 -3
  67. package/src/flows/FlowPortalNode.tsx +1 -1
  68. package/src/flows/FlowWhatsAppPreview.tsx +14 -3
  69. package/src/flows/FlowsWorkspace.tsx +1003 -0
  70. package/src/flows/flowCanvasModel.test.ts +293 -0
  71. package/src/flows/flowCanvasModel.ts +342 -0
  72. package/src/flows/flowEditorOps.test.ts +241 -0
  73. package/src/flows/flowEditorOps.ts +177 -0
  74. package/src/flows/flowGraph.ts +6 -6
  75. package/src/flows/index.ts +40 -1
  76. package/src/flows/labels.ts +141 -0
  77. package/src/flows/workspaceContract.test.ts +95 -0
  78. package/src/hooks/useContainerWidth.ts +35 -0
  79. package/src/hooks/useConversationActions.ts +56 -0
  80. package/src/hooks/useConversationDocuments.ts +11 -7
  81. package/src/hooks/useConversationList.ts +15 -9
  82. package/src/hooks/useConversationMessages.ts +2 -2
  83. package/src/hooks/useConversationRealtime.ts +10 -8
  84. package/src/hooks/useScrollToLatestMessage.ts +127 -0
  85. package/src/hooks/useUrlFilterState.ts +107 -0
  86. package/src/icon.constant.ts +12 -0
  87. package/src/index.ts +114 -13
  88. package/src/lib/cn.test.ts +29 -0
  89. package/src/lib/composer-formatting.test.ts +78 -0
  90. package/src/lib/composer-formatting.ts +145 -0
  91. package/src/lib/createMediaUrlResolver.ts +33 -0
  92. package/src/lib/paginated.test.ts +33 -0
  93. package/src/lib/paginated.ts +26 -0
  94. package/src/lib/whatsapp-formatting.test.tsx +37 -0
  95. package/src/lib/whatsapp-formatting.tsx +28 -3
  96. package/src/listing/index.tsx +202 -0
  97. package/src/pagination.constant.ts +10 -0
  98. package/src/preview/ConversationPreview.tsx +199 -13
  99. package/src/preview/ConversationSimulatorPanel.test.tsx +55 -0
  100. package/src/preview/ConversationSimulatorPanel.tsx +89 -0
  101. package/src/preview/MediaTypesPreview.tsx +87 -0
  102. package/src/preview/conversationPreviewFailures.test.ts +64 -0
  103. package/src/preview/createMockConversationsApi.ts +175 -15
  104. package/src/preview/createPreviewBridgeClient.test.ts +92 -0
  105. package/src/preview/createPreviewBridgeClient.ts +124 -0
  106. package/src/preview/createPreviewMediaUploader.ts +82 -0
  107. package/src/preview/createPreviewWebhookClient.test.ts +96 -0
  108. package/src/preview/createPreviewWebhookClient.ts +127 -4
  109. package/src/preview/index.ts +33 -3
  110. package/src/preview/mediaTypeOf.test.ts +15 -0
  111. package/src/preview/mockDocumentsSearch.test.ts +57 -0
  112. package/src/preview/preview.test.ts +5 -3
  113. package/src/preview/previewFileSamples.test.ts +151 -0
  114. package/src/preview/previewFileSamples.ts +74 -0
  115. package/src/preview/previewFixtures.ts +288 -1
  116. package/src/preview/previewMediaSource.test.ts +62 -0
  117. package/src/preview/previewMediaSource.ts +91 -0
  118. package/src/preview/previewMediaUploader.test.ts +61 -0
  119. package/src/providers/ConversationsProvider.tsx +8 -6
  120. package/src/providers/types.ts +185 -10
  121. package/src/quickReply.test.ts +58 -0
  122. package/src/settings/MessagesWorkspace.tsx +468 -0
  123. package/src/settings/TopicsForm.tsx +2 -0
  124. package/src/settings/TranscriptionSettingsForm.test.tsx +81 -0
  125. package/src/settings/TranscriptionSettingsForm.tsx +190 -0
  126. package/src/settings/WelcomeFarewellForm.tsx +1 -0
  127. package/src/settings/WhatsAppCreateTemplateForm.tsx +4 -1
  128. package/src/settings/WhatsAppTemplateSettingsForm.tsx +5 -2
  129. package/src/settings/WhatsAppTemplatesSettings.tsx +9 -1
  130. package/src/styles.css +783 -0
  131. package/src/types.ts +64 -1
  132. package/src/useWaitingNotifications.ts +74 -29
  133. package/src/workspace/BulkTemplateModal.tsx +132 -0
  134. package/src/workspace/ConversationPane.tsx +432 -0
  135. package/src/workspace/ConversationsInboxList.tsx +194 -0
  136. package/src/workspace/ConversationsWorkspace.tsx +346 -0
  137. package/src/workspace/index.ts +12 -0
  138. package/src/workspace/labels.test.ts +17 -0
  139. package/src/workspace/labels.ts +85 -0
  140. package/src/workspace/useConversationsInbox.ts +332 -0
  141. package/dist/chunk-N7B24WYD.js +0 -719
  142. package/dist/chunk-NV2RZ5KT.js +0 -56
  143. package/dist/types-C0PtaO7S.d.ts +0 -207
@@ -1,28 +1,196 @@
1
- import type { MessagePayload } from '../types'
1
+ import type { MessagePayload, MessageTranscription } from '../types'
2
2
  import type { ConversationChannel } from '../conversationChannel'
3
3
 
4
+ export interface ListConversationsParams {
5
+ page?: number
6
+ limit?: number
7
+ waitingHuman?: boolean
8
+ search?: string
9
+ /**
10
+ * Recortes que só o produto conhece (tipo de financiamento, carteira, campanha) repassados
11
+ * crus ao backend dele. É o que evita o vocabulário de uma vertical virar campo fixo aqui:
12
+ * o pacote transporta o filtro sem saber o que ele significa.
13
+ */
14
+ filters?: Record<string, string | undefined>
15
+ }
16
+
17
+ /**
18
+ * Página com o total, para a UI conseguir desenhar controles de paginação.
19
+ *
20
+ * `fetchConversations` devolve isto **ou** o array puro de antes: implementações existentes
21
+ * continuam válidas sem mudar uma linha, e quem precisa paginar passa a ter o total. Sem a união
22
+ * seria impossível saber se um retorno curto é a última página ou uma página cheia por acaso.
23
+ */
24
+ export interface ConversationPage {
25
+ conversations: ConversationSummary[]
26
+ total: number
27
+ }
28
+
29
+ export interface ListDocumentsParams {
30
+ search?: string
31
+ page?: number
32
+ /** Tamanho da página. Sem ele, `page` sozinho não define fatia nenhuma. */
33
+ limit?: number
34
+ /**
35
+ * Origem do arquivo (`customer`, `agent`, `bot`…). O vocabulário é do host.
36
+ *
37
+ * Seleção múltipla viaja como lista separada por vírgula em vez de virar `string[]`: mudar o
38
+ * tipo quebraria em compile-time toda implementação de host que já repassa este campo adiante,
39
+ * e o ganho seria nenhum — quem recebe faz `split(',')`.
40
+ */
41
+ source?: string
42
+ /** Categoria do arquivo (`document`, `image`, `audio`, `video`…), mesma convenção de lista. */
43
+ fileCategory?: string
44
+ /** Recorte por data de recebimento, em `YYYY-MM-DD`. */
45
+ startDate?: string
46
+ endDate?: string
47
+ sortDirection?: 'asc' | 'desc'
48
+ /** Coluna ordenada. Ausente, o host ordena pela data — é o padrão de toda listagem de arquivo. */
49
+ sortField?: string
50
+ /**
51
+ * Filtros que só existem no produto (`clientId`, `unidade`…). O pacote não os interpreta: passa
52
+ * adiante o que o host injetou pelo slot de filtros. É a porta que evita um fork da tela por
53
+ * causa de um `<select>`.
54
+ */
55
+ extra?: Readonly<Record<string, string | number>>
56
+ }
57
+
58
+ /** Arquivo na biblioteca da empresa: o mesmo da conversa, mais de qual conversa veio. */
59
+ export interface CompanyDocument extends ConversationDocument {
60
+ conversationId: string
61
+ /**
62
+ * Nome de quem enviou, quando o host o conhece. Opcional porque a biblioteca sempre tem o
63
+ * telefone e nem todo produto tem cadastro por trás dele — ausente, a coluna cai para o número.
64
+ */
65
+ contactName?: string | null
66
+ }
67
+
68
+ export interface CompanyDocumentPage {
69
+ documents: CompanyDocument[]
70
+ total: number
71
+ }
72
+
73
+ export interface ConversationDocumentPage {
74
+ documents: ConversationDocument[]
75
+ total: number
76
+ }
77
+
78
+ /**
79
+ * Template disponível para envio a partir da inbox. Distinto do `WhatsAppTemplateSummary` de
80
+ * `settings/`, e de propósito: aquele serve ao formulário que **edita** template e carrega o que
81
+ * a edição precisa (`shortId`, `variableCount`); este serve a quem só vai **escolher um para
82
+ * enviar**, e pedir os campos de edição obrigaria todo host a produzi-los sem uso.
83
+ */
84
+ export interface ConversationTemplate {
85
+ name: string
86
+ language: string
87
+ status: string
88
+ category?: string
89
+ bodyText?: string | null
90
+ }
91
+
4
92
  export interface ConversationsApi {
5
93
  fetchMessages(conversationId: string, params?: { limit?: number; before?: string }): Promise<MessagePayload[]>
6
- fetchConversations(params?: {
7
- page?: number
8
- limit?: number
9
- waitingHuman?: boolean
10
- search?: string
11
- }): Promise<ConversationSummary[]>
94
+ fetchConversations(params?: ListConversationsParams): Promise<ConversationSummary[] | ConversationPage>
12
95
  sendMessage(conversationId: string, text: string): Promise<MessagePayload>
13
96
  sendMedia(
14
97
  conversationId: string,
15
98
  data: { base64: string; mimeType: string; filename: string; caption?: string },
16
99
  ): Promise<MessagePayload>
100
+ /**
101
+ * `templateName` é opcional porque reabrir a janela é a operação, e escolher *qual* template a
102
+ * usa nem sempre é decisão da UI: backends que guardam um template padrão configurado só
103
+ * precisam do "reabra". Exigir o nome obrigaria toda inbox a listar templates antes de poder
104
+ * mandar o primeiro — e a listagem é `listTemplates?`, opcional.
105
+ */
17
106
  sendTemplate(
18
107
  conversationId: string,
19
- data: { templateName: string; languageCode?: string; bodyParams?: string[] },
108
+ data: { templateName?: string; languageCode?: string; bodyParams?: string[] },
20
109
  ): Promise<void>
21
110
  markRead(conversationId: string): Promise<void>
22
111
  getContext(conversationId: string): Promise<Record<string, unknown>>
23
- getDocuments(conversationId: string, params?: { search?: string; page?: number }): Promise<ConversationDocument[]>
24
- getDocumentUrl(uploadId: string): Promise<string>
112
+ getDocuments(
113
+ conversationId: string,
114
+ params?: ListDocumentsParams,
115
+ ): Promise<ConversationDocument[] | ConversationDocumentPage>
116
+ /**
117
+ * `disposition` decide entre abrir no navegador e baixar. É o backend que assina a URL e grava
118
+ * o `Content-Disposition` nela, então a escolha precisa viajar na chamada — depois de assinada
119
+ * não há como o cliente mudá-la. Ausente = o padrão do host.
120
+ */
121
+ getDocumentUrl(uploadId: string, disposition?: 'inline' | 'attachment'): Promise<string>
122
+ /**
123
+ * Baixa vários arquivos num zip único.
124
+ *
125
+ * **Opcional por capacidade:** montar zip exige o host LER os bytes do storage, o que nem toda
126
+ * instalação faz — as que só assinam URL não conseguem. Ausente, o painel esconde a seleção em
127
+ * lote em vez de oferecer um botão que falha.
128
+ */
129
+ downloadDocumentsArchive?(conversationId: string, uploadIds: readonly string[]): Promise<Blob>
130
+ /**
131
+ * Biblioteca de TODAS as conversas, para uma tela de Documentos fora do atendimento.
132
+ *
133
+ * Opcional por capacidade: host que só expõe anexo dentro da conversa não implementa, e o
134
+ * componente de biblioteca simplesmente não é usável — melhor que uma tela que sempre erra.
135
+ */
136
+ getAllDocuments?(params?: ListDocumentsParams): Promise<CompanyDocumentPage>
137
+ /**
138
+ * Remove um arquivo da biblioteca. **Opcional por capacidade:** apagar anexo trocado com o
139
+ * cliente é decisão de retenção do produto — instalação que precisa guardar tudo por obrigação
140
+ * legal não implementa, e a tela simplesmente não desenha a lixeira.
141
+ */
142
+ deleteDocument?(uploadId: string): Promise<void>
143
+ /**
144
+ * Zip de arquivos avulsos da biblioteca, sem conversa de origem única — irmão do
145
+ * `downloadDocumentsArchive`, que é por conversa. Ausente, a seleção em lote não oferece o botão.
146
+ */
147
+ downloadDocumentsArchiveByIds?(uploadIds: readonly string[]): Promise<Blob>
148
+ /**
149
+ * Envia um arquivo avulso direto pra biblioteca, fora do fluxo de uma conversa. **Opcional por
150
+ * capacidade:** cada host tem seu próprio contrato de upload (base64, multipart, presigned URL) —
151
+ * o pacote não escolhe um formato de payload, só entrega o `File` do input e deixa o host montar
152
+ * a chamada do jeito que seu backend espera. Ausente, a tela de biblioteca não desenha o botão de
153
+ * enviar, em vez de oferecer uma ação que sempre falha.
154
+ *
155
+ * `extra` é o mesmo vocabulário livre do produto que já viaja em `renderFilters` — cliente,
156
+ * unidade, campanha — pra associar o arquivo enviado ao contexto que a tela estava filtrando.
157
+ */
158
+ uploadDocument?(file: File, extra?: Readonly<Record<string, string | number>>): Promise<ConversationDocument>
25
159
  getMediaProxyUrl(mediaId: string): Promise<{ mimeType: string; data: string }>
160
+
161
+ /**
162
+ * Operações de atendimento humano. **Opcionais por capacidade, não por descuido:** nem toda
163
+ * inbox tem fila humana — um canal só-bot, ou um chat de site sem operador, não sabe o que é
164
+ * assumir conversa. Quem não implementa não ganha o botão, em vez de ganhar um botão que
165
+ * estoura no clique. Os hooks devolvem `undefined` para a ação ausente, e é isso que a UI
166
+ * consulta para decidir se desenha a afordância.
167
+ */
168
+ takeover?(conversationId: string): Promise<void>
169
+ release?(conversationId: string): Promise<void>
170
+ /** Encerra o atendimento. Despedida, se houver, é decisão do host — o pacote não a inventa. */
171
+ finalize?(conversationId: string): Promise<void>
172
+
173
+ markAllRead?(): Promise<void>
174
+ listTemplates?(): Promise<ConversationTemplate[]>
175
+
176
+ /**
177
+ * Transcrição completa gerada pelo servidor. Existe ao lado de `buildTranscriptText`, que monta
178
+ * a partir das mensagens já em memória: a tela costuma ter só a última página carregada, e
179
+ * exportar dali entregaria um recorte parcial com cara de histórico inteiro. Opcional porque
180
+ * nem todo backend expõe a rota — quem não tem continua usando o builder local.
181
+ */
182
+ exportTranscript?(conversationId: string): Promise<{ transcript: string; filename: string }>
183
+
184
+ /**
185
+ * Transcreve o áudio de uma mensagem e devolve o resultado.
186
+ *
187
+ * **Opcional por capacidade.** Um host em modo automático transcreve na ingestão e não expõe rota
188
+ * nenhuma; um host sem engine configurado não transcreve de jeito algum. Nos dois casos o balão
189
+ * simplesmente não desenha o botão, em vez de oferecer uma ação que estoura no clique.
190
+ *
191
+ * `messageId` e não `conversationId`: transcrição é por áudio, e uma conversa tem vários.
192
+ */
193
+ transcribeAudio?(messageId: string): Promise<MessageTranscription>
26
194
  }
27
195
 
28
196
  /**
@@ -76,6 +244,13 @@ export interface ConversationSummary {
76
244
  waitingHuman: boolean
77
245
  unread: number
78
246
  currentState: string
247
+ /**
248
+ * Atributos que só o produto conhece e desenha (tipo de financiamento, carteira, campanha). É a
249
+ * contraparte de leitura do `filters` de `ListConversationsParams`: o pacote transporta e nunca
250
+ * interpreta. Sem isto, exibir um selo próprio na linha exigiria o host manter uma segunda
251
+ * consulta paralela à mesma listagem — a implementação duplicada que o pacote existe para evitar.
252
+ */
253
+ attributes?: Record<string, string | undefined>
79
254
  }
80
255
 
81
256
  export interface ConversationDocument {
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Guarda a interpolação das mensagens rápidas.
3
+ *
4
+ * O caso que decide o desenho: variável ausente. Deixar `{{nome}}` no texto significa o atendente
5
+ * mandar "Olá {{nome}}!" para o cliente — pior que a saudação sem nome.
6
+ */
7
+
8
+ import { describe, expect, it } from 'bun:test'
9
+
10
+ import { applyQuickReplyVariables, resolveQuickReply } from './MessageComposer'
11
+
12
+ describe('applyQuickReplyVariables', () => {
13
+ it('troca a variável pelo valor', () => {
14
+ expect(applyQuickReplyVariables('Olá {{nome}}!', { nome: 'Marina' })).toBe('Olá Marina!')
15
+ })
16
+
17
+ it('aceita espaço dentro das chaves', () => {
18
+ expect(applyQuickReplyVariables('Olá {{ nome }}!', { nome: 'Rita' })).toBe('Olá Rita!')
19
+ })
20
+
21
+ it('troca todas as ocorrências', () => {
22
+ expect(applyQuickReplyVariables('{{nome}}, confirma? Obrigado, {{nome}}.', { nome: 'Ana' })).toBe(
23
+ 'Ana, confirma? Obrigado, Ana.',
24
+ )
25
+ })
26
+
27
+ // Nunca vaza o literal para o cliente.
28
+ it('apaga a variável que não foi passada', () => {
29
+ expect(applyQuickReplyVariables('Olá {{nome}}!', {})).toBe('Olá !')
30
+ expect(applyQuickReplyVariables('Olá {{nome}}!')).toBe('Olá !')
31
+ })
32
+
33
+ it('não mexe em texto sem variável', () => {
34
+ expect(applyQuickReplyVariables('Bom dia!', { nome: 'X' })).toBe('Bom dia!')
35
+ })
36
+ })
37
+
38
+ describe('resolveQuickReply', () => {
39
+ it('interpola quando o texto é string', () => {
40
+ const resolvido = resolveQuickReply({ key: 'g', label: '👋', text: 'Olá {{nome}}!' }, { nome: 'Rita' })
41
+
42
+ expect(resolvido).toBe('Olá Rita!')
43
+ })
44
+
45
+ // A função existe para o que a string não resolve: escolher copy por produto, pluralizar, formatar.
46
+ it('chama a função com as variáveis', () => {
47
+ const resolvido = resolveQuickReply(
48
+ { key: 's', label: '📋', text: (variables) => `Status de ${variables['produto'] ?? 'seu pedido'}` },
49
+ { produto: 'financiamento' },
50
+ )
51
+
52
+ expect(resolvido).toBe('Status de financiamento')
53
+ })
54
+
55
+ it('função sem variáveis não quebra', () => {
56
+ expect(resolveQuickReply({ key: 'c', label: '📞', text: () => 'Posso ligar?' })).toBe('Posso ligar?')
57
+ })
58
+ })