@adatechnology/conversations-ui 0.13.0 → 0.14.0

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 (35) hide show
  1. package/dist/chunk-RLUNDWGA.js +51 -0
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.js +5 -43
  4. package/dist/participant/index.d.ts +74 -2
  5. package/dist/participant/index.js +1058 -399
  6. package/dist/styles.css +213 -0
  7. package/package.json +3 -3
  8. package/src/conversationTranscript.ts +6 -2
  9. package/src/participant/ConversationThread.tsx +4 -0
  10. package/src/participant/ParticipantAuthorAvatar.tsx +7 -1
  11. package/src/participant/ParticipantBubbleBody.tsx +18 -3
  12. package/src/participant/ParticipantConversationMenu.tsx +89 -0
  13. package/src/participant/ParticipantConversations.tsx +3 -0
  14. package/src/participant/ParticipantMenuBubble.tsx +106 -0
  15. package/src/participant/ParticipantMessageBubble.tsx +34 -7
  16. package/src/participant/ParticipantMessageInfo.tsx +70 -0
  17. package/src/participant/ParticipantMessageMenu.tsx +168 -0
  18. package/src/participant/ParticipantMessageMenuContext.tsx +109 -0
  19. package/src/participant/ParticipantSelectionBar.tsx +64 -0
  20. package/src/participant/ParticipantThread.tsx +27 -2
  21. package/src/participant/ParticipantThreadScreen.tsx +1 -0
  22. package/src/participant/index.ts +6 -0
  23. package/src/participant/participantConversationMenuDom.test.tsx +166 -0
  24. package/src/participant/participantLabels.ts +40 -0
  25. package/src/participant/participantMessageInfoModel.test.ts +68 -0
  26. package/src/participant/participantMessageInfoModel.ts +80 -0
  27. package/src/participant/participantMessageMenuDom.test.tsx +111 -0
  28. package/src/participant/participantMessageMenuModel.test.ts +43 -0
  29. package/src/participant/participantMessageMenuModel.ts +91 -0
  30. package/src/participant/participantMessageMoment.ts +7 -0
  31. package/src/participant/participantTranscriptionText.test.ts +48 -0
  32. package/src/participant/participantTranscriptionText.ts +28 -0
  33. package/src/participant/useOpenUpwards.ts +22 -0
  34. package/src/participant/useParticipantFailedMenu.ts +3 -0
  35. package/src/styles.css +40 -0
@@ -0,0 +1,51 @@
1
+ // src/conversationTranscript.ts
2
+ var SENDER_LABEL = {
3
+ customer: "Cliente",
4
+ bot: "Bot",
5
+ agent: "Atendente"
6
+ };
7
+ function buildTranscriptText(params) {
8
+ const header = [
9
+ `Conversa: ${params.clientName ?? params.whatsappNumber}`,
10
+ `N\xFAmero: ${params.whatsappNumber}`,
11
+ `Mensagens: ${params.messages.length}`,
12
+ ""
13
+ ];
14
+ const lines = params.messages.map((message) => {
15
+ const author = message.agentName ?? SENDER_LABEL[message.sender];
16
+ return `[${formatStamp(message.timestamp)}] ${author}: ${bodyOf(message)}`;
17
+ });
18
+ return [...header, ...lines].join("\n");
19
+ }
20
+ function bodyOf(message) {
21
+ const transcript = message.transcription?.text?.trim();
22
+ if (message.type === "audio" && transcript) return `[\xE1udio] ${transcript}`;
23
+ return message.content ?? message.caption ?? `<${message.type}>`;
24
+ }
25
+ function formatStamp(timestamp) {
26
+ if (!timestamp) return "data indispon\xEDvel";
27
+ const parsed = new Date(timestamp);
28
+ return Number.isNaN(parsed.getTime()) ? "data indispon\xEDvel" : parsed.toLocaleString("pt-BR");
29
+ }
30
+ function buildTranscriptFilename(whatsappNumber, generatedAt) {
31
+ const stamp = generatedAt.toISOString().slice(0, 10);
32
+ return `conversa-${whatsappNumber}-${stamp}.txt`;
33
+ }
34
+ function downloadTextFile(filename, content) {
35
+ const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
36
+ const url = URL.createObjectURL(blob);
37
+ const anchor = document.createElement("a");
38
+ anchor.href = url;
39
+ anchor.download = filename;
40
+ anchor.rel = "noopener";
41
+ document.body.appendChild(anchor);
42
+ anchor.click();
43
+ anchor.remove();
44
+ setTimeout(() => URL.revokeObjectURL(url), 0);
45
+ }
46
+
47
+ export {
48
+ buildTranscriptText,
49
+ buildTranscriptFilename,
50
+ downloadTextFile
51
+ };
package/dist/index.d.ts CHANGED
@@ -1246,7 +1246,8 @@ declare function buildTranscriptText(params: BuildTranscriptTextParams): string;
1246
1246
  declare function buildTranscriptFilename(whatsappNumber: string, generatedAt: Date): string;
1247
1247
  /**
1248
1248
  * Dispara o download no navegador. `revokeObjectURL` no fim não é higiene opcional: sem ele cada
1249
- * export retém o blob inteiro em memória até a aba fechar.
1249
+ * export retém o blob inteiro em memória até a aba fechar. O link entra no documento enquanto é clicado,
1250
+ * como o Firefox exige, e a revogação vai para o tique seguinte, depois de o download começar.
1250
1251
  */
1251
1252
  declare function downloadTextFile(filename: string, content: string): void;
1252
1253
 
package/dist/index.js CHANGED
@@ -1,6 +1,11 @@
1
1
  import {
2
2
  WhatsAppMessagePreview
3
3
  } from "./chunk-ZSOD5AQT.js";
4
+ import {
5
+ buildTranscriptFilename,
6
+ buildTranscriptText,
7
+ downloadTextFile
8
+ } from "./chunk-RLUNDWGA.js";
4
9
  import {
5
10
  TOOLTIP_ATTRIBUTE,
6
11
  TooltipLayer
@@ -2781,49 +2786,6 @@ function DocumentsWorkspace({
2781
2786
  ] });
2782
2787
  }
2783
2788
 
2784
- // src/conversationTranscript.ts
2785
- var SENDER_LABEL = {
2786
- customer: "Cliente",
2787
- bot: "Bot",
2788
- agent: "Atendente"
2789
- };
2790
- function buildTranscriptText(params) {
2791
- const header = [
2792
- `Conversa: ${params.clientName ?? params.whatsappNumber}`,
2793
- `N\xFAmero: ${params.whatsappNumber}`,
2794
- `Mensagens: ${params.messages.length}`,
2795
- ""
2796
- ];
2797
- const lines = params.messages.map((message) => {
2798
- const author = message.agentName ?? SENDER_LABEL[message.sender];
2799
- return `[${formatStamp(message.timestamp)}] ${author}: ${bodyOf(message)}`;
2800
- });
2801
- return [...header, ...lines].join("\n");
2802
- }
2803
- function bodyOf(message) {
2804
- const transcript = message.transcription?.text?.trim();
2805
- if (message.type === "audio" && transcript) return `[\xE1udio] ${transcript}`;
2806
- return message.content ?? message.caption ?? `<${message.type}>`;
2807
- }
2808
- function formatStamp(timestamp) {
2809
- if (!timestamp) return "data indispon\xEDvel";
2810
- const parsed = new Date(timestamp);
2811
- return Number.isNaN(parsed.getTime()) ? "data indispon\xEDvel" : parsed.toLocaleString("pt-BR");
2812
- }
2813
- function buildTranscriptFilename(whatsappNumber, generatedAt) {
2814
- const stamp = generatedAt.toISOString().slice(0, 10);
2815
- return `conversa-${whatsappNumber}-${stamp}.txt`;
2816
- }
2817
- function downloadTextFile(filename, content) {
2818
- const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
2819
- const url = URL.createObjectURL(blob);
2820
- const anchor = document.createElement("a");
2821
- anchor.href = url;
2822
- anchor.download = filename;
2823
- anchor.click();
2824
- URL.revokeObjectURL(url);
2825
- }
2826
-
2827
2789
  // src/DarkModeToggle.tsx
2828
2790
  import { Moon, Sun } from "lucide-react";
2829
2791
  import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { ParticipantAttachment, ParticipantConversationSummary, ParticipantConversationPage, ParticipantSubjectRef, ParticipantMessage } from '@adatechnology/conversation-contracts';
3
+ import { ParticipantAttachment, MessageDeliveryStatus, ParticipantConversationSummary, ParticipantConversationPage, ParticipantSubjectRef, ParticipantMessage } from '@adatechnology/conversation-contracts';
4
4
  export { ParticipantAttachment, ParticipantConversationPage, ParticipantConversationSummary, ParticipantMessage, ParticipantSubjectRef, ParticipantTranscription } from '@adatechnology/conversation-contracts';
5
5
  import { C as ConversationChannel, d as ConversationsTheme, Q as QuickReply } from '../quickReply.types-DzWH3NYV.js';
6
6
 
@@ -14,6 +14,12 @@ type ParticipantMessageActionsTarget = {
14
14
  readonly authorName: string | null;
15
15
  readonly isMine: boolean;
16
16
  readonly attachments: readonly ParticipantAttachment[];
17
+ /** When the message was written (ISO 8601). */
18
+ readonly createdAt: string;
19
+ /** Delivery state of a message this side sent, when the channel reports one. */
20
+ readonly status?: MessageDeliveryStatus | undefined;
21
+ /** When the other side read it (ISO 8601), when known. */
22
+ readonly readAt?: string | null;
17
23
  };
18
24
  type ParticipantMessageActionsRenderer = (message: ParticipantMessageActionsTarget) => ReactNode;
19
25
 
@@ -199,6 +205,29 @@ type ParticipantConversationsLabels = {
199
205
  readonly edit: string;
200
206
  /** Name of the menu button on a failed message. Optional; translated hosts should fill it, otherwise the English default shows. */
201
207
  readonly messageOptions?: string;
208
+ /** Message menu (opt-in `messageMenu`). Optional; translated hosts should fill them, otherwise English shows. */
209
+ readonly copyMessage?: string;
210
+ readonly copySelection?: string;
211
+ readonly selectMessages?: string;
212
+ /** Name of the check box beside each message in selection mode. */
213
+ readonly selectMessage?: string;
214
+ readonly cancelSelection?: string;
215
+ /** Selection bar count; `{count}` is replaced. */
216
+ readonly selectedCount?: string;
217
+ /** Message info panel: when it was sent, its delivery state and when it was read. */
218
+ readonly messageInfo?: string;
219
+ readonly infoAuthor?: string;
220
+ readonly infoSentAt?: string;
221
+ readonly infoStatus?: string;
222
+ readonly infoReadAt?: string;
223
+ readonly closeMessageInfo?: string;
224
+ /** Header menu of a thread with `messageMenu`: select messages and download the conversation as text. */
225
+ readonly conversationOptions?: string;
226
+ readonly downloadConversation?: string;
227
+ /** Transcribed audio: the word before what was said in copies and exports, and its two menu entries. */
228
+ readonly transcriptionPrefix?: string;
229
+ readonly copyTranscription?: string;
230
+ readonly downloadTranscription?: string;
202
231
  /** Toggle that opens the message as received (e.g. the whole email). Optional; the English default shows otherwise. */
203
232
  readonly showOriginalText?: string;
204
233
  readonly notFound: string;
@@ -228,6 +257,45 @@ type ParticipantConversationsLabels = {
228
257
  } & Readonly<Partial<ParticipantAudioLabels>>;
229
258
  declare const DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS: ParticipantConversationsLabels;
230
259
 
260
+ /** One entry the host adds to a message's menu, after the package's own (copy, select). */
261
+ type ParticipantMessageMenuItem = {
262
+ readonly id: string;
263
+ readonly label: string;
264
+ /** Runs on choice; the menu closes first. */
265
+ readonly onSelect?: () => void;
266
+ /** Draws the entry as a link (a file to open or save) instead of a button. */
267
+ readonly href?: string;
268
+ /** With `href`: the file name the browser saves it under. */
269
+ readonly download?: string;
270
+ readonly disabled?: boolean;
271
+ };
272
+ /** One button the host adds to the selection bar; it receives the selected messages in conversation order. */
273
+ type ParticipantSelectionAction = {
274
+ readonly id: string;
275
+ readonly label: string;
276
+ readonly onSelect: (messages: readonly ParticipantMessageActionsTarget[]) => void;
277
+ readonly disabled?: boolean;
278
+ };
279
+ /**
280
+ * Opt-in message menu, as in common chat apps: a discreet chevron on the bubble (or a right-click, or a long
281
+ * press) opens Copy and Select messages, plus the host's entries. Absent leaves the bubble as it was.
282
+ */
283
+ type ParticipantMessageMenuOptions = {
284
+ readonly items?: (message: ParticipantMessageActionsTarget) => readonly ParticipantMessageMenuItem[];
285
+ readonly selectionActions?: (messages: readonly ParticipantMessageActionsTarget[]) => readonly ParticipantSelectionAction[];
286
+ };
287
+ type CopyFormat = {
288
+ readonly locale?: string;
289
+ readonly meLabel: string;
290
+ /** Precedes what was said in a transcribed audio. */
291
+ readonly transcriptionLabel?: string;
292
+ };
293
+ /**
294
+ * Several messages as text: one line each, `[date time] author: text [file]`, like a chat export. A transcribed
295
+ * audio carries what was said, so the text stands on its own outside the thread.
296
+ */
297
+ declare function formatMessagesForCopy(messages: readonly ParticipantMessageActionsTarget[], format: CopyFormat): string;
298
+
231
299
  type ParticipantConversationsProps = {
232
300
  api: ParticipantConversationsApi;
233
301
  subjectGroups: readonly ParticipantSubjectGroup[];
@@ -253,6 +321,8 @@ type ParticipantConversationsProps = {
253
321
  renderAuthorAvatar?: ParticipantAuthorAvatarRenderer;
254
322
  /** Host slot at the end of each message's meta line (copy, forward...). The package knows no action. */
255
323
  renderMessageActions?: ParticipantMessageActionsRenderer;
324
+ /** Opt-in message menu as in common chat apps: copy, select messages, plus the host's entries. */
325
+ messageMenu?: ParticipantMessageMenuOptions;
256
326
  /** Speech-bubble tail on the bottom corner; false removes it. Default true. */
257
327
  tail?: boolean;
258
328
  /** Absent draws no microphone. Present lets the user record and send an audio as a message of its own. */
@@ -302,6 +372,8 @@ type ConversationThreadProps = {
302
372
  readonly renderAuthorAvatar?: ParticipantAuthorAvatarRenderer;
303
373
  /** Host slot at the end of each message's meta line (copy, forward...). The package knows no action. */
304
374
  readonly renderMessageActions?: ParticipantMessageActionsRenderer;
375
+ /** Opt-in message menu as in common chat apps: copy, select messages, plus the host's entries. */
376
+ readonly messageMenu?: ParticipantMessageMenuOptions;
305
377
  /** Absent draws no microphone. Present lets the user record and send an audio as a message of its own. */
306
378
  readonly audioRecording?: ParticipantAudioRecordingOptions;
307
379
  /** Return a node to replace the composer (notice + host actions); undefined keeps the composer. `closed` still wins. Evaluated every render. */
@@ -312,4 +384,4 @@ declare function ConversationThread(props: ConversationThreadProps): react.JSX.E
312
384
 
313
385
  declare function groupParticipantConversations(params: GroupParticipantConversationsParams): ParticipantInboxView;
314
386
 
315
- export { ConversationThread, type ConversationThreadApi, type ConversationThreadProps, DEFAULT_MAX_RECORDING_BYTES, DEFAULT_PARTICIPANT_AUDIO_LABELS, DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, type GroupParticipantConversationsParams, type ParticipantAudioLabels, type ParticipantAudioRecordingOptions, type ParticipantAuthorAvatarRenderer, type ParticipantConversationEvent, ParticipantConversations, type ParticipantConversationsApi, type ParticipantConversationsClassNames, type ParticipantConversationsLabels, type ParticipantConversationsProps, type ParticipantInboxSection, type ParticipantInboxView, type ParticipantLocalPendingMessage, type ParticipantMessageActionsRenderer, type ParticipantMessageActionsTarget, type ParticipantPendingMessage, type ParticipantPerspective, type ParticipantSendInput, type ParticipantSendResult, type ParticipantSubjectGroup, type ParticipantSubjectIconRenderer, groupParticipantConversations };
387
+ export { ConversationThread, type ConversationThreadApi, type ConversationThreadProps, DEFAULT_MAX_RECORDING_BYTES, DEFAULT_PARTICIPANT_AUDIO_LABELS, DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, type GroupParticipantConversationsParams, type ParticipantAudioLabels, type ParticipantAudioRecordingOptions, type ParticipantAuthorAvatarRenderer, type ParticipantConversationEvent, ParticipantConversations, type ParticipantConversationsApi, type ParticipantConversationsClassNames, type ParticipantConversationsLabels, type ParticipantConversationsProps, type ParticipantInboxSection, type ParticipantInboxView, type ParticipantLocalPendingMessage, type ParticipantMessageActionsRenderer, type ParticipantMessageActionsTarget, type ParticipantMessageMenuItem, type ParticipantMessageMenuOptions, type ParticipantPendingMessage, type ParticipantPerspective, type ParticipantSelectionAction, type ParticipantSendInput, type ParticipantSendResult, type ParticipantSubjectGroup, type ParticipantSubjectIconRenderer, formatMessagesForCopy, groupParticipantConversations };