@customerhero/js 0.0.2 → 1.0.1
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/index.cjs +981 -47
- package/dist/index.d.cts +87 -4
- package/dist/index.d.ts +87 -4
- package/dist/index.js +971 -46
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,7 +21,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
CustomerHeroChat: () => CustomerHeroChat,
|
|
24
|
-
DEFAULTS: () => DEFAULTS
|
|
24
|
+
DEFAULTS: () => DEFAULTS,
|
|
25
|
+
SUPPORTED_LOCALES: () => SUPPORTED_LOCALES,
|
|
26
|
+
ScreenshotCancelled: () => ScreenshotCancelled,
|
|
27
|
+
ScreenshotUnavailable: () => ScreenshotUnavailable,
|
|
28
|
+
canCaptureScreenshot: () => canCaptureScreenshot,
|
|
29
|
+
captureScreenshot: () => captureScreenshot,
|
|
30
|
+
createTranslator: () => createTranslator,
|
|
31
|
+
detectLocale: () => detectLocale,
|
|
32
|
+
isRtlLocale: () => isRtlLocale,
|
|
33
|
+
resolveLocale: () => resolveLocale
|
|
25
34
|
});
|
|
26
35
|
module.exports = __toCommonJS(index_exports);
|
|
27
36
|
|
|
@@ -37,7 +46,7 @@ var DEFAULTS = {
|
|
|
37
46
|
title: "CustomerHero"
|
|
38
47
|
};
|
|
39
48
|
|
|
40
|
-
// src/i18n.ts
|
|
49
|
+
// src/i18n/locales/en.ts
|
|
41
50
|
var en = {
|
|
42
51
|
online: "Online",
|
|
43
52
|
typing: "Typing...",
|
|
@@ -49,8 +58,20 @@ var en = {
|
|
|
49
58
|
send_message: "Send message",
|
|
50
59
|
helpful: "Helpful",
|
|
51
60
|
not_helpful: "Not helpful",
|
|
52
|
-
menu: "Menu"
|
|
61
|
+
menu: "Menu",
|
|
62
|
+
action_approve: "Approve",
|
|
63
|
+
action_cancel: "Cancel",
|
|
64
|
+
action_what_will_happen: "What will happen?",
|
|
65
|
+
action_already_resolved: "This action has already been resolved.",
|
|
66
|
+
action_failed: "Couldn't complete that action. Please try again.",
|
|
67
|
+
status_sending: "Sending",
|
|
68
|
+
status_sent: "Sent",
|
|
69
|
+
status_failed: "Failed to send",
|
|
70
|
+
screenshot_capture: "Capture screenshot",
|
|
71
|
+
attachment_remove: "Remove attachment"
|
|
53
72
|
};
|
|
73
|
+
|
|
74
|
+
// src/i18n/locales/es.ts
|
|
54
75
|
var es = {
|
|
55
76
|
online: "En l\xEDnea",
|
|
56
77
|
typing: "Escribiendo...",
|
|
@@ -62,25 +83,471 @@ var es = {
|
|
|
62
83
|
send_message: "Enviar mensaje",
|
|
63
84
|
helpful: "\xDAtil",
|
|
64
85
|
not_helpful: "No \xFAtil",
|
|
65
|
-
menu: "Men\xFA"
|
|
86
|
+
menu: "Men\xFA",
|
|
87
|
+
action_approve: "Aprobar",
|
|
88
|
+
action_cancel: "Cancelar",
|
|
89
|
+
action_what_will_happen: "\xBFQu\xE9 pasar\xE1?",
|
|
90
|
+
action_already_resolved: "Esta acci\xF3n ya se ha resuelto.",
|
|
91
|
+
action_failed: "No se pudo completar la acci\xF3n. Int\xE9ntalo de nuevo.",
|
|
92
|
+
status_sending: "Enviando",
|
|
93
|
+
status_sent: "Enviado",
|
|
94
|
+
status_failed: "Error al enviar",
|
|
95
|
+
screenshot_capture: "Capturar pantalla",
|
|
96
|
+
attachment_remove: "Quitar adjunto"
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// src/i18n/locales/pt-BR.ts
|
|
100
|
+
var ptBR = {
|
|
101
|
+
online: "Online",
|
|
102
|
+
typing: "Digitando...",
|
|
103
|
+
unable_to_load: "N\xE3o foi poss\xEDvel carregar o chat",
|
|
104
|
+
powered_by: "Desenvolvido por",
|
|
105
|
+
new_conversation: "Nova conversa",
|
|
106
|
+
open_chat: "Abrir chat",
|
|
107
|
+
close_chat: "Fechar chat",
|
|
108
|
+
send_message: "Enviar mensagem",
|
|
109
|
+
helpful: "\xDAtil",
|
|
110
|
+
not_helpful: "N\xE3o \xFAtil",
|
|
111
|
+
menu: "Menu",
|
|
112
|
+
action_approve: "Aprovar",
|
|
113
|
+
action_cancel: "Cancelar",
|
|
114
|
+
action_what_will_happen: "O que vai acontecer?",
|
|
115
|
+
action_already_resolved: "Esta a\xE7\xE3o j\xE1 foi resolvida.",
|
|
116
|
+
action_failed: "N\xE3o foi poss\xEDvel concluir a a\xE7\xE3o. Tente novamente.",
|
|
117
|
+
status_sending: "Enviando",
|
|
118
|
+
status_sent: "Enviado",
|
|
119
|
+
status_failed: "Falha no envio",
|
|
120
|
+
screenshot_capture: "Capturar tela",
|
|
121
|
+
attachment_remove: "Remover anexo"
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// src/i18n/locales/pt-PT.ts
|
|
125
|
+
var ptPT = {
|
|
126
|
+
online: "Online",
|
|
127
|
+
typing: "A escrever...",
|
|
128
|
+
unable_to_load: "N\xE3o foi poss\xEDvel carregar o chat",
|
|
129
|
+
powered_by: "Desenvolvido por",
|
|
130
|
+
new_conversation: "Nova conversa",
|
|
131
|
+
open_chat: "Abrir chat",
|
|
132
|
+
close_chat: "Fechar chat",
|
|
133
|
+
send_message: "Enviar mensagem",
|
|
134
|
+
helpful: "\xDAtil",
|
|
135
|
+
not_helpful: "N\xE3o \xFAtil",
|
|
136
|
+
menu: "Menu",
|
|
137
|
+
action_approve: "Aprovar",
|
|
138
|
+
action_cancel: "Cancelar",
|
|
139
|
+
action_what_will_happen: "O que ir\xE1 acontecer?",
|
|
140
|
+
action_already_resolved: "Esta a\xE7\xE3o j\xE1 foi resolvida.",
|
|
141
|
+
action_failed: "N\xE3o foi poss\xEDvel concluir a a\xE7\xE3o. Tente novamente.",
|
|
142
|
+
status_sending: "A enviar",
|
|
143
|
+
status_sent: "Enviada",
|
|
144
|
+
status_failed: "Falha no envio",
|
|
145
|
+
screenshot_capture: "Capturar ecr\xE3",
|
|
146
|
+
attachment_remove: "Remover anexo"
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// src/i18n/locales/fr.ts
|
|
150
|
+
var fr = {
|
|
151
|
+
online: "En ligne",
|
|
152
|
+
typing: "Saisie...",
|
|
153
|
+
unable_to_load: "Impossible de charger le chat",
|
|
154
|
+
powered_by: "Propuls\xE9 par",
|
|
155
|
+
new_conversation: "Nouvelle conversation",
|
|
156
|
+
open_chat: "Ouvrir le chat",
|
|
157
|
+
close_chat: "Fermer le chat",
|
|
158
|
+
send_message: "Envoyer le message",
|
|
159
|
+
helpful: "Utile",
|
|
160
|
+
not_helpful: "Pas utile",
|
|
161
|
+
menu: "Menu",
|
|
162
|
+
action_approve: "Approuver",
|
|
163
|
+
action_cancel: "Annuler",
|
|
164
|
+
action_what_will_happen: "Que va-t-il se passer ?",
|
|
165
|
+
action_already_resolved: "Cette action a d\xE9j\xE0 \xE9t\xE9 trait\xE9e.",
|
|
166
|
+
action_failed: "Impossible de r\xE9aliser cette action. Veuillez r\xE9essayer.",
|
|
167
|
+
status_sending: "Envoi\u2026",
|
|
168
|
+
status_sent: "Envoy\xE9",
|
|
169
|
+
status_failed: "\xC9chec de l'envoi",
|
|
170
|
+
screenshot_capture: "Capture d'\xE9cran",
|
|
171
|
+
attachment_remove: "Supprimer la pi\xE8ce jointe"
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// src/i18n/locales/de.ts
|
|
175
|
+
var de = {
|
|
176
|
+
online: "Online",
|
|
177
|
+
typing: "Schreibt...",
|
|
178
|
+
unable_to_load: "Chat konnte nicht geladen werden",
|
|
179
|
+
powered_by: "Bereitgestellt von",
|
|
180
|
+
new_conversation: "Neue Unterhaltung",
|
|
181
|
+
open_chat: "Chat \xF6ffnen",
|
|
182
|
+
close_chat: "Chat schlie\xDFen",
|
|
183
|
+
send_message: "Nachricht senden",
|
|
184
|
+
helpful: "Hilfreich",
|
|
185
|
+
not_helpful: "Nicht hilfreich",
|
|
186
|
+
menu: "Men\xFC",
|
|
187
|
+
action_approve: "Best\xE4tigen",
|
|
188
|
+
action_cancel: "Abbrechen",
|
|
189
|
+
action_what_will_happen: "Was passiert dann?",
|
|
190
|
+
action_already_resolved: "Diese Aktion wurde bereits abgeschlossen.",
|
|
191
|
+
action_failed: "Aktion konnte nicht abgeschlossen werden. Bitte erneut versuchen.",
|
|
192
|
+
status_sending: "Wird gesendet",
|
|
193
|
+
status_sent: "Gesendet",
|
|
194
|
+
status_failed: "Senden fehlgeschlagen",
|
|
195
|
+
screenshot_capture: "Bildschirmfoto erstellen",
|
|
196
|
+
attachment_remove: "Anhang entfernen"
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/i18n/locales/it.ts
|
|
200
|
+
var it = {
|
|
201
|
+
online: "Online",
|
|
202
|
+
typing: "Sta scrivendo...",
|
|
203
|
+
unable_to_load: "Impossibile caricare la chat",
|
|
204
|
+
powered_by: "Realizzato da",
|
|
205
|
+
new_conversation: "Nuova conversazione",
|
|
206
|
+
open_chat: "Apri chat",
|
|
207
|
+
close_chat: "Chiudi chat",
|
|
208
|
+
send_message: "Invia messaggio",
|
|
209
|
+
helpful: "Utile",
|
|
210
|
+
not_helpful: "Non utile",
|
|
211
|
+
menu: "Menu",
|
|
212
|
+
action_approve: "Approva",
|
|
213
|
+
action_cancel: "Annulla",
|
|
214
|
+
action_what_will_happen: "Cosa accadr\xE0?",
|
|
215
|
+
action_already_resolved: "Questa azione \xE8 gi\xE0 stata risolta.",
|
|
216
|
+
action_failed: "Impossibile completare l'azione. Riprova.",
|
|
217
|
+
status_sending: "Invio in corso",
|
|
218
|
+
status_sent: "Inviato",
|
|
219
|
+
status_failed: "Invio non riuscito",
|
|
220
|
+
screenshot_capture: "Cattura schermata",
|
|
221
|
+
attachment_remove: "Rimuovi allegato"
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// src/i18n/locales/nl.ts
|
|
225
|
+
var nl = {
|
|
226
|
+
online: "Online",
|
|
227
|
+
typing: "Typt...",
|
|
228
|
+
unable_to_load: "Chat kon niet worden geladen",
|
|
229
|
+
powered_by: "Mogelijk gemaakt door",
|
|
230
|
+
new_conversation: "Nieuw gesprek",
|
|
231
|
+
open_chat: "Chat openen",
|
|
232
|
+
close_chat: "Chat sluiten",
|
|
233
|
+
send_message: "Bericht verzenden",
|
|
234
|
+
helpful: "Nuttig",
|
|
235
|
+
not_helpful: "Niet nuttig",
|
|
236
|
+
menu: "Menu",
|
|
237
|
+
action_approve: "Goedkeuren",
|
|
238
|
+
action_cancel: "Annuleren",
|
|
239
|
+
action_what_will_happen: "Wat gaat er gebeuren?",
|
|
240
|
+
action_already_resolved: "Deze actie is al afgehandeld.",
|
|
241
|
+
action_failed: "Actie kon niet worden voltooid. Probeer het opnieuw.",
|
|
242
|
+
status_sending: "Verzenden",
|
|
243
|
+
status_sent: "Verzonden",
|
|
244
|
+
status_failed: "Verzenden mislukt",
|
|
245
|
+
screenshot_capture: "Schermafbeelding maken",
|
|
246
|
+
attachment_remove: "Bijlage verwijderen"
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// src/i18n/locales/pl.ts
|
|
250
|
+
var pl = {
|
|
251
|
+
online: "Online",
|
|
252
|
+
typing: "Pisze...",
|
|
253
|
+
unable_to_load: "Nie mo\u017Cna za\u0142adowa\u0107 czatu",
|
|
254
|
+
powered_by: "Dzia\u0142a dzi\u0119ki",
|
|
255
|
+
new_conversation: "Nowa rozmowa",
|
|
256
|
+
open_chat: "Otw\xF3rz czat",
|
|
257
|
+
close_chat: "Zamknij czat",
|
|
258
|
+
send_message: "Wy\u015Blij wiadomo\u015B\u0107",
|
|
259
|
+
helpful: "Pomocne",
|
|
260
|
+
not_helpful: "Niepomocne",
|
|
261
|
+
menu: "Menu",
|
|
262
|
+
action_approve: "Zatwierd\u017A",
|
|
263
|
+
action_cancel: "Anuluj",
|
|
264
|
+
action_what_will_happen: "Co si\u0119 stanie?",
|
|
265
|
+
action_already_resolved: "Ta akcja zosta\u0142a ju\u017C rozwi\u0105zana.",
|
|
266
|
+
action_failed: "Nie uda\u0142o si\u0119 wykona\u0107 akcji. Spr\xF3buj ponownie.",
|
|
267
|
+
status_sending: "Wysy\u0142anie",
|
|
268
|
+
status_sent: "Wys\u0142ano",
|
|
269
|
+
status_failed: "Nie uda\u0142o si\u0119 wys\u0142a\u0107",
|
|
270
|
+
screenshot_capture: "Zr\xF3b zrzut ekranu",
|
|
271
|
+
attachment_remove: "Usu\u0144 za\u0142\u0105cznik"
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// src/i18n/locales/tr.ts
|
|
275
|
+
var tr = {
|
|
276
|
+
online: "\xC7evrimi\xE7i",
|
|
277
|
+
typing: "Yaz\u0131yor...",
|
|
278
|
+
unable_to_load: "Sohbet y\xFCklenemedi",
|
|
279
|
+
powered_by: "Sa\u011Flayan",
|
|
280
|
+
new_conversation: "Yeni sohbet",
|
|
281
|
+
open_chat: "Sohbeti a\xE7",
|
|
282
|
+
close_chat: "Sohbeti kapat",
|
|
283
|
+
send_message: "Mesaj g\xF6nder",
|
|
284
|
+
helpful: "Yararl\u0131",
|
|
285
|
+
not_helpful: "Yararl\u0131 de\u011Fil",
|
|
286
|
+
menu: "Men\xFC",
|
|
287
|
+
action_approve: "Onayla",
|
|
288
|
+
action_cancel: "\u0130ptal",
|
|
289
|
+
action_what_will_happen: "Ne olacak?",
|
|
290
|
+
action_already_resolved: "Bu i\u015Flem zaten \xE7\xF6z\xFCld\xFC.",
|
|
291
|
+
action_failed: "\u0130\u015Flem tamamlanamad\u0131. L\xFCtfen tekrar deneyin.",
|
|
292
|
+
status_sending: "G\xF6nderiliyor",
|
|
293
|
+
status_sent: "G\xF6nderildi",
|
|
294
|
+
status_failed: "G\xF6nderilemedi",
|
|
295
|
+
screenshot_capture: "Ekran g\xF6r\xFCnt\xFCs\xFC al",
|
|
296
|
+
attachment_remove: "Eki kald\u0131r"
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// src/i18n/locales/ar.ts
|
|
300
|
+
var ar = {
|
|
301
|
+
online: "\u0645\u062A\u0635\u0644",
|
|
302
|
+
typing: "\u064A\u0643\u062A\u0628...",
|
|
303
|
+
unable_to_load: "\u062A\u0639\u0630\u0631 \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0645\u062D\u0627\u062F\u062B\u0629",
|
|
304
|
+
powered_by: "\u0645\u062F\u0639\u0648\u0645 \u0645\u0646",
|
|
305
|
+
new_conversation: "\u0645\u062D\u0627\u062F\u062B\u0629 \u062C\u062F\u064A\u062F\u0629",
|
|
306
|
+
open_chat: "\u0641\u062A\u062D \u0627\u0644\u0645\u062D\u0627\u062F\u062B\u0629",
|
|
307
|
+
close_chat: "\u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0645\u062D\u0627\u062F\u062B\u0629",
|
|
308
|
+
send_message: "\u0625\u0631\u0633\u0627\u0644 \u0627\u0644\u0631\u0633\u0627\u0644\u0629",
|
|
309
|
+
helpful: "\u0645\u0641\u064A\u062F",
|
|
310
|
+
not_helpful: "\u063A\u064A\u0631 \u0645\u0641\u064A\u062F",
|
|
311
|
+
menu: "\u0627\u0644\u0642\u0627\u0626\u0645\u0629",
|
|
312
|
+
action_approve: "\u0645\u0648\u0627\u0641\u0642\u0629",
|
|
313
|
+
action_cancel: "\u0625\u0644\u063A\u0627\u0621",
|
|
314
|
+
action_what_will_happen: "\u0645\u0627\u0630\u0627 \u0633\u064A\u062D\u062F\u062B\u061F",
|
|
315
|
+
action_already_resolved: "\u062A\u0645 \u062D\u0644 \u0647\u0630\u0627 \u0627\u0644\u0625\u062C\u0631\u0627\u0621 \u0628\u0627\u0644\u0641\u0639\u0644.",
|
|
316
|
+
action_failed: "\u062A\u0639\u0630\u0631 \u0625\u0643\u0645\u0627\u0644 \u0627\u0644\u0625\u062C\u0631\u0627\u0621. \u064A\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649.",
|
|
317
|
+
status_sending: "\u062C\u0627\u0631\u064D \u0627\u0644\u0625\u0631\u0633\u0627\u0644",
|
|
318
|
+
status_sent: "\u062A\u0645 \u0627\u0644\u0625\u0631\u0633\u0627\u0644",
|
|
319
|
+
status_failed: "\u0641\u0634\u0644 \u0627\u0644\u0625\u0631\u0633\u0627\u0644",
|
|
320
|
+
screenshot_capture: "\u0627\u0644\u062A\u0642\u0627\u0637 \u0644\u0642\u0637\u0629 \u0634\u0627\u0634\u0629",
|
|
321
|
+
attachment_remove: "\u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0645\u0631\u0641\u0642"
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// src/i18n/locales/ja.ts
|
|
325
|
+
var ja = {
|
|
326
|
+
online: "\u30AA\u30F3\u30E9\u30A4\u30F3",
|
|
327
|
+
typing: "\u5165\u529B\u4E2D...",
|
|
328
|
+
unable_to_load: "\u30C1\u30E3\u30C3\u30C8\u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093",
|
|
329
|
+
powered_by: "\u63D0\u4F9B\uFF1A",
|
|
330
|
+
new_conversation: "\u65B0\u3057\u3044\u4F1A\u8A71",
|
|
331
|
+
open_chat: "\u30C1\u30E3\u30C3\u30C8\u3092\u958B\u304F",
|
|
332
|
+
close_chat: "\u30C1\u30E3\u30C3\u30C8\u3092\u9589\u3058\u308B",
|
|
333
|
+
send_message: "\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u9001\u4FE1",
|
|
334
|
+
helpful: "\u5F79\u306B\u7ACB\u3063\u305F",
|
|
335
|
+
not_helpful: "\u5F79\u306B\u7ACB\u305F\u306A\u304B\u3063\u305F",
|
|
336
|
+
menu: "\u30E1\u30CB\u30E5\u30FC",
|
|
337
|
+
action_approve: "\u627F\u8A8D",
|
|
338
|
+
action_cancel: "\u30AD\u30E3\u30F3\u30BB\u30EB",
|
|
339
|
+
action_what_will_happen: "\u4F55\u304C\u8D77\u3053\u308A\u307E\u3059\u304B\uFF1F",
|
|
340
|
+
action_already_resolved: "\u3053\u306E\u30A2\u30AF\u30B7\u30E7\u30F3\u306F\u65E2\u306B\u51E6\u7406\u3055\u308C\u3066\u3044\u307E\u3059\u3002",
|
|
341
|
+
action_failed: "\u30A2\u30AF\u30B7\u30E7\u30F3\u3092\u5B8C\u4E86\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044\u3002",
|
|
342
|
+
status_sending: "\u9001\u4FE1\u4E2D",
|
|
343
|
+
status_sent: "\u9001\u4FE1\u6E08\u307F",
|
|
344
|
+
status_failed: "\u9001\u4FE1\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
345
|
+
screenshot_capture: "\u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8\u3092\u64AE\u308B",
|
|
346
|
+
attachment_remove: "\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB\u3092\u524A\u9664"
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// src/i18n/locales/ko.ts
|
|
350
|
+
var ko = {
|
|
351
|
+
online: "\uC628\uB77C\uC778",
|
|
352
|
+
typing: "\uC785\uB825 \uC911...",
|
|
353
|
+
unable_to_load: "\uCC44\uD305\uC744 \uBD88\uB7EC\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
354
|
+
powered_by: "\uC81C\uACF5:",
|
|
355
|
+
new_conversation: "\uC0C8 \uB300\uD654",
|
|
356
|
+
open_chat: "\uCC44\uD305 \uC5F4\uAE30",
|
|
357
|
+
close_chat: "\uCC44\uD305 \uB2EB\uAE30",
|
|
358
|
+
send_message: "\uBA54\uC2DC\uC9C0 \uBCF4\uB0B4\uAE30",
|
|
359
|
+
helpful: "\uB3C4\uC6C0\uC774 \uB428",
|
|
360
|
+
not_helpful: "\uB3C4\uC6C0\uC774 \uC548 \uB428",
|
|
361
|
+
menu: "\uBA54\uB274",
|
|
362
|
+
action_approve: "\uC2B9\uC778",
|
|
363
|
+
action_cancel: "\uCDE8\uC18C",
|
|
364
|
+
action_what_will_happen: "\uBB34\uC2A8 \uC77C\uC774 \uC77C\uC5B4\uB098\uB098\uC694?",
|
|
365
|
+
action_already_resolved: "\uC774 \uC791\uC5C5\uC740 \uC774\uBBF8 \uCC98\uB9AC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
|
|
366
|
+
action_failed: "\uC791\uC5C5\uC744 \uC644\uB8CC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
|
|
367
|
+
status_sending: "\uBCF4\uB0B4\uB294 \uC911",
|
|
368
|
+
status_sent: "\uBCF4\uB0C8\uC74C",
|
|
369
|
+
status_failed: "\uBCF4\uB0B4\uAE30 \uC2E4\uD328",
|
|
370
|
+
screenshot_capture: "\uC2A4\uD06C\uB9B0\uC0F7 \uCC0D\uAE30",
|
|
371
|
+
attachment_remove: "\uCCA8\uBD80 \uD30C\uC77C \uC81C\uAC70"
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// src/i18n/locales/zh-CN.ts
|
|
375
|
+
var zhCN = {
|
|
376
|
+
online: "\u5728\u7EBF",
|
|
377
|
+
typing: "\u6B63\u5728\u8F93\u5165...",
|
|
378
|
+
unable_to_load: "\u65E0\u6CD5\u52A0\u8F7D\u804A\u5929",
|
|
379
|
+
powered_by: "\u6280\u672F\u652F\u6301\uFF1A",
|
|
380
|
+
new_conversation: "\u65B0\u5BF9\u8BDD",
|
|
381
|
+
open_chat: "\u6253\u5F00\u804A\u5929",
|
|
382
|
+
close_chat: "\u5173\u95ED\u804A\u5929",
|
|
383
|
+
send_message: "\u53D1\u9001\u6D88\u606F",
|
|
384
|
+
helpful: "\u6709\u7528",
|
|
385
|
+
not_helpful: "\u6CA1\u7528",
|
|
386
|
+
menu: "\u83DC\u5355",
|
|
387
|
+
action_approve: "\u6279\u51C6",
|
|
388
|
+
action_cancel: "\u53D6\u6D88",
|
|
389
|
+
action_what_will_happen: "\u4F1A\u53D1\u751F\u4EC0\u4E48\uFF1F",
|
|
390
|
+
action_already_resolved: "\u6B64\u64CD\u4F5C\u5DF2\u5904\u7406\u3002",
|
|
391
|
+
action_failed: "\u64CD\u4F5C\u672A\u80FD\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5\u3002",
|
|
392
|
+
status_sending: "\u53D1\u9001\u4E2D",
|
|
393
|
+
status_sent: "\u5DF2\u53D1\u9001",
|
|
394
|
+
status_failed: "\u53D1\u9001\u5931\u8D25",
|
|
395
|
+
screenshot_capture: "\u622A\u5C4F",
|
|
396
|
+
attachment_remove: "\u79FB\u9664\u9644\u4EF6"
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// src/i18n/locales/zh-TW.ts
|
|
400
|
+
var zhTW = {
|
|
401
|
+
online: "\u5728\u7DDA",
|
|
402
|
+
typing: "\u6B63\u5728\u8F38\u5165...",
|
|
403
|
+
unable_to_load: "\u7121\u6CD5\u8F09\u5165\u804A\u5929",
|
|
404
|
+
powered_by: "\u6280\u8853\u652F\u63F4\uFF1A",
|
|
405
|
+
new_conversation: "\u65B0\u5C0D\u8A71",
|
|
406
|
+
open_chat: "\u958B\u555F\u804A\u5929",
|
|
407
|
+
close_chat: "\u95DC\u9589\u804A\u5929",
|
|
408
|
+
send_message: "\u50B3\u9001\u8A0A\u606F",
|
|
409
|
+
helpful: "\u6709\u7528",
|
|
410
|
+
not_helpful: "\u6C92\u7528",
|
|
411
|
+
menu: "\u9078\u55AE",
|
|
412
|
+
action_approve: "\u6838\u51C6",
|
|
413
|
+
action_cancel: "\u53D6\u6D88",
|
|
414
|
+
action_what_will_happen: "\u6703\u767C\u751F\u4EC0\u9EBC\uFF1F",
|
|
415
|
+
action_already_resolved: "\u6B64\u64CD\u4F5C\u5DF2\u8655\u7406\u3002",
|
|
416
|
+
action_failed: "\u64CD\u4F5C\u672A\u80FD\u5B8C\u6210\uFF0C\u8ACB\u91CD\u8A66\u3002",
|
|
417
|
+
status_sending: "\u50B3\u9001\u4E2D",
|
|
418
|
+
status_sent: "\u5DF2\u50B3\u9001",
|
|
419
|
+
status_failed: "\u50B3\u9001\u5931\u6557",
|
|
420
|
+
screenshot_capture: "\u64F7\u53D6\u756B\u9762",
|
|
421
|
+
attachment_remove: "\u79FB\u9664\u9644\u4EF6"
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// src/i18n/index.ts
|
|
425
|
+
var SUPPORTED_LOCALES = [
|
|
426
|
+
"en",
|
|
427
|
+
"es",
|
|
428
|
+
"pt-BR",
|
|
429
|
+
"pt-PT",
|
|
430
|
+
"fr",
|
|
431
|
+
"de",
|
|
432
|
+
"it",
|
|
433
|
+
"nl",
|
|
434
|
+
"pl",
|
|
435
|
+
"tr",
|
|
436
|
+
"ar",
|
|
437
|
+
"ja",
|
|
438
|
+
"ko",
|
|
439
|
+
"zh-CN",
|
|
440
|
+
"zh-TW"
|
|
441
|
+
];
|
|
442
|
+
var catalog = {
|
|
443
|
+
en,
|
|
444
|
+
es,
|
|
445
|
+
"pt-BR": ptBR,
|
|
446
|
+
"pt-PT": ptPT,
|
|
447
|
+
fr,
|
|
448
|
+
de,
|
|
449
|
+
it,
|
|
450
|
+
nl,
|
|
451
|
+
pl,
|
|
452
|
+
tr,
|
|
453
|
+
ar,
|
|
454
|
+
ja,
|
|
455
|
+
ko,
|
|
456
|
+
"zh-CN": zhCN,
|
|
457
|
+
"zh-TW": zhTW
|
|
66
458
|
};
|
|
67
|
-
var
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
const translations = locales[resolved] ?? en;
|
|
71
|
-
return (key) => translations[key] ?? en[key] ?? key;
|
|
459
|
+
var RTL_PRIMARY_LANGS = /* @__PURE__ */ new Set(["ar", "he", "fa", "ur"]);
|
|
460
|
+
function isRtlLocale(locale) {
|
|
461
|
+
return RTL_PRIMARY_LANGS.has(locale.split("-")[0].toLowerCase());
|
|
72
462
|
}
|
|
73
463
|
function resolveLocale(locale) {
|
|
74
|
-
if (locale)
|
|
75
|
-
|
|
76
|
-
|
|
464
|
+
if (!locale) return "en";
|
|
465
|
+
if (SUPPORTED_LOCALES.includes(locale)) {
|
|
466
|
+
return locale;
|
|
467
|
+
}
|
|
468
|
+
const ciMatch = SUPPORTED_LOCALES.find(
|
|
469
|
+
(l) => l.toLowerCase() === locale.toLowerCase()
|
|
470
|
+
);
|
|
471
|
+
if (ciMatch) return ciMatch;
|
|
472
|
+
const base = locale.split("-")[0].toLowerCase();
|
|
473
|
+
if (base === "pt") return "pt-BR";
|
|
474
|
+
if (base === "zh") return "zh-CN";
|
|
475
|
+
if (SUPPORTED_LOCALES.includes(base)) {
|
|
476
|
+
return base;
|
|
477
|
+
}
|
|
478
|
+
return "en";
|
|
479
|
+
}
|
|
480
|
+
function detectLocale(explicit) {
|
|
481
|
+
if (explicit) return resolveLocale(explicit);
|
|
482
|
+
if (typeof window !== "undefined" && window.location?.search) {
|
|
483
|
+
try {
|
|
484
|
+
const params = new URLSearchParams(window.location.search);
|
|
485
|
+
const lang = params.get("lang");
|
|
486
|
+
if (lang) return resolveLocale(lang);
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
77
489
|
}
|
|
78
490
|
if (typeof navigator !== "undefined" && navigator.language) {
|
|
79
|
-
|
|
80
|
-
if (locales[base]) return base;
|
|
491
|
+
return resolveLocale(navigator.language);
|
|
81
492
|
}
|
|
82
493
|
return "en";
|
|
83
494
|
}
|
|
495
|
+
function createTranslator(locale, overrides) {
|
|
496
|
+
const translations = catalog[locale] ?? catalog.en;
|
|
497
|
+
return (key) => {
|
|
498
|
+
const ov = overrides?.[key];
|
|
499
|
+
if (ov) {
|
|
500
|
+
const localized = ov[locale];
|
|
501
|
+
if (typeof localized === "string") return localized;
|
|
502
|
+
const enOverride = ov.en;
|
|
503
|
+
if (typeof enOverride === "string") return enOverride;
|
|
504
|
+
}
|
|
505
|
+
return translations[key] ?? catalog.en[key] ?? key;
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// src/sse.ts
|
|
510
|
+
async function* readSSEStream(stream) {
|
|
511
|
+
const reader = stream.getReader();
|
|
512
|
+
const decoder = new TextDecoder();
|
|
513
|
+
let buffer = "";
|
|
514
|
+
try {
|
|
515
|
+
while (true) {
|
|
516
|
+
const { done, value } = await reader.read();
|
|
517
|
+
if (done) break;
|
|
518
|
+
buffer += decoder.decode(value, { stream: true });
|
|
519
|
+
let sep = buffer.indexOf("\n\n");
|
|
520
|
+
while (sep !== -1) {
|
|
521
|
+
const raw = buffer.slice(0, sep);
|
|
522
|
+
buffer = buffer.slice(sep + 2);
|
|
523
|
+
const evt = parseSSERecord(raw);
|
|
524
|
+
if (evt) yield evt;
|
|
525
|
+
sep = buffer.indexOf("\n\n");
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const remaining = buffer.trim();
|
|
529
|
+
if (remaining.length > 0) {
|
|
530
|
+
const evt = parseSSERecord(remaining);
|
|
531
|
+
if (evt) yield evt;
|
|
532
|
+
}
|
|
533
|
+
} finally {
|
|
534
|
+
reader.releaseLock();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function parseSSERecord(raw) {
|
|
538
|
+
let event = "";
|
|
539
|
+
let data = "";
|
|
540
|
+
for (const line of raw.split("\n")) {
|
|
541
|
+
if (line.startsWith("event:")) {
|
|
542
|
+
event = line.slice(6).trim();
|
|
543
|
+
} else if (line.startsWith("data:")) {
|
|
544
|
+
data = data.length > 0 ? `${data}
|
|
545
|
+
${line.slice(5).trim()}` : line.slice(5).trim();
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (!event) return null;
|
|
549
|
+
return { event, data };
|
|
550
|
+
}
|
|
84
551
|
|
|
85
552
|
// src/client.ts
|
|
86
553
|
function resolveConfig(userConfig, fetched) {
|
|
@@ -95,7 +562,8 @@ function resolveConfig(userConfig, fetched) {
|
|
|
95
562
|
welcomeMessage: userConfig.welcomeMessage ?? fetched?.welcomeMessage ?? DEFAULTS.welcomeMessage,
|
|
96
563
|
title: userConfig.title ?? fetched?.title ?? DEFAULTS.title,
|
|
97
564
|
avatarUrl: userConfig.avatarUrl ?? fetched?.avatarUrl,
|
|
98
|
-
suggestedMessages: userConfig.suggestedMessages ?? fetched?.suggestedMessages ?? []
|
|
565
|
+
suggestedMessages: userConfig.suggestedMessages ?? fetched?.suggestedMessages ?? [],
|
|
566
|
+
stringOverrides: fetched?.stringOverrides
|
|
99
567
|
};
|
|
100
568
|
}
|
|
101
569
|
function getStorage() {
|
|
@@ -111,12 +579,18 @@ var CustomerHeroChat = class {
|
|
|
111
579
|
storage;
|
|
112
580
|
userConfig;
|
|
113
581
|
identityData = null;
|
|
582
|
+
// `t` is mutable: it gets rebuilt when locale changes or when the fetched
|
|
583
|
+
// widget config delivers `stringOverrides`. The React layer reads this
|
|
584
|
+
// property directly each render rather than caching it in a state snapshot,
|
|
585
|
+
// so a `setLocale` call propagates through `useSyncExternalStore` via the
|
|
586
|
+
// accompanying `setState({ locale, isRtl })` notification.
|
|
114
587
|
t;
|
|
115
588
|
constructor(config) {
|
|
116
589
|
this.userConfig = config;
|
|
117
590
|
this.storage = getStorage();
|
|
118
|
-
|
|
591
|
+
const locale = detectLocale(config.locale);
|
|
119
592
|
const resolved = resolveConfig(config);
|
|
593
|
+
this.t = createTranslator(locale, resolved.stringOverrides);
|
|
120
594
|
const storedConvId = this.storage?.getItem(`ch_conv_${config.chatbotId}`);
|
|
121
595
|
this.state = {
|
|
122
596
|
messages: [],
|
|
@@ -127,9 +601,26 @@ var CustomerHeroChat = class {
|
|
|
127
601
|
configLoaded: false,
|
|
128
602
|
configError: null,
|
|
129
603
|
error: null,
|
|
130
|
-
identity: null
|
|
604
|
+
identity: null,
|
|
605
|
+
locale,
|
|
606
|
+
isRtl: isRtlLocale(locale)
|
|
131
607
|
};
|
|
132
608
|
}
|
|
609
|
+
// Switch the active locale at runtime. No-op when the resolved tag matches
|
|
610
|
+
// the current locale and `stringOverrides` is unchanged. Subscribers get a
|
|
611
|
+
// single state notification with the new `locale` / `isRtl`.
|
|
612
|
+
setLocale(tag) {
|
|
613
|
+
const next = detectLocale(tag);
|
|
614
|
+
if (next === this.state.locale) return;
|
|
615
|
+
this.t = createTranslator(next, this.state.config.stringOverrides);
|
|
616
|
+
this.setState({ locale: next, isRtl: isRtlLocale(next) });
|
|
617
|
+
}
|
|
618
|
+
rebuildTranslator() {
|
|
619
|
+
this.t = createTranslator(
|
|
620
|
+
this.state.locale,
|
|
621
|
+
this.state.config.stringOverrides
|
|
622
|
+
);
|
|
623
|
+
}
|
|
133
624
|
subscribe(listener) {
|
|
134
625
|
this.listeners.add(listener);
|
|
135
626
|
return () => {
|
|
@@ -143,6 +634,18 @@ var CustomerHeroChat = class {
|
|
|
143
634
|
this.state = { ...this.state, ...partial };
|
|
144
635
|
this.notifyListeners();
|
|
145
636
|
}
|
|
637
|
+
// Mutate the last message in place and notify. Used during streaming so
|
|
638
|
+
// listeners see tokens land without allocating a new messages array per
|
|
639
|
+
// token. The array itself is still replaced so consumers using
|
|
640
|
+
// structural equality (React's useSyncExternalStore) see a new reference.
|
|
641
|
+
patchLastMessage(patch) {
|
|
642
|
+
const { messages } = this.state;
|
|
643
|
+
if (messages.length === 0) return;
|
|
644
|
+
const next = messages.slice();
|
|
645
|
+
const last = next[next.length - 1];
|
|
646
|
+
next[next.length - 1] = { ...last, ...patch };
|
|
647
|
+
this.setState({ messages: next });
|
|
648
|
+
}
|
|
146
649
|
notifyListeners() {
|
|
147
650
|
for (const listener of this.listeners) {
|
|
148
651
|
listener(this.state);
|
|
@@ -159,6 +662,7 @@ var CustomerHeroChat = class {
|
|
|
159
662
|
const fetched = await response.json();
|
|
160
663
|
const resolved = resolveConfig(this.userConfig, fetched);
|
|
161
664
|
this.setState({ config: resolved, configLoaded: true });
|
|
665
|
+
if (resolved.stringOverrides) this.rebuildTranslator();
|
|
162
666
|
} catch (error) {
|
|
163
667
|
const errorMsg = error instanceof Error ? error.message : "Failed to load widget config";
|
|
164
668
|
console.error("CustomerHero: Failed to fetch widget config", error);
|
|
@@ -186,68 +690,322 @@ var CustomerHeroChat = class {
|
|
|
186
690
|
return;
|
|
187
691
|
}
|
|
188
692
|
const data = await response.json();
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
693
|
+
const raw = data.messages ?? [];
|
|
694
|
+
const messages = raw.map((m) => ({
|
|
695
|
+
id: m.id,
|
|
696
|
+
role: m.role,
|
|
697
|
+
content: m.content,
|
|
698
|
+
...m.sources ? { sources: m.sources } : {},
|
|
699
|
+
...m.blocks ? { blocks: m.blocks } : {},
|
|
700
|
+
...m.suggestions ? { suggestions: m.suggestions } : {}
|
|
701
|
+
}));
|
|
702
|
+
const lastBotIndex = findLastIndex(
|
|
703
|
+
messages,
|
|
704
|
+
(m) => m.role === "bot" && !!m.suggestions?.length
|
|
195
705
|
);
|
|
706
|
+
for (let i = 0; i < messages.length; i++) {
|
|
707
|
+
if (i !== lastBotIndex && messages[i].suggestions) {
|
|
708
|
+
const { suggestions: _s, ...rest } = messages[i];
|
|
709
|
+
void _s;
|
|
710
|
+
messages[i] = rest;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
196
713
|
if (messages.length > 0) {
|
|
197
714
|
this.setState({ messages });
|
|
198
715
|
}
|
|
199
716
|
} catch {
|
|
200
717
|
}
|
|
201
718
|
}
|
|
202
|
-
async sendMessage(message) {
|
|
719
|
+
async sendMessage(message, options) {
|
|
203
720
|
const trimmed = message.trim();
|
|
721
|
+
const attachmentTokens = options?.attachmentTokens ?? [];
|
|
204
722
|
if (!trimmed || this.state.isLoading) return;
|
|
205
|
-
const userMsg = {
|
|
723
|
+
const userMsg = {
|
|
724
|
+
role: "user",
|
|
725
|
+
content: trimmed,
|
|
726
|
+
status: "sending"
|
|
727
|
+
};
|
|
728
|
+
const cleanedHistory = this.state.messages.map((m) => {
|
|
729
|
+
let next = m;
|
|
730
|
+
if (next.suggestions) next = stripSuggestions(next);
|
|
731
|
+
if (next.blocks?.some((b) => b.type === "action_confirmation")) {
|
|
732
|
+
next = stripActionConfirmationBlocks(next);
|
|
733
|
+
}
|
|
734
|
+
return next;
|
|
735
|
+
});
|
|
736
|
+
const userMsgIndex = cleanedHistory.length;
|
|
206
737
|
this.setState({
|
|
207
|
-
messages: [...
|
|
738
|
+
messages: [...cleanedHistory, userMsg],
|
|
208
739
|
isLoading: true,
|
|
209
740
|
error: null
|
|
210
741
|
});
|
|
211
|
-
const { chatbotId } = this.state.config;
|
|
212
|
-
|
|
742
|
+
const { chatbotId, apiBase } = this.state.config;
|
|
743
|
+
let botMessageCreated = false;
|
|
213
744
|
try {
|
|
214
745
|
const response = await fetch(`${apiBase}/api/chat/${chatbotId}`, {
|
|
215
746
|
method: "POST",
|
|
216
|
-
headers: {
|
|
747
|
+
headers: {
|
|
748
|
+
"Content-Type": "application/json",
|
|
749
|
+
Accept: "text/event-stream"
|
|
750
|
+
},
|
|
217
751
|
body: JSON.stringify({
|
|
218
752
|
message: trimmed,
|
|
219
753
|
...this.state.conversationId ? { conversationId: this.state.conversationId } : {},
|
|
220
|
-
...this.identityData ? { identity: this.identityData } : {}
|
|
754
|
+
...this.identityData ? { identity: this.identityData } : {},
|
|
755
|
+
...attachmentTokens.length > 0 ? { attachmentTokens } : {}
|
|
221
756
|
})
|
|
222
757
|
});
|
|
223
758
|
if (!response.ok) {
|
|
224
|
-
const
|
|
225
|
-
const errorMsg =
|
|
759
|
+
const data = await response.json().catch(() => null);
|
|
760
|
+
const errorMsg = data?.error ?? `Request failed: ${response.status}`;
|
|
226
761
|
throw new Error(errorMsg);
|
|
227
762
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
id: data.messageId,
|
|
231
|
-
role: "bot",
|
|
232
|
-
content: data.message
|
|
233
|
-
};
|
|
234
|
-
const conversationId = data.conversationId ?? this.state.conversationId;
|
|
235
|
-
if (conversationId) {
|
|
236
|
-
this.storage?.setItem(`ch_conv_${chatbotId}`, conversationId);
|
|
763
|
+
if (!response.body) {
|
|
764
|
+
throw new Error("Empty response body");
|
|
237
765
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
766
|
+
let fullContent = "";
|
|
767
|
+
let messageId;
|
|
768
|
+
for await (const evt of readSSEStream(response.body)) {
|
|
769
|
+
switch (evt.event) {
|
|
770
|
+
case "metadata": {
|
|
771
|
+
const meta = safeParse(evt.data);
|
|
772
|
+
if (meta?.conversationId) {
|
|
773
|
+
this.storage?.setItem(
|
|
774
|
+
`ch_conv_${chatbotId}`,
|
|
775
|
+
meta.conversationId
|
|
776
|
+
);
|
|
777
|
+
this.setState({ conversationId: meta.conversationId });
|
|
778
|
+
}
|
|
779
|
+
this.patchMessageAt(userMsgIndex, { status: "sent" });
|
|
780
|
+
if (meta?.messageId) {
|
|
781
|
+
messageId = meta.messageId;
|
|
782
|
+
}
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
case "token": {
|
|
786
|
+
const tok = safeParse(evt.data);
|
|
787
|
+
const text = tok?.text ?? "";
|
|
788
|
+
fullContent += text;
|
|
789
|
+
if (!botMessageCreated) {
|
|
790
|
+
const botMsg = {
|
|
791
|
+
id: messageId,
|
|
792
|
+
role: "bot",
|
|
793
|
+
content: fullContent,
|
|
794
|
+
streaming: true
|
|
795
|
+
};
|
|
796
|
+
this.setState({
|
|
797
|
+
messages: [...this.state.messages, botMsg]
|
|
798
|
+
});
|
|
799
|
+
botMessageCreated = true;
|
|
800
|
+
} else {
|
|
801
|
+
this.patchLastMessage({ content: fullContent });
|
|
802
|
+
}
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
case "sources": {
|
|
806
|
+
const payload = safeParse(evt.data);
|
|
807
|
+
if (payload?.sources?.length && botMessageCreated) {
|
|
808
|
+
this.patchLastMessage({ sources: payload.sources });
|
|
809
|
+
}
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
case "block": {
|
|
813
|
+
const payload = safeParse(evt.data);
|
|
814
|
+
if (payload?.block && botMessageCreated) {
|
|
815
|
+
const existing = this.state.messages.at(-1)?.blocks ?? [];
|
|
816
|
+
this.patchLastMessage({
|
|
817
|
+
blocks: [...existing, payload.block]
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
case "suggestions": {
|
|
823
|
+
const payload = safeParse(evt.data);
|
|
824
|
+
if (payload?.suggestions?.length && botMessageCreated) {
|
|
825
|
+
this.patchLastMessage({ suggestions: payload.suggestions });
|
|
826
|
+
}
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
case "done": {
|
|
830
|
+
if (botMessageCreated) {
|
|
831
|
+
this.patchLastMessage({
|
|
832
|
+
id: messageId,
|
|
833
|
+
streaming: false
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
break;
|
|
837
|
+
}
|
|
838
|
+
case "error": {
|
|
839
|
+
const payload = safeParse(evt.data);
|
|
840
|
+
throw new Error(payload?.error ?? "Stream failed");
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (!botMessageCreated && !fullContent) {
|
|
845
|
+
}
|
|
846
|
+
this.setState({ isLoading: false });
|
|
243
847
|
} catch (error) {
|
|
244
848
|
const errorMsg = error instanceof Error ? error.message : "Something went wrong";
|
|
849
|
+
if (botMessageCreated) {
|
|
850
|
+
this.patchLastMessage({ streaming: false });
|
|
851
|
+
}
|
|
852
|
+
const userStatus = this.state.messages[userMsgIndex]?.status;
|
|
853
|
+
if (userStatus !== "sent") {
|
|
854
|
+
this.patchMessageAt(userMsgIndex, { status: "failed" });
|
|
855
|
+
}
|
|
245
856
|
this.setState({
|
|
246
857
|
isLoading: false,
|
|
247
858
|
error: errorMsg
|
|
248
859
|
});
|
|
249
860
|
}
|
|
250
861
|
}
|
|
862
|
+
// Upload a screenshot blob to the public attachments endpoint and return
|
|
863
|
+
// the token the caller should pass to `sendMessage(text, { attachmentTokens })`.
|
|
864
|
+
async uploadAttachment(blob, options) {
|
|
865
|
+
const { chatbotId, apiBase } = this.state.config;
|
|
866
|
+
const filename = options?.filename ?? `screenshot.${pickExtension(blob.type)}`;
|
|
867
|
+
const form = new FormData();
|
|
868
|
+
form.append("file", blob, filename);
|
|
869
|
+
const response = await fetch(
|
|
870
|
+
`${apiBase}/api/chat/${chatbotId}/attachments`,
|
|
871
|
+
{ method: "POST", body: form }
|
|
872
|
+
);
|
|
873
|
+
if (!response.ok) {
|
|
874
|
+
const data = await response.json().catch(() => null);
|
|
875
|
+
const errorMsg = data?.error ?? `Upload failed: ${response.status}`;
|
|
876
|
+
throw new Error(errorMsg);
|
|
877
|
+
}
|
|
878
|
+
const json = await response.json();
|
|
879
|
+
return json;
|
|
880
|
+
}
|
|
881
|
+
approveAction(pendingId) {
|
|
882
|
+
return this.sendDecision(pendingId, "approve");
|
|
883
|
+
}
|
|
884
|
+
cancelAction(pendingId) {
|
|
885
|
+
return this.sendDecision(pendingId, "cancel");
|
|
886
|
+
}
|
|
887
|
+
// Locate the bot bubble that carries the action_confirmation block for
|
|
888
|
+
// `pendingId`, strip the block, mark the bubble streaming, then POST the
|
|
889
|
+
// decision and stream tokens back into the same bubble.
|
|
890
|
+
async sendDecision(pendingId, decision) {
|
|
891
|
+
const targetIndex = this.findActionConfirmationMessageIndex(pendingId);
|
|
892
|
+
if (targetIndex === -1) {
|
|
893
|
+
this.setState({ error: this.t("action_already_resolved") });
|
|
894
|
+
await this.loadHistory();
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
const messages = this.state.messages.slice();
|
|
898
|
+
const original = messages[targetIndex];
|
|
899
|
+
messages[targetIndex] = {
|
|
900
|
+
...stripActionConfirmationBlocks(original),
|
|
901
|
+
streaming: true
|
|
902
|
+
};
|
|
903
|
+
this.setState({ messages, error: null });
|
|
904
|
+
const { chatbotId, apiBase } = this.state.config;
|
|
905
|
+
const url = `${apiBase}/api/chat/${chatbotId}/tool-calls/${pendingId}/decision`;
|
|
906
|
+
try {
|
|
907
|
+
const response = await fetch(url, {
|
|
908
|
+
method: "POST",
|
|
909
|
+
headers: {
|
|
910
|
+
"Content-Type": "application/json",
|
|
911
|
+
Accept: "text/event-stream"
|
|
912
|
+
},
|
|
913
|
+
body: JSON.stringify({
|
|
914
|
+
decision,
|
|
915
|
+
...this.identityData ? { identity: this.identityData } : {}
|
|
916
|
+
})
|
|
917
|
+
});
|
|
918
|
+
if (!response.ok) {
|
|
919
|
+
const data = await response.json().catch(() => null);
|
|
920
|
+
const errorMsg = data?.error ?? `Request failed: ${response.status}`;
|
|
921
|
+
throw new Error(errorMsg);
|
|
922
|
+
}
|
|
923
|
+
if (!response.body) throw new Error("Empty response body");
|
|
924
|
+
for await (const evt of readSSEStream(response.body)) {
|
|
925
|
+
switch (evt.event) {
|
|
926
|
+
case "metadata": {
|
|
927
|
+
const meta = safeParse(evt.data);
|
|
928
|
+
if (meta?.conversationId) {
|
|
929
|
+
this.storage?.setItem(
|
|
930
|
+
`ch_conv_${chatbotId}`,
|
|
931
|
+
meta.conversationId
|
|
932
|
+
);
|
|
933
|
+
this.setState({ conversationId: meta.conversationId });
|
|
934
|
+
}
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
case "token": {
|
|
938
|
+
const tok = safeParse(evt.data);
|
|
939
|
+
const text = tok?.text ?? "";
|
|
940
|
+
if (text) {
|
|
941
|
+
this.appendToMessageAt(targetIndex, text);
|
|
942
|
+
}
|
|
943
|
+
break;
|
|
944
|
+
}
|
|
945
|
+
case "block": {
|
|
946
|
+
const payload = safeParse(evt.data);
|
|
947
|
+
if (payload?.block) {
|
|
948
|
+
this.appendBlockToMessageAt(targetIndex, payload.block);
|
|
949
|
+
}
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
case "done": {
|
|
953
|
+
this.patchMessageAt(targetIndex, { streaming: false });
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
case "error": {
|
|
957
|
+
const payload = safeParse(
|
|
958
|
+
evt.data
|
|
959
|
+
);
|
|
960
|
+
if (payload?.kind === "already_resolved") {
|
|
961
|
+
this.patchMessageAt(targetIndex, { streaming: false });
|
|
962
|
+
this.setState({ error: this.t("action_already_resolved") });
|
|
963
|
+
await this.loadHistory();
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
throw new Error(payload?.error ?? "Stream failed");
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
} catch (error) {
|
|
971
|
+
const errorMsg = error instanceof Error ? error.message : this.t("action_failed");
|
|
972
|
+
this.patchMessageAt(targetIndex, { streaming: false });
|
|
973
|
+
this.setState({ error: errorMsg });
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
findActionConfirmationMessageIndex(pendingId) {
|
|
977
|
+
const { messages } = this.state;
|
|
978
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
979
|
+
const blocks = messages[i].blocks;
|
|
980
|
+
if (blocks?.some(
|
|
981
|
+
(b) => b.type === "action_confirmation" && b.pendingToolCallId === pendingId
|
|
982
|
+
)) {
|
|
983
|
+
return i;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return -1;
|
|
987
|
+
}
|
|
988
|
+
patchMessageAt(index, patch) {
|
|
989
|
+
const messages = this.state.messages.slice();
|
|
990
|
+
if (!messages[index]) return;
|
|
991
|
+
messages[index] = { ...messages[index], ...patch };
|
|
992
|
+
this.setState({ messages });
|
|
993
|
+
}
|
|
994
|
+
appendToMessageAt(index, text) {
|
|
995
|
+
const messages = this.state.messages.slice();
|
|
996
|
+
const target = messages[index];
|
|
997
|
+
if (!target) return;
|
|
998
|
+
messages[index] = { ...target, content: target.content + text };
|
|
999
|
+
this.setState({ messages });
|
|
1000
|
+
}
|
|
1001
|
+
appendBlockToMessageAt(index, block) {
|
|
1002
|
+
const messages = this.state.messages.slice();
|
|
1003
|
+
const target = messages[index];
|
|
1004
|
+
if (!target) return;
|
|
1005
|
+
const existing = target.blocks ?? [];
|
|
1006
|
+
messages[index] = { ...target, blocks: [...existing, block] };
|
|
1007
|
+
this.setState({ messages });
|
|
1008
|
+
}
|
|
251
1009
|
async rateMessage(messageId, rating) {
|
|
252
1010
|
const { chatbotId, apiBase } = this.state.config;
|
|
253
1011
|
const { conversationId } = this.state;
|
|
@@ -317,8 +1075,184 @@ var CustomerHeroChat = class {
|
|
|
317
1075
|
});
|
|
318
1076
|
}
|
|
319
1077
|
};
|
|
1078
|
+
function safeParse(data) {
|
|
1079
|
+
try {
|
|
1080
|
+
return JSON.parse(data);
|
|
1081
|
+
} catch {
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
function stripSuggestions(message) {
|
|
1086
|
+
const { suggestions: _s, ...rest } = message;
|
|
1087
|
+
void _s;
|
|
1088
|
+
return rest;
|
|
1089
|
+
}
|
|
1090
|
+
function stripActionConfirmationBlocks(message) {
|
|
1091
|
+
if (!message.blocks?.length) return message;
|
|
1092
|
+
const blocks = message.blocks.filter((b) => b.type !== "action_confirmation");
|
|
1093
|
+
if (blocks.length === message.blocks.length) return message;
|
|
1094
|
+
if (blocks.length === 0) {
|
|
1095
|
+
const { blocks: _b, ...rest } = message;
|
|
1096
|
+
void _b;
|
|
1097
|
+
return rest;
|
|
1098
|
+
}
|
|
1099
|
+
return { ...message, blocks };
|
|
1100
|
+
}
|
|
1101
|
+
function findLastIndex(arr, pred) {
|
|
1102
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
1103
|
+
if (pred(arr[i])) return i;
|
|
1104
|
+
}
|
|
1105
|
+
return -1;
|
|
1106
|
+
}
|
|
1107
|
+
function pickExtension(mime) {
|
|
1108
|
+
if (mime === "image/png") return "png";
|
|
1109
|
+
if (mime === "image/webp") return "webp";
|
|
1110
|
+
return "jpg";
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// src/screenshot.ts
|
|
1114
|
+
var ScreenshotCancelled = class extends Error {
|
|
1115
|
+
constructor(message = "Screenshot cancelled") {
|
|
1116
|
+
super(message);
|
|
1117
|
+
this.name = "ScreenshotCancelled";
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
var ScreenshotUnavailable = class extends Error {
|
|
1121
|
+
constructor(message = "Screenshot unavailable") {
|
|
1122
|
+
super(message);
|
|
1123
|
+
this.name = "ScreenshotUnavailable";
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
var MAX_DIMENSION = 2048;
|
|
1127
|
+
var SIZE_BUDGET = 5 * 1024 * 1024;
|
|
1128
|
+
function canCaptureScreenshot() {
|
|
1129
|
+
if (typeof navigator === "undefined" || typeof window === "undefined") {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
if (!navigator.mediaDevices?.getDisplayMedia) return false;
|
|
1133
|
+
if (typeof window.matchMedia !== "function") return false;
|
|
1134
|
+
return window.matchMedia("(pointer: fine)").matches;
|
|
1135
|
+
}
|
|
1136
|
+
async function captureScreenshot() {
|
|
1137
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices) {
|
|
1138
|
+
throw new ScreenshotUnavailable("Screen capture not supported");
|
|
1139
|
+
}
|
|
1140
|
+
let stream;
|
|
1141
|
+
try {
|
|
1142
|
+
stream = await navigator.mediaDevices.getDisplayMedia({
|
|
1143
|
+
video: true,
|
|
1144
|
+
audio: false
|
|
1145
|
+
});
|
|
1146
|
+
} catch (e) {
|
|
1147
|
+
if (e instanceof Error && (e.name === "NotAllowedError" || e.name === "AbortError")) {
|
|
1148
|
+
throw new ScreenshotCancelled(e.message);
|
|
1149
|
+
}
|
|
1150
|
+
const msg = e instanceof Error ? e.message : "Screen capture failed";
|
|
1151
|
+
throw new ScreenshotUnavailable(msg);
|
|
1152
|
+
}
|
|
1153
|
+
try {
|
|
1154
|
+
const bitmap = await grabFrame(stream);
|
|
1155
|
+
return await encodeJpeg(bitmap);
|
|
1156
|
+
} finally {
|
|
1157
|
+
for (const track of stream.getTracks()) track.stop();
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
async function grabFrame(stream) {
|
|
1161
|
+
const [track] = stream.getVideoTracks();
|
|
1162
|
+
if (!track) throw new ScreenshotUnavailable("No video track");
|
|
1163
|
+
const ImageCaptureCtor = globalThis.ImageCapture;
|
|
1164
|
+
if (ImageCaptureCtor) {
|
|
1165
|
+
try {
|
|
1166
|
+
const cap = new ImageCaptureCtor(track);
|
|
1167
|
+
const bitmap = await cap.grabFrame();
|
|
1168
|
+
if (bitmap.width > 0 && bitmap.height > 0) return bitmap;
|
|
1169
|
+
} catch {
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return await grabFrameFromVideo(stream);
|
|
1173
|
+
}
|
|
1174
|
+
async function grabFrameFromVideo(stream) {
|
|
1175
|
+
const video = document.createElement("video");
|
|
1176
|
+
video.autoplay = true;
|
|
1177
|
+
video.muted = true;
|
|
1178
|
+
video.playsInline = true;
|
|
1179
|
+
video.srcObject = stream;
|
|
1180
|
+
try {
|
|
1181
|
+
await new Promise((resolve, reject) => {
|
|
1182
|
+
const onReady = () => {
|
|
1183
|
+
video.removeEventListener("loadedmetadata", onReady);
|
|
1184
|
+
resolve();
|
|
1185
|
+
};
|
|
1186
|
+
const onError = () => {
|
|
1187
|
+
video.removeEventListener("error", onError);
|
|
1188
|
+
reject(new ScreenshotUnavailable("Video element failed"));
|
|
1189
|
+
};
|
|
1190
|
+
video.addEventListener("loadedmetadata", onReady);
|
|
1191
|
+
video.addEventListener("error", onError);
|
|
1192
|
+
});
|
|
1193
|
+
await video.play().catch(() => {
|
|
1194
|
+
});
|
|
1195
|
+
const w = video.videoWidth;
|
|
1196
|
+
const h = video.videoHeight;
|
|
1197
|
+
if (w === 0 || h === 0) throw new ScreenshotUnavailable("Empty frame");
|
|
1198
|
+
const canvas = document.createElement("canvas");
|
|
1199
|
+
canvas.width = w;
|
|
1200
|
+
canvas.height = h;
|
|
1201
|
+
const ctx = canvas.getContext("2d");
|
|
1202
|
+
if (!ctx) throw new ScreenshotUnavailable("No 2D context");
|
|
1203
|
+
ctx.drawImage(video, 0, 0, w, h);
|
|
1204
|
+
return await createImageBitmap(canvas);
|
|
1205
|
+
} finally {
|
|
1206
|
+
video.srcObject = null;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
async function encodeJpeg(bitmap) {
|
|
1210
|
+
const { width, height } = scaleToFit(
|
|
1211
|
+
bitmap.width,
|
|
1212
|
+
bitmap.height,
|
|
1213
|
+
MAX_DIMENSION
|
|
1214
|
+
);
|
|
1215
|
+
const canvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(width, height) : Object.assign(document.createElement("canvas"), { width, height });
|
|
1216
|
+
const ctx = canvas.getContext("2d");
|
|
1217
|
+
if (!ctx) throw new ScreenshotUnavailable("No 2D context");
|
|
1218
|
+
ctx.drawImage(bitmap, 0, 0, width, height);
|
|
1219
|
+
bitmap.close?.();
|
|
1220
|
+
const blob = await canvasToBlob(canvas, 0.85);
|
|
1221
|
+
if (blob.size <= SIZE_BUDGET) return blob;
|
|
1222
|
+
const slim = await canvasToBlob(canvas, 0.7);
|
|
1223
|
+
return slim;
|
|
1224
|
+
}
|
|
1225
|
+
function scaleToFit(w, h, max) {
|
|
1226
|
+
if (w <= max && h <= max) return { width: w, height: h };
|
|
1227
|
+
const ratio = w >= h ? max / w : max / h;
|
|
1228
|
+
return { width: Math.round(w * ratio), height: Math.round(h * ratio) };
|
|
1229
|
+
}
|
|
1230
|
+
async function canvasToBlob(canvas, quality) {
|
|
1231
|
+
if ("convertToBlob" in canvas) {
|
|
1232
|
+
return await canvas.convertToBlob({
|
|
1233
|
+
type: "image/jpeg",
|
|
1234
|
+
quality
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
return await new Promise((resolve, reject) => {
|
|
1238
|
+
canvas.toBlob(
|
|
1239
|
+
(b) => b ? resolve(b) : reject(new ScreenshotUnavailable("Encode failed")),
|
|
1240
|
+
"image/jpeg",
|
|
1241
|
+
quality
|
|
1242
|
+
);
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
320
1245
|
// Annotate the CommonJS export names for ESM import in node:
|
|
321
1246
|
0 && (module.exports = {
|
|
322
1247
|
CustomerHeroChat,
|
|
323
|
-
DEFAULTS
|
|
1248
|
+
DEFAULTS,
|
|
1249
|
+
SUPPORTED_LOCALES,
|
|
1250
|
+
ScreenshotCancelled,
|
|
1251
|
+
ScreenshotUnavailable,
|
|
1252
|
+
canCaptureScreenshot,
|
|
1253
|
+
captureScreenshot,
|
|
1254
|
+
createTranslator,
|
|
1255
|
+
detectLocale,
|
|
1256
|
+
isRtlLocale,
|
|
1257
|
+
resolveLocale
|
|
324
1258
|
});
|