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

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.
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Bytes de verdade para o preview abrir.
3
+ *
4
+ * O mock antes devolvia a MESMA imagem PNG para qualquer documento, então clicar em "visualizar" num
5
+ * PDF entregava um PNG rotulado `application/pdf` e o leitor dizia que o arquivo era inválido. Cada
6
+ * amostra aqui é um arquivo mínimo porém legítimo do seu tipo: as de mídia foram geradas com ffmpeg
7
+ * e conferidas com ffprobe, o PDF tem xref/startxref/%%EOF, e os pacotes Office são zips com as
8
+ * partes obrigatórias (`[Content_Types].xml`, `_rels/.rels`, o documento).
9
+ *
10
+ * Não há amostra de `audio/amr`, `video/3gp` nem dos formatos binários legados do Office
11
+ * (`application/msword`, `application/vnd.ms-excel`, `application/vnd.ms-powerpoint`): não há
12
+ * encoder para eles nesta máquina e forjar OLE2 à mão daria arquivo corrompido — exatamente o que
13
+ * este módulo existe para evitar. Esses caem no texto explicativo de `resolvePreviewFileSample`,
14
+ * que abre sem erro e diz o que é.
15
+ *
16
+ * GERADO — não editar à mão.
17
+ */
18
+
19
+ export const PREVIEW_FILE_SAMPLES: Readonly<Record<string, string>> = {
20
+ 'application/pdf':
21
+ 'data:application/pdf;base64,JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgMjYwIDExMF0vQ29udGVudHMgNCAwIFIvUmVzb3VyY2VzPDwvRm9udDw8L0YxIDUgMCBSPj4+Pj4+ZW5kb2JqCjQgMCBvYmo8PC9MZW5ndGggNjI+PnN0cmVhbQpCVCAvRjEgMTMgVGYgMTggNTUgVGQgKERvY3VtZW50byBkZSBleGVtcGxvIGRvIHByZXZpZXcpIFRqIEVUCmVuZHN0cmVhbWVuZG9iago1IDAgb2JqPDwvVHlwZS9Gb250L1N1YnR5cGUvVHlwZTEvQmFzZUZvbnQvSGVsdmV0aWNhPj5lbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTIgMDAwMDAgbiAKMDAwMDAwMDEwMSAwMDAwMCBuIAowMDAwMDAwMjExIDAwMDAwIG4gCjAwMDAwMDAzMTcgMDAwMDAgbiAKdHJhaWxlcjw8L1NpemUgNi9Sb290IDEgMCBSPj4Kc3RhcnR4cmVmCjM3OAolJUVPRgo=',
22
+ 'image/png':
23
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAAACXBIWXMAAAABAAAAAQBPJcTWAAAATElEQVR4nO3OMRHAIAAAMbhDWo2hCYPdu/xIh0RB5rPP+JN1O/AlVISKUBEqQkWoCBWhIlSEilARKkJFqAgVoSJUhIpQESpCRagIlRcgDwI25HmPQAAAAABJRU5ErkJggg==',
24
+ 'image/jpeg':
25
+ 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD//gAQTGF2YzYyLjExLjEwMAD/2wBDAAgEBAQEBAUFBQUFBQYGBgYGBgYGBgYGBgYHBwcICAgHBwcGBgcHCAgICAkJCQgICAgJCQoKCgwMCwsODg4RERT/xABMAAEBAAAAAAAAAAAAAAAAAAAABAEBAQAAAAAAAAAAAAAAAAAAAAYQAQAAAAAAAAAAAAAAAAAAAAARAQAAAAAAAAAAAAAAAAAAAAD/wAARCAAwADADASIAAhEAAxEA/9oADAMBAAIRAxEAPwCUBRpwAAAAAAAAAAAAAAAB/9k=',
26
+ 'image/webp': 'data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==',
27
+ 'video/mp4':
28
+ 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAANdbW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAASwAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAoh0cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAASwAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAEAAAABAAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAAEsAAAIAAABAAAAAAIAbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAAoAAAAEABVxAAAAAAALWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAABq21pbmYAAAAUdm1oZAAAAAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAWtzdGJsAAAAv3N0c2QAAAAAAAAAAQAAAK9hdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAEAAQABIAAAASAAAAAAAAAABFUxhdmM2Mi4xMS4xMDAgbGlieDI2NAAAAAAAAAAAAAAAGP//AAAANWF2Y0MBZAAK/+EAGGdkAAqs2UQmwEQAAAMABAAAAwBQPEiWWAEABmjr48siwP34+AAAAAAQcGFzcAAAAAEAAAABAAAAFGJ0cnQAAAAAAABO2gAAAAAAAAAYc3R0cwAAAAAAAAABAAAAAwAABAAAAAAUc3RzcwAAAAAAAAABAAAAAQAAAChjdHRzAAAAAAAAAAMAAAABAAAIAAAAAAEAAAwAAAAAAQAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAMAAAABAAAAIHN0c3oAAAAAAAAAAAAAAAMAAALbAAAADgAAAAwAAAAUc3RjbwAAAAAAAAABAAADjQAAAGF1ZHRhAAAAWW1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALGlsc3QAAAAkqXRvbwAAABxkYXRhAAAAAQAAAABMYXZmNjIuMy4xMDAAAAAIZnJlZQAAAv1tZGF0AAACrgYF//+q3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2NSByMzIyMiBiMzU2MDVhIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAyNSAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTIgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTEwIHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAJWWIhAA///7mdfgUoKykhh+DdK6xVynnAWznTQ4fVSgMZ1FwY28AAAAKQZoibEN//qePiAAAAAgBnkF5Df8HfQ==',
29
+ 'audio/mpeg':
30
+ 'data:audio/mpeg;base64,SUQzBAAAAAAAIlRTU0UAAAAOAAADTGF2ZjYyLjMuMTAwAAAAAAAAAAAAAAD/40jAAAAAAAAAAAAASW5mbwAAAA8AAAAFAAAGwABVVVVVVVVVVVVVVVVVVVVVVVVVgICAgICAgICAgICAgICAgICAgICqqqqqqqqqqqqqqqqqqqqqqqqqqtXV1dXV1dXV1dXV1dXV1dXV1dXV//////////////////////////8AAAAATGF2YzYyLjExAAAAAAAAAAAAAAAAJALAAAAAAAAABsDR4nNFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/40jEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU=',
31
+ 'audio/ogg':
32
+ 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAABXDTmmAAAAAKMnDKEBE09wdXNIZWFkAQE4AYC7AAAAAABPZ2dTAAAAAAAAAAAAAFcNOaYBAAAAZlPuKwE9T3B1c1RhZ3MMAAAATGF2ZjYyLjMuMTAwAQAAAB0AAABlbmNvZGVyPUxhdmM2Mi4xMS4xMDAgbGlib3B1c09nZ1MABLgmAAAAAAAAVw05pgIAAAA3uL6lCw8REhISEhISEhISeAvkwTbsxY2MSUaZpCaweAfJcifhROpV8fDA1d3pUmB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOB4B8l5yMlXwKISI/rvZ/NkwOA=',
33
+ 'audio/mp4':
34
+ 'data:audio/mp4;base64,AAAAHGZ0eXBNNEEgAAACAE00QSBpc29taXNvMgAAAAhmcmVlAAAAQW1kYXTeAgBMYXZjNjIuMTEuMTAwAAIwQA4BGCAHARggBwEYIAcBGCAHARggBwEYIAcBGCAHARggBwEYIAcAAAMibW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAAMgAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAk10cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAAMgAAAAAAAAAAAAAAAEBAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAADIAAAEAAABAAAAAAHFbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAACsRAAAJnRVxAAAAAAALWhkbHIAAAAAAAAAAHNvdW4AAAAAAAAAAAAAAABTb3VuZEhhbmRsZXIAAAABcG1pbmYAAAAQc21oZAAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABNHN0YmwAAABqc3RzZAAAAAAAAAABAAAAWm1wNGEAAAAAAAAAAQAAAAAAAAAAAAEAEAAAAACsRAAAAAAANmVzZHMAAAAAA4CAgCUAAQAEgICAF0AVAAAAAAB9AAAAB/oFgICABRIIVuUABoCAgAECAAAAIHN0dHMAAAAAAAAAAgAAAAkAAAQAAAAAAQAAAnQAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAoAAAABAAAAPHN0c3oAAAAAAAAAAAAAAAoAAAAVAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAAFHN0Y28AAAAAAAAAAQAAACwAAAAac2dwZAEAAAByb2xsAAAAAgAAAAH//wAAABxzYmdwAAAAAHJvbGwAAAABAAAACgAAAAEAAABhdWR0YQAAAFltZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAACxpbHN0AAAAJKl0b28AAAAcZGF0YQAAAAEAAAAATGF2ZjYyLjMuMTAw',
35
+ 'audio/aac':
36
+ 'data:audio/aac;base64,//FQQAOf/N4CAExhdmM2Mi4xMS4xMDAAAjBADv/xUEABf/wBGCAH//FQQAF//AEYIAf/8VBAAX/8ARggB//xUEABf/wBGCAH//FQQAF//AEYIAf/8VBAAX/8ARggB//xUEABf/wBGCAH//FQQAF//AEYIAf/8VBAAX/8ARggBw==',
37
+ 'text/plain': 'data:text/plain;base64,TGlzdGEgZGUgY29tcHJhcwotIGFycm96IDJrZwotIG9sZW8gMXVuCg==',
38
+ 'text/csv': 'data:text/csv;base64,cHJvZHV0byxxdWFudGlkYWRlLHByZWNvCmFycm96LDIsMjQuOTAKb2xlbywxLDcuNTAK',
39
+ 'application/zip':
40
+ 'data:application/zip;base64,UEsDBBQAAAAIANFO/FwtmGOxGwAAABkAAAARAAAAY29tcHJvdmFudGUtMS50eHRLzs8tKMovS8wrSVVISVUoSExPzE3NK8nnAgBQSwMEFAAAAAgA0U78XCftV8YWAAAAFAAAABEAAABjb21wcm92YW50ZS0yLnR4dCtOTS/NS8lXSM7PLSjKL0vMK0nlAgBQSwECFAMUAAAACADRTvxcLZhjsRsAAAAZAAAAEQAAAAAAAAAAAAAAgAEAAAAAY29tcHJvdmFudGUtMS50eHRQSwECFAMUAAAACADRTvxcJ+1XxhYAAAAUAAAAEQAAAAAAAAAAAAAAgAFKAAAAY29tcHJvdmFudGUtMi50eHRQSwUGAAAAAAIAAgB+AAAAjwAAAAAA',
41
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
42
+ '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==',
43
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
44
+ 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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',
45
+ }
46
+
47
+ const FAMILY_FALLBACK: Readonly<Record<string, string>> = {
48
+ image: 'image/png',
49
+ video: 'video/mp4',
50
+ audio: 'audio/mpeg',
51
+ text: 'text/plain',
52
+ }
53
+
54
+ /**
55
+ * A data URL que o preview abre para um documento.
56
+ *
57
+ * Ordem: tipo exato → família (`audio/amr` toca como mp3, `video/3gp` como mp4) → nota em texto.
58
+ * A nota fecha o caso dos tipos sem amostra: abre num leitor de texto e explica, em vez de baixar
59
+ * um binário quebrado com extensão de Word.
60
+ */
61
+ export function resolvePreviewFileSample(mimeType: string | undefined, filename?: string): string {
62
+ const normalized = (mimeType ?? '').split(';')[0]!.trim().toLowerCase()
63
+ const exact = PREVIEW_FILE_SAMPLES[normalized]
64
+ if (exact) return exact
65
+
66
+ const family = FAMILY_FALLBACK[normalized.split('/')[0] ?? '']
67
+ const byFamily = family ? PREVIEW_FILE_SAMPLES[family] : undefined
68
+ if (byFamily) return byFamily
69
+
70
+ const note = `Preview do SDK: nao existe amostra binaria valida para ${normalized || 'tipo desconhecido'}${
71
+ filename ? ` (${filename})` : ''
72
+ }. Na tela real este arquivo vem do storage.`
73
+ return `data:text/plain;charset=utf-8,${encodeURIComponent(note)}`
74
+ }
@@ -275,8 +275,7 @@ export const PREVIEW_MESSAGES: Readonly<Record<string, readonly MessagePayload[]
275
275
  timestamp: at('15:09:00'),
276
276
  },
277
277
  {
278
- // Extensão fora do EXTENSION_STYLE: garante que o ícone genérico cinza também apareça.
279
- id: 'fixture-doc-generic',
278
+ id: 'fixture-doc-txt',
280
279
  type: 'document',
281
280
  uploadId: 'preview/documentos/lista-compras.txt',
282
281
  filename: 'lista-compras.txt',
@@ -286,20 +285,153 @@ export const PREVIEW_MESSAGES: Readonly<Record<string, readonly MessagePayload[]
286
285
  sender: 'customer',
287
286
  timestamp: at('15:10:00'),
288
287
  },
288
+ {
289
+ id: 'fixture-doc-csv',
290
+ type: 'document',
291
+ uploadId: 'preview/documentos/planilha-do-pedido.csv',
292
+ filename: 'planilha-do-pedido.csv',
293
+ mimeType: 'text/csv',
294
+ sizeBytes: 2_048,
295
+ direction: 'inbound',
296
+ sender: 'customer',
297
+ timestamp: at('15:11:00'),
298
+ },
299
+ {
300
+ id: 'fixture-doc-pptx',
301
+ type: 'document',
302
+ uploadId: 'preview/documentos/apresentacao.pptx',
303
+ filename: 'apresentacao.pptx',
304
+ mimeType: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
305
+ sizeBytes: 512_000,
306
+ direction: 'inbound',
307
+ sender: 'customer',
308
+ timestamp: at('15:12:00'),
309
+ },
310
+ {
311
+ id: 'fixture-doc-ppt',
312
+ type: 'document',
313
+ uploadId: 'preview/documentos/antiga.ppt',
314
+ filename: 'antiga.ppt',
315
+ mimeType: 'application/vnd.ms-powerpoint',
316
+ sizeBytes: 348_160,
317
+ direction: 'inbound',
318
+ sender: 'customer',
319
+ timestamp: at('15:13:00'),
320
+ },
321
+ // Mídia enviada COMO arquivo: aí a Meta manda o nome, então é onde a extensão de música e de
322
+ // vídeo aparece na lista. Mandada como `image`/`audio`/`video` (acima e abaixo) ela chega sem
323
+ // nome nenhum, e o rótulo vira o id da mídia — como em produção.
324
+ {
325
+ id: 'fixture-doc-mp3',
326
+ type: 'document',
327
+ uploadId: 'preview/documentos/musica-do-anuncio.mp3',
328
+ filename: 'musica-do-anuncio.mp3',
329
+ mimeType: 'audio/mpeg',
330
+ sizeBytes: 3_145_728,
331
+ direction: 'inbound',
332
+ sender: 'customer',
333
+ timestamp: at('15:14:00'),
334
+ },
335
+ {
336
+ id: 'fixture-doc-m4a',
337
+ type: 'document',
338
+ uploadId: 'preview/documentos/recado.m4a',
339
+ filename: 'recado.m4a',
340
+ mimeType: 'audio/mp4',
341
+ sizeBytes: 486_400,
342
+ direction: 'inbound',
343
+ sender: 'customer',
344
+ timestamp: at('15:15:00'),
345
+ },
346
+ {
347
+ id: 'fixture-doc-mp4-file',
348
+ type: 'document',
349
+ uploadId: 'preview/documentos/video-do-produto.mp4',
350
+ filename: 'video-do-produto.mp4',
351
+ mimeType: 'video/mp4',
352
+ sizeBytes: 8_388_608,
353
+ direction: 'inbound',
354
+ sender: 'customer',
355
+ timestamp: at('15:16:00'),
356
+ },
357
+ {
358
+ id: 'fixture-doc-jpg',
359
+ type: 'document',
360
+ uploadId: 'preview/documentos/foto-do-recibo.jpg',
361
+ filename: 'foto-do-recibo.jpg',
362
+ mimeType: 'image/jpeg',
363
+ sizeBytes: 262_144,
364
+ direction: 'inbound',
365
+ sender: 'customer',
366
+ timestamp: at('15:17:00'),
367
+ },
368
+ // As espécies restantes que a Meta aceita, sem nome de arquivo — fecham a lista de tipos
369
+ // aceitos e exercitam o fallback de ícone pela família do mimeType.
370
+ {
371
+ id: 'fixture-media-jpeg',
372
+ type: 'image',
373
+ mediaId: 'preview-image-jpeg',
374
+ mimeType: 'image/jpeg',
375
+ sizeBytes: 138_240,
376
+ direction: 'inbound',
377
+ sender: 'customer',
378
+ timestamp: at('15:18:00'),
379
+ },
380
+ {
381
+ id: 'fixture-media-3gp',
382
+ type: 'video',
383
+ mediaId: 'preview-video-3gp',
384
+ mimeType: 'video/3gp',
385
+ sizeBytes: 921_600,
386
+ direction: 'inbound',
387
+ sender: 'customer',
388
+ timestamp: at('15:19:00'),
389
+ },
390
+ {
391
+ id: 'fixture-media-aac',
392
+ type: 'audio',
393
+ mediaId: 'preview-audio-aac',
394
+ mimeType: 'audio/aac',
395
+ sizeBytes: 76_800,
396
+ direction: 'inbound',
397
+ sender: 'customer',
398
+ timestamp: at('15:20:00'),
399
+ },
400
+ {
401
+ id: 'fixture-media-amr',
402
+ type: 'audio',
403
+ mediaId: 'preview-audio-amr',
404
+ mimeType: 'audio/amr',
405
+ sizeBytes: 24_576,
406
+ direction: 'inbound',
407
+ sender: 'customer',
408
+ timestamp: at('15:21:00'),
409
+ },
289
410
  ],
290
411
  }
291
412
 
292
413
  /**
293
- * A biblioteca de arquivos da conversa, como o backend a devolveria. Deriva das mensagens de
294
- * documento acima em vez de repetir os dados: fixture duplicada divergiria na primeira edição, e o
295
- * painel passaria a mostrar arquivo que a thread não tem.
414
+ * A biblioteca de arquivos da conversa, como o backend a devolveria. Deriva das mensagens acima em
415
+ * vez de repetir os dados: fixture duplicada divergiria na primeira edição, e o painel passaria a
416
+ * mostrar arquivo que a thread não tem.
417
+ *
418
+ * Entram as CINCO espécies de mídia, na mesma lista de `extractMediaDescriptor` no backend — não só
419
+ * `document`. Filtrar por documento era infidelidade grave do preview: `IngestInboundMedia` linka
420
+ * foto, vídeo, áudio e sticker na mesma tabela, então na tela real eles aparecem na biblioteca e no
421
+ * preview não apareciam.
296
422
  */
423
+ const LIBRARY_MEDIA_TYPES = new Set(['document', 'image', 'video', 'audio', 'sticker'])
424
+
297
425
  export const PREVIEW_DOCUMENTS: Readonly<Record<string, readonly ConversationDocument[]>> = {
298
426
  '5511944443333': (PREVIEW_MESSAGES['5511944443333'] ?? [])
299
- .filter((message) => message.type === 'document')
427
+ .filter((message) => LIBRARY_MEDIA_TYPES.has(message.type))
300
428
  .map((message) => ({
301
- id: message.uploadId ?? message.id,
302
- filename: message.filename ?? message.id,
429
+ // Mídia não tem `uploadId` na mensagem: o backend deriva a key do id da mídia na Meta, e o
430
+ // caminho abaixo imita esse formato para o download assinado receber algo do mesmo feitio.
431
+ id: message.uploadId ?? (message.mediaId ? `preview/inbound/${message.mediaId}` : message.id),
432
+ // Só documento chega nomeado. Para os outros o backend salva o id da mídia como rótulo —
433
+ // manter isso aqui é o que faz o ícone por família do mimeType ser exercitado de verdade.
434
+ filename: message.filename ?? message.mediaId ?? message.id,
303
435
  mimeType: message.mimeType ?? 'application/octet-stream',
304
436
  sizeBytes: message.sizeBytes ?? 0,
305
437
  source: message.sender,
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Guarda o resolvedor de mídia do preview.
3
+ *
4
+ * As asserções comparam com `previewFileUrl(tipo)` em vez de com a data URL da amostra: o bun também
5
+ * tem `URL.createObjectURL`, então o retorno é `blob:` aqui como no navegador, e blob URL não expõe
6
+ * o conteúdo para comparar. O que este teste trava é o MAPEAMENTO — mensagem → amostra do tipo certo
7
+ * —, enquanto os bytes de cada tipo são conferidos em `previewFileSamples.test.ts`.
8
+ */
9
+
10
+ import { describe, expect, it } from 'bun:test'
11
+
12
+ import { createPreviewMediaResolver, previewFileUrl } from './previewMediaSource'
13
+ import type { MessagePayload } from '../types'
14
+
15
+ const resolve = createPreviewMediaResolver()
16
+
17
+ function message(partial: Partial<MessagePayload>): MessagePayload {
18
+ return {
19
+ id: 'msg-1',
20
+ type: 'image',
21
+ direction: 'inbound',
22
+ sender: 'customer',
23
+ timestamp: '2026-07-26T15:00:00.000Z',
24
+ ...partial,
25
+ } as MessagePayload
26
+ }
27
+
28
+ describe('createPreviewMediaResolver', () => {
29
+ it('resolve mídia pelo mediaId, achando o tipo na biblioteca do fixture', async () => {
30
+ // preview-image-1 é a foto png da thread da Rita.
31
+ expect(await resolve(message({ mediaId: 'preview-image-1' }))).toBe(previewFileUrl('image/png'))
32
+ })
33
+
34
+ it('resolve documento pelo uploadId', async () => {
35
+ const resolved = await resolve(message({ type: 'document', uploadId: 'preview/documentos/nota-fiscal.pdf' }))
36
+
37
+ expect(resolved).toBe(previewFileUrl('application/pdf'))
38
+ })
39
+
40
+ // O sticker é webp e o áudio de voz é opus: dois tipos que o painel antes nem listava.
41
+ it('resolve sticker e áudio', async () => {
42
+ expect(await resolve(message({ mediaId: 'preview-sticker-1' }))).toBe(previewFileUrl('image/webp'))
43
+ expect(await resolve(message({ mediaId: 'preview-audio-2' }))).toBe(previewFileUrl('audio/ogg'))
44
+ })
45
+
46
+ // Mídia que não está na biblioteca ainda tem de abrir: cai no mimeType da própria mensagem.
47
+ it('usa o mimeType da mensagem quando o id é desconhecido', async () => {
48
+ const resolved = await resolve(message({ mediaId: 'nunca-visto', mimeType: 'image/jpeg' }))
49
+
50
+ expect(resolved).toBe(previewFileUrl('image/jpeg'))
51
+ })
52
+
53
+ // Sem referência nenhuma o MediaRenderer não deve receber URL — devolver algo aqui faria a bolha
54
+ // mostrar arquivo que a mensagem não tem.
55
+ it('devolve null para mensagem sem uploadId nem mediaId', async () => {
56
+ expect(await resolve(message({ type: 'text' }))).toBeNull()
57
+ })
58
+
59
+ it('reaproveita a mesma URL para o mesmo tipo', () => {
60
+ expect(previewFileUrl('application/pdf')).toBe(previewFileUrl('application/pdf'))
61
+ })
62
+ })
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Como o preview entrega mídia para a UI abrir.
3
+ *
4
+ * Separado de `previewFileSamples` (que é gerado) porque aqui está a parte que depende do navegador:
5
+ * **blob URL, não data URL**. O Chrome bloqueia navegação de topo para `data:` desde a v60, então
6
+ * `window.open(dataUrl)` — que é o que os botões "visualizar" e "baixar" fazem — abre aba em branco
7
+ * mesmo com bytes perfeitamente válidos. Em `src` de `<img>`/`<video>` a data URL funcionaria; no
8
+ * `window.open` não, e o mesmo `getDocumentUrl` alimenta os dois.
9
+ */
10
+
11
+ import type { MessagePayload } from '../types'
12
+ import type { ResolveMediaUrl } from '../MediaRenderer'
13
+ import { resolvePreviewFileSample } from './previewFileSamples'
14
+ import { PREVIEW_DOCUMENTS } from './previewFixtures'
15
+
16
+ // Uma blob URL por tipo, reaproveitada: cada `createObjectURL` retém o blob até um `revokeObjectURL`
17
+ // que ninguém chamaria, e a lista redesenha a cada filtro digitado.
18
+ const blobUrlCache = new Map<string, string>()
19
+
20
+ function toBlob(dataUrl: string): Blob {
21
+ const [head, payload] = dataUrl.split(',')
22
+ const mimeType = head!
23
+ .replace(/^data:/, '')
24
+ .replace(/;base64$/, '')
25
+ .replace(/;charset=.*$/, '')
26
+
27
+ if (!head!.endsWith(';base64')) {
28
+ return new Blob([decodeURIComponent(payload!)], { type: mimeType || 'text/plain' })
29
+ }
30
+
31
+ const binary = atob(payload!)
32
+ const bytes = new Uint8Array(binary.length)
33
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index)
34
+ return new Blob([bytes], { type: mimeType })
35
+ }
36
+
37
+ /** URL que o preview pode abrir em aba nova. Cai na data URL fora do navegador (teste, SSR). */
38
+ export function previewFileUrl(mimeType: string | undefined, filename?: string): string {
39
+ const dataUrl = resolvePreviewFileSample(mimeType, filename)
40
+ if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') return dataUrl
41
+
42
+ const cached = blobUrlCache.get(dataUrl)
43
+ if (cached) return cached
44
+
45
+ const blobUrl = URL.createObjectURL(toBlob(dataUrl))
46
+ blobUrlCache.set(dataUrl, blobUrl)
47
+ return blobUrl
48
+ }
49
+
50
+ /**
51
+ * A mesma amostra em base64 cru, como o proxy de mídia do backend a devolveria.
52
+ *
53
+ * Existe porque `getMediaProxyUrl` é o caminho da mídia AINDA NÃO ingerida (só existe o id na Meta),
54
+ * e o contrato pede `{ mimeType, data }` — não URL. Sem isto o mock devolvia o PNG 1x1 para
55
+ * qualquer id, e vídeo e áudio da thread apareciam quebrados apesar de haver amostra válida.
56
+ */
57
+ export function previewFileBase64(mimeType: string | undefined, filename?: string): { mimeType: string; data: string } {
58
+ const dataUrl = resolvePreviewFileSample(mimeType, filename)
59
+ const [head, payload] = dataUrl.split(',')
60
+ const declared = head!
61
+ .replace(/^data:/, '')
62
+ .replace(/;base64$/, '')
63
+ .replace(/;charset=.*$/, '')
64
+
65
+ if (head!.endsWith(';base64')) return { mimeType: declared, data: payload! }
66
+ return { mimeType: declared, data: btoa(decodeURIComponent(payload!)) }
67
+ }
68
+
69
+ /**
70
+ * O `onResolveMediaUrl` que o `MessageBubble` espera.
71
+ *
72
+ * Sem ele, foto, vídeo e áudio da thread ficam parados no placeholder para sempre — o
73
+ * `MediaRenderer` só resolve `uploadId`/`mediaId` por esta porta, de propósito, para o pacote nunca
74
+ * chamar endpoint fixo. O preview não tinha resolvedor nenhum, então nenhuma mídia carregava.
75
+ */
76
+ export function createPreviewMediaResolver(): ResolveMediaUrl {
77
+ const byId = new Map<string, { mimeType: string; filename: string }>()
78
+ for (const documents of Object.values(PREVIEW_DOCUMENTS)) {
79
+ for (const document of documents) {
80
+ byId.set(document.id, { mimeType: document.mimeType, filename: document.filename })
81
+ }
82
+ }
83
+
84
+ return async (message: MessagePayload): Promise<string | null> => {
85
+ const reference = message.uploadId ?? (message.mediaId ? `preview/inbound/${message.mediaId}` : undefined)
86
+ if (!reference) return null
87
+
88
+ const known = byId.get(reference)
89
+ return previewFileUrl(known?.mimeType ?? message.mimeType, known?.filename ?? message.filename)
90
+ }
91
+ }
@@ -36,6 +36,16 @@ export interface ListDocumentsParams {
36
36
  sortDirection?: 'asc' | 'desc'
37
37
  }
38
38
 
39
+ /** Arquivo na biblioteca da empresa: o mesmo da conversa, mais de qual conversa veio. */
40
+ export interface CompanyDocument extends ConversationDocument {
41
+ conversationId: string
42
+ }
43
+
44
+ export interface CompanyDocumentPage {
45
+ documents: CompanyDocument[]
46
+ total: number
47
+ }
48
+
39
49
  export interface ConversationDocumentPage {
40
50
  documents: ConversationDocument[]
41
51
  total: number
@@ -93,6 +103,13 @@ export interface ConversationsApi {
93
103
  * lote em vez de oferecer um botão que falha.
94
104
  */
95
105
  downloadDocumentsArchive?(conversationId: string, uploadIds: readonly string[]): Promise<Blob>
106
+ /**
107
+ * Biblioteca de TODAS as conversas, para uma tela de Documentos fora do atendimento.
108
+ *
109
+ * Opcional por capacidade: host que só expõe anexo dentro da conversa não implementa, e o
110
+ * componente de biblioteca simplesmente não é usável — melhor que uma tela que sempre erra.
111
+ */
112
+ getAllDocuments?(params?: ListDocumentsParams): Promise<CompanyDocumentPage>
96
113
  getMediaProxyUrl(mediaId: string): Promise<{ mimeType: string; data: string }>
97
114
 
98
115
  /**
@@ -24,6 +24,7 @@ export interface WhatsAppCreateTemplateFormLabels {
24
24
  sectionDescription: string
25
25
  nameLabel: string
26
26
  nameHint: string
27
+ namePlaceholder: string
27
28
  categoryLabel: string
28
29
  languageLabel: string
29
30
  headerLabel: string
@@ -57,6 +58,7 @@ const DEFAULT_LABELS: WhatsAppCreateTemplateFormLabels = {
57
58
  sectionDescription: 'Envia um template para aprovação da Meta.',
58
59
  nameLabel: 'Nome do template',
59
60
  nameHint: 'Somente letras minúsculas, números e underscore.',
61
+ namePlaceholder: 'reengajamento_cliente',
60
62
  categoryLabel: 'Categoria',
61
63
  languageLabel: 'Idioma',
62
64
  headerLabel: 'Cabeçalho',
@@ -142,7 +144,7 @@ export function WhatsAppCreateTemplateForm({
142
144
  type="text"
143
145
  value={value.name}
144
146
  onChange={(e) => set('name', e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '_'))}
145
- placeholder="reengajamento_cliente"
147
+ placeholder={labels.namePlaceholder}
146
148
  className="w-full border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition-all"
147
149
  required
148
150
  />