rowsky-chatbot-widget 1.0.9 → 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 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:#fef3c7;color:#92400e;font-size:12px;padding:8px 14px;border-radius:8px;text-align:center;max-width:90%;border:1px solid #fde68a}
67
- #fluxia-widget-root .fluxia-typing{display:flex;gap:5px;align-items:center;padding:4px 0}
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="Pergunte sobre leads, métricas ou integrações"
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
- if (config.initialMessage) {
173
- addMsg("assistant", config.initialMessage);
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
- bubble.addEventListener("click", () => open());
176
- closeBtn.addEventListener("click", () => close());
177
- sendBtn.addEventListener("click", send);
178
- input.addEventListener("keydown", (e) => {
179
- if (e.key === "Enter" && !e.shiftKey) {
180
- e.preventDefault();
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(() => input.focus(), 350);
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 addMsg(role, content) {
194
- const div = document.createElement("div");
195
- div.className = `fluxia-msg fluxia-msg-${role}`;
196
- div.textContent = content;
197
- messages.appendChild(div);
198
- messages.scrollTop = messages.scrollHeight;
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
- input.disabled = true;
205
- sendBtn.disabled = true;
703
+ if (flowEngine && flowEngine.isWaiting()) {
704
+ const handled = flowEngine.handleUserMessage(text);
705
+ if (handled) return;
706
+ }
206
707
  addMsg("user", text);
207
- const loader = document.createElement("div");
208
- loader.className = "fluxia-msg fluxia-msg-assistant";
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
- loader.remove();
215
- if (result.reply) {
216
- addMsg("assistant", result.reply.content);
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
- loader.remove();
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
- input.disabled = false;
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,
@@ -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()});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rowsky-chatbot-widget",
3
- "version": "1.0.9",
3
+ "version": "1.1.0",
4
4
  "description": "Widget público do chatbot Fluxia para embed via script tag",
5
5
  "main": "dist/chatbot.umd.js",
6
6
  "module": "dist/chatbot.es.js",