@adatechnology/conversations-ui 0.12.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.
- package/dist/chunk-RLUNDWGA.js +51 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -43
- package/dist/participant/index.d.ts +87 -2
- package/dist/participant/index.js +1073 -398
- package/dist/styles.css +218 -0
- package/package.json +1 -1
- package/src/conversationTranscript.ts +6 -2
- package/src/participant/ConversationThread.tsx +8 -1
- package/src/participant/ParticipantAuthorAvatar.tsx +18 -0
- package/src/participant/ParticipantBubbleBody.tsx +33 -3
- package/src/participant/ParticipantConversationMenu.tsx +89 -0
- package/src/participant/ParticipantConversations.tsx +6 -1
- package/src/participant/ParticipantMenuBubble.tsx +106 -0
- package/src/participant/ParticipantMessageBubble.test.tsx +51 -0
- package/src/participant/ParticipantMessageBubble.tsx +39 -7
- package/src/participant/ParticipantMessageInfo.tsx +70 -0
- package/src/participant/ParticipantMessageMenu.tsx +168 -0
- package/src/participant/ParticipantMessageMenuContext.tsx +109 -0
- package/src/participant/ParticipantSelectionBar.tsx +64 -0
- package/src/participant/ParticipantThread.tsx +30 -3
- package/src/participant/ParticipantThreadScreen.tsx +2 -0
- package/src/participant/ParticipantTimeline.tsx +7 -1
- package/src/participant/index.ts +11 -1
- package/src/participant/participantConversationMenuDom.test.tsx +166 -0
- package/src/participant/participantLabels.ts +40 -0
- package/src/participant/participantMessageInfoModel.test.ts +68 -0
- package/src/participant/participantMessageInfoModel.ts +80 -0
- package/src/participant/participantMessageMenuDom.test.tsx +111 -0
- package/src/participant/participantMessageMenuModel.test.ts +43 -0
- package/src/participant/participantMessageMenuModel.ts +91 -0
- package/src/participant/participantMessageMoment.ts +7 -0
- package/src/participant/participantTranscriptionText.test.ts +48 -0
- package/src/participant/participantTranscriptionText.ts +28 -0
- package/src/participant/useOpenUpwards.ts +22 -0
- package/src/participant/useParticipantFailedMenu.ts +3 -0
- package/src/styles.css +41 -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,12 +1,27 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { ParticipantConversationSummary, ParticipantConversationPage, ParticipantSubjectRef, ParticipantMessage
|
|
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
|
|
|
7
7
|
type ParticipantAuthorAvatarRenderer = (author: {
|
|
8
8
|
name: string | null;
|
|
9
9
|
}) => ReactNode;
|
|
10
|
+
/** What the host receives to draw per-message actions (copy, forward...); only messages the server already holds. */
|
|
11
|
+
type ParticipantMessageActionsTarget = {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly text: string | null;
|
|
14
|
+
readonly authorName: string | null;
|
|
15
|
+
readonly isMine: boolean;
|
|
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;
|
|
23
|
+
};
|
|
24
|
+
type ParticipantMessageActionsRenderer = (message: ParticipantMessageActionsTarget) => ReactNode;
|
|
10
25
|
|
|
11
26
|
type ParticipantSubjectGroup = {
|
|
12
27
|
readonly subjectType: string;
|
|
@@ -190,6 +205,29 @@ type ParticipantConversationsLabels = {
|
|
|
190
205
|
readonly edit: string;
|
|
191
206
|
/** Name of the menu button on a failed message. Optional; translated hosts should fill it, otherwise the English default shows. */
|
|
192
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;
|
|
193
231
|
/** Toggle that opens the message as received (e.g. the whole email). Optional; the English default shows otherwise. */
|
|
194
232
|
readonly showOriginalText?: string;
|
|
195
233
|
readonly notFound: string;
|
|
@@ -219,6 +257,45 @@ type ParticipantConversationsLabels = {
|
|
|
219
257
|
} & Readonly<Partial<ParticipantAudioLabels>>;
|
|
220
258
|
declare const DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS: ParticipantConversationsLabels;
|
|
221
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
|
+
|
|
222
299
|
type ParticipantConversationsProps = {
|
|
223
300
|
api: ParticipantConversationsApi;
|
|
224
301
|
subjectGroups: readonly ParticipantSubjectGroup[];
|
|
@@ -242,6 +319,10 @@ type ParticipantConversationsProps = {
|
|
|
242
319
|
avatars?: 'initials';
|
|
243
320
|
/** Host slot for the author's photo; wins over 'initials' and enables avatars on its own. */
|
|
244
321
|
renderAuthorAvatar?: ParticipantAuthorAvatarRenderer;
|
|
322
|
+
/** Host slot at the end of each message's meta line (copy, forward...). The package knows no action. */
|
|
323
|
+
renderMessageActions?: ParticipantMessageActionsRenderer;
|
|
324
|
+
/** Opt-in message menu as in common chat apps: copy, select messages, plus the host's entries. */
|
|
325
|
+
messageMenu?: ParticipantMessageMenuOptions;
|
|
245
326
|
/** Speech-bubble tail on the bottom corner; false removes it. Default true. */
|
|
246
327
|
tail?: boolean;
|
|
247
328
|
/** Absent draws no microphone. Present lets the user record and send an audio as a message of its own. */
|
|
@@ -289,6 +370,10 @@ type ConversationThreadProps = {
|
|
|
289
370
|
readonly tail?: boolean;
|
|
290
371
|
readonly avatars?: 'initials';
|
|
291
372
|
readonly renderAuthorAvatar?: ParticipantAuthorAvatarRenderer;
|
|
373
|
+
/** Host slot at the end of each message's meta line (copy, forward...). The package knows no action. */
|
|
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;
|
|
292
377
|
/** Absent draws no microphone. Present lets the user record and send an audio as a message of its own. */
|
|
293
378
|
readonly audioRecording?: ParticipantAudioRecordingOptions;
|
|
294
379
|
/** Return a node to replace the composer (notice + host actions); undefined keeps the composer. `closed` still wins. Evaluated every render. */
|
|
@@ -299,4 +384,4 @@ declare function ConversationThread(props: ConversationThreadProps): react.JSX.E
|
|
|
299
384
|
|
|
300
385
|
declare function groupParticipantConversations(params: GroupParticipantConversationsParams): ParticipantInboxView;
|
|
301
386
|
|
|
302
|
-
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 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 };
|