@adatechnology/conversations-ui 0.1.0-rc.2 → 0.1.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ZDURDZTM.js → chunk-2AYDBWNE.js} +14 -0
- package/dist/chunk-TV4OQRGH.js +2187 -0
- package/dist/flows/index.d.ts +14 -2
- package/dist/flows/index.js +117 -39
- package/dist/index.d.ts +862 -136
- package/dist/index.js +1746 -1124
- package/dist/preview/index.d.ts +470 -0
- package/dist/preview/index.js +1329 -0
- package/dist/styles.css +228 -0
- package/dist/types-C6A_9edv.d.ts +456 -0
- package/package.json +10 -3
- package/src/AudioRecorderButton.test.tsx +30 -0
- package/src/AudioRecorderButton.tsx +248 -0
- package/src/AudioTranscription.test.tsx +115 -0
- package/src/AudioTranscription.tsx +249 -0
- package/src/Avatar.tsx +30 -4
- package/src/ChannelIcon.tsx +87 -0
- package/src/ConversationContextPanel.tsx +280 -0
- package/src/ConversationDocumentsPanel.tsx +425 -0
- package/src/ConversationHeader.tsx +257 -0
- package/src/ConversationListItem.tsx +54 -7
- package/src/ConversationLocalesProvider.tsx +44 -0
- package/src/ConversationRow.tsx +137 -0
- package/src/DateDivider.tsx +16 -3
- package/src/DocumentsLibrary.tsx +322 -0
- package/src/EmojiPicker.tsx +69 -55
- package/src/FileIcon.test.ts +83 -0
- package/src/FileIcon.tsx +88 -11
- package/src/InteractiveMessage.test.tsx +41 -0
- package/src/InteractiveMessage.tsx +143 -0
- package/src/Lightbox.tsx +18 -3
- package/src/MediaRenderer.tsx +96 -22
- package/src/MessageBubble.tsx +77 -5
- package/src/MessageComposer.test.tsx +35 -0
- package/src/MessageComposer.tsx +165 -19
- package/src/RichMessageComposer.test.tsx +83 -0
- package/src/RichMessageComposer.tsx +380 -0
- package/src/Wallpaper.test.tsx +21 -0
- package/src/Wallpaper.tsx +69 -7
- package/src/WhatsAppMessageEditor.tsx +28 -4
- package/src/WindowExpiredNotice.tsx +57 -0
- package/src/audioRecorderFormat.test.ts +67 -0
- package/src/conversationChannel.test.ts +53 -0
- package/src/conversationChannel.ts +146 -0
- package/src/conversationTranscript.test.ts +122 -0
- package/src/conversationTranscript.ts +89 -0
- package/src/conversationWindow.test.ts +90 -0
- package/src/conversationWindow.ts +78 -0
- package/src/documentTypeLabel.test.ts +57 -0
- package/src/emojiCatalog.test.ts +35 -0
- package/src/emojiCatalog.ts +189 -0
- package/src/flows/FlowGroupHeader.tsx +12 -2
- package/src/flows/FlowMapCanvas.tsx +17 -14
- package/src/flows/FlowMapNode.tsx +3 -1
- package/src/flows/FlowNodeCard.tsx +22 -4
- package/src/flows/FlowNodePanel.tsx +132 -35
- package/src/flows/FlowPalette.tsx +6 -2
- package/src/flows/FlowWhatsAppPreview.tsx +14 -3
- package/src/flows/flowGraph.ts +5 -5
- package/src/flows/labels.ts +5 -0
- package/src/hooks/useConversationActions.ts +56 -0
- package/src/hooks/useConversationDocuments.ts +15 -9
- package/src/hooks/useConversationList.ts +15 -9
- package/src/hooks/useConversationMessages.ts +2 -2
- package/src/hooks/useScrollToLatestMessage.ts +127 -0
- package/src/index.ts +140 -16
- package/src/lib/cn.test.ts +29 -0
- package/src/lib/cn.ts +15 -0
- package/src/lib/createMediaUrlResolver.ts +33 -0
- package/src/lib/paginated.test.ts +33 -0
- package/src/lib/paginated.ts +26 -0
- package/src/lib/phone.ts +34 -0
- package/src/preview/ConversationPreview.tsx +334 -0
- package/src/preview/MediaTypesPreview.tsx +87 -0
- package/src/preview/conversationPreviewFailures.test.ts +64 -0
- package/src/preview/createMockConversationsApi.ts +271 -0
- package/src/preview/createMockSSEProvider.ts +40 -0
- package/src/preview/createPreviewBridgeClient.test.ts +92 -0
- package/src/preview/createPreviewBridgeClient.ts +124 -0
- package/src/preview/createPreviewMediaUploader.ts +82 -0
- package/src/preview/createPreviewWebhookClient.test.ts +194 -0
- package/src/preview/createPreviewWebhookClient.ts +222 -0
- package/src/preview/index.ts +67 -0
- package/src/preview/mediaTypeOf.test.ts +15 -0
- package/src/preview/mockDocumentsSearch.test.ts +57 -0
- package/src/preview/mockEventSource.ts +53 -0
- package/src/preview/preview.test.ts +177 -0
- package/src/preview/previewFileSamples.test.ts +151 -0
- package/src/preview/previewFileSamples.ts +74 -0
- package/src/preview/previewFixtures.ts +440 -0
- package/src/preview/previewMediaSource.test.ts +62 -0
- package/src/preview/previewMediaSource.ts +91 -0
- package/src/preview/previewMediaUploader.test.ts +61 -0
- package/src/preview/previewStore.ts +193 -0
- package/src/preview/startPreviewScript.ts +60 -0
- package/src/providers/types.ts +175 -12
- package/src/quickReply.test.ts +58 -0
- package/src/settings/TranscriptionSettingsForm.test.tsx +81 -0
- package/src/settings/TranscriptionSettingsForm.tsx +189 -0
- package/src/settings/WhatsAppCreateTemplateForm.tsx +3 -1
- package/src/settings/WhatsAppTemplatesSettings.tsx +106 -0
- package/src/styles.css +173 -0
- package/src/types.ts +72 -1
- package/src/useDarkMode.ts +26 -0
- package/src/useIsNarrow.ts +29 -0
- package/src/useWaitingNotifications.ts +74 -29
package/dist/index.d.ts
CHANGED
|
@@ -1,55 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import react__default, { ReactNode, FormEvent } from 'react';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
apiBaseUrl: string;
|
|
6
|
-
theme?: ConversationsTheme;
|
|
7
|
-
features?: ConversationsFeatures;
|
|
8
|
-
}
|
|
9
|
-
interface ConversationsTheme {
|
|
10
|
-
primaryColor?: string;
|
|
11
|
-
backgroundColor?: string;
|
|
12
|
-
bubbleSent?: string;
|
|
13
|
-
bubbleReceived?: string;
|
|
14
|
-
textPrimary?: string;
|
|
15
|
-
textSecondary?: string;
|
|
16
|
-
}
|
|
17
|
-
interface ConversationsFeatures {
|
|
18
|
-
audio?: boolean;
|
|
19
|
-
documents?: boolean;
|
|
20
|
-
emoji?: boolean;
|
|
21
|
-
darkMode?: boolean;
|
|
22
|
-
}
|
|
23
|
-
interface MessagePayload {
|
|
24
|
-
id: string;
|
|
25
|
-
type: 'text' | 'image' | 'video' | 'audio' | 'document' | 'sticker' | 'template';
|
|
26
|
-
content?: string;
|
|
27
|
-
caption?: string;
|
|
28
|
-
mediaUrl?: string;
|
|
29
|
-
base64?: string;
|
|
30
|
-
uploadId?: string;
|
|
31
|
-
mediaId?: string;
|
|
32
|
-
mimeType?: string;
|
|
33
|
-
filename?: string;
|
|
34
|
-
sizeBytes?: number;
|
|
35
|
-
direction: 'inbound' | 'outbound';
|
|
36
|
-
sender: 'bot' | 'customer' | 'agent';
|
|
37
|
-
timestamp: string;
|
|
38
|
-
status?: 'sent' | 'delivered' | 'read' | 'failed';
|
|
39
|
-
readAt?: string;
|
|
40
|
-
agentName?: string | null;
|
|
41
|
-
templateName?: string;
|
|
42
|
-
isFirstInGroup?: boolean;
|
|
43
|
-
isLastInGroup?: boolean;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
type ResolveMediaUrl = (message: MessagePayload) => Promise<string | null>;
|
|
47
|
-
interface MediaRendererProps {
|
|
48
|
-
message: MessagePayload;
|
|
49
|
-
onLightbox: (src: string) => void;
|
|
50
|
-
onResolveUrl?: ResolveMediaUrl;
|
|
51
|
-
}
|
|
52
|
-
declare function MediaRenderer({ message, onLightbox, onResolveUrl }: MediaRendererProps): react.JSX.Element | null;
|
|
2
|
+
import react__default, { ReactNode, CSSProperties, UIEvent, FormEvent, RefObject } from 'react';
|
|
3
|
+
import { G as MessagePayload, N as ResolveMediaUrl, z as InteractiveSelection, J as MessageTranscription, x as InteractivePayload, r as ConversationsFeatures, o as ConversationSummary, j as ConversationChannel, L as ListConversationsParams, q as ConversationsApi, S as SSEProvider, T as TranscriptionMode, B as ListDocumentsParams, k as ConversationDocument, p as ConversationTemplate } from './types-C6A_9edv.js';
|
|
4
|
+
export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, C as CHANNEL_CAPABILITIES, c as CHANNEL_FILTER_ALL, d as CONVERSATION_CHANNEL, e as ChannelCapabilities, f as ChannelFilter, g as ChannelFilterOption, h as CompanyDocument, i as CompanyDocumentPage, l as ConversationDocumentPage, m as ConversationEventSource, n as ConversationPage, s as ConversationsTheme, t as ConversationsUIConfig, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, u as DEFAULT_CONVERSATION_CHANNEL, v as DEFAULT_MAX_RECORDING_MILLISECONDS, F as FormatContactHandleParams, H as HANDLE_KIND, w as HandleKind, I as InteractiveOption, y as InteractiveSection, M as MediaRenderer, E as MediaRendererProps, R as REOPEN_MECHANISM, K as ReopenMechanism, O as TranscriptionStatus, P as capabilitiesOf, Q as channelFiltersFor, U as contactFlag, V as formatContactHandle } from './types-C6A_9edv.js';
|
|
53
5
|
|
|
54
6
|
interface MessageBubbleProps {
|
|
55
7
|
message: MessagePayload;
|
|
@@ -59,15 +11,56 @@ interface MessageBubbleProps {
|
|
|
59
11
|
isSelecting?: boolean;
|
|
60
12
|
isSelected?: boolean;
|
|
61
13
|
onToggleSelect?: () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Como buscar a mídia da mensagem. Ausente, o balão usa o `ConversationsApi` do
|
|
16
|
+
* `ConversationsProvider` — passe apenas para sobrescrever (cache próprio, CDN, proxy do host).
|
|
17
|
+
*/
|
|
62
18
|
onResolveMediaUrl?: ResolveMediaUrl;
|
|
19
|
+
/**
|
|
20
|
+
* Toque numa opção do menu interativo. Ausente, as opções aparecem desabilitadas — que é o certo
|
|
21
|
+
* no histórico da inbox: o operador vê o que foi oferecido, sem responder no lugar do cliente.
|
|
22
|
+
*/
|
|
23
|
+
onInteractiveSelect?: (selection: InteractiveSelection) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Pede a transcrição do áudio. Ausente, o balão usa `transcribeAudio` do `ConversationsApi` do
|
|
26
|
+
* contexto — passe apenas para sobrescrever. Sem nenhum dos dois, o bloco de transcrição só exibe
|
|
27
|
+
* o que já veio pronto do backend.
|
|
28
|
+
*/
|
|
29
|
+
onTranscribeAudio?: (messageId: string) => Promise<MessageTranscription | void>;
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
declare function MessageBubble({ message, isMine, senderName, isFirstInGroup, isSelecting, isSelected, onToggleSelect, onResolveMediaUrl, onInteractiveSelect, onTranscribeAudio, className, }: MessageBubbleProps): react.JSX.Element;
|
|
33
|
+
|
|
34
|
+
interface InteractiveMessageLabels {
|
|
35
|
+
/** Fallback do rótulo do botão que abre a lista, quando o payload não traz um. */
|
|
36
|
+
openList: string;
|
|
63
37
|
}
|
|
64
|
-
declare
|
|
38
|
+
declare const DEFAULT_INTERACTIVE_MESSAGE_LABELS: InteractiveMessageLabels;
|
|
39
|
+
interface InteractiveMessageProps {
|
|
40
|
+
payload: InteractivePayload;
|
|
41
|
+
onSelect?: (selection: InteractiveSelection) => void;
|
|
42
|
+
labels?: Partial<InteractiveMessageLabels>;
|
|
43
|
+
className?: string;
|
|
44
|
+
}
|
|
45
|
+
declare function InteractiveMessage({ payload, onSelect, labels, className }: InteractiveMessageProps): react.JSX.Element;
|
|
65
46
|
|
|
66
47
|
interface ConversationWallpaperProps {
|
|
67
48
|
children?: ReactNode;
|
|
68
49
|
className?: string;
|
|
50
|
+
/** Ajusta ou substitui o fundo padrão — para produto com identidade visual própria. */
|
|
51
|
+
style?: CSSProperties;
|
|
52
|
+
/**
|
|
53
|
+
* Rolagem da área de mensagens. Par do `ref`: é este elemento que rola, então é aqui que
|
|
54
|
+
* `useScrollToLatestMessage` observa a posição para saber se o operador está acompanhando o fim.
|
|
55
|
+
*/
|
|
56
|
+
onScroll?: (event: UIEvent<HTMLDivElement>) => void;
|
|
69
57
|
}
|
|
70
|
-
|
|
58
|
+
/**
|
|
59
|
+
* `forwardRef` porque quem controla a rolagem é de fora — o hook precisa do elemento para saltar
|
|
60
|
+
* até a última mensagem. `forwardRef` e não `ref` como prop: o pacote suporta React 18, onde
|
|
61
|
+
* ref-como-prop ainda não existe.
|
|
62
|
+
*/
|
|
63
|
+
declare const ConversationWallpaper: react.ForwardRefExoticComponent<ConversationWallpaperProps & react.RefAttributes<HTMLDivElement>>;
|
|
71
64
|
|
|
72
65
|
interface ConversationLocales {
|
|
73
66
|
bubble: {
|
|
@@ -82,6 +75,28 @@ interface ConversationLocales {
|
|
|
82
75
|
viewImage: string;
|
|
83
76
|
listenAudio: string;
|
|
84
77
|
viewVideo: string;
|
|
78
|
+
moderationFlagged: string;
|
|
79
|
+
mediaLoading: string;
|
|
80
|
+
mediaRetry: string;
|
|
81
|
+
mediaError: string;
|
|
82
|
+
mediaUnavailable: string;
|
|
83
|
+
imageAlt: string;
|
|
84
|
+
untitledDocument: string;
|
|
85
|
+
downloadFile: string;
|
|
86
|
+
};
|
|
87
|
+
transcription: {
|
|
88
|
+
label: string;
|
|
89
|
+
copy: string;
|
|
90
|
+
copied: string;
|
|
91
|
+
transcribe: string;
|
|
92
|
+
transcribing: string;
|
|
93
|
+
retry: string;
|
|
94
|
+
failed: string;
|
|
95
|
+
/** Áudio processado sem fala detectada — distinto de "não transcrito". */
|
|
96
|
+
empty: string;
|
|
97
|
+
unsupported: string;
|
|
98
|
+
showMore: string;
|
|
99
|
+
showLess: string;
|
|
85
100
|
};
|
|
86
101
|
selection: {
|
|
87
102
|
select: string;
|
|
@@ -107,13 +122,96 @@ interface AudioPlayerProps {
|
|
|
107
122
|
}
|
|
108
123
|
declare function AudioPlayer({ src, isMine }: AudioPlayerProps): react.JSX.Element;
|
|
109
124
|
|
|
125
|
+
interface AudioTranscriptionProps {
|
|
126
|
+
transcription?: MessageTranscription | null;
|
|
127
|
+
/**
|
|
128
|
+
* Pede a transcrição ao backend. Ausente, o bloco nunca oferece o botão — um host em modo
|
|
129
|
+
* automático não tem rota para isso, e desenhar um botão que estoura no clique é pior do que não
|
|
130
|
+
* desenhar nada.
|
|
131
|
+
*
|
|
132
|
+
* O que for devolvido é exibido na hora. Sem isso o texto só apareceria no próximo refetch da
|
|
133
|
+
* lista, e transcrição não emite evento de tempo real: o operador clicaria, veria o spinner
|
|
134
|
+
* terminar e continuaria sem o texto na tela.
|
|
135
|
+
*/
|
|
136
|
+
onTranscribe?: () => Promise<MessageTranscription | void>;
|
|
137
|
+
isMine?: boolean;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Transcrição sob a nota de voz, com botão de copiar.
|
|
141
|
+
*
|
|
142
|
+
* Copiar é o motivo de o bloco existir: o operador cola o pedido do cliente no sistema interno, no
|
|
143
|
+
* orçamento, na busca. Seleção manual de texto dentro de um balão de chat é exatamente onde o
|
|
144
|
+
* arrasto do mouse falha — pega o balão vizinho, o horário, o nome do remetente.
|
|
145
|
+
*
|
|
146
|
+
* `navigator.clipboard.writeText` com try/catch silencioso, mesmo padrão de `MessageText.tsx`: a API
|
|
147
|
+
* exige contexto seguro e permissão, e um host servindo em HTTP simples não a tem. Falhar sem alarme
|
|
148
|
+
* é o certo — o texto continua na tela para seleção manual.
|
|
149
|
+
*/
|
|
150
|
+
declare function AudioTranscription({ transcription, onTranscribe, isMine }: AudioTranscriptionProps): react.JSX.Element | null;
|
|
151
|
+
|
|
152
|
+
interface EmojiPickerLabels {
|
|
153
|
+
search: string;
|
|
154
|
+
noResults: string;
|
|
155
|
+
}
|
|
156
|
+
declare const DEFAULT_EMOJI_PICKER_LABELS: EmojiPickerLabels;
|
|
110
157
|
interface EmojiPickerProps {
|
|
111
158
|
onSelect: (emoji: string) => void;
|
|
159
|
+
labels?: Partial<EmojiPickerLabels>;
|
|
112
160
|
className?: string;
|
|
113
161
|
}
|
|
114
|
-
declare const EmojiPicker: ({ onSelect, className }: EmojiPickerProps) => react.JSX.Element;
|
|
162
|
+
declare const EmojiPicker: ({ onSelect, labels, className }: EmojiPickerProps) => react.JSX.Element;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Catálogo de emojis com palavras-chave em português, usado pela busca do seletor.
|
|
166
|
+
*
|
|
167
|
+
* Fica em módulo próprio (e não dentro do `EmojiPicker`) porque a busca precisa varrer TODAS as
|
|
168
|
+
* categorias, não só a aberta — o índice é montado uma vez, no carregamento, e não a cada tecla.
|
|
169
|
+
*
|
|
170
|
+
* As palavras-chave são de busca, não legendas: incluem sinônimos e formas sem acento, porque quem
|
|
171
|
+
* digita rápido escreve "coracao" e "polegar" com a mesma frequência que a forma correta.
|
|
172
|
+
*/
|
|
173
|
+
type EmojiEntry = {
|
|
174
|
+
readonly emoji: string;
|
|
175
|
+
readonly keywords: readonly string[];
|
|
176
|
+
};
|
|
177
|
+
type EmojiCategory = {
|
|
178
|
+
readonly name: string;
|
|
179
|
+
readonly entries: readonly EmojiEntry[];
|
|
180
|
+
};
|
|
181
|
+
declare const EMOJI_CATEGORIES: readonly EmojiCategory[];
|
|
182
|
+
/**
|
|
183
|
+
* Busca por prefixo de palavra-chave, não por substring: "ca" traz "casa" e "cartão", mas "sa" não
|
|
184
|
+
* traz "casa" — casar no meio da palavra devolvia resultado que ninguém consegue explicar.
|
|
185
|
+
*/
|
|
186
|
+
declare function searchEmojis(query: string): readonly EmojiEntry[];
|
|
115
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Mensagem pronta que o atendente cola no campo com um clique.
|
|
190
|
+
*
|
|
191
|
+
* `text` aceita string com `{{variavel}}` ou função: a string cobre o caso comum (copy fixa com o
|
|
192
|
+
* nome do cliente no meio) sem o host escrever código, e a função cobre o que precisa de lógica —
|
|
193
|
+
* escolher texto por produto, pluralizar, formatar moeda.
|
|
194
|
+
*/
|
|
195
|
+
interface QuickReply {
|
|
196
|
+
key: string;
|
|
197
|
+
/** O que aparece no chip, emoji incluso: `👋 Saudação`. */
|
|
198
|
+
label: string;
|
|
199
|
+
text: string | ((variables: Readonly<Record<string, string>>) => string);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Troca `{{nome}}` pelos valores passados. Variável ausente vira string vazia, e não o literal
|
|
203
|
+
* `{{nome}}`: mandar "Olá {{nome}}!" para o cliente é pior que mandar "Olá !".
|
|
204
|
+
*/
|
|
205
|
+
declare function applyQuickReplyVariables(template: string, variables?: Readonly<Record<string, string>>): string;
|
|
206
|
+
declare function resolveQuickReply(quickReply: QuickReply, variables?: Readonly<Record<string, string>>): string;
|
|
207
|
+
interface MessageComposerLabels {
|
|
208
|
+
emoji: string;
|
|
209
|
+
attach: string;
|
|
210
|
+
send: string;
|
|
211
|
+
}
|
|
212
|
+
declare const DEFAULT_MESSAGE_COMPOSER_LABELS: MessageComposerLabels;
|
|
116
213
|
interface MessageComposerProps {
|
|
214
|
+
labels?: Partial<MessageComposerLabels>;
|
|
117
215
|
onSend: (text: string) => void;
|
|
118
216
|
onAttach?: (file: File) => void;
|
|
119
217
|
value?: string;
|
|
@@ -123,10 +221,122 @@ interface MessageComposerProps {
|
|
|
123
221
|
maxLength?: number;
|
|
124
222
|
disabled?: boolean;
|
|
125
223
|
acceptedFileTypes?: string;
|
|
224
|
+
/**
|
|
225
|
+
* Ocupa o lugar do botão de enviar enquanto não há nada para enviar — é onde o WhatsApp põe o
|
|
226
|
+
* microfone. Fora do campo, o botão vira um bloco solto ao lado do pill e quebra a barra.
|
|
227
|
+
*/
|
|
228
|
+
idleAction?: ReactNode;
|
|
229
|
+
/** Mensagens prontas exibidas acima do campo. Vazio ou ausente, a faixa não é renderizada. */
|
|
230
|
+
quickReplies?: readonly QuickReply[];
|
|
231
|
+
/** Valores para `{{variavel}}` — tipicamente nome do cliente, produto, protocolo. */
|
|
232
|
+
quickReplyVariables?: Readonly<Record<string, string>>;
|
|
233
|
+
className?: string;
|
|
234
|
+
classNames?: Partial<MessageComposerClassNames>;
|
|
235
|
+
}
|
|
236
|
+
interface MessageComposerClassNames {
|
|
237
|
+
root: string;
|
|
238
|
+
quickReplies: string;
|
|
239
|
+
quickReply: string;
|
|
240
|
+
field: string;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Exatamente o que a Meta aceita em mensagem de mídia — imagem, sticker, áudio, vídeo e a lista
|
|
244
|
+
* fechada de documentos. Oferecer no seletor um formato que o WhatsApp recusa (`.zip`, `.rtf`)
|
|
245
|
+
* empurra a falha para depois do envio, quando já não dá para explicar ao operador o que houve.
|
|
246
|
+
* Produto com regra própria passa `acceptedFileTypes`.
|
|
247
|
+
*/
|
|
248
|
+
declare const DEFAULT_ACCEPTED_FILE_TYPES: string;
|
|
249
|
+
declare const MessageComposer: ({ onSend, onAttach, value: externalValue, onChange: externalOnChange, quickReplies, quickReplyVariables, features, placeholder, maxLength, disabled, acceptedFileTypes, idleAction, className, classNames, labels, }: MessageComposerProps) => react.JSX.Element;
|
|
250
|
+
|
|
251
|
+
/** Cada ação que a barra sabe oferecer. `toolbar` decide quais delas aparecem. */
|
|
252
|
+
declare const RICH_COMPOSER_ACTION: {
|
|
253
|
+
readonly BOLD: "bold";
|
|
254
|
+
readonly ITALIC: "italic";
|
|
255
|
+
readonly STRIKETHROUGH: "strikethrough";
|
|
256
|
+
readonly MONOSPACE: "monospace";
|
|
257
|
+
readonly EMOJI: "emoji";
|
|
258
|
+
readonly ATTACH: "attach";
|
|
259
|
+
readonly VARIABLES: "variables";
|
|
260
|
+
};
|
|
261
|
+
type RichComposerAction = (typeof RICH_COMPOSER_ACTION)[keyof typeof RICH_COMPOSER_ACTION];
|
|
262
|
+
/** Texto das dicas. O host troca só o que quiser; o resto fica no padrão. */
|
|
263
|
+
interface RichComposerTooltips {
|
|
264
|
+
bold: string;
|
|
265
|
+
italic: string;
|
|
266
|
+
strikethrough: string;
|
|
267
|
+
monospace: string;
|
|
268
|
+
emoji: string;
|
|
269
|
+
attach: string;
|
|
270
|
+
send: string;
|
|
271
|
+
variables: string;
|
|
272
|
+
}
|
|
273
|
+
declare const DEFAULT_RICH_COMPOSER_TOOLTIPS: RichComposerTooltips;
|
|
274
|
+
/**
|
|
275
|
+
* Um valor que o operador pode inserir no texto sem digitar. `value` é o que entra no campo — pode
|
|
276
|
+
* ser o dado já resolvido ("Anderson") ou um marcador a resolver depois ("{{nome}}"): quem decide é
|
|
277
|
+
* o host, porque só ele sabe se a substituição acontece aqui ou no envio.
|
|
278
|
+
*/
|
|
279
|
+
interface RichComposerVariable {
|
|
280
|
+
id: string;
|
|
281
|
+
label: string;
|
|
282
|
+
value: string;
|
|
283
|
+
/** Prévia do que será inserido, para o operador conferir antes de tocar. */
|
|
284
|
+
tooltip?: string;
|
|
285
|
+
}
|
|
286
|
+
/** Uma resposta rápida: o que o operador lê no chip e o que cai no campo ao tocar nele. */
|
|
287
|
+
interface RichComposerQuickReply {
|
|
288
|
+
id: string;
|
|
289
|
+
label: string;
|
|
290
|
+
text: string;
|
|
291
|
+
/** Dica ao passar o mouse. Sem isto, o chip não tem `title` — rótulo curto já se explica. */
|
|
292
|
+
tooltip?: string;
|
|
293
|
+
}
|
|
294
|
+
/** Handle imperativo: `contentEditable` controlado pelo React perde o cursor a cada tecla. */
|
|
295
|
+
interface RichMessageComposerHandle {
|
|
296
|
+
setContent: (text: string) => void;
|
|
297
|
+
clear: () => void;
|
|
298
|
+
focus: () => void;
|
|
299
|
+
}
|
|
300
|
+
interface RichMessageComposerProps {
|
|
301
|
+
/** Texto na notação do WhatsApp (`*negrito*`), não HTML. */
|
|
302
|
+
value: string;
|
|
303
|
+
onChange: (value: string) => void;
|
|
304
|
+
onSend: () => void;
|
|
305
|
+
onAttachFiles?: (files: FileList) => void;
|
|
306
|
+
quickReplies?: RichComposerQuickReply[];
|
|
307
|
+
/** Variáveis que o operador pode inserir no texto. Vazio ou ausente, o botão não aparece. */
|
|
308
|
+
variables?: RichComposerVariable[];
|
|
309
|
+
/**
|
|
310
|
+
* Quais ações aparecem. Ausente, todas aparecem — menos as que não têm como funcionar (anexo sem
|
|
311
|
+
* `onAttachFiles` some sozinho, porque botão que não faz nada é pior que botão nenhum).
|
|
312
|
+
*/
|
|
313
|
+
toolbar?: Partial<Record<RichComposerAction, boolean>>;
|
|
314
|
+
tooltips?: Partial<RichComposerTooltips>;
|
|
315
|
+
placeholder?: string;
|
|
316
|
+
disabled?: boolean;
|
|
317
|
+
isSending?: boolean;
|
|
318
|
+
/** Ocupa o lugar do enviar enquanto não há nada para enviar — onde o WhatsApp põe o microfone. */
|
|
319
|
+
idleAction?: ReactNode;
|
|
320
|
+
/** Prévia dos anexos já escolhidos, desenhada pelo host acima da barra. */
|
|
321
|
+
attachmentsPreview?: ReactNode;
|
|
322
|
+
acceptedFileTypes?: string;
|
|
323
|
+
className?: string;
|
|
126
324
|
}
|
|
127
|
-
declare const
|
|
325
|
+
declare const RichMessageComposer: react.ForwardRefExoticComponent<RichMessageComposerProps & react.RefAttributes<RichMessageComposerHandle>>;
|
|
128
326
|
|
|
327
|
+
interface WhatsAppMessageEditorLabels {
|
|
328
|
+
bold: string;
|
|
329
|
+
/** Tooltip da negrito — traz a sintaxe do WhatsApp junto, por isso é separado do `aria-label`. */
|
|
330
|
+
boldHint: string;
|
|
331
|
+
italic: string;
|
|
332
|
+
italicHint: string;
|
|
333
|
+
strikethrough: string;
|
|
334
|
+
strikethroughHint: string;
|
|
335
|
+
insertPlaceholder: (token: string) => string;
|
|
336
|
+
}
|
|
337
|
+
declare const DEFAULT_WHATSAPP_MESSAGE_EDITOR_LABELS: WhatsAppMessageEditorLabels;
|
|
129
338
|
interface WhatsAppMessageEditorProps {
|
|
339
|
+
labels?: Partial<WhatsAppMessageEditorLabels>;
|
|
130
340
|
value: string;
|
|
131
341
|
onChange: (value: string) => void;
|
|
132
342
|
placeholder?: string;
|
|
@@ -135,7 +345,7 @@ interface WhatsAppMessageEditorProps {
|
|
|
135
345
|
previewLabel?: string;
|
|
136
346
|
emptyPreviewText?: string;
|
|
137
347
|
}
|
|
138
|
-
declare function WhatsAppMessageEditor({ value, onChange, placeholder, placeholders, rows, previewLabel, emptyPreviewText, }: WhatsAppMessageEditorProps): react.JSX.Element;
|
|
348
|
+
declare function WhatsAppMessageEditor({ value, onChange, placeholder, placeholders, rows, previewLabel, emptyPreviewText, labels, }: WhatsAppMessageEditorProps): react.JSX.Element;
|
|
139
349
|
|
|
140
350
|
interface SimpleEmojiPickerProps {
|
|
141
351
|
onSelect: (emoji: string) => void;
|
|
@@ -145,89 +355,58 @@ interface SimpleEmojiPickerProps {
|
|
|
145
355
|
}
|
|
146
356
|
declare function SimpleEmojiPicker({ onSelect, label, pickerWidth, pickerMaxHeight }: SimpleEmojiPickerProps): react.JSX.Element;
|
|
147
357
|
|
|
358
|
+
interface DateDividerClassNames {
|
|
359
|
+
root: string;
|
|
360
|
+
label: string;
|
|
361
|
+
}
|
|
148
362
|
interface DateDividerProps {
|
|
149
363
|
iso: string;
|
|
364
|
+
className?: string;
|
|
365
|
+
classNames?: Partial<DateDividerClassNames>;
|
|
150
366
|
}
|
|
151
|
-
declare function DateDivider({ iso }: DateDividerProps): react.JSX.Element;
|
|
367
|
+
declare function DateDivider({ iso, className, classNames }: DateDividerProps): react.JSX.Element;
|
|
152
368
|
|
|
369
|
+
interface AvatarLabels {
|
|
370
|
+
/** Lido por leitor de tela quando não há nome nem imagem — a silhueta genérica. */
|
|
371
|
+
unnamedContact: string;
|
|
372
|
+
}
|
|
153
373
|
interface AvatarProps {
|
|
154
374
|
name?: string | null;
|
|
155
375
|
avatarUrl?: string;
|
|
156
376
|
size?: 'sm' | 'md' | 'lg';
|
|
157
377
|
className?: string;
|
|
378
|
+
labels?: Partial<AvatarLabels>;
|
|
158
379
|
}
|
|
159
|
-
declare
|
|
160
|
-
|
|
161
|
-
interface ConversationsApi {
|
|
162
|
-
fetchMessages(conversationId: string, params?: {
|
|
163
|
-
limit?: number;
|
|
164
|
-
before?: string;
|
|
165
|
-
}): Promise<MessagePayload[]>;
|
|
166
|
-
fetchConversations(params?: {
|
|
167
|
-
page?: number;
|
|
168
|
-
limit?: number;
|
|
169
|
-
waitingHuman?: boolean;
|
|
170
|
-
search?: string;
|
|
171
|
-
}): Promise<ConversationSummary[]>;
|
|
172
|
-
sendMessage(conversationId: string, text: string): Promise<MessagePayload>;
|
|
173
|
-
sendMedia(conversationId: string, data: {
|
|
174
|
-
base64: string;
|
|
175
|
-
mimeType: string;
|
|
176
|
-
filename: string;
|
|
177
|
-
caption?: string;
|
|
178
|
-
}): Promise<MessagePayload>;
|
|
179
|
-
sendTemplate(conversationId: string, data: {
|
|
180
|
-
templateName: string;
|
|
181
|
-
languageCode?: string;
|
|
182
|
-
bodyParams?: string[];
|
|
183
|
-
}): Promise<void>;
|
|
184
|
-
markRead(conversationId: string): Promise<void>;
|
|
185
|
-
getContext(conversationId: string): Promise<Record<string, unknown>>;
|
|
186
|
-
getDocuments(conversationId: string, params?: {
|
|
187
|
-
search?: string;
|
|
188
|
-
page?: number;
|
|
189
|
-
}): Promise<ConversationDocument[]>;
|
|
190
|
-
getDocumentUrl(uploadId: string): Promise<string>;
|
|
191
|
-
getMediaProxyUrl(mediaId: string): Promise<{
|
|
192
|
-
mimeType: string;
|
|
193
|
-
data: string;
|
|
194
|
-
}>;
|
|
195
|
-
}
|
|
196
|
-
interface SSEProvider {
|
|
197
|
-
connectConversationStream(conversationId: string): EventSource;
|
|
198
|
-
connectGlobalStream(): EventSource;
|
|
199
|
-
}
|
|
200
|
-
interface ConversationSummary {
|
|
201
|
-
id: string;
|
|
202
|
-
whatsappNumber: string;
|
|
203
|
-
clientName?: string;
|
|
204
|
-
lastContent?: string;
|
|
205
|
-
lastDirection?: 'inbound' | 'outbound';
|
|
206
|
-
lastAt: string;
|
|
207
|
-
lastInboundAt: string | null;
|
|
208
|
-
mode: 'bot' | 'human';
|
|
209
|
-
assignedUserId: string | null;
|
|
210
|
-
waitingHuman: boolean;
|
|
211
|
-
unread: number;
|
|
212
|
-
currentState: string;
|
|
213
|
-
}
|
|
214
|
-
interface ConversationDocument {
|
|
215
|
-
id: string;
|
|
216
|
-
filename: string;
|
|
217
|
-
mimeType: string;
|
|
218
|
-
sizeBytes: number;
|
|
219
|
-
source: string;
|
|
220
|
-
linkedAt: string;
|
|
221
|
-
}
|
|
380
|
+
declare const DEFAULT_AVATAR_LABELS: AvatarLabels;
|
|
381
|
+
declare function Avatar({ name, avatarUrl, size, className, labels }: AvatarProps): react.JSX.Element;
|
|
222
382
|
|
|
383
|
+
interface ConversationListItemLabels {
|
|
384
|
+
/** Tooltip do ponto vermelho: a janela de atendimento de 24h já fechou. */
|
|
385
|
+
expiredWindow: string;
|
|
386
|
+
/** Tooltip do ponto laranja: a janela de atendimento está perto de fechar. */
|
|
387
|
+
warningWindow: string;
|
|
388
|
+
}
|
|
389
|
+
declare const DEFAULT_CONVERSATION_LIST_ITEM_LABELS: ConversationListItemLabels;
|
|
223
390
|
interface ConversationListItemProps {
|
|
224
391
|
conversation: ConversationSummary;
|
|
392
|
+
labels?: Partial<ConversationListItemLabels>;
|
|
225
393
|
active?: boolean;
|
|
226
394
|
selected?: boolean;
|
|
227
395
|
onClick?: () => void;
|
|
228
396
|
onSelect?: (id: string) => void;
|
|
397
|
+
/**
|
|
398
|
+
* Desliga a borda inferior quando o item é composto dentro de outra linha (ver `ConversationRow`):
|
|
399
|
+
* com ela ligada, a borda corta a própria linha ao meio, separando o item do rodapé de status.
|
|
400
|
+
*/
|
|
401
|
+
showDivider?: boolean;
|
|
402
|
+
/**
|
|
403
|
+
* Desliga o fundo de selecionado. Par do `showDivider`: quando o item é composto dentro de uma
|
|
404
|
+
* linha maior, quem pinta o realce é a linha — senão só o bloco do item fica cinza e o resto
|
|
405
|
+
* (checkbox, pills, barra lateral) continua branco, como se metade da linha estivesse selecionada.
|
|
406
|
+
*/
|
|
407
|
+
highlightActive?: boolean;
|
|
229
408
|
}
|
|
230
|
-
declare const ConversationListItem: ({ conversation, active, selected, onClick, onSelect, }: ConversationListItemProps) => react.JSX.Element;
|
|
409
|
+
declare const ConversationListItem: ({ conversation, active, selected, onClick, onSelect, showDivider, highlightActive, labels, }: ConversationListItemProps) => react.JSX.Element;
|
|
231
410
|
|
|
232
411
|
type ToastType = 'success' | 'error' | 'info';
|
|
233
412
|
interface ToastContextValue {
|
|
@@ -247,12 +426,19 @@ interface StatusTicksProps {
|
|
|
247
426
|
}
|
|
248
427
|
declare function StatusTicks({ status, title }: StatusTicksProps): react.JSX.Element;
|
|
249
428
|
|
|
429
|
+
interface LightboxLabels {
|
|
430
|
+
/** Texto alternativo quando a imagem não tem legenda — sem ele o leitor de tela anuncia a URL. */
|
|
431
|
+
imageAlt: string;
|
|
432
|
+
close: string;
|
|
433
|
+
}
|
|
250
434
|
interface LightboxProps {
|
|
251
435
|
imageUrl: string;
|
|
252
436
|
caption?: string;
|
|
253
437
|
onClose: () => void;
|
|
438
|
+
labels?: Partial<LightboxLabels>;
|
|
254
439
|
}
|
|
255
|
-
declare
|
|
440
|
+
declare const DEFAULT_LIGHTBOX_LABELS: LightboxLabels;
|
|
441
|
+
declare function Lightbox({ imageUrl, caption, onClose, labels }: LightboxProps): react.JSX.Element;
|
|
256
442
|
|
|
257
443
|
interface FileIconProps {
|
|
258
444
|
filename?: string;
|
|
@@ -279,16 +465,431 @@ interface MessageTailProps {
|
|
|
279
465
|
}
|
|
280
466
|
declare function MessageTail({ isOutbound }: MessageTailProps): react.JSX.Element;
|
|
281
467
|
|
|
468
|
+
/**
|
|
469
|
+
* Janela de sessão: o intervalo em que o canal aceita mensagem livre do atendente. No WhatsApp são
|
|
470
|
+
* 24h desde o último contato do cliente; fora dela só template. Cada canal tem a sua regra — e há
|
|
471
|
+
* canal sem janela nenhuma — então a política vem de `capabilitiesOf`, não de constante fixa.
|
|
472
|
+
*
|
|
473
|
+
* Mora no SDK porque é regra de plataforma, não de produto: todo projeto que usa este pacote
|
|
474
|
+
* precisa dela para saber o que o atendente ainda consegue fazer.
|
|
475
|
+
*/
|
|
476
|
+
|
|
477
|
+
declare const CONVERSATION_WINDOW: {
|
|
478
|
+
readonly ALL: "all";
|
|
479
|
+
readonly FRESH: "fresh";
|
|
480
|
+
readonly WARNING: "warning";
|
|
481
|
+
readonly CRITICAL: "critical";
|
|
482
|
+
readonly EXPIRED: "expired";
|
|
483
|
+
};
|
|
484
|
+
type ConversationWindow = (typeof CONVERSATION_WINDOW)[keyof typeof CONVERSATION_WINDOW];
|
|
485
|
+
declare const WINDOW_FILTERS: readonly [{
|
|
486
|
+
readonly value: "all";
|
|
487
|
+
readonly label: "Todas";
|
|
488
|
+
readonly dotClass: "";
|
|
489
|
+
}, {
|
|
490
|
+
readonly value: "fresh";
|
|
491
|
+
readonly label: "<12h";
|
|
492
|
+
readonly dotClass: "bg-green-500";
|
|
493
|
+
}, {
|
|
494
|
+
readonly value: "warning";
|
|
495
|
+
readonly label: "12-21h";
|
|
496
|
+
readonly dotClass: "bg-yellow-500";
|
|
497
|
+
}, {
|
|
498
|
+
readonly value: "critical";
|
|
499
|
+
readonly label: "21-24h";
|
|
500
|
+
readonly dotClass: "bg-red-500";
|
|
501
|
+
}, {
|
|
502
|
+
readonly value: "expired";
|
|
503
|
+
readonly label: ">24h";
|
|
504
|
+
readonly dotClass: "bg-gray-400";
|
|
505
|
+
}];
|
|
506
|
+
type WindowOfParams = {
|
|
507
|
+
readonly lastInboundAt: string | null;
|
|
508
|
+
readonly now: number;
|
|
509
|
+
/** Ausente = `whatsapp`. */
|
|
510
|
+
readonly channel?: ConversationChannel | undefined;
|
|
511
|
+
};
|
|
512
|
+
/**
|
|
513
|
+
* Sem `lastInboundAt` o cliente nunca escreveu, então não há janela aberta — classificar como
|
|
514
|
+
* expirada é o comportamento seguro: evita o atendente tentar texto livre e receber recusa.
|
|
515
|
+
*
|
|
516
|
+
* Canal sem janela de sessão (chat de site) é sempre `fresh`: ali nada expira, e marcar expirado
|
|
517
|
+
* bloquearia o composer inventando um limite que a plataforma não impõe.
|
|
518
|
+
*/
|
|
519
|
+
declare function windowOf(params: WindowOfParams): ConversationWindow;
|
|
520
|
+
declare function formatStalledFor(lastAt: string, now: number): string;
|
|
521
|
+
|
|
522
|
+
type ConversationRowClassNames = {
|
|
523
|
+
root: string;
|
|
524
|
+
windowBar: string;
|
|
525
|
+
};
|
|
526
|
+
type ConversationRowProps = {
|
|
527
|
+
conversation: ConversationSummary;
|
|
528
|
+
active: boolean;
|
|
529
|
+
selected: boolean;
|
|
530
|
+
now: number;
|
|
531
|
+
busy: boolean;
|
|
532
|
+
onOpen: () => void;
|
|
533
|
+
onToggleSelected: () => void;
|
|
534
|
+
onTakeover: () => void;
|
|
535
|
+
className?: string;
|
|
536
|
+
classNames?: Partial<ConversationRowClassNames>;
|
|
537
|
+
};
|
|
538
|
+
declare function ConversationRow({ conversation, active, selected, now, busy, onOpen, onToggleSelected, onTakeover, className, classNames, }: ConversationRowProps): react.JSX.Element;
|
|
539
|
+
|
|
540
|
+
declare const CHANNEL_BRAND_COLOR: Readonly<Record<ConversationChannel, string>>;
|
|
541
|
+
interface ChannelIconProps {
|
|
542
|
+
channel?: ConversationChannel | undefined;
|
|
543
|
+
size?: number;
|
|
544
|
+
className?: string;
|
|
545
|
+
}
|
|
546
|
+
declare function ChannelIcon({ channel, size, className }: ChannelIconProps): react.JSX.Element;
|
|
547
|
+
|
|
548
|
+
interface ConversationHeaderLabels {
|
|
549
|
+
botMode: string;
|
|
550
|
+
humanMode: string;
|
|
551
|
+
returnToBot: string;
|
|
552
|
+
finish: string;
|
|
553
|
+
takeover: string;
|
|
554
|
+
download: string;
|
|
555
|
+
documents: string;
|
|
556
|
+
back: string;
|
|
557
|
+
moreActions: string;
|
|
558
|
+
}
|
|
559
|
+
declare const DEFAULT_CONVERSATION_HEADER_LABELS: ConversationHeaderLabels;
|
|
560
|
+
/**
|
|
561
|
+
* Partes estilizáveis do cabeçalho. Cada chave recebe classes que o `cn` funde por cima da base, e
|
|
562
|
+
* conflito de utilitário (padding, gap, borda) fica com o valor do produto.
|
|
563
|
+
*/
|
|
564
|
+
interface ConversationHeaderClassNames {
|
|
565
|
+
root: string;
|
|
566
|
+
identity: string;
|
|
567
|
+
name: string;
|
|
568
|
+
meta: string;
|
|
569
|
+
actions: string;
|
|
570
|
+
desktopActions: string;
|
|
571
|
+
mobileMenu: string;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Utilitário extra que o host pendura no cabeçalho — ícone no desktop, item de menu no celular,
|
|
575
|
+
* como os nativos. Entra por aqui, e não por um slot de ReactNode, porque é isso que preserva o
|
|
576
|
+
* comportamento responsivo: um nó solto viraria um quarto ícone em 375px, sem área de toque.
|
|
577
|
+
*/
|
|
578
|
+
interface ConversationHeaderUtility {
|
|
579
|
+
key: string;
|
|
580
|
+
/** Emoji, para casar com os utilitários nativos do cabeçalho. */
|
|
581
|
+
icon: string;
|
|
582
|
+
label: string;
|
|
583
|
+
run: () => void;
|
|
584
|
+
active?: boolean;
|
|
585
|
+
}
|
|
586
|
+
interface ConversationHeaderProps {
|
|
587
|
+
conversation: ConversationSummary;
|
|
588
|
+
busy?: boolean;
|
|
589
|
+
onTakeover?: () => void;
|
|
590
|
+
onReturnToBot?: () => void;
|
|
591
|
+
onFinish?: () => void;
|
|
592
|
+
onDownload?: () => void;
|
|
593
|
+
onOpenDocuments?: () => void;
|
|
594
|
+
documentsOpen?: boolean;
|
|
595
|
+
/** Ações do produto que não existem no contrato do pacote (ex.: ferramentas de dev). */
|
|
596
|
+
extraUtilities?: readonly ConversationHeaderUtility[];
|
|
597
|
+
onBack?: () => void;
|
|
598
|
+
labels?: Partial<ConversationHeaderLabels>;
|
|
599
|
+
className?: string;
|
|
600
|
+
classNames?: Partial<ConversationHeaderClassNames>;
|
|
601
|
+
}
|
|
602
|
+
declare function ConversationHeader({ conversation, busy, onTakeover, onReturnToBot, onFinish, onDownload, onOpenDocuments, documentsOpen, extraUtilities, onBack, labels: labelsOverride, className, classNames, }: ConversationHeaderProps): react.JSX.Element;
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* "Suas Seleções": o que o bot já coletou na conversa. Para o atendente que assume no meio, é a
|
|
606
|
+
* diferença entre ler o transcript inteiro e ver o estado de relance.
|
|
607
|
+
*
|
|
608
|
+
* O pacote não interpreta o contexto — ele é `Record<string, unknown>` e cada produto nomeia as
|
|
609
|
+
* próprias chaves. O host traduz para `entries`; aqui só se decide como mostrar.
|
|
610
|
+
*
|
|
611
|
+
* Desenho em paridade com financiamento-imobiliario-bot/apps/web/src/components/SelectionsSummary.tsx:
|
|
612
|
+
* card com gradiente, badge de contagem, pills de status no cabeçalho e grid de cards com borda
|
|
613
|
+
* colorida por estado. O que ficou diferente de lá, e por quê, está comentado no ponto.
|
|
614
|
+
*/
|
|
615
|
+
/**
|
|
616
|
+
* `completed` tem valor, `pending` ainda não foi coletado, `editing` é o cliente refazendo a
|
|
617
|
+
* resposta. O host só precisa mandar `status` para o terceiro caso — os dois primeiros saem do
|
|
618
|
+
* próprio `value`, e exigir o campo quebraria quem já usa o painel.
|
|
619
|
+
*/
|
|
620
|
+
type ConversationContextStatus = 'completed' | 'pending' | 'editing';
|
|
621
|
+
interface ConversationContextEntry {
|
|
622
|
+
key: string;
|
|
623
|
+
label: string;
|
|
624
|
+
value?: string | undefined;
|
|
625
|
+
icon?: string;
|
|
626
|
+
status?: ConversationContextStatus;
|
|
627
|
+
}
|
|
628
|
+
interface ConversationContextPanelLabels {
|
|
629
|
+
title: string;
|
|
630
|
+
empty: string;
|
|
631
|
+
collapse: string;
|
|
632
|
+
expand: string;
|
|
633
|
+
/** Placeholder do card ainda não coletado. */
|
|
634
|
+
notCollected: string;
|
|
635
|
+
}
|
|
636
|
+
declare const DEFAULT_CONVERSATION_CONTEXT_LABELS: ConversationContextPanelLabels;
|
|
637
|
+
interface ConversationContextPanelClassNames {
|
|
638
|
+
root: string;
|
|
639
|
+
toggle: string;
|
|
640
|
+
counter: string;
|
|
641
|
+
body: string;
|
|
642
|
+
}
|
|
643
|
+
interface ConversationContextPanelProps {
|
|
644
|
+
entries: readonly ConversationContextEntry[];
|
|
645
|
+
/**
|
|
646
|
+
* Estado inicial. Ausente, abre sozinho no desktop quando há algum dado preenchido.
|
|
647
|
+
*
|
|
648
|
+
* Existe porque "abre sozinho" nem sempre é o que o produto quer: com 1 de 6 campos preenchidos o
|
|
649
|
+
* painel ocupa altura mostrando quase só pendências, e empurra a conversa — que é o que se veio ver.
|
|
650
|
+
*/
|
|
651
|
+
defaultOpen?: boolean;
|
|
652
|
+
/**
|
|
653
|
+
* Rótulo do fluxo em curso, exibido em azul ao lado do título — no financiamento é o produto
|
|
654
|
+
* escolhido ("Consórcio", "MCMV"). Ausente, o cabeçalho fica só com título e contagens.
|
|
655
|
+
*/
|
|
656
|
+
flowLabel?: string;
|
|
657
|
+
labels?: Partial<ConversationContextPanelLabels>;
|
|
658
|
+
className?: string;
|
|
659
|
+
classNames?: Partial<ConversationContextPanelClassNames>;
|
|
660
|
+
}
|
|
661
|
+
declare function ConversationContextPanel({ entries, defaultOpen, flowLabel, labels: labelsOverride, className, classNames, }: ConversationContextPanelProps): react.JSX.Element;
|
|
662
|
+
|
|
663
|
+
interface WindowExpiredNoticeLabels {
|
|
664
|
+
title: string;
|
|
665
|
+
description: string;
|
|
666
|
+
sendTemplate: string;
|
|
667
|
+
}
|
|
668
|
+
declare const DEFAULT_WINDOW_EXPIRED_LABELS: WindowExpiredNoticeLabels;
|
|
669
|
+
interface WindowExpiredNoticeProps {
|
|
670
|
+
onSendTemplate?: () => void;
|
|
671
|
+
disabled?: boolean;
|
|
672
|
+
labels?: Partial<WindowExpiredNoticeLabels>;
|
|
673
|
+
className?: string;
|
|
674
|
+
}
|
|
675
|
+
declare function WindowExpiredNotice({ onSendTemplate, disabled, labels: labelsOverride, className, }: WindowExpiredNoticeProps): react.JSX.Element;
|
|
676
|
+
/**
|
|
677
|
+
* Só a faixa `expired` bloqueia: 21-24h ainda aceita texto livre e merece alerta, não impedimento.
|
|
678
|
+
*/
|
|
679
|
+
declare function isWindowBlocking(window: ConversationWindow): boolean;
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Biblioteca de arquivos de TODAS as conversas — a tela de Documentos do painel, fora do
|
|
683
|
+
* atendimento.
|
|
684
|
+
*
|
|
685
|
+
* Distinta do `ConversationDocumentsPanel`: aquele parte de uma conversa aberta e vive dentro dela;
|
|
686
|
+
* esta varre a empresa e por isso mostra de qual conversa cada arquivo veio, com o número
|
|
687
|
+
* clicável. Sem essa referência, uma lista global de anexos não responde nenhuma pergunta.
|
|
688
|
+
*/
|
|
689
|
+
interface DocumentsLibraryLabels {
|
|
690
|
+
title: string;
|
|
691
|
+
searchPlaceholder: string;
|
|
692
|
+
empty: string;
|
|
693
|
+
noResults: string;
|
|
694
|
+
loading: string;
|
|
695
|
+
failure: string;
|
|
696
|
+
view: string;
|
|
697
|
+
download: string;
|
|
698
|
+
openConversation: string;
|
|
699
|
+
sourceFilterAll: string;
|
|
700
|
+
sourceFilterCustomer: string;
|
|
701
|
+
sourceFilterTeam: string;
|
|
702
|
+
sortMostRecent: string;
|
|
703
|
+
sortOldest: string;
|
|
704
|
+
clearFilters: string;
|
|
705
|
+
total: (count: number) => string;
|
|
706
|
+
page: (current: number, last: number) => string;
|
|
707
|
+
}
|
|
708
|
+
declare const DEFAULT_DOCUMENTS_LIBRARY_LABELS: DocumentsLibraryLabels;
|
|
709
|
+
interface DocumentsLibraryClassNames {
|
|
710
|
+
root: string;
|
|
711
|
+
title: string;
|
|
712
|
+
filters: string;
|
|
713
|
+
search: string;
|
|
714
|
+
sourceSelect: string;
|
|
715
|
+
sortButton: string;
|
|
716
|
+
clearButton: string;
|
|
717
|
+
status: string;
|
|
718
|
+
list: string;
|
|
719
|
+
item: string;
|
|
720
|
+
conversationLink: string;
|
|
721
|
+
filename: string;
|
|
722
|
+
meta: string;
|
|
723
|
+
pagination: string;
|
|
724
|
+
}
|
|
725
|
+
interface DocumentsLibraryProps {
|
|
726
|
+
/** Itens por página. O total vem do servidor. */
|
|
727
|
+
perPage?: number;
|
|
728
|
+
/** Abrir a conversa de origem. Ausente, o número aparece como texto e não como link. */
|
|
729
|
+
onOpenConversation?: (conversationId: string) => void;
|
|
730
|
+
labels?: Partial<DocumentsLibraryLabels>;
|
|
731
|
+
className?: string;
|
|
732
|
+
classNames?: Partial<DocumentsLibraryClassNames>;
|
|
733
|
+
}
|
|
734
|
+
declare function DocumentsLibrary({ perPage, onOpenConversation, labels: labelsOverride, className, classNames, }: DocumentsLibraryProps): react.JSX.Element | null;
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Arquivos trocados na conversa. Sai do transcript e vira lista própria porque anexo é o que o
|
|
738
|
+
* atendente mais precisa reencontrar depois — rolar meses de mensagens para achar um comprovante é
|
|
739
|
+
* o caso que a busca por documento existe para eliminar.
|
|
740
|
+
*
|
|
741
|
+
* Usa `useConversationDocuments`, então funciona com qualquer `ConversationsApi`. Host sem
|
|
742
|
+
* biblioteca de documentos cai no estado vazio, sem quebrar.
|
|
743
|
+
*/
|
|
744
|
+
/** Origens que o filtro oferece. `all` não é origem — é a ausência de filtro. */
|
|
745
|
+
declare const DOCUMENT_SOURCE_FILTER: {
|
|
746
|
+
readonly ALL: "all";
|
|
747
|
+
readonly CUSTOMER: "customer";
|
|
748
|
+
readonly TEAM: "team";
|
|
749
|
+
};
|
|
750
|
+
type DocumentSourceFilter = (typeof DOCUMENT_SOURCE_FILTER)[keyof typeof DOCUMENT_SOURCE_FILTER];
|
|
751
|
+
interface ConversationDocumentsPanelLabels {
|
|
752
|
+
toggle: string;
|
|
753
|
+
title: string;
|
|
754
|
+
searchPlaceholder: string;
|
|
755
|
+
empty: string;
|
|
756
|
+
/** Distinto de `empty`: sem resultado POR CAUSA do filtro, e não conversa sem anexo nenhum. */
|
|
757
|
+
noResults: string;
|
|
758
|
+
loading: string;
|
|
759
|
+
failure: string;
|
|
760
|
+
download: string;
|
|
761
|
+
view: string;
|
|
762
|
+
sourceFilterAll: string;
|
|
763
|
+
sourceFilterCustomer: string;
|
|
764
|
+
sourceFilterTeam: string;
|
|
765
|
+
sortMostRecent: string;
|
|
766
|
+
sortOldest: string;
|
|
767
|
+
clearFilters: string;
|
|
768
|
+
selectAll: string;
|
|
769
|
+
downloadSelected: (count: number) => string;
|
|
770
|
+
archiveFailed: string;
|
|
771
|
+
total: (count: number) => string;
|
|
772
|
+
page: (current: number, last: number) => string;
|
|
773
|
+
}
|
|
774
|
+
declare const DEFAULT_CONVERSATION_DOCUMENTS_LABELS: ConversationDocumentsPanelLabels;
|
|
775
|
+
/**
|
|
776
|
+
* Partes estilizáveis do painel, no mesmo contrato do `ConversationHeader`: `cn` funde por cima da
|
|
777
|
+
* base e conflito de utilitário (padding, tamanho de fonte, borda) fica com o valor do produto.
|
|
778
|
+
*
|
|
779
|
+
* `status` cobre carregando/erro/vazio de uma vez — são a mesma linha de texto auxiliar, e slots
|
|
780
|
+
* separados só multiplicariam chave para quem quer mudar a cor de aviso.
|
|
781
|
+
*/
|
|
782
|
+
interface ConversationDocumentsPanelClassNames {
|
|
783
|
+
root: string;
|
|
784
|
+
body: string;
|
|
785
|
+
title: string;
|
|
786
|
+
filters: string;
|
|
787
|
+
search: string;
|
|
788
|
+
sourceSelect: string;
|
|
789
|
+
sortButton: string;
|
|
790
|
+
clearButton: string;
|
|
791
|
+
status: string;
|
|
792
|
+
list: string;
|
|
793
|
+
item: string;
|
|
794
|
+
sourceBadge: string;
|
|
795
|
+
filename: string;
|
|
796
|
+
meta: string;
|
|
797
|
+
viewButton: string;
|
|
798
|
+
downloadButton: string;
|
|
799
|
+
pagination: string;
|
|
800
|
+
selectionBar: string;
|
|
801
|
+
checkbox: string;
|
|
802
|
+
}
|
|
803
|
+
interface ConversationDocumentsPanelProps {
|
|
804
|
+
conversationId: string;
|
|
805
|
+
/** Controlado de fora porque o gatilho vive no cabeçalho, junto das outras ações da conversa. */
|
|
806
|
+
open: boolean;
|
|
807
|
+
/** Itens por página. O total vem do servidor; sem paginação no host, a barra não aparece. */
|
|
808
|
+
perPage?: number;
|
|
809
|
+
labels?: Partial<ConversationDocumentsPanelLabels>;
|
|
810
|
+
className?: string;
|
|
811
|
+
classNames?: Partial<ConversationDocumentsPanelClassNames>;
|
|
812
|
+
}
|
|
813
|
+
declare function ConversationDocumentsPanel({ conversationId, open, perPage, labels: labelsOverride, className, classNames, }: ConversationDocumentsPanelProps): react.JSX.Element | null;
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Serialização do transcript para download. Fica no pacote porque o formato de um histórico de
|
|
817
|
+
* WhatsApp legível não é regra de negócio de ninguém — e porque o host que já tem as mensagens em
|
|
818
|
+
* tela não deveria precisar de rota nova só para salvar um arquivo.
|
|
819
|
+
*
|
|
820
|
+
* Funções puras, separadas do disparo do download: é o que permite testá-las sem DOM.
|
|
821
|
+
*/
|
|
822
|
+
|
|
823
|
+
type BuildTranscriptTextParams = {
|
|
824
|
+
readonly messages: readonly MessagePayload[];
|
|
825
|
+
readonly whatsappNumber: string;
|
|
826
|
+
readonly clientName?: string | undefined;
|
|
827
|
+
};
|
|
828
|
+
/**
|
|
829
|
+
* Formato próximo ao export nativo do WhatsApp (`[data hora] Autor: texto`), que é o que pessoas e
|
|
830
|
+
* ferramentas de suporte já sabem ler.
|
|
831
|
+
*/
|
|
832
|
+
declare function buildTranscriptText(params: BuildTranscriptTextParams): string;
|
|
833
|
+
declare function buildTranscriptFilename(whatsappNumber: string, generatedAt: Date): string;
|
|
834
|
+
/**
|
|
835
|
+
* Dispara o download no navegador. `revokeObjectURL` no fim não é higiene opcional: sem ele cada
|
|
836
|
+
* export retém o blob inteiro em memória até a aba fechar.
|
|
837
|
+
*/
|
|
838
|
+
declare function downloadTextFile(filename: string, content: string): void;
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Leitura passiva do tema do host: observa a classe `dark` no `<html>` e não escreve nada.
|
|
842
|
+
*
|
|
843
|
+
* Existe porque `useDarkMode` é um controlador — ele grava a classe e persiste a preferência. Um
|
|
844
|
+
* componente que só precisa escolher cor não pode usá-lo: bastava renderizar o mapa de fluxos
|
|
845
|
+
* para o app inteiro do host trocar de tema, seguindo o `prefers-color-scheme` do sistema em vez
|
|
846
|
+
* da configuração da aplicação.
|
|
847
|
+
*/
|
|
848
|
+
declare function useIsDarkTheme(): boolean;
|
|
282
849
|
declare function useDarkMode(): {
|
|
283
850
|
isDark: boolean;
|
|
284
851
|
toggle: () => void;
|
|
285
852
|
};
|
|
286
853
|
|
|
854
|
+
/**
|
|
855
|
+
* Detecta tela estreita para decisões que CSS não resolve — como abrir ou não um painel por padrão.
|
|
856
|
+
*
|
|
857
|
+
* O breakpoint é o mesmo das classes `cv-only-*` e `.cv-back` (1024px). Duplicado aqui porque
|
|
858
|
+
* JavaScript não lê media query do stylesheet; se um dia divergirem, o sintoma é painel abrindo
|
|
859
|
+
* numa largura onde o resto da UI já mudou de modo.
|
|
860
|
+
*/
|
|
861
|
+
declare const NARROW_MAX_WIDTH_PX = 1023;
|
|
862
|
+
declare function useIsNarrow(): boolean;
|
|
863
|
+
|
|
864
|
+
interface UseWaitingNotificationsLabels {
|
|
865
|
+
/** Título da notificação do sistema. Recebe a conversa para o host escolher nome × número. */
|
|
866
|
+
title: (conversation: ConversationSummary) => string;
|
|
867
|
+
body: (conversation: ConversationSummary) => string;
|
|
868
|
+
}
|
|
869
|
+
interface UseWaitingNotificationsParams {
|
|
870
|
+
/**
|
|
871
|
+
* Repassado cru ao `fetchConversations`. É o que permite filtrar não lidas **no servidor** em
|
|
872
|
+
* vez de baixar a lista inteira e contar no cliente: um painel com milhares de conversas não
|
|
873
|
+
* pode paginar 50 por vez atrás de quem tem `unread > 0`.
|
|
874
|
+
*/
|
|
875
|
+
readonly params?: ListConversationsParams;
|
|
876
|
+
readonly intervalMs?: number;
|
|
877
|
+
/** Desliga o polling sem desmontar quem chama — útil com a aba em segundo plano. */
|
|
878
|
+
readonly enabled?: boolean;
|
|
879
|
+
readonly icon?: string;
|
|
880
|
+
readonly labels?: Partial<UseWaitingNotificationsLabels>;
|
|
881
|
+
}
|
|
287
882
|
interface UseWaitingNotificationsResult {
|
|
288
883
|
unreadCount: number;
|
|
289
884
|
conversations: ConversationSummary[];
|
|
885
|
+
/**
|
|
886
|
+
* Releitura sob demanda. Existe porque o polling é o piso, não o mecanismo: quem já recebe SSE
|
|
887
|
+
* ou acabou de marcar tudo como lido sabe da mudança antes do próximo tick, e esperar 10s para
|
|
888
|
+
* o contador acompanhar faz a interface parecer travada.
|
|
889
|
+
*/
|
|
890
|
+
refresh: () => Promise<void>;
|
|
290
891
|
}
|
|
291
|
-
declare function useWaitingNotifications(): UseWaitingNotificationsResult;
|
|
892
|
+
declare function useWaitingNotifications(params?: UseWaitingNotificationsParams): UseWaitingNotificationsResult;
|
|
292
893
|
|
|
293
894
|
interface ConversationsContextValue {
|
|
294
895
|
api: ConversationsApi;
|
|
@@ -370,6 +971,7 @@ interface WhatsAppCreateTemplateFormLabels {
|
|
|
370
971
|
sectionDescription: string;
|
|
371
972
|
nameLabel: string;
|
|
372
973
|
nameHint: string;
|
|
974
|
+
namePlaceholder: string;
|
|
373
975
|
categoryLabel: string;
|
|
374
976
|
languageLabel: string;
|
|
375
977
|
headerLabel: string;
|
|
@@ -435,6 +1037,82 @@ interface WelcomeFarewellFormProps {
|
|
|
435
1037
|
}
|
|
436
1038
|
declare function WelcomeFarewellForm({ welcomeMessage, onWelcomeMessageChange, farewellMessage, onFarewellMessageChange, onSave, saving, saveSuccess, welcomePlaceholders, farewellPlaceholders, labels: labelsOverride, }: WelcomeFarewellFormProps): react.JSX.Element;
|
|
437
1039
|
|
|
1040
|
+
interface TranscriptionSettingsFormLabels {
|
|
1041
|
+
sectionTitle: string;
|
|
1042
|
+
enabled: string;
|
|
1043
|
+
enabledHint: string;
|
|
1044
|
+
modeTitle: string;
|
|
1045
|
+
modeAuto: string;
|
|
1046
|
+
modeAutoHint: string;
|
|
1047
|
+
modeOnDemand: string;
|
|
1048
|
+
modeOnDemandHint: string;
|
|
1049
|
+
unavailable: string;
|
|
1050
|
+
save: string;
|
|
1051
|
+
saving: string;
|
|
1052
|
+
saveSuccess: string;
|
|
1053
|
+
}
|
|
1054
|
+
interface TranscriptionSettingsFormProps {
|
|
1055
|
+
enabled: boolean;
|
|
1056
|
+
onEnabledChange: (value: boolean) => void;
|
|
1057
|
+
mode: TranscriptionMode;
|
|
1058
|
+
onModeChange: (value: TranscriptionMode) => void;
|
|
1059
|
+
onSave: (event: FormEvent) => void;
|
|
1060
|
+
/**
|
|
1061
|
+
* A capacidade existe no servidor (engine e credencial configurados)?
|
|
1062
|
+
*
|
|
1063
|
+
* Distinto de `enabled`: isto é "o ambiente consegue", aquilo é "esta empresa quer". Sem a
|
|
1064
|
+
* distinção, um lojista ligaria o interruptor num ambiente sem engine e concluiria que o produto
|
|
1065
|
+
* está quebrado — o texto nunca apareceria e nada explicaria por quê.
|
|
1066
|
+
*/
|
|
1067
|
+
isAvailable?: boolean;
|
|
1068
|
+
saving?: boolean;
|
|
1069
|
+
saveSuccess?: boolean;
|
|
1070
|
+
labels?: Partial<TranscriptionSettingsFormLabels>;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Interruptor de transcrição por empresa, para a tela de configurações.
|
|
1074
|
+
*
|
|
1075
|
+
* Puramente apresentacional, como os outros forms daqui: quem persiste é o host. O pacote não sabe a
|
|
1076
|
+
* rota, e a política vive nas configurações do módulo (`settings.transcriptionEnabled`), não em
|
|
1077
|
+
* variável de ambiente — mudar de ideia sobre transcrever não deveria exigir deploy.
|
|
1078
|
+
*/
|
|
1079
|
+
declare function TranscriptionSettingsForm({ enabled, onEnabledChange, mode, onModeChange, onSave, isAvailable, saving, saveSuccess, labels: labelsOverride, }: TranscriptionSettingsFormProps): react.JSX.Element;
|
|
1080
|
+
|
|
1081
|
+
declare const TEMPLATE_SETTINGS_TAB: {
|
|
1082
|
+
readonly SELECT: "select";
|
|
1083
|
+
readonly CREATE: "create";
|
|
1084
|
+
};
|
|
1085
|
+
type TemplateSettingsTab = (typeof TEMPLATE_SETTINGS_TAB)[keyof typeof TEMPLATE_SETTINGS_TAB];
|
|
1086
|
+
interface WhatsAppTemplatesSettingsLabels {
|
|
1087
|
+
selectTab: string;
|
|
1088
|
+
createTab: string;
|
|
1089
|
+
}
|
|
1090
|
+
declare const DEFAULT_TEMPLATES_SETTINGS_LABELS: WhatsAppTemplatesSettingsLabels;
|
|
1091
|
+
interface WhatsAppTemplatesSettingsProps {
|
|
1092
|
+
templates: WhatsAppTemplateSummary[];
|
|
1093
|
+
loadingTemplates?: boolean;
|
|
1094
|
+
templatesError?: boolean;
|
|
1095
|
+
onRefreshTemplates?: () => void;
|
|
1096
|
+
selectedTemplateName: string;
|
|
1097
|
+
onSelectTemplate: (name: string, template: WhatsAppTemplateSummary | undefined) => void;
|
|
1098
|
+
variables: string[];
|
|
1099
|
+
onVariablesChange: (variables: string[]) => void;
|
|
1100
|
+
availableVariables?: WhatsAppTemplateVariableSuggestion[];
|
|
1101
|
+
saving?: boolean;
|
|
1102
|
+
saveSuccess?: boolean;
|
|
1103
|
+
onSave: (event: FormEvent) => void;
|
|
1104
|
+
/** Ausente = host não sabe criar template; a aba de criação nem aparece. */
|
|
1105
|
+
create?: {
|
|
1106
|
+
value: WhatsAppCreateTemplateState;
|
|
1107
|
+
onChange: (value: WhatsAppCreateTemplateState) => void;
|
|
1108
|
+
onSubmit: (event: FormEvent) => void;
|
|
1109
|
+
submitting?: boolean;
|
|
1110
|
+
result?: WhatsAppCreateTemplateResult | null;
|
|
1111
|
+
};
|
|
1112
|
+
labels?: Partial<WhatsAppTemplatesSettingsLabels>;
|
|
1113
|
+
}
|
|
1114
|
+
declare function WhatsAppTemplatesSettings({ labels: labelsOverride, create, ...settingsProps }: WhatsAppTemplatesSettingsProps): react.JSX.Element;
|
|
1115
|
+
|
|
438
1116
|
interface TopicItem {
|
|
439
1117
|
key: string;
|
|
440
1118
|
label: string;
|
|
@@ -472,7 +1150,7 @@ interface UseConversationMessagesResult {
|
|
|
472
1150
|
caption?: string;
|
|
473
1151
|
}) => Promise<MessagePayload>;
|
|
474
1152
|
sendTemplate: (data: {
|
|
475
|
-
templateName
|
|
1153
|
+
templateName?: string;
|
|
476
1154
|
languageCode?: string;
|
|
477
1155
|
bodyParams?: string[];
|
|
478
1156
|
}) => Promise<void>;
|
|
@@ -483,14 +1161,28 @@ declare function useConversationMessages(conversationId: string, params?: {
|
|
|
483
1161
|
before?: string;
|
|
484
1162
|
}): UseConversationMessagesResult;
|
|
485
1163
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
1164
|
+
type UseScrollToLatestMessageParams = {
|
|
1165
|
+
/** Troca de conversa. Muda ⇒ salto instantâneo para o fim. */
|
|
1166
|
+
readonly conversationId: string | undefined;
|
|
1167
|
+
/** Quantidade de mensagens carregadas. Cresce ⇒ acompanha o fim, se o operador estiver lá. */
|
|
1168
|
+
readonly messageCount: number;
|
|
1169
|
+
};
|
|
1170
|
+
type UseScrollToLatestMessageResult = {
|
|
1171
|
+
/** Vai no elemento que rola — tipicamente o `ConversationWallpaper`. */
|
|
1172
|
+
readonly containerRef: RefObject<HTMLDivElement | null>;
|
|
1173
|
+
/** Ligue no `onScroll` do mesmo elemento: é o que detecta o operador lendo o histórico. */
|
|
1174
|
+
readonly handleScroll: (event: UIEvent<HTMLDivElement>) => void;
|
|
1175
|
+
/** `true` quando o operador rolou para trás — serve a um botão "ir para a última". */
|
|
1176
|
+
readonly isAwayFromBottom: boolean;
|
|
1177
|
+
readonly scrollToBottom: (behavior?: ScrollBehavior) => void;
|
|
1178
|
+
};
|
|
1179
|
+
declare function useScrollToLatestMessage({ conversationId, messageCount, }: UseScrollToLatestMessageParams): UseScrollToLatestMessageResult;
|
|
1180
|
+
|
|
1181
|
+
type UseConversationListParams = ListConversationsParams;
|
|
492
1182
|
interface UseConversationListResult {
|
|
493
1183
|
conversations: ConversationSummary[];
|
|
1184
|
+
/** Total no servidor. Cai para o tamanho da página quando a API devolve só o array. */
|
|
1185
|
+
total: number;
|
|
494
1186
|
loading: boolean;
|
|
495
1187
|
error: Error | undefined;
|
|
496
1188
|
refetch: () => Promise<void>;
|
|
@@ -505,22 +1197,42 @@ interface UseConversationContextResult {
|
|
|
505
1197
|
}
|
|
506
1198
|
declare function useConversationContext(conversationId: string): UseConversationContextResult;
|
|
507
1199
|
|
|
508
|
-
|
|
509
|
-
search?: string;
|
|
510
|
-
page?: number;
|
|
511
|
-
}
|
|
1200
|
+
type UseConversationDocumentsParams = ListDocumentsParams;
|
|
512
1201
|
interface UseConversationDocumentsResult {
|
|
513
1202
|
documents: ConversationDocument[];
|
|
1203
|
+
/** Total no servidor. Cai para o tamanho da página quando a API devolve só o array. */
|
|
1204
|
+
total: number;
|
|
514
1205
|
loading: boolean;
|
|
515
1206
|
error: Error | undefined;
|
|
516
1207
|
refetch: () => Promise<void>;
|
|
517
1208
|
}
|
|
518
|
-
declare function useConversationDocuments(conversationId: string, params?: UseConversationDocumentsParams): UseConversationDocumentsResult;
|
|
1209
|
+
declare function useConversationDocuments(conversationId: string | undefined, params?: UseConversationDocumentsParams): UseConversationDocumentsResult;
|
|
519
1210
|
|
|
520
1211
|
type ConversationRealtimeHandler = (event: MessageEvent) => void;
|
|
521
1212
|
declare function useConversationRealtime(conversationId: string | undefined, onEvent: ConversationRealtimeHandler): void;
|
|
522
1213
|
declare function useGlobalRealtime(onEvent: ConversationRealtimeHandler): void;
|
|
523
1214
|
|
|
1215
|
+
interface UseConversationActionsResult {
|
|
1216
|
+
/** `undefined` quando a API do host não implementa a operação — a UI esconde a afordância. */
|
|
1217
|
+
takeover: (() => Promise<void>) | undefined;
|
|
1218
|
+
release: (() => Promise<void>) | undefined;
|
|
1219
|
+
finalize: (() => Promise<void>) | undefined;
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Ações de atendimento de UMA conversa, já ligadas ao id.
|
|
1223
|
+
*
|
|
1224
|
+
* Separado de `useConversationMessages` porque assumir e devolver conversa também acontece a
|
|
1225
|
+
* partir da lista, onde nenhuma thread está aberta — embutir nas mensagens obrigaria a carregar
|
|
1226
|
+
* a thread inteira só para desenhar um botão na linha.
|
|
1227
|
+
*/
|
|
1228
|
+
declare function useConversationActions(conversationId: string): UseConversationActionsResult;
|
|
1229
|
+
interface UseInboxActionsResult {
|
|
1230
|
+
markAllRead: (() => Promise<void>) | undefined;
|
|
1231
|
+
listTemplates: (() => Promise<ConversationTemplate[]>) | undefined;
|
|
1232
|
+
}
|
|
1233
|
+
/** Ações que valem para a caixa inteira, sem conversa selecionada. */
|
|
1234
|
+
declare function useInboxActions(): UseInboxActionsResult;
|
|
1235
|
+
|
|
524
1236
|
declare function parseWhatsAppFormatting(text: string): ReactNode[];
|
|
525
1237
|
declare function waToHTML(text: string): string;
|
|
526
1238
|
declare function htmlToWA(html: string): string;
|
|
@@ -530,6 +1242,8 @@ declare function formatPhone(number: string): string;
|
|
|
530
1242
|
declare function phoneInitials(number: string): string;
|
|
531
1243
|
|
|
532
1244
|
declare function formatTimestamp(timestamp: string): string;
|
|
1245
|
+
declare function formatDateTime(iso: string): string;
|
|
1246
|
+
declare function isSameDay(a: Date, b: Date): boolean;
|
|
533
1247
|
declare function formatFileSize(bytes: number): string;
|
|
534
1248
|
|
|
535
1249
|
interface AsyncResourceState<T> {
|
|
@@ -539,4 +1253,16 @@ interface AsyncResourceState<T> {
|
|
|
539
1253
|
refetch: () => Promise<void>;
|
|
540
1254
|
}
|
|
541
1255
|
|
|
542
|
-
|
|
1256
|
+
/**
|
|
1257
|
+
* Resolve a mídia de uma mensagem numa URL exibível, usando só o `ConversationsApi`.
|
|
1258
|
+
*
|
|
1259
|
+
* Mora no pacote, e não em cada host, porque a regra não tem nada de específico de produto: é a
|
|
1260
|
+
* tradução de `uploadId`/`mediaId` pelos dois métodos que o próprio contrato já declara. Deixá-la no
|
|
1261
|
+
* host significava que todo projeto que adotasse o SDK reescreveria as mesmas oito linhas — e, na
|
|
1262
|
+
* prática, ninguém escrevia: o `MediaRenderer` só busca mídia pela porta `onResolveMediaUrl`, então
|
|
1263
|
+
* onde nada era injetado foto, vídeo e áudio ficavam no placeholder para sempre.
|
|
1264
|
+
*/
|
|
1265
|
+
|
|
1266
|
+
declare function createMediaUrlResolver(api: Pick<ConversationsApi, 'getDocumentUrl' | 'getMediaProxyUrl'>): ResolveMediaUrl;
|
|
1267
|
+
|
|
1268
|
+
export { type AsyncResourceState, AudioPlayer, type AudioPlayerProps, AudioTranscription, type AudioTranscriptionProps, Avatar, type AvatarLabels, type AvatarProps, type BuildTranscriptTextParams, CHANNEL_BRAND_COLOR, CONVERSATION_WINDOW, ChannelIcon, type ChannelIconProps, ConversationChannel, type ConversationContextEntry, ConversationContextPanel, type ConversationContextPanelClassNames, type ConversationContextPanelLabels, type ConversationContextPanelProps, type ConversationContextStatus, ConversationDocument, ConversationDocumentsPanel, type ConversationDocumentsPanelClassNames, type ConversationDocumentsPanelLabels, type ConversationDocumentsPanelProps, ConversationHeader, type ConversationHeaderClassNames, type ConversationHeaderLabels, type ConversationHeaderProps, type ConversationHeaderUtility, ConversationListItem, type ConversationListItemLabels, type ConversationListItemProps, type ConversationLocales, ConversationLocalesProvider, type ConversationLocalesProviderProps, type ConversationRealtimeHandler, ConversationRow, type ConversationRowClassNames, type ConversationRowProps, ConversationSummary, ConversationTemplate, ConversationWallpaper, type ConversationWallpaperProps, type ConversationWindow, ConversationsApi, ConversationsFeatures, ConversationsProvider, DEFAULT_ACCEPTED_FILE_TYPES, DEFAULT_AVATAR_LABELS, DEFAULT_CONVERSATION_CONTEXT_LABELS, DEFAULT_CONVERSATION_DOCUMENTS_LABELS, DEFAULT_CONVERSATION_HEADER_LABELS, DEFAULT_CONVERSATION_LIST_ITEM_LABELS, DEFAULT_DOCUMENTS_LIBRARY_LABELS, DEFAULT_EMOJI_PICKER_LABELS, DEFAULT_INTERACTIVE_MESSAGE_LABELS, DEFAULT_LIGHTBOX_LABELS, DEFAULT_MESSAGE_COMPOSER_LABELS, DEFAULT_RICH_COMPOSER_TOOLTIPS, DEFAULT_TEMPLATES_SETTINGS_LABELS, DEFAULT_WHATSAPP_MESSAGE_EDITOR_LABELS, DEFAULT_WINDOW_EXPIRED_LABELS, DOCUMENT_SOURCE_FILTER, DateDivider, type DateDividerClassNames, type DateDividerProps, type DocumentSourceFilter, DocumentsLibrary, type DocumentsLibraryClassNames, type DocumentsLibraryLabels, type DocumentsLibraryProps, EMOJI_CATEGORIES, type EmojiCategory, type EmojiEntry, EmojiPicker, type EmojiPickerLabels, type EmojiPickerProps, FileIcon, type FileIconProps, InteractiveMessage, type InteractiveMessageLabels, type InteractiveMessageProps, InteractivePayload, InteractiveSelection, Lightbox, type LightboxLabels, type LightboxProps, ListConversationsParams, ListDocumentsParams, MessageBubble, type MessageBubbleProps, MessageComposer, type MessageComposerClassNames, type MessageComposerLabels, type MessageComposerProps, MessagePayload, MessageTail, MessageText, type MessageTextProps, MessageTimestamp, MessageTranscription, NARROW_MAX_WIDTH_PX, type QuickReply, RICH_COMPOSER_ACTION, ResolveMediaUrl, type RichComposerAction, type RichComposerQuickReply, type RichComposerTooltips, type RichComposerVariable, RichMessageComposer, type RichMessageComposerHandle, type RichMessageComposerProps, SSEProvider, SimpleEmojiPicker, type SimpleEmojiPickerProps, StatusTicks, type StatusTicksProps, TEMPLATE_SETTINGS_TAB, type TemplateSettingsTab, ToastProvider, type TopicItem, TopicsForm, type TopicsFormLabels, type TopicsFormProps, TranscriptionMode, TranscriptionSettingsForm, type TranscriptionSettingsFormLabels, type TranscriptionSettingsFormProps, type UseConversationActionsResult, type UseConversationContextResult, type UseConversationDocumentsParams, type UseConversationDocumentsResult, type UseConversationListParams, type UseConversationListResult, type UseConversationMessagesResult, type UseInboxActionsResult, type UseScrollToLatestMessageParams, type UseScrollToLatestMessageResult, type UseWaitingNotificationsLabels, type UseWaitingNotificationsParams, type UseWaitingNotificationsResult, WINDOW_FILTERS, WelcomeFarewellForm, type WelcomeFarewellFormLabels, type WelcomeFarewellFormProps, WhatsAppCreateTemplateForm, type WhatsAppCreateTemplateFormLabels, type WhatsAppCreateTemplateFormProps, type WhatsAppCreateTemplateResult, type WhatsAppCreateTemplateState, WhatsAppMessageEditor, type WhatsAppMessageEditorLabels, type WhatsAppMessageEditorProps, type WhatsAppTemplateHeaderType, WhatsAppTemplateSettingsForm, type WhatsAppTemplateSettingsFormLabels, type WhatsAppTemplateSettingsFormProps, type WhatsAppTemplateSummary, type WhatsAppTemplateVariableSuggestion, WhatsAppTemplatesSettings, type WhatsAppTemplatesSettingsLabels, type WhatsAppTemplatesSettingsProps, WindowExpiredNotice, type WindowExpiredNoticeLabels, type WindowExpiredNoticeProps, type WindowOfParams, applyQuickReplyVariables, buildTranscriptFilename, buildTranscriptText, createMediaUrlResolver, downloadTextFile, formatDateTime, formatFileSize, formatPhone, formatStalledFor, formatTimestamp, htmlToWA, isSameDay, isWindowBlocking, parseWhatsAppFormatting, phoneInitials, resolveQuickReply, searchEmojis, toast, useConversationActions, useConversationContext, useConversationDocuments, useConversationList, useConversationLocales, useConversationMessages, useConversationRealtime, useConversations, useDarkMode, useGlobalRealtime, useInboxActions, useIsDarkTheme, useIsNarrow, useScrollToLatestMessage, useToast, useWaitingNotifications, waToHTML, waToHTMLInline, windowOf };
|