@adatechnology/conversations-ui 0.1.0-rc.20 → 0.1.0-rc.22
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-OIDAIVCH.js → chunk-TV4OQRGH.js} +636 -434
- package/dist/index.d.ts +142 -9
- package/dist/index.js +374 -66
- package/dist/preview/index.d.ts +122 -3
- package/dist/preview/index.js +137 -21
- package/dist/styles.css +112 -0
- package/dist/{types-O7kMP1Yn.d.ts → types-C6A_9edv.d.ts} +42 -2
- package/package.json +2 -2
- package/src/AudioTranscription.test.tsx +115 -0
- package/src/AudioTranscription.tsx +249 -0
- package/src/ConversationContextPanel.tsx +205 -52
- package/src/ConversationLocalesProvider.tsx +28 -0
- package/src/MediaRenderer.tsx +37 -6
- package/src/MessageBubble.tsx +26 -3
- package/src/MessageComposer.tsx +21 -2
- package/src/Wallpaper.tsx +27 -13
- package/src/conversationTranscript.test.ts +57 -0
- package/src/conversationTranscript.ts +29 -4
- package/src/hooks/useScrollToLatestMessage.ts +127 -0
- package/src/index.ts +11 -0
- package/src/preview/ConversationPreview.tsx +13 -4
- package/src/preview/ConversationSimulatorPanel.test.tsx +55 -0
- package/src/preview/ConversationSimulatorPanel.tsx +89 -0
- package/src/preview/createPreviewBridgeClient.ts +37 -1
- package/src/preview/createPreviewMediaUploader.ts +82 -0
- package/src/preview/createPreviewWebhookClient.test.ts +89 -0
- package/src/preview/createPreviewWebhookClient.ts +91 -0
- package/src/preview/index.ts +9 -0
- package/src/preview/previewMediaUploader.test.ts +61 -0
- package/src/providers/types.ts +12 -1
- package/src/settings/TranscriptionSettingsForm.test.tsx +81 -0
- package/src/settings/TranscriptionSettingsForm.tsx +189 -0
- package/src/styles.css +122 -0
- package/src/types.ts +26 -0
package/dist/preview/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { G as MessagePayload, o as ConversationSummary, m as ConversationEventSource, q as ConversationsApi, L as ListConversationsParams, n as ConversationPage, S as SSEProvider, k as ConversationDocument,
|
|
2
|
-
export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS } from '../types-
|
|
1
|
+
import { G as MessagePayload, o as ConversationSummary, m as ConversationEventSource, q as ConversationsApi, L as ListConversationsParams, n as ConversationPage, S as SSEProvider, k as ConversationDocument, N as ResolveMediaUrl } from '../types-C6A_9edv.js';
|
|
2
|
+
export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS } from '../types-C6A_9edv.js';
|
|
3
3
|
import * as react from 'react';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
4
5
|
import { InteractiveReplyOption, InboundMediaType } from '@adatechnology/meta-whatsapp-contracts/testing';
|
|
6
|
+
export { PREVIEW_MEDIA_ID_PREFIX } from '@adatechnology/meta-whatsapp-contracts';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Estado em memória que alimenta o preview de atendimento humano. Mock de API e mock de SSE
|
|
@@ -112,6 +114,52 @@ declare const PREVIEW_CONVERSATIONS: readonly ConversationSummary[];
|
|
|
112
114
|
declare const PREVIEW_MESSAGES: Readonly<Record<string, readonly MessagePayload[]>>;
|
|
113
115
|
declare const PREVIEW_DOCUMENTS: Readonly<Record<string, readonly ConversationDocument[]>>;
|
|
114
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Entrega ao simulador o `uploadMedia` que ele precisa para desenhar o microfone.
|
|
119
|
+
*
|
|
120
|
+
* O `ConversationPreview` esconde o gravador sem esta função, e com razão: microfone que grava sem
|
|
121
|
+
* ter onde guardar o arquivo faz o operador falar para o vazio. O que faltava era montar isto —
|
|
122
|
+
* lê o `File`, manda para a rota do host, devolve o `mediaId` prefixado que o webhook referencia.
|
|
123
|
+
*
|
|
124
|
+
* Fica no pacote porque a parte que erra é sempre a mesma em todo produto: converter o binário sem
|
|
125
|
+
* estourar a pilha e marcar o id com o prefixo que o backend reconhece. O que muda por produto é só
|
|
126
|
+
* a rota e o cliente HTTP — e é exatamente isso que entra por parâmetro.
|
|
127
|
+
*/
|
|
128
|
+
/**
|
|
129
|
+
* Do `contracts`, que este pacote já consome — não uma cópia.
|
|
130
|
+
*
|
|
131
|
+
* A convenção tem duas pontas (o front gera o id, o backend resolve) e a versão anterior disso vivia
|
|
132
|
+
* duplicada em dois pacotes de um produto, cada cópia com um comentário pedindo para não divergir.
|
|
133
|
+
* Contrato compartilhado é o que o `contracts` existe para guardar.
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
type PreviewUploadedMedia$1 = {
|
|
137
|
+
readonly mediaId: string;
|
|
138
|
+
readonly mimeType?: string;
|
|
139
|
+
readonly filename?: string;
|
|
140
|
+
};
|
|
141
|
+
type PreviewMediaUploadRequest = {
|
|
142
|
+
readonly base64: string;
|
|
143
|
+
readonly mimeType: string;
|
|
144
|
+
readonly filename: string;
|
|
145
|
+
};
|
|
146
|
+
type CreatePreviewMediaUploaderParams = {
|
|
147
|
+
/**
|
|
148
|
+
* Envia o arquivo à rota do host e devolve o `uploadId` (sem prefixo) que o backend gerou.
|
|
149
|
+
*
|
|
150
|
+
* Recebe a função inteira, e não uma URL, porque autenticação varia: uma instalação assina com
|
|
151
|
+
* HMAC, outra manda token de admin, outra usa cookie de sessão. Pedir a URL obrigaria o pacote a
|
|
152
|
+
* escolher por elas.
|
|
153
|
+
*/
|
|
154
|
+
readonly upload: (request: PreviewMediaUploadRequest) => Promise<{
|
|
155
|
+
uploadId: string;
|
|
156
|
+
}>;
|
|
157
|
+
/** Nome usado quando o gravador entrega o áudio sem nome próprio. */
|
|
158
|
+
readonly fallbackFilename?: string;
|
|
159
|
+
readonly fallbackMimeType?: string;
|
|
160
|
+
};
|
|
161
|
+
declare function createPreviewMediaUploader(params: CreatePreviewMediaUploaderParams): (file: File) => Promise<PreviewUploadedMedia$1>;
|
|
162
|
+
|
|
115
163
|
/**
|
|
116
164
|
* Cliente que entrega mensagens do preview no webhook real, assinadas com HMAC — a mesma validação
|
|
117
165
|
* de staging e produção, sem rota alternativa e sem bypass. Do ponto de vista da API, este cliente
|
|
@@ -136,6 +184,19 @@ type PreviewWebhookClient = {
|
|
|
136
184
|
sendListReply(reply: InteractiveReplyOption): Promise<void>;
|
|
137
185
|
sendAudio(mediaId: string): Promise<void>;
|
|
138
186
|
sendMedia(params: SendPreviewMediaParams): Promise<void>;
|
|
187
|
+
/**
|
|
188
|
+
* Guarda um arquivo gravado e devolve o `mediaId` já prefixado, pronto para `sendMedia`.
|
|
189
|
+
*
|
|
190
|
+
* Existe no cliente, e não como prop de quem monta a tela, porque isto é exatamente o que ele já
|
|
191
|
+
* sabe fazer: falar com ESTE host usando ESTE segredo. Enquanto era responsabilidade do produto,
|
|
192
|
+
* o resultado prático foi um produto com microfone no simulador e outro sem — não por decisão,
|
|
193
|
+
* por esquecimento. Cliente montado, microfone na tela.
|
|
194
|
+
*
|
|
195
|
+
* Opcional porque o cliente-ponte só consegue oferecer isto quando sabe a rota de mídia (ou quando
|
|
196
|
+
* o host injeta a função): sem destino, gravar áudio seria falar para o vazio, e aí a tela
|
|
197
|
+
* corretamente não desenha o gravador.
|
|
198
|
+
*/
|
|
199
|
+
uploadMedia?(file: File): Promise<PreviewUploadedMedia$1>;
|
|
139
200
|
};
|
|
140
201
|
type SendPreviewMediaParams = {
|
|
141
202
|
readonly mediaType: InboundMediaType;
|
|
@@ -154,8 +215,20 @@ type CreatePreviewWebhookClientParams = {
|
|
|
154
215
|
readonly appSecret: string;
|
|
155
216
|
readonly from: string;
|
|
156
217
|
readonly phoneNumberId?: string;
|
|
218
|
+
/**
|
|
219
|
+
* Rota que guarda o áudio gravado. Por padrão, `/v1/preview/media` na mesma origem do webhook.
|
|
220
|
+
*
|
|
221
|
+
* O padrão cobre o caso normal — as duas rotas são do mesmo servidor — e a prop existe para quem
|
|
222
|
+
* publica a API em outro host ou versiona o caminho.
|
|
223
|
+
*/
|
|
224
|
+
readonly mediaUploadUrl?: string;
|
|
157
225
|
readonly fetchImplementation?: typeof fetch;
|
|
158
226
|
};
|
|
227
|
+
/** Falha da rota de upload, separada da do webhook: os dois lados quebram por motivos diferentes. */
|
|
228
|
+
declare class PreviewMediaUploadRejectedError extends Error {
|
|
229
|
+
readonly status: number;
|
|
230
|
+
constructor(status: number);
|
|
231
|
+
}
|
|
159
232
|
declare class PreviewInProductionError extends Error {
|
|
160
233
|
constructor();
|
|
161
234
|
}
|
|
@@ -180,6 +253,18 @@ declare function signPreviewPayload(params: {
|
|
|
180
253
|
rawBody: string;
|
|
181
254
|
appSecret: string;
|
|
182
255
|
}): Promise<string>;
|
|
256
|
+
declare const DEFAULT_MEDIA_UPLOAD_PATH = "/v1/preview/media";
|
|
257
|
+
/**
|
|
258
|
+
* O POST de mídia, sem a parte de assinatura — para os dois clientes usarem o mesmo caminho.
|
|
259
|
+
*
|
|
260
|
+
* O cliente-ponte autentica por sessão e o de webhook por HMAC; o que não muda é a rota, o formato
|
|
261
|
+
* do corpo e a leitura do `uploadId`. Duas cópias disso é como o prefixo de mídia divergiu antes.
|
|
262
|
+
*/
|
|
263
|
+
declare function createPreviewMediaPoster(params: {
|
|
264
|
+
readonly url: string;
|
|
265
|
+
readonly headers?: (mimeType: string) => Promise<Readonly<Record<string, string>>>;
|
|
266
|
+
readonly fetchImplementation?: typeof fetch;
|
|
267
|
+
}): (file: File) => Promise<PreviewUploadedMedia$1>;
|
|
183
268
|
declare function createPreviewWebhookClient(params: CreatePreviewWebhookClientParams): PreviewWebhookClient;
|
|
184
269
|
|
|
185
270
|
type ConversationPreviewProps = {
|
|
@@ -200,6 +285,7 @@ type ConversationPreviewProps = {
|
|
|
200
285
|
* inventa um endpoint de upload. Ausente, o compositor não oferece anexo nem gravação: melhor um
|
|
201
286
|
* botão que não existe do que um que falha ao ser tocado.
|
|
202
287
|
*/
|
|
288
|
+
/** Destino alternativo do áudio gravado. Sem isto, usa o do próprio `client`. */
|
|
203
289
|
uploadMedia?: (file: File) => Promise<PreviewUploadedMedia>;
|
|
204
290
|
};
|
|
205
291
|
type PreviewUploadedMedia = {
|
|
@@ -211,6 +297,27 @@ type PreviewUploadedMedia = {
|
|
|
211
297
|
declare function mediaTypeOf(mimeType: string): SendPreviewMediaParams['mediaType'];
|
|
212
298
|
declare function ConversationPreview({ client, sse, conversationId, loadMessages, placeholder, pollIntervalMs, uploadMedia, }: ConversationPreviewProps): react.JSX.Element;
|
|
213
299
|
|
|
300
|
+
type ConversationSimulatorPanelLabels = {
|
|
301
|
+
readonly title: string;
|
|
302
|
+
/** Complementa o telefone no subtítulo, explicando para onde a mensagem realmente vai. */
|
|
303
|
+
readonly destinationHint: string;
|
|
304
|
+
readonly close: string;
|
|
305
|
+
readonly placeholder: string;
|
|
306
|
+
};
|
|
307
|
+
declare const DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS: ConversationSimulatorPanelLabels;
|
|
308
|
+
type ConversationSimulatorPanelProps = Omit<ConversationPreviewProps, 'placeholder'> & {
|
|
309
|
+
readonly onClose: () => void;
|
|
310
|
+
/**
|
|
311
|
+
* Telefone já formatado para leitura. É o host que formata: máscara de telefone é convenção
|
|
312
|
+
* regional, e o pacote não tem como saber a do produto.
|
|
313
|
+
*/
|
|
314
|
+
readonly displayNumber?: string;
|
|
315
|
+
readonly labels?: Partial<ConversationSimulatorPanelLabels>;
|
|
316
|
+
/** Ações extras no cabeçalho — roteiro automático, limpar conversa, trocar de contato. */
|
|
317
|
+
readonly headerActions?: ReactNode;
|
|
318
|
+
};
|
|
319
|
+
declare function ConversationSimulatorPanel({ onClose, displayNumber, labels, headerActions, ...previewProps }: ConversationSimulatorPanelProps): react.JSX.Element;
|
|
320
|
+
|
|
214
321
|
/**
|
|
215
322
|
* Cliente do preview que NÃO carrega segredo: em vez de montar e assinar o payload da Meta no
|
|
216
323
|
* navegador, manda um comando semântico (`{ kind: 'text', text }`) para uma rota do próprio host,
|
|
@@ -269,6 +376,18 @@ type CreatePreviewBridgeClientParams = {
|
|
|
269
376
|
readonly endpointUrl?: string;
|
|
270
377
|
readonly headers?: Readonly<Record<string, string>>;
|
|
271
378
|
readonly fetchImplementation?: typeof fetch;
|
|
379
|
+
/**
|
|
380
|
+
* Rota que guarda o áudio gravado. Por padrão, `/v1/preview/media` na origem do `endpointUrl`.
|
|
381
|
+
*
|
|
382
|
+
* Aqui não há assinatura a calcular: a ponte existe justamente para não ter segredo no navegador,
|
|
383
|
+
* e a rota é protegida pela sessão do painel — os mesmos `headers` do comando valem para o upload.
|
|
384
|
+
*/
|
|
385
|
+
readonly mediaUploadUrl?: string;
|
|
386
|
+
/**
|
|
387
|
+
* Substitui o upload embutido. Necessário para host que só passa `sendCommand`: sem `endpointUrl`
|
|
388
|
+
* não há origem a derivar, e sem destino o gravador não é desenhado.
|
|
389
|
+
*/
|
|
390
|
+
readonly uploadMedia?: (file: File) => Promise<PreviewUploadedMedia$1>;
|
|
272
391
|
};
|
|
273
392
|
declare function createPreviewBridgeClient(params: CreatePreviewBridgeClientParams): PreviewWebhookClient;
|
|
274
393
|
|
|
@@ -370,4 +489,4 @@ type MediaTypesPreviewProps = {
|
|
|
370
489
|
};
|
|
371
490
|
declare function MediaTypesPreview({ conversationId, className, }: MediaTypesPreviewProps): react.JSX.Element;
|
|
372
491
|
|
|
373
|
-
export { type AppendMessageParams, ConversationPreview, type ConversationPreviewProps, type CreateMockConversationsApiParams, type CreateMockSSEProviderParams, type CreatePreviewBridgeClientParams, type CreatePreviewStoreParams, type CreatePreviewWebhookClientParams, DEFAULT_PREVIEW_SCRIPT, GLOBAL_CHANNEL, type ListConversationsFilters, MEDIA_TYPES_CONVERSATION_ID, MediaTypesPreview, type MediaTypesPreviewProps, type MockEventSource, PREVIEW_CONVERSATIONS, PREVIEW_DOCUMENTS, PREVIEW_FILE_SAMPLES, PREVIEW_MESSAGES, PreviewBridgeRejectedError, type PreviewEmission, PreviewInProductionError, type PreviewInboundCommand, type PreviewScriptStep, type PreviewStore, type PreviewStoreListener, type PreviewUploadedMedia, type PreviewWebhookClient, PreviewWebhookRejectedError, type SendPreviewInboundCommand, type SendPreviewMediaParams, type SetModeParams, type StartPreviewScriptParams, assertPreviewEnvironment, conversationChannel, createMockConversationsApi, createMockEventSource, createMockSSEProvider, createPreviewBridgeClient, createPreviewMediaResolver, createPreviewStore, createPreviewWebhookClient, mediaTypeOf, previewFileBase64, previewFileUrl, resolvePreviewFileSample, signPreviewPayload, startPreviewScript };
|
|
492
|
+
export { type AppendMessageParams, ConversationPreview, type ConversationPreviewProps, ConversationSimulatorPanel, type ConversationSimulatorPanelLabels, type ConversationSimulatorPanelProps, type CreateMockConversationsApiParams, type CreateMockSSEProviderParams, type CreatePreviewBridgeClientParams, type CreatePreviewMediaUploaderParams, type CreatePreviewStoreParams, type CreatePreviewWebhookClientParams, DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS, DEFAULT_MEDIA_UPLOAD_PATH, DEFAULT_PREVIEW_SCRIPT, GLOBAL_CHANNEL, type ListConversationsFilters, MEDIA_TYPES_CONVERSATION_ID, MediaTypesPreview, type MediaTypesPreviewProps, type MockEventSource, PREVIEW_CONVERSATIONS, PREVIEW_DOCUMENTS, PREVIEW_FILE_SAMPLES, PREVIEW_MESSAGES, PreviewBridgeRejectedError, type PreviewEmission, PreviewInProductionError, type PreviewInboundCommand, PreviewMediaUploadRejectedError, type PreviewMediaUploadRequest, type PreviewScriptStep, type PreviewStore, type PreviewStoreListener, type PreviewUploadedMedia, type PreviewWebhookClient, PreviewWebhookRejectedError, type SendPreviewInboundCommand, type SendPreviewMediaParams, type SetModeParams, type StartPreviewScriptParams, assertPreviewEnvironment, conversationChannel, createMockConversationsApi, createMockEventSource, createMockSSEProvider, createPreviewBridgeClient, createPreviewMediaPoster, createPreviewMediaResolver, createPreviewMediaUploader, createPreviewStore, createPreviewWebhookClient, mediaTypeOf, previewFileBase64, previewFileUrl, resolvePreviewFileSample, signPreviewPayload, startPreviewScript };
|
package/dist/preview/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
DocumentsLibrary,
|
|
9
9
|
MessageBubble,
|
|
10
10
|
MessageComposer
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-TV4OQRGH.js";
|
|
12
12
|
import "../chunk-2AYDBWNE.js";
|
|
13
13
|
|
|
14
14
|
// src/preview/previewStore.ts
|
|
@@ -980,11 +980,12 @@ function ConversationPreview({
|
|
|
980
980
|
setFailure(error instanceof Error ? error.message : "Falha ao entregar a resposta no webhook.");
|
|
981
981
|
}
|
|
982
982
|
}
|
|
983
|
+
const uploadFile = uploadMedia ?? client.uploadMedia;
|
|
983
984
|
async function handleAttach(file) {
|
|
984
|
-
if (!
|
|
985
|
+
if (!uploadFile) return;
|
|
985
986
|
setFailure(void 0);
|
|
986
987
|
try {
|
|
987
|
-
const uploaded = await
|
|
988
|
+
const uploaded = await uploadFile(file);
|
|
988
989
|
await client.sendMedia({
|
|
989
990
|
mediaType: mediaTypeOf(uploaded.mimeType ?? file.type),
|
|
990
991
|
mediaId: uploaded.mediaId,
|
|
@@ -1018,9 +1019,9 @@ function ConversationPreview({
|
|
|
1018
1019
|
MessageComposer,
|
|
1019
1020
|
{
|
|
1020
1021
|
onSend: (text) => void handleSend(text),
|
|
1021
|
-
onAttach:
|
|
1022
|
+
onAttach: uploadFile ? (file) => void handleAttach(file) : void 0,
|
|
1022
1023
|
placeholder: isRecording ? "Gravando\u2026 toque no quadrado para ouvir" : placeholder ?? "Escreva como o cliente\u2026",
|
|
1023
|
-
idleAction:
|
|
1024
|
+
idleAction: uploadFile ? /* @__PURE__ */ jsx(
|
|
1024
1025
|
AudioRecorderButton,
|
|
1025
1026
|
{
|
|
1026
1027
|
onRecorded: (file) => void handleAttach(file),
|
|
@@ -1033,6 +1034,38 @@ function ConversationPreview({
|
|
|
1033
1034
|
] });
|
|
1034
1035
|
}
|
|
1035
1036
|
|
|
1037
|
+
// src/preview/ConversationSimulatorPanel.tsx
|
|
1038
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1039
|
+
var DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS = {
|
|
1040
|
+
title: "Simulador do cliente",
|
|
1041
|
+
destinationHint: "entrega no webhook real",
|
|
1042
|
+
close: "Fechar simulador",
|
|
1043
|
+
placeholder: "Escreva como o cliente\u2026"
|
|
1044
|
+
};
|
|
1045
|
+
function ConversationSimulatorPanel({
|
|
1046
|
+
onClose,
|
|
1047
|
+
displayNumber,
|
|
1048
|
+
labels,
|
|
1049
|
+
headerActions,
|
|
1050
|
+
...previewProps
|
|
1051
|
+
}) {
|
|
1052
|
+
const text = { ...DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS, ...labels };
|
|
1053
|
+
const subtitle = [displayNumber ?? previewProps.conversationId, text.destinationHint].join(" \xB7 ");
|
|
1054
|
+
return /* @__PURE__ */ jsxs2("aside", { className: "cv-simulator-panel", "aria-label": text.title, children: [
|
|
1055
|
+
/* @__PURE__ */ jsxs2("header", { className: "cv-simulator-panel__header", children: [
|
|
1056
|
+
/* @__PURE__ */ jsxs2("div", { className: "cv-simulator-panel__heading", children: [
|
|
1057
|
+
/* @__PURE__ */ jsx2("h2", { className: "cv-simulator-panel__title", children: text.title }),
|
|
1058
|
+
/* @__PURE__ */ jsx2("p", { className: "cv-simulator-panel__subtitle", children: subtitle })
|
|
1059
|
+
] }),
|
|
1060
|
+
/* @__PURE__ */ jsxs2("div", { className: "cv-simulator-panel__actions", children: [
|
|
1061
|
+
headerActions,
|
|
1062
|
+
/* @__PURE__ */ jsx2("button", { type: "button", onClick: onClose, title: text.close, "aria-label": text.close, className: "cv-simulator-panel__close", children: /* @__PURE__ */ jsx2("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "M18 6 6 18M6 6l12 12" }) }) })
|
|
1063
|
+
] })
|
|
1064
|
+
] }),
|
|
1065
|
+
/* @__PURE__ */ jsx2("div", { className: "cv-simulator-panel__body", children: /* @__PURE__ */ jsx2(ConversationPreview, { ...previewProps, placeholder: text.placeholder }) })
|
|
1066
|
+
] });
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1036
1069
|
// src/preview/createPreviewWebhookClient.ts
|
|
1037
1070
|
import {
|
|
1038
1071
|
buildInboundAudioPayload,
|
|
@@ -1041,6 +1074,38 @@ import {
|
|
|
1041
1074
|
buildInboundTextPayload,
|
|
1042
1075
|
serializeWebhookPayload
|
|
1043
1076
|
} from "@adatechnology/meta-whatsapp-contracts/testing";
|
|
1077
|
+
|
|
1078
|
+
// src/preview/createPreviewMediaUploader.ts
|
|
1079
|
+
import { PREVIEW_MEDIA_ID_PREFIX } from "@adatechnology/meta-whatsapp-contracts";
|
|
1080
|
+
import { toPreviewMediaId } from "@adatechnology/meta-whatsapp-contracts";
|
|
1081
|
+
var CHUNK_SIZE = 8192;
|
|
1082
|
+
async function fileToBase64(file) {
|
|
1083
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
1084
|
+
let binary = "";
|
|
1085
|
+
for (let offset = 0; offset < bytes.length; offset += CHUNK_SIZE) {
|
|
1086
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + CHUNK_SIZE));
|
|
1087
|
+
}
|
|
1088
|
+
return btoa(binary);
|
|
1089
|
+
}
|
|
1090
|
+
function createPreviewMediaUploader(params) {
|
|
1091
|
+
const fallbackMimeType = params.fallbackMimeType ?? "audio/ogg";
|
|
1092
|
+
const fallbackFilename = params.fallbackFilename ?? "audio.ogg";
|
|
1093
|
+
return async function uploadPreviewMedia(file) {
|
|
1094
|
+
const mimeType = file.type || fallbackMimeType;
|
|
1095
|
+
const filename = file.name || fallbackFilename;
|
|
1096
|
+
const { uploadId } = await params.upload({ base64: await fileToBase64(file), mimeType, filename });
|
|
1097
|
+
return { mediaId: toPreviewMediaId(uploadId), mimeType, filename };
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// src/preview/createPreviewWebhookClient.ts
|
|
1102
|
+
var PreviewMediaUploadRejectedError = class extends Error {
|
|
1103
|
+
constructor(status) {
|
|
1104
|
+
super(`A rota de m\xEDdia do simulador recusou o upload (HTTP ${status}).`);
|
|
1105
|
+
this.status = status;
|
|
1106
|
+
this.name = "PreviewMediaUploadRejectedError";
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1044
1109
|
var PreviewInProductionError = class extends Error {
|
|
1045
1110
|
constructor() {
|
|
1046
1111
|
super("O preview de conversa carrega um app secret e n\xE3o pode ser montado em produ\xE7\xE3o.");
|
|
@@ -1070,6 +1135,30 @@ async function signPreviewPayload(params) {
|
|
|
1070
1135
|
return `sha256=${[...new Uint8Array(signature)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
1071
1136
|
}
|
|
1072
1137
|
var signWithWebCrypto = signPreviewPayload;
|
|
1138
|
+
var DEFAULT_MEDIA_UPLOAD_PATH = "/v1/preview/media";
|
|
1139
|
+
function defaultMediaUploadUrl(webhookUrl) {
|
|
1140
|
+
try {
|
|
1141
|
+
return new URL(DEFAULT_MEDIA_UPLOAD_PATH, webhookUrl).toString();
|
|
1142
|
+
} catch {
|
|
1143
|
+
return DEFAULT_MEDIA_UPLOAD_PATH;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
function createPreviewMediaPoster(params) {
|
|
1147
|
+
return createPreviewMediaUploader({
|
|
1148
|
+
upload: async (request) => {
|
|
1149
|
+
const performRequest = params.fetchImplementation ?? fetch;
|
|
1150
|
+
const extraHeaders = await params.headers?.(request.mimeType) ?? {};
|
|
1151
|
+
const response = await performRequest(params.url, {
|
|
1152
|
+
method: "POST",
|
|
1153
|
+
headers: { "content-type": "application/json", ...extraHeaders },
|
|
1154
|
+
body: JSON.stringify(request)
|
|
1155
|
+
});
|
|
1156
|
+
if (!response.ok) throw new PreviewMediaUploadRejectedError(response.status);
|
|
1157
|
+
const body = await response.json();
|
|
1158
|
+
return { uploadId: body.data.uploadId };
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1073
1162
|
function createPreviewWebhookClient(params) {
|
|
1074
1163
|
const sendPayload = async (payload) => {
|
|
1075
1164
|
const rawBody = serializeWebhookPayload(payload);
|
|
@@ -1082,13 +1171,21 @@ function createPreviewWebhookClient(params) {
|
|
|
1082
1171
|
});
|
|
1083
1172
|
if (!response.ok) throw new PreviewWebhookRejectedError(response.status);
|
|
1084
1173
|
};
|
|
1174
|
+
const uploadMedia = createPreviewMediaPoster({
|
|
1175
|
+
url: params.mediaUploadUrl ?? defaultMediaUploadUrl(params.webhookUrl),
|
|
1176
|
+
headers: async (mimeType) => ({
|
|
1177
|
+
"x-preview-signature": await signWithWebCrypto({ rawBody: mimeType, appSecret: params.appSecret })
|
|
1178
|
+
}),
|
|
1179
|
+
...params.fetchImplementation ? { fetchImplementation: params.fetchImplementation } : {}
|
|
1180
|
+
});
|
|
1085
1181
|
const envelope = { from: params.from, phoneNumberId: params.phoneNumberId };
|
|
1086
1182
|
return {
|
|
1087
1183
|
sendText: (text) => sendPayload(buildInboundTextPayload({ ...envelope, text })),
|
|
1088
1184
|
sendButtonReply: (reply) => sendPayload(buildInboundInteractivePayload({ ...envelope, buttonReply: reply })),
|
|
1089
1185
|
sendListReply: (reply) => sendPayload(buildInboundInteractivePayload({ ...envelope, listReply: reply })),
|
|
1090
1186
|
sendAudio: (mediaId) => sendPayload(buildInboundAudioPayload({ ...envelope, mediaId })),
|
|
1091
|
-
sendMedia: (media) => sendPayload(buildInboundMediaPayload({ ...envelope, ...media }))
|
|
1187
|
+
sendMedia: (media) => sendPayload(buildInboundMediaPayload({ ...envelope, ...media })),
|
|
1188
|
+
uploadMedia
|
|
1092
1189
|
};
|
|
1093
1190
|
}
|
|
1094
1191
|
|
|
@@ -1117,15 +1214,27 @@ function buildFetchSender(params) {
|
|
|
1117
1214
|
if (!response.ok) throw new PreviewBridgeRejectedError(response.status);
|
|
1118
1215
|
};
|
|
1119
1216
|
}
|
|
1217
|
+
function resolveBridgeUpload(params) {
|
|
1218
|
+
if (params.uploadMedia) return params.uploadMedia;
|
|
1219
|
+
const url = params.mediaUploadUrl ?? (params.endpointUrl ? defaultMediaUploadUrl(params.endpointUrl) : void 0);
|
|
1220
|
+
if (!url) return void 0;
|
|
1221
|
+
return createPreviewMediaPoster({
|
|
1222
|
+
url,
|
|
1223
|
+
...params.headers ? { headers: async () => params.headers ?? {} } : {},
|
|
1224
|
+
...params.fetchImplementation ? { fetchImplementation: params.fetchImplementation } : {}
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1120
1227
|
function createPreviewBridgeClient(params) {
|
|
1121
1228
|
const send = params.sendCommand ?? buildFetchSender(params);
|
|
1122
1229
|
const from = params.from;
|
|
1230
|
+
const uploadMedia = resolveBridgeUpload(params);
|
|
1123
1231
|
return {
|
|
1124
1232
|
sendText: (text) => send({ kind: "text", from, text }),
|
|
1125
1233
|
sendButtonReply: (reply) => send({ kind: "buttonReply", from, reply }),
|
|
1126
1234
|
sendListReply: (reply) => send({ kind: "listReply", from, reply }),
|
|
1127
1235
|
sendAudio: (mediaId) => send({ kind: "audio", from, mediaId }),
|
|
1128
|
-
sendMedia: (media) => send({ kind: "media", from, ...media })
|
|
1236
|
+
sendMedia: (media) => send({ kind: "media", from, ...media }),
|
|
1237
|
+
...uploadMedia ? { uploadMedia } : {}
|
|
1129
1238
|
};
|
|
1130
1239
|
}
|
|
1131
1240
|
|
|
@@ -1167,7 +1276,7 @@ function startPreviewScript(params) {
|
|
|
1167
1276
|
|
|
1168
1277
|
// src/preview/MediaTypesPreview.tsx
|
|
1169
1278
|
import { useMemo as useMemo2 } from "react";
|
|
1170
|
-
import { jsx as
|
|
1279
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1171
1280
|
var MEDIA_TYPES_CONVERSATION_ID = "5511944443333";
|
|
1172
1281
|
function MediaTypesPreview({
|
|
1173
1282
|
conversationId = MEDIA_TYPES_CONVERSATION_ID,
|
|
@@ -1182,26 +1291,26 @@ function MediaTypesPreview({
|
|
|
1182
1291
|
const messages = PREVIEW_MESSAGES[conversationId] ?? [];
|
|
1183
1292
|
const documents = PREVIEW_DOCUMENTS[conversationId] ?? [];
|
|
1184
1293
|
const mimeTypes = [...new Set(documents.map((document) => document.mimeType))];
|
|
1185
|
-
return /* @__PURE__ */
|
|
1186
|
-
/* @__PURE__ */
|
|
1187
|
-
/* @__PURE__ */
|
|
1188
|
-
/* @__PURE__ */
|
|
1294
|
+
return /* @__PURE__ */ jsx3(ConversationsProvider, { api, sse, children: /* @__PURE__ */ jsxs3("div", { className, children: [
|
|
1295
|
+
/* @__PURE__ */ jsxs3("header", { className: "border-b px-4 py-3 dark:border-gray-700", children: [
|
|
1296
|
+
/* @__PURE__ */ jsx3("h1", { className: "text-lg font-semibold", children: "Teste manual de m\xEDdia" }),
|
|
1297
|
+
/* @__PURE__ */ jsxs3("p", { className: "text-sm text-gray-500", children: [
|
|
1189
1298
|
documents.length,
|
|
1190
1299
|
" arquivos, ",
|
|
1191
1300
|
mimeTypes.length,
|
|
1192
1301
|
" tipos. Clique no olho para abrir em aba nova e no bot\xE3o da bolha para carregar a m\xEDdia na thread \u2014 \xE9 o que teste automatizado n\xE3o v\xEA."
|
|
1193
1302
|
] })
|
|
1194
1303
|
] }),
|
|
1195
|
-
/* @__PURE__ */
|
|
1196
|
-
/* @__PURE__ */
|
|
1197
|
-
/* @__PURE__ */
|
|
1198
|
-
/* @__PURE__ */
|
|
1304
|
+
/* @__PURE__ */ jsxs3("div", { className: "grid gap-4 p-4 lg:grid-cols-2", children: [
|
|
1305
|
+
/* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
|
|
1306
|
+
/* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Biblioteca da empresa" }),
|
|
1307
|
+
/* @__PURE__ */ jsx3(DocumentsLibrary, { perPage: documents.length || 20 })
|
|
1199
1308
|
] }),
|
|
1200
|
-
/* @__PURE__ */
|
|
1201
|
-
/* @__PURE__ */
|
|
1202
|
-
/* @__PURE__ */
|
|
1203
|
-
/* @__PURE__ */
|
|
1204
|
-
/* @__PURE__ */
|
|
1309
|
+
/* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
|
|
1310
|
+
/* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Painel da conversa" }),
|
|
1311
|
+
/* @__PURE__ */ jsx3(ConversationDocumentsPanel, { conversationId, open: true, perPage: documents.length || 20 }),
|
|
1312
|
+
/* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Bolhas na thread" }),
|
|
1313
|
+
/* @__PURE__ */ jsx3(ConversationWallpaper, { className: "max-h-[70vh] overflow-y-auto rounded-lg px-3 py-2", children: messages.map((message, index) => /* @__PURE__ */ jsx3(
|
|
1205
1314
|
MessageBubble,
|
|
1206
1315
|
{
|
|
1207
1316
|
message,
|
|
@@ -1217,7 +1326,10 @@ function MediaTypesPreview({
|
|
|
1217
1326
|
export {
|
|
1218
1327
|
AudioRecorderButton,
|
|
1219
1328
|
ConversationPreview,
|
|
1329
|
+
ConversationSimulatorPanel,
|
|
1220
1330
|
DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
|
|
1331
|
+
DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS,
|
|
1332
|
+
DEFAULT_MEDIA_UPLOAD_PATH,
|
|
1221
1333
|
DEFAULT_PREVIEW_SCRIPT,
|
|
1222
1334
|
GLOBAL_CHANNEL,
|
|
1223
1335
|
MEDIA_TYPES_CONVERSATION_ID,
|
|
@@ -1225,9 +1337,11 @@ export {
|
|
|
1225
1337
|
PREVIEW_CONVERSATIONS,
|
|
1226
1338
|
PREVIEW_DOCUMENTS,
|
|
1227
1339
|
PREVIEW_FILE_SAMPLES,
|
|
1340
|
+
PREVIEW_MEDIA_ID_PREFIX,
|
|
1228
1341
|
PREVIEW_MESSAGES,
|
|
1229
1342
|
PreviewBridgeRejectedError,
|
|
1230
1343
|
PreviewInProductionError,
|
|
1344
|
+
PreviewMediaUploadRejectedError,
|
|
1231
1345
|
PreviewWebhookRejectedError,
|
|
1232
1346
|
assertPreviewEnvironment,
|
|
1233
1347
|
conversationChannel,
|
|
@@ -1235,7 +1349,9 @@ export {
|
|
|
1235
1349
|
createMockEventSource,
|
|
1236
1350
|
createMockSSEProvider,
|
|
1237
1351
|
createPreviewBridgeClient,
|
|
1352
|
+
createPreviewMediaPoster,
|
|
1238
1353
|
createPreviewMediaResolver,
|
|
1354
|
+
createPreviewMediaUploader,
|
|
1239
1355
|
createPreviewStore,
|
|
1240
1356
|
createPreviewWebhookClient,
|
|
1241
1357
|
mediaTypeOf,
|
package/dist/styles.css
CHANGED
|
@@ -207,3 +207,115 @@
|
|
|
207
207
|
max-width: calc(100vw - 1.5rem);
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
+
.cv-scrollbar-thin {
|
|
211
|
+
scrollbar-width: thin;
|
|
212
|
+
scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
|
|
213
|
+
}
|
|
214
|
+
.cv-scrollbar-thin::-webkit-scrollbar {
|
|
215
|
+
width: 4px;
|
|
216
|
+
height: 4px;
|
|
217
|
+
}
|
|
218
|
+
.cv-scrollbar-thin::-webkit-scrollbar-track {
|
|
219
|
+
background: transparent;
|
|
220
|
+
}
|
|
221
|
+
.cv-scrollbar-thin::-webkit-scrollbar-thumb {
|
|
222
|
+
background: rgba(0, 0, 0, 0.15);
|
|
223
|
+
border-radius: 2px;
|
|
224
|
+
}
|
|
225
|
+
.cv-scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
|
226
|
+
background: rgba(0, 0, 0, 0.3);
|
|
227
|
+
}
|
|
228
|
+
.dark .cv-scrollbar-thin {
|
|
229
|
+
scrollbar-color: rgba(255, 255, 255, 0.15) transparent;
|
|
230
|
+
}
|
|
231
|
+
.dark .cv-scrollbar-thin::-webkit-scrollbar-thumb {
|
|
232
|
+
background: rgba(255, 255, 255, 0.15);
|
|
233
|
+
}
|
|
234
|
+
.dark .cv-scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
|
235
|
+
background: rgba(255, 255, 255, 0.3);
|
|
236
|
+
}
|
|
237
|
+
:where(button:not(:disabled), [role=button]:not(:disabled), summary) {
|
|
238
|
+
cursor: pointer;
|
|
239
|
+
}
|
|
240
|
+
.cv-simulator-panel {
|
|
241
|
+
display: flex;
|
|
242
|
+
flex-direction: column;
|
|
243
|
+
width: 24rem;
|
|
244
|
+
flex-shrink: 0;
|
|
245
|
+
border-left: 1px solid rgb(229 231 235);
|
|
246
|
+
background: #fff;
|
|
247
|
+
}
|
|
248
|
+
.cv-simulator-panel__header {
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
justify-content: space-between;
|
|
252
|
+
gap: 0.5rem;
|
|
253
|
+
padding: 0.75rem 1rem;
|
|
254
|
+
border-bottom: 1px solid rgb(229 231 235);
|
|
255
|
+
}
|
|
256
|
+
.cv-simulator-panel__heading {
|
|
257
|
+
min-width: 0;
|
|
258
|
+
}
|
|
259
|
+
.cv-simulator-panel__title {
|
|
260
|
+
font-size: 0.875rem;
|
|
261
|
+
font-weight: 600;
|
|
262
|
+
color: rgb(17 24 39);
|
|
263
|
+
overflow: hidden;
|
|
264
|
+
text-overflow: ellipsis;
|
|
265
|
+
white-space: nowrap;
|
|
266
|
+
}
|
|
267
|
+
.cv-simulator-panel__subtitle {
|
|
268
|
+
font-size: 0.75rem;
|
|
269
|
+
color: rgb(156 163 175);
|
|
270
|
+
overflow: hidden;
|
|
271
|
+
text-overflow: ellipsis;
|
|
272
|
+
white-space: nowrap;
|
|
273
|
+
}
|
|
274
|
+
.cv-simulator-panel__actions {
|
|
275
|
+
display: flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
gap: 0.25rem;
|
|
278
|
+
}
|
|
279
|
+
.cv-simulator-panel__close {
|
|
280
|
+
display: inline-flex;
|
|
281
|
+
padding: 0.375rem;
|
|
282
|
+
border: 0;
|
|
283
|
+
border-radius: 0.5rem;
|
|
284
|
+
background: transparent;
|
|
285
|
+
color: rgb(156 163 175);
|
|
286
|
+
transition: color 150ms, background-color 150ms;
|
|
287
|
+
}
|
|
288
|
+
.cv-simulator-panel__close:hover {
|
|
289
|
+
color: rgb(55 65 81);
|
|
290
|
+
background: rgb(243 244 246);
|
|
291
|
+
}
|
|
292
|
+
.cv-simulator-panel__body {
|
|
293
|
+
flex: 1;
|
|
294
|
+
min-height: 0;
|
|
295
|
+
}
|
|
296
|
+
.dark .cv-simulator-panel {
|
|
297
|
+
border-left-color: rgb(31 41 55);
|
|
298
|
+
background: rgb(17 24 39);
|
|
299
|
+
}
|
|
300
|
+
.dark .cv-simulator-panel__header {
|
|
301
|
+
border-bottom-color: rgb(31 41 55);
|
|
302
|
+
}
|
|
303
|
+
.dark .cv-simulator-panel__title {
|
|
304
|
+
color: rgb(243 244 246);
|
|
305
|
+
}
|
|
306
|
+
.dark .cv-simulator-panel__subtitle {
|
|
307
|
+
color: rgb(107 114 128);
|
|
308
|
+
}
|
|
309
|
+
.dark .cv-simulator-panel__close:hover {
|
|
310
|
+
color: rgb(229 231 235);
|
|
311
|
+
background: rgb(31 41 55);
|
|
312
|
+
}
|
|
313
|
+
@media (max-width: 1023px) {
|
|
314
|
+
.cv-simulator-panel {
|
|
315
|
+
position: fixed;
|
|
316
|
+
inset: 0;
|
|
317
|
+
width: 100%;
|
|
318
|
+
z-index: 40;
|
|
319
|
+
border-left: 0;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -90,19 +90,49 @@ interface MessagePayload {
|
|
|
90
90
|
isOffensive: boolean;
|
|
91
91
|
terms: string[];
|
|
92
92
|
} | null;
|
|
93
|
+
/**
|
|
94
|
+
* Transcrição do áudio, vinda do backend — a UI só exibe, nunca transcreve. Rodar STT no browser
|
|
95
|
+
* exigiria baixar modelo por aba e daria resultado diferente por versão de cliente.
|
|
96
|
+
*
|
|
97
|
+
* `null`/ausente = não avaliado, que é diferente de `'done'` com texto vazio (áudio em silêncio,
|
|
98
|
+
* já processado). É essa distinção que decide se o balão oferece "transcrever" ou "sem fala
|
|
99
|
+
* detectada".
|
|
100
|
+
*/
|
|
101
|
+
transcription?: MessageTranscription | null;
|
|
93
102
|
isFirstInGroup?: boolean;
|
|
94
103
|
isLastInGroup?: boolean;
|
|
95
104
|
}
|
|
105
|
+
type TranscriptionStatus = 'pending' | 'done' | 'failed' | 'unsupported';
|
|
106
|
+
/**
|
|
107
|
+
* Quando transcrever, escolhido nas configurações da empresa. Espelha o
|
|
108
|
+
* `TranscriptionMode` de `@adatechnology/meta-whatsapp-contracts`; declarado aqui para o pacote de
|
|
109
|
+
* UI não obrigar quem só desenha telas a instalar os contratos do backend.
|
|
110
|
+
*/
|
|
111
|
+
type TranscriptionMode = 'auto' | 'onDemand';
|
|
112
|
+
interface MessageTranscription {
|
|
113
|
+
status: TranscriptionStatus;
|
|
114
|
+
text?: string | null;
|
|
115
|
+
/** ISO 639-1 ou nome do idioma, conforme o engine. Exibido como dica, não interpretado. */
|
|
116
|
+
language?: string | null;
|
|
117
|
+
engine?: string | null;
|
|
118
|
+
}
|
|
96
119
|
|
|
97
120
|
type ResolveMediaUrl = (message: MessagePayload) => Promise<string | null>;
|
|
98
121
|
interface MediaRendererProps {
|
|
99
122
|
message: MessagePayload;
|
|
100
123
|
onLightbox: (src: string) => void;
|
|
101
124
|
onResolveUrl?: ResolveMediaUrl;
|
|
125
|
+
/**
|
|
126
|
+
* Pede ao backend a transcrição do áudio desta mensagem. Ausente, o bloco de transcrição só exibe
|
|
127
|
+
* o que já veio pronto — sem oferecer um botão que o host não sabe atender.
|
|
128
|
+
*
|
|
129
|
+
* O que devolver é exibido na hora, sem esperar refetch da lista.
|
|
130
|
+
*/
|
|
131
|
+
onTranscribeAudio?: () => Promise<MessageTranscription | void>;
|
|
102
132
|
/** Aplicado no wrapper de cada tipo de mídia — imagem, vídeo, áudio e documento. */
|
|
103
133
|
className?: string;
|
|
104
134
|
}
|
|
105
|
-
declare function MediaRenderer({ message, onLightbox, onResolveUrl, className }: MediaRendererProps): react.JSX.Element | null;
|
|
135
|
+
declare function MediaRenderer({ message, onLightbox, onResolveUrl, onTranscribeAudio, className, }: MediaRendererProps): react.JSX.Element | null;
|
|
106
136
|
|
|
107
137
|
/**
|
|
108
138
|
* Gravação de áudio no simulador, pelo microfone do próprio navegador.
|
|
@@ -358,6 +388,16 @@ interface ConversationsApi {
|
|
|
358
388
|
transcript: string;
|
|
359
389
|
filename: string;
|
|
360
390
|
}>;
|
|
391
|
+
/**
|
|
392
|
+
* Transcreve o áudio de uma mensagem e devolve o resultado.
|
|
393
|
+
*
|
|
394
|
+
* **Opcional por capacidade.** Um host em modo automático transcreve na ingestão e não expõe rota
|
|
395
|
+
* nenhuma; um host sem engine configurado não transcreve de jeito algum. Nos dois casos o balão
|
|
396
|
+
* simplesmente não desenha o botão, em vez de oferecer uma ação que estoura no clique.
|
|
397
|
+
*
|
|
398
|
+
* `messageId` e não `conversationId`: transcrição é por áudio, e uma conversa tem vários.
|
|
399
|
+
*/
|
|
400
|
+
transcribeAudio?(messageId: string): Promise<MessageTranscription>;
|
|
361
401
|
}
|
|
362
402
|
/**
|
|
363
403
|
* Superfície mínima de stream que o pacote consome — exatamente o que `useConversationRealtime`
|
|
@@ -413,4 +453,4 @@ interface ConversationDocument {
|
|
|
413
453
|
linkedAt: string;
|
|
414
454
|
}
|
|
415
455
|
|
|
416
|
-
export { AudioRecorderButton as A, type ListDocumentsParams as B, CHANNEL_CAPABILITIES as C, DEFAULT_AUDIO_RECORDER_BUTTON_LABELS as D, type MediaRendererProps as E, type FormatContactHandleParams as F, type MessagePayload as G, HANDLE_KIND as H, type InteractiveOption as I, type
|
|
456
|
+
export { AudioRecorderButton as A, type ListDocumentsParams as B, CHANNEL_CAPABILITIES as C, DEFAULT_AUDIO_RECORDER_BUTTON_LABELS as D, type MediaRendererProps as E, type FormatContactHandleParams as F, type MessagePayload as G, HANDLE_KIND as H, type InteractiveOption as I, type MessageTranscription as J, type ReopenMechanism as K, type ListConversationsParams as L, MediaRenderer as M, type ResolveMediaUrl as N, type TranscriptionStatus as O, capabilitiesOf as P, channelFiltersFor as Q, REOPEN_MECHANISM as R, type SSEProvider as S, type TranscriptionMode as T, contactFlag as U, formatContactHandle as V, type AudioRecorderButtonLabels as a, type AudioRecorderButtonProps as b, CHANNEL_FILTER_ALL as c, CONVERSATION_CHANNEL as d, type ChannelCapabilities as e, type ChannelFilter as f, type ChannelFilterOption as g, type CompanyDocument as h, type CompanyDocumentPage as i, type ConversationChannel as j, type ConversationDocument as k, type ConversationDocumentPage as l, type ConversationEventSource as m, type ConversationPage as n, type ConversationSummary as o, type ConversationTemplate as p, type ConversationsApi as q, type ConversationsFeatures as r, type ConversationsTheme as s, type ConversationsUIConfig as t, DEFAULT_CONVERSATION_CHANNEL as u, DEFAULT_MAX_RECORDING_MILLISECONDS as v, type HandleKind as w, type InteractivePayload as x, type InteractiveSection as y, type InteractiveSelection as z };
|