@adatechnology/conversations-ui 0.1.0-rc.5 → 0.1.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/chunk-TGTBMMFC.js +1707 -0
  2. package/dist/index.d.ts +209 -22
  3. package/dist/index.js +243 -590
  4. package/dist/preview/index.d.ts +153 -9
  5. package/dist/preview/index.js +520 -38
  6. package/dist/{types-C2Yexi8A.d.ts → types-B5C1DLu1.d.ts} +70 -2
  7. package/package.json +2 -2
  8. package/src/Avatar.tsx +13 -2
  9. package/src/ConversationHeader.tsx +18 -0
  10. package/src/ConversationListItem.tsx +18 -2
  11. package/src/DocumentsLibrary.tsx +322 -0
  12. package/src/EmojiPicker.tsx +69 -55
  13. package/src/FileIcon.test.ts +46 -1
  14. package/src/FileIcon.tsx +76 -10
  15. package/src/InteractiveMessage.tsx +126 -0
  16. package/src/Lightbox.tsx +18 -3
  17. package/src/MessageBubble.tsx +31 -4
  18. package/src/MessageComposer.tsx +36 -5
  19. package/src/WhatsAppMessageEditor.tsx +28 -4
  20. package/src/emojiCatalog.test.ts +35 -0
  21. package/src/emojiCatalog.ts +189 -0
  22. package/src/index.ts +25 -12
  23. package/src/lib/createMediaUrlResolver.ts +33 -0
  24. package/src/preview/AudioRecorderButton.tsx +117 -0
  25. package/src/preview/ConversationPreview.tsx +184 -15
  26. package/src/preview/MediaTypesPreview.tsx +87 -0
  27. package/src/preview/audioRecorderFormat.test.ts +67 -0
  28. package/src/preview/conversationPreviewFailures.test.ts +64 -0
  29. package/src/preview/createMockConversationsApi.ts +62 -8
  30. package/src/preview/createPreviewWebhookClient.ts +28 -1
  31. package/src/preview/index.ts +16 -2
  32. package/src/preview/mediaTypeOf.test.ts +15 -0
  33. package/src/preview/mockDocumentsSearch.test.ts +57 -0
  34. package/src/preview/previewFileSamples.test.ts +151 -0
  35. package/src/preview/previewFileSamples.ts +74 -0
  36. package/src/preview/previewFixtures.ts +140 -8
  37. package/src/preview/previewMediaSource.test.ts +62 -0
  38. package/src/preview/previewMediaSource.ts +91 -0
  39. package/src/providers/types.ts +17 -0
  40. package/src/settings/WhatsAppCreateTemplateForm.tsx +3 -1
  41. package/src/types.ts +38 -1
  42. package/dist/chunk-YWITIIHD.js +0 -728
@@ -1,9 +1,12 @@
1
1
  import {
2
+ ConversationDocumentsPanel,
2
3
  ConversationWallpaper,
4
+ ConversationsProvider,
3
5
  DateDivider,
6
+ DocumentsLibrary,
4
7
  MessageBubble,
5
8
  MessageComposer
6
- } from "../chunk-YWITIIHD.js";
9
+ } from "../chunk-TGTBMMFC.js";
7
10
  import "../chunk-OGRRHQQW.js";
8
11
 
9
12
  // src/preview/previewStore.ts
@@ -399,8 +402,7 @@ var PREVIEW_MESSAGES = {
399
402
  timestamp: at("15:09:00")
400
403
  },
401
404
  {
402
- // Extensão fora do EXTENSION_STYLE: garante que o ícone genérico cinza também apareça.
403
- id: "fixture-doc-generic",
405
+ id: "fixture-doc-txt",
404
406
  type: "document",
405
407
  uploadId: "preview/documentos/lista-compras.txt",
406
408
  filename: "lista-compras.txt",
@@ -409,13 +411,140 @@ var PREVIEW_MESSAGES = {
409
411
  direction: "inbound",
410
412
  sender: "customer",
411
413
  timestamp: at("15:10:00")
414
+ },
415
+ {
416
+ id: "fixture-doc-csv",
417
+ type: "document",
418
+ uploadId: "preview/documentos/planilha-do-pedido.csv",
419
+ filename: "planilha-do-pedido.csv",
420
+ mimeType: "text/csv",
421
+ sizeBytes: 2048,
422
+ direction: "inbound",
423
+ sender: "customer",
424
+ timestamp: at("15:11:00")
425
+ },
426
+ {
427
+ id: "fixture-doc-pptx",
428
+ type: "document",
429
+ uploadId: "preview/documentos/apresentacao.pptx",
430
+ filename: "apresentacao.pptx",
431
+ mimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
432
+ sizeBytes: 512e3,
433
+ direction: "inbound",
434
+ sender: "customer",
435
+ timestamp: at("15:12:00")
436
+ },
437
+ {
438
+ id: "fixture-doc-ppt",
439
+ type: "document",
440
+ uploadId: "preview/documentos/antiga.ppt",
441
+ filename: "antiga.ppt",
442
+ mimeType: "application/vnd.ms-powerpoint",
443
+ sizeBytes: 348160,
444
+ direction: "inbound",
445
+ sender: "customer",
446
+ timestamp: at("15:13:00")
447
+ },
448
+ // Mídia enviada COMO arquivo: aí a Meta manda o nome, então é onde a extensão de música e de
449
+ // vídeo aparece na lista. Mandada como `image`/`audio`/`video` (acima e abaixo) ela chega sem
450
+ // nome nenhum, e o rótulo vira o id da mídia — como em produção.
451
+ {
452
+ id: "fixture-doc-mp3",
453
+ type: "document",
454
+ uploadId: "preview/documentos/musica-do-anuncio.mp3",
455
+ filename: "musica-do-anuncio.mp3",
456
+ mimeType: "audio/mpeg",
457
+ sizeBytes: 3145728,
458
+ direction: "inbound",
459
+ sender: "customer",
460
+ timestamp: at("15:14:00")
461
+ },
462
+ {
463
+ id: "fixture-doc-m4a",
464
+ type: "document",
465
+ uploadId: "preview/documentos/recado.m4a",
466
+ filename: "recado.m4a",
467
+ mimeType: "audio/mp4",
468
+ sizeBytes: 486400,
469
+ direction: "inbound",
470
+ sender: "customer",
471
+ timestamp: at("15:15:00")
472
+ },
473
+ {
474
+ id: "fixture-doc-mp4-file",
475
+ type: "document",
476
+ uploadId: "preview/documentos/video-do-produto.mp4",
477
+ filename: "video-do-produto.mp4",
478
+ mimeType: "video/mp4",
479
+ sizeBytes: 8388608,
480
+ direction: "inbound",
481
+ sender: "customer",
482
+ timestamp: at("15:16:00")
483
+ },
484
+ {
485
+ id: "fixture-doc-jpg",
486
+ type: "document",
487
+ uploadId: "preview/documentos/foto-do-recibo.jpg",
488
+ filename: "foto-do-recibo.jpg",
489
+ mimeType: "image/jpeg",
490
+ sizeBytes: 262144,
491
+ direction: "inbound",
492
+ sender: "customer",
493
+ timestamp: at("15:17:00")
494
+ },
495
+ // As espécies restantes que a Meta aceita, sem nome de arquivo — fecham a lista de tipos
496
+ // aceitos e exercitam o fallback de ícone pela família do mimeType.
497
+ {
498
+ id: "fixture-media-jpeg",
499
+ type: "image",
500
+ mediaId: "preview-image-jpeg",
501
+ mimeType: "image/jpeg",
502
+ sizeBytes: 138240,
503
+ direction: "inbound",
504
+ sender: "customer",
505
+ timestamp: at("15:18:00")
506
+ },
507
+ {
508
+ id: "fixture-media-3gp",
509
+ type: "video",
510
+ mediaId: "preview-video-3gp",
511
+ mimeType: "video/3gp",
512
+ sizeBytes: 921600,
513
+ direction: "inbound",
514
+ sender: "customer",
515
+ timestamp: at("15:19:00")
516
+ },
517
+ {
518
+ id: "fixture-media-aac",
519
+ type: "audio",
520
+ mediaId: "preview-audio-aac",
521
+ mimeType: "audio/aac",
522
+ sizeBytes: 76800,
523
+ direction: "inbound",
524
+ sender: "customer",
525
+ timestamp: at("15:20:00")
526
+ },
527
+ {
528
+ id: "fixture-media-amr",
529
+ type: "audio",
530
+ mediaId: "preview-audio-amr",
531
+ mimeType: "audio/amr",
532
+ sizeBytes: 24576,
533
+ direction: "inbound",
534
+ sender: "customer",
535
+ timestamp: at("15:21:00")
412
536
  }
413
537
  ]
414
538
  };
539
+ var LIBRARY_MEDIA_TYPES = /* @__PURE__ */ new Set(["document", "image", "video", "audio", "sticker"]);
415
540
  var PREVIEW_DOCUMENTS = {
416
- "5511944443333": (PREVIEW_MESSAGES["5511944443333"] ?? []).filter((message) => message.type === "document").map((message) => ({
417
- id: message.uploadId ?? message.id,
418
- filename: message.filename ?? message.id,
541
+ "5511944443333": (PREVIEW_MESSAGES["5511944443333"] ?? []).filter((message) => LIBRARY_MEDIA_TYPES.has(message.type)).map((message) => ({
542
+ // Mídia não tem `uploadId` na mensagem: o backend deriva a key do id da mídia na Meta, e o
543
+ // caminho abaixo imita esse formato para o download assinado receber algo do mesmo feitio.
544
+ id: message.uploadId ?? (message.mediaId ? `preview/inbound/${message.mediaId}` : message.id),
545
+ // Só documento chega nomeado. Para os outros o backend salva o id da mídia como rótulo —
546
+ // manter isso aqui é o que faz o ícone por família do mimeType ser exercitado de verdade.
547
+ filename: message.filename ?? message.mediaId ?? message.id,
419
548
  mimeType: message.mimeType ?? "application/octet-stream",
420
549
  sizeBytes: message.sizeBytes ?? 0,
421
550
  source: message.sender,
@@ -423,8 +552,85 @@ var PREVIEW_DOCUMENTS = {
423
552
  }))
424
553
  };
425
554
 
555
+ // src/preview/previewFileSamples.ts
556
+ var PREVIEW_FILE_SAMPLES = {
557
+ "application/pdf": "data:application/pdf;base64,JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgMjYwIDExMF0vQ29udGVudHMgNCAwIFIvUmVzb3VyY2VzPDwvRm9udDw8L0YxIDUgMCBSPj4+Pj4+ZW5kb2JqCjQgMCBvYmo8PC9MZW5ndGggNjI+PnN0cmVhbQpCVCAvRjEgMTMgVGYgMTggNTUgVGQgKERvY3VtZW50byBkZSBleGVtcGxvIGRvIHByZXZpZXcpIFRqIEVUCmVuZHN0cmVhbWVuZG9iago1IDAgb2JqPDwvVHlwZS9Gb250L1N1YnR5cGUvVHlwZTEvQmFzZUZvbnQvSGVsdmV0aWNhPj5lbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTIgMDAwMDAgbiAKMDAwMDAwMDEwMSAwMDAwMCBuIAowMDAwMDAwMjExIDAwMDAwIG4gCjAwMDAwMDAzMTcgMDAwMDAgbiAKdHJhaWxlcjw8L1NpemUgNi9Sb290IDEgMCBSPj4Kc3RhcnR4cmVmCjM3OAolJUVPRgo=",
558
+ "image/png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAAACXBIWXMAAAABAAAAAQBPJcTWAAAATElEQVR4nO3OMRHAIAAAMbhDWo2hCYPdu/xIh0RB5rPP+JN1O/AlVISKUBEqQkWoCBWhIlSEilARKkJFqAgVoSJUhIpQESpCRagIlRcgDwI25HmPQAAAAABJRU5ErkJggg==",
559
+ "image/jpeg": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD//gAQTGF2YzYyLjExLjEwMAD/2wBDAAgEBAQEBAUFBQUFBQYGBgYGBgYGBgYGBgYHBwcICAgHBwcGBgcHCAgICAkJCQgICAgJCQoKCgwMCwsODg4RERT/xABMAAEBAAAAAAAAAAAAAAAAAAAABAEBAQAAAAAAAAAAAAAAAAAAAAYQAQAAAAAAAAAAAAAAAAAAAAARAQAAAAAAAAAAAAAAAAAAAAD/wAARCAAwADADASIAAhEAAxEA/9oADAMBAAIRAxEAPwCUBRpwAAAAAAAAAAAAAAAB/9k=",
560
+ "image/webp": "data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
561
+ "video/mp4": "data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAANdbW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAASwAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAoh0cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAASwAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAEAAAABAAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAAEsAAAIAAABAAAAAAIAbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAAoAAAAEABVxAAAAAAALWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAABq21pbmYAAAAUdm1oZAAAAAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAWtzdGJsAAAAv3N0c2QAAAAAAAAAAQAAAK9hdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAEAAQABIAAAASAAAAAAAAAABFUxhdmM2Mi4xMS4xMDAgbGlieDI2NAAAAAAAAAAAAAAAGP//AAAANWF2Y0MBZAAK/+EAGGdkAAqs2UQmwEQAAAMABAAAAwBQPEiWWAEABmjr48siwP34+AAAAAAQcGFzcAAAAAEAAAABAAAAFGJ0cnQAAAAAAABO2gAAAAAAAAAYc3R0cwAAAAAAAAABAAAAAwAABAAAAAAUc3RzcwAAAAAAAAABAAAAAQAAAChjdHRzAAAAAAAAAAMAAAABAAAIAAAAAAEAAAwAAAAAAQAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAMAAAABAAAAIHN0c3oAAAAAAAAAAAAAAAMAAALbAAAADgAAAAwAAAAUc3RjbwAAAAAAAAABAAADjQAAAGF1ZHRhAAAAWW1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALGlsc3QAAAAkqXRvbwAAABxkYXRhAAAAAQAAAABMYXZmNjIuMy4xMDAAAAAIZnJlZQAAAv1tZGF0AAACrgYF//+q3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2NSByMzIyMiBiMzU2MDVhIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAyNSAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTIgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTEwIHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAJWWIhAA///7mdfgUoKykhh+DdK6xVynnAWznTQ4fVSgMZ1FwY28AAAAKQZoibEN//qePiAAAAAgBnkF5Df8HfQ==",
562
+ "audio/mpeg": "data:audio/mpeg;base64,SUQzBAAAAAAAIlRTU0UAAAAOAAADTGF2ZjYyLjMuMTAwAAAAAAAAAAAAAAD/40jAAAAAAAAAAAAASW5mbwAAAA8AAAAFAAAGwABVVVVVVVVVVVVVVVVVVVVVVVVVgICAgICAgICAgICAgICAgICAgICqqqqqqqqqqqqqqqqqqqqqqqqqqtXV1dXV1dXV1dXV1dXV1dXV1dXV//////////////////////////8AAAAATGF2YzYyLjExAAAAAAAAAAAAAAAAJALAAAAAAAAABsDR4nNFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU=",
563
+ "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=",
564
+ "audio/mp4": "data:audio/mp4;base64,AAAAHGZ0eXBNNEEgAAACAE00QSBpc29taXNvMgAAAAhmcmVlAAAAQW1kYXTeAgBMYXZjNjIuMTEuMTAwAAIwQA4BGCAHARggBwEYIAcBGCAHARggBwEYIAcBGCAHARggBwEYIAcAAAMibW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAAMgAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAk10cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAAMgAAAAAAAAAAAAAAAEBAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAADIAAAEAAABAAAAAAHFbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAACsRAAAJnRVxAAAAAAALWhkbHIAAAAAAAAAAHNvdW4AAAAAAAAAAAAAAABTb3VuZEhhbmRsZXIAAAABcG1pbmYAAAAQc21oZAAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABNHN0YmwAAABqc3RzZAAAAAAAAAABAAAAWm1wNGEAAAAAAAAAAQAAAAAAAAAAAAEAEAAAAACsRAAAAAAANmVzZHMAAAAAA4CAgCUAAQAEgICAF0AVAAAAAAB9AAAAB/oFgICABRIIVuUABoCAgAECAAAAIHN0dHMAAAAAAAAAAgAAAAkAAAQAAAAAAQAAAnQAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAoAAAABAAAAPHN0c3oAAAAAAAAAAAAAAAoAAAAVAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAAFHN0Y28AAAAAAAAAAQAAACwAAAAac2dwZAEAAAByb2xsAAAAAgAAAAH//wAAABxzYmdwAAAAAHJvbGwAAAABAAAACgAAAAEAAABhdWR0YQAAAFltZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAACxpbHN0AAAAJKl0b28AAAAcZGF0YQAAAAEAAAAATGF2ZjYyLjMuMTAw",
565
+ "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==",
566
+ "text/plain": "data:text/plain;base64,TGlzdGEgZGUgY29tcHJhcwotIGFycm96IDJrZwotIG9sZW8gMXVuCg==",
567
+ "text/csv": "data:text/csv;base64,cHJvZHV0byxxdWFudGlkYWRlLHByZWNvCmFycm96LDIsMjQuOTAKb2xlbywxLDcuNTAK",
568
+ "application/zip": "data:application/zip;base64,UEsDBBQAAAAIANFO/FwtmGOxGwAAABkAAAARAAAAY29tcHJvdmFudGUtMS50eHRLzs8tKMovS8wrSVVISVUoSExPzE3NK8nnAgBQSwMEFAAAAAgA0U78XCftV8YWAAAAFAAAABEAAABjb21wcm92YW50ZS0yLnR4dCtOTS/NS8lXSM7PLSjKL0vMK0nlAgBQSwECFAMUAAAACADRTvxcLZhjsRsAAAAZAAAAEQAAAAAAAAAAAAAAgAEAAAAAY29tcHJvdmFudGUtMS50eHRQSwECFAMUAAAACADRTvxcJ+1XxhYAAAAUAAAAEQAAAAAAAAAAAAAAgAFKAAAAY29tcHJvdmFudGUtMi50eHRQSwUGAAAAAAIAAgB+AAAAjwAAAAAA",
569
+ "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==",
570
+ "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=="
571
+ };
572
+ var FAMILY_FALLBACK = {
573
+ image: "image/png",
574
+ video: "video/mp4",
575
+ audio: "audio/mpeg",
576
+ text: "text/plain"
577
+ };
578
+ function resolvePreviewFileSample(mimeType, filename) {
579
+ const normalized = (mimeType ?? "").split(";")[0].trim().toLowerCase();
580
+ const exact = PREVIEW_FILE_SAMPLES[normalized];
581
+ if (exact) return exact;
582
+ const family = FAMILY_FALLBACK[normalized.split("/")[0] ?? ""];
583
+ const byFamily = family ? PREVIEW_FILE_SAMPLES[family] : void 0;
584
+ if (byFamily) return byFamily;
585
+ const note = `Preview do SDK: nao existe amostra binaria valida para ${normalized || "tipo desconhecido"}${filename ? ` (${filename})` : ""}. Na tela real este arquivo vem do storage.`;
586
+ return `data:text/plain;charset=utf-8,${encodeURIComponent(note)}`;
587
+ }
588
+
589
+ // src/preview/previewMediaSource.ts
590
+ var blobUrlCache = /* @__PURE__ */ new Map();
591
+ function toBlob(dataUrl) {
592
+ const [head, payload] = dataUrl.split(",");
593
+ const mimeType = head.replace(/^data:/, "").replace(/;base64$/, "").replace(/;charset=.*$/, "");
594
+ if (!head.endsWith(";base64")) {
595
+ return new Blob([decodeURIComponent(payload)], { type: mimeType || "text/plain" });
596
+ }
597
+ const binary = atob(payload);
598
+ const bytes = new Uint8Array(binary.length);
599
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
600
+ return new Blob([bytes], { type: mimeType });
601
+ }
602
+ function previewFileUrl(mimeType, filename) {
603
+ const dataUrl = resolvePreviewFileSample(mimeType, filename);
604
+ if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return dataUrl;
605
+ const cached = blobUrlCache.get(dataUrl);
606
+ if (cached) return cached;
607
+ const blobUrl = URL.createObjectURL(toBlob(dataUrl));
608
+ blobUrlCache.set(dataUrl, blobUrl);
609
+ return blobUrl;
610
+ }
611
+ function previewFileBase64(mimeType, filename) {
612
+ const dataUrl = resolvePreviewFileSample(mimeType, filename);
613
+ const [head, payload] = dataUrl.split(",");
614
+ const declared = head.replace(/^data:/, "").replace(/;base64$/, "").replace(/;charset=.*$/, "");
615
+ if (head.endsWith(";base64")) return { mimeType: declared, data: payload };
616
+ return { mimeType: declared, data: btoa(decodeURIComponent(payload)) };
617
+ }
618
+ function createPreviewMediaResolver() {
619
+ const byId = /* @__PURE__ */ new Map();
620
+ for (const documents of Object.values(PREVIEW_DOCUMENTS)) {
621
+ for (const document of documents) {
622
+ byId.set(document.id, { mimeType: document.mimeType, filename: document.filename });
623
+ }
624
+ }
625
+ return async (message) => {
626
+ const reference = message.uploadId ?? (message.mediaId ? `preview/inbound/${message.mediaId}` : void 0);
627
+ if (!reference) return null;
628
+ const known = byId.get(reference);
629
+ return previewFileUrl(known?.mimeType ?? message.mimeType, known?.filename ?? message.filename);
630
+ };
631
+ }
632
+
426
633
  // src/preview/createMockConversationsApi.ts
427
- var PREVIEW_IMAGE_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYGD4DwABBAEAX+XyEgAAAABJRU5ErkJggg==";
428
634
  var DEFAULT_LATENCY_MS = 120;
429
635
  var PREVIEW_AGENT_ID = "preview-agent";
430
636
  var PREVIEW_TEMPLATES = [
@@ -542,11 +748,51 @@ function createMockConversationsApi(params) {
542
748
  ${names.join("\n")}`], { type: "application/zip" });
543
749
  });
544
750
  },
545
- getDocumentUrl() {
546
- return withLatency(() => `data:image/png;base64,${PREVIEW_IMAGE_BASE64}`);
751
+ /** Junta as bibliotecas de todas as conversas do fixture, com a origem de cada arquivo. */
752
+ getAllDocuments(documentParams) {
753
+ return withLatency(() => {
754
+ let all = Object.entries(PREVIEW_DOCUMENTS).flatMap(
755
+ ([conversationId, docs]) => docs.map((document) => ({ ...document, conversationId }))
756
+ );
757
+ const search = documentParams?.search?.trim().toLowerCase();
758
+ if (search) {
759
+ const digits = search.replace(/\D/g, "");
760
+ all = all.filter(
761
+ (document) => document.filename.toLowerCase().includes(search) || digits !== "" && document.conversationId.includes(digits)
762
+ );
763
+ }
764
+ const source = documentParams?.source;
765
+ if (source === "team") {
766
+ all = all.filter((document) => document.source === "agent" || document.source === "bot");
767
+ } else if (source) {
768
+ all = all.filter((document) => document.source === source);
769
+ }
770
+ all.sort(
771
+ (left, right) => documentParams?.sortDirection === "asc" ? left.linkedAt.localeCompare(right.linkedAt) : right.linkedAt.localeCompare(left.linkedAt)
772
+ );
773
+ const total = all.length;
774
+ const limit = documentParams?.limit ?? total;
775
+ const page = documentParams?.page ?? 1;
776
+ return { documents: all.slice((page - 1) * limit, page * limit), total };
777
+ });
547
778
  },
548
- getMediaProxyUrl() {
549
- return withLatency(() => ({ mimeType: "image/png", data: PREVIEW_IMAGE_BASE64 }));
779
+ // Devolve os bytes DO TIPO do documento, não uma imagem para tudo: antes, abrir um PDF entregava
780
+ // um PNG rotulado `application/pdf` e o leitor recusava o arquivo. O `uploadId` é a única pista
781
+ // que o contrato dá, então o tipo vem da própria biblioteca.
782
+ getDocumentUrl(uploadId) {
783
+ return withLatency(() => {
784
+ const found = Object.values(PREVIEW_DOCUMENTS).flat().find((document) => document.id === uploadId);
785
+ return previewFileUrl(found?.mimeType, found?.filename);
786
+ });
787
+ },
788
+ // Caminho da mídia ainda não ingerida: o backend busca na Meta e devolve base64. Resolve pelo
789
+ // id para a bolha receber os bytes DO TIPO dela — devolvendo um PNG para todo id, vídeo e áudio
790
+ // apareciam quebrados na thread mesmo havendo amostra válida do formato.
791
+ getMediaProxyUrl(mediaId) {
792
+ return withLatency(() => {
793
+ const found = Object.values(PREVIEW_DOCUMENTS).flat().find((document) => document.id === `preview/inbound/${mediaId}`);
794
+ return previewFileBase64(found?.mimeType, found?.filename);
795
+ });
550
796
  },
551
797
  takeover(conversationId) {
552
798
  return withLatency(
@@ -599,9 +845,89 @@ function createMockSSEProvider(params) {
599
845
  }
600
846
 
601
847
  // src/preview/ConversationPreview.tsx
602
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
603
- import { jsx, jsxs } from "react/jsx-runtime";
848
+ import { useCallback as useCallback2, useEffect, useMemo, useRef as useRef2, useState as useState2 } from "react";
849
+
850
+ // src/preview/AudioRecorderButton.tsx
851
+ import { useCallback, useRef, useState } from "react";
852
+ import { jsx } from "react/jsx-runtime";
853
+ var DEFAULT_AUDIO_RECORDER_BUTTON_LABELS = {
854
+ start: "Gravar \xE1udio",
855
+ stop: "Parar grava\xE7\xE3o",
856
+ unsupported: "Este navegador n\xE3o grava \xE1udio.",
857
+ denied: "Sem permiss\xE3o para usar o microfone."
858
+ };
859
+ var RECORDING_FORMATS = [
860
+ { mimeType: "audio/ogg;codecs=opus", uploadMimeType: "audio/ogg", extension: "ogg" },
861
+ { mimeType: "audio/mp4", uploadMimeType: "audio/mp4", extension: "m4a" },
862
+ { mimeType: "audio/webm", uploadMimeType: "audio/webm", extension: "webm" }
863
+ ];
864
+ function resolveRecordingFormat() {
865
+ if (typeof MediaRecorder === "undefined") return void 0;
866
+ if (typeof MediaRecorder.isTypeSupported !== "function") return RECORDING_FORMATS[0];
867
+ return RECORDING_FORMATS.find((format) => MediaRecorder.isTypeSupported(format.mimeType));
868
+ }
869
+ function AudioRecorderButton({ onRecorded, onFailure, labels, disabled }) {
870
+ const startLabel = labels?.start ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.start;
871
+ const stopLabel = labels?.stop ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.stop;
872
+ const [isRecording, setIsRecording] = useState(false);
873
+ const recorderRef = useRef(null);
874
+ const stop = useCallback(() => {
875
+ recorderRef.current?.stop();
876
+ }, []);
877
+ const start = useCallback(async () => {
878
+ const format = resolveRecordingFormat();
879
+ if (!format || !navigator.mediaDevices?.getUserMedia) {
880
+ onFailure?.(labels?.unsupported ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.unsupported);
881
+ return;
882
+ }
883
+ try {
884
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
885
+ const recorder = new MediaRecorder(stream, { mimeType: format.mimeType });
886
+ const chunks = [];
887
+ recorder.addEventListener("dataavailable", (event) => {
888
+ if (event.data.size > 0) chunks.push(event.data);
889
+ });
890
+ recorder.addEventListener("stop", () => {
891
+ stream.getTracks().forEach((track) => track.stop());
892
+ setIsRecording(false);
893
+ recorderRef.current = null;
894
+ const blob = new Blob(chunks, { type: format.uploadMimeType });
895
+ void onRecorded(
896
+ new File([blob], `audio-${Date.now()}.${format.extension}`, { type: format.uploadMimeType })
897
+ );
898
+ });
899
+ recorderRef.current = recorder;
900
+ recorder.start();
901
+ setIsRecording(true);
902
+ } catch {
903
+ onFailure?.(labels?.denied ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.denied);
904
+ }
905
+ }, [labels?.denied, labels?.unsupported, onFailure, onRecorded]);
906
+ return /* @__PURE__ */ jsx(
907
+ "button",
908
+ {
909
+ type: "button",
910
+ disabled,
911
+ onClick: () => isRecording ? stop() : void start(),
912
+ title: isRecording ? stopLabel : startLabel,
913
+ "aria-label": isRecording ? stopLabel : startLabel,
914
+ "aria-pressed": isRecording,
915
+ className: `flex h-9 w-9 items-center justify-center rounded-full transition-colors ${isRecording ? "bg-red-100 text-red-600 dark:bg-red-900/40" : "text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700"}`,
916
+ children: isRecording ? "\u25A0" : "\u{1F3A4}"
917
+ }
918
+ );
919
+ }
920
+
921
+ // src/preview/ConversationPreview.tsx
922
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
923
+ function mediaTypeOf(mimeType) {
924
+ if (mimeType.startsWith("image/")) return "image";
925
+ if (mimeType.startsWith("video/")) return "video";
926
+ if (mimeType.startsWith("audio/")) return "audio";
927
+ return "document";
928
+ }
604
929
  var GROUPING_WINDOW_MS = 5 * 60 * 1e3;
930
+ var FOLLOW_UP_REFRESH_MS = [400, 1200, 3e3];
605
931
  function decorate(messages) {
606
932
  return messages.map((message, index) => {
607
933
  const previous = index > 0 ? messages[index - 1] : void 0;
@@ -614,23 +940,52 @@ function decorate(messages) {
614
940
  };
615
941
  });
616
942
  }
943
+ function statusOf(error) {
944
+ if (typeof error !== "object" || error === null) return void 0;
945
+ const candidate = error;
946
+ const value = candidate.status ?? candidate.statusCode;
947
+ return typeof value === "number" ? value : void 0;
948
+ }
949
+ function isNotFound(error) {
950
+ return statusOf(error) === 404;
951
+ }
952
+ function describeLoadFailure(error) {
953
+ const status = statusOf(error);
954
+ if (status === 401 || status === 403) {
955
+ 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.";
956
+ }
957
+ if (error instanceof Error && error.message) return `N\xE3o foi poss\xEDvel ler o transcript: ${error.message}`;
958
+ return "N\xE3o foi poss\xEDvel ler o transcript da conversa.";
959
+ }
617
960
  function ConversationPreview({
618
961
  client,
619
962
  sse,
620
963
  conversationId,
621
964
  loadMessages,
622
- placeholder
965
+ placeholder,
966
+ pollIntervalMs,
967
+ uploadMedia
623
968
  }) {
624
- const [messages, setMessages] = useState([]);
625
- const [failure, setFailure] = useState(void 0);
626
- const loadMessagesRef = useRef(loadMessages);
627
- const bottomRef = useRef(null);
969
+ const [messages, setMessages] = useState2([]);
970
+ const [failure, setFailure] = useState2(void 0);
971
+ const [loadFailure, setLoadFailure] = useState2(void 0);
972
+ const [pendingLocal, setPendingLocal] = useState2([]);
973
+ const loadMessagesRef = useRef2(loadMessages);
974
+ const bottomRef = useRef2(null);
628
975
  loadMessagesRef.current = loadMessages;
629
- const refresh = useCallback(async () => {
976
+ const refresh = useCallback2(async () => {
630
977
  try {
631
- setMessages(await loadMessagesRef.current(conversationId));
632
- } catch {
633
- setMessages([]);
978
+ const loaded = await loadMessagesRef.current(conversationId);
979
+ setMessages(loaded);
980
+ setLoadFailure(void 0);
981
+ if (loaded.length > 0) setPendingLocal([]);
982
+ } catch (error) {
983
+ if (isNotFound(error)) {
984
+ setMessages([]);
985
+ setLoadFailure(void 0);
986
+ return;
987
+ }
988
+ setLoadFailure(describeLoadFailure(error));
634
989
  }
635
990
  }, [conversationId]);
636
991
  useEffect(() => {
@@ -647,42 +1002,105 @@ function ConversationPreview({
647
1002
  source.close();
648
1003
  };
649
1004
  }, [sse, conversationId, refresh]);
1005
+ useEffect(() => {
1006
+ if (!pollIntervalMs) return;
1007
+ const timer = setInterval(() => void refresh(), pollIntervalMs);
1008
+ return () => clearInterval(timer);
1009
+ }, [pollIntervalMs, refresh]);
650
1010
  useEffect(() => {
651
1011
  bottomRef.current?.scrollIntoView({ behavior: "smooth" });
652
1012
  }, [messages]);
653
- const rendered = useMemo(() => decorate(messages), [messages]);
1013
+ const rendered = useMemo(() => decorate([...messages, ...pendingLocal]), [messages, pendingLocal]);
1014
+ async function refreshWithFollowUps() {
1015
+ await refresh();
1016
+ for (const atraso of FOLLOW_UP_REFRESH_MS) {
1017
+ setTimeout(() => void refresh(), atraso);
1018
+ }
1019
+ }
654
1020
  async function handleSend(text) {
655
1021
  setFailure(void 0);
656
1022
  try {
657
1023
  await client.sendText(text);
658
- await refresh();
1024
+ setPendingLocal((current) => [
1025
+ ...current,
1026
+ {
1027
+ id: `local-${current.length}-${text.length}`,
1028
+ type: "text",
1029
+ content: text,
1030
+ // Do ponto de vista do servidor, mensagem do cliente é inbound — é assim que ela aparece
1031
+ // como "minha" nesta visão.
1032
+ direction: "inbound",
1033
+ sender: "customer",
1034
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1035
+ status: "sent"
1036
+ }
1037
+ ]);
1038
+ await refreshWithFollowUps();
659
1039
  } catch (error) {
660
1040
  setFailure(error instanceof Error ? error.message : "Falha ao entregar a mensagem no webhook.");
661
1041
  }
662
1042
  }
1043
+ async function handleInteractiveSelect(selection) {
1044
+ setFailure(void 0);
1045
+ const reply = { id: selection.option.id, title: selection.option.title };
1046
+ try {
1047
+ await (selection.kind === "button" ? client.sendButtonReply(reply) : client.sendListReply(reply));
1048
+ await refreshWithFollowUps();
1049
+ } catch (error) {
1050
+ setFailure(error instanceof Error ? error.message : "Falha ao entregar a resposta no webhook.");
1051
+ }
1052
+ }
1053
+ async function handleAttach(file) {
1054
+ if (!uploadMedia) return;
1055
+ setFailure(void 0);
1056
+ try {
1057
+ const uploaded = await uploadMedia(file);
1058
+ await client.sendMedia({
1059
+ mediaType: mediaTypeOf(uploaded.mimeType ?? file.type),
1060
+ mediaId: uploaded.mediaId,
1061
+ mimeType: uploaded.mimeType ?? file.type,
1062
+ filename: uploaded.filename ?? file.name
1063
+ });
1064
+ await refreshWithFollowUps();
1065
+ } catch (error) {
1066
+ setFailure(error instanceof Error ? error.message : "Falha ao enviar o arquivo.");
1067
+ }
1068
+ }
663
1069
  return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
664
1070
  /* @__PURE__ */ jsxs(ConversationWallpaper, { className: "flex-1 min-h-0 overflow-y-auto px-4 py-3", children: [
665
1071
  rendered.map(({ message, isFirstInGroup, showDateDivider }) => /* @__PURE__ */ jsxs("div", { children: [
666
- showDateDivider ? /* @__PURE__ */ jsx(DateDivider, { iso: message.timestamp }) : null,
667
- /* @__PURE__ */ jsx(
1072
+ showDateDivider ? /* @__PURE__ */ jsx2(DateDivider, { iso: message.timestamp }) : null,
1073
+ /* @__PURE__ */ jsx2(
668
1074
  MessageBubble,
669
1075
  {
670
1076
  message,
671
1077
  isMine: message.direction === "inbound",
672
- isFirstInGroup
1078
+ isFirstInGroup,
1079
+ onInteractiveSelect: message.direction === "outbound" ? (selection) => void handleInteractiveSelect(selection) : void 0
673
1080
  }
674
1081
  )
675
1082
  ] }, message.id)),
676
- /* @__PURE__ */ jsx("div", { ref: bottomRef })
1083
+ /* @__PURE__ */ jsx2("div", { ref: bottomRef })
677
1084
  ] }),
678
- failure ? /* @__PURE__ */ jsx("p", { role: "alert", className: "px-4 py-2 text-sm text-red-600 dark:text-red-400", children: failure }) : null,
679
- /* @__PURE__ */ jsx(
680
- MessageComposer,
681
- {
682
- onSend: (text) => void handleSend(text),
683
- placeholder: placeholder ?? "Escreva como o cliente\u2026"
684
- }
685
- )
1085
+ failure ? /* @__PURE__ */ jsx2("p", { role: "alert", className: "px-4 py-2 text-sm text-red-600 dark:text-red-400", children: failure }) : null,
1086
+ loadFailure ? /* @__PURE__ */ jsx2("p", { role: "status", className: "px-4 py-2 text-sm text-amber-700 dark:text-amber-400", children: loadFailure }) : null,
1087
+ /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-1", children: [
1088
+ /* @__PURE__ */ jsx2("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx2(
1089
+ MessageComposer,
1090
+ {
1091
+ onSend: (text) => void handleSend(text),
1092
+ onAttach: uploadMedia ? (file) => void handleAttach(file) : void 0,
1093
+ placeholder: placeholder ?? "Escreva como o cliente\u2026"
1094
+ }
1095
+ ) }),
1096
+ uploadMedia ? /* @__PURE__ */ jsx2(
1097
+ AudioRecorderButton,
1098
+ {
1099
+ onRecorded: (file) => void handleAttach(file),
1100
+ onFailure: (message) => setFailure(message)
1101
+ }
1102
+ ) : null
1103
+ ] })
686
1104
  ] });
687
1105
  }
688
1106
 
@@ -690,6 +1108,7 @@ function ConversationPreview({
690
1108
  import {
691
1109
  buildInboundAudioPayload,
692
1110
  buildInboundInteractivePayload,
1111
+ buildInboundMediaPayload,
693
1112
  buildInboundTextPayload,
694
1113
  serializeWebhookPayload
695
1114
  } from "@adatechnology/meta-whatsapp-contracts/testing";
@@ -709,7 +1128,7 @@ var PreviewWebhookRejectedError = class extends Error {
709
1128
  function assertPreviewEnvironment(isProduction) {
710
1129
  if (isProduction) throw new PreviewInProductionError();
711
1130
  }
712
- async function signWithWebCrypto(params) {
1131
+ async function signPreviewPayload(params) {
713
1132
  const encoder = new TextEncoder();
714
1133
  const key = await globalThis.crypto.subtle.importKey(
715
1134
  "raw",
@@ -721,6 +1140,7 @@ async function signWithWebCrypto(params) {
721
1140
  const signature = await globalThis.crypto.subtle.sign("HMAC", key, encoder.encode(params.rawBody));
722
1141
  return `sha256=${[...new Uint8Array(signature)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
723
1142
  }
1143
+ var signWithWebCrypto = signPreviewPayload;
724
1144
  function createPreviewWebhookClient(params) {
725
1145
  const sendPayload = async (payload) => {
726
1146
  const rawBody = serializeWebhookPayload(payload);
@@ -738,7 +1158,8 @@ function createPreviewWebhookClient(params) {
738
1158
  sendText: (text) => sendPayload(buildInboundTextPayload({ ...envelope, text })),
739
1159
  sendButtonReply: (reply) => sendPayload(buildInboundInteractivePayload({ ...envelope, buttonReply: reply })),
740
1160
  sendListReply: (reply) => sendPayload(buildInboundInteractivePayload({ ...envelope, listReply: reply })),
741
- sendAudio: (mediaId) => sendPayload(buildInboundAudioPayload({ ...envelope, mediaId }))
1161
+ sendAudio: (mediaId) => sendPayload(buildInboundAudioPayload({ ...envelope, mediaId })),
1162
+ sendMedia: (media) => sendPayload(buildInboundMediaPayload({ ...envelope, ...media }))
742
1163
  };
743
1164
  }
744
1165
 
@@ -777,12 +1198,67 @@ function startPreviewScript(params) {
777
1198
  }, intervalMs);
778
1199
  return () => clearInterval(timer);
779
1200
  }
1201
+
1202
+ // src/preview/MediaTypesPreview.tsx
1203
+ import { useMemo as useMemo2 } from "react";
1204
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1205
+ var MEDIA_TYPES_CONVERSATION_ID = "5511944443333";
1206
+ function MediaTypesPreview({
1207
+ conversationId = MEDIA_TYPES_CONVERSATION_ID,
1208
+ className
1209
+ }) {
1210
+ const store = useMemo2(
1211
+ () => createPreviewStore({ conversations: PREVIEW_CONVERSATIONS, messages: PREVIEW_MESSAGES }),
1212
+ []
1213
+ );
1214
+ const api = useMemo2(() => createMockConversationsApi({ store }), [store]);
1215
+ const sse = useMemo2(() => createMockSSEProvider({ store }), [store]);
1216
+ const messages = PREVIEW_MESSAGES[conversationId] ?? [];
1217
+ const documents = PREVIEW_DOCUMENTS[conversationId] ?? [];
1218
+ const mimeTypes = [...new Set(documents.map((document) => document.mimeType))];
1219
+ return /* @__PURE__ */ jsx3(ConversationsProvider, { api, sse, children: /* @__PURE__ */ jsxs2("div", { className, children: [
1220
+ /* @__PURE__ */ jsxs2("header", { className: "border-b px-4 py-3 dark:border-gray-700", children: [
1221
+ /* @__PURE__ */ jsx3("h1", { className: "text-lg font-semibold", children: "Teste manual de m\xEDdia" }),
1222
+ /* @__PURE__ */ jsxs2("p", { className: "text-sm text-gray-500", children: [
1223
+ documents.length,
1224
+ " arquivos, ",
1225
+ mimeTypes.length,
1226
+ " 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."
1227
+ ] })
1228
+ ] }),
1229
+ /* @__PURE__ */ jsxs2("div", { className: "grid gap-4 p-4 lg:grid-cols-2", children: [
1230
+ /* @__PURE__ */ jsxs2("section", { className: "space-y-3", children: [
1231
+ /* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Biblioteca da empresa" }),
1232
+ /* @__PURE__ */ jsx3(DocumentsLibrary, { perPage: documents.length || 20 })
1233
+ ] }),
1234
+ /* @__PURE__ */ jsxs2("section", { className: "space-y-3", children: [
1235
+ /* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Painel da conversa" }),
1236
+ /* @__PURE__ */ jsx3(ConversationDocumentsPanel, { conversationId, open: true, perPage: documents.length || 20 }),
1237
+ /* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Bolhas na thread" }),
1238
+ /* @__PURE__ */ jsx3(ConversationWallpaper, { className: "max-h-[70vh] overflow-y-auto rounded-lg px-3 py-2", children: messages.map((message, index) => /* @__PURE__ */ jsx3(
1239
+ MessageBubble,
1240
+ {
1241
+ message,
1242
+ isMine: message.direction === "outbound",
1243
+ isFirstInGroup: index === 0 || messages[index - 1]?.sender !== message.sender
1244
+ },
1245
+ message.id
1246
+ )) })
1247
+ ] })
1248
+ ] })
1249
+ ] }) });
1250
+ }
780
1251
  export {
1252
+ AudioRecorderButton,
781
1253
  ConversationPreview,
1254
+ DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
782
1255
  DEFAULT_PREVIEW_SCRIPT,
783
1256
  GLOBAL_CHANNEL,
1257
+ MEDIA_TYPES_CONVERSATION_ID,
1258
+ MediaTypesPreview,
784
1259
  PREVIEW_CONVERSATIONS,
785
1260
  PREVIEW_DOCUMENTS,
1261
+ PREVIEW_FILE_SAMPLES,
786
1262
  PREVIEW_MESSAGES,
787
1263
  PreviewInProductionError,
788
1264
  PreviewWebhookRejectedError,
@@ -791,7 +1267,13 @@ export {
791
1267
  createMockConversationsApi,
792
1268
  createMockEventSource,
793
1269
  createMockSSEProvider,
1270
+ createPreviewMediaResolver,
794
1271
  createPreviewStore,
795
1272
  createPreviewWebhookClient,
1273
+ mediaTypeOf,
1274
+ previewFileBase64,
1275
+ previewFileUrl,
1276
+ resolvePreviewFileSample,
1277
+ signPreviewPayload,
796
1278
  startPreviewScript
797
1279
  };