@adatechnology/conversations-ui 0.1.0-rc.3 → 0.1.0-rc.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-CUYYYZWD.js +2317 -0
- package/dist/chunk-DXPSPUWF.js +110 -0
- package/dist/{chunk-OGRRHQQW.js → chunk-WCBDXZ3X.js} +68 -4
- package/dist/flows/index.d.ts +338 -4
- package/dist/flows/index.js +1331 -55
- package/dist/index.d.ts +1074 -42
- package/dist/index.js +3572 -742
- package/dist/preview/index.d.ts +328 -8
- package/dist/preview/index.js +902 -115
- package/dist/styles.css +899 -0
- package/dist/types-De5aN-E_.d.ts +502 -0
- package/package.json +3 -3
- 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 +42 -0
- package/src/ConversationRow.tsx +31 -7
- 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 +98 -22
- 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 +164 -0
- package/src/Wallpaper.test.tsx +21 -0
- package/src/Wallpaper.tsx +67 -7
- package/src/WhatsAppMessageEditor.tsx +28 -4
- 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/FlowGroupHeader.tsx +12 -2
- package/src/flows/FlowMapCanvas.tsx +15 -12
- package/src/flows/FlowMapNode.tsx +4 -1
- package/src/flows/FlowNodeCard.tsx +35 -8
- package/src/flows/FlowNodePanel.tsx +149 -38
- package/src/flows/FlowPalette.tsx +13 -3
- package/src/flows/FlowPortalNode.tsx +1 -1
- package/src/flows/FlowWhatsAppPreview.tsx +14 -3
- package/src/flows/FlowsWorkspace.tsx +1023 -0
- package/src/flows/flowCanvasModel.test.ts +312 -0
- package/src/flows/flowCanvasModel.ts +368 -0
- package/src/flows/flowEditorOps.test.ts +241 -0
- package/src/flows/flowEditorOps.ts +177 -0
- package/src/flows/flowGraph.ts +6 -6
- package/src/flows/index.ts +47 -1
- package/src/flows/labels.ts +141 -0
- package/src/flows/workspaceContract.test.ts +95 -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 +114 -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 +199 -13
- package/src/preview/ConversationSimulatorPanel.test.tsx +55 -0
- package/src/preview/ConversationSimulatorPanel.tsx +89 -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 +33 -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 +468 -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.tsx +9 -1
- package/src/styles.css +862 -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 +346 -0
- package/src/workspace/index.ts +12 -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-N7B24WYD.js +0 -719
- package/dist/chunk-NV2RZ5KT.js +0 -56
- package/dist/types-C0PtaO7S.d.ts +0 -207
package/dist/preview/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AudioRecorderButton,
|
|
3
|
+
ConversationDocumentsPanel,
|
|
2
4
|
ConversationWallpaper,
|
|
5
|
+
ConversationsProvider,
|
|
6
|
+
DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
|
|
3
7
|
DateDivider,
|
|
8
|
+
DocumentsLibrary,
|
|
4
9
|
MessageBubble,
|
|
5
10
|
MessageComposer
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import "../chunk-
|
|
11
|
+
} from "../chunk-CUYYYZWD.js";
|
|
12
|
+
import "../chunk-WCBDXZ3X.js";
|
|
8
13
|
|
|
9
14
|
// src/preview/previewStore.ts
|
|
10
15
|
var GLOBAL_CHANNEL = "global";
|
|
@@ -135,108 +140,6 @@ function createMockEventSource() {
|
|
|
135
140
|
};
|
|
136
141
|
}
|
|
137
142
|
|
|
138
|
-
// src/preview/createMockConversationsApi.ts
|
|
139
|
-
var PREVIEW_IMAGE_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYGD4DwABBAEAX+XyEgAAAABJRU5ErkJggg==";
|
|
140
|
-
var DEFAULT_LATENCY_MS = 120;
|
|
141
|
-
function createMockConversationsApi(params) {
|
|
142
|
-
const latencyMs = params.latencyMs ?? DEFAULT_LATENCY_MS;
|
|
143
|
-
async function withLatency(produce) {
|
|
144
|
-
await new Promise((resolve) => setTimeout(resolve, latencyMs));
|
|
145
|
-
return produce();
|
|
146
|
-
}
|
|
147
|
-
return {
|
|
148
|
-
fetchConversations(fetchParams) {
|
|
149
|
-
return withLatency(() => {
|
|
150
|
-
const conversations = params.store.listConversations({
|
|
151
|
-
waitingHuman: fetchParams?.waitingHuman,
|
|
152
|
-
search: fetchParams?.search
|
|
153
|
-
});
|
|
154
|
-
const limit = fetchParams?.limit ?? conversations.length;
|
|
155
|
-
const page = fetchParams?.page ?? 1;
|
|
156
|
-
return conversations.slice((page - 1) * limit, page * limit);
|
|
157
|
-
});
|
|
158
|
-
},
|
|
159
|
-
fetchMessages(conversationId, fetchParams) {
|
|
160
|
-
return withLatency(() => {
|
|
161
|
-
const messages = params.store.listMessages(conversationId);
|
|
162
|
-
const limit = fetchParams?.limit;
|
|
163
|
-
return limit ? messages.slice(-limit) : messages;
|
|
164
|
-
});
|
|
165
|
-
},
|
|
166
|
-
sendMessage(conversationId, text) {
|
|
167
|
-
return withLatency(
|
|
168
|
-
() => params.store.appendMessage({ conversationId, content: text, direction: "outbound", sender: "agent" })
|
|
169
|
-
);
|
|
170
|
-
},
|
|
171
|
-
sendMedia(conversationId, data) {
|
|
172
|
-
return withLatency(
|
|
173
|
-
() => params.store.appendMessage({
|
|
174
|
-
conversationId,
|
|
175
|
-
content: data.caption ?? data.filename,
|
|
176
|
-
direction: "outbound",
|
|
177
|
-
sender: "agent"
|
|
178
|
-
})
|
|
179
|
-
);
|
|
180
|
-
},
|
|
181
|
-
sendTemplate(conversationId, data) {
|
|
182
|
-
return withLatency(() => {
|
|
183
|
-
params.store.appendMessage({
|
|
184
|
-
conversationId,
|
|
185
|
-
content: `[template] ${data.templateName}`,
|
|
186
|
-
direction: "outbound",
|
|
187
|
-
sender: "agent"
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
},
|
|
191
|
-
markRead(conversationId) {
|
|
192
|
-
return withLatency(() => params.store.markRead(conversationId));
|
|
193
|
-
},
|
|
194
|
-
getContext(conversationId) {
|
|
195
|
-
return withLatency(() => {
|
|
196
|
-
const conversation = params.store.listConversations().find((item) => item.id === conversationId);
|
|
197
|
-
return {
|
|
198
|
-
currentState: conversation?.currentState ?? "unknown",
|
|
199
|
-
mode: conversation?.mode ?? "bot",
|
|
200
|
-
preview: true
|
|
201
|
-
};
|
|
202
|
-
});
|
|
203
|
-
},
|
|
204
|
-
getDocuments() {
|
|
205
|
-
return withLatency(() => []);
|
|
206
|
-
},
|
|
207
|
-
getDocumentUrl() {
|
|
208
|
-
return withLatency(() => `data:image/png;base64,${PREVIEW_IMAGE_BASE64}`);
|
|
209
|
-
},
|
|
210
|
-
getMediaProxyUrl() {
|
|
211
|
-
return withLatency(() => ({ mimeType: "image/png", data: PREVIEW_IMAGE_BASE64 }));
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// src/preview/createMockSSEProvider.ts
|
|
217
|
-
function createMockSSEProvider(params) {
|
|
218
|
-
function connect(channel) {
|
|
219
|
-
const source = createMockEventSource();
|
|
220
|
-
const unsubscribe = params.store.subscribe(channel, (emission) => {
|
|
221
|
-
source.emit(emission.event, emission.payload);
|
|
222
|
-
});
|
|
223
|
-
const close = source.close.bind(source);
|
|
224
|
-
source.close = () => {
|
|
225
|
-
unsubscribe();
|
|
226
|
-
close();
|
|
227
|
-
};
|
|
228
|
-
return source;
|
|
229
|
-
}
|
|
230
|
-
return {
|
|
231
|
-
connectConversationStream(conversationId) {
|
|
232
|
-
return connect(conversationChannel(conversationId));
|
|
233
|
-
},
|
|
234
|
-
connectGlobalStream() {
|
|
235
|
-
return connect(GLOBAL_CHANNEL);
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
143
|
// src/preview/previewFixtures.ts
|
|
241
144
|
var BASE_DAY = "2026-07-26";
|
|
242
145
|
function at(time) {
|
|
@@ -297,6 +200,24 @@ var PREVIEW_CONVERSATIONS = [
|
|
|
297
200
|
waitingHuman: false,
|
|
298
201
|
unread: 1,
|
|
299
202
|
currentState: "list_import"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
// Cobre TODO tipo que o composer aceita (DEFAULT_ACCEPTED_FILE_TYPES: image/*, video/*,
|
|
206
|
+
// audio/*, .pdf, .doc, .docx, .xls, .xlsx, .zip) mais sticker. Existe para que cada ramo do
|
|
207
|
+
// MediaRenderer e cada ícone/cor do FileIcon apareçam em algum lugar — ramo sem fixture é ramo
|
|
208
|
+
// que ninguém olha até quebrar em produção.
|
|
209
|
+
id: "5511944443333",
|
|
210
|
+
whatsappNumber: "5511944443333",
|
|
211
|
+
clientName: "Rita Documentos",
|
|
212
|
+
lastContent: "segue a planilha do pedido",
|
|
213
|
+
lastDirection: "inbound",
|
|
214
|
+
lastAt: at("15:10:00"),
|
|
215
|
+
lastInboundAt: at("15:10:00"),
|
|
216
|
+
mode: "human",
|
|
217
|
+
assignedUserId: "agent-1",
|
|
218
|
+
waitingHuman: false,
|
|
219
|
+
unread: 3,
|
|
220
|
+
currentState: "human_handling"
|
|
300
221
|
}
|
|
301
222
|
];
|
|
302
223
|
var PREVIEW_MESSAGES = {
|
|
@@ -375,13 +296,567 @@ var PREVIEW_MESSAGES = {
|
|
|
375
296
|
sender: "customer",
|
|
376
297
|
timestamp: at("13:31:00")
|
|
377
298
|
}
|
|
299
|
+
],
|
|
300
|
+
// Um tipo por mensagem, na ordem em que o MediaRenderer os trata.
|
|
301
|
+
"5511944443333": [
|
|
302
|
+
{
|
|
303
|
+
id: "fixture-doc-image",
|
|
304
|
+
type: "image",
|
|
305
|
+
mediaId: "preview-image-1",
|
|
306
|
+
mimeType: "image/png",
|
|
307
|
+
caption: "foto da prateleira",
|
|
308
|
+
direction: "inbound",
|
|
309
|
+
sender: "customer",
|
|
310
|
+
timestamp: at("15:00:00")
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
id: "fixture-doc-video",
|
|
314
|
+
type: "video",
|
|
315
|
+
mediaId: "preview-video-1",
|
|
316
|
+
mimeType: "video/mp4",
|
|
317
|
+
direction: "inbound",
|
|
318
|
+
sender: "customer",
|
|
319
|
+
timestamp: at("15:01:00")
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: "fixture-doc-audio",
|
|
323
|
+
type: "audio",
|
|
324
|
+
mediaId: "preview-audio-2",
|
|
325
|
+
mimeType: "audio/ogg",
|
|
326
|
+
direction: "inbound",
|
|
327
|
+
sender: "customer",
|
|
328
|
+
timestamp: at("15:02:00")
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
id: "fixture-doc-sticker",
|
|
332
|
+
type: "sticker",
|
|
333
|
+
mediaId: "preview-sticker-1",
|
|
334
|
+
mimeType: "image/webp",
|
|
335
|
+
direction: "inbound",
|
|
336
|
+
sender: "customer",
|
|
337
|
+
timestamp: at("15:03:00")
|
|
338
|
+
},
|
|
339
|
+
// Os cinco ramos do FileIcon: pdf, doc, xls, zip e o genérico do fallback.
|
|
340
|
+
{
|
|
341
|
+
id: "fixture-doc-pdf",
|
|
342
|
+
type: "document",
|
|
343
|
+
uploadId: "preview/documentos/nota-fiscal.pdf",
|
|
344
|
+
filename: "nota-fiscal.pdf",
|
|
345
|
+
mimeType: "application/pdf",
|
|
346
|
+
sizeBytes: 184320,
|
|
347
|
+
direction: "inbound",
|
|
348
|
+
sender: "customer",
|
|
349
|
+
timestamp: at("15:04:00")
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: "fixture-doc-docx",
|
|
353
|
+
type: "document",
|
|
354
|
+
uploadId: "preview/documentos/contrato.docx",
|
|
355
|
+
filename: "contrato.docx",
|
|
356
|
+
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
357
|
+
sizeBytes: 42112,
|
|
358
|
+
direction: "inbound",
|
|
359
|
+
sender: "customer",
|
|
360
|
+
timestamp: at("15:05:00")
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
id: "fixture-doc-doc",
|
|
364
|
+
type: "document",
|
|
365
|
+
uploadId: "preview/documentos/procuracao.doc",
|
|
366
|
+
filename: "procuracao.doc",
|
|
367
|
+
mimeType: "application/msword",
|
|
368
|
+
sizeBytes: 31744,
|
|
369
|
+
direction: "inbound",
|
|
370
|
+
sender: "customer",
|
|
371
|
+
timestamp: at("15:06:00")
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: "fixture-doc-xlsx",
|
|
375
|
+
type: "document",
|
|
376
|
+
uploadId: "preview/documentos/pedido.xlsx",
|
|
377
|
+
filename: "pedido.xlsx",
|
|
378
|
+
mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
379
|
+
sizeBytes: 15872,
|
|
380
|
+
direction: "inbound",
|
|
381
|
+
sender: "customer",
|
|
382
|
+
timestamp: at("15:07:00")
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
id: "fixture-doc-xls",
|
|
386
|
+
type: "document",
|
|
387
|
+
uploadId: "preview/documentos/tabela-antiga.xls",
|
|
388
|
+
filename: "tabela-antiga.xls",
|
|
389
|
+
mimeType: "application/vnd.ms-excel",
|
|
390
|
+
sizeBytes: 9216,
|
|
391
|
+
direction: "inbound",
|
|
392
|
+
sender: "customer",
|
|
393
|
+
timestamp: at("15:08:00")
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
id: "fixture-doc-zip",
|
|
397
|
+
type: "document",
|
|
398
|
+
uploadId: "preview/documentos/comprovantes.zip",
|
|
399
|
+
filename: "comprovantes.zip",
|
|
400
|
+
mimeType: "application/zip",
|
|
401
|
+
sizeBytes: 2355200,
|
|
402
|
+
direction: "inbound",
|
|
403
|
+
sender: "customer",
|
|
404
|
+
timestamp: at("15:09:00")
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
id: "fixture-doc-txt",
|
|
408
|
+
type: "document",
|
|
409
|
+
uploadId: "preview/documentos/lista-compras.txt",
|
|
410
|
+
filename: "lista-compras.txt",
|
|
411
|
+
mimeType: "text/plain",
|
|
412
|
+
sizeBytes: 1024,
|
|
413
|
+
direction: "inbound",
|
|
414
|
+
sender: "customer",
|
|
415
|
+
timestamp: at("15:10:00")
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
id: "fixture-doc-csv",
|
|
419
|
+
type: "document",
|
|
420
|
+
uploadId: "preview/documentos/planilha-do-pedido.csv",
|
|
421
|
+
filename: "planilha-do-pedido.csv",
|
|
422
|
+
mimeType: "text/csv",
|
|
423
|
+
sizeBytes: 2048,
|
|
424
|
+
direction: "inbound",
|
|
425
|
+
sender: "customer",
|
|
426
|
+
timestamp: at("15:11:00")
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
id: "fixture-doc-pptx",
|
|
430
|
+
type: "document",
|
|
431
|
+
uploadId: "preview/documentos/apresentacao.pptx",
|
|
432
|
+
filename: "apresentacao.pptx",
|
|
433
|
+
mimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
434
|
+
sizeBytes: 512e3,
|
|
435
|
+
direction: "inbound",
|
|
436
|
+
sender: "customer",
|
|
437
|
+
timestamp: at("15:12:00")
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
id: "fixture-doc-ppt",
|
|
441
|
+
type: "document",
|
|
442
|
+
uploadId: "preview/documentos/antiga.ppt",
|
|
443
|
+
filename: "antiga.ppt",
|
|
444
|
+
mimeType: "application/vnd.ms-powerpoint",
|
|
445
|
+
sizeBytes: 348160,
|
|
446
|
+
direction: "inbound",
|
|
447
|
+
sender: "customer",
|
|
448
|
+
timestamp: at("15:13:00")
|
|
449
|
+
},
|
|
450
|
+
// Mídia enviada COMO arquivo: aí a Meta manda o nome, então é onde a extensão de música e de
|
|
451
|
+
// vídeo aparece na lista. Mandada como `image`/`audio`/`video` (acima e abaixo) ela chega sem
|
|
452
|
+
// nome nenhum, e o rótulo vira o id da mídia — como em produção.
|
|
453
|
+
{
|
|
454
|
+
id: "fixture-doc-mp3",
|
|
455
|
+
type: "document",
|
|
456
|
+
uploadId: "preview/documentos/musica-do-anuncio.mp3",
|
|
457
|
+
filename: "musica-do-anuncio.mp3",
|
|
458
|
+
mimeType: "audio/mpeg",
|
|
459
|
+
sizeBytes: 3145728,
|
|
460
|
+
direction: "inbound",
|
|
461
|
+
sender: "customer",
|
|
462
|
+
timestamp: at("15:14:00")
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: "fixture-doc-m4a",
|
|
466
|
+
type: "document",
|
|
467
|
+
uploadId: "preview/documentos/recado.m4a",
|
|
468
|
+
filename: "recado.m4a",
|
|
469
|
+
mimeType: "audio/mp4",
|
|
470
|
+
sizeBytes: 486400,
|
|
471
|
+
direction: "inbound",
|
|
472
|
+
sender: "customer",
|
|
473
|
+
timestamp: at("15:15:00")
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
id: "fixture-doc-mp4-file",
|
|
477
|
+
type: "document",
|
|
478
|
+
uploadId: "preview/documentos/video-do-produto.mp4",
|
|
479
|
+
filename: "video-do-produto.mp4",
|
|
480
|
+
mimeType: "video/mp4",
|
|
481
|
+
sizeBytes: 8388608,
|
|
482
|
+
direction: "inbound",
|
|
483
|
+
sender: "customer",
|
|
484
|
+
timestamp: at("15:16:00")
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
id: "fixture-doc-jpg",
|
|
488
|
+
type: "document",
|
|
489
|
+
uploadId: "preview/documentos/foto-do-recibo.jpg",
|
|
490
|
+
filename: "foto-do-recibo.jpg",
|
|
491
|
+
mimeType: "image/jpeg",
|
|
492
|
+
sizeBytes: 262144,
|
|
493
|
+
direction: "inbound",
|
|
494
|
+
sender: "customer",
|
|
495
|
+
timestamp: at("15:17:00")
|
|
496
|
+
},
|
|
497
|
+
// As espécies restantes que a Meta aceita, sem nome de arquivo — fecham a lista de tipos
|
|
498
|
+
// aceitos e exercitam o fallback de ícone pela família do mimeType.
|
|
499
|
+
{
|
|
500
|
+
id: "fixture-media-jpeg",
|
|
501
|
+
type: "image",
|
|
502
|
+
mediaId: "preview-image-jpeg",
|
|
503
|
+
mimeType: "image/jpeg",
|
|
504
|
+
sizeBytes: 138240,
|
|
505
|
+
direction: "inbound",
|
|
506
|
+
sender: "customer",
|
|
507
|
+
timestamp: at("15:18:00")
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
id: "fixture-media-3gp",
|
|
511
|
+
type: "video",
|
|
512
|
+
mediaId: "preview-video-3gp",
|
|
513
|
+
mimeType: "video/3gp",
|
|
514
|
+
sizeBytes: 921600,
|
|
515
|
+
direction: "inbound",
|
|
516
|
+
sender: "customer",
|
|
517
|
+
timestamp: at("15:19:00")
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
id: "fixture-media-aac",
|
|
521
|
+
type: "audio",
|
|
522
|
+
mediaId: "preview-audio-aac",
|
|
523
|
+
mimeType: "audio/aac",
|
|
524
|
+
sizeBytes: 76800,
|
|
525
|
+
direction: "inbound",
|
|
526
|
+
sender: "customer",
|
|
527
|
+
timestamp: at("15:20:00")
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
id: "fixture-media-amr",
|
|
531
|
+
type: "audio",
|
|
532
|
+
mediaId: "preview-audio-amr",
|
|
533
|
+
mimeType: "audio/amr",
|
|
534
|
+
sizeBytes: 24576,
|
|
535
|
+
direction: "inbound",
|
|
536
|
+
sender: "customer",
|
|
537
|
+
timestamp: at("15:21:00")
|
|
538
|
+
}
|
|
378
539
|
]
|
|
379
540
|
};
|
|
541
|
+
var LIBRARY_MEDIA_TYPES = /* @__PURE__ */ new Set(["document", "image", "video", "audio", "sticker"]);
|
|
542
|
+
var PREVIEW_DOCUMENTS = {
|
|
543
|
+
"5511944443333": (PREVIEW_MESSAGES["5511944443333"] ?? []).filter((message) => LIBRARY_MEDIA_TYPES.has(message.type)).map((message) => ({
|
|
544
|
+
// Mídia não tem `uploadId` na mensagem: o backend deriva a key do id da mídia na Meta, e o
|
|
545
|
+
// caminho abaixo imita esse formato para o download assinado receber algo do mesmo feitio.
|
|
546
|
+
id: message.uploadId ?? (message.mediaId ? `preview/inbound/${message.mediaId}` : message.id),
|
|
547
|
+
// Só documento chega nomeado. Para os outros o backend salva o id da mídia como rótulo —
|
|
548
|
+
// manter isso aqui é o que faz o ícone por família do mimeType ser exercitado de verdade.
|
|
549
|
+
filename: message.filename ?? message.mediaId ?? message.id,
|
|
550
|
+
mimeType: message.mimeType ?? "application/octet-stream",
|
|
551
|
+
sizeBytes: message.sizeBytes ?? 0,
|
|
552
|
+
source: message.sender,
|
|
553
|
+
linkedAt: message.timestamp
|
|
554
|
+
}))
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// src/preview/previewFileSamples.ts
|
|
558
|
+
var PREVIEW_FILE_SAMPLES = {
|
|
559
|
+
"application/pdf": "data:application/pdf;base64,JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgMjYwIDExMF0vQ29udGVudHMgNCAwIFIvUmVzb3VyY2VzPDwvRm9udDw8L0YxIDUgMCBSPj4+Pj4+ZW5kb2JqCjQgMCBvYmo8PC9MZW5ndGggNjI+PnN0cmVhbQpCVCAvRjEgMTMgVGYgMTggNTUgVGQgKERvY3VtZW50byBkZSBleGVtcGxvIGRvIHByZXZpZXcpIFRqIEVUCmVuZHN0cmVhbWVuZG9iago1IDAgb2JqPDwvVHlwZS9Gb250L1N1YnR5cGUvVHlwZTEvQmFzZUZvbnQvSGVsdmV0aWNhPj5lbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTIgMDAwMDAgbiAKMDAwMDAwMDEwMSAwMDAwMCBuIAowMDAwMDAwMjExIDAwMDAwIG4gCjAwMDAwMDAzMTcgMDAwMDAgbiAKdHJhaWxlcjw8L1NpemUgNi9Sb290IDEgMCBSPj4Kc3RhcnR4cmVmCjM3OAolJUVPRgo=",
|
|
560
|
+
"image/png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAAACXBIWXMAAAABAAAAAQBPJcTWAAAATElEQVR4nO3OMRHAIAAAMbhDWo2hCYPdu/xIh0RB5rPP+JN1O/AlVISKUBEqQkWoCBWhIlSEilARKkJFqAgVoSJUhIpQESpCRagIlRcgDwI25HmPQAAAAABJRU5ErkJggg==",
|
|
561
|
+
"image/jpeg": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD//gAQTGF2YzYyLjExLjEwMAD/2wBDAAgEBAQEBAUFBQUFBQYGBgYGBgYGBgYGBgYHBwcICAgHBwcGBgcHCAgICAkJCQgICAgJCQoKCgwMCwsODg4RERT/xABMAAEBAAAAAAAAAAAAAAAAAAAABAEBAQAAAAAAAAAAAAAAAAAAAAYQAQAAAAAAAAAAAAAAAAAAAAARAQAAAAAAAAAAAAAAAAAAAAD/wAARCAAwADADASIAAhEAAxEA/9oADAMBAAIRAxEAPwCUBRpwAAAAAAAAAAAAAAAB/9k=",
|
|
562
|
+
"image/webp": "data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
|
|
563
|
+
"video/mp4": "data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAANdbW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAASwAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAoh0cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAASwAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAEAAAABAAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAAEsAAAIAAABAAAAAAIAbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAAoAAAAEABVxAAAAAAALWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAABq21pbmYAAAAUdm1oZAAAAAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAWtzdGJsAAAAv3N0c2QAAAAAAAAAAQAAAK9hdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAEAAQABIAAAASAAAAAAAAAABFUxhdmM2Mi4xMS4xMDAgbGlieDI2NAAAAAAAAAAAAAAAGP//AAAANWF2Y0MBZAAK/+EAGGdkAAqs2UQmwEQAAAMABAAAAwBQPEiWWAEABmjr48siwP34+AAAAAAQcGFzcAAAAAEAAAABAAAAFGJ0cnQAAAAAAABO2gAAAAAAAAAYc3R0cwAAAAAAAAABAAAAAwAABAAAAAAUc3RzcwAAAAAAAAABAAAAAQAAAChjdHRzAAAAAAAAAAMAAAABAAAIAAAAAAEAAAwAAAAAAQAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAMAAAABAAAAIHN0c3oAAAAAAAAAAAAAAAMAAALbAAAADgAAAAwAAAAUc3RjbwAAAAAAAAABAAADjQAAAGF1ZHRhAAAAWW1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALGlsc3QAAAAkqXRvbwAAABxkYXRhAAAAAQAAAABMYXZmNjIuMy4xMDAAAAAIZnJlZQAAAv1tZGF0AAACrgYF//+q3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2NSByMzIyMiBiMzU2MDVhIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAyNSAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTIgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTEwIHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAJWWIhAA///7mdfgUoKykhh+DdK6xVynnAWznTQ4fVSgMZ1FwY28AAAAKQZoibEN//qePiAAAAAgBnkF5Df8HfQ==",
|
|
564
|
+
"audio/mpeg": "data:audio/mpeg;base64,SUQzBAAAAAAAIlRTU0UAAAAOAAADTGF2ZjYyLjMuMTAwAAAAAAAAAAAAAAD/40jAAAAAAAAAAAAASW5mbwAAAA8AAAAFAAAGwABVVVVVVVVVVVVVVVVVVVVVVVVVgICAgICAgICAgICAgICAgICAgICqqqqqqqqqqqqqqqqqqqqqqqqqqtXV1dXV1dXV1dXV1dXV1dXV1dXV//////////////////////////8AAAAATGF2YzYyLjExAAAAAAAAAAAAAAAAJALAAAAAAAAABsDR4nNFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU=",
|
|
565
|
+
"audio/ogg": "data:audio/ogg;base64,T2dnUwACAAAAAAAAAABXDTmmAAAAAKMnDKEBE09wdXNIZWFkAQE4AYC7AAAAAABPZ2dTAAAAAAAAAAAAAFcNOaYBAAAAZlPuKwE9T3B1c1RhZ3MMAAAATGF2ZjYyLjMuMTAwAQAAAB0AAABlbmNvZGVyPUxhdmM2Mi4xMS4xMDAgbGlib3B1c09nZ1MABLgmAAAAAAAAVw05pgIAAAA3uL6lCw8REhISEhISEhISeAvkwTbsxY2MSUaZpCaweAfJcifhROpV8fDA1d3pUmB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOA=",
|
|
566
|
+
"audio/mp4": "data:audio/mp4;base64,AAAAHGZ0eXBNNEEgAAACAE00QSBpc29taXNvMgAAAAhmcmVlAAAAQW1kYXTeAgBMYXZjNjIuMTEuMTAwAAIwQA4BGCAHARggBwEYIAcBGCAHARggBwEYIAcBGCAHARggBwEYIAcAAAMibW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAAMgAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAk10cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAAMgAAAAAAAAAAAAAAAEBAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAADIAAAEAAABAAAAAAHFbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAACsRAAAJnRVxAAAAAAALWhkbHIAAAAAAAAAAHNvdW4AAAAAAAAAAAAAAABTb3VuZEhhbmRsZXIAAAABcG1pbmYAAAAQc21oZAAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABNHN0YmwAAABqc3RzZAAAAAAAAAABAAAAWm1wNGEAAAAAAAAAAQAAAAAAAAAAAAEAEAAAAACsRAAAAAAANmVzZHMAAAAAA4CAgCUAAQAEgICAF0AVAAAAAAB9AAAAB/oFgICABRIIVuUABoCAgAECAAAAIHN0dHMAAAAAAAAAAgAAAAkAAAQAAAAAAQAAAnQAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAoAAAABAAAAPHN0c3oAAAAAAAAAAAAAAAoAAAAVAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAAFHN0Y28AAAAAAAAAAQAAACwAAAAac2dwZAEAAAByb2xsAAAAAgAAAAH//wAAABxzYmdwAAAAAHJvbGwAAAABAAAACgAAAAEAAABhdWR0YQAAAFltZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAACxpbHN0AAAAJKl0b28AAAAcZGF0YQAAAAEAAAAATGF2ZjYyLjMuMTAw",
|
|
567
|
+
"audio/aac": "data:audio/aac;base64,//FQQAOf/N4CAExhdmM2Mi4xMS4xMDAAAjBADv/xUEABf/wBGCAH//FQQAF//AEYIAf/8VBAAX/8ARggB//xUEABf/wBGCAH//FQQAF//AEYIAf/8VBAAX/8ARggB//xUEABf/wBGCAH//FQQAF//AEYIAf/8VBAAX/8ARggBw==",
|
|
568
|
+
"text/plain": "data:text/plain;base64,TGlzdGEgZGUgY29tcHJhcwotIGFycm96IDJrZwotIG9sZW8gMXVuCg==",
|
|
569
|
+
"text/csv": "data:text/csv;base64,cHJvZHV0byxxdWFudGlkYWRlLHByZWNvCmFycm96LDIsMjQuOTAKb2xlbywxLDcuNTAK",
|
|
570
|
+
"application/zip": "data:application/zip;base64,UEsDBBQAAAAIANFO/FwtmGOxGwAAABkAAAARAAAAY29tcHJvdmFudGUtMS50eHRLzs8tKMovS8wrSVVISVUoSExPzE3NK8nnAgBQSwMEFAAAAAgA0U78XCftV8YWAAAAFAAAABEAAABjb21wcm92YW50ZS0yLnR4dCtOTS/NS8lXSM7PLSjKL0vMK0nlAgBQSwECFAMUAAAACADRTvxcLZhjsRsAAAAZAAAAEQAAAAAAAAAAAAAAgAEAAAAAY29tcHJvdmFudGUtMS50eHRQSwECFAMUAAAACADRTvxcJ+1XxhYAAAAUAAAAEQAAAAAAAAAAAAAAgAFKAAAAY29tcHJvdmFudGUtMi50eHRQSwUGAAAAAAIAAgB+AAAAjwAAAAAA",
|
|
571
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,UEsDBBQAAAAIANFO/Fx5bjPX6AAAAK0BAAATAAAAW0NvbnRlbnRfVHlwZXNdLnhtbH1QyU7DMBD9FWuuKHHggBCK0wPLETiUDxjZk8SqN3nc0v49Tlt6QIXjzFv1+tXeO7GjzDYGBbdtB4KCjsaGScHn+rV5AMEFg0EXAyk4EMNq6NeHRCyqNrCCuZT0KCXrmTxyGxOFiowxeyz1zJNMqDc4kbzrunupYygUSlMWDxj6Zxpx64p42df3qUcmxyCeTsQlSwGm5KzGUnG5C+ZXSnNOaKvyyOHZJr6pBJBXExbk74Cz7r0Ok60h8YG5vKGvLPkVs5Em6q2vyvZ/mys94zhaTRf94pZy1MRcF/euvSAebfjpL49zD99QSwMEFAAAAAgA0U78XJv9N+qtAAAAKQEAAAsAAABfcmVscy8ucmVsc43POw7CMAwG4KtE3mlaBoRQ0y4IqSsqB7ASN61oHkrCo7cnAwNFDIy2f3+W6/ZpZnanECdnBVRFCYysdGqyWsClP232wGJCq3B2lgQsFKFt6jPNmPJKHCcfWTZsFDCm5A+cRzmSwVg4TzZPBhcMplwGzT3KK2ri27Lc8fBpwNpknRIQOlUB6xdP/9huGCZJRydvhmz6ceIrkWUMmpKAhwuKq3e7yCzwpuarF5sXUEsDBBQAAAAIANFO/FyraIuoqgAAAOcAAAARAAAAd29yZC9kb2N1bWVudC54bWxFjksOwjAMBa8SZU9TWCBUNWWBxAngAKExbaTGjpzQz+1JyoLNPFt+Grm9rn4SM3B0hFoeq1oKwJ6sw0HL5+N+uEgRk0FrJkLQcoMor127NJb6jwdMIgswNouWY0qhUSr2I3gTKwqA+fYm9ibllQe1ENvA1EOM2e8ndarrs/LGoSzKF9mtZCjggtTdCBObRMKCgBV8mPJIIjDMDpZWlU4h7ww7fx71/7H7AlBLAQIUAxQAAAAIANFO/Fx5bjPX6AAAAK0BAAATAAAAAAAAAAAAAACAAQAAAABbQ29udGVudF9UeXBlc10ueG1sUEsBAhQDFAAAAAgA0U78XJv9N+qtAAAAKQEAAAsAAAAAAAAAAAAAAIABGQEAAF9yZWxzLy5yZWxzUEsBAhQDFAAAAAgA0U78XKtoi6iqAAAA5wAAABEAAAAAAAAAAAAAAIAB7wEAAHdvcmQvZG9jdW1lbnQueG1sUEsFBgAAAAADAAMAuQAAAMgCAAAAAA==",
|
|
572
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,UEsDBBQAAAAIANFO/FxuYbgN/gAAAC0CAAATAAAAW0NvbnRlbnRfVHlwZXNdLnhtbK2RzU7DMBCEX8XytYqdckAIJe2BnyNwKA+w2JvEiv/kdUv69jhp4YAKXDit7JnZb2Q328lZdsBEJviWr0XNGXoVtPF9y193j9UNZ5TBa7DBY8uPSHy7aXbHiMRK1lPLh5zjrZSkBnRAIkT0RelCcpDLMfUyghqhR3lV19dSBZ/R5yrPO/imuccO9jazh6lcn3oktMTZ3ck4s1oOMVqjIBddHrz+RqnOBFGSi4cGE2lVDFxeJMzKz4Bz7rk8TDIa2Quk/ASuuORk5XtI41sIo/h9yYWWoeuMQh3U3pWIoJgQNA2I2VmxTOHA+NXf/MVMchnrfy7ytf+zh1y+e/MBUEsDBBQAAAAIANFO/FyY2uuLrgAAACcBAAALAAAAX3JlbHMvLnJlbHONz8EOgjAMBuBXWXqXgQdjDIOLMeFq8AHmVgYB1mWbCm/vjmI8eGz69/vTsl7miT3Rh4GsgCLLgaFVpAdrBNzay+4ILERptZzIooAVA9RVecVJxnQS+sEFlgwbBPQxuhPnQfU4y5CRQ5s2HflZxjR6w51UozTI93l+4P7TgK3JGi3AN7oA1q4O/7Gp6waFZ1KPGW38UfGVSLL0BqOAZeIv8uOdaMwSCrwq+ebB6g1QSwMEFAAAAAgA0U78XEkKfby5AAAAGwEAAA8AAAB4bC93b3JrYm9vay54bWyNj7sOwjAMRX8l8g4pDAhVbVkQEhsDfEBoXBrR2JUdXn9PeO1Mfuke31ut7nEwVxQNTDXMpgUYpJZ9oFMNh/1msgSjyZF3AxPW8ECFVVPdWM5H5rPJctIa+pTG0lpte4xOpzwi5UvHEl3Ko5ysjoLOa4+Y4mDnRbGw0QWCD6GUfxjcdaHFNbeXiJQ+EMHBpWxe+zAqNNX7g36rIRez6R364DkHee22PucEI2XIjWz9DGxT2Z/M/pI1T1BLAwQUAAAACADRTvxcWv2Ca7EAAAAoAQAAGgAAAHhsL19yZWxzL3dvcmtib29rLnhtbC5yZWxzjc/JCsJADAbgVxlyt2k9iEinXkToVeoDDNN0oZ2Fybj07R08iAUPnkLyky+kPD7NLO4UeHRWQpHlIMhq1462l3Btzps9CI7Ktmp2liQsxHCsygvNKqYVHkbPIhmWJQwx+gMi64GM4sx5sinpXDAqpjb06JWeVE+4zfMdhm8D1qaoWwmhbgsQzeLpH9t13ajp5PTNkI0/TuDDhYkHophQFXqKEj4jxncpsqQCViWuPqxeUEsDBBQAAAAIANFO/FwcNOTnuQAAAP8AAAAYAAAAeGwvd29ya3NoZWV0cy9zaGVldDEueG1sTY5RTsMwDIavEvmdudsDmlCSaQhxAdgBotasEY1T2VYHO/3SgRAvlv1Zn/37w1eZ3EKiuXKA7aYDR9zXIfM5wOn99WEPTi3xkKbKFOCbFA7RX6p86khkrvmsAUaz+QlR+5FK0k2didvmo0pJ1kY5o85CabhLZcJd1z1iSZkh+jt7SZail3px0nI02q/NcQvOAmSeMtObSeNZo7eYROrVo0WPK8D+V3he1SXuPC4/FNvJVv/9wL/w8QZQSwECFAMUAAAACADRTvxcbmG4Df4AAAAtAgAAEwAAAAAAAAAAAAAAgAEAAAAAW0NvbnRlbnRfVHlwZXNdLnhtbFBLAQIUAxQAAAAIANFO/FyY2uuLrgAAACcBAAALAAAAAAAAAAAAAACAAS8BAABfcmVscy8ucmVsc1BLAQIUAxQAAAAIANFO/FxJCn28uQAAABsBAAAPAAAAAAAAAAAAAACAAQYCAAB4bC93b3JrYm9vay54bWxQSwECFAMUAAAACADRTvxcWv2Ca7EAAAAoAQAAGgAAAAAAAAAAAAAAgAHsAgAAeGwvX3JlbHMvd29ya2Jvb2sueG1sLnJlbHNQSwECFAMUAAAACADRTvxcHDTk57kAAAD/AAAAGAAAAAAAAAAAAAAAgAHVAwAAeGwvd29ya3NoZWV0cy9zaGVldDEueG1sUEsFBgAAAAAFAAUARQEAAMQEAAAAAA=="
|
|
573
|
+
};
|
|
574
|
+
var FAMILY_FALLBACK = {
|
|
575
|
+
image: "image/png",
|
|
576
|
+
video: "video/mp4",
|
|
577
|
+
audio: "audio/mpeg",
|
|
578
|
+
text: "text/plain"
|
|
579
|
+
};
|
|
580
|
+
function resolvePreviewFileSample(mimeType, filename) {
|
|
581
|
+
const normalized = (mimeType ?? "").split(";")[0].trim().toLowerCase();
|
|
582
|
+
const exact = PREVIEW_FILE_SAMPLES[normalized];
|
|
583
|
+
if (exact) return exact;
|
|
584
|
+
const family = FAMILY_FALLBACK[normalized.split("/")[0] ?? ""];
|
|
585
|
+
const byFamily = family ? PREVIEW_FILE_SAMPLES[family] : void 0;
|
|
586
|
+
if (byFamily) return byFamily;
|
|
587
|
+
const note = `Preview do SDK: nao existe amostra binaria valida para ${normalized || "tipo desconhecido"}${filename ? ` (${filename})` : ""}. Na tela real este arquivo vem do storage.`;
|
|
588
|
+
return `data:text/plain;charset=utf-8,${encodeURIComponent(note)}`;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/preview/previewMediaSource.ts
|
|
592
|
+
var blobUrlCache = /* @__PURE__ */ new Map();
|
|
593
|
+
function toBlob(dataUrl) {
|
|
594
|
+
const [head, payload] = dataUrl.split(",");
|
|
595
|
+
const mimeType = head.replace(/^data:/, "").replace(/;base64$/, "").replace(/;charset=.*$/, "");
|
|
596
|
+
if (!head.endsWith(";base64")) {
|
|
597
|
+
return new Blob([decodeURIComponent(payload)], { type: mimeType || "text/plain" });
|
|
598
|
+
}
|
|
599
|
+
const binary = atob(payload);
|
|
600
|
+
const bytes = new Uint8Array(binary.length);
|
|
601
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
602
|
+
return new Blob([bytes], { type: mimeType });
|
|
603
|
+
}
|
|
604
|
+
function previewFileUrl(mimeType, filename) {
|
|
605
|
+
const dataUrl = resolvePreviewFileSample(mimeType, filename);
|
|
606
|
+
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return dataUrl;
|
|
607
|
+
const cached = blobUrlCache.get(dataUrl);
|
|
608
|
+
if (cached) return cached;
|
|
609
|
+
const blobUrl = URL.createObjectURL(toBlob(dataUrl));
|
|
610
|
+
blobUrlCache.set(dataUrl, blobUrl);
|
|
611
|
+
return blobUrl;
|
|
612
|
+
}
|
|
613
|
+
function previewFileBase64(mimeType, filename) {
|
|
614
|
+
const dataUrl = resolvePreviewFileSample(mimeType, filename);
|
|
615
|
+
const [head, payload] = dataUrl.split(",");
|
|
616
|
+
const declared = head.replace(/^data:/, "").replace(/;base64$/, "").replace(/;charset=.*$/, "");
|
|
617
|
+
if (head.endsWith(";base64")) return { mimeType: declared, data: payload };
|
|
618
|
+
return { mimeType: declared, data: btoa(decodeURIComponent(payload)) };
|
|
619
|
+
}
|
|
620
|
+
function createPreviewMediaResolver() {
|
|
621
|
+
const byId = /* @__PURE__ */ new Map();
|
|
622
|
+
for (const documents of Object.values(PREVIEW_DOCUMENTS)) {
|
|
623
|
+
for (const document of documents) {
|
|
624
|
+
byId.set(document.id, { mimeType: document.mimeType, filename: document.filename });
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return async (message) => {
|
|
628
|
+
const reference = message.uploadId ?? (message.mediaId ? `preview/inbound/${message.mediaId}` : void 0);
|
|
629
|
+
if (!reference) return null;
|
|
630
|
+
const known = byId.get(reference);
|
|
631
|
+
return previewFileUrl(known?.mimeType ?? message.mimeType, known?.filename ?? message.filename);
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// src/preview/createMockConversationsApi.ts
|
|
636
|
+
var DEFAULT_LATENCY_MS = 120;
|
|
637
|
+
var PREVIEW_AGENT_ID = "preview-agent";
|
|
638
|
+
var PREVIEW_TEMPLATES = [
|
|
639
|
+
{ name: "retomada_atendimento", language: "pt_BR", status: "APPROVED", category: "UTILITY" },
|
|
640
|
+
{ name: "lembrete_documentos", language: "pt_BR", status: "APPROVED", category: "UTILITY" },
|
|
641
|
+
{ name: "promocao_taxa", language: "pt_BR", status: "PENDING", category: "MARKETING" }
|
|
642
|
+
];
|
|
643
|
+
function createMockConversationsApi(params) {
|
|
644
|
+
const latencyMs = params.latencyMs ?? DEFAULT_LATENCY_MS;
|
|
645
|
+
async function withLatency(produce) {
|
|
646
|
+
await new Promise((resolve) => setTimeout(resolve, latencyMs));
|
|
647
|
+
return produce();
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
// Devolve a forma paginada, não o array puro: é a que o contrato passou a oferecer e a que
|
|
651
|
+
// permite o preview desenhar controles de página. O total é contado ANTES do corte — depois
|
|
652
|
+
// dele seria sempre o tamanho da página, e a paginação nunca sairia da primeira.
|
|
653
|
+
fetchConversations(fetchParams) {
|
|
654
|
+
return withLatency(() => {
|
|
655
|
+
const conversations = params.store.listConversations({
|
|
656
|
+
waitingHuman: fetchParams?.waitingHuman,
|
|
657
|
+
search: fetchParams?.search
|
|
658
|
+
});
|
|
659
|
+
const limit = fetchParams?.limit ?? conversations.length;
|
|
660
|
+
const page = fetchParams?.page ?? 1;
|
|
661
|
+
return {
|
|
662
|
+
conversations: conversations.slice((page - 1) * limit, page * limit),
|
|
663
|
+
total: conversations.length
|
|
664
|
+
};
|
|
665
|
+
});
|
|
666
|
+
},
|
|
667
|
+
fetchMessages(conversationId, fetchParams) {
|
|
668
|
+
return withLatency(() => {
|
|
669
|
+
const messages = params.store.listMessages(conversationId);
|
|
670
|
+
const limit = fetchParams?.limit;
|
|
671
|
+
return limit ? messages.slice(-limit) : messages;
|
|
672
|
+
});
|
|
673
|
+
},
|
|
674
|
+
sendMessage(conversationId, text) {
|
|
675
|
+
return withLatency(
|
|
676
|
+
() => params.store.appendMessage({ conversationId, content: text, direction: "outbound", sender: "agent" })
|
|
677
|
+
);
|
|
678
|
+
},
|
|
679
|
+
sendMedia(conversationId, data) {
|
|
680
|
+
return withLatency(
|
|
681
|
+
() => params.store.appendMessage({
|
|
682
|
+
conversationId,
|
|
683
|
+
content: data.caption ?? data.filename,
|
|
684
|
+
direction: "outbound",
|
|
685
|
+
sender: "agent"
|
|
686
|
+
})
|
|
687
|
+
);
|
|
688
|
+
},
|
|
689
|
+
sendTemplate(conversationId, data) {
|
|
690
|
+
return withLatency(() => {
|
|
691
|
+
params.store.appendMessage({
|
|
692
|
+
conversationId,
|
|
693
|
+
// Sem nome, o host está pedindo o template padrão do backend — o mock representa isso
|
|
694
|
+
// pelo que o atendente veria, não por um nome inventado.
|
|
695
|
+
content: `[template] ${data.templateName ?? PREVIEW_TEMPLATES[0]?.name ?? "padrao"}`,
|
|
696
|
+
direction: "outbound",
|
|
697
|
+
sender: "agent"
|
|
698
|
+
});
|
|
699
|
+
});
|
|
700
|
+
},
|
|
701
|
+
markRead(conversationId) {
|
|
702
|
+
return withLatency(() => params.store.markRead(conversationId));
|
|
703
|
+
},
|
|
704
|
+
getContext(conversationId) {
|
|
705
|
+
return withLatency(() => {
|
|
706
|
+
const conversation = params.store.listConversations().find((item) => item.id === conversationId);
|
|
707
|
+
return {
|
|
708
|
+
currentState: conversation?.currentState ?? "unknown",
|
|
709
|
+
mode: conversation?.mode ?? "bot",
|
|
710
|
+
preview: true
|
|
711
|
+
};
|
|
712
|
+
});
|
|
713
|
+
},
|
|
714
|
+
/**
|
|
715
|
+
* Espelha o backend em busca, filtro de origem, ordenação E paginação. Mock que ignora params
|
|
716
|
+
* faz o painel parecer quebrado aqui e, pior, esconde o caso em que o backend também os ignora
|
|
717
|
+
* — foi exatamente assim que o filtro de origem passou a existir só no contrato.
|
|
718
|
+
*/
|
|
719
|
+
getDocuments(conversationId, documentParams) {
|
|
720
|
+
return withLatency(() => {
|
|
721
|
+
let documents = [...PREVIEW_DOCUMENTS[conversationId] ?? []];
|
|
722
|
+
const search = documentParams?.search?.trim().toLowerCase();
|
|
723
|
+
if (search) {
|
|
724
|
+
documents = documents.filter((document) => document.filename.toLowerCase().includes(search));
|
|
725
|
+
}
|
|
726
|
+
const source = documentParams?.source;
|
|
727
|
+
if (source === "team") {
|
|
728
|
+
documents = documents.filter((document) => document.source === "agent" || document.source === "bot");
|
|
729
|
+
} else if (source) {
|
|
730
|
+
documents = documents.filter((document) => document.source === source);
|
|
731
|
+
}
|
|
732
|
+
documents.sort(
|
|
733
|
+
(left, right) => documentParams?.sortDirection === "asc" ? left.linkedAt.localeCompare(right.linkedAt) : right.linkedAt.localeCompare(left.linkedAt)
|
|
734
|
+
);
|
|
735
|
+
const total = documents.length;
|
|
736
|
+
const limit = documentParams?.limit ?? total;
|
|
737
|
+
const page = documentParams?.page ?? 1;
|
|
738
|
+
return { documents: documents.slice((page - 1) * limit, page * limit), total };
|
|
739
|
+
});
|
|
740
|
+
},
|
|
741
|
+
/**
|
|
742
|
+
* Zip de mentira: um texto listando o que entraria. Basta para exercitar seleção, botão e o
|
|
743
|
+
* caminho de download no preview, sem arrastar uma lib de compactação para o pacote.
|
|
744
|
+
*/
|
|
745
|
+
downloadDocumentsArchive(conversationId, uploadIds) {
|
|
746
|
+
return withLatency(() => {
|
|
747
|
+
const known = PREVIEW_DOCUMENTS[conversationId] ?? [];
|
|
748
|
+
const names = uploadIds.map((id) => known.find((document) => document.id === id)?.filename ?? id);
|
|
749
|
+
return new Blob([`preview: ${names.length} arquivo(s)
|
|
750
|
+
${names.join("\n")}`], { type: "application/zip" });
|
|
751
|
+
});
|
|
752
|
+
},
|
|
753
|
+
/** Junta as bibliotecas de todas as conversas do fixture, com a origem de cada arquivo. */
|
|
754
|
+
getAllDocuments(documentParams) {
|
|
755
|
+
return withLatency(() => {
|
|
756
|
+
let all = Object.entries(PREVIEW_DOCUMENTS).flatMap(
|
|
757
|
+
([conversationId, docs]) => docs.map((document) => ({ ...document, conversationId }))
|
|
758
|
+
);
|
|
759
|
+
const search = documentParams?.search?.trim().toLowerCase();
|
|
760
|
+
if (search) {
|
|
761
|
+
const digits = search.replace(/\D/g, "");
|
|
762
|
+
all = all.filter(
|
|
763
|
+
(document) => document.filename.toLowerCase().includes(search) || digits !== "" && document.conversationId.includes(digits)
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
const source = documentParams?.source;
|
|
767
|
+
if (source === "team") {
|
|
768
|
+
all = all.filter((document) => document.source === "agent" || document.source === "bot");
|
|
769
|
+
} else if (source) {
|
|
770
|
+
all = all.filter((document) => document.source === source);
|
|
771
|
+
}
|
|
772
|
+
all.sort(
|
|
773
|
+
(left, right) => documentParams?.sortDirection === "asc" ? left.linkedAt.localeCompare(right.linkedAt) : right.linkedAt.localeCompare(left.linkedAt)
|
|
774
|
+
);
|
|
775
|
+
const total = all.length;
|
|
776
|
+
const limit = documentParams?.limit ?? total;
|
|
777
|
+
const page = documentParams?.page ?? 1;
|
|
778
|
+
return { documents: all.slice((page - 1) * limit, page * limit), total };
|
|
779
|
+
});
|
|
780
|
+
},
|
|
781
|
+
// Devolve os bytes DO TIPO do documento, não uma imagem para tudo: antes, abrir um PDF entregava
|
|
782
|
+
// um PNG rotulado `application/pdf` e o leitor recusava o arquivo. O `uploadId` é a única pista
|
|
783
|
+
// que o contrato dá, então o tipo vem da própria biblioteca.
|
|
784
|
+
getDocumentUrl(uploadId) {
|
|
785
|
+
return withLatency(() => {
|
|
786
|
+
const found = Object.values(PREVIEW_DOCUMENTS).flat().find((document) => document.id === uploadId);
|
|
787
|
+
return previewFileUrl(found?.mimeType, found?.filename);
|
|
788
|
+
});
|
|
789
|
+
},
|
|
790
|
+
// Caminho da mídia ainda não ingerida: o backend busca na Meta e devolve base64. Resolve pelo
|
|
791
|
+
// id para a bolha receber os bytes DO TIPO dela — devolvendo um PNG para todo id, vídeo e áudio
|
|
792
|
+
// apareciam quebrados na thread mesmo havendo amostra válida do formato.
|
|
793
|
+
getMediaProxyUrl(mediaId) {
|
|
794
|
+
return withLatency(() => {
|
|
795
|
+
const found = Object.values(PREVIEW_DOCUMENTS).flat().find((document) => document.id === `preview/inbound/${mediaId}`);
|
|
796
|
+
return previewFileBase64(found?.mimeType, found?.filename);
|
|
797
|
+
});
|
|
798
|
+
},
|
|
799
|
+
takeover(conversationId) {
|
|
800
|
+
return withLatency(
|
|
801
|
+
() => params.store.setMode({ conversationId, mode: "human", assignedUserId: PREVIEW_AGENT_ID })
|
|
802
|
+
);
|
|
803
|
+
},
|
|
804
|
+
release(conversationId) {
|
|
805
|
+
return withLatency(() => params.store.setMode({ conversationId, mode: "bot" }));
|
|
806
|
+
},
|
|
807
|
+
// Encerrar devolve ao bot como o release, e é de propósito: a diferença entre os dois é a
|
|
808
|
+
// despedida, que o host manda antes de chamar aqui. O mock não a inventa.
|
|
809
|
+
finalize(conversationId) {
|
|
810
|
+
return withLatency(() => params.store.setMode({ conversationId, mode: "bot" }));
|
|
811
|
+
},
|
|
812
|
+
markAllRead() {
|
|
813
|
+
return withLatency(() => {
|
|
814
|
+
for (const conversation of params.store.listConversations()) {
|
|
815
|
+
params.store.markRead(conversation.id);
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
},
|
|
819
|
+
listTemplates() {
|
|
820
|
+
return withLatency(() => [...PREVIEW_TEMPLATES]);
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/preview/createMockSSEProvider.ts
|
|
826
|
+
function createMockSSEProvider(params) {
|
|
827
|
+
function connect(channel) {
|
|
828
|
+
const source = createMockEventSource();
|
|
829
|
+
const unsubscribe = params.store.subscribe(channel, (emission) => {
|
|
830
|
+
source.emit(emission.event, emission.payload);
|
|
831
|
+
});
|
|
832
|
+
const close = source.close.bind(source);
|
|
833
|
+
source.close = () => {
|
|
834
|
+
unsubscribe();
|
|
835
|
+
close();
|
|
836
|
+
};
|
|
837
|
+
return source;
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
connectConversationStream(conversationId) {
|
|
841
|
+
return connect(conversationChannel(conversationId));
|
|
842
|
+
},
|
|
843
|
+
connectGlobalStream() {
|
|
844
|
+
return connect(GLOBAL_CHANNEL);
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
}
|
|
380
848
|
|
|
381
849
|
// src/preview/ConversationPreview.tsx
|
|
382
850
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
383
851
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
852
|
+
function mediaTypeOf(mimeType) {
|
|
853
|
+
if (mimeType.startsWith("image/")) return "image";
|
|
854
|
+
if (mimeType.startsWith("video/")) return "video";
|
|
855
|
+
if (mimeType.startsWith("audio/")) return "audio";
|
|
856
|
+
return "document";
|
|
857
|
+
}
|
|
384
858
|
var GROUPING_WINDOW_MS = 5 * 60 * 1e3;
|
|
859
|
+
var FOLLOW_UP_REFRESH_MS = [400, 1200, 3e3];
|
|
385
860
|
function decorate(messages) {
|
|
386
861
|
return messages.map((message, index) => {
|
|
387
862
|
const previous = index > 0 ? messages[index - 1] : void 0;
|
|
@@ -394,23 +869,53 @@ function decorate(messages) {
|
|
|
394
869
|
};
|
|
395
870
|
});
|
|
396
871
|
}
|
|
872
|
+
function statusOf(error) {
|
|
873
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
874
|
+
const candidate = error;
|
|
875
|
+
const value = candidate.status ?? candidate.statusCode;
|
|
876
|
+
return typeof value === "number" ? value : void 0;
|
|
877
|
+
}
|
|
878
|
+
function isNotFound(error) {
|
|
879
|
+
return statusOf(error) === 404;
|
|
880
|
+
}
|
|
881
|
+
function describeLoadFailure(error) {
|
|
882
|
+
const status = statusOf(error);
|
|
883
|
+
if (status === 401 || status === 403) {
|
|
884
|
+
return "Sem sess\xE3o de administrador nesta aba: a mensagem \xE9 entregue no webhook, mas o transcript n\xE3o pode ser lido. Entre no painel nesta mesma aba e reabra o simulador.";
|
|
885
|
+
}
|
|
886
|
+
if (error instanceof Error && error.message) return `N\xE3o foi poss\xEDvel ler o transcript: ${error.message}`;
|
|
887
|
+
return "N\xE3o foi poss\xEDvel ler o transcript da conversa.";
|
|
888
|
+
}
|
|
397
889
|
function ConversationPreview({
|
|
398
890
|
client,
|
|
399
891
|
sse,
|
|
400
892
|
conversationId,
|
|
401
893
|
loadMessages,
|
|
402
|
-
placeholder
|
|
894
|
+
placeholder,
|
|
895
|
+
pollIntervalMs,
|
|
896
|
+
uploadMedia
|
|
403
897
|
}) {
|
|
404
898
|
const [messages, setMessages] = useState([]);
|
|
405
899
|
const [failure, setFailure] = useState(void 0);
|
|
900
|
+
const [loadFailure, setLoadFailure] = useState(void 0);
|
|
901
|
+
const [isRecording, setIsRecording] = useState(false);
|
|
902
|
+
const [pendingLocal, setPendingLocal] = useState([]);
|
|
406
903
|
const loadMessagesRef = useRef(loadMessages);
|
|
407
904
|
const bottomRef = useRef(null);
|
|
408
905
|
loadMessagesRef.current = loadMessages;
|
|
409
906
|
const refresh = useCallback(async () => {
|
|
410
907
|
try {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
908
|
+
const loaded = await loadMessagesRef.current(conversationId);
|
|
909
|
+
setMessages(loaded);
|
|
910
|
+
setLoadFailure(void 0);
|
|
911
|
+
if (loaded.length > 0) setPendingLocal([]);
|
|
912
|
+
} catch (error) {
|
|
913
|
+
if (isNotFound(error)) {
|
|
914
|
+
setMessages([]);
|
|
915
|
+
setLoadFailure(void 0);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
setLoadFailure(describeLoadFailure(error));
|
|
414
919
|
}
|
|
415
920
|
}, [conversationId]);
|
|
416
921
|
useEffect(() => {
|
|
@@ -428,18 +933,70 @@ function ConversationPreview({
|
|
|
428
933
|
};
|
|
429
934
|
}, [sse, conversationId, refresh]);
|
|
430
935
|
useEffect(() => {
|
|
431
|
-
|
|
936
|
+
if (!pollIntervalMs) return;
|
|
937
|
+
const timer = setInterval(() => void refresh(), pollIntervalMs);
|
|
938
|
+
return () => clearInterval(timer);
|
|
939
|
+
}, [pollIntervalMs, refresh]);
|
|
940
|
+
useEffect(() => {
|
|
941
|
+
bottomRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
432
942
|
}, [messages]);
|
|
433
|
-
const rendered = useMemo(() => decorate(messages), [messages]);
|
|
943
|
+
const rendered = useMemo(() => decorate([...messages, ...pendingLocal]), [messages, pendingLocal]);
|
|
944
|
+
async function refreshWithFollowUps() {
|
|
945
|
+
await refresh();
|
|
946
|
+
for (const atraso of FOLLOW_UP_REFRESH_MS) {
|
|
947
|
+
setTimeout(() => void refresh(), atraso);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
434
950
|
async function handleSend(text) {
|
|
435
951
|
setFailure(void 0);
|
|
436
952
|
try {
|
|
437
953
|
await client.sendText(text);
|
|
438
|
-
|
|
954
|
+
setPendingLocal((current) => [
|
|
955
|
+
...current,
|
|
956
|
+
{
|
|
957
|
+
id: `local-${current.length}-${text.length}`,
|
|
958
|
+
type: "text",
|
|
959
|
+
content: text,
|
|
960
|
+
// Do ponto de vista do servidor, mensagem do cliente é inbound — é assim que ela aparece
|
|
961
|
+
// como "minha" nesta visão.
|
|
962
|
+
direction: "inbound",
|
|
963
|
+
sender: "customer",
|
|
964
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
965
|
+
status: "sent"
|
|
966
|
+
}
|
|
967
|
+
]);
|
|
968
|
+
await refreshWithFollowUps();
|
|
439
969
|
} catch (error) {
|
|
440
970
|
setFailure(error instanceof Error ? error.message : "Falha ao entregar a mensagem no webhook.");
|
|
441
971
|
}
|
|
442
972
|
}
|
|
973
|
+
async function handleInteractiveSelect(selection) {
|
|
974
|
+
setFailure(void 0);
|
|
975
|
+
const reply = { id: selection.option.id, title: selection.option.title };
|
|
976
|
+
try {
|
|
977
|
+
await (selection.kind === "button" ? client.sendButtonReply(reply) : client.sendListReply(reply));
|
|
978
|
+
await refreshWithFollowUps();
|
|
979
|
+
} catch (error) {
|
|
980
|
+
setFailure(error instanceof Error ? error.message : "Falha ao entregar a resposta no webhook.");
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
const uploadFile = uploadMedia ?? client.uploadMedia;
|
|
984
|
+
async function handleAttach(file) {
|
|
985
|
+
if (!uploadFile) return;
|
|
986
|
+
setFailure(void 0);
|
|
987
|
+
try {
|
|
988
|
+
const uploaded = await uploadFile(file);
|
|
989
|
+
await client.sendMedia({
|
|
990
|
+
mediaType: mediaTypeOf(uploaded.mimeType ?? file.type),
|
|
991
|
+
mediaId: uploaded.mediaId,
|
|
992
|
+
mimeType: uploaded.mimeType ?? file.type,
|
|
993
|
+
filename: uploaded.filename ?? file.name
|
|
994
|
+
});
|
|
995
|
+
await refreshWithFollowUps();
|
|
996
|
+
} catch (error) {
|
|
997
|
+
setFailure(error instanceof Error ? error.message : "Falha ao enviar o arquivo.");
|
|
998
|
+
}
|
|
999
|
+
}
|
|
443
1000
|
return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
444
1001
|
/* @__PURE__ */ jsxs(ConversationWallpaper, { className: "flex-1 min-h-0 overflow-y-auto px-4 py-3", children: [
|
|
445
1002
|
rendered.map(({ message, isFirstInGroup, showDateDivider }) => /* @__PURE__ */ jsxs("div", { children: [
|
|
@@ -449,30 +1006,106 @@ function ConversationPreview({
|
|
|
449
1006
|
{
|
|
450
1007
|
message,
|
|
451
1008
|
isMine: message.direction === "inbound",
|
|
452
|
-
isFirstInGroup
|
|
1009
|
+
isFirstInGroup,
|
|
1010
|
+
onInteractiveSelect: message.direction === "outbound" ? (selection) => void handleInteractiveSelect(selection) : void 0
|
|
453
1011
|
}
|
|
454
1012
|
)
|
|
455
1013
|
] }, message.id)),
|
|
456
1014
|
/* @__PURE__ */ jsx("div", { ref: bottomRef })
|
|
457
1015
|
] }),
|
|
458
1016
|
failure ? /* @__PURE__ */ jsx("p", { role: "alert", className: "px-4 py-2 text-sm text-red-600 dark:text-red-400", children: failure }) : null,
|
|
1017
|
+
loadFailure ? /* @__PURE__ */ jsx("p", { role: "status", className: "px-4 py-2 text-sm text-amber-700 dark:text-amber-400", children: loadFailure }) : null,
|
|
459
1018
|
/* @__PURE__ */ jsx(
|
|
460
1019
|
MessageComposer,
|
|
461
1020
|
{
|
|
462
1021
|
onSend: (text) => void handleSend(text),
|
|
463
|
-
|
|
1022
|
+
onAttach: uploadFile ? (file) => void handleAttach(file) : void 0,
|
|
1023
|
+
placeholder: isRecording ? "Gravando\u2026 toque no quadrado para ouvir" : placeholder ?? "Escreva como o cliente\u2026",
|
|
1024
|
+
idleAction: uploadFile ? /* @__PURE__ */ jsx(
|
|
1025
|
+
AudioRecorderButton,
|
|
1026
|
+
{
|
|
1027
|
+
onRecorded: (file) => void handleAttach(file),
|
|
1028
|
+
onFailure: (message) => setFailure(message),
|
|
1029
|
+
onRecordingChange: setIsRecording
|
|
1030
|
+
}
|
|
1031
|
+
) : void 0
|
|
464
1032
|
}
|
|
465
1033
|
)
|
|
466
1034
|
] });
|
|
467
1035
|
}
|
|
468
1036
|
|
|
1037
|
+
// src/preview/ConversationSimulatorPanel.tsx
|
|
1038
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1039
|
+
var DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS = {
|
|
1040
|
+
title: "Simulador do cliente",
|
|
1041
|
+
destinationHint: "entrega no webhook real",
|
|
1042
|
+
close: "Fechar simulador",
|
|
1043
|
+
placeholder: "Escreva como o cliente\u2026"
|
|
1044
|
+
};
|
|
1045
|
+
function ConversationSimulatorPanel({
|
|
1046
|
+
onClose,
|
|
1047
|
+
displayNumber,
|
|
1048
|
+
labels,
|
|
1049
|
+
headerActions,
|
|
1050
|
+
...previewProps
|
|
1051
|
+
}) {
|
|
1052
|
+
const text = { ...DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS, ...labels };
|
|
1053
|
+
const subtitle = [displayNumber ?? previewProps.conversationId, text.destinationHint].join(" \xB7 ");
|
|
1054
|
+
return /* @__PURE__ */ jsxs2("aside", { className: "cv-simulator-panel", "aria-label": text.title, children: [
|
|
1055
|
+
/* @__PURE__ */ jsxs2("header", { className: "cv-simulator-panel__header", children: [
|
|
1056
|
+
/* @__PURE__ */ jsxs2("div", { className: "cv-simulator-panel__heading", children: [
|
|
1057
|
+
/* @__PURE__ */ jsx2("h2", { className: "cv-simulator-panel__title", children: text.title }),
|
|
1058
|
+
/* @__PURE__ */ jsx2("p", { className: "cv-simulator-panel__subtitle", children: subtitle })
|
|
1059
|
+
] }),
|
|
1060
|
+
/* @__PURE__ */ jsxs2("div", { className: "cv-simulator-panel__actions", children: [
|
|
1061
|
+
headerActions,
|
|
1062
|
+
/* @__PURE__ */ jsx2("button", { type: "button", onClick: onClose, "data-cv-tooltip": text.close, "aria-label": text.close, className: "cv-simulator-panel__close", children: /* @__PURE__ */ jsx2("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "M18 6 6 18M6 6l12 12" }) }) })
|
|
1063
|
+
] })
|
|
1064
|
+
] }),
|
|
1065
|
+
/* @__PURE__ */ jsx2("div", { className: "cv-simulator-panel__body", children: /* @__PURE__ */ jsx2(ConversationPreview, { ...previewProps, placeholder: text.placeholder }) })
|
|
1066
|
+
] });
|
|
1067
|
+
}
|
|
1068
|
+
|
|
469
1069
|
// src/preview/createPreviewWebhookClient.ts
|
|
470
1070
|
import {
|
|
471
1071
|
buildInboundAudioPayload,
|
|
472
1072
|
buildInboundInteractivePayload,
|
|
1073
|
+
buildInboundMediaPayload,
|
|
473
1074
|
buildInboundTextPayload,
|
|
474
1075
|
serializeWebhookPayload
|
|
475
1076
|
} from "@adatechnology/meta-whatsapp-contracts/testing";
|
|
1077
|
+
|
|
1078
|
+
// src/preview/createPreviewMediaUploader.ts
|
|
1079
|
+
import { PREVIEW_MEDIA_ID_PREFIX } from "@adatechnology/meta-whatsapp-contracts";
|
|
1080
|
+
import { toPreviewMediaId } from "@adatechnology/meta-whatsapp-contracts";
|
|
1081
|
+
var CHUNK_SIZE = 8192;
|
|
1082
|
+
async function fileToBase64(file) {
|
|
1083
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
1084
|
+
let binary = "";
|
|
1085
|
+
for (let offset = 0; offset < bytes.length; offset += CHUNK_SIZE) {
|
|
1086
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + CHUNK_SIZE));
|
|
1087
|
+
}
|
|
1088
|
+
return btoa(binary);
|
|
1089
|
+
}
|
|
1090
|
+
function createPreviewMediaUploader(params) {
|
|
1091
|
+
const fallbackMimeType = params.fallbackMimeType ?? "audio/ogg";
|
|
1092
|
+
const fallbackFilename = params.fallbackFilename ?? "audio.ogg";
|
|
1093
|
+
return async function uploadPreviewMedia(file) {
|
|
1094
|
+
const mimeType = file.type || fallbackMimeType;
|
|
1095
|
+
const filename = file.name || fallbackFilename;
|
|
1096
|
+
const { uploadId } = await params.upload({ base64: await fileToBase64(file), mimeType, filename });
|
|
1097
|
+
return { mediaId: toPreviewMediaId(uploadId), mimeType, filename };
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// src/preview/createPreviewWebhookClient.ts
|
|
1102
|
+
var PreviewMediaUploadRejectedError = class extends Error {
|
|
1103
|
+
constructor(status) {
|
|
1104
|
+
super(`A rota de m\xEDdia do simulador recusou o upload (HTTP ${status}).`);
|
|
1105
|
+
this.status = status;
|
|
1106
|
+
this.name = "PreviewMediaUploadRejectedError";
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
476
1109
|
var PreviewInProductionError = class extends Error {
|
|
477
1110
|
constructor() {
|
|
478
1111
|
super("O preview de conversa carrega um app secret e n\xE3o pode ser montado em produ\xE7\xE3o.");
|
|
@@ -489,7 +1122,7 @@ var PreviewWebhookRejectedError = class extends Error {
|
|
|
489
1122
|
function assertPreviewEnvironment(isProduction) {
|
|
490
1123
|
if (isProduction) throw new PreviewInProductionError();
|
|
491
1124
|
}
|
|
492
|
-
async function
|
|
1125
|
+
async function signPreviewPayload(params) {
|
|
493
1126
|
const encoder = new TextEncoder();
|
|
494
1127
|
const key = await globalThis.crypto.subtle.importKey(
|
|
495
1128
|
"raw",
|
|
@@ -501,6 +1134,31 @@ async function signWithWebCrypto(params) {
|
|
|
501
1134
|
const signature = await globalThis.crypto.subtle.sign("HMAC", key, encoder.encode(params.rawBody));
|
|
502
1135
|
return `sha256=${[...new Uint8Array(signature)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
503
1136
|
}
|
|
1137
|
+
var signWithWebCrypto = signPreviewPayload;
|
|
1138
|
+
var DEFAULT_MEDIA_UPLOAD_PATH = "/v1/preview/media";
|
|
1139
|
+
function defaultMediaUploadUrl(webhookUrl) {
|
|
1140
|
+
try {
|
|
1141
|
+
return new URL(DEFAULT_MEDIA_UPLOAD_PATH, webhookUrl).toString();
|
|
1142
|
+
} catch {
|
|
1143
|
+
return DEFAULT_MEDIA_UPLOAD_PATH;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
function createPreviewMediaPoster(params) {
|
|
1147
|
+
return createPreviewMediaUploader({
|
|
1148
|
+
upload: async (request) => {
|
|
1149
|
+
const performRequest = params.fetchImplementation ?? fetch;
|
|
1150
|
+
const extraHeaders = await params.headers?.(request.mimeType) ?? {};
|
|
1151
|
+
const response = await performRequest(params.url, {
|
|
1152
|
+
method: "POST",
|
|
1153
|
+
headers: { "content-type": "application/json", ...extraHeaders },
|
|
1154
|
+
body: JSON.stringify(request)
|
|
1155
|
+
});
|
|
1156
|
+
if (!response.ok) throw new PreviewMediaUploadRejectedError(response.status);
|
|
1157
|
+
const body = await response.json();
|
|
1158
|
+
return { uploadId: body.data.uploadId };
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
504
1162
|
function createPreviewWebhookClient(params) {
|
|
505
1163
|
const sendPayload = async (payload) => {
|
|
506
1164
|
const rawBody = serializeWebhookPayload(payload);
|
|
@@ -513,12 +1171,70 @@ function createPreviewWebhookClient(params) {
|
|
|
513
1171
|
});
|
|
514
1172
|
if (!response.ok) throw new PreviewWebhookRejectedError(response.status);
|
|
515
1173
|
};
|
|
1174
|
+
const uploadMedia = createPreviewMediaPoster({
|
|
1175
|
+
url: params.mediaUploadUrl ?? defaultMediaUploadUrl(params.webhookUrl),
|
|
1176
|
+
headers: async (mimeType) => ({
|
|
1177
|
+
"x-preview-signature": await signWithWebCrypto({ rawBody: mimeType, appSecret: params.appSecret })
|
|
1178
|
+
}),
|
|
1179
|
+
...params.fetchImplementation ? { fetchImplementation: params.fetchImplementation } : {}
|
|
1180
|
+
});
|
|
516
1181
|
const envelope = { from: params.from, phoneNumberId: params.phoneNumberId };
|
|
517
1182
|
return {
|
|
518
1183
|
sendText: (text) => sendPayload(buildInboundTextPayload({ ...envelope, text })),
|
|
519
1184
|
sendButtonReply: (reply) => sendPayload(buildInboundInteractivePayload({ ...envelope, buttonReply: reply })),
|
|
520
1185
|
sendListReply: (reply) => sendPayload(buildInboundInteractivePayload({ ...envelope, listReply: reply })),
|
|
521
|
-
sendAudio: (mediaId) => sendPayload(buildInboundAudioPayload({ ...envelope, mediaId }))
|
|
1186
|
+
sendAudio: (mediaId) => sendPayload(buildInboundAudioPayload({ ...envelope, mediaId })),
|
|
1187
|
+
sendMedia: (media) => sendPayload(buildInboundMediaPayload({ ...envelope, ...media })),
|
|
1188
|
+
uploadMedia
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// src/preview/createPreviewBridgeClient.ts
|
|
1193
|
+
var PreviewBridgeRejectedError = class extends Error {
|
|
1194
|
+
constructor(status) {
|
|
1195
|
+
super(`A rota de preview do host recusou a entrega (HTTP ${status}).`);
|
|
1196
|
+
this.status = status;
|
|
1197
|
+
this.name = "PreviewBridgeRejectedError";
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1200
|
+
function buildFetchSender(params) {
|
|
1201
|
+
const endpointUrl = params.endpointUrl;
|
|
1202
|
+
if (!endpointUrl) {
|
|
1203
|
+
throw new Error("createPreviewBridgeClient exige `sendCommand` ou `endpointUrl`.");
|
|
1204
|
+
}
|
|
1205
|
+
return async (command) => {
|
|
1206
|
+
const performRequest = params.fetchImplementation ?? fetch;
|
|
1207
|
+
const response = await performRequest(endpointUrl, {
|
|
1208
|
+
method: "POST",
|
|
1209
|
+
// `credentials` fica com o host via `headers`/`fetchImplementation`: sessão por cookie e por
|
|
1210
|
+
// bearer não convivem numa escolha default sem quebrar um dos dois.
|
|
1211
|
+
headers: { "content-type": "application/json", ...params.headers },
|
|
1212
|
+
body: JSON.stringify(command)
|
|
1213
|
+
});
|
|
1214
|
+
if (!response.ok) throw new PreviewBridgeRejectedError(response.status);
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
function resolveBridgeUpload(params) {
|
|
1218
|
+
if (params.uploadMedia) return params.uploadMedia;
|
|
1219
|
+
const url = params.mediaUploadUrl ?? (params.endpointUrl ? defaultMediaUploadUrl(params.endpointUrl) : void 0);
|
|
1220
|
+
if (!url) return void 0;
|
|
1221
|
+
return createPreviewMediaPoster({
|
|
1222
|
+
url,
|
|
1223
|
+
...params.headers ? { headers: async () => params.headers ?? {} } : {},
|
|
1224
|
+
...params.fetchImplementation ? { fetchImplementation: params.fetchImplementation } : {}
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
function createPreviewBridgeClient(params) {
|
|
1228
|
+
const send = params.sendCommand ?? buildFetchSender(params);
|
|
1229
|
+
const from = params.from;
|
|
1230
|
+
const uploadMedia = resolveBridgeUpload(params);
|
|
1231
|
+
return {
|
|
1232
|
+
sendText: (text) => send({ kind: "text", from, text }),
|
|
1233
|
+
sendButtonReply: (reply) => send({ kind: "buttonReply", from, reply }),
|
|
1234
|
+
sendListReply: (reply) => send({ kind: "listReply", from, reply }),
|
|
1235
|
+
sendAudio: (mediaId) => send({ kind: "audio", from, mediaId }),
|
|
1236
|
+
sendMedia: (media) => send({ kind: "media", from, ...media }),
|
|
1237
|
+
...uploadMedia ? { uploadMedia } : {}
|
|
522
1238
|
};
|
|
523
1239
|
}
|
|
524
1240
|
|
|
@@ -557,20 +1273,91 @@ function startPreviewScript(params) {
|
|
|
557
1273
|
}, intervalMs);
|
|
558
1274
|
return () => clearInterval(timer);
|
|
559
1275
|
}
|
|
1276
|
+
|
|
1277
|
+
// src/preview/MediaTypesPreview.tsx
|
|
1278
|
+
import { useMemo as useMemo2 } from "react";
|
|
1279
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1280
|
+
var MEDIA_TYPES_CONVERSATION_ID = "5511944443333";
|
|
1281
|
+
function MediaTypesPreview({
|
|
1282
|
+
conversationId = MEDIA_TYPES_CONVERSATION_ID,
|
|
1283
|
+
className
|
|
1284
|
+
}) {
|
|
1285
|
+
const store = useMemo2(
|
|
1286
|
+
() => createPreviewStore({ conversations: PREVIEW_CONVERSATIONS, messages: PREVIEW_MESSAGES }),
|
|
1287
|
+
[]
|
|
1288
|
+
);
|
|
1289
|
+
const api = useMemo2(() => createMockConversationsApi({ store }), [store]);
|
|
1290
|
+
const sse = useMemo2(() => createMockSSEProvider({ store }), [store]);
|
|
1291
|
+
const messages = PREVIEW_MESSAGES[conversationId] ?? [];
|
|
1292
|
+
const documents = PREVIEW_DOCUMENTS[conversationId] ?? [];
|
|
1293
|
+
const mimeTypes = [...new Set(documents.map((document) => document.mimeType))];
|
|
1294
|
+
return /* @__PURE__ */ jsx3(ConversationsProvider, { api, sse, children: /* @__PURE__ */ jsxs3("div", { className, children: [
|
|
1295
|
+
/* @__PURE__ */ jsxs3("header", { className: "border-b px-4 py-3 dark:border-gray-700", children: [
|
|
1296
|
+
/* @__PURE__ */ jsx3("h1", { className: "text-lg font-semibold", children: "Teste manual de m\xEDdia" }),
|
|
1297
|
+
/* @__PURE__ */ jsxs3("p", { className: "text-sm text-gray-500", children: [
|
|
1298
|
+
documents.length,
|
|
1299
|
+
" arquivos, ",
|
|
1300
|
+
mimeTypes.length,
|
|
1301
|
+
" tipos. Clique no olho para abrir em aba nova e no bot\xE3o da bolha para carregar a m\xEDdia na thread \u2014 \xE9 o que teste automatizado n\xE3o v\xEA."
|
|
1302
|
+
] })
|
|
1303
|
+
] }),
|
|
1304
|
+
/* @__PURE__ */ jsxs3("div", { className: "grid gap-4 p-4 lg:grid-cols-2", children: [
|
|
1305
|
+
/* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
|
|
1306
|
+
/* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Biblioteca da empresa" }),
|
|
1307
|
+
/* @__PURE__ */ jsx3(DocumentsLibrary, { perPage: documents.length || 20 })
|
|
1308
|
+
] }),
|
|
1309
|
+
/* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
|
|
1310
|
+
/* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Painel da conversa" }),
|
|
1311
|
+
/* @__PURE__ */ jsx3(ConversationDocumentsPanel, { conversationId, open: true, perPage: documents.length || 20 }),
|
|
1312
|
+
/* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Bolhas na thread" }),
|
|
1313
|
+
/* @__PURE__ */ jsx3(ConversationWallpaper, { className: "max-h-[70vh] overflow-y-auto rounded-lg px-3 py-2", children: messages.map((message, index) => /* @__PURE__ */ jsx3(
|
|
1314
|
+
MessageBubble,
|
|
1315
|
+
{
|
|
1316
|
+
message,
|
|
1317
|
+
isMine: message.direction === "outbound",
|
|
1318
|
+
isFirstInGroup: index === 0 || messages[index - 1]?.sender !== message.sender
|
|
1319
|
+
},
|
|
1320
|
+
message.id
|
|
1321
|
+
)) })
|
|
1322
|
+
] })
|
|
1323
|
+
] })
|
|
1324
|
+
] }) });
|
|
1325
|
+
}
|
|
560
1326
|
export {
|
|
1327
|
+
AudioRecorderButton,
|
|
561
1328
|
ConversationPreview,
|
|
1329
|
+
ConversationSimulatorPanel,
|
|
1330
|
+
DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
|
|
1331
|
+
DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS,
|
|
1332
|
+
DEFAULT_MEDIA_UPLOAD_PATH,
|
|
562
1333
|
DEFAULT_PREVIEW_SCRIPT,
|
|
563
1334
|
GLOBAL_CHANNEL,
|
|
1335
|
+
MEDIA_TYPES_CONVERSATION_ID,
|
|
1336
|
+
MediaTypesPreview,
|
|
564
1337
|
PREVIEW_CONVERSATIONS,
|
|
1338
|
+
PREVIEW_DOCUMENTS,
|
|
1339
|
+
PREVIEW_FILE_SAMPLES,
|
|
1340
|
+
PREVIEW_MEDIA_ID_PREFIX,
|
|
565
1341
|
PREVIEW_MESSAGES,
|
|
1342
|
+
PreviewBridgeRejectedError,
|
|
566
1343
|
PreviewInProductionError,
|
|
1344
|
+
PreviewMediaUploadRejectedError,
|
|
567
1345
|
PreviewWebhookRejectedError,
|
|
568
1346
|
assertPreviewEnvironment,
|
|
569
1347
|
conversationChannel,
|
|
570
1348
|
createMockConversationsApi,
|
|
571
1349
|
createMockEventSource,
|
|
572
1350
|
createMockSSEProvider,
|
|
1351
|
+
createPreviewBridgeClient,
|
|
1352
|
+
createPreviewMediaPoster,
|
|
1353
|
+
createPreviewMediaResolver,
|
|
1354
|
+
createPreviewMediaUploader,
|
|
573
1355
|
createPreviewStore,
|
|
574
1356
|
createPreviewWebhookClient,
|
|
1357
|
+
mediaTypeOf,
|
|
1358
|
+
previewFileBase64,
|
|
1359
|
+
previewFileUrl,
|
|
1360
|
+
resolvePreviewFileSample,
|
|
1361
|
+
signPreviewPayload,
|
|
575
1362
|
startPreviewScript
|
|
576
1363
|
};
|