@adatechnology/conversations-ui 0.1.0-rc.4 → 0.1.0-rc.41
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/ConversationSimulatorPanel--5fIzXWY.d.ts +804 -0
- package/dist/chunk-BJNRLLDO.js +2708 -0
- package/dist/chunk-DKPXKQGC.js +110 -0
- package/dist/{chunk-OGRRHQQW.js → chunk-WCBDXZ3X.js} +68 -4
- package/dist/flows/index.d.ts +422 -5
- package/dist/flows/index.js +2389 -678
- package/dist/index.d.ts +1171 -42
- package/dist/index.js +3755 -843
- package/dist/preview/index.d.ts +157 -42
- package/dist/preview/index.js +772 -191
- package/dist/styles.css +893 -0
- package/package.json +9 -8
- package/src/AudioPlayer.tsx +8 -0
- package/src/AudioRecorderButton.test.tsx +30 -0
- package/src/AudioRecorderButton.tsx +248 -0
- package/src/AudioTranscription.test.tsx +115 -0
- package/src/AudioTranscription.tsx +252 -0
- package/src/Avatar.tsx +14 -3
- package/src/ConversationContextPanel.tsx +218 -44
- package/src/ConversationDocumentsPanel.tsx +347 -24
- package/src/ConversationHeader.test.tsx +66 -0
- package/src/ConversationHeader.tsx +163 -45
- package/src/ConversationListItem.tsx +19 -2
- package/src/ConversationLocalesProvider.tsx +28 -0
- package/src/ConversationRow.tsx +31 -7
- package/src/DarkModeToggle.test.tsx +76 -0
- package/src/DarkModeToggle.tsx +92 -0
- package/src/DocumentsLibrary.tsx +382 -0
- package/src/EmojiPicker.tsx +70 -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 +146 -0
- package/src/Lightbox.tsx +18 -3
- package/src/MediaRenderer.tsx +92 -16
- package/src/MessageBubble.test.tsx +41 -0
- package/src/MessageBubble.tsx +75 -6
- package/src/MessageComposer.test.tsx +35 -0
- package/src/MessageComposer.tsx +155 -19
- package/src/RichMessageComposer.test.tsx +113 -0
- package/src/RichMessageComposer.tsx +551 -0
- package/src/SimpleEmojiPicker.tsx +5 -3
- package/src/StatusTicks.tsx +1 -1
- package/src/Toast.tsx +4 -0
- package/src/Tooltip.test.ts +42 -0
- package/src/Tooltip.tsx +167 -0
- package/src/Wallpaper.test.tsx +21 -0
- package/src/Wallpaper.tsx +67 -7
- package/src/WhatsAppMessageEditor.tsx +34 -7
- package/src/WindowExpiredNotice.tsx +12 -4
- package/src/audioRecorderFormat.test.ts +67 -0
- package/src/buildOutput.test.ts +79 -0
- package/src/composer.constant.ts +33 -0
- package/src/conversationTranscript.test.ts +57 -0
- package/src/conversationTranscript.ts +29 -4
- package/src/conversationWindow.ts +7 -5
- package/src/documentTypeLabel.test.ts +57 -0
- package/src/documents/DocumentsWorkspace.tsx +550 -0
- package/src/documents/index.ts +8 -0
- package/src/documents/labels.ts +92 -0
- package/src/emojiCatalog.test.ts +35 -0
- package/src/emojiCatalog.ts +189 -0
- package/src/flows/FlowConnectionEdge.tsx +104 -0
- package/src/flows/FlowGroupHeader.tsx +12 -2
- package/src/flows/FlowLegend.tsx +125 -0
- package/src/flows/FlowMapCanvas.tsx +15 -12
- package/src/flows/FlowMapNode.tsx +4 -1
- package/src/flows/FlowNodeCard.tsx +219 -34
- package/src/flows/FlowNodePanel.tsx +153 -39
- package/src/flows/FlowPalette.tsx +106 -69
- package/src/flows/FlowPortalNode.tsx +1 -1
- package/src/flows/FlowWhatsAppPreview.tsx +14 -3
- package/src/flows/FlowsWorkspace.tsx +1219 -0
- package/src/flows/flowCanvasModel.test.ts +456 -0
- package/src/flows/flowCanvasModel.ts +378 -0
- package/src/flows/flowEditorOps.test.ts +276 -0
- package/src/flows/flowEditorOps.ts +202 -0
- package/src/flows/flowGraph.ts +78 -53
- package/src/flows/index.ts +51 -2
- package/src/flows/labels.ts +180 -0
- package/src/flows/workspaceContract.test.ts +126 -0
- package/src/hooks/useContainerWidth.ts +35 -0
- package/src/hooks/useConversationActions.ts +56 -0
- package/src/hooks/useConversationDocuments.ts +11 -7
- package/src/hooks/useConversationList.ts +15 -9
- package/src/hooks/useConversationMessages.ts +2 -2
- package/src/hooks/useConversationRealtime.ts +10 -8
- package/src/hooks/useScrollToLatestMessage.ts +127 -0
- package/src/hooks/useUrlFilterState.ts +107 -0
- package/src/icon.constant.ts +12 -0
- package/src/index.ts +129 -13
- package/src/lib/cn.test.ts +29 -0
- package/src/lib/composer-formatting.test.ts +78 -0
- package/src/lib/composer-formatting.ts +145 -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/whatsapp-formatting.test.tsx +37 -0
- package/src/lib/whatsapp-formatting.tsx +28 -3
- package/src/listing/index.tsx +202 -0
- package/src/pagination.constant.ts +10 -0
- package/src/preview/ConversationPreview.tsx +225 -17
- package/src/preview/ConversationSimulatorClient.ts +143 -0
- package/src/preview/ConversationSimulatorPanel.test.tsx +55 -0
- package/src/preview/ConversationSimulatorPanel.tsx +131 -0
- package/src/preview/MediaTypesPreview.tsx +87 -0
- package/src/preview/conversationPreviewFailures.test.ts +64 -0
- package/src/preview/createMockConversationsApi.ts +175 -15
- 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 +96 -0
- package/src/preview/createPreviewWebhookClient.ts +127 -4
- package/src/preview/index.ts +51 -3
- package/src/preview/mediaTypeOf.test.ts +15 -0
- package/src/preview/mockDocumentsSearch.test.ts +57 -0
- package/src/preview/preview.test.ts +5 -3
- package/src/preview/previewFileSamples.test.ts +151 -0
- package/src/preview/previewFileSamples.ts +74 -0
- package/src/preview/previewFixtures.ts +288 -1
- 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/providers/ConversationsProvider.tsx +8 -6
- package/src/providers/types.ts +185 -10
- package/src/quickReply.test.ts +58 -0
- package/src/settings/MessagesWorkspace.tsx +571 -0
- package/src/settings/TopicsForm.tsx +2 -0
- package/src/settings/TranscriptionSettingsForm.test.tsx +81 -0
- package/src/settings/TranscriptionSettingsForm.tsx +190 -0
- package/src/settings/WelcomeFarewellForm.tsx +1 -0
- package/src/settings/WhatsAppCreateTemplateForm.tsx +4 -1
- package/src/settings/WhatsAppTemplateSettingsForm.tsx +5 -2
- package/src/settings/WhatsAppTemplatesSettings.test.tsx +61 -0
- package/src/settings/WhatsAppTemplatesSettings.tsx +22 -2
- package/src/styles.css +858 -0
- package/src/types.ts +64 -1
- package/src/useWaitingNotifications.ts +74 -29
- package/src/workspace/BulkTemplateModal.tsx +132 -0
- package/src/workspace/ConversationPane.tsx +432 -0
- package/src/workspace/ConversationsInboxList.tsx +194 -0
- package/src/workspace/ConversationsWorkspace.tsx +423 -0
- package/src/workspace/index.ts +17 -0
- package/src/workspace/labels.test.ts +17 -0
- package/src/workspace/labels.ts +85 -0
- package/src/workspace/useConversationsInbox.ts +332 -0
- package/dist/chunk-4R6Y43DQ.js +0 -726
- package/dist/chunk-NV2RZ5KT.js +0 -56
- package/dist/types-C0PtaO7S.d.ts +0 -207
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import react__default, { ReactNode, FormEvent } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
5
|
-
|
|
6
|
-
type ResolveMediaUrl = (message: MessagePayload) => Promise<string | null>;
|
|
7
|
-
interface MediaRendererProps {
|
|
8
|
-
message: MessagePayload;
|
|
9
|
-
onLightbox: (src: string) => void;
|
|
10
|
-
onResolveUrl?: ResolveMediaUrl;
|
|
11
|
-
}
|
|
12
|
-
declare function MediaRenderer({ message, onLightbox, onResolveUrl }: MediaRendererProps): react.JSX.Element | null;
|
|
2
|
+
import react__default, { ReactNode, CSSProperties, UIEvent, FormEvent, RefObject } from 'react';
|
|
3
|
+
import { U as MessagePayload, a3 as ResolveMediaUrl, P as InteractiveSelection, V as MessageTranscription, N as InteractivePayload, x as ConversationsFeatures, u as ConversationSummary, j as ConversationChannel, Q as ListConversationsParams, w as ConversationsApi, a5 as SSEProvider, aa as TranscriptionMode, R as ListDocumentsParams, k as ConversationDocument, v as ConversationTemplate, f as ChannelFilter, g as ChannelFilterOption, q as ConversationSimulatorClient, s as ConversationSimulatorPanelLabels, Z as PreviewUploadedMedia } from './ConversationSimulatorPanel--5fIzXWY.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, h as CompanyDocument, i as CompanyDocumentPage, l as ConversationDocumentPage, m as ConversationEventSource, n as ConversationPage, y as ConversationsTheme, z as ConversationsUIConfig, E as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, F as DEFAULT_CONVERSATION_CHANNEL, H as DEFAULT_MAX_RECORDING_MILLISECONDS, J as FormatContactHandleParams, K as HANDLE_KIND, L as HandleKind, M as InteractiveOption, O as InteractiveSection, S as MediaRenderer, T as MediaRendererProps, a1 as REOPEN_MECHANISM, a2 as ReopenMechanism, a7 as SendSimulatorMediaParams, a8 as SimulatorMediaKind, ab as TranscriptionStatus, ae as capabilitiesOf, af as channelFiltersFor, ag as contactFlag, ak as formatContactHandle } from './ConversationSimulatorPanel--5fIzXWY.js';
|
|
5
|
+
import '@adatechnology/meta-whatsapp-contracts/testing';
|
|
13
6
|
|
|
14
7
|
interface MessageBubbleProps {
|
|
15
8
|
message: MessagePayload;
|
|
@@ -19,16 +12,56 @@ interface MessageBubbleProps {
|
|
|
19
12
|
isSelecting?: boolean;
|
|
20
13
|
isSelected?: boolean;
|
|
21
14
|
onToggleSelect?: () => void;
|
|
15
|
+
/**
|
|
16
|
+
* Como buscar a mídia da mensagem. Ausente, o balão usa o `ConversationsApi` do
|
|
17
|
+
* `ConversationsProvider` — passe apenas para sobrescrever (cache próprio, CDN, proxy do host).
|
|
18
|
+
*/
|
|
22
19
|
onResolveMediaUrl?: ResolveMediaUrl;
|
|
20
|
+
/**
|
|
21
|
+
* Toque numa opção do menu interativo. Ausente, as opções aparecem desabilitadas — que é o certo
|
|
22
|
+
* no histórico da inbox: o operador vê o que foi oferecido, sem responder no lugar do cliente.
|
|
23
|
+
*/
|
|
24
|
+
onInteractiveSelect?: (selection: InteractiveSelection) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Pede a transcrição do áudio. Ausente, o balão usa `transcribeAudio` do `ConversationsApi` do
|
|
27
|
+
* contexto — passe apenas para sobrescrever. Sem nenhum dos dois, o bloco de transcrição só exibe
|
|
28
|
+
* o que já veio pronto do backend.
|
|
29
|
+
*/
|
|
30
|
+
onTranscribeAudio?: (messageId: string) => Promise<MessageTranscription | void>;
|
|
23
31
|
className?: string;
|
|
24
32
|
}
|
|
25
|
-
declare function MessageBubble({ message, isMine, senderName, isFirstInGroup, isSelecting, isSelected, onToggleSelect, onResolveMediaUrl, className, }: MessageBubbleProps): react.JSX.Element;
|
|
33
|
+
declare function MessageBubble({ message, isMine, senderName, isFirstInGroup, isSelecting, isSelected, onToggleSelect, onResolveMediaUrl, onInteractiveSelect, onTranscribeAudio, className, }: MessageBubbleProps): react.JSX.Element;
|
|
34
|
+
|
|
35
|
+
interface InteractiveMessageLabels {
|
|
36
|
+
/** Fallback do rótulo do botão que abre a lista, quando o payload não traz um. */
|
|
37
|
+
openList: string;
|
|
38
|
+
}
|
|
39
|
+
declare const DEFAULT_INTERACTIVE_MESSAGE_LABELS: InteractiveMessageLabels;
|
|
40
|
+
interface InteractiveMessageProps {
|
|
41
|
+
payload: InteractivePayload;
|
|
42
|
+
onSelect?: (selection: InteractiveSelection) => void;
|
|
43
|
+
labels?: Partial<InteractiveMessageLabels>;
|
|
44
|
+
className?: string;
|
|
45
|
+
}
|
|
46
|
+
declare function InteractiveMessage({ payload, onSelect, labels, className }: InteractiveMessageProps): react.JSX.Element;
|
|
26
47
|
|
|
27
48
|
interface ConversationWallpaperProps {
|
|
28
49
|
children?: ReactNode;
|
|
29
50
|
className?: string;
|
|
51
|
+
/** Ajusta ou substitui o fundo padrão — para produto com identidade visual própria. */
|
|
52
|
+
style?: CSSProperties;
|
|
53
|
+
/**
|
|
54
|
+
* Rolagem da área de mensagens. Par do `ref`: é este elemento que rola, então é aqui que
|
|
55
|
+
* `useScrollToLatestMessage` observa a posição para saber se o operador está acompanhando o fim.
|
|
56
|
+
*/
|
|
57
|
+
onScroll?: (event: UIEvent<HTMLDivElement>) => void;
|
|
30
58
|
}
|
|
31
|
-
|
|
59
|
+
/**
|
|
60
|
+
* `forwardRef` porque quem controla a rolagem é de fora — o hook precisa do elemento para saltar
|
|
61
|
+
* até a última mensagem. `forwardRef` e não `ref` como prop: o pacote suporta React 18, onde
|
|
62
|
+
* ref-como-prop ainda não existe.
|
|
63
|
+
*/
|
|
64
|
+
declare const ConversationWallpaper: react.ForwardRefExoticComponent<ConversationWallpaperProps & react.RefAttributes<HTMLDivElement>>;
|
|
32
65
|
|
|
33
66
|
interface ConversationLocales {
|
|
34
67
|
bubble: {
|
|
@@ -52,6 +85,20 @@ interface ConversationLocales {
|
|
|
52
85
|
untitledDocument: string;
|
|
53
86
|
downloadFile: string;
|
|
54
87
|
};
|
|
88
|
+
transcription: {
|
|
89
|
+
label: string;
|
|
90
|
+
copy: string;
|
|
91
|
+
copied: string;
|
|
92
|
+
transcribe: string;
|
|
93
|
+
transcribing: string;
|
|
94
|
+
retry: string;
|
|
95
|
+
failed: string;
|
|
96
|
+
/** Áudio processado sem fala detectada — distinto de "não transcrito". */
|
|
97
|
+
empty: string;
|
|
98
|
+
unsupported: string;
|
|
99
|
+
showMore: string;
|
|
100
|
+
showLess: string;
|
|
101
|
+
};
|
|
55
102
|
selection: {
|
|
56
103
|
select: string;
|
|
57
104
|
};
|
|
@@ -76,13 +123,97 @@ interface AudioPlayerProps {
|
|
|
76
123
|
}
|
|
77
124
|
declare function AudioPlayer({ src, isMine }: AudioPlayerProps): react.JSX.Element;
|
|
78
125
|
|
|
126
|
+
interface AudioTranscriptionProps {
|
|
127
|
+
transcription?: MessageTranscription | null;
|
|
128
|
+
/**
|
|
129
|
+
* Pede a transcrição ao backend. Ausente, o bloco nunca oferece o botão — um host em modo
|
|
130
|
+
* automático não tem rota para isso, e desenhar um botão que estoura no clique é pior do que não
|
|
131
|
+
* desenhar nada.
|
|
132
|
+
*
|
|
133
|
+
* O que for devolvido é exibido na hora. Sem isso o texto só apareceria no próximo refetch da
|
|
134
|
+
* lista, e transcrição não emite evento de tempo real: o operador clicaria, veria o spinner
|
|
135
|
+
* terminar e continuaria sem o texto na tela.
|
|
136
|
+
*/
|
|
137
|
+
onTranscribe?: () => Promise<MessageTranscription | void>;
|
|
138
|
+
isMine?: boolean;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Transcrição sob a nota de voz, com botão de copiar.
|
|
142
|
+
*
|
|
143
|
+
* Copiar é o motivo de o bloco existir: o operador cola o pedido do cliente no sistema interno, no
|
|
144
|
+
* orçamento, na busca. Seleção manual de texto dentro de um balão de chat é exatamente onde o
|
|
145
|
+
* arrasto do mouse falha — pega o balão vizinho, o horário, o nome do remetente.
|
|
146
|
+
*
|
|
147
|
+
* `navigator.clipboard.writeText` com try/catch silencioso, mesmo padrão de `MessageText.tsx`: a API
|
|
148
|
+
* exige contexto seguro e permissão, e um host servindo em HTTP simples não a tem. Falhar sem alarme
|
|
149
|
+
* é o certo — o texto continua na tela para seleção manual.
|
|
150
|
+
*/
|
|
151
|
+
declare function AudioTranscription({ transcription, onTranscribe, isMine }: AudioTranscriptionProps): react.JSX.Element | null;
|
|
152
|
+
|
|
153
|
+
interface EmojiPickerLabels {
|
|
154
|
+
search: string;
|
|
155
|
+
noResults: string;
|
|
156
|
+
}
|
|
157
|
+
declare const DEFAULT_EMOJI_PICKER_LABELS: EmojiPickerLabels;
|
|
79
158
|
interface EmojiPickerProps {
|
|
80
159
|
onSelect: (emoji: string) => void;
|
|
160
|
+
labels?: Partial<EmojiPickerLabels>;
|
|
81
161
|
className?: string;
|
|
82
162
|
}
|
|
83
|
-
declare const EmojiPicker: ({ onSelect, className }: EmojiPickerProps) => react.JSX.Element;
|
|
163
|
+
declare const EmojiPicker: ({ onSelect, labels, className }: EmojiPickerProps) => react.JSX.Element;
|
|
84
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Catálogo de emojis com palavras-chave em português, usado pela busca do seletor.
|
|
167
|
+
*
|
|
168
|
+
* Fica em módulo próprio (e não dentro do `EmojiPicker`) porque a busca precisa varrer TODAS as
|
|
169
|
+
* categorias, não só a aberta — o índice é montado uma vez, no carregamento, e não a cada tecla.
|
|
170
|
+
*
|
|
171
|
+
* As palavras-chave são de busca, não legendas: incluem sinônimos e formas sem acento, porque quem
|
|
172
|
+
* digita rápido escreve "coracao" e "polegar" com a mesma frequência que a forma correta.
|
|
173
|
+
*/
|
|
174
|
+
type EmojiEntry = {
|
|
175
|
+
readonly emoji: string;
|
|
176
|
+
readonly keywords: readonly string[];
|
|
177
|
+
};
|
|
178
|
+
type EmojiCategory = {
|
|
179
|
+
readonly name: string;
|
|
180
|
+
readonly entries: readonly EmojiEntry[];
|
|
181
|
+
};
|
|
182
|
+
declare const EMOJI_CATEGORIES: readonly EmojiCategory[];
|
|
183
|
+
/**
|
|
184
|
+
* Busca por prefixo de palavra-chave, não por substring: "ca" traz "casa" e "cartão", mas "sa" não
|
|
185
|
+
* traz "casa" — casar no meio da palavra devolvia resultado que ninguém consegue explicar.
|
|
186
|
+
*/
|
|
187
|
+
declare function searchEmojis(query: string): readonly EmojiEntry[];
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Mensagem pronta que o atendente cola no campo com um clique.
|
|
191
|
+
*
|
|
192
|
+
* `text` aceita string com `{{variavel}}` ou função: a string cobre o caso comum (copy fixa com o
|
|
193
|
+
* nome do cliente no meio) sem o host escrever código, e a função cobre o que precisa de lógica —
|
|
194
|
+
* escolher texto por produto, pluralizar, formatar moeda.
|
|
195
|
+
*/
|
|
196
|
+
interface QuickReply {
|
|
197
|
+
key: string;
|
|
198
|
+
/** O que aparece no chip, emoji incluso: `👋 Saudação`. */
|
|
199
|
+
label: string;
|
|
200
|
+
text: string | ((variables: Readonly<Record<string, string>>) => string);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Troca `{{nome}}` pelos valores passados. Variável ausente vira string vazia, e não o literal
|
|
204
|
+
* `{{nome}}`: mandar "Olá {{nome}}!" para o cliente é pior que mandar "Olá !".
|
|
205
|
+
*/
|
|
206
|
+
declare function applyQuickReplyVariables(template: string, variables?: Readonly<Record<string, string>>): string;
|
|
207
|
+
declare function resolveQuickReply(quickReply: QuickReply, variables?: Readonly<Record<string, string>>): string;
|
|
208
|
+
interface MessageComposerLabels {
|
|
209
|
+
emoji: string;
|
|
210
|
+
attach: string;
|
|
211
|
+
send: string;
|
|
212
|
+
removeAttachment: string;
|
|
213
|
+
}
|
|
214
|
+
declare const DEFAULT_MESSAGE_COMPOSER_LABELS: MessageComposerLabels;
|
|
85
215
|
interface MessageComposerProps {
|
|
216
|
+
labels?: Partial<MessageComposerLabels>;
|
|
86
217
|
onSend: (text: string) => void;
|
|
87
218
|
onAttach?: (file: File) => void;
|
|
88
219
|
value?: string;
|
|
@@ -92,16 +223,130 @@ interface MessageComposerProps {
|
|
|
92
223
|
maxLength?: number;
|
|
93
224
|
disabled?: boolean;
|
|
94
225
|
acceptedFileTypes?: string;
|
|
226
|
+
/**
|
|
227
|
+
* Ocupa o lugar do botão de enviar enquanto não há nada para enviar — é onde o WhatsApp põe o
|
|
228
|
+
* microfone. Fora do campo, o botão vira um bloco solto ao lado do pill e quebra a barra.
|
|
229
|
+
*/
|
|
230
|
+
idleAction?: ReactNode;
|
|
231
|
+
/** Mensagens prontas exibidas acima do campo. Vazio ou ausente, a faixa não é renderizada. */
|
|
232
|
+
quickReplies?: readonly QuickReply[];
|
|
233
|
+
/** Valores para `{{variavel}}` — tipicamente nome do cliente, produto, protocolo. */
|
|
234
|
+
quickReplyVariables?: Readonly<Record<string, string>>;
|
|
95
235
|
className?: string;
|
|
96
236
|
classNames?: Partial<MessageComposerClassNames>;
|
|
97
237
|
}
|
|
98
238
|
interface MessageComposerClassNames {
|
|
99
239
|
root: string;
|
|
240
|
+
quickReplies: string;
|
|
241
|
+
quickReply: string;
|
|
100
242
|
field: string;
|
|
101
243
|
}
|
|
102
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Exatamente o que a Meta aceita em mensagem de mídia — imagem, sticker, áudio, vídeo e a lista
|
|
246
|
+
* fechada de documentos. Oferecer no seletor um formato que o WhatsApp recusa (`.zip`, `.rtf`)
|
|
247
|
+
* empurra a falha para depois do envio, quando já não dá para explicar ao operador o que houve.
|
|
248
|
+
* Produto com regra própria passa `acceptedFileTypes`.
|
|
249
|
+
*/
|
|
250
|
+
declare const DEFAULT_ACCEPTED_FILE_TYPES: string;
|
|
251
|
+
declare const MessageComposer: ({ onSend, onAttach, value: externalValue, onChange: externalOnChange, quickReplies, quickReplyVariables, features, placeholder, maxLength, disabled, acceptedFileTypes, idleAction, className, classNames, labels, }: MessageComposerProps) => react.JSX.Element;
|
|
252
|
+
|
|
253
|
+
/** Cada ação que a barra sabe oferecer. `toolbar` decide quais delas aparecem. */
|
|
254
|
+
declare const RICH_COMPOSER_ACTION: {
|
|
255
|
+
readonly BOLD: "bold";
|
|
256
|
+
readonly ITALIC: "italic";
|
|
257
|
+
readonly STRIKETHROUGH: "strikethrough";
|
|
258
|
+
readonly MONOSPACE: "monospace";
|
|
259
|
+
readonly EMOJI: "emoji";
|
|
260
|
+
readonly ATTACH: "attach";
|
|
261
|
+
readonly VARIABLES: "variables";
|
|
262
|
+
};
|
|
263
|
+
type RichComposerAction = (typeof RICH_COMPOSER_ACTION)[keyof typeof RICH_COMPOSER_ACTION];
|
|
264
|
+
/** Texto das dicas. O host troca só o que quiser; o resto fica no padrão. */
|
|
265
|
+
interface RichComposerTooltips {
|
|
266
|
+
bold: string;
|
|
267
|
+
italic: string;
|
|
268
|
+
strikethrough: string;
|
|
269
|
+
monospace: string;
|
|
270
|
+
emoji: string;
|
|
271
|
+
attach: string;
|
|
272
|
+
send: string;
|
|
273
|
+
variables: string;
|
|
274
|
+
/** Botão que abre a formatação recolhida, quando a barra está estreita demais para os quatro. */
|
|
275
|
+
formatting: string;
|
|
276
|
+
}
|
|
277
|
+
declare const DEFAULT_RICH_COMPOSER_TOOLTIPS: RichComposerTooltips;
|
|
278
|
+
/**
|
|
279
|
+
* Um valor que o operador pode inserir no texto sem digitar. `value` é o que entra no campo — pode
|
|
280
|
+
* ser o dado já resolvido ("Anderson") ou um marcador a resolver depois ("{{nome}}"): quem decide é
|
|
281
|
+
* o host, porque só ele sabe se a substituição acontece aqui ou no envio.
|
|
282
|
+
*/
|
|
283
|
+
interface RichComposerVariable {
|
|
284
|
+
id: string;
|
|
285
|
+
label: string;
|
|
286
|
+
value: string;
|
|
287
|
+
/** Prévia do que será inserido, para o operador conferir antes de tocar. */
|
|
288
|
+
tooltip?: string;
|
|
289
|
+
}
|
|
290
|
+
/** Uma resposta rápida: o que o operador lê no chip e o que cai no campo ao tocar nele. */
|
|
291
|
+
interface RichComposerQuickReply {
|
|
292
|
+
id: string;
|
|
293
|
+
label: string;
|
|
294
|
+
text: string;
|
|
295
|
+
/** Dica ao passar o mouse. Sem isto, o chip não tem `title` — rótulo curto já se explica. */
|
|
296
|
+
tooltip?: string;
|
|
297
|
+
}
|
|
298
|
+
/** Handle imperativo: `contentEditable` controlado pelo React perde o cursor a cada tecla. */
|
|
299
|
+
interface RichMessageComposerHandle {
|
|
300
|
+
setContent: (text: string) => void;
|
|
301
|
+
clear: () => void;
|
|
302
|
+
focus: () => void;
|
|
303
|
+
}
|
|
304
|
+
interface RichMessageComposerProps {
|
|
305
|
+
/** Texto na notação do WhatsApp (`*negrito*`), não HTML. */
|
|
306
|
+
value: string;
|
|
307
|
+
onChange: (value: string) => void;
|
|
308
|
+
onSend: () => void;
|
|
309
|
+
onAttachFiles?: (files: FileList) => void;
|
|
310
|
+
quickReplies?: RichComposerQuickReply[];
|
|
311
|
+
/** Variáveis que o operador pode inserir no texto. Vazio ou ausente, o botão não aparece. */
|
|
312
|
+
variables?: RichComposerVariable[];
|
|
313
|
+
/**
|
|
314
|
+
* Quais ações aparecem. Ausente, todas aparecem — menos as que não têm como funcionar (anexo sem
|
|
315
|
+
* `onAttachFiles` some sozinho, porque botão que não faz nada é pior que botão nenhum).
|
|
316
|
+
*/
|
|
317
|
+
toolbar?: Partial<Record<RichComposerAction, boolean>>;
|
|
318
|
+
tooltips?: Partial<RichComposerTooltips>;
|
|
319
|
+
placeholder?: string;
|
|
320
|
+
disabled?: boolean;
|
|
321
|
+
isSending?: boolean;
|
|
322
|
+
/** Ocupa o lugar do enviar enquanto não há nada para enviar — onde o WhatsApp põe o microfone. */
|
|
323
|
+
idleAction?: ReactNode;
|
|
324
|
+
/** Prévia dos anexos já escolhidos, desenhada pelo host acima da barra. */
|
|
325
|
+
attachmentsPreview?: ReactNode;
|
|
326
|
+
/**
|
|
327
|
+
* Há anexo na fila esperando envio. O campo não conhece a fila — é do host — e sem essa dica ele
|
|
328
|
+
* concluía que não havia nada a enviar: com uma imagem anexada e nenhum texto, o botão de enviar
|
|
329
|
+
* dava lugar ao `idleAction` e a imagem ficava presa.
|
|
330
|
+
*/
|
|
331
|
+
hasQueuedAttachments?: boolean;
|
|
332
|
+
acceptedFileTypes?: string;
|
|
333
|
+
className?: string;
|
|
334
|
+
}
|
|
335
|
+
declare const RichMessageComposer: react.ForwardRefExoticComponent<RichMessageComposerProps & react.RefAttributes<RichMessageComposerHandle>>;
|
|
103
336
|
|
|
337
|
+
interface WhatsAppMessageEditorLabels {
|
|
338
|
+
bold: string;
|
|
339
|
+
/** Tooltip da negrito — traz a sintaxe do WhatsApp junto, por isso é separado do `aria-label`. */
|
|
340
|
+
boldHint: string;
|
|
341
|
+
italic: string;
|
|
342
|
+
italicHint: string;
|
|
343
|
+
strikethrough: string;
|
|
344
|
+
strikethroughHint: string;
|
|
345
|
+
insertPlaceholder: (token: string) => string;
|
|
346
|
+
}
|
|
347
|
+
declare const DEFAULT_WHATSAPP_MESSAGE_EDITOR_LABELS: WhatsAppMessageEditorLabels;
|
|
104
348
|
interface WhatsAppMessageEditorProps {
|
|
349
|
+
labels?: Partial<WhatsAppMessageEditorLabels>;
|
|
105
350
|
value: string;
|
|
106
351
|
onChange: (value: string) => void;
|
|
107
352
|
placeholder?: string;
|
|
@@ -110,7 +355,7 @@ interface WhatsAppMessageEditorProps {
|
|
|
110
355
|
previewLabel?: string;
|
|
111
356
|
emptyPreviewText?: string;
|
|
112
357
|
}
|
|
113
|
-
declare function WhatsAppMessageEditor({ value, onChange, placeholder, placeholders, rows, previewLabel, emptyPreviewText, }: WhatsAppMessageEditorProps): react.JSX.Element;
|
|
358
|
+
declare function WhatsAppMessageEditor({ value, onChange, placeholder, placeholders, rows, previewLabel, emptyPreviewText, labels, }: WhatsAppMessageEditorProps): react.JSX.Element;
|
|
114
359
|
|
|
115
360
|
interface SimpleEmojiPickerProps {
|
|
116
361
|
onSelect: (emoji: string) => void;
|
|
@@ -131,16 +376,30 @@ interface DateDividerProps {
|
|
|
131
376
|
}
|
|
132
377
|
declare function DateDivider({ iso, className, classNames }: DateDividerProps): react.JSX.Element;
|
|
133
378
|
|
|
379
|
+
interface AvatarLabels {
|
|
380
|
+
/** Lido por leitor de tela quando não há nome nem imagem — a silhueta genérica. */
|
|
381
|
+
unnamedContact: string;
|
|
382
|
+
}
|
|
134
383
|
interface AvatarProps {
|
|
135
384
|
name?: string | null;
|
|
136
385
|
avatarUrl?: string;
|
|
137
386
|
size?: 'sm' | 'md' | 'lg';
|
|
138
387
|
className?: string;
|
|
388
|
+
labels?: Partial<AvatarLabels>;
|
|
139
389
|
}
|
|
140
|
-
declare
|
|
390
|
+
declare const DEFAULT_AVATAR_LABELS: AvatarLabels;
|
|
391
|
+
declare function Avatar({ name, avatarUrl, size, className, labels }: AvatarProps): react.JSX.Element;
|
|
141
392
|
|
|
393
|
+
interface ConversationListItemLabels {
|
|
394
|
+
/** Tooltip do ponto vermelho: a janela de atendimento de 24h já fechou. */
|
|
395
|
+
expiredWindow: string;
|
|
396
|
+
/** Tooltip do ponto laranja: a janela de atendimento está perto de fechar. */
|
|
397
|
+
warningWindow: string;
|
|
398
|
+
}
|
|
399
|
+
declare const DEFAULT_CONVERSATION_LIST_ITEM_LABELS: ConversationListItemLabels;
|
|
142
400
|
interface ConversationListItemProps {
|
|
143
401
|
conversation: ConversationSummary;
|
|
402
|
+
labels?: Partial<ConversationListItemLabels>;
|
|
144
403
|
active?: boolean;
|
|
145
404
|
selected?: boolean;
|
|
146
405
|
onClick?: () => void;
|
|
@@ -157,7 +416,7 @@ interface ConversationListItemProps {
|
|
|
157
416
|
*/
|
|
158
417
|
highlightActive?: boolean;
|
|
159
418
|
}
|
|
160
|
-
declare const ConversationListItem: ({ conversation, active, selected, onClick, onSelect, showDivider, highlightActive, }: ConversationListItemProps) => react.JSX.Element;
|
|
419
|
+
declare const ConversationListItem: ({ conversation, active, selected, onClick, onSelect, showDivider, highlightActive, labels, }: ConversationListItemProps) => react.JSX.Element;
|
|
161
420
|
|
|
162
421
|
type ToastType = 'success' | 'error' | 'info';
|
|
163
422
|
interface ToastContextValue {
|
|
@@ -171,18 +430,41 @@ declare function ToastProvider({ children }: {
|
|
|
171
430
|
}): react.JSX.Element;
|
|
172
431
|
declare function useToast(): ToastContextValue;
|
|
173
432
|
|
|
433
|
+
/**
|
|
434
|
+
* Camada única de dicas do pacote.
|
|
435
|
+
*
|
|
436
|
+
* O `title` nativo existia em todos os botões e mesmo assim a dica "não aparecia": o navegador
|
|
437
|
+
* espera de 1 a 2 segundos antes de desenhar, o balão sai fora do tema e some ao menor movimento do
|
|
438
|
+
* mouse. Aqui a dica é do produto — aparece em 120ms, com o mesmo contraste do resto da interface.
|
|
439
|
+
*
|
|
440
|
+
* Um só ouvinte no documento atende a interface inteira: cada botão declara `data-cv-tooltip` e não
|
|
441
|
+
* precisa de estado, ref ou wrapper próprio. O balão é impresso em `document.body` via portal
|
|
442
|
+
* porque `overflow` de container (a régua de respostas rápidas rola na horizontal) recortaria um
|
|
443
|
+
* balão desenhado dentro do próprio botão.
|
|
444
|
+
*/
|
|
445
|
+
/** Atributo que marca um elemento como portador de dica. */
|
|
446
|
+
declare const TOOLTIP_ATTRIBUTE = "data-cv-tooltip";
|
|
447
|
+
declare function TooltipLayer(): react.ReactPortal | null;
|
|
448
|
+
|
|
174
449
|
interface StatusTicksProps {
|
|
175
450
|
status: string;
|
|
176
451
|
title?: string;
|
|
177
452
|
}
|
|
178
453
|
declare function StatusTicks({ status, title }: StatusTicksProps): react.JSX.Element;
|
|
179
454
|
|
|
455
|
+
interface LightboxLabels {
|
|
456
|
+
/** Texto alternativo quando a imagem não tem legenda — sem ele o leitor de tela anuncia a URL. */
|
|
457
|
+
imageAlt: string;
|
|
458
|
+
close: string;
|
|
459
|
+
}
|
|
180
460
|
interface LightboxProps {
|
|
181
461
|
imageUrl: string;
|
|
182
462
|
caption?: string;
|
|
183
463
|
onClose: () => void;
|
|
464
|
+
labels?: Partial<LightboxLabels>;
|
|
184
465
|
}
|
|
185
|
-
declare
|
|
466
|
+
declare const DEFAULT_LIGHTBOX_LABELS: LightboxLabels;
|
|
467
|
+
declare function Lightbox({ imageUrl, caption, onClose, labels }: LightboxProps): react.JSX.Element;
|
|
186
468
|
|
|
187
469
|
interface FileIconProps {
|
|
188
470
|
filename?: string;
|
|
@@ -229,23 +511,23 @@ type ConversationWindow = (typeof CONVERSATION_WINDOW)[keyof typeof CONVERSATION
|
|
|
229
511
|
declare const WINDOW_FILTERS: readonly [{
|
|
230
512
|
readonly value: "all";
|
|
231
513
|
readonly label: "Todas";
|
|
232
|
-
readonly
|
|
514
|
+
readonly tone: "";
|
|
233
515
|
}, {
|
|
234
516
|
readonly value: "fresh";
|
|
235
517
|
readonly label: "<12h";
|
|
236
|
-
readonly
|
|
518
|
+
readonly tone: "fresh";
|
|
237
519
|
}, {
|
|
238
520
|
readonly value: "warning";
|
|
239
521
|
readonly label: "12-21h";
|
|
240
|
-
readonly
|
|
522
|
+
readonly tone: "warning";
|
|
241
523
|
}, {
|
|
242
524
|
readonly value: "critical";
|
|
243
525
|
readonly label: "21-24h";
|
|
244
|
-
readonly
|
|
526
|
+
readonly tone: "critical";
|
|
245
527
|
}, {
|
|
246
528
|
readonly value: "expired";
|
|
247
529
|
readonly label: ">24h";
|
|
248
|
-
readonly
|
|
530
|
+
readonly tone: "expired";
|
|
249
531
|
}];
|
|
250
532
|
type WindowOfParams = {
|
|
251
533
|
readonly lastInboundAt: string | null;
|
|
@@ -314,6 +596,19 @@ interface ConversationHeaderClassNames {
|
|
|
314
596
|
desktopActions: string;
|
|
315
597
|
mobileMenu: string;
|
|
316
598
|
}
|
|
599
|
+
/**
|
|
600
|
+
* Utilitário extra que o host pendura no cabeçalho — ícone no desktop, item de menu no celular,
|
|
601
|
+
* como os nativos. Entra por aqui, e não por um slot de ReactNode, porque é isso que preserva o
|
|
602
|
+
* comportamento responsivo: um nó solto viraria um quarto ícone em 375px, sem área de toque.
|
|
603
|
+
*/
|
|
604
|
+
interface ConversationHeaderUtility {
|
|
605
|
+
key: string;
|
|
606
|
+
/** Ícone da biblioteca (lucide), no tamanho dos utilitários nativos: `<Play size={16} />`. */
|
|
607
|
+
icon: ReactNode;
|
|
608
|
+
label: string;
|
|
609
|
+
run: () => void;
|
|
610
|
+
active?: boolean;
|
|
611
|
+
}
|
|
317
612
|
interface ConversationHeaderProps {
|
|
318
613
|
conversation: ConversationSummary;
|
|
319
614
|
busy?: boolean;
|
|
@@ -323,29 +618,46 @@ interface ConversationHeaderProps {
|
|
|
323
618
|
onDownload?: () => void;
|
|
324
619
|
onOpenDocuments?: () => void;
|
|
325
620
|
documentsOpen?: boolean;
|
|
621
|
+
/** Ações do produto que não existem no contrato do pacote (ex.: ferramentas de dev). */
|
|
622
|
+
extraUtilities?: readonly ConversationHeaderUtility[];
|
|
326
623
|
onBack?: () => void;
|
|
327
624
|
labels?: Partial<ConversationHeaderLabels>;
|
|
328
625
|
className?: string;
|
|
329
626
|
classNames?: Partial<ConversationHeaderClassNames>;
|
|
330
627
|
}
|
|
331
|
-
declare function ConversationHeader({ conversation, busy, onTakeover, onReturnToBot, onFinish, onDownload, onOpenDocuments, documentsOpen, onBack, labels: labelsOverride, className, classNames, }: ConversationHeaderProps): react.JSX.Element;
|
|
628
|
+
declare function ConversationHeader({ conversation, busy, onTakeover, onReturnToBot, onFinish, onDownload, onOpenDocuments, documentsOpen, extraUtilities, onBack, labels: labelsOverride, className, classNames, }: ConversationHeaderProps): react.JSX.Element;
|
|
332
629
|
|
|
333
630
|
/**
|
|
334
631
|
* "Suas Seleções": o que o bot já coletou na conversa. Para o atendente que assume no meio, é a
|
|
335
|
-
* diferença entre ler o transcript inteiro e ver o estado
|
|
632
|
+
* diferença entre ler o transcript inteiro e ver o estado de relance.
|
|
336
633
|
*
|
|
337
634
|
* O pacote não interpreta o contexto — ele é `Record<string, unknown>` e cada produto nomeia as
|
|
338
635
|
* próprias chaves. O host traduz para `entries`; aqui só se decide como mostrar.
|
|
636
|
+
*
|
|
637
|
+
* Desenho em paridade com financiamento-imobiliario-bot/apps/web/src/components/SelectionsSummary.tsx:
|
|
638
|
+
* card com gradiente, badge de contagem, pills de status no cabeçalho e grid de cards com borda
|
|
639
|
+
* colorida por estado. O que ficou diferente de lá, e por quê, está comentado no ponto.
|
|
339
640
|
*/
|
|
641
|
+
/**
|
|
642
|
+
* `completed` tem valor, `pending` ainda não foi coletado, `editing` é o cliente refazendo a
|
|
643
|
+
* resposta. O host só precisa mandar `status` para o terceiro caso — os dois primeiros saem do
|
|
644
|
+
* próprio `value`, e exigir o campo quebraria quem já usa o painel.
|
|
645
|
+
*/
|
|
646
|
+
type ConversationContextStatus = 'completed' | 'pending' | 'editing';
|
|
340
647
|
interface ConversationContextEntry {
|
|
341
648
|
key: string;
|
|
342
649
|
label: string;
|
|
343
650
|
value?: string | undefined;
|
|
344
651
|
icon?: string;
|
|
652
|
+
status?: ConversationContextStatus;
|
|
345
653
|
}
|
|
346
654
|
interface ConversationContextPanelLabels {
|
|
347
655
|
title: string;
|
|
348
656
|
empty: string;
|
|
657
|
+
collapse: string;
|
|
658
|
+
expand: string;
|
|
659
|
+
/** Placeholder do card ainda não coletado. */
|
|
660
|
+
notCollected: string;
|
|
349
661
|
}
|
|
350
662
|
declare const DEFAULT_CONVERSATION_CONTEXT_LABELS: ConversationContextPanelLabels;
|
|
351
663
|
interface ConversationContextPanelClassNames {
|
|
@@ -356,11 +668,23 @@ interface ConversationContextPanelClassNames {
|
|
|
356
668
|
}
|
|
357
669
|
interface ConversationContextPanelProps {
|
|
358
670
|
entries: readonly ConversationContextEntry[];
|
|
671
|
+
/**
|
|
672
|
+
* Estado inicial. Ausente, abre sozinho no desktop quando há algum dado preenchido.
|
|
673
|
+
*
|
|
674
|
+
* Existe porque "abre sozinho" nem sempre é o que o produto quer: com 1 de 6 campos preenchidos o
|
|
675
|
+
* painel ocupa altura mostrando quase só pendências, e empurra a conversa — que é o que se veio ver.
|
|
676
|
+
*/
|
|
677
|
+
defaultOpen?: boolean;
|
|
678
|
+
/**
|
|
679
|
+
* Rótulo do fluxo em curso, exibido em azul ao lado do título — no financiamento é o produto
|
|
680
|
+
* escolhido ("Consórcio", "MCMV"). Ausente, o cabeçalho fica só com título e contagens.
|
|
681
|
+
*/
|
|
682
|
+
flowLabel?: string;
|
|
359
683
|
labels?: Partial<ConversationContextPanelLabels>;
|
|
360
684
|
className?: string;
|
|
361
685
|
classNames?: Partial<ConversationContextPanelClassNames>;
|
|
362
686
|
}
|
|
363
|
-
declare function ConversationContextPanel({ entries, labels: labelsOverride, className, classNames, }: ConversationContextPanelProps): react.JSX.Element;
|
|
687
|
+
declare function ConversationContextPanel({ entries, defaultOpen, flowLabel, labels: labelsOverride, className, classNames, }: ConversationContextPanelProps): react.JSX.Element;
|
|
364
688
|
|
|
365
689
|
interface WindowExpiredNoticeLabels {
|
|
366
690
|
title: string;
|
|
@@ -380,6 +704,217 @@ declare function WindowExpiredNotice({ onSendTemplate, disabled, labels: labelsO
|
|
|
380
704
|
*/
|
|
381
705
|
declare function isWindowBlocking(window: ConversationWindow): boolean;
|
|
382
706
|
|
|
707
|
+
/**
|
|
708
|
+
* Biblioteca de arquivos de TODAS as conversas — a tela de Documentos do painel, fora do
|
|
709
|
+
* atendimento.
|
|
710
|
+
*
|
|
711
|
+
* Distinta do `ConversationDocumentsPanel`: aquele parte de uma conversa aberta e vive dentro dela;
|
|
712
|
+
* esta varre a empresa e por isso mostra de qual conversa cada arquivo veio, com o número
|
|
713
|
+
* clicável. Sem essa referência, uma lista global de anexos não responde nenhuma pergunta.
|
|
714
|
+
*/
|
|
715
|
+
interface DocumentsLibraryLabels {
|
|
716
|
+
title: string;
|
|
717
|
+
searchPlaceholder: string;
|
|
718
|
+
empty: string;
|
|
719
|
+
noResults: string;
|
|
720
|
+
loading: string;
|
|
721
|
+
failure: string;
|
|
722
|
+
view: string;
|
|
723
|
+
download: string;
|
|
724
|
+
openConversation: string;
|
|
725
|
+
sourceFilterAll: string;
|
|
726
|
+
sourceFilterCustomer: string;
|
|
727
|
+
sourceFilterTeam: string;
|
|
728
|
+
sortMostRecent: string;
|
|
729
|
+
sortOldest: string;
|
|
730
|
+
clearFilters: string;
|
|
731
|
+
upload: string;
|
|
732
|
+
uploadError: string;
|
|
733
|
+
total: (count: number) => string;
|
|
734
|
+
page: (current: number, last: number) => string;
|
|
735
|
+
}
|
|
736
|
+
declare const DEFAULT_DOCUMENTS_LIBRARY_LABELS: DocumentsLibraryLabels;
|
|
737
|
+
interface DocumentsLibraryClassNames {
|
|
738
|
+
root: string;
|
|
739
|
+
title: string;
|
|
740
|
+
filters: string;
|
|
741
|
+
search: string;
|
|
742
|
+
sourceSelect: string;
|
|
743
|
+
sortButton: string;
|
|
744
|
+
clearButton: string;
|
|
745
|
+
status: string;
|
|
746
|
+
list: string;
|
|
747
|
+
item: string;
|
|
748
|
+
conversationLink: string;
|
|
749
|
+
filename: string;
|
|
750
|
+
meta: string;
|
|
751
|
+
pagination: string;
|
|
752
|
+
}
|
|
753
|
+
interface DocumentsLibraryProps {
|
|
754
|
+
/** Itens por página. O total vem do servidor. */
|
|
755
|
+
perPage?: number;
|
|
756
|
+
/** Abrir a conversa de origem. Ausente, o número aparece como texto e não como link. */
|
|
757
|
+
onOpenConversation?: (conversationId: string) => void;
|
|
758
|
+
labels?: Partial<DocumentsLibraryLabels>;
|
|
759
|
+
className?: string;
|
|
760
|
+
classNames?: Partial<DocumentsLibraryClassNames>;
|
|
761
|
+
}
|
|
762
|
+
declare function DocumentsLibrary({ perPage, onOpenConversation, labels: labelsOverride, className, classNames, }: DocumentsLibraryProps): react.JSX.Element | null;
|
|
763
|
+
|
|
764
|
+
type SortDirection = 'asc' | 'desc';
|
|
765
|
+
interface SortableHeadProps {
|
|
766
|
+
readonly label: string;
|
|
767
|
+
readonly field: string;
|
|
768
|
+
readonly activeField: string;
|
|
769
|
+
readonly direction: SortDirection;
|
|
770
|
+
readonly onSort: (field: string) => void;
|
|
771
|
+
readonly className?: string;
|
|
772
|
+
}
|
|
773
|
+
declare function SortableHead({ label, field, activeField, direction, onSort, className }: SortableHeadProps): react.JSX.Element;
|
|
774
|
+
interface FilterOption {
|
|
775
|
+
readonly value: string;
|
|
776
|
+
readonly label: string;
|
|
777
|
+
}
|
|
778
|
+
interface MultiSelectFilterProps {
|
|
779
|
+
readonly label: string;
|
|
780
|
+
readonly options: readonly FilterOption[];
|
|
781
|
+
readonly selected: readonly string[];
|
|
782
|
+
readonly onChange: (selected: readonly string[]) => void;
|
|
783
|
+
readonly className?: string;
|
|
784
|
+
}
|
|
785
|
+
declare function MultiSelectFilter({ label, options, selected, onChange, className }: MultiSelectFilterProps): react.JSX.Element;
|
|
786
|
+
interface BulkActionBarProps {
|
|
787
|
+
readonly selectedCount: number;
|
|
788
|
+
readonly selectedLabel: (count: number) => string;
|
|
789
|
+
readonly clearLabel: string;
|
|
790
|
+
readonly onClear: () => void;
|
|
791
|
+
readonly children?: ReactNode;
|
|
792
|
+
}
|
|
793
|
+
declare function BulkActionBar({ selectedCount, selectedLabel, clearLabel, onClear, children }: BulkActionBarProps): react.JSX.Element | null;
|
|
794
|
+
interface ListingPaginationProps {
|
|
795
|
+
readonly page: number;
|
|
796
|
+
readonly total: number;
|
|
797
|
+
readonly perPage: number;
|
|
798
|
+
readonly perPageOptions?: readonly number[];
|
|
799
|
+
readonly onPageChange: (page: number) => void;
|
|
800
|
+
readonly onPerPageChange?: (perPage: number) => void;
|
|
801
|
+
readonly labels: {
|
|
802
|
+
readonly show: string;
|
|
803
|
+
readonly perPage: string;
|
|
804
|
+
readonly total: (count: number) => string;
|
|
805
|
+
readonly page: (current: number, last: number) => string;
|
|
806
|
+
readonly previous: string;
|
|
807
|
+
readonly next: string;
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
declare function ListingPagination({ page, total, perPage, perPageOptions, onPageChange, onPerPageChange, labels, }: ListingPaginationProps): react.JSX.Element;
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Vocabulário da biblioteca de arquivos. Sobrescrevível campo a campo: o produto troca "cliente"
|
|
814
|
+
* por "lead" ou o idioma inteiro sem manter uma cópia da tela para isso.
|
|
815
|
+
*/
|
|
816
|
+
interface DocumentsWorkspaceLabels {
|
|
817
|
+
readonly title: string;
|
|
818
|
+
readonly subtitle: (total: number) => string;
|
|
819
|
+
readonly searchPlaceholder: string;
|
|
820
|
+
readonly empty: string;
|
|
821
|
+
readonly noResults: string;
|
|
822
|
+
readonly loading: string;
|
|
823
|
+
readonly failure: string;
|
|
824
|
+
readonly view: string;
|
|
825
|
+
readonly download: string;
|
|
826
|
+
readonly remove: string;
|
|
827
|
+
readonly removeConfirm: (filename: string) => string;
|
|
828
|
+
readonly openConversation: string;
|
|
829
|
+
readonly sourceFilter: string;
|
|
830
|
+
readonly categoryFilter: string;
|
|
831
|
+
readonly startDate: string;
|
|
832
|
+
readonly endDate: string;
|
|
833
|
+
readonly clearFilters: string;
|
|
834
|
+
readonly selectAllPage: string;
|
|
835
|
+
readonly selectRow: string;
|
|
836
|
+
readonly bulkSelected: (count: number) => string;
|
|
837
|
+
readonly bulkClear: string;
|
|
838
|
+
readonly bulkDownloadZip: string;
|
|
839
|
+
readonly bulkRemove: (count: number) => string;
|
|
840
|
+
readonly bulkRemoveConfirm: (count: number) => string;
|
|
841
|
+
readonly upload: string;
|
|
842
|
+
readonly uploadError: string;
|
|
843
|
+
readonly columnFilename: string;
|
|
844
|
+
readonly columnContact: string;
|
|
845
|
+
readonly columnType: string;
|
|
846
|
+
readonly columnSize: string;
|
|
847
|
+
readonly columnSource: string;
|
|
848
|
+
readonly columnDate: string;
|
|
849
|
+
readonly columnActions: string;
|
|
850
|
+
readonly sourceLabels: Readonly<Record<string, string>>;
|
|
851
|
+
readonly categoryLabels: Readonly<Record<string, string>>;
|
|
852
|
+
readonly show: string;
|
|
853
|
+
readonly perPage: string;
|
|
854
|
+
readonly total: (count: number) => string;
|
|
855
|
+
readonly page: (current: number, last: number) => string;
|
|
856
|
+
readonly previousPage: string;
|
|
857
|
+
readonly nextPage: string;
|
|
858
|
+
}
|
|
859
|
+
declare const DEFAULT_DOCUMENTS_WORKSPACE_LABELS: DocumentsWorkspaceLabels;
|
|
860
|
+
|
|
861
|
+
interface DocumentsWorkspaceClassNames {
|
|
862
|
+
root: string;
|
|
863
|
+
header: string;
|
|
864
|
+
filters: string;
|
|
865
|
+
table: string;
|
|
866
|
+
row: string;
|
|
867
|
+
status: string;
|
|
868
|
+
}
|
|
869
|
+
interface DocumentsWorkspaceProps {
|
|
870
|
+
readonly perPage?: number;
|
|
871
|
+
readonly perPageOptions?: readonly number[];
|
|
872
|
+
/** Abrir a conversa de origem. Ausente, o número aparece como texto e não como link. */
|
|
873
|
+
readonly onOpenConversation?: (conversationId: string) => void;
|
|
874
|
+
/** Origens filtráveis. Ausente, usa o vocabulário padrão (`customer`, `agent`, `bot`). */
|
|
875
|
+
readonly sources?: readonly FilterOption[];
|
|
876
|
+
/** Categorias de arquivo. Lista vazia esconde o filtro — nem todo host classifica anexo. */
|
|
877
|
+
readonly categories?: readonly FilterOption[];
|
|
878
|
+
/** Recorte por data. Desligado quando o backend não sabe filtrar por período. */
|
|
879
|
+
readonly dateFilter?: boolean;
|
|
880
|
+
/**
|
|
881
|
+
* Filtros do produto (cliente, unidade, campanha). Recebe os parâmetros extras atuais e devolve
|
|
882
|
+
* os controles; o que o produto guardar aqui viaja em `extra` para o `getAllDocuments`.
|
|
883
|
+
*/
|
|
884
|
+
readonly renderFilters?: (context: DocumentsFiltersContext) => ReactNode;
|
|
885
|
+
readonly labels?: Partial<DocumentsWorkspaceLabels>;
|
|
886
|
+
readonly className?: string;
|
|
887
|
+
readonly classNames?: Partial<DocumentsWorkspaceClassNames>;
|
|
888
|
+
/** Espelhar filtros e paginação na URL. Desligado em preview, onde não há rota de verdade. */
|
|
889
|
+
readonly syncUrl?: boolean;
|
|
890
|
+
}
|
|
891
|
+
interface DocumentsFiltersContext {
|
|
892
|
+
readonly extra: Readonly<Record<string, string | number>>;
|
|
893
|
+
readonly setExtra: (next: Readonly<Record<string, string | number>>) => void;
|
|
894
|
+
}
|
|
895
|
+
declare function DocumentsWorkspace({ perPage: initialPerPage, perPageOptions, onOpenConversation, sources, categories, dateFilter, renderFilters, labels: labelsOverride, className, classNames, syncUrl, }: DocumentsWorkspaceProps): react.JSX.Element | null;
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Estado de listagem espelhado na URL — ordenação, filtros e paginação (regra `web.md` §7).
|
|
899
|
+
*
|
|
900
|
+
* Escrito sobre `history.replaceState` e não sobre um router: o pacote roda em três produtos com
|
|
901
|
+
* routers diferentes, e exigir um deles arrastaria dependência de framework para dentro do módulo.
|
|
902
|
+
* `replaceState` também é o comportamento certo aqui — filtrar não é navegar, e cada tecla digitada
|
|
903
|
+
* na busca não deve virar uma entrada no botão "voltar".
|
|
904
|
+
*/
|
|
905
|
+
/** `enabled: false` mantém o mesmo contrato de estado sem tocar na URL — para uso em preview e teste. */
|
|
906
|
+
interface UrlStateOptions {
|
|
907
|
+
readonly enabled?: boolean;
|
|
908
|
+
}
|
|
909
|
+
declare function useUrlStringState(key: string, initial: string, { enabled }?: UrlStateOptions): [string, (next: string) => void];
|
|
910
|
+
declare function useUrlNumberState(key: string, initial: number, { enabled }?: UrlStateOptions): [number, (next: number) => void];
|
|
911
|
+
declare function useUrlArrayState(key: string, { enabled }?: UrlStateOptions): [readonly string[], (next: readonly string[]) => void];
|
|
912
|
+
/**
|
|
913
|
+
* Espera o usuário parar de digitar antes de deixar o valor chegar à query. Sem isto, cada tecla
|
|
914
|
+
* na busca vira uma chamada de rede e uma reescrita de URL.
|
|
915
|
+
*/
|
|
916
|
+
declare function useDebouncedValue<TValue>(value: TValue, delayMs?: number): TValue;
|
|
917
|
+
|
|
383
918
|
/**
|
|
384
919
|
* Arquivos trocados na conversa. Sai do transcript e vira lista própria porque anexo é o que o
|
|
385
920
|
* atendente mais precisa reencontrar depois — rolar meses de mensagens para achar um comprovante é
|
|
@@ -388,29 +923,76 @@ declare function isWindowBlocking(window: ConversationWindow): boolean;
|
|
|
388
923
|
* Usa `useConversationDocuments`, então funciona com qualquer `ConversationsApi`. Host sem
|
|
389
924
|
* biblioteca de documentos cai no estado vazio, sem quebrar.
|
|
390
925
|
*/
|
|
926
|
+
/** Origens que o filtro oferece. `all` não é origem — é a ausência de filtro. */
|
|
927
|
+
declare const DOCUMENT_SOURCE_FILTER: {
|
|
928
|
+
readonly ALL: "all";
|
|
929
|
+
readonly CUSTOMER: "customer";
|
|
930
|
+
readonly TEAM: "team";
|
|
931
|
+
};
|
|
932
|
+
type DocumentSourceFilter = (typeof DOCUMENT_SOURCE_FILTER)[keyof typeof DOCUMENT_SOURCE_FILTER];
|
|
391
933
|
interface ConversationDocumentsPanelLabels {
|
|
392
934
|
toggle: string;
|
|
393
935
|
title: string;
|
|
394
936
|
searchPlaceholder: string;
|
|
395
937
|
empty: string;
|
|
938
|
+
/** Distinto de `empty`: sem resultado POR CAUSA do filtro, e não conversa sem anexo nenhum. */
|
|
939
|
+
noResults: string;
|
|
396
940
|
loading: string;
|
|
397
941
|
failure: string;
|
|
398
942
|
download: string;
|
|
943
|
+
view: string;
|
|
944
|
+
sourceFilterAll: string;
|
|
945
|
+
sourceFilterCustomer: string;
|
|
946
|
+
sourceFilterTeam: string;
|
|
947
|
+
sortMostRecent: string;
|
|
948
|
+
sortOldest: string;
|
|
949
|
+
clearFilters: string;
|
|
950
|
+
selectAll: string;
|
|
951
|
+
downloadSelected: (count: number) => string;
|
|
952
|
+
archiveFailed: string;
|
|
953
|
+
total: (count: number) => string;
|
|
954
|
+
page: (current: number, last: number) => string;
|
|
399
955
|
}
|
|
400
956
|
declare const DEFAULT_CONVERSATION_DOCUMENTS_LABELS: ConversationDocumentsPanelLabels;
|
|
957
|
+
/**
|
|
958
|
+
* Partes estilizáveis do painel, no mesmo contrato do `ConversationHeader`: `cn` funde por cima da
|
|
959
|
+
* base e conflito de utilitário (padding, tamanho de fonte, borda) fica com o valor do produto.
|
|
960
|
+
*
|
|
961
|
+
* `status` cobre carregando/erro/vazio de uma vez — são a mesma linha de texto auxiliar, e slots
|
|
962
|
+
* separados só multiplicariam chave para quem quer mudar a cor de aviso.
|
|
963
|
+
*/
|
|
401
964
|
interface ConversationDocumentsPanelClassNames {
|
|
402
965
|
root: string;
|
|
403
966
|
body: string;
|
|
967
|
+
title: string;
|
|
968
|
+
filters: string;
|
|
969
|
+
search: string;
|
|
970
|
+
sourceSelect: string;
|
|
971
|
+
sortButton: string;
|
|
972
|
+
clearButton: string;
|
|
973
|
+
status: string;
|
|
974
|
+
list: string;
|
|
975
|
+
item: string;
|
|
976
|
+
sourceBadge: string;
|
|
977
|
+
filename: string;
|
|
978
|
+
meta: string;
|
|
979
|
+
viewButton: string;
|
|
980
|
+
downloadButton: string;
|
|
981
|
+
pagination: string;
|
|
982
|
+
selectionBar: string;
|
|
983
|
+
checkbox: string;
|
|
404
984
|
}
|
|
405
985
|
interface ConversationDocumentsPanelProps {
|
|
406
986
|
conversationId: string;
|
|
407
987
|
/** Controlado de fora porque o gatilho vive no cabeçalho, junto das outras ações da conversa. */
|
|
408
988
|
open: boolean;
|
|
989
|
+
/** Itens por página. O total vem do servidor; sem paginação no host, a barra não aparece. */
|
|
990
|
+
perPage?: number;
|
|
409
991
|
labels?: Partial<ConversationDocumentsPanelLabels>;
|
|
410
992
|
className?: string;
|
|
411
993
|
classNames?: Partial<ConversationDocumentsPanelClassNames>;
|
|
412
994
|
}
|
|
413
|
-
declare function ConversationDocumentsPanel({ conversationId, open, labels: labelsOverride, className, classNames, }: ConversationDocumentsPanelProps): react.JSX.Element | null;
|
|
995
|
+
declare function ConversationDocumentsPanel({ conversationId, open, perPage, labels: labelsOverride, className, classNames, }: ConversationDocumentsPanelProps): react.JSX.Element | null;
|
|
414
996
|
|
|
415
997
|
/**
|
|
416
998
|
* Serialização do transcript para download. Fica no pacote porque o formato de um histórico de
|
|
@@ -451,6 +1033,49 @@ declare function useDarkMode(): {
|
|
|
451
1033
|
toggle: () => void;
|
|
452
1034
|
};
|
|
453
1035
|
|
|
1036
|
+
interface DarkModeToggleLabels {
|
|
1037
|
+
/** Ação de ir para o tema escuro — é o rótulo lido enquanto o tema claro está ativo. */
|
|
1038
|
+
toDark: string;
|
|
1039
|
+
toLight: string;
|
|
1040
|
+
}
|
|
1041
|
+
interface DarkModeToggleClassNames {
|
|
1042
|
+
/** O tamanho do ícone vem daqui quando o host usa a própria escala (`size-4`, `size-5`). */
|
|
1043
|
+
icon?: string;
|
|
1044
|
+
label?: string;
|
|
1045
|
+
}
|
|
1046
|
+
interface DarkModeToggleProps {
|
|
1047
|
+
isDark: boolean;
|
|
1048
|
+
onToggle: () => void;
|
|
1049
|
+
/** Rótulo ao lado do ícone. Só-ícone exige a área de toque mínima, que a classe `cv-touch` dá. */
|
|
1050
|
+
showLabel?: boolean;
|
|
1051
|
+
labels?: Partial<DarkModeToggleLabels>;
|
|
1052
|
+
/**
|
|
1053
|
+
* Substitui a aparência do botão. As classes passam por `tailwind-merge`, então utilitário do
|
|
1054
|
+
* host vence o do pacote em vez de somar: `px-4` descarta o `px-3` daqui.
|
|
1055
|
+
*
|
|
1056
|
+
* A exceção é utilitário de token do host (`rounded-panel`), que o merge não reconhece como
|
|
1057
|
+
* família e deixa passar junto com o `rounded-lg` do pacote — aí quem decide é a ordem no CSS
|
|
1058
|
+
* gerado, não a do atributo. Nesse caso use a forma arbitrária (`rounded-[var(--radius-panel)]`),
|
|
1059
|
+
* que o merge entende e substitui.
|
|
1060
|
+
*/
|
|
1061
|
+
className?: string;
|
|
1062
|
+
classNames?: DarkModeToggleClassNames;
|
|
1063
|
+
}
|
|
1064
|
+
declare const DEFAULT_DARK_MODE_TOGGLE_LABELS: DarkModeToggleLabels;
|
|
1065
|
+
/**
|
|
1066
|
+
* Interruptor de tema, apresentacional: o estado vem do host, junto com `useDarkMode`.
|
|
1067
|
+
*
|
|
1068
|
+
* Ele não monta o hook de propósito. `useDarkMode` é o controlador — escreve a classe `dark` no
|
|
1069
|
+
* `<html>` e persiste a escolha —, e uma barra lateral que também existe como gaveta fica montada
|
|
1070
|
+
* duas vezes na mesma tela. Dois controladores guardam estados iniciais separados, e o segundo
|
|
1071
|
+
* botão passa a mostrar o ícone contrário ao tema que está no ar. Com o estado no host, o hook é
|
|
1072
|
+
* chamado uma vez acima dos dois e as duas cópias concordam.
|
|
1073
|
+
*
|
|
1074
|
+
* O rótulo nomeia o destino, não o estado atual: no claro se lê "Tema escuro", que é o que o clique
|
|
1075
|
+
* faz. `aria-pressed` diria o oposto na mesma frase, então fica de fora.
|
|
1076
|
+
*/
|
|
1077
|
+
declare function DarkModeToggle({ isDark, onToggle, showLabel, labels, className, classNames, }: DarkModeToggleProps): react.JSX.Element;
|
|
1078
|
+
|
|
454
1079
|
/**
|
|
455
1080
|
* Detecta tela estreita para decisões que CSS não resolve — como abrir ou não um painel por padrão.
|
|
456
1081
|
*
|
|
@@ -461,11 +1086,35 @@ declare function useDarkMode(): {
|
|
|
461
1086
|
declare const NARROW_MAX_WIDTH_PX = 1023;
|
|
462
1087
|
declare function useIsNarrow(): boolean;
|
|
463
1088
|
|
|
1089
|
+
interface UseWaitingNotificationsLabels {
|
|
1090
|
+
/** Título da notificação do sistema. Recebe a conversa para o host escolher nome × número. */
|
|
1091
|
+
title: (conversation: ConversationSummary) => string;
|
|
1092
|
+
body: (conversation: ConversationSummary) => string;
|
|
1093
|
+
}
|
|
1094
|
+
interface UseWaitingNotificationsParams {
|
|
1095
|
+
/**
|
|
1096
|
+
* Repassado cru ao `fetchConversations`. É o que permite filtrar não lidas **no servidor** em
|
|
1097
|
+
* vez de baixar a lista inteira e contar no cliente: um painel com milhares de conversas não
|
|
1098
|
+
* pode paginar 50 por vez atrás de quem tem `unread > 0`.
|
|
1099
|
+
*/
|
|
1100
|
+
readonly params?: ListConversationsParams;
|
|
1101
|
+
readonly intervalMs?: number;
|
|
1102
|
+
/** Desliga o polling sem desmontar quem chama — útil com a aba em segundo plano. */
|
|
1103
|
+
readonly enabled?: boolean;
|
|
1104
|
+
readonly icon?: string;
|
|
1105
|
+
readonly labels?: Partial<UseWaitingNotificationsLabels>;
|
|
1106
|
+
}
|
|
464
1107
|
interface UseWaitingNotificationsResult {
|
|
465
1108
|
unreadCount: number;
|
|
466
1109
|
conversations: ConversationSummary[];
|
|
1110
|
+
/**
|
|
1111
|
+
* Releitura sob demanda. Existe porque o polling é o piso, não o mecanismo: quem já recebe SSE
|
|
1112
|
+
* ou acabou de marcar tudo como lido sabe da mudança antes do próximo tick, e esperar 10s para
|
|
1113
|
+
* o contador acompanhar faz a interface parecer travada.
|
|
1114
|
+
*/
|
|
1115
|
+
refresh: () => Promise<void>;
|
|
467
1116
|
}
|
|
468
|
-
declare function useWaitingNotifications(): UseWaitingNotificationsResult;
|
|
1117
|
+
declare function useWaitingNotifications(params?: UseWaitingNotificationsParams): UseWaitingNotificationsResult;
|
|
469
1118
|
|
|
470
1119
|
interface ConversationsContextValue {
|
|
471
1120
|
api: ConversationsApi;
|
|
@@ -547,6 +1196,7 @@ interface WhatsAppCreateTemplateFormLabels {
|
|
|
547
1196
|
sectionDescription: string;
|
|
548
1197
|
nameLabel: string;
|
|
549
1198
|
nameHint: string;
|
|
1199
|
+
namePlaceholder: string;
|
|
550
1200
|
categoryLabel: string;
|
|
551
1201
|
languageLabel: string;
|
|
552
1202
|
headerLabel: string;
|
|
@@ -612,6 +1262,47 @@ interface WelcomeFarewellFormProps {
|
|
|
612
1262
|
}
|
|
613
1263
|
declare function WelcomeFarewellForm({ welcomeMessage, onWelcomeMessageChange, farewellMessage, onFarewellMessageChange, onSave, saving, saveSuccess, welcomePlaceholders, farewellPlaceholders, labels: labelsOverride, }: WelcomeFarewellFormProps): react.JSX.Element;
|
|
614
1264
|
|
|
1265
|
+
interface TranscriptionSettingsFormLabels {
|
|
1266
|
+
sectionTitle: string;
|
|
1267
|
+
enabled: string;
|
|
1268
|
+
enabledHint: string;
|
|
1269
|
+
modeTitle: string;
|
|
1270
|
+
modeAuto: string;
|
|
1271
|
+
modeAutoHint: string;
|
|
1272
|
+
modeOnDemand: string;
|
|
1273
|
+
modeOnDemandHint: string;
|
|
1274
|
+
unavailable: string;
|
|
1275
|
+
save: string;
|
|
1276
|
+
saving: string;
|
|
1277
|
+
saveSuccess: string;
|
|
1278
|
+
}
|
|
1279
|
+
interface TranscriptionSettingsFormProps {
|
|
1280
|
+
enabled: boolean;
|
|
1281
|
+
onEnabledChange: (value: boolean) => void;
|
|
1282
|
+
mode: TranscriptionMode;
|
|
1283
|
+
onModeChange: (value: TranscriptionMode) => void;
|
|
1284
|
+
onSave: (event: FormEvent) => void;
|
|
1285
|
+
/**
|
|
1286
|
+
* A capacidade existe no servidor (engine e credencial configurados)?
|
|
1287
|
+
*
|
|
1288
|
+
* Distinto de `enabled`: isto é "o ambiente consegue", aquilo é "esta empresa quer". Sem a
|
|
1289
|
+
* distinção, um lojista ligaria o interruptor num ambiente sem engine e concluiria que o produto
|
|
1290
|
+
* está quebrado — o texto nunca apareceria e nada explicaria por quê.
|
|
1291
|
+
*/
|
|
1292
|
+
isAvailable?: boolean;
|
|
1293
|
+
saving?: boolean;
|
|
1294
|
+
saveSuccess?: boolean;
|
|
1295
|
+
labels?: Partial<TranscriptionSettingsFormLabels>;
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Interruptor de transcrição por empresa, para a tela de configurações.
|
|
1299
|
+
*
|
|
1300
|
+
* Puramente apresentacional, como os outros forms daqui: quem persiste é o host. O pacote não sabe a
|
|
1301
|
+
* rota, e a política vive nas configurações do módulo (`settings.transcriptionEnabled`), não em
|
|
1302
|
+
* variável de ambiente — mudar de ideia sobre transcrever não deveria exigir deploy.
|
|
1303
|
+
*/
|
|
1304
|
+
declare function TranscriptionSettingsForm({ enabled, onEnabledChange, mode, onModeChange, onSave, isAvailable, saving, saveSuccess, labels: labelsOverride, }: TranscriptionSettingsFormProps): react.JSX.Element;
|
|
1305
|
+
|
|
615
1306
|
declare const TEMPLATE_SETTINGS_TAB: {
|
|
616
1307
|
readonly SELECT: "select";
|
|
617
1308
|
readonly CREATE: "create";
|
|
@@ -642,10 +1333,21 @@ interface WhatsAppTemplatesSettingsProps {
|
|
|
642
1333
|
onSubmit: (event: FormEvent) => void;
|
|
643
1334
|
submitting?: boolean;
|
|
644
1335
|
result?: WhatsAppCreateTemplateResult | null;
|
|
1336
|
+
labels?: Partial<WhatsAppCreateTemplateFormLabels>;
|
|
1337
|
+
/** Nome exibido na prévia do template criado; sem isto o form usa seu próprio fallback. */
|
|
1338
|
+
previewCompanyName?: string;
|
|
1339
|
+
variableExamples?: readonly string[];
|
|
645
1340
|
};
|
|
646
1341
|
labels?: Partial<WhatsAppTemplatesSettingsLabels>;
|
|
1342
|
+
/** Vocabulário do formulário de seleção — separado de `labels`, que nomeia só as abas daqui. */
|
|
1343
|
+
settingsLabels?: Partial<WhatsAppTemplateSettingsFormLabels>;
|
|
1344
|
+
/**
|
|
1345
|
+
* Formulários extras de seleção (um por papel adicional de template, ex.: despedida), empilhados
|
|
1346
|
+
* abaixo do principal na mesma sub-aba. Ausente = só o papel principal, comportamento de sempre.
|
|
1347
|
+
*/
|
|
1348
|
+
extraRoleForms?: ReactNode;
|
|
647
1349
|
}
|
|
648
|
-
declare function WhatsAppTemplatesSettings({ labels: labelsOverride, create, ...settingsProps }: WhatsAppTemplatesSettingsProps): react.JSX.Element;
|
|
1350
|
+
declare function WhatsAppTemplatesSettings({ labels: labelsOverride, settingsLabels, create, extraRoleForms, ...settingsProps }: WhatsAppTemplatesSettingsProps): react.JSX.Element;
|
|
649
1351
|
|
|
650
1352
|
interface TopicItem {
|
|
651
1353
|
key: string;
|
|
@@ -671,6 +1373,88 @@ interface TopicsFormProps {
|
|
|
671
1373
|
}
|
|
672
1374
|
declare function TopicsForm({ topics, onToggle, onMessageChange, onSave, saving, saveSuccess, labels: labelsOverride, }: TopicsFormProps): react.JSX.Element;
|
|
673
1375
|
|
|
1376
|
+
interface BotMessages {
|
|
1377
|
+
welcomeMessage: string;
|
|
1378
|
+
farewellMessage: string;
|
|
1379
|
+
}
|
|
1380
|
+
interface TemplateSettings {
|
|
1381
|
+
templateName: string;
|
|
1382
|
+
templateLanguage: string;
|
|
1383
|
+
variables: string[];
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Papel adicional de template (ex.: despedida) além do principal (`getTemplateSettings`/
|
|
1387
|
+
* `saveTemplateSettings`). Cada papel ganha seu próprio formulário empilhado na mesma aba, com
|
|
1388
|
+
* carregamento e salvamento independentes — é o que permite hosts com mais de um papel (o
|
|
1389
|
+
* financiamento tem boas-vindas + despedida) convergirem para este componente em vez de remontar
|
|
1390
|
+
* a tela à parte.
|
|
1391
|
+
*/
|
|
1392
|
+
interface MessagesWorkspaceTemplateRole {
|
|
1393
|
+
readonly key: string;
|
|
1394
|
+
readonly labels: {
|
|
1395
|
+
sectionTitle: string;
|
|
1396
|
+
sectionDescription: string;
|
|
1397
|
+
};
|
|
1398
|
+
getSettings(): Promise<TemplateSettings>;
|
|
1399
|
+
saveSettings(settings: TemplateSettings): Promise<void>;
|
|
1400
|
+
}
|
|
1401
|
+
interface TranscriptionSettings {
|
|
1402
|
+
enabled: boolean;
|
|
1403
|
+
mode: TranscriptionMode;
|
|
1404
|
+
available: boolean;
|
|
1405
|
+
}
|
|
1406
|
+
interface MessagesWorkspaceApi {
|
|
1407
|
+
getMessages(): Promise<BotMessages>;
|
|
1408
|
+
saveMessages(messages: BotMessages): Promise<void>;
|
|
1409
|
+
/** Sem este par, a seção de tópicos não é desenhada. */
|
|
1410
|
+
getTopics?(): Promise<TopicItem[]>;
|
|
1411
|
+
saveTopics?(topics: TopicItem[]): Promise<void>;
|
|
1412
|
+
/** Sem este par, a aba de templates não é desenhada. */
|
|
1413
|
+
getTemplateSettings?(): Promise<TemplateSettings>;
|
|
1414
|
+
saveTemplateSettings?(settings: TemplateSettings): Promise<void>;
|
|
1415
|
+
/** Papéis adicionais de template (ex.: despedida) além do principal acima. */
|
|
1416
|
+
templateRoles?: MessagesWorkspaceTemplateRole[];
|
|
1417
|
+
/**
|
|
1418
|
+
* Lista de templates aprovados na Meta. Ausente, a aba ainda existe (dá para salvar o nome
|
|
1419
|
+
* escolhido), mas nasce sem opções e sem botão de recarregar.
|
|
1420
|
+
*/
|
|
1421
|
+
listTemplates?(): Promise<WhatsAppTemplateSummary[]>;
|
|
1422
|
+
/** Sem isto, o submenu "Criar template" some. */
|
|
1423
|
+
createTemplate?(input: WhatsAppCreateTemplateState): Promise<WhatsAppCreateTemplateResult>;
|
|
1424
|
+
/** Sem este par, a aba de transcrição não é desenhada. */
|
|
1425
|
+
getTranscription?(): Promise<TranscriptionSettings>;
|
|
1426
|
+
saveTranscription?(settings: Omit<TranscriptionSettings, 'available'>): Promise<void>;
|
|
1427
|
+
}
|
|
1428
|
+
interface MessagesWorkspaceLabels {
|
|
1429
|
+
title: string;
|
|
1430
|
+
subtitle: string;
|
|
1431
|
+
loading: string;
|
|
1432
|
+
loadError: string;
|
|
1433
|
+
tabBot: string;
|
|
1434
|
+
tabTemplates: string;
|
|
1435
|
+
tabTranscription: string;
|
|
1436
|
+
welcomeFarewell: Partial<WelcomeFarewellFormLabels>;
|
|
1437
|
+
topics: Partial<TopicsFormLabels>;
|
|
1438
|
+
templates: Partial<WhatsAppTemplatesSettingsLabels>;
|
|
1439
|
+
templateSettings: Partial<WhatsAppTemplateSettingsFormLabels>;
|
|
1440
|
+
createTemplate: Partial<WhatsAppCreateTemplateFormLabels>;
|
|
1441
|
+
transcription: Partial<TranscriptionSettingsFormLabels>;
|
|
1442
|
+
}
|
|
1443
|
+
interface MessagesWorkspaceProps {
|
|
1444
|
+
readonly api: MessagesWorkspaceApi;
|
|
1445
|
+
readonly labels?: Partial<MessagesWorkspaceLabels>;
|
|
1446
|
+
readonly welcomePlaceholders?: readonly string[];
|
|
1447
|
+
readonly farewellPlaceholders?: readonly string[];
|
|
1448
|
+
readonly availableVariables?: WhatsAppTemplateVariableSuggestion[];
|
|
1449
|
+
/** Aviso do produto acima da aba de templates (ex.: rota da Graph API ainda não implementada). */
|
|
1450
|
+
readonly renderTemplatesNotice?: () => ReactNode;
|
|
1451
|
+
/** Repassados ao formulário de criação — mesma prévia que `WhatsAppCreateTemplateForm` já suporta. */
|
|
1452
|
+
readonly createTemplatePreviewCompanyName?: string;
|
|
1453
|
+
readonly createTemplateVariableExamples?: readonly string[];
|
|
1454
|
+
readonly className?: string;
|
|
1455
|
+
}
|
|
1456
|
+
declare function MessagesWorkspace({ api, labels: labelsOverride, welcomePlaceholders, farewellPlaceholders, availableVariables, renderTemplatesNotice, createTemplatePreviewCompanyName, createTemplateVariableExamples, className, }: MessagesWorkspaceProps): react.JSX.Element;
|
|
1457
|
+
|
|
674
1458
|
interface UseConversationMessagesResult {
|
|
675
1459
|
messages: MessagePayload[];
|
|
676
1460
|
loading: boolean;
|
|
@@ -684,7 +1468,7 @@ interface UseConversationMessagesResult {
|
|
|
684
1468
|
caption?: string;
|
|
685
1469
|
}) => Promise<MessagePayload>;
|
|
686
1470
|
sendTemplate: (data: {
|
|
687
|
-
templateName
|
|
1471
|
+
templateName?: string;
|
|
688
1472
|
languageCode?: string;
|
|
689
1473
|
bodyParams?: string[];
|
|
690
1474
|
}) => Promise<void>;
|
|
@@ -695,14 +1479,28 @@ declare function useConversationMessages(conversationId: string, params?: {
|
|
|
695
1479
|
before?: string;
|
|
696
1480
|
}): UseConversationMessagesResult;
|
|
697
1481
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
}
|
|
1482
|
+
type UseScrollToLatestMessageParams = {
|
|
1483
|
+
/** Troca de conversa. Muda ⇒ salto instantâneo para o fim. */
|
|
1484
|
+
readonly conversationId: string | undefined;
|
|
1485
|
+
/** Quantidade de mensagens carregadas. Cresce ⇒ acompanha o fim, se o operador estiver lá. */
|
|
1486
|
+
readonly messageCount: number;
|
|
1487
|
+
};
|
|
1488
|
+
type UseScrollToLatestMessageResult = {
|
|
1489
|
+
/** Vai no elemento que rola — tipicamente o `ConversationWallpaper`. */
|
|
1490
|
+
readonly containerRef: RefObject<HTMLDivElement | null>;
|
|
1491
|
+
/** Ligue no `onScroll` do mesmo elemento: é o que detecta o operador lendo o histórico. */
|
|
1492
|
+
readonly handleScroll: (event: UIEvent<HTMLDivElement>) => void;
|
|
1493
|
+
/** `true` quando o operador rolou para trás — serve a um botão "ir para a última". */
|
|
1494
|
+
readonly isAwayFromBottom: boolean;
|
|
1495
|
+
readonly scrollToBottom: (behavior?: ScrollBehavior) => void;
|
|
1496
|
+
};
|
|
1497
|
+
declare function useScrollToLatestMessage({ conversationId, messageCount, }: UseScrollToLatestMessageParams): UseScrollToLatestMessageResult;
|
|
1498
|
+
|
|
1499
|
+
type UseConversationListParams = ListConversationsParams;
|
|
704
1500
|
interface UseConversationListResult {
|
|
705
1501
|
conversations: ConversationSummary[];
|
|
1502
|
+
/** Total no servidor. Cai para o tamanho da página quando a API devolve só o array. */
|
|
1503
|
+
total: number;
|
|
706
1504
|
loading: boolean;
|
|
707
1505
|
error: Error | undefined;
|
|
708
1506
|
refetch: () => Promise<void>;
|
|
@@ -717,12 +1515,11 @@ interface UseConversationContextResult {
|
|
|
717
1515
|
}
|
|
718
1516
|
declare function useConversationContext(conversationId: string): UseConversationContextResult;
|
|
719
1517
|
|
|
720
|
-
|
|
721
|
-
search?: string;
|
|
722
|
-
page?: number;
|
|
723
|
-
}
|
|
1518
|
+
type UseConversationDocumentsParams = ListDocumentsParams;
|
|
724
1519
|
interface UseConversationDocumentsResult {
|
|
725
1520
|
documents: ConversationDocument[];
|
|
1521
|
+
/** Total no servidor. Cai para o tamanho da página quando a API devolve só o array. */
|
|
1522
|
+
total: number;
|
|
726
1523
|
loading: boolean;
|
|
727
1524
|
error: Error | undefined;
|
|
728
1525
|
refetch: () => Promise<void>;
|
|
@@ -733,6 +1530,27 @@ type ConversationRealtimeHandler = (event: MessageEvent) => void;
|
|
|
733
1530
|
declare function useConversationRealtime(conversationId: string | undefined, onEvent: ConversationRealtimeHandler): void;
|
|
734
1531
|
declare function useGlobalRealtime(onEvent: ConversationRealtimeHandler): void;
|
|
735
1532
|
|
|
1533
|
+
interface UseConversationActionsResult {
|
|
1534
|
+
/** `undefined` quando a API do host não implementa a operação — a UI esconde a afordância. */
|
|
1535
|
+
takeover: (() => Promise<void>) | undefined;
|
|
1536
|
+
release: (() => Promise<void>) | undefined;
|
|
1537
|
+
finalize: (() => Promise<void>) | undefined;
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Ações de atendimento de UMA conversa, já ligadas ao id.
|
|
1541
|
+
*
|
|
1542
|
+
* Separado de `useConversationMessages` porque assumir e devolver conversa também acontece a
|
|
1543
|
+
* partir da lista, onde nenhuma thread está aberta — embutir nas mensagens obrigaria a carregar
|
|
1544
|
+
* a thread inteira só para desenhar um botão na linha.
|
|
1545
|
+
*/
|
|
1546
|
+
declare function useConversationActions(conversationId: string): UseConversationActionsResult;
|
|
1547
|
+
interface UseInboxActionsResult {
|
|
1548
|
+
markAllRead: (() => Promise<void>) | undefined;
|
|
1549
|
+
listTemplates: (() => Promise<ConversationTemplate[]>) | undefined;
|
|
1550
|
+
}
|
|
1551
|
+
/** Ações que valem para a caixa inteira, sem conversa selecionada. */
|
|
1552
|
+
declare function useInboxActions(): UseInboxActionsResult;
|
|
1553
|
+
|
|
736
1554
|
declare function parseWhatsAppFormatting(text: string): ReactNode[];
|
|
737
1555
|
declare function waToHTML(text: string): string;
|
|
738
1556
|
declare function htmlToWA(html: string): string;
|
|
@@ -742,6 +1560,8 @@ declare function formatPhone(number: string): string;
|
|
|
742
1560
|
declare function phoneInitials(number: string): string;
|
|
743
1561
|
|
|
744
1562
|
declare function formatTimestamp(timestamp: string): string;
|
|
1563
|
+
declare function formatDateTime(iso: string): string;
|
|
1564
|
+
declare function isSameDay(a: Date, b: Date): boolean;
|
|
745
1565
|
declare function formatFileSize(bytes: number): string;
|
|
746
1566
|
|
|
747
1567
|
interface AsyncResourceState<T> {
|
|
@@ -751,4 +1571,313 @@ interface AsyncResourceState<T> {
|
|
|
751
1571
|
refetch: () => Promise<void>;
|
|
752
1572
|
}
|
|
753
1573
|
|
|
754
|
-
|
|
1574
|
+
/**
|
|
1575
|
+
* Resolve a mídia de uma mensagem numa URL exibível, usando só o `ConversationsApi`.
|
|
1576
|
+
*
|
|
1577
|
+
* Mora no pacote, e não em cada host, porque a regra não tem nada de específico de produto: é a
|
|
1578
|
+
* tradução de `uploadId`/`mediaId` pelos dois métodos que o próprio contrato já declara. Deixá-la no
|
|
1579
|
+
* host significava que todo projeto que adotasse o SDK reescreveria as mesmas oito linhas — e, na
|
|
1580
|
+
* prática, ninguém escrevia: o `MediaRenderer` só busca mídia pela porta `onResolveMediaUrl`, então
|
|
1581
|
+
* onde nada era injetado foto, vídeo e áudio ficavam no placeholder para sempre.
|
|
1582
|
+
*/
|
|
1583
|
+
|
|
1584
|
+
declare function createMediaUrlResolver(api: Pick<ConversationsApi, 'getDocumentUrl' | 'getMediaProxyUrl'>): ResolveMediaUrl;
|
|
1585
|
+
|
|
1586
|
+
/**
|
|
1587
|
+
* Textos da inbox. Todos sobrescrevíveis: o produto troca vocabulário ("cliente" vira "lead") ou
|
|
1588
|
+
* o idioma inteiro sem tocar no layout — e sem manter uma cópia da tela para isso.
|
|
1589
|
+
*/
|
|
1590
|
+
interface ConversationsWorkspaceLabels {
|
|
1591
|
+
readonly title: string;
|
|
1592
|
+
readonly conversations: string;
|
|
1593
|
+
readonly waiting: string;
|
|
1594
|
+
readonly unread: string;
|
|
1595
|
+
readonly waitingOnly: string;
|
|
1596
|
+
readonly markSelectedAsRead: string;
|
|
1597
|
+
readonly search: string;
|
|
1598
|
+
readonly windowLegend: string;
|
|
1599
|
+
readonly channelLegend: string;
|
|
1600
|
+
readonly selectAll: string;
|
|
1601
|
+
readonly emptyList: string;
|
|
1602
|
+
readonly emptyDetail: string;
|
|
1603
|
+
readonly bulkSelected: (count: number) => string;
|
|
1604
|
+
readonly bulkClear: string;
|
|
1605
|
+
readonly bulkFinalize: string;
|
|
1606
|
+
readonly bulkFinalizeConfirm: (count: number) => string;
|
|
1607
|
+
readonly bulkTemplate: string;
|
|
1608
|
+
readonly markAllAsRead: string;
|
|
1609
|
+
readonly templateModalTitle: string;
|
|
1610
|
+
readonly templateModalSearch: string;
|
|
1611
|
+
readonly templateModalEmpty: string;
|
|
1612
|
+
readonly templateModalCancel: string;
|
|
1613
|
+
readonly templateModalSending: string;
|
|
1614
|
+
readonly templateModalNoneExpired: string;
|
|
1615
|
+
readonly templateModalAvailable: (count: number) => string;
|
|
1616
|
+
readonly templateModalSend: (count: number) => string;
|
|
1617
|
+
readonly messagesSelected: (count: number) => string;
|
|
1618
|
+
readonly copySelected: string;
|
|
1619
|
+
readonly composerPlaceholder: string;
|
|
1620
|
+
readonly attachFailure: string;
|
|
1621
|
+
/** Tira um arquivo da fila antes de enviar. */
|
|
1622
|
+
readonly attachmentRemove: string;
|
|
1623
|
+
readonly recordFailure: string;
|
|
1624
|
+
readonly sendFailure: string;
|
|
1625
|
+
readonly takeoverToReply: string;
|
|
1626
|
+
readonly signIn: string;
|
|
1627
|
+
readonly pageOf: (page: number, pageCount: number) => string;
|
|
1628
|
+
readonly rangeOf: (first: number, last: number, total: number) => string;
|
|
1629
|
+
}
|
|
1630
|
+
declare const DEFAULT_CONVERSATIONS_WORKSPACE_LABELS: ConversationsWorkspaceLabels;
|
|
1631
|
+
|
|
1632
|
+
/**
|
|
1633
|
+
* Estado da inbox: filtros, paginação, seleção em massa e ações de atendimento.
|
|
1634
|
+
*
|
|
1635
|
+
* Nasceu igual em três produtos e divergiu em três — contadores diferentes, um marcava lida ao
|
|
1636
|
+
* abrir e outro não, um perdia a página ao trocar de filtro. É a lógica de operar uma inbox, não
|
|
1637
|
+
* regra de nenhum negócio, e por isso mora aqui.
|
|
1638
|
+
*
|
|
1639
|
+
* A paginação é do cliente porque nem todo backend pagina a listagem. Fatiar aqui mantém a tela
|
|
1640
|
+
* utilizável com centenas de conversas; com dezenas de milhares o gargalo volta e a resposta é
|
|
1641
|
+
* paginar no servidor.
|
|
1642
|
+
*/
|
|
1643
|
+
|
|
1644
|
+
declare const CONVERSATIONS_PER_PAGE = 50;
|
|
1645
|
+
interface UseConversationsInboxParams {
|
|
1646
|
+
/** Recortes do produto repassados crus ao backend dele (carteira, campanha, tipo). */
|
|
1647
|
+
readonly filters?: Record<string, string | undefined>;
|
|
1648
|
+
readonly perPage?: number;
|
|
1649
|
+
/**
|
|
1650
|
+
* Marca como lida a conversa aberta. Ligado por padrão: badge subindo na conversa que o atendente
|
|
1651
|
+
* tem na frente é ruído que ele não tem como resolver.
|
|
1652
|
+
*/
|
|
1653
|
+
readonly markReadOnOpen?: boolean;
|
|
1654
|
+
/**
|
|
1655
|
+
* Pede a página ao servidor em vez de fatiar o que veio. Necessário quando a base é grande — o
|
|
1656
|
+
* padrão traz tudo e pagina no cliente porque nem todo backend pagina a listagem.
|
|
1657
|
+
*/
|
|
1658
|
+
readonly serverPaginated?: boolean;
|
|
1659
|
+
readonly describeFailure?: (error: unknown) => string;
|
|
1660
|
+
}
|
|
1661
|
+
interface UseConversationsInboxResult {
|
|
1662
|
+
readonly conversations: readonly ConversationSummary[];
|
|
1663
|
+
readonly pageConversations: readonly ConversationSummary[];
|
|
1664
|
+
readonly selectedConversation: ConversationSummary | undefined;
|
|
1665
|
+
readonly loading: boolean;
|
|
1666
|
+
readonly now: number;
|
|
1667
|
+
readonly totalCount: number;
|
|
1668
|
+
readonly unreadCount: number;
|
|
1669
|
+
readonly waitingCount: number;
|
|
1670
|
+
readonly filteredCount: number;
|
|
1671
|
+
/** Selecionadas fora da janela de 24h — as únicas que um template alcança. */
|
|
1672
|
+
readonly expiredSelectedCount: number;
|
|
1673
|
+
readonly page: number;
|
|
1674
|
+
readonly pageCount: number;
|
|
1675
|
+
readonly firstOnPage: number;
|
|
1676
|
+
readonly lastOnPage: number;
|
|
1677
|
+
readonly selectedId: string | undefined;
|
|
1678
|
+
readonly selectedIds: ReadonlySet<string>;
|
|
1679
|
+
readonly allOnPageSelected: boolean;
|
|
1680
|
+
readonly waitingOnly: boolean;
|
|
1681
|
+
readonly windowFilter: ConversationWindow;
|
|
1682
|
+
readonly channelFilter: ChannelFilter;
|
|
1683
|
+
readonly channelFilters: readonly ChannelFilterOption[];
|
|
1684
|
+
readonly search: string;
|
|
1685
|
+
readonly busy: boolean;
|
|
1686
|
+
/** Por que a lista está vazia, quando não é por não haver conversa. */
|
|
1687
|
+
readonly loadFailure: string | undefined;
|
|
1688
|
+
/** Ausentes quando o host não implementa a capacidade — a UI não desenha a afordância. */
|
|
1689
|
+
readonly canTakeover: boolean;
|
|
1690
|
+
readonly canFinalize: boolean;
|
|
1691
|
+
readonly canListTemplates: boolean;
|
|
1692
|
+
readonly canMarkAllRead: boolean;
|
|
1693
|
+
refetch(): Promise<void> | void;
|
|
1694
|
+
selectConversation(conversationId: string): void;
|
|
1695
|
+
clearSelection(): void;
|
|
1696
|
+
toggleSelected(conversationId: string): void;
|
|
1697
|
+
toggleSelectAllOnPage(): void;
|
|
1698
|
+
clearBulkSelection(): void;
|
|
1699
|
+
setWaitingOnly(value: boolean): void;
|
|
1700
|
+
setWindowFilter(value: ConversationWindow): void;
|
|
1701
|
+
setChannelFilter(value: ChannelFilter): void;
|
|
1702
|
+
setSearch(value: string): void;
|
|
1703
|
+
goToPage(value: number): void;
|
|
1704
|
+
markSelectedAsRead(): Promise<void>;
|
|
1705
|
+
markAllAsRead(): Promise<void>;
|
|
1706
|
+
takeover(conversationId: string): Promise<void>;
|
|
1707
|
+
releaseToBot(conversationId: string): Promise<void>;
|
|
1708
|
+
finalize(conversationId: string): Promise<void>;
|
|
1709
|
+
finalizeSelected(): Promise<void>;
|
|
1710
|
+
sendTemplateToSelected(templateName?: string): Promise<void>;
|
|
1711
|
+
}
|
|
1712
|
+
declare function useConversationsInbox(params?: UseConversationsInboxParams): UseConversationsInboxResult;
|
|
1713
|
+
|
|
1714
|
+
type SimulatorTransportParams = {
|
|
1715
|
+
readonly conversationId: string;
|
|
1716
|
+
readonly channel: ConversationChannel;
|
|
1717
|
+
/** Identificador do contato no canal: telefone no WhatsApp, id de sessão no chat do site. */
|
|
1718
|
+
readonly handle: string;
|
|
1719
|
+
};
|
|
1720
|
+
/**
|
|
1721
|
+
* Fábrica do transporte daquele canal.
|
|
1722
|
+
*
|
|
1723
|
+
* É o único ponto onde o host precisa saber de canal: o painel, a moldura e o comportamento são os
|
|
1724
|
+
* mesmos em todos. No WhatsApp devolve o cliente-ponte (assinatura no servidor do host); no chat do
|
|
1725
|
+
* site, o cliente das rotas do widget.
|
|
1726
|
+
*/
|
|
1727
|
+
type SimulatorTransportFactory = (params: SimulatorTransportParams) => ConversationSimulatorClient;
|
|
1728
|
+
interface ConversationsWorkspaceSimulator {
|
|
1729
|
+
/**
|
|
1730
|
+
* Um transporte por canal — o workspace monta o painel com o da conversa selecionada.
|
|
1731
|
+
*
|
|
1732
|
+
* Canal sem transporte não desenha o botão: capacidade é opcional por ausência, e oferecer
|
|
1733
|
+
* "simular" numa conversa que não tem como receber a mensagem é um botão que falha ao ser tocado.
|
|
1734
|
+
*/
|
|
1735
|
+
readonly transports?: Partial<Record<ConversationChannel, SimulatorTransportFactory>>;
|
|
1736
|
+
/**
|
|
1737
|
+
* Válvula de escape: o host desenha o painel inteiro. Tem precedência sobre `transports`.
|
|
1738
|
+
*
|
|
1739
|
+
* Era a única porta antes de `transports`, quando o simulador só falava WhatsApp e cada produto
|
|
1740
|
+
* remontava a moldura — o que fez duas telas da mesma casa divergirem. Continua aceito para não
|
|
1741
|
+
* quebrar quem já a usa.
|
|
1742
|
+
*/
|
|
1743
|
+
render?(params: {
|
|
1744
|
+
conversationId: string;
|
|
1745
|
+
channel: ConversationChannel;
|
|
1746
|
+
close: () => void;
|
|
1747
|
+
}): ReactNode;
|
|
1748
|
+
/** Ausente = ligado. Serve para esconder fora de desenvolvimento sem condicionar o JSX. */
|
|
1749
|
+
readonly enabled?: boolean;
|
|
1750
|
+
/** Ícone da biblioteca (lucide) no utilitário do cabeçalho. Ausente, entra o frasco de teste. */
|
|
1751
|
+
readonly icon?: ReactNode;
|
|
1752
|
+
readonly label?: string;
|
|
1753
|
+
/** Vocabulário do painel. O que muda de canal (destino, placeholder) já vem resolvido. */
|
|
1754
|
+
readonly labels?: Partial<ConversationSimulatorPanelLabels>;
|
|
1755
|
+
/** Destino do upload no canal que entrega mídia por referência (o caminho da Meta). */
|
|
1756
|
+
readonly uploadMedia?: (file: File) => Promise<PreviewUploadedMedia>;
|
|
1757
|
+
/** Recarrega o transcript a cada N ms. Serve a host sem stream. */
|
|
1758
|
+
readonly pollIntervalMs?: number;
|
|
1759
|
+
}
|
|
1760
|
+
interface ConversationsWorkspaceProps {
|
|
1761
|
+
readonly labels?: Partial<ConversationsWorkspaceLabels>;
|
|
1762
|
+
readonly filters?: Record<string, string | undefined>;
|
|
1763
|
+
readonly perPage?: number;
|
|
1764
|
+
readonly markReadOnOpen?: boolean;
|
|
1765
|
+
/** Pede a página ao servidor em vez de fatiar no cliente. */
|
|
1766
|
+
readonly serverPaginated?: boolean;
|
|
1767
|
+
/** Conversa a abrir na montagem (deep link `?id=`). */
|
|
1768
|
+
readonly initialConversationId?: string | undefined;
|
|
1769
|
+
/** Idem, pelo telefone — é o que costuma vir no link de um alerta ou de um pedido. */
|
|
1770
|
+
readonly initialWhatsappNumber?: string | undefined;
|
|
1771
|
+
readonly simulator?: ConversationsWorkspaceSimulator;
|
|
1772
|
+
readonly quickReplies?: readonly QuickReply[];
|
|
1773
|
+
readonly quickReplyVariablesFor?: (conversation: ConversationSummary, context: Record<string, unknown> | undefined) => Record<string, string>;
|
|
1774
|
+
/** Etapa do fluxo mostrada no painel de contexto. */
|
|
1775
|
+
readonly flowLabelOf?: (conversation: ConversationSummary) => string | undefined;
|
|
1776
|
+
/** Substitui o download local do transcript (ex.: exportação completa pela rota do servidor). */
|
|
1777
|
+
readonly onDownload?: (conversation: ConversationSummary) => void;
|
|
1778
|
+
/** Bloqueia o composer enquanto a conversa estiver com o bot. */
|
|
1779
|
+
readonly requireTakeoverToReply?: boolean;
|
|
1780
|
+
/** Deixa marcar mensagens no transcript e copiar o trecho. */
|
|
1781
|
+
readonly messageSelection?: boolean;
|
|
1782
|
+
/** Texto já no campo ao abrir a conversa (deep link que sugere a resposta). */
|
|
1783
|
+
readonly initialComposerText?: string | undefined;
|
|
1784
|
+
/** `rich` troca o campo simples pelo texto com a formatação do WhatsApp desenhada ao escrever. */
|
|
1785
|
+
readonly composer?: 'simple' | 'rich';
|
|
1786
|
+
/** Valores que o operador insere sem digitar. Só o composer `rich` os oferece. */
|
|
1787
|
+
readonly composerVariablesFor?: (conversation: ConversationSummary, context: Record<string, unknown> | undefined) => readonly RichComposerVariable[];
|
|
1788
|
+
/** Fila de anexos com legenda, como no WhatsApp. Ausente, o clipe manda cada arquivo na hora. */
|
|
1789
|
+
readonly onSendAttachments?: (conversation: ConversationSummary, files: readonly File[], caption: string) => Promise<void>;
|
|
1790
|
+
/** Nota de voz. Ausente, o microfone não aparece. */
|
|
1791
|
+
readonly onRecordAudio?: (conversation: ConversationSummary, file: File) => Promise<void>;
|
|
1792
|
+
readonly contextEntriesOf?: (context: Record<string, unknown> | undefined) => readonly ConversationContextEntry[];
|
|
1793
|
+
readonly onAttach?: (conversation: ConversationSummary, file: File) => Promise<void>;
|
|
1794
|
+
readonly extraUtilitiesFor?: (conversation: ConversationSummary) => readonly ConversationHeaderUtility[];
|
|
1795
|
+
readonly renderFilters?: (inbox: UseConversationsInboxResult) => ReactNode;
|
|
1796
|
+
readonly renderBulkActions?: (inbox: UseConversationsInboxResult) => ReactNode;
|
|
1797
|
+
readonly renderRow?: (conversation: ConversationSummary) => ReactNode;
|
|
1798
|
+
readonly renderAboveTranscript?: (conversation: ConversationSummary, context: Record<string, unknown> | undefined) => ReactNode;
|
|
1799
|
+
readonly renderHeaderActions?: (inbox: UseConversationsInboxResult) => ReactNode;
|
|
1800
|
+
readonly onSendTemplateToSelected?: (inbox: UseConversationsInboxResult) => void;
|
|
1801
|
+
/** Destino do link de reentrar no painel, mostrado junto do aviso de sessão expirada. */
|
|
1802
|
+
readonly signInHref?: string;
|
|
1803
|
+
readonly className?: string;
|
|
1804
|
+
}
|
|
1805
|
+
declare function ConversationsWorkspace({ labels: labelsOverride, filters, perPage, markReadOnOpen, serverPaginated, initialConversationId, initialWhatsappNumber, simulator, quickReplies, quickReplyVariablesFor, flowLabelOf, onDownload, requireTakeoverToReply, messageSelection, initialComposerText, composer, composerVariablesFor, onSendAttachments, onRecordAudio, contextEntriesOf, onAttach, extraUtilitiesFor, renderFilters, renderBulkActions, renderRow, renderAboveTranscript, renderHeaderActions, onSendTemplateToSelected, signInHref, className, }: ConversationsWorkspaceProps): react.JSX.Element;
|
|
1806
|
+
|
|
1807
|
+
interface ConversationPaneProps {
|
|
1808
|
+
readonly conversation: ConversationSummary;
|
|
1809
|
+
readonly now: number;
|
|
1810
|
+
readonly busy: boolean;
|
|
1811
|
+
readonly labels: ConversationsWorkspaceLabels;
|
|
1812
|
+
/** Devolve a promessa quando o host a tem: o envio de template espera a tomada antes de sair. */
|
|
1813
|
+
readonly onTakeover?: (() => void | Promise<void>) | undefined;
|
|
1814
|
+
readonly onReturnToBot?: (() => void) | undefined;
|
|
1815
|
+
readonly onFinish?: (() => void) | undefined;
|
|
1816
|
+
readonly onBack: () => void;
|
|
1817
|
+
readonly extraUtilities?: readonly ConversationHeaderUtility[];
|
|
1818
|
+
/** Traduz o contexto cru do produto nas linhas do painel. Ausente, o painel não aparece. */
|
|
1819
|
+
readonly contextEntriesOf?: (context: Record<string, unknown> | undefined) => readonly ConversationContextEntry[];
|
|
1820
|
+
readonly quickReplies?: readonly QuickReply[];
|
|
1821
|
+
/**
|
|
1822
|
+
* Recebe o contexto junto porque o dado que interessa à variável (o nome que o bot perguntou,
|
|
1823
|
+
* por exemplo) vive no contexto do fluxo, não no resumo da listagem.
|
|
1824
|
+
*/
|
|
1825
|
+
readonly quickReplyVariablesFor?: (conversation: ConversationSummary, context: Record<string, unknown> | undefined) => Record<string, string>;
|
|
1826
|
+
/** Etapa do fluxo ao lado do contexto (ex.: "Anotando o pedido"). */
|
|
1827
|
+
readonly flowLabel?: string | undefined;
|
|
1828
|
+
/**
|
|
1829
|
+
* Substitui o download local do transcript. Existe para o produto que exporta pela rota do
|
|
1830
|
+
* servidor, onde o arquivo sai completo em vez de só com o que a tela carregou.
|
|
1831
|
+
*/
|
|
1832
|
+
readonly onDownload?: (() => void) | undefined;
|
|
1833
|
+
/**
|
|
1834
|
+
* Bloqueia o composer enquanto a conversa estiver com o bot. Ligado, responder sem assumir
|
|
1835
|
+
* atropelaria o fluxo automático no meio de uma pergunta.
|
|
1836
|
+
*/
|
|
1837
|
+
readonly requireTakeoverToReply?: boolean;
|
|
1838
|
+
/**
|
|
1839
|
+
* Deixa marcar mensagens e copiar o trecho. É o que se faz para levar um pedaço da conversa a um
|
|
1840
|
+
* e-mail ou a um chamado, sem baixar o transcript inteiro.
|
|
1841
|
+
*/
|
|
1842
|
+
readonly messageSelection?: boolean;
|
|
1843
|
+
/** Texto já no campo ao abrir (deep link que sugere a resposta). */
|
|
1844
|
+
readonly initialComposerText?: string | undefined;
|
|
1845
|
+
/**
|
|
1846
|
+
* Peça do produto entre o contexto e o transcript (ex.: ficha do lead, resumo do pedido). Recebe o
|
|
1847
|
+
* contexto do fluxo junto: é dele que sai o resumo, e sem isso o host teria de buscá-lo de novo.
|
|
1848
|
+
*/
|
|
1849
|
+
readonly renderAboveTranscript?: (conversation: ConversationSummary, context: Record<string, unknown> | undefined) => ReactNode;
|
|
1850
|
+
readonly onAttach?: ((file: File) => Promise<void>) | undefined;
|
|
1851
|
+
/**
|
|
1852
|
+
* `rich` troca o `textarea` pelo campo com a formatação do WhatsApp desenhada enquanto se escreve.
|
|
1853
|
+
* Vale onde o atendente manda texto longo e formatado; para responder "já vou ver" o campo simples
|
|
1854
|
+
* é menos coisa na tela.
|
|
1855
|
+
*/
|
|
1856
|
+
readonly composer?: 'simple' | 'rich';
|
|
1857
|
+
/** Valores que o operador insere sem digitar. Só o composer `rich` os oferece. */
|
|
1858
|
+
readonly composerVariablesFor?: (conversation: ConversationSummary, context: Record<string, unknown> | undefined) => readonly RichComposerVariable[];
|
|
1859
|
+
/**
|
|
1860
|
+
* Fila de anexos com legenda, como no WhatsApp: os arquivos escolhidos ficam visíveis acima da
|
|
1861
|
+
* barra e saem junto com o texto escrito. Ausente, o clipe manda cada arquivo na hora — o que
|
|
1862
|
+
* perde a legenda, e era a diferença entre as telas.
|
|
1863
|
+
*/
|
|
1864
|
+
readonly onSendAttachments?: (files: readonly File[], caption: string) => Promise<void>;
|
|
1865
|
+
/** Grava e envia nota de voz. Ausente, o microfone não aparece. */
|
|
1866
|
+
readonly onRecordAudio?: (file: File) => Promise<void>;
|
|
1867
|
+
}
|
|
1868
|
+
declare function ConversationPane({ conversation, now, busy, labels, onTakeover, onReturnToBot, onFinish, onBack, extraUtilities, contextEntriesOf, quickReplies, quickReplyVariablesFor, flowLabel, onDownload, requireTakeoverToReply, messageSelection, initialComposerText, renderAboveTranscript, onAttach, composer, composerVariablesFor, onSendAttachments, onRecordAudio, }: ConversationPaneProps): react.JSX.Element;
|
|
1869
|
+
|
|
1870
|
+
interface ConversationsInboxListProps {
|
|
1871
|
+
readonly inbox: UseConversationsInboxResult;
|
|
1872
|
+
readonly labels: ConversationsWorkspaceLabels;
|
|
1873
|
+
readonly className?: string;
|
|
1874
|
+
/** Barra extra do produto acima da lista (ex.: filtro de carteira, seletor de campanha). */
|
|
1875
|
+
readonly renderFilters?: (inbox: UseConversationsInboxResult) => ReactNode;
|
|
1876
|
+
/** Ações em lote do produto, ao lado de finalizar e template. */
|
|
1877
|
+
readonly renderBulkActions?: (inbox: UseConversationsInboxResult) => ReactNode;
|
|
1878
|
+
readonly renderRow?: (conversation: ConversationSummary) => ReactNode;
|
|
1879
|
+
readonly onSendTemplateToSelected?: () => void;
|
|
1880
|
+
}
|
|
1881
|
+
declare function ConversationsInboxList({ inbox, labels, className, renderFilters, renderBulkActions, renderRow, onSendTemplateToSelected, }: ConversationsInboxListProps): react.JSX.Element;
|
|
1882
|
+
|
|
1883
|
+
export { type AsyncResourceState, AudioPlayer, type AudioPlayerProps, AudioTranscription, type AudioTranscriptionProps, Avatar, type AvatarLabels, type AvatarProps, type BotMessages, type BuildTranscriptTextParams, BulkActionBar, type BulkActionBarProps, CHANNEL_BRAND_COLOR, CONVERSATIONS_PER_PAGE, CONVERSATION_WINDOW, ChannelFilter, ChannelFilterOption, 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, ConversationPane, type ConversationPaneProps, type ConversationRealtimeHandler, ConversationRow, type ConversationRowClassNames, type ConversationRowProps, ConversationSimulatorClient, ConversationSummary, ConversationTemplate, ConversationWallpaper, type ConversationWallpaperProps, type ConversationWindow, ConversationsApi, ConversationsFeatures, ConversationsInboxList, type ConversationsInboxListProps, ConversationsProvider, ConversationsWorkspace, type ConversationsWorkspaceLabels, type ConversationsWorkspaceProps, type ConversationsWorkspaceSimulator, DEFAULT_ACCEPTED_FILE_TYPES, DEFAULT_AVATAR_LABELS, DEFAULT_CONVERSATIONS_WORKSPACE_LABELS, DEFAULT_CONVERSATION_CONTEXT_LABELS, DEFAULT_CONVERSATION_DOCUMENTS_LABELS, DEFAULT_CONVERSATION_HEADER_LABELS, DEFAULT_CONVERSATION_LIST_ITEM_LABELS, DEFAULT_DARK_MODE_TOGGLE_LABELS, DEFAULT_DOCUMENTS_LIBRARY_LABELS, DEFAULT_DOCUMENTS_WORKSPACE_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, DarkModeToggle, type DarkModeToggleLabels, type DarkModeToggleProps, DateDivider, type DateDividerClassNames, type DateDividerProps, type DocumentSourceFilter, type DocumentsFiltersContext, DocumentsLibrary, type DocumentsLibraryClassNames, type DocumentsLibraryLabels, type DocumentsLibraryProps, DocumentsWorkspace, type DocumentsWorkspaceClassNames, type DocumentsWorkspaceLabels, type DocumentsWorkspaceProps, EMOJI_CATEGORIES, type EmojiCategory, type EmojiEntry, EmojiPicker, type EmojiPickerLabels, type EmojiPickerProps, FileIcon, type FileIconProps, type FilterOption, InteractiveMessage, type InteractiveMessageLabels, type InteractiveMessageProps, InteractivePayload, InteractiveSelection, Lightbox, type LightboxLabels, type LightboxProps, ListConversationsParams, ListDocumentsParams, ListingPagination, type ListingPaginationProps, MessageBubble, type MessageBubbleProps, MessageComposer, type MessageComposerClassNames, type MessageComposerLabels, type MessageComposerProps, MessagePayload, MessageTail, MessageText, type MessageTextProps, MessageTimestamp, MessageTranscription, MessagesWorkspace, type MessagesWorkspaceApi, type MessagesWorkspaceLabels, type MessagesWorkspaceProps, type MessagesWorkspaceTemplateRole, MultiSelectFilter, type MultiSelectFilterProps, 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, type SimulatorTransportFactory, type SimulatorTransportParams, type SortDirection, SortableHead, type SortableHeadProps, StatusTicks, type StatusTicksProps, TEMPLATE_SETTINGS_TAB, TOOLTIP_ATTRIBUTE, type TemplateSettings, type TemplateSettingsTab, ToastProvider, TooltipLayer, type TopicItem, TopicsForm, type TopicsFormLabels, type TopicsFormProps, TranscriptionMode, type TranscriptionSettings, TranscriptionSettingsForm, type TranscriptionSettingsFormLabels, type TranscriptionSettingsFormProps, type UrlStateOptions, type UseConversationActionsResult, type UseConversationContextResult, type UseConversationDocumentsParams, type UseConversationDocumentsResult, type UseConversationListParams, type UseConversationListResult, type UseConversationMessagesResult, type UseConversationsInboxParams, type UseConversationsInboxResult, 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, useConversationsInbox, useDarkMode, useDebouncedValue, useGlobalRealtime, useInboxActions, useIsDarkTheme, useIsNarrow, useScrollToLatestMessage, useToast, useUrlArrayState, useUrlNumberState, useUrlStringState, useWaitingNotifications, waToHTML, waToHTMLInline, windowOf };
|