rowsky-chatbot-widget 1.0.10 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chatbot.mjs +546 -38
- package/dist/chatbot.umd.js +1 -1
- package/package.json +1 -1
package/dist/chatbot.mjs
CHANGED
|
@@ -37,9 +37,254 @@ const createWidgetAPI = (baseUrl) => {
|
|
|
37
37
|
captureLead: (token, leadData) => request("POST", "/widget/lead", leadData, token)
|
|
38
38
|
};
|
|
39
39
|
};
|
|
40
|
+
const FLOW_STATE = {
|
|
41
|
+
IDLE: "idle",
|
|
42
|
+
RUNNING: "running",
|
|
43
|
+
WAITING_INPUT: "waiting_input",
|
|
44
|
+
WAITING_OPTION: "waiting_option",
|
|
45
|
+
AI_MODE: "ai_mode"
|
|
46
|
+
};
|
|
47
|
+
const createFlowEngine = (options) => {
|
|
48
|
+
const {
|
|
49
|
+
flows = [],
|
|
50
|
+
onMessage,
|
|
51
|
+
onOptions,
|
|
52
|
+
onInput,
|
|
53
|
+
onTyping,
|
|
54
|
+
onFlowEnd,
|
|
55
|
+
onLeadCapture,
|
|
56
|
+
onVariableSet
|
|
57
|
+
} = options;
|
|
58
|
+
let state = FLOW_STATE.IDLE;
|
|
59
|
+
let currentStepIndex = 0;
|
|
60
|
+
let currentFlow = null;
|
|
61
|
+
let variables = {};
|
|
62
|
+
let flowStack = [];
|
|
63
|
+
let waitingResolver = null;
|
|
64
|
+
let isDestroyed = false;
|
|
65
|
+
const delay = (ms) => new Promise((resolve) => {
|
|
66
|
+
if (isDestroyed) return resolve();
|
|
67
|
+
setTimeout(resolve, Math.max(0, ms));
|
|
68
|
+
});
|
|
69
|
+
const interpolate = (text) => {
|
|
70
|
+
if (!text || typeof text !== "string") return text || "";
|
|
71
|
+
return text.replace(/\{\{(\w+)\}\}/g, (_, key) => variables[key] || `{{${key}}}`);
|
|
72
|
+
};
|
|
73
|
+
const resolveFlow = (flowId) => {
|
|
74
|
+
if (!flowId) return null;
|
|
75
|
+
return flows.find(
|
|
76
|
+
(f) => String(f.id) === String(flowId) || f.name === flowId
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
const getActiveSteps = () => (currentFlow == null ? void 0 : currentFlow.steps) || [];
|
|
80
|
+
const selectInitialFlow = () => {
|
|
81
|
+
const immediate = flows.find((f) => {
|
|
82
|
+
const trigger = (f.trigger || "").toLowerCase();
|
|
83
|
+
return trigger.includes("abrir") && (trigger.includes("site") || trigger.includes("imediat"));
|
|
84
|
+
});
|
|
85
|
+
return immediate || flows[0] || null;
|
|
86
|
+
};
|
|
87
|
+
const executeStep = async (step) => {
|
|
88
|
+
if (isDestroyed) return;
|
|
89
|
+
const stepDelay = (step.delay || 0) * 1e3;
|
|
90
|
+
if (stepDelay > 0 || step.type === "message") {
|
|
91
|
+
onTyping == null ? void 0 : onTyping(true);
|
|
92
|
+
await delay(stepDelay > 0 ? stepDelay : randomTypingDelay(step));
|
|
93
|
+
if (isDestroyed) return;
|
|
94
|
+
onTyping == null ? void 0 : onTyping(false);
|
|
95
|
+
}
|
|
96
|
+
switch (step.type) {
|
|
97
|
+
case "message":
|
|
98
|
+
case "text":
|
|
99
|
+
await handleMessageStep(step);
|
|
100
|
+
break;
|
|
101
|
+
case "options":
|
|
102
|
+
case "question":
|
|
103
|
+
await handleOptionsStep(step);
|
|
104
|
+
break;
|
|
105
|
+
case "input":
|
|
106
|
+
case "lead_capture":
|
|
107
|
+
await handleInputStep(step);
|
|
108
|
+
break;
|
|
109
|
+
case "subflow":
|
|
110
|
+
await handleSubflowStep(step);
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
console.warn(`[FlowEngine] Tipo de passo desconhecido: ${step.type}`);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const randomTypingDelay = (step) => {
|
|
118
|
+
const text = step.content || step.title || "";
|
|
119
|
+
const baseDelay = 500;
|
|
120
|
+
const perChar = 18;
|
|
121
|
+
const calculated = baseDelay + text.length * perChar;
|
|
122
|
+
return Math.min(calculated, 2500);
|
|
123
|
+
};
|
|
124
|
+
const handleMessageStep = async (step) => {
|
|
125
|
+
const content = interpolate(step.content || step.title || "");
|
|
126
|
+
onMessage == null ? void 0 : onMessage(content);
|
|
127
|
+
};
|
|
128
|
+
const handleOptionsStep = (step) => {
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
if (isDestroyed) return resolve();
|
|
131
|
+
const content = interpolate(step.content || "");
|
|
132
|
+
if (content) {
|
|
133
|
+
onMessage == null ? void 0 : onMessage(content);
|
|
134
|
+
}
|
|
135
|
+
state = FLOW_STATE.WAITING_OPTION;
|
|
136
|
+
const opts = (step.options || []).map(
|
|
137
|
+
(opt) => typeof opt === "string" ? { label: opt, value: opt } : opt
|
|
138
|
+
);
|
|
139
|
+
waitingResolver = (selectedValue) => {
|
|
140
|
+
const varKey = step.variableKey || `option_step_${step.id}`;
|
|
141
|
+
variables[varKey] = selectedValue;
|
|
142
|
+
onVariableSet == null ? void 0 : onVariableSet(varKey, selectedValue);
|
|
143
|
+
state = FLOW_STATE.RUNNING;
|
|
144
|
+
resolve();
|
|
145
|
+
};
|
|
146
|
+
onOptions == null ? void 0 : onOptions(opts, (selected) => {
|
|
147
|
+
if (waitingResolver) {
|
|
148
|
+
waitingResolver(selected);
|
|
149
|
+
waitingResolver = null;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
const handleInputStep = (step) => {
|
|
155
|
+
return new Promise((resolve) => {
|
|
156
|
+
if (isDestroyed) return resolve();
|
|
157
|
+
const content = interpolate(step.content || "");
|
|
158
|
+
if (content) {
|
|
159
|
+
onMessage == null ? void 0 : onMessage(content);
|
|
160
|
+
}
|
|
161
|
+
state = FLOW_STATE.WAITING_INPUT;
|
|
162
|
+
const inputs = (step.inputs || []).map((inp) => {
|
|
163
|
+
var _a;
|
|
164
|
+
return {
|
|
165
|
+
type: inp.type || "text",
|
|
166
|
+
label: inp.label || "",
|
|
167
|
+
required: inp.required !== false,
|
|
168
|
+
placeholder: inp.placeholder || "",
|
|
169
|
+
key: inp.key || ((_a = inp.label) == null ? void 0 : _a.toLowerCase().replace(/[^a-z0-9_]/g, "_")) || `field_${Math.random().toString(36).slice(2, 6)}`
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
waitingResolver = (formData) => {
|
|
173
|
+
for (const [key, value] of Object.entries(formData)) {
|
|
174
|
+
variables[key] = value;
|
|
175
|
+
onVariableSet == null ? void 0 : onVariableSet(key, value);
|
|
176
|
+
}
|
|
177
|
+
const emailField = inputs.find(
|
|
178
|
+
(inp) => inp.type === "email" || inp.key === "email" || inp.key === "e_mail"
|
|
179
|
+
);
|
|
180
|
+
if (emailField && formData[emailField.key]) {
|
|
181
|
+
onLeadCapture == null ? void 0 : onLeadCapture({
|
|
182
|
+
name: formData.name || formData.nome || variables.name || variables.nome || "",
|
|
183
|
+
email: formData[emailField.key],
|
|
184
|
+
phone: formData.phone || formData.telefone || variables.phone || variables.telefone || "",
|
|
185
|
+
metadata: { ...variables }
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
state = FLOW_STATE.RUNNING;
|
|
189
|
+
resolve();
|
|
190
|
+
};
|
|
191
|
+
onInput == null ? void 0 : onInput(inputs, (formData) => {
|
|
192
|
+
if (waitingResolver) {
|
|
193
|
+
waitingResolver(formData);
|
|
194
|
+
waitingResolver = null;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
const handleSubflowStep = async (step) => {
|
|
200
|
+
const subflow = resolveFlow(step.flowId);
|
|
201
|
+
if (!subflow) {
|
|
202
|
+
console.warn(`[FlowEngine] Subflow não encontrado: ${step.flowId}`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (step.mappings && typeof step.mappings === "object") {
|
|
206
|
+
for (const [subKey, parentKey] of Object.entries(step.mappings)) {
|
|
207
|
+
if (variables[parentKey] !== void 0) {
|
|
208
|
+
variables[subKey] = variables[parentKey];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
flowStack.push({
|
|
213
|
+
flow: currentFlow,
|
|
214
|
+
stepIndex: currentStepIndex
|
|
215
|
+
});
|
|
216
|
+
currentFlow = subflow;
|
|
217
|
+
currentStepIndex = 0;
|
|
218
|
+
await runCurrentFlow();
|
|
219
|
+
const parent = flowStack.pop();
|
|
220
|
+
if (parent) {
|
|
221
|
+
currentFlow = parent.flow;
|
|
222
|
+
currentStepIndex = parent.stepIndex;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const runCurrentFlow = async () => {
|
|
226
|
+
const steps = getActiveSteps();
|
|
227
|
+
while (currentStepIndex < steps.length) {
|
|
228
|
+
if (isDestroyed) return;
|
|
229
|
+
const step = steps[currentStepIndex];
|
|
230
|
+
await executeStep(step);
|
|
231
|
+
currentStepIndex++;
|
|
232
|
+
if (currentStepIndex < steps.length) {
|
|
233
|
+
await delay(350);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const start = async () => {
|
|
238
|
+
currentFlow = selectInitialFlow();
|
|
239
|
+
if (!currentFlow) {
|
|
240
|
+
state = FLOW_STATE.AI_MODE;
|
|
241
|
+
onFlowEnd == null ? void 0 : onFlowEnd();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
currentStepIndex = 0;
|
|
245
|
+
state = FLOW_STATE.RUNNING;
|
|
246
|
+
variables = {};
|
|
247
|
+
await runCurrentFlow();
|
|
248
|
+
if (!isDestroyed) {
|
|
249
|
+
state = FLOW_STATE.AI_MODE;
|
|
250
|
+
onFlowEnd == null ? void 0 : onFlowEnd();
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const handleUserMessage = (message) => {
|
|
254
|
+
if (state === FLOW_STATE.WAITING_OPTION) {
|
|
255
|
+
if (waitingResolver) {
|
|
256
|
+
waitingResolver(message);
|
|
257
|
+
waitingResolver = null;
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
if (state === FLOW_STATE.WAITING_INPUT) {
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
};
|
|
266
|
+
const getState = () => state;
|
|
267
|
+
const getVariables = () => ({ ...variables });
|
|
268
|
+
const isAIMode = () => state === FLOW_STATE.AI_MODE;
|
|
269
|
+
const isWaiting = () => state === FLOW_STATE.WAITING_INPUT || state === FLOW_STATE.WAITING_OPTION;
|
|
270
|
+
const destroy = () => {
|
|
271
|
+
isDestroyed = true;
|
|
272
|
+
waitingResolver = null;
|
|
273
|
+
flowStack = [];
|
|
274
|
+
};
|
|
275
|
+
return {
|
|
276
|
+
start,
|
|
277
|
+
handleUserMessage,
|
|
278
|
+
getState,
|
|
279
|
+
getVariables,
|
|
280
|
+
isAIMode,
|
|
281
|
+
isWaiting,
|
|
282
|
+
destroy
|
|
283
|
+
};
|
|
284
|
+
};
|
|
40
285
|
const STYLE_ID = "fluxia-widget-styles";
|
|
41
286
|
const CRITICAL_CSS = `
|
|
42
|
-
#fluxia-widget-root{all:initial;font-family:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-size:14px;line-height:1.5;color:#1f2937;--fw-header:#0f172a;--fw-white:#fff;--fw-green:#22c55e;--fw-red:#ef4444;--fw-red-dark:#dc2626;--fw-text:#1f2937;--fw-gray-100:#f3f4f6;--fw-gray-200:#e5e7eb;--fw-gray-300:#d1d5db;--fw-gray-bg:#fafbfc;--fw-radius:20px;--fw-shadow:0 20px 60px rgba(0,0,0,.25)}
|
|
287
|
+
#fluxia-widget-root{all:initial;font-family:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-size:14px;line-height:1.5;color:#1f2937;--fw-header:#0f172a;--fw-white:#fff;--fw-green:#22c55e;--fw-red:#ef4444;--fw-red-dark:#dc2626;--fw-text:#1f2937;--fw-text-light:#6b7280;--fw-gray-100:#f3f4f6;--fw-gray-200:#e5e7eb;--fw-gray-300:#d1d5db;--fw-gray-bg:#fafbfc;--fw-radius:20px;--fw-shadow:0 20px 60px rgba(0,0,0,.25)}
|
|
43
288
|
#fluxia-widget-root *,#fluxia-widget-root *::before,#fluxia-widget-root *::after{box-sizing:border-box;margin:0;padding:0}
|
|
44
289
|
#fluxia-widget-root .fluxia-bubble{position:fixed;bottom:24px;right:24px;z-index:2147483646;width:60px;height:60px;border-radius:50%;border:none;background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 8px 24px rgba(239,68,68,.5),0 0 0 4px rgba(255,255,255,.25);transition:transform .3s cubic-bezier(.34,1.56,.64,1),box-shadow .3s,opacity .25s;opacity:1}
|
|
45
290
|
#fluxia-widget-root .fluxia-bubble.fw-hidden{transform:scale(0);opacity:0;pointer-events:none}
|
|
@@ -58,17 +303,37 @@ const CRITICAL_CSS = `
|
|
|
58
303
|
#fluxia-widget-root .fluxia-close{width:34px;height:34px;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.08);border:none;color:var(--fw-white);cursor:pointer;border-radius:8px;transition:background .2s}
|
|
59
304
|
#fluxia-widget-root .fluxia-close:hover{background:rgba(255,255,255,.18)}
|
|
60
305
|
#fluxia-widget-root .fluxia-close svg{width:16px;height:16px}
|
|
61
|
-
#fluxia-widget-root .fluxia-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px;background:var(--fw-white)}
|
|
306
|
+
#fluxia-widget-root .fluxia-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px;background:var(--fw-white);scroll-behavior:smooth}
|
|
62
307
|
#fluxia-widget-root .fluxia-msg{max-width:82%;padding:10px 14px;font-size:13.5px;line-height:1.55;word-wrap:break-word;overflow-wrap:break-word;animation:fw-msgIn .3s ease-out}
|
|
63
308
|
@keyframes fw-msgIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
64
309
|
#fluxia-widget-root .fluxia-msg-assistant{align-self:flex-start;background:var(--fw-gray-100);color:var(--fw-text);border-radius:16px 16px 16px 4px}
|
|
65
310
|
#fluxia-widget-root .fluxia-msg-user{align-self:flex-end;background:var(--fw-header);color:var(--fw-white);border-radius:16px 16px 4px 16px}
|
|
66
|
-
#fluxia-widget-root .fluxia-msg-system{align-self:center;background
|
|
67
|
-
#fluxia-widget-root .fluxia-typing{display:flex;
|
|
311
|
+
#fluxia-widget-root .fluxia-msg-system{align-self:center;background:transparent;color:var(--fw-text-light);font-size:12px;padding:4px 8px;border-radius:8px;text-align:center;max-width:90%}
|
|
312
|
+
#fluxia-widget-root .fluxia-typing-wrapper{display:flex;align-items:center;gap:4px;padding:10px 14px;max-width:80px;background:var(--fw-gray-100);border-radius:16px 16px 16px 4px;align-self:flex-start;animation:fw-msgIn .3s ease-out}
|
|
68
313
|
#fluxia-widget-root .fluxia-typing-dot{width:7px;height:7px;border-radius:50%;background:#94a3b8;animation:fw-bounce 1.4s infinite ease-in-out}
|
|
69
314
|
#fluxia-widget-root .fluxia-typing-dot:nth-child(2){animation-delay:.16s}
|
|
70
315
|
#fluxia-widget-root .fluxia-typing-dot:nth-child(3){animation-delay:.32s}
|
|
71
316
|
@keyframes fw-bounce{0%,80%,100%{transform:scale(.7);opacity:.4}40%{transform:scale(1.1);opacity:1}}
|
|
317
|
+
#fluxia-widget-root .fluxia-options{display:flex;flex-direction:column;gap:8px;align-self:flex-start;max-width:85%;animation:fw-msgIn .3s ease-out;padding:4px 0}
|
|
318
|
+
#fluxia-widget-root .fluxia-option-btn{background:var(--fw-white);color:var(--fw-red-dark);border:2px solid var(--fw-red-dark);border-radius:12px;padding:10px 18px;font-size:13.5px;font-weight:500;font-family:inherit;cursor:pointer;transition:all .2s ease;text-align:left;line-height:1.4}
|
|
319
|
+
#fluxia-widget-root .fluxia-option-btn:hover{background:var(--fw-red-dark);color:#fff;transform:translateY(-1px);box-shadow:0 4px 12px rgba(220,38,38,.25)}
|
|
320
|
+
#fluxia-widget-root .fluxia-option-btn:active{transform:scale(.97)}
|
|
321
|
+
#fluxia-widget-root .fluxia-option-btn.selected{background:var(--fw-red-dark);color:#fff;pointer-events:none;opacity:.8}
|
|
322
|
+
#fluxia-widget-root .fluxia-option-btn.disabled{opacity:.4;pointer-events:none}
|
|
323
|
+
#fluxia-widget-root .fluxia-form{display:flex;flex-direction:column;gap:10px;align-self:flex-start;max-width:85%;animation:fw-msgIn .3s ease-out;background:var(--fw-gray-100);border-radius:16px 16px 16px 4px;padding:16px;min-width:240px}
|
|
324
|
+
#fluxia-widget-root .fluxia-form-field{display:flex;flex-direction:column;gap:4px}
|
|
325
|
+
#fluxia-widget-root .fluxia-form-label{font-size:12px;font-weight:600;color:var(--fw-text);display:flex;align-items:center;gap:2px}
|
|
326
|
+
#fluxia-widget-root .fluxia-form-label .required{color:var(--fw-red);font-size:14px}
|
|
327
|
+
#fluxia-widget-root .fluxia-form-input{border:1.5px solid var(--fw-gray-300);border-radius:8px;padding:9px 12px;font-size:13.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s;color:var(--fw-text);width:100%;background:var(--fw-white)}
|
|
328
|
+
#fluxia-widget-root .fluxia-form-input:focus{border-color:var(--fw-red);box-shadow:0 0 0 3px rgba(239,68,68,.1)}
|
|
329
|
+
#fluxia-widget-root .fluxia-form-input.error{border-color:var(--fw-red);background:#fef2f2}
|
|
330
|
+
#fluxia-widget-root .fluxia-form-error{font-size:11px;color:var(--fw-red);min-height:14px}
|
|
331
|
+
#fluxia-widget-root .fluxia-form-submit{background:var(--fw-red-dark);color:#fff;border:none;border-radius:8px;padding:10px 20px;font-size:13.5px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .2s,transform .1s;margin-top:4px}
|
|
332
|
+
#fluxia-widget-root .fluxia-form-submit:hover{background:var(--fw-red)}
|
|
333
|
+
#fluxia-widget-root .fluxia-form-submit:active{transform:scale(.97)}
|
|
334
|
+
#fluxia-widget-root .fluxia-form-submit:disabled{background:var(--fw-gray-300);cursor:not-allowed}
|
|
335
|
+
#fluxia-widget-root .fluxia-day-divider{display:flex;align-items:center;gap:12px;padding:8px 0;color:var(--fw-text-light);font-size:11px;font-weight:500}
|
|
336
|
+
#fluxia-widget-root .fluxia-day-divider::before,#fluxia-widget-root .fluxia-day-divider::after{content:'';flex:1;height:1px;background:var(--fw-gray-200)}
|
|
72
337
|
#fluxia-widget-root .fluxia-input-area{padding:12px 14px;background:var(--fw-white);border-top:1px solid var(--fw-gray-200);display:flex;align-items:center;gap:10px;flex-shrink:0}
|
|
73
338
|
#fluxia-widget-root .fluxia-input{flex:1;border:1.5px solid var(--fw-gray-300);border-radius:24px;padding:10px 16px;font-size:13px;font-family:inherit;outline:none;color:var(--fw-text);background:var(--fw-gray-bg);transition:border-color .2s,box-shadow .2s}
|
|
74
339
|
#fluxia-widget-root .fluxia-input::placeholder{color:#9ca3af}
|
|
@@ -98,6 +363,14 @@ const esc = (str) => {
|
|
|
98
363
|
d.textContent = str;
|
|
99
364
|
return d.innerHTML;
|
|
100
365
|
};
|
|
366
|
+
const formatBotMessage = (text) => {
|
|
367
|
+
if (!text) return "";
|
|
368
|
+
let safe = esc(text);
|
|
369
|
+
safe = safe.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
|
|
370
|
+
safe = safe.replace(new RegExp("(?<!\\*)\\*(?!\\*)(.*?)(?<!\\*)\\*(?!\\*)", "g"), "<em>$1</em>");
|
|
371
|
+
safe = safe.replace(/\n/g, "<br>");
|
|
372
|
+
return safe;
|
|
373
|
+
};
|
|
101
374
|
function widgetHTML({ botName, botAvatar }) {
|
|
102
375
|
const avatar = botAvatar ? `<img src="${esc(botAvatar)}" alt="Avatar" />` : "🤖";
|
|
103
376
|
return `
|
|
@@ -128,7 +401,7 @@ function widgetHTML({ botName, botAvatar }) {
|
|
|
128
401
|
|
|
129
402
|
<div class="fluxia-input-area">
|
|
130
403
|
<input class="fluxia-input" type="text"
|
|
131
|
-
placeholder="
|
|
404
|
+
placeholder="Digite sua mensagem..."
|
|
132
405
|
autocomplete="off" />
|
|
133
406
|
<button class="fluxia-send" title="Enviar">
|
|
134
407
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
|
|
@@ -152,6 +425,7 @@ const renderWidget = async ({ config, token, api, visitorId, botId }) => {
|
|
|
152
425
|
const botName = config.botName || config.name || "Assistente";
|
|
153
426
|
const botAvatar = config.botAvatar || "";
|
|
154
427
|
const customCss = config.customCss || "";
|
|
428
|
+
const flows = config.flows || [];
|
|
155
429
|
const root = document.createElement("div");
|
|
156
430
|
root.id = "fluxia-widget-root";
|
|
157
431
|
root.dataset.position = position;
|
|
@@ -163,72 +437,303 @@ const renderWidget = async ({ config, token, api, visitorId, botId }) => {
|
|
|
163
437
|
s.textContent = customCss;
|
|
164
438
|
document.head.appendChild(s);
|
|
165
439
|
}
|
|
440
|
+
if (theme.primaryColor) {
|
|
441
|
+
root.style.setProperty("--fw-red-dark", theme.primaryColor);
|
|
442
|
+
root.style.setProperty("--fw-red", theme.primaryColor);
|
|
443
|
+
}
|
|
166
444
|
const bubble = root.querySelector(".fluxia-bubble");
|
|
167
445
|
const chatbox = root.querySelector(".fluxia-chatbox");
|
|
168
446
|
const closeBtn = root.querySelector(".fluxia-close");
|
|
169
447
|
const input = root.querySelector(".fluxia-input");
|
|
170
448
|
const sendBtn = root.querySelector(".fluxia-send");
|
|
171
449
|
const messages = root.querySelector(".fluxia-messages");
|
|
172
|
-
|
|
173
|
-
|
|
450
|
+
let flowEngine = null;
|
|
451
|
+
let flowStarted = false;
|
|
452
|
+
let inputDisabled = false;
|
|
453
|
+
function scrollToBottom() {
|
|
454
|
+
requestAnimationFrame(() => {
|
|
455
|
+
messages.scrollTop = messages.scrollHeight;
|
|
456
|
+
});
|
|
174
457
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
send();
|
|
458
|
+
function addDayDivider() {
|
|
459
|
+
if (messages.children.length === 0) {
|
|
460
|
+
const div = document.createElement("div");
|
|
461
|
+
div.className = "fluxia-day-divider";
|
|
462
|
+
div.textContent = "Hoje";
|
|
463
|
+
messages.appendChild(div);
|
|
182
464
|
}
|
|
183
|
-
}
|
|
465
|
+
}
|
|
466
|
+
function addMsg(role, content) {
|
|
467
|
+
const div = document.createElement("div");
|
|
468
|
+
div.className = `fluxia-msg fluxia-msg-${role}`;
|
|
469
|
+
if (role === "assistant") {
|
|
470
|
+
div.innerHTML = formatBotMessage(content);
|
|
471
|
+
} else {
|
|
472
|
+
div.textContent = content;
|
|
473
|
+
}
|
|
474
|
+
messages.appendChild(div);
|
|
475
|
+
scrollToBottom();
|
|
476
|
+
}
|
|
477
|
+
function showTyping() {
|
|
478
|
+
hideTyping();
|
|
479
|
+
const typing = document.createElement("div");
|
|
480
|
+
typing.className = "fluxia-typing-wrapper";
|
|
481
|
+
typing.id = "fluxia-typing-indicator";
|
|
482
|
+
typing.innerHTML = '<div class="fluxia-typing-dot"></div><div class="fluxia-typing-dot"></div><div class="fluxia-typing-dot"></div>';
|
|
483
|
+
messages.appendChild(typing);
|
|
484
|
+
scrollToBottom();
|
|
485
|
+
}
|
|
486
|
+
function hideTyping() {
|
|
487
|
+
const existing = document.getElementById("fluxia-typing-indicator");
|
|
488
|
+
if (existing) existing.remove();
|
|
489
|
+
}
|
|
490
|
+
function showOptions(options, onSelect) {
|
|
491
|
+
hideTyping();
|
|
492
|
+
const container = document.createElement("div");
|
|
493
|
+
container.className = "fluxia-options";
|
|
494
|
+
options.forEach((opt) => {
|
|
495
|
+
const btn = document.createElement("button");
|
|
496
|
+
btn.className = "fluxia-option-btn";
|
|
497
|
+
btn.textContent = typeof opt === "string" ? opt : opt.label;
|
|
498
|
+
btn.addEventListener("click", () => {
|
|
499
|
+
const value = typeof opt === "string" ? opt : opt.value || opt.label;
|
|
500
|
+
container.querySelectorAll(".fluxia-option-btn").forEach((b) => {
|
|
501
|
+
b.classList.add("disabled");
|
|
502
|
+
});
|
|
503
|
+
btn.classList.remove("disabled");
|
|
504
|
+
btn.classList.add("selected");
|
|
505
|
+
addMsg("user", value);
|
|
506
|
+
onSelect(value);
|
|
507
|
+
});
|
|
508
|
+
container.appendChild(btn);
|
|
509
|
+
});
|
|
510
|
+
messages.appendChild(container);
|
|
511
|
+
scrollToBottom();
|
|
512
|
+
}
|
|
513
|
+
function showForm(inputs, onSubmit) {
|
|
514
|
+
var _a, _b;
|
|
515
|
+
hideTyping();
|
|
516
|
+
const form = document.createElement("div");
|
|
517
|
+
form.className = "fluxia-form";
|
|
518
|
+
const fieldElements = {};
|
|
519
|
+
inputs.forEach((inp) => {
|
|
520
|
+
const fieldWrapper = document.createElement("div");
|
|
521
|
+
fieldWrapper.className = "fluxia-form-field";
|
|
522
|
+
const label = document.createElement("label");
|
|
523
|
+
label.className = "fluxia-form-label";
|
|
524
|
+
label.textContent = inp.label;
|
|
525
|
+
if (inp.required) {
|
|
526
|
+
const req = document.createElement("span");
|
|
527
|
+
req.className = "required";
|
|
528
|
+
req.textContent = " *";
|
|
529
|
+
label.appendChild(req);
|
|
530
|
+
}
|
|
531
|
+
const fieldInput = document.createElement("input");
|
|
532
|
+
fieldInput.className = "fluxia-form-input";
|
|
533
|
+
fieldInput.type = inp.type || "text";
|
|
534
|
+
fieldInput.placeholder = inp.placeholder || inp.label || "";
|
|
535
|
+
fieldInput.required = inp.required;
|
|
536
|
+
fieldInput.autocomplete = getAutocomplete(inp.type);
|
|
537
|
+
const errorMsg = document.createElement("div");
|
|
538
|
+
errorMsg.className = "fluxia-form-error";
|
|
539
|
+
fieldWrapper.appendChild(label);
|
|
540
|
+
fieldWrapper.appendChild(fieldInput);
|
|
541
|
+
fieldWrapper.appendChild(errorMsg);
|
|
542
|
+
form.appendChild(fieldWrapper);
|
|
543
|
+
fieldElements[inp.key] = {
|
|
544
|
+
input: fieldInput,
|
|
545
|
+
error: errorMsg,
|
|
546
|
+
config: inp
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
const submitBtn = document.createElement("button");
|
|
550
|
+
submitBtn.className = "fluxia-form-submit";
|
|
551
|
+
submitBtn.textContent = "Enviar";
|
|
552
|
+
submitBtn.type = "button";
|
|
553
|
+
submitBtn.addEventListener("click", () => {
|
|
554
|
+
const formData = {};
|
|
555
|
+
let hasError = false;
|
|
556
|
+
for (const [key, field] of Object.entries(fieldElements)) {
|
|
557
|
+
const value = field.input.value.trim();
|
|
558
|
+
field.error.textContent = "";
|
|
559
|
+
field.input.classList.remove("error");
|
|
560
|
+
if (field.config.required && !value) {
|
|
561
|
+
field.error.textContent = `${field.config.label} é obrigatório`;
|
|
562
|
+
field.input.classList.add("error");
|
|
563
|
+
hasError = true;
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (field.config.type === "email" && value && !isValidEmail(value)) {
|
|
567
|
+
field.error.textContent = "E-mail inválido";
|
|
568
|
+
field.input.classList.add("error");
|
|
569
|
+
hasError = true;
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
if (field.config.type === "tel" && value && !isValidPhone(value)) {
|
|
573
|
+
field.error.textContent = "Telefone inválido";
|
|
574
|
+
field.input.classList.add("error");
|
|
575
|
+
hasError = true;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
formData[key] = value;
|
|
579
|
+
}
|
|
580
|
+
if (hasError) return;
|
|
581
|
+
submitBtn.disabled = true;
|
|
582
|
+
submitBtn.textContent = "Enviado ✓";
|
|
583
|
+
for (const field of Object.values(fieldElements)) {
|
|
584
|
+
field.input.disabled = true;
|
|
585
|
+
}
|
|
586
|
+
const summary = Object.entries(formData).filter(([, v]) => v).map(([, v]) => v).join(" • ");
|
|
587
|
+
if (summary) {
|
|
588
|
+
addMsg("user", summary);
|
|
589
|
+
}
|
|
590
|
+
onSubmit(formData);
|
|
591
|
+
});
|
|
592
|
+
if (inputs.length === 1) {
|
|
593
|
+
const singleInput = (_a = Object.values(fieldElements)[0]) == null ? void 0 : _a.input;
|
|
594
|
+
if (singleInput) {
|
|
595
|
+
singleInput.addEventListener("keydown", (e) => {
|
|
596
|
+
if (e.key === "Enter") {
|
|
597
|
+
e.preventDefault();
|
|
598
|
+
submitBtn.click();
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
form.appendChild(submitBtn);
|
|
604
|
+
messages.appendChild(form);
|
|
605
|
+
scrollToBottom();
|
|
606
|
+
const firstField = (_b = Object.values(fieldElements)[0]) == null ? void 0 : _b.input;
|
|
607
|
+
if (firstField) {
|
|
608
|
+
setTimeout(() => firstField.focus(), 100);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function getAutocomplete(type) {
|
|
612
|
+
switch (type) {
|
|
613
|
+
case "email":
|
|
614
|
+
return "email";
|
|
615
|
+
case "tel":
|
|
616
|
+
return "tel";
|
|
617
|
+
case "name":
|
|
618
|
+
return "name";
|
|
619
|
+
default:
|
|
620
|
+
return "off";
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function isValidEmail(email) {
|
|
624
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
625
|
+
}
|
|
626
|
+
function isValidPhone(phone) {
|
|
627
|
+
return /^[\d\s\-+().]{7,20}$/.test(phone);
|
|
628
|
+
}
|
|
629
|
+
function setInputDisabled(disabled) {
|
|
630
|
+
inputDisabled = disabled;
|
|
631
|
+
input.disabled = disabled;
|
|
632
|
+
sendBtn.disabled = disabled;
|
|
633
|
+
if (disabled) {
|
|
634
|
+
input.placeholder = "Aguarde...";
|
|
635
|
+
} else {
|
|
636
|
+
input.placeholder = "Digite sua mensagem...";
|
|
637
|
+
}
|
|
638
|
+
}
|
|
184
639
|
function open() {
|
|
185
640
|
bubble.classList.add("fw-hidden");
|
|
186
641
|
chatbox.classList.add("fw-open");
|
|
187
|
-
setTimeout(() =>
|
|
642
|
+
setTimeout(() => {
|
|
643
|
+
if (!inputDisabled) input.focus();
|
|
644
|
+
}, 350);
|
|
645
|
+
if (!flowStarted) {
|
|
646
|
+
flowStarted = true;
|
|
647
|
+
startFlow();
|
|
648
|
+
}
|
|
188
649
|
}
|
|
189
650
|
function close() {
|
|
190
651
|
chatbox.classList.remove("fw-open");
|
|
191
652
|
setTimeout(() => bubble.classList.remove("fw-hidden"), 300);
|
|
192
653
|
}
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
654
|
+
function startFlow() {
|
|
655
|
+
addDayDivider();
|
|
656
|
+
if (!flows.length) {
|
|
657
|
+
if (config.initialMessage) {
|
|
658
|
+
addMsg("assistant", config.initialMessage);
|
|
659
|
+
}
|
|
660
|
+
setInputDisabled(false);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
setInputDisabled(true);
|
|
664
|
+
flowEngine = createFlowEngine({
|
|
665
|
+
flows,
|
|
666
|
+
onMessage: (content) => {
|
|
667
|
+
addMsg("assistant", content);
|
|
668
|
+
},
|
|
669
|
+
onOptions: (options, onSelect) => {
|
|
670
|
+
showOptions(options, onSelect);
|
|
671
|
+
},
|
|
672
|
+
onInput: (inputs, onSubmit) => {
|
|
673
|
+
showForm(inputs, onSubmit);
|
|
674
|
+
},
|
|
675
|
+
onTyping: (show) => {
|
|
676
|
+
if (show) showTyping();
|
|
677
|
+
else hideTyping();
|
|
678
|
+
},
|
|
679
|
+
onFlowEnd: () => {
|
|
680
|
+
hideTyping();
|
|
681
|
+
setInputDisabled(false);
|
|
682
|
+
input.placeholder = "Pergunte o que quiser...";
|
|
683
|
+
},
|
|
684
|
+
onLeadCapture: async (leadData) => {
|
|
685
|
+
try {
|
|
686
|
+
await api.captureLead(token, leadData);
|
|
687
|
+
console.log("[Fluxia] Lead capturado com sucesso");
|
|
688
|
+
} catch (err) {
|
|
689
|
+
console.warn("[Fluxia] Erro ao capturar lead:", err.message);
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
onVariableSet: (key, value) => {
|
|
693
|
+
console.log(`[Fluxia] Variável: ${key} = ${value}`);
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
flowEngine.start();
|
|
199
697
|
}
|
|
200
698
|
async function send() {
|
|
699
|
+
var _a, _b;
|
|
201
700
|
const text = input.value.trim();
|
|
202
|
-
if (!text) return;
|
|
701
|
+
if (!text || inputDisabled) return;
|
|
203
702
|
input.value = "";
|
|
204
|
-
|
|
205
|
-
|
|
703
|
+
if (flowEngine && flowEngine.isWaiting()) {
|
|
704
|
+
const handled = flowEngine.handleUserMessage(text);
|
|
705
|
+
if (handled) return;
|
|
706
|
+
}
|
|
206
707
|
addMsg("user", text);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
loader.innerHTML = '<div class="fluxia-typing"><div class="fluxia-typing-dot"></div><div class="fluxia-typing-dot"></div><div class="fluxia-typing-dot"></div></div>';
|
|
210
|
-
messages.appendChild(loader);
|
|
211
|
-
messages.scrollTop = messages.scrollHeight;
|
|
708
|
+
setInputDisabled(true);
|
|
709
|
+
showTyping();
|
|
212
710
|
try {
|
|
213
711
|
const result = await api.sendMessage(token, text);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
712
|
+
hideTyping();
|
|
713
|
+
const reply = ((_a = result == null ? void 0 : result.reply) == null ? void 0 : _a.content) || (result == null ? void 0 : result.reply) || (result == null ? void 0 : result.message) || ((_b = result == null ? void 0 : result.data) == null ? void 0 : _b.reply) || "Desculpe, não entendi. Pode reformular?";
|
|
714
|
+
addMsg("assistant", typeof reply === "string" ? reply : JSON.stringify(reply));
|
|
218
715
|
} catch (err) {
|
|
219
|
-
|
|
716
|
+
hideTyping();
|
|
717
|
+
console.error("[Fluxia]", err);
|
|
220
718
|
if (err.status === 401) {
|
|
221
719
|
addMsg("system", "Sessão expirada. Recarregue a página.");
|
|
222
720
|
} else {
|
|
223
721
|
addMsg("system", "Erro ao enviar mensagem. Tente novamente.");
|
|
224
722
|
}
|
|
225
|
-
console.error("[Fluxia]", err);
|
|
226
723
|
} finally {
|
|
227
|
-
|
|
228
|
-
sendBtn.disabled = false;
|
|
724
|
+
setInputDisabled(false);
|
|
229
725
|
input.focus();
|
|
230
726
|
}
|
|
231
727
|
}
|
|
728
|
+
bubble.addEventListener("click", () => open());
|
|
729
|
+
closeBtn.addEventListener("click", () => close());
|
|
730
|
+
sendBtn.addEventListener("click", send);
|
|
731
|
+
input.addEventListener("keydown", (e) => {
|
|
732
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
733
|
+
e.preventDefault();
|
|
734
|
+
send();
|
|
735
|
+
}
|
|
736
|
+
});
|
|
232
737
|
};
|
|
233
738
|
const VISITOR_ID_KEY = "fluxia_visitor_id";
|
|
234
739
|
const scriptFromLoad = document.currentScript;
|
|
@@ -306,6 +811,9 @@ const init = async () => {
|
|
|
306
811
|
if (!botConfig) {
|
|
307
812
|
botConfig = await api.getConfig(token);
|
|
308
813
|
}
|
|
814
|
+
if (!botConfig.flows) {
|
|
815
|
+
botConfig.flows = [];
|
|
816
|
+
}
|
|
309
817
|
await renderWidget({
|
|
310
818
|
config: botConfig,
|
|
311
819
|
token,
|
package/dist/chatbot.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t){"function"==typeof define&&define.amd?define(t):t()}(function(){"use strict";const t="fluxia-widget-styles",e=t=>{const e=document.createElement("div");return e.textContent=t,e.innerHTML};const i=async({config:i,token:a,api:o,visitorId:n,botId:r})=>{(()=>{if(document.getElementById(t))return;const e=document.createElement("style");e.id=t,e.textContent='\n#fluxia-widget-root{all:initial;font-family:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-size:14px;line-height:1.5;color:#1f2937;--fw-header:#0f172a;--fw-white:#fff;--fw-green:#22c55e;--fw-red:#ef4444;--fw-red-dark:#dc2626;--fw-text:#1f2937;--fw-gray-100:#f3f4f6;--fw-gray-200:#e5e7eb;--fw-gray-300:#d1d5db;--fw-gray-bg:#fafbfc;--fw-radius:20px;--fw-shadow:0 20px 60px rgba(0,0,0,.25)}\n#fluxia-widget-root *,#fluxia-widget-root *::before,#fluxia-widget-root *::after{box-sizing:border-box;margin:0;padding:0}\n#fluxia-widget-root .fluxia-bubble{position:fixed;bottom:24px;right:24px;z-index:2147483646;width:60px;height:60px;border-radius:50%;border:none;background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 8px 24px rgba(239,68,68,.5),0 0 0 4px rgba(255,255,255,.25);transition:transform .3s cubic-bezier(.34,1.56,.64,1),box-shadow .3s,opacity .25s;opacity:1}\n#fluxia-widget-root .fluxia-bubble.fw-hidden{transform:scale(0);opacity:0;pointer-events:none}\n#fluxia-widget-root .fluxia-bubble svg{width:28px;height:28px;fill:#fff;stroke:none}\n#fluxia-widget-root .fluxia-chatbox{position:fixed;bottom:10px;right:10px;z-index:2147483647;width:400px;height:600px;max-height:calc(100vh - 120px);border-radius:var(--fw-radius);background:var(--fw-white);box-shadow:var(--fw-shadow);display:flex;flex-direction:column;overflow:hidden;transform-origin:bottom right;transform:scale(0) translateY(20px);opacity:0;pointer-events:none;transition:transform .35s cubic-bezier(.34,1.56,.64,1),opacity .25s}\n#fluxia-widget-root .fluxia-chatbox.fw-open{transform:scale(1) translateY(0);opacity:1;pointer-events:auto}\n#fluxia-widget-root .fluxia-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 16px;background:var(--fw-header);color:var(--fw-white);flex-shrink:0;border-radius:var(--fw-radius) var(--fw-radius) 0 0}\n#fluxia-widget-root .fluxia-header-info{display:flex;align-items:center;gap:12px;flex:1;min-width:0}\n#fluxia-widget-root .fluxia-avatar{width:42px;height:42px;min-width:42px;border-radius:50%;background:rgba(255,255,255,.12);border:2px solid rgba(34,197,94,.5);display:flex;align-items:center;justify-content:center;font-size:20px;overflow:hidden;color:#fff}\n#fluxia-widget-root .fluxia-avatar img{width:100%;height:100%;object-fit:cover;border-radius:50%}\n#fluxia-widget-root .fluxia-header-text{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}\n#fluxia-widget-root .fluxia-title{font-weight:700;font-size:15px;color:var(--fw-white);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n#fluxia-widget-root .fluxia-status{font-size:12px;color:var(--fw-green);display:flex;align-items:center;gap:6px;font-weight:600}\n#fluxia-widget-root .fluxia-status-dot{width:7px;height:7px;border-radius:50%;background:var(--fw-green);display:inline-block;animation:fw-pulse 2s ease-in-out infinite}\n@keyframes fw-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(1.3)}}\n#fluxia-widget-root .fluxia-close{width:34px;height:34px;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.08);border:none;color:var(--fw-white);cursor:pointer;border-radius:8px;transition:background .2s}\n#fluxia-widget-root .fluxia-close:hover{background:rgba(255,255,255,.18)}\n#fluxia-widget-root .fluxia-close svg{width:16px;height:16px}\n#fluxia-widget-root .fluxia-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px;background:var(--fw-white)}\n#fluxia-widget-root .fluxia-msg{max-width:82%;padding:10px 14px;font-size:13.5px;line-height:1.55;word-wrap:break-word;overflow-wrap:break-word;animation:fw-msgIn .3s ease-out}\n@keyframes fw-msgIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}\n#fluxia-widget-root .fluxia-msg-assistant{align-self:flex-start;background:var(--fw-gray-100);color:var(--fw-text);border-radius:16px 16px 16px 4px}\n#fluxia-widget-root .fluxia-msg-user{align-self:flex-end;background:var(--fw-header);color:var(--fw-white);border-radius:16px 16px 4px 16px}\n#fluxia-widget-root .fluxia-msg-system{align-self:center;background:#fef3c7;color:#92400e;font-size:12px;padding:8px 14px;border-radius:8px;text-align:center;max-width:90%;border:1px solid #fde68a}\n#fluxia-widget-root .fluxia-typing{display:flex;gap:5px;align-items:center;padding:4px 0}\n#fluxia-widget-root .fluxia-typing-dot{width:7px;height:7px;border-radius:50%;background:#94a3b8;animation:fw-bounce 1.4s infinite ease-in-out}\n#fluxia-widget-root .fluxia-typing-dot:nth-child(2){animation-delay:.16s}\n#fluxia-widget-root .fluxia-typing-dot:nth-child(3){animation-delay:.32s}\n@keyframes fw-bounce{0%,80%,100%{transform:scale(.7);opacity:.4}40%{transform:scale(1.1);opacity:1}}\n#fluxia-widget-root .fluxia-input-area{padding:12px 14px;background:var(--fw-white);border-top:1px solid var(--fw-gray-200);display:flex;align-items:center;gap:10px;flex-shrink:0}\n#fluxia-widget-root .fluxia-input{flex:1;border:1.5px solid var(--fw-gray-300);border-radius:24px;padding:10px 16px;font-size:13px;font-family:inherit;outline:none;color:var(--fw-text);background:var(--fw-gray-bg);transition:border-color .2s,box-shadow .2s}\n#fluxia-widget-root .fluxia-input::placeholder{color:#9ca3af}\n#fluxia-widget-root .fluxia-input:focus{border-color:var(--fw-header);background:var(--fw-white);box-shadow:0 0 0 3px rgba(15,23,42,.06)}\n#fluxia-widget-root .fluxia-input:disabled{opacity:.5;cursor:not-allowed}\n#fluxia-widget-root .fluxia-send{width:40px;height:40px;min-width:40px;border-radius:50%;background:var(--fw-red);color:var(--fw-white);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s,transform .2s;flex-shrink:0}\n#fluxia-widget-root .fluxia-send:hover{background:var(--fw-red-dark);transform:scale(1.08)}\n#fluxia-widget-root .fluxia-send:active{transform:scale(.92)}\n#fluxia-widget-root .fluxia-send:disabled{opacity:.4;cursor:not-allowed;transform:none}\n#fluxia-widget-root .fluxia-send svg{width:18px;height:18px}\n#fluxia-widget-root .fluxia-powered{text-align:center;padding:8px 0;font-size:11px;color:#9ca3af;background:var(--fw-white);border-top:1px solid var(--fw-gray-200);flex-shrink:0}\n#fluxia-widget-root .fluxia-powered a{color:#6b7280;text-decoration:none;font-weight:600}\n#fluxia-widget-root[data-position="left"] .fluxia-bubble{left:24px;right:auto}\n#fluxia-widget-root[data-position="left"] .fluxia-chatbox{left:24px;right:auto;transform-origin:bottom left}\n@media(max-width:768px){#fluxia-widget-root .fluxia-chatbox{width:calc(100vw - 32px);max-width:400px;height:calc(100vh - 140px);max-height:600px;bottom:10px;right:10px}#fluxia-widget-root .fluxia-bubble{bottom:20px;right:16px;width:56px;height:56px}}\n@media(max-width:480px){#fluxia-widget-root .fluxia-chatbox{position:fixed;top:0;left:0;right:0;bottom:0;width:100%;height:100%;max-width:none;max-height:none;border-radius:0;transform-origin:bottom center}#fluxia-widget-root .fluxia-chatbox.fw-open{border-radius:0}#fluxia-widget-root .fluxia-header{border-radius:0}#fluxia-widget-root .fluxia-bubble{bottom:16px;right:16px;width:52px;height:52px}#fluxia-widget-root .fluxia-bubble svg{width:24px;height:24px}#fluxia-widget-root .fluxia-avatar{width:38px;height:38px;min-width:38px}#fluxia-widget-root .fluxia-title{font-size:14px}#fluxia-widget-root .fluxia-msg{max-width:88%;font-size:13px}#fluxia-widget-root .fluxia-input{font-size:14px}#fluxia-widget-root .fluxia-send{width:38px;height:38px;min-width:38px}}\n',document.head.appendChild(e)})();const d=(i.theme||{}).position||"right",l=i.botName||i.name||"Assistente",s=i.botAvatar||"",f=i.customCss||"",u=document.createElement("div");if(u.id="fluxia-widget-root",u.dataset.position=d,u.innerHTML=function({botName:t,botAvatar:i}){return`\n <button class="fluxia-bubble" aria-label="Abrir chat" title="Abrir chat">\n <svg viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>\n </button>\n\n <div class="fluxia-chatbox" role="dialog" aria-label="Chat">\n <div class="fluxia-header">\n <div class="fluxia-header-info">\n <div class="fluxia-avatar">${i?`<img src="${e(i)}" alt="Avatar" />`:"🤖"}</div>\n <div class="fluxia-header-text">\n <div class="fluxia-title">${t}</div>\n <div class="fluxia-status">\n <span class="fluxia-status-dot"></span>\n Online agora\n </div>\n </div>\n </div>\n <button class="fluxia-close" aria-label="Fechar chat" title="Fechar">\n <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">\n <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>\n </svg>\n </button>\n </div>\n\n <div class="fluxia-messages"></div>\n\n <div class="fluxia-input-area">\n <input class="fluxia-input" type="text"\n placeholder="Pergunte sobre leads, métricas ou integrações"\n autocomplete="off" />\n <button class="fluxia-send" title="Enviar">\n <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"\n stroke-linecap="round" stroke-linejoin="round">\n <line x1="22" y1="2" x2="11" y2="13"/>\n <polygon points="22 2 15 22 11 13 2 9 22 2"/>\n </svg>\n </button>\n </div>\n\n <div class="fluxia-powered">\n Powered by <a href="https://rowsky.com.br/" target="_blank" rel="noopener">Rowsky</a>\n </div>\n </div>\n `}({botName:e(l),botAvatar:s}),document.body.appendChild(u),f&&f.trim()){const t=document.createElement("style");t.id="fluxia-widget-custom",t.textContent=f,document.head.appendChild(t)}const x=u.querySelector(".fluxia-bubble"),c=u.querySelector(".fluxia-chatbox"),p=u.querySelector(".fluxia-close"),g=u.querySelector(".fluxia-input"),w=u.querySelector(".fluxia-send"),h=u.querySelector(".fluxia-messages");function b(t,e){const i=document.createElement("div");i.className=`fluxia-msg fluxia-msg-${t}`,i.textContent=e,h.appendChild(i),h.scrollTop=h.scrollHeight}async function m(){const t=g.value.trim();if(!t)return;g.value="",g.disabled=!0,w.disabled=!0,b("user",t);const e=document.createElement("div");e.className="fluxia-msg fluxia-msg-assistant",e.innerHTML='<div class="fluxia-typing"><div class="fluxia-typing-dot"></div><div class="fluxia-typing-dot"></div><div class="fluxia-typing-dot"></div></div>',h.appendChild(e),h.scrollTop=h.scrollHeight;try{const i=await o.sendMessage(a,t);e.remove(),i.reply&&b("assistant",i.reply.content)}catch(i){e.remove(),401===i.status?b("system","Sessão expirada. Recarregue a página."):b("system","Erro ao enviar mensagem. Tente novamente."),console.error("[Fluxia]",i)}finally{g.disabled=!1,w.disabled=!1,g.focus()}}i.initialMessage&&b("assistant",i.initialMessage),x.addEventListener("click",()=>(x.classList.add("fw-hidden"),c.classList.add("fw-open"),void setTimeout(()=>g.focus(),350))),p.addEventListener("click",()=>(c.classList.remove("fw-open"),void setTimeout(()=>x.classList.remove("fw-hidden"),300))),w.addEventListener("click",m),g.addEventListener("keydown",t=>{"Enter"!==t.key||t.shiftKey||(t.preventDefault(),m())})},a="fluxia_visitor_id",o=document.currentScript,n=async()=>{const t=(()=>{const t=o||document.currentScript||document.querySelector("script[data-client-id], script[data-bot-id]");if(!t)return console.error("[Fluxia] Script tag não encontrada. Use data-client-id OU data-bot-id + data-public-key."),null;const e=t.getAttribute("data-bot-id"),i=t.getAttribute("data-public-key"),a=t.getAttribute("data-client-id"),n=t.getAttribute("data-api-base")||"";return n&&(a||e&&i)?{botId:e?Number(e):null,publicKey:i||"",clientId:a||"",apiBase:n.replace(/\/$/,"")}:(console.error("[Fluxia] Atributos obrigatorios: data-api-base e (data-client-id OU data-bot-id + data-public-key)"),null)})();if(!t)return;const e=(()=>{let t=null;try{t=localStorage.getItem(a)}catch{}if(!t){t="v_"+crypto.randomUUID();try{localStorage.setItem(a,t)}catch{}}return t})(),n=(t=>{const e=async(e,i,a=null,o=null)=>{const n={"Content-Type":"application/json"};o&&(n.Authorization=`Bearer ${o}`);const r={method:e,headers:n};a&&(r.body=JSON.stringify(a));const d=await fetch(`${t}${i}`,r);if(!d.ok){const t=await d.json().catch(()=>({})),e=new Error(t.error||`HTTP ${d.status}`);throw e.status=d.status,e.data=t,e}return d.json()};return{bootstrap:({clientId:t,visitorId:i})=>e("POST","/widget/bootstrap",{clientId:t,visitorId:i}),handshake:({botId:t,publicKey:i,visitorId:a})=>e("POST","/widget/handshake",{botId:t,publicKey:i,visitorId:a}),getConfig:t=>e("GET","/widget/config",null,t),sendMessage:(t,i)=>e("POST","/widget/message",{message:i},t),captureLead:(t,i)=>e("POST","/widget/lead",i,t)}})(t.apiBase);try{let a="",o=null;if(t.clientId){const i=await n.bootstrap({clientId:t.clientId,visitorId:e});if(!i||!i.token)return void console.error("[Fluxia] Bootstrap falhou. Widget nao sera renderizado.");a=i.token,o=i.config||null}else{const i=await n.handshake({botId:t.botId,publicKey:t.publicKey,visitorId:e});if(!i||!i.token)return void console.error("[Fluxia] Handshake falhou. Widget nao sera renderizado.");a=i.token}o||(o=await n.getConfig(a)),await i({config:o,token:a,api:n,visitorId:e,botId:t.botId||(null==o?void 0:o.botId)}),console.log("[Fluxia] Widget inicializado com sucesso.")}catch(r){console.error("[Fluxia] Erro na inicialização:",r.message||r)}};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",n):n()});
|
|
1
|
+
!function(e){"function"==typeof define&&define.amd?define(e):e()}(function(){"use strict";const e="idle",t="running",i="waiting_input",a="waiting_option",o="ai_mode",n=n=>{const{flows:r=[],onMessage:l,onOptions:s,onInput:d,onTyping:f,onFlowEnd:u,onLeadCapture:c,onVariableSet:x}=n;let p=e,g=0,w=null,m={},b=[],h=null,v=!1;const y=e=>new Promise(t=>{if(v)return t();setTimeout(t,Math.max(0,e))}),k=e=>e&&"string"==typeof e?e.replace(/\{\{(\w+)\}\}/g,(e,t)=>m[t]||`{{${t}}}`):e||"",E=async e=>{if(v)return;const t=1e3*(e.delay||0);if(t>0||"message"===e.type){if(null==f||f(!0),await y(t>0?t:C(e)),v)return;null==f||f(!1)}switch(e.type){case"message":case"text":await I(e);break;case"options":case"question":await L(e);break;case"input":case"lead_capture":await z(e);break;case"subflow":await S(e);break;default:console.warn(`[FlowEngine] Tipo de passo desconhecido: ${e.type}`)}},C=e=>{const t=500+18*(e.content||e.title||"").length;return Math.min(t,2500)},I=async e=>{const t=k(e.content||e.title||"");null==l||l(t)},L=e=>new Promise(i=>{if(v)return i();const o=k(e.content||"");o&&(null==l||l(o)),p=a;const n=(e.options||[]).map(e=>"string"==typeof e?{label:e,value:e}:e);h=a=>{const o=e.variableKey||`option_step_${e.id}`;m[o]=a,null==x||x(o,a),p=t,i()},null==s||s(n,e=>{h&&(h(e),h=null)})}),z=e=>new Promise(a=>{if(v)return a();const o=k(e.content||"");o&&(null==l||l(o)),p=i;const n=(e.inputs||[]).map(e=>{var t;return{type:e.type||"text",label:e.label||"",required:!1!==e.required,placeholder:e.placeholder||"",key:e.key||(null==(t=e.label)?void 0:t.toLowerCase().replace(/[^a-z0-9_]/g,"_"))||`field_${Math.random().toString(36).slice(2,6)}`}});h=e=>{for(const[t,a]of Object.entries(e))m[t]=a,null==x||x(t,a);const i=n.find(e=>"email"===e.type||"email"===e.key||"e_mail"===e.key);i&&e[i.key]&&(null==c||c({name:e.name||e.nome||m.name||m.nome||"",email:e[i.key],phone:e.phone||e.telefone||m.phone||m.telefone||"",metadata:{...m}})),p=t,a()},null==d||d(n,e=>{h&&(h(e),h=null)})}),S=async e=>{const t=(i=e.flowId)?r.find(e=>String(e.id)===String(i)||e.name===i):null;var i;if(!t)return void console.warn(`[FlowEngine] Subflow não encontrado: ${e.flowId}`);if(e.mappings&&"object"==typeof e.mappings)for(const[o,n]of Object.entries(e.mappings))void 0!==m[n]&&(m[o]=m[n]);b.push({flow:w,stepIndex:g}),w=t,g=0,await T();const a=b.pop();a&&(w=a.flow,g=a.stepIndex)},T=async()=>{const e=(null==w?void 0:w.steps)||[];for(;g<e.length;){if(v)return;const t=e[g];await E(t),g++,g<e.length&&await y(350)}};return{start:async()=>{if(w=r.find(e=>{const t=(e.trigger||"").toLowerCase();return t.includes("abrir")&&(t.includes("site")||t.includes("imediat"))})||r[0]||null,!w)return p=o,void(null==u||u());g=0,p=t,m={},await T(),v||(p=o,null==u||u())},handleUserMessage:e=>p===a?(h&&(h(e),h=null),!0):p===i,getState:()=>p,getVariables:()=>({...m}),isAIMode:()=>p===o,isWaiting:()=>p===i||p===a,destroy:()=>{v=!0,h=null,b=[]}}},r="fluxia-widget-styles",l=e=>{const t=document.createElement("div");return t.textContent=e,t.innerHTML};const s=async({config:e,token:t,api:i,visitorId:a,botId:o})=>{(()=>{if(document.getElementById(r))return;const e=document.createElement("style");e.id=r,e.textContent='\n#fluxia-widget-root{all:initial;font-family:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-size:14px;line-height:1.5;color:#1f2937;--fw-header:#0f172a;--fw-white:#fff;--fw-green:#22c55e;--fw-red:#ef4444;--fw-red-dark:#dc2626;--fw-text:#1f2937;--fw-text-light:#6b7280;--fw-gray-100:#f3f4f6;--fw-gray-200:#e5e7eb;--fw-gray-300:#d1d5db;--fw-gray-bg:#fafbfc;--fw-radius:20px;--fw-shadow:0 20px 60px rgba(0,0,0,.25)}\n#fluxia-widget-root *,#fluxia-widget-root *::before,#fluxia-widget-root *::after{box-sizing:border-box;margin:0;padding:0}\n#fluxia-widget-root .fluxia-bubble{position:fixed;bottom:24px;right:24px;z-index:2147483646;width:60px;height:60px;border-radius:50%;border:none;background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 8px 24px rgba(239,68,68,.5),0 0 0 4px rgba(255,255,255,.25);transition:transform .3s cubic-bezier(.34,1.56,.64,1),box-shadow .3s,opacity .25s;opacity:1}\n#fluxia-widget-root .fluxia-bubble.fw-hidden{transform:scale(0);opacity:0;pointer-events:none}\n#fluxia-widget-root .fluxia-bubble svg{width:28px;height:28px;fill:#fff;stroke:none}\n#fluxia-widget-root .fluxia-chatbox{position:fixed;bottom:10px;right:10px;z-index:2147483647;width:400px;height:600px;max-height:calc(100vh - 120px);border-radius:var(--fw-radius);background:var(--fw-white);box-shadow:var(--fw-shadow);display:flex;flex-direction:column;overflow:hidden;transform-origin:bottom right;transform:scale(0) translateY(20px);opacity:0;pointer-events:none;transition:transform .35s cubic-bezier(.34,1.56,.64,1),opacity .25s}\n#fluxia-widget-root .fluxia-chatbox.fw-open{transform:scale(1) translateY(0);opacity:1;pointer-events:auto}\n#fluxia-widget-root .fluxia-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 16px;background:var(--fw-header);color:var(--fw-white);flex-shrink:0;border-radius:var(--fw-radius) var(--fw-radius) 0 0}\n#fluxia-widget-root .fluxia-header-info{display:flex;align-items:center;gap:12px;flex:1;min-width:0}\n#fluxia-widget-root .fluxia-avatar{width:42px;height:42px;min-width:42px;border-radius:50%;background:rgba(255,255,255,.12);border:2px solid rgba(34,197,94,.5);display:flex;align-items:center;justify-content:center;font-size:20px;overflow:hidden;color:#fff}\n#fluxia-widget-root .fluxia-avatar img{width:100%;height:100%;object-fit:cover;border-radius:50%}\n#fluxia-widget-root .fluxia-header-text{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}\n#fluxia-widget-root .fluxia-title{font-weight:700;font-size:15px;color:var(--fw-white);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n#fluxia-widget-root .fluxia-status{font-size:12px;color:var(--fw-green);display:flex;align-items:center;gap:6px;font-weight:600}\n#fluxia-widget-root .fluxia-status-dot{width:7px;height:7px;border-radius:50%;background:var(--fw-green);display:inline-block;animation:fw-pulse 2s ease-in-out infinite}\n@keyframes fw-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(1.3)}}\n#fluxia-widget-root .fluxia-close{width:34px;height:34px;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.08);border:none;color:var(--fw-white);cursor:pointer;border-radius:8px;transition:background .2s}\n#fluxia-widget-root .fluxia-close:hover{background:rgba(255,255,255,.18)}\n#fluxia-widget-root .fluxia-close svg{width:16px;height:16px}\n#fluxia-widget-root .fluxia-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px;background:var(--fw-white);scroll-behavior:smooth}\n#fluxia-widget-root .fluxia-msg{max-width:82%;padding:10px 14px;font-size:13.5px;line-height:1.55;word-wrap:break-word;overflow-wrap:break-word;animation:fw-msgIn .3s ease-out}\n@keyframes fw-msgIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}\n#fluxia-widget-root .fluxia-msg-assistant{align-self:flex-start;background:var(--fw-gray-100);color:var(--fw-text);border-radius:16px 16px 16px 4px}\n#fluxia-widget-root .fluxia-msg-user{align-self:flex-end;background:var(--fw-header);color:var(--fw-white);border-radius:16px 16px 4px 16px}\n#fluxia-widget-root .fluxia-msg-system{align-self:center;background:transparent;color:var(--fw-text-light);font-size:12px;padding:4px 8px;border-radius:8px;text-align:center;max-width:90%}\n#fluxia-widget-root .fluxia-typing-wrapper{display:flex;align-items:center;gap:4px;padding:10px 14px;max-width:80px;background:var(--fw-gray-100);border-radius:16px 16px 16px 4px;align-self:flex-start;animation:fw-msgIn .3s ease-out}\n#fluxia-widget-root .fluxia-typing-dot{width:7px;height:7px;border-radius:50%;background:#94a3b8;animation:fw-bounce 1.4s infinite ease-in-out}\n#fluxia-widget-root .fluxia-typing-dot:nth-child(2){animation-delay:.16s}\n#fluxia-widget-root .fluxia-typing-dot:nth-child(3){animation-delay:.32s}\n@keyframes fw-bounce{0%,80%,100%{transform:scale(.7);opacity:.4}40%{transform:scale(1.1);opacity:1}}\n#fluxia-widget-root .fluxia-options{display:flex;flex-direction:column;gap:8px;align-self:flex-start;max-width:85%;animation:fw-msgIn .3s ease-out;padding:4px 0}\n#fluxia-widget-root .fluxia-option-btn{background:var(--fw-white);color:var(--fw-red-dark);border:2px solid var(--fw-red-dark);border-radius:12px;padding:10px 18px;font-size:13.5px;font-weight:500;font-family:inherit;cursor:pointer;transition:all .2s ease;text-align:left;line-height:1.4}\n#fluxia-widget-root .fluxia-option-btn:hover{background:var(--fw-red-dark);color:#fff;transform:translateY(-1px);box-shadow:0 4px 12px rgba(220,38,38,.25)}\n#fluxia-widget-root .fluxia-option-btn:active{transform:scale(.97)}\n#fluxia-widget-root .fluxia-option-btn.selected{background:var(--fw-red-dark);color:#fff;pointer-events:none;opacity:.8}\n#fluxia-widget-root .fluxia-option-btn.disabled{opacity:.4;pointer-events:none}\n#fluxia-widget-root .fluxia-form{display:flex;flex-direction:column;gap:10px;align-self:flex-start;max-width:85%;animation:fw-msgIn .3s ease-out;background:var(--fw-gray-100);border-radius:16px 16px 16px 4px;padding:16px;min-width:240px}\n#fluxia-widget-root .fluxia-form-field{display:flex;flex-direction:column;gap:4px}\n#fluxia-widget-root .fluxia-form-label{font-size:12px;font-weight:600;color:var(--fw-text);display:flex;align-items:center;gap:2px}\n#fluxia-widget-root .fluxia-form-label .required{color:var(--fw-red);font-size:14px}\n#fluxia-widget-root .fluxia-form-input{border:1.5px solid var(--fw-gray-300);border-radius:8px;padding:9px 12px;font-size:13.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s;color:var(--fw-text);width:100%;background:var(--fw-white)}\n#fluxia-widget-root .fluxia-form-input:focus{border-color:var(--fw-red);box-shadow:0 0 0 3px rgba(239,68,68,.1)}\n#fluxia-widget-root .fluxia-form-input.error{border-color:var(--fw-red);background:#fef2f2}\n#fluxia-widget-root .fluxia-form-error{font-size:11px;color:var(--fw-red);min-height:14px}\n#fluxia-widget-root .fluxia-form-submit{background:var(--fw-red-dark);color:#fff;border:none;border-radius:8px;padding:10px 20px;font-size:13.5px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .2s,transform .1s;margin-top:4px}\n#fluxia-widget-root .fluxia-form-submit:hover{background:var(--fw-red)}\n#fluxia-widget-root .fluxia-form-submit:active{transform:scale(.97)}\n#fluxia-widget-root .fluxia-form-submit:disabled{background:var(--fw-gray-300);cursor:not-allowed}\n#fluxia-widget-root .fluxia-day-divider{display:flex;align-items:center;gap:12px;padding:8px 0;color:var(--fw-text-light);font-size:11px;font-weight:500}\n#fluxia-widget-root .fluxia-day-divider::before,#fluxia-widget-root .fluxia-day-divider::after{content:\'\';flex:1;height:1px;background:var(--fw-gray-200)}\n#fluxia-widget-root .fluxia-input-area{padding:12px 14px;background:var(--fw-white);border-top:1px solid var(--fw-gray-200);display:flex;align-items:center;gap:10px;flex-shrink:0}\n#fluxia-widget-root .fluxia-input{flex:1;border:1.5px solid var(--fw-gray-300);border-radius:24px;padding:10px 16px;font-size:13px;font-family:inherit;outline:none;color:var(--fw-text);background:var(--fw-gray-bg);transition:border-color .2s,box-shadow .2s}\n#fluxia-widget-root .fluxia-input::placeholder{color:#9ca3af}\n#fluxia-widget-root .fluxia-input:focus{border-color:var(--fw-header);background:var(--fw-white);box-shadow:0 0 0 3px rgba(15,23,42,.06)}\n#fluxia-widget-root .fluxia-input:disabled{opacity:.5;cursor:not-allowed}\n#fluxia-widget-root .fluxia-send{width:40px;height:40px;min-width:40px;border-radius:50%;background:var(--fw-red);color:var(--fw-white);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s,transform .2s;flex-shrink:0}\n#fluxia-widget-root .fluxia-send:hover{background:var(--fw-red-dark);transform:scale(1.08)}\n#fluxia-widget-root .fluxia-send:active{transform:scale(.92)}\n#fluxia-widget-root .fluxia-send:disabled{opacity:.4;cursor:not-allowed;transform:none}\n#fluxia-widget-root .fluxia-send svg{width:18px;height:18px}\n#fluxia-widget-root .fluxia-powered{text-align:center;padding:8px 0;font-size:11px;color:#9ca3af;background:var(--fw-white);border-top:1px solid var(--fw-gray-200);flex-shrink:0}\n#fluxia-widget-root .fluxia-powered a{color:#6b7280;text-decoration:none;font-weight:600}\n#fluxia-widget-root[data-position="left"] .fluxia-bubble{left:24px;right:auto}\n#fluxia-widget-root[data-position="left"] .fluxia-chatbox{left:24px;right:auto;transform-origin:bottom left}\n@media(max-width:768px){#fluxia-widget-root .fluxia-chatbox{width:calc(100vw - 32px);max-width:400px;height:calc(100vh - 140px);max-height:600px;bottom:10px;right:10px}#fluxia-widget-root .fluxia-bubble{bottom:20px;right:16px;width:56px;height:56px}}\n@media(max-width:480px){#fluxia-widget-root .fluxia-chatbox{position:fixed;top:0;left:0;right:0;bottom:0;width:100%;height:100%;max-width:none;max-height:none;border-radius:0;transform-origin:bottom center}#fluxia-widget-root .fluxia-chatbox.fw-open{border-radius:0}#fluxia-widget-root .fluxia-header{border-radius:0}#fluxia-widget-root .fluxia-bubble{bottom:16px;right:16px;width:52px;height:52px}#fluxia-widget-root .fluxia-bubble svg{width:24px;height:24px}#fluxia-widget-root .fluxia-avatar{width:38px;height:38px;min-width:38px}#fluxia-widget-root .fluxia-title{font-size:14px}#fluxia-widget-root .fluxia-msg{max-width:88%;font-size:13px}#fluxia-widget-root .fluxia-input{font-size:14px}#fluxia-widget-root .fluxia-send{width:38px;height:38px;min-width:38px}}\n',document.head.appendChild(e)})();const s=e.theme||{},d=s.position||"right",f=e.botName||e.name||"Assistente",u=e.botAvatar||"",c=e.customCss||"",x=e.flows||[],p=document.createElement("div");if(p.id="fluxia-widget-root",p.dataset.position=d,p.innerHTML=function({botName:e,botAvatar:t}){return`\n <button class="fluxia-bubble" aria-label="Abrir chat" title="Abrir chat">\n <svg viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>\n </button>\n\n <div class="fluxia-chatbox" role="dialog" aria-label="Chat">\n <div class="fluxia-header">\n <div class="fluxia-header-info">\n <div class="fluxia-avatar">${t?`<img src="${l(t)}" alt="Avatar" />`:"🤖"}</div>\n <div class="fluxia-header-text">\n <div class="fluxia-title">${e}</div>\n <div class="fluxia-status">\n <span class="fluxia-status-dot"></span>\n Online agora\n </div>\n </div>\n </div>\n <button class="fluxia-close" aria-label="Fechar chat" title="Fechar">\n <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">\n <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>\n </svg>\n </button>\n </div>\n\n <div class="fluxia-messages"></div>\n\n <div class="fluxia-input-area">\n <input class="fluxia-input" type="text"\n placeholder="Digite sua mensagem..."\n autocomplete="off" />\n <button class="fluxia-send" title="Enviar">\n <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"\n stroke-linecap="round" stroke-linejoin="round">\n <line x1="22" y1="2" x2="11" y2="13"/>\n <polygon points="22 2 15 22 11 13 2 9 22 2"/>\n </svg>\n </button>\n </div>\n\n <div class="fluxia-powered">\n Powered by <a href="https://rowsky.com.br/" target="_blank" rel="noopener">Rowsky</a>\n </div>\n </div>\n `}({botName:l(f),botAvatar:u}),document.body.appendChild(p),c&&c.trim()){const e=document.createElement("style");e.id="fluxia-widget-custom",e.textContent=c,document.head.appendChild(e)}s.primaryColor&&(p.style.setProperty("--fw-red-dark",s.primaryColor),p.style.setProperty("--fw-red",s.primaryColor));const g=p.querySelector(".fluxia-bubble"),w=p.querySelector(".fluxia-chatbox"),m=p.querySelector(".fluxia-close"),b=p.querySelector(".fluxia-input"),h=p.querySelector(".fluxia-send"),v=p.querySelector(".fluxia-messages");let y=null,k=!1,E=!1;function C(){requestAnimationFrame(()=>{v.scrollTop=v.scrollHeight})}function I(e,t){const i=document.createElement("div");i.className=`fluxia-msg fluxia-msg-${e}`,"assistant"===e?i.innerHTML=(e=>{if(!e)return"";let t=l(e);return t=t.replace(/\*\*(.*?)\*\*/g,"<strong>$1</strong>"),t=t.replace(new RegExp("(?<!\\*)\\*(?!\\*)(.*?)(?<!\\*)\\*(?!\\*)","g"),"<em>$1</em>"),t=t.replace(/\n/g,"<br>"),t})(t):i.textContent=t,v.appendChild(i),C()}function L(){z();const e=document.createElement("div");e.className="fluxia-typing-wrapper",e.id="fluxia-typing-indicator",e.innerHTML='<div class="fluxia-typing-dot"></div><div class="fluxia-typing-dot"></div><div class="fluxia-typing-dot"></div>',v.appendChild(e),C()}function z(){const e=document.getElementById("fluxia-typing-indicator");e&&e.remove()}function S(e){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)}function T(e){return/^[\d\s\-+().]{7,20}$/.test(e)}function $(e){E=e,b.disabled=e,h.disabled=e,b.placeholder=e?"Aguarde...":"Digite sua mensagem..."}function j(){g.classList.add("fw-hidden"),w.classList.add("fw-open"),setTimeout(()=>{E||b.focus()},350),k||(k=!0,function(){if(function(){if(0===v.children.length){const e=document.createElement("div");e.className="fluxia-day-divider",e.textContent="Hoje",v.appendChild(e)}}(),!x.length)return e.initialMessage&&I("assistant",e.initialMessage),void $(!1);$(!0),y=n({flows:x,onMessage:e=>{I("assistant",e)},onOptions:(e,t)=>{!function(e,t){z();const i=document.createElement("div");i.className="fluxia-options",e.forEach(e=>{const a=document.createElement("button");a.className="fluxia-option-btn",a.textContent="string"==typeof e?e:e.label,a.addEventListener("click",()=>{const o="string"==typeof e?e:e.value||e.label;i.querySelectorAll(".fluxia-option-btn").forEach(e=>{e.classList.add("disabled")}),a.classList.remove("disabled"),a.classList.add("selected"),I("user",o),t(o)}),i.appendChild(a)}),v.appendChild(i),C()}(e,t)},onInput:(e,t)=>{!function(e,t){var i,a;z();const o=document.createElement("div");o.className="fluxia-form";const n={};e.forEach(e=>{const t=document.createElement("div");t.className="fluxia-form-field";const i=document.createElement("label");if(i.className="fluxia-form-label",i.textContent=e.label,e.required){const e=document.createElement("span");e.className="required",e.textContent=" *",i.appendChild(e)}const a=document.createElement("input");a.className="fluxia-form-input",a.type=e.type||"text",a.placeholder=e.placeholder||e.label||"",a.required=e.required,a.autocomplete=function(e){switch(e){case"email":return"email";case"tel":return"tel";case"name":return"name";default:return"off"}}(e.type);const r=document.createElement("div");r.className="fluxia-form-error",t.appendChild(i),t.appendChild(a),t.appendChild(r),o.appendChild(t),n[e.key]={input:a,error:r,config:e}});const r=document.createElement("button");if(r.className="fluxia-form-submit",r.textContent="Enviar",r.type="button",r.addEventListener("click",()=>{const e={};let i=!1;for(const[t,o]of Object.entries(n)){const a=o.input.value.trim();o.error.textContent="",o.input.classList.remove("error"),!o.config.required||a?"email"!==o.config.type||!a||S(a)?"tel"!==o.config.type||!a||T(a)?e[t]=a:(o.error.textContent="Telefone inválido",o.input.classList.add("error"),i=!0):(o.error.textContent="E-mail inválido",o.input.classList.add("error"),i=!0):(o.error.textContent=`${o.config.label} é obrigatório`,o.input.classList.add("error"),i=!0)}if(i)return;r.disabled=!0,r.textContent="Enviado ✓";for(const t of Object.values(n))t.input.disabled=!0;const a=Object.entries(e).filter(([,e])=>e).map(([,e])=>e).join(" • ");a&&I("user",a),t(e)}),1===e.length){const e=null==(i=Object.values(n)[0])?void 0:i.input;e&&e.addEventListener("keydown",e=>{"Enter"===e.key&&(e.preventDefault(),r.click())})}o.appendChild(r),v.appendChild(o),C();const l=null==(a=Object.values(n)[0])?void 0:a.input;l&&setTimeout(()=>l.focus(),100)}(e,t)},onTyping:e=>{e?L():z()},onFlowEnd:()=>{z(),$(!1),b.placeholder="Pergunte o que quiser..."},onLeadCapture:async e=>{try{await i.captureLead(t,e),console.log("[Fluxia] Lead capturado com sucesso")}catch(a){console.warn("[Fluxia] Erro ao capturar lead:",a.message)}},onVariableSet:(e,t)=>{console.log(`[Fluxia] Variável: ${e} = ${t}`)}}),y.start()}())}async function q(){var e,a;const o=b.value.trim();if(o&&!E){if(b.value="",y&&y.isWaiting()){if(y.handleUserMessage(o))return}I("user",o),$(!0),L();try{const n=await i.sendMessage(t,o);z();const r=(null==(e=null==n?void 0:n.reply)?void 0:e.content)||(null==n?void 0:n.reply)||(null==n?void 0:n.message)||(null==(a=null==n?void 0:n.data)?void 0:a.reply)||"Desculpe, não entendi. Pode reformular?";I("assistant","string"==typeof r?r:JSON.stringify(r))}catch(n){z(),console.error("[Fluxia]",n),401===n.status?I("system","Sessão expirada. Recarregue a página."):I("system","Erro ao enviar mensagem. Tente novamente.")}finally{$(!1),b.focus()}}}g.addEventListener("click",()=>j()),m.addEventListener("click",()=>(w.classList.remove("fw-open"),void setTimeout(()=>g.classList.remove("fw-hidden"),300))),h.addEventListener("click",q),b.addEventListener("keydown",e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),q())})},d="fluxia_visitor_id",f=document.currentScript,u=async()=>{const e=(()=>{const e=f||document.currentScript||document.querySelector("script[data-client-id], script[data-bot-id]");if(!e)return console.error("[Fluxia] Script tag não encontrada. Use data-client-id OU data-bot-id + data-public-key."),null;const t=e.getAttribute("data-bot-id"),i=e.getAttribute("data-public-key"),a=e.getAttribute("data-client-id"),o=e.getAttribute("data-api-base")||"";return o&&(a||t&&i)?{botId:t?Number(t):null,publicKey:i||"",clientId:a||"",apiBase:o.replace(/\/$/,"")}:(console.error("[Fluxia] Atributos obrigatorios: data-api-base e (data-client-id OU data-bot-id + data-public-key)"),null)})();if(!e)return;const t=(()=>{let e=null;try{e=localStorage.getItem(d)}catch{}if(!e){e="v_"+crypto.randomUUID();try{localStorage.setItem(d,e)}catch{}}return e})(),i=(e=>{const t=async(t,i,a=null,o=null)=>{const n={"Content-Type":"application/json"};o&&(n.Authorization=`Bearer ${o}`);const r={method:t,headers:n};a&&(r.body=JSON.stringify(a));const l=await fetch(`${e}${i}`,r);if(!l.ok){const e=await l.json().catch(()=>({})),t=new Error(e.error||`HTTP ${l.status}`);throw t.status=l.status,t.data=e,t}return l.json()};return{bootstrap:({clientId:e,visitorId:i})=>t("POST","/widget/bootstrap",{clientId:e,visitorId:i}),handshake:({botId:e,publicKey:i,visitorId:a})=>t("POST","/widget/handshake",{botId:e,publicKey:i,visitorId:a}),getConfig:e=>t("GET","/widget/config",null,e),sendMessage:(e,i)=>t("POST","/widget/message",{message:i},e),captureLead:(e,i)=>t("POST","/widget/lead",i,e)}})(e.apiBase);try{let a="",o=null;if(e.clientId){const n=await i.bootstrap({clientId:e.clientId,visitorId:t});if(!n||!n.token)return void console.error("[Fluxia] Bootstrap falhou. Widget nao sera renderizado.");a=n.token,o=n.config||null}else{const o=await i.handshake({botId:e.botId,publicKey:e.publicKey,visitorId:t});if(!o||!o.token)return void console.error("[Fluxia] Handshake falhou. Widget nao sera renderizado.");a=o.token}o||(o=await i.getConfig(a)),o.flows||(o.flows=[]),await s({config:o,token:a,api:i,visitorId:t,botId:e.botId||(null==o?void 0:o.botId)}),console.log("[Fluxia] Widget inicializado com sucesso.")}catch(a){console.error("[Fluxia] Erro na inicialização:",a.message||a)}};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",u):u()});
|