@erlancarreira/evolution-chat 0.1.2 → 0.1.4
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/README.md +69 -13
- package/api/index.cjs +41 -1
- package/api/index.cjs.map +1 -1
- package/api/index.d.cts +56 -30
- package/api/index.d.ts +56 -30
- package/api/index.js +41 -1
- package/api/index.js.map +1 -1
- package/bridge/index.cjs +90 -0
- package/bridge/index.cjs.map +1 -1
- package/bridge/index.d.cts +37 -0
- package/bridge/index.d.ts +37 -0
- package/bridge/index.js +90 -0
- package/bridge/index.js.map +1 -1
- package/next/index.d.cts +37 -0
- package/next/index.d.ts +37 -0
- package/package.json +1 -1
- package/transports/supabase/index.d.cts +8 -0
- package/transports/supabase/index.d.ts +8 -0
- package/widget/index.cjs +115 -28
- package/widget/index.cjs.map +1 -1
- package/widget/index.d.cts +19 -3
- package/widget/index.d.ts +19 -3
- package/widget/index.js +115 -28
- package/widget/index.js.map +1 -1
- package/widget/styles.css +33 -0
- package/widget/styles.css.map +1 -1
- package/widget-embed/evolution-chat.iife.js +113 -27
|
@@ -7307,7 +7307,8 @@ var EvolutionChatEmbed = (() => {
|
|
|
7307
7307
|
retry: "Tentar novamente",
|
|
7308
7308
|
sessionClosed: "Esta conversa foi encerrada. Abra uma nova para continuar.",
|
|
7309
7309
|
newConversation: "Iniciar nova conversa",
|
|
7310
|
-
poweredBy: "Powered by"
|
|
7310
|
+
poweredBy: "Powered by",
|
|
7311
|
+
typing: "digitando\u2026"
|
|
7311
7312
|
};
|
|
7312
7313
|
var en = {
|
|
7313
7314
|
openChat: "Open chat",
|
|
@@ -7324,7 +7325,8 @@ var EvolutionChatEmbed = (() => {
|
|
|
7324
7325
|
retry: "Try again",
|
|
7325
7326
|
sessionClosed: "This conversation was closed. Start a new one to continue.",
|
|
7326
7327
|
newConversation: "Start a new conversation",
|
|
7327
|
-
poweredBy: "Powered by"
|
|
7328
|
+
poweredBy: "Powered by",
|
|
7329
|
+
typing: "typing\u2026"
|
|
7328
7330
|
};
|
|
7329
7331
|
var es = {
|
|
7330
7332
|
openChat: "Abrir chat",
|
|
@@ -7341,7 +7343,8 @@ var EvolutionChatEmbed = (() => {
|
|
|
7341
7343
|
retry: "Reintentar",
|
|
7342
7344
|
sessionClosed: "Esta conversaci\xF3n fue cerrada. Abre una nueva para continuar.",
|
|
7343
7345
|
newConversation: "Iniciar nueva conversaci\xF3n",
|
|
7344
|
-
poweredBy: "Powered by"
|
|
7346
|
+
poweredBy: "Powered by",
|
|
7347
|
+
typing: "escribiendo\u2026"
|
|
7345
7348
|
};
|
|
7346
7349
|
var dictionaries = { pt, en, es };
|
|
7347
7350
|
function t(locale, key, overrides) {
|
|
@@ -7590,6 +7593,31 @@ var EvolutionChatEmbed = (() => {
|
|
|
7590
7593
|
.ecw-send:disabled { opacity: 0.55; cursor: not-allowed; }
|
|
7591
7594
|
.ecw-send svg { width: 18px; height: 18px; }
|
|
7592
7595
|
|
|
7596
|
+
/* \u2500\u2500 indicador "digitando\u2026" (3 pontinhos) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
7597
|
+
.ecw-typing {
|
|
7598
|
+
display: inline-flex;
|
|
7599
|
+
align-items: center;
|
|
7600
|
+
gap: 4px;
|
|
7601
|
+
padding: 10px 14px;
|
|
7602
|
+
border-radius: 12px;
|
|
7603
|
+
border-bottom-left-radius: 4px;
|
|
7604
|
+
background: var(--ecw-surface);
|
|
7605
|
+
border: 1px solid var(--ecw-border);
|
|
7606
|
+
}
|
|
7607
|
+
.ecw-typing-dot {
|
|
7608
|
+
width: 7px;
|
|
7609
|
+
height: 7px;
|
|
7610
|
+
border-radius: 50%;
|
|
7611
|
+
background: var(--ecw-muted);
|
|
7612
|
+
animation: ecw-typing-bounce 1.2s infinite ease-in-out;
|
|
7613
|
+
}
|
|
7614
|
+
.ecw-typing-dot:nth-child(2) { animation-delay: 0.18s; }
|
|
7615
|
+
.ecw-typing-dot:nth-child(3) { animation-delay: 0.36s; }
|
|
7616
|
+
@keyframes ecw-typing-bounce {
|
|
7617
|
+
0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
|
|
7618
|
+
30% { transform: translateY(-4px); opacity: 1; }
|
|
7619
|
+
}
|
|
7620
|
+
|
|
7593
7621
|
/* \u2500\u2500 sess\xE3o encerrada / falha \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
7594
7622
|
.ecw-closed { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--ecw-border); background: var(--ecw-surface); }
|
|
7595
7623
|
|
|
@@ -7654,17 +7682,23 @@ var EvolutionChatEmbed = (() => {
|
|
|
7654
7682
|
messages: [],
|
|
7655
7683
|
unread: 0,
|
|
7656
7684
|
sending: false,
|
|
7657
|
-
error: null
|
|
7685
|
+
error: null,
|
|
7686
|
+
ownerTyping: false,
|
|
7687
|
+
visitorTyping: false
|
|
7658
7688
|
};
|
|
7659
7689
|
function withMessage(state, message) {
|
|
7660
7690
|
const index = state.messages.findIndex((m) => m.id === message.id);
|
|
7691
|
+
let messages;
|
|
7661
7692
|
if (index >= 0) {
|
|
7662
|
-
|
|
7693
|
+
messages = [...state.messages];
|
|
7663
7694
|
messages[index] = message;
|
|
7664
|
-
|
|
7695
|
+
} else {
|
|
7696
|
+
messages = [...state.messages, message];
|
|
7665
7697
|
}
|
|
7666
7698
|
const bump = state.open || message.direction !== "owner" ? 0 : 1;
|
|
7667
|
-
|
|
7699
|
+
const next = { ...state, messages, unread: state.unread + bump };
|
|
7700
|
+
if (message.direction === "owner") next.ownerTyping = false;
|
|
7701
|
+
return next;
|
|
7668
7702
|
}
|
|
7669
7703
|
function chatReducer(state, action) {
|
|
7670
7704
|
switch (action.type) {
|
|
@@ -7728,6 +7762,8 @@ var EvolutionChatEmbed = (() => {
|
|
|
7728
7762
|
return { ...state, sending: action.value };
|
|
7729
7763
|
case "error":
|
|
7730
7764
|
return { ...state, error: action.value };
|
|
7765
|
+
case "typing":
|
|
7766
|
+
return action.from === "owner" ? state.ownerTyping === action.isTyping ? state : { ...state, ownerTyping: action.isTyping } : state.visitorTyping === action.isTyping ? state : { ...state, visitorTyping: action.isTyping };
|
|
7731
7767
|
}
|
|
7732
7768
|
}
|
|
7733
7769
|
function normalizePhone(value) {
|
|
@@ -7798,8 +7834,9 @@ var EvolutionChatEmbed = (() => {
|
|
|
7798
7834
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7799
7835
|
};
|
|
7800
7836
|
}
|
|
7801
|
-
function useChat({ endpoint, realtime }) {
|
|
7837
|
+
function useChat({ endpoint, realtime, typingEndpoint }) {
|
|
7802
7838
|
const [state, dispatch] = (0, import_react.useReducer)(chatReducer, initialState);
|
|
7839
|
+
const typingUrl = typingEndpoint ?? `${endpoint.replace(/\/+$/, "")}/typing`;
|
|
7803
7840
|
const stateRef = (0, import_react.useRef)(state);
|
|
7804
7841
|
(0, import_react.useEffect)(() => {
|
|
7805
7842
|
stateRef.current = state;
|
|
@@ -7854,6 +7891,8 @@ var EvolutionChatEmbed = (() => {
|
|
|
7854
7891
|
dispatch({ type: "append", message: e.message });
|
|
7855
7892
|
} else if (e.type === "session" && e.status !== void 0) {
|
|
7856
7893
|
dispatch({ type: "session-status", status: e.status });
|
|
7894
|
+
} else if (e.type === "typing" && e.from !== void 0) {
|
|
7895
|
+
dispatch({ type: "typing", from: e.from, isTyping: e.isTyping });
|
|
7857
7896
|
}
|
|
7858
7897
|
}, []);
|
|
7859
7898
|
(0, import_react.useEffect)(() => {
|
|
@@ -7960,6 +7999,21 @@ var EvolutionChatEmbed = (() => {
|
|
|
7960
7999
|
clearStoredSession();
|
|
7961
8000
|
dispatch({ type: "reset-form" });
|
|
7962
8001
|
}, []);
|
|
8002
|
+
const notifyTyping = (0, import_react.useCallback)(
|
|
8003
|
+
async (isTyping) => {
|
|
8004
|
+
const token = stateRef.current.token;
|
|
8005
|
+
if (token === null) return;
|
|
8006
|
+
try {
|
|
8007
|
+
await fetch(typingUrl, {
|
|
8008
|
+
method: "POST",
|
|
8009
|
+
headers: { "content-type": "application/json" },
|
|
8010
|
+
body: JSON.stringify({ token, isTyping })
|
|
8011
|
+
});
|
|
8012
|
+
} catch {
|
|
8013
|
+
}
|
|
8014
|
+
},
|
|
8015
|
+
[typingUrl]
|
|
8016
|
+
);
|
|
7963
8017
|
const submitForm = (0, import_react.useCallback)(
|
|
7964
8018
|
async ({ name, phone, message, honeypot }) => {
|
|
7965
8019
|
if (honeypot.trim() !== "") return;
|
|
@@ -8007,7 +8061,7 @@ var EvolutionChatEmbed = (() => {
|
|
|
8007
8061
|
},
|
|
8008
8062
|
[endpoint]
|
|
8009
8063
|
);
|
|
8010
|
-
return { state, openPanel, closePanel, togglePanel, submitForm, sendMessage, retryMessage, startNewConversation };
|
|
8064
|
+
return { state, openPanel, closePanel, togglePanel, submitForm, sendMessage, retryMessage, startNewConversation, notifyTyping };
|
|
8011
8065
|
}
|
|
8012
8066
|
|
|
8013
8067
|
// src/widget/chat-widget.tsx
|
|
@@ -8031,6 +8085,14 @@ var EvolutionChatEmbed = (() => {
|
|
|
8031
8085
|
const glyph = status === "pending" ? "\u2713" : status === "sent" ? "\u2713\u2713" : "\u26A0";
|
|
8032
8086
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: status === "failed" ? "ecw-status ecw-status--failed" : "ecw-status", "aria-hidden": "true", children: glyph });
|
|
8033
8087
|
}
|
|
8088
|
+
function TypingIndicator({ label }) {
|
|
8089
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "ecw-item ecw-item--owner", "aria-live": "polite", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ecw-typing", role: "status", "aria-label": label, children: [
|
|
8090
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ecw-typing-dot" }),
|
|
8091
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ecw-typing-dot" }),
|
|
8092
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ecw-typing-dot" }),
|
|
8093
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ecw-sr-only", children: label })
|
|
8094
|
+
] }) });
|
|
8095
|
+
}
|
|
8034
8096
|
function PreChatForm({ welcome, tr, error, sending, firstFieldRef, onSubmit }) {
|
|
8035
8097
|
const uid = (0, import_react2.useId)();
|
|
8036
8098
|
const [name, setName] = (0, import_react2.useState)("");
|
|
@@ -8130,29 +8192,53 @@ var EvolutionChatEmbed = (() => {
|
|
|
8130
8192
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ecw-notice", children: tr("privacyNote") })
|
|
8131
8193
|
] });
|
|
8132
8194
|
}
|
|
8133
|
-
function ChatPanel({ messages, canCompose, sending, locale, tr, listRef, firstFieldRef, onSend, onRetry, onNewConversation }) {
|
|
8195
|
+
function ChatPanel({ messages, canCompose, sending, locale, tr, listRef, firstFieldRef, ownerTyping, onTyping, onSend, onRetry, onNewConversation }) {
|
|
8134
8196
|
const uid = (0, import_react2.useId)();
|
|
8135
8197
|
const [draft, setDraft] = (0, import_react2.useState)("");
|
|
8198
|
+
const typingTimer = (0, import_react2.useRef)(null);
|
|
8199
|
+
(0, import_react2.useEffect)(() => {
|
|
8200
|
+
return () => {
|
|
8201
|
+
if (typingTimer.current !== null) clearTimeout(typingTimer.current);
|
|
8202
|
+
};
|
|
8203
|
+
}, []);
|
|
8204
|
+
const handleDraftChange = (e) => {
|
|
8205
|
+
setDraft(e.target.value);
|
|
8206
|
+
if (!canCompose) return;
|
|
8207
|
+
void onTyping(true);
|
|
8208
|
+
if (typingTimer.current !== null) clearTimeout(typingTimer.current);
|
|
8209
|
+
typingTimer.current = setTimeout(() => {
|
|
8210
|
+
void onTyping(false);
|
|
8211
|
+
typingTimer.current = null;
|
|
8212
|
+
}, 2500);
|
|
8213
|
+
};
|
|
8136
8214
|
const submit = (e) => {
|
|
8137
8215
|
e.preventDefault();
|
|
8138
8216
|
const text = draft.trim();
|
|
8139
8217
|
if (text === "" || !canCompose) return;
|
|
8218
|
+
if (typingTimer.current !== null) {
|
|
8219
|
+
clearTimeout(typingTimer.current);
|
|
8220
|
+
typingTimer.current = null;
|
|
8221
|
+
}
|
|
8222
|
+
void onTyping(false);
|
|
8140
8223
|
setDraft("");
|
|
8141
8224
|
void onSend(text);
|
|
8142
8225
|
};
|
|
8143
8226
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
8144
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
8145
|
-
|
|
8146
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `ecw-
|
|
8147
|
-
|
|
8148
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8227
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { ref: listRef, className: "ecw-list", children: [
|
|
8228
|
+
messages.map(
|
|
8229
|
+
(m) => m.direction === "system" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "ecw-item ecw-item--system", role: "status", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ecw-system", children: m.body }) }, m.id) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: `ecw-item ecw-item--${m.direction}`, children: [
|
|
8230
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `ecw-bubble ecw-bubble--${m.direction}`, children: m.body }),
|
|
8231
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ecw-meta", children: [
|
|
8232
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("time", { dateTime: m.createdAt, children: formatTime(m.createdAt, locale) }),
|
|
8233
|
+
m.direction === "visitor" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusMark, { status: m.status }),
|
|
8234
|
+
m.status === "failed" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "ecw-retry", onClick: () => {
|
|
8235
|
+
void onRetry(m.id);
|
|
8236
|
+
}, children: tr("retry") })
|
|
8237
|
+
] })
|
|
8238
|
+
] }, m.id)
|
|
8239
|
+
),
|
|
8240
|
+
ownerTyping && canCompose && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TypingIndicator, { label: tr("typing") })
|
|
8241
|
+
] }),
|
|
8156
8242
|
!canCompose && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ecw-closed", children: [
|
|
8157
8243
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ecw-notice", role: "status", children: tr("sessionClosed") }),
|
|
8158
8244
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "ecw-submit", onClick: onNewConversation, children: tr("newConversation") })
|
|
@@ -8169,9 +8255,7 @@ var EvolutionChatEmbed = (() => {
|
|
|
8169
8255
|
autoComplete: "off",
|
|
8170
8256
|
placeholder: tr("message"),
|
|
8171
8257
|
value: draft,
|
|
8172
|
-
onChange:
|
|
8173
|
-
setDraft(e.target.value);
|
|
8174
|
-
},
|
|
8258
|
+
onChange: handleDraftChange,
|
|
8175
8259
|
disabled: !canCompose
|
|
8176
8260
|
}
|
|
8177
8261
|
),
|
|
@@ -8183,7 +8267,7 @@ var EvolutionChatEmbed = (() => {
|
|
|
8183
8267
|
const { endpoint, locale, welcome, projectName, realtime, labels } = props;
|
|
8184
8268
|
const accentColor = props.accentColor ?? DEFAULT_ACCENT;
|
|
8185
8269
|
const tr = (0, import_react2.useCallback)((key) => t(locale, key, labels), [locale, labels]);
|
|
8186
|
-
const { state, closePanel, togglePanel, submitForm, sendMessage, retryMessage, startNewConversation } = useChat({ endpoint, realtime });
|
|
8270
|
+
const { state, closePanel, togglePanel, submitForm, sendMessage, retryMessage, startNewConversation, notifyTyping } = useChat({ endpoint, realtime });
|
|
8187
8271
|
const bubbleRef = (0, import_react2.useRef)(null);
|
|
8188
8272
|
const listRef = (0, import_react2.useRef)(null);
|
|
8189
8273
|
const firstFieldRef = (0, import_react2.useRef)(null);
|
|
@@ -8208,7 +8292,7 @@ var EvolutionChatEmbed = (() => {
|
|
|
8208
8292
|
(0, import_react2.useEffect)(() => {
|
|
8209
8293
|
const list = listRef.current;
|
|
8210
8294
|
if (list !== null) list.scrollTop = list.scrollHeight;
|
|
8211
|
-
}, [state.messages, state.open]);
|
|
8295
|
+
}, [state.messages, state.ownerTyping, state.open]);
|
|
8212
8296
|
const handleClose = (0, import_react2.useCallback)(() => {
|
|
8213
8297
|
closePanel();
|
|
8214
8298
|
bubbleRef.current?.focus();
|
|
@@ -8251,6 +8335,8 @@ var EvolutionChatEmbed = (() => {
|
|
|
8251
8335
|
tr,
|
|
8252
8336
|
listRef,
|
|
8253
8337
|
firstFieldRef,
|
|
8338
|
+
ownerTyping: state.ownerTyping,
|
|
8339
|
+
onTyping: notifyTyping,
|
|
8254
8340
|
onSend: sendMessage,
|
|
8255
8341
|
onRetry: retryMessage,
|
|
8256
8342
|
onNewConversation: startNewConversation
|