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