@getnexorai/sdk 0.1.0 → 0.1.2
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/chat.cjs +62 -26
- package/dist/chat.cjs.map +1 -1
- package/dist/chat.d.cts +2 -2
- package/dist/chat.d.ts +2 -2
- package/dist/chat.js +1 -1
- package/dist/{chunk-ZQT7QEXV.js → chunk-NBE72ISL.js} +64 -28
- package/dist/chunk-NBE72ISL.js.map +1 -0
- package/dist/{config-eTstZgSV.d.cts → config-khGNZDHI.d.cts} +8 -0
- package/dist/{config-eTstZgSV.d.ts → config-khGNZDHI.d.ts} +8 -0
- package/dist/index.cjs +68 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/nexor.iife.js +8 -8
- package/dist/nexor.iife.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZQT7QEXV.js.map +0 -1
|
@@ -13,6 +13,7 @@ function applyDefaults(o) {
|
|
|
13
13
|
position: o.position ?? "bottom-right",
|
|
14
14
|
openOnLoad: o.openOnLoad ?? false,
|
|
15
15
|
debounceMs: o.debounceMs ?? 700,
|
|
16
|
+
requestTimeoutMs: o.requestTimeoutMs ?? 6e4,
|
|
16
17
|
container: o.container,
|
|
17
18
|
systemPrompt: o.systemPrompt,
|
|
18
19
|
clientPrompt: o.clientPrompt,
|
|
@@ -698,7 +699,6 @@ function escapeHtml(s) {
|
|
|
698
699
|
}
|
|
699
700
|
|
|
700
701
|
// src/chat/index.ts
|
|
701
|
-
var TYPING_DELAY_MS = 220;
|
|
702
702
|
function initChat(client, options) {
|
|
703
703
|
ensureBrowser();
|
|
704
704
|
if (!options || !options.workflowId) {
|
|
@@ -722,6 +722,14 @@ function initChat(client, options) {
|
|
|
722
722
|
pendingLead: { ...cfg.lead ?? {} },
|
|
723
723
|
history: []
|
|
724
724
|
};
|
|
725
|
+
const send = {
|
|
726
|
+
pending: [],
|
|
727
|
+
timer: 0,
|
|
728
|
+
inFlight: false,
|
|
729
|
+
typing: null
|
|
730
|
+
};
|
|
731
|
+
const greeting = { pending: false, timer: 0 };
|
|
732
|
+
const GREETING_DELAY_MS = 2e3;
|
|
725
733
|
launcher.el.addEventListener("click", toggle);
|
|
726
734
|
panel.closeBtn.addEventListener("click", close);
|
|
727
735
|
panel.form.addEventListener("submit", onComposerSubmit);
|
|
@@ -740,7 +748,7 @@ function initChat(client, options) {
|
|
|
740
748
|
root.setAttribute("data-open", "true");
|
|
741
749
|
clearUnread();
|
|
742
750
|
if (state.history.length === 0 && cfg.greeting && state.captured) {
|
|
743
|
-
|
|
751
|
+
scheduleGreeting();
|
|
744
752
|
}
|
|
745
753
|
panel.input.focus();
|
|
746
754
|
cfg.onOpen?.();
|
|
@@ -769,15 +777,8 @@ function initChat(client, options) {
|
|
|
769
777
|
panel.captureForm?.remove();
|
|
770
778
|
syncComposerLock();
|
|
771
779
|
panel.input.focus();
|
|
772
|
-
if (cfg.greeting && state.history.length === 0)
|
|
780
|
+
if (cfg.greeting && state.history.length === 0) scheduleGreeting();
|
|
773
781
|
}
|
|
774
|
-
const send = {
|
|
775
|
-
pending: [],
|
|
776
|
-
timer: 0,
|
|
777
|
-
typingTimer: 0,
|
|
778
|
-
inFlight: false,
|
|
779
|
-
typing: null
|
|
780
|
-
};
|
|
781
782
|
function onComposerSubmit(e) {
|
|
782
783
|
e.preventDefault();
|
|
783
784
|
const text = panel.input.value.trim();
|
|
@@ -790,11 +791,43 @@ function initChat(client, options) {
|
|
|
790
791
|
panel.captureInputs.first_name?.focus();
|
|
791
792
|
return;
|
|
792
793
|
}
|
|
794
|
+
cancelGreeting();
|
|
793
795
|
appendUser(text);
|
|
794
796
|
send.pending.push(text);
|
|
795
797
|
scheduleFlush();
|
|
796
798
|
}
|
|
799
|
+
function scheduleGreeting() {
|
|
800
|
+
if (greeting.pending) return;
|
|
801
|
+
greeting.pending = true;
|
|
802
|
+
showTyping();
|
|
803
|
+
greeting.timer = window.setTimeout(() => {
|
|
804
|
+
greeting.pending = false;
|
|
805
|
+
greeting.timer = 0;
|
|
806
|
+
hideTyping();
|
|
807
|
+
appendBot(cfg.greeting);
|
|
808
|
+
}, GREETING_DELAY_MS);
|
|
809
|
+
}
|
|
810
|
+
function cancelGreeting() {
|
|
811
|
+
if (!greeting.pending) return;
|
|
812
|
+
if (greeting.timer) window.clearTimeout(greeting.timer);
|
|
813
|
+
greeting.timer = 0;
|
|
814
|
+
greeting.pending = false;
|
|
815
|
+
hideTyping();
|
|
816
|
+
}
|
|
817
|
+
function showTyping() {
|
|
818
|
+
if (send.typing) {
|
|
819
|
+
panel.body.appendChild(send.typing);
|
|
820
|
+
scrollToBottom();
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
send.typing = appendNode(buildTypingIndicator());
|
|
824
|
+
}
|
|
825
|
+
function hideTyping() {
|
|
826
|
+
send.typing?.remove();
|
|
827
|
+
send.typing = null;
|
|
828
|
+
}
|
|
797
829
|
function scheduleFlush() {
|
|
830
|
+
showTyping();
|
|
798
831
|
if (send.timer) window.clearTimeout(send.timer);
|
|
799
832
|
send.timer = window.setTimeout(() => void flush(), cfg.debounceMs);
|
|
800
833
|
}
|
|
@@ -803,24 +836,27 @@ function initChat(client, options) {
|
|
|
803
836
|
const batch = send.pending.splice(0, send.pending.length);
|
|
804
837
|
const message = batch.join("\n");
|
|
805
838
|
send.inFlight = true;
|
|
806
|
-
send.typingTimer = window.setTimeout(() => {
|
|
807
|
-
send.typing = appendNode(buildTypingIndicator());
|
|
808
|
-
}, TYPING_DELAY_MS);
|
|
809
839
|
try {
|
|
810
|
-
const res = await client.chat(
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
840
|
+
const res = await client.chat(
|
|
841
|
+
{
|
|
842
|
+
workflow_id: cfg.workflowId,
|
|
843
|
+
session_id: sessionId,
|
|
844
|
+
message,
|
|
845
|
+
system_prompt: cfg.systemPrompt,
|
|
846
|
+
client_prompt: cfg.clientPrompt,
|
|
847
|
+
lead: Object.keys(state.pendingLead).length ? state.pendingLead : void 0,
|
|
848
|
+
metadata: cfg.metadata
|
|
849
|
+
},
|
|
850
|
+
// A chat turn is NOT idempotent (re-sending re-runs the LLM and
|
|
851
|
+
// re-inserts the message). Never retry; just give it a generous timeout.
|
|
852
|
+
{ timeoutMs: cfg.requestTimeoutMs, maxRetries: 0 }
|
|
853
|
+
);
|
|
819
854
|
if (res.lead_id && !state.leadId) {
|
|
820
855
|
state.leadId = res.lead_id;
|
|
821
856
|
cfg.onLeadCaptured?.(state.leadId);
|
|
822
857
|
}
|
|
823
858
|
const reply = (res.reply ?? "").trim();
|
|
859
|
+
hideTyping();
|
|
824
860
|
if (reply) {
|
|
825
861
|
appendBot(reply);
|
|
826
862
|
} else {
|
|
@@ -828,12 +864,11 @@ function initChat(client, options) {
|
|
|
828
864
|
cfg.onError?.(new Error("Nexor SDK: empty reply from server"));
|
|
829
865
|
}
|
|
830
866
|
} catch (err) {
|
|
867
|
+
hideTyping();
|
|
831
868
|
appendSystem(cfg.errorText);
|
|
832
869
|
cfg.onError?.(err);
|
|
833
870
|
} finally {
|
|
834
|
-
|
|
835
|
-
send.typing?.remove();
|
|
836
|
-
send.typing = null;
|
|
871
|
+
hideTyping();
|
|
837
872
|
send.inFlight = false;
|
|
838
873
|
panel.input.focus();
|
|
839
874
|
if (send.pending.length) scheduleFlush();
|
|
@@ -893,6 +928,7 @@ function initChat(client, options) {
|
|
|
893
928
|
}, 6e4);
|
|
894
929
|
function appendNode(node) {
|
|
895
930
|
panel.body.appendChild(node);
|
|
931
|
+
if (send.typing && send.typing !== node) panel.body.appendChild(send.typing);
|
|
896
932
|
scrollToBottom();
|
|
897
933
|
return node;
|
|
898
934
|
}
|
|
@@ -926,7 +962,7 @@ function initChat(client, options) {
|
|
|
926
962
|
},
|
|
927
963
|
destroy: () => {
|
|
928
964
|
if (send.timer) window.clearTimeout(send.timer);
|
|
929
|
-
if (
|
|
965
|
+
if (greeting.timer) window.clearTimeout(greeting.timer);
|
|
930
966
|
window.clearInterval(refreshTimer);
|
|
931
967
|
root.remove();
|
|
932
968
|
},
|
|
@@ -956,5 +992,5 @@ function readCaptureFields(panel) {
|
|
|
956
992
|
}
|
|
957
993
|
|
|
958
994
|
export { initChat };
|
|
959
|
-
//# sourceMappingURL=chunk-
|
|
960
|
-
//# sourceMappingURL=chunk-
|
|
995
|
+
//# sourceMappingURL=chunk-NBE72ISL.js.map
|
|
996
|
+
//# sourceMappingURL=chunk-NBE72ISL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/chat/config.ts","../src/chat/session.ts","../src/chat/styles.ts","../src/chat/dom.ts","../src/chat/index.ts"],"names":[],"mappings":"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","file":"chunk-NBE72ISL.js","sourcesContent":["/**\n * Chat widget configuration types + defaults.\n *\n * Kept separate from the widget's runtime code so other modules (and the\n * top-level `nexor.initChat()` re-export) can pull types without dragging in\n * the DOM construction code.\n */\nimport type { UUID } from \"../types\";\n\n// ─── Public option / handle types ──────────────────────────────────────────\n\nexport interface ChatLeadCapture {\n /** Which fields to ask for before chatting. Defaults to [\"first_name\", \"email\"]. */\n fields?: Array<\"first_name\" | \"last_name\" | \"email\" | \"phone\">;\n /** \"before\" gates chat behind the form; \"skip\" never asks. Default \"before\". */\n mode?: \"before\" | \"skip\";\n /** Label rendered above the form. */\n label?: string;\n /** Submit button label. */\n submitLabel?: string;\n}\n\nexport interface ChatInitOptions {\n /** Workflow that handles this chat conversation. Required. */\n workflowId: UUID;\n\n // ── Identity / branding ──\n title?: string;\n subtitle?: string;\n greeting?: string;\n /** Shown when a turn fails or the server returns no reply. */\n errorText?: string;\n /** BCP-47 locale for timestamps (e.g. \"es\", \"en-US\"). Defaults to the browser. */\n locale?: string;\n /** Hex colour for bubble + accents. Defaults to #111827. */\n accentColor?: string;\n /** Text colour rendered on top of the accent. Defaults to white. */\n accentTextColor?: string;\n /** Show the \"Powered by Nexor\" footer. Defaults to true. */\n showBranding?: boolean;\n\n // ── Behaviour ──\n position?: \"bottom-right\" | \"bottom-left\";\n /** Auto-open the panel on first load. Defaults to false. */\n openOnLoad?: boolean;\n /** Coalesce rapid-fire messages: wait this many ms of quiet, then send the\n * buffered messages as one combined turn. Defaults to 700. 0 = per message. */\n debounceMs?: number;\n /** Per-turn request timeout (ms). Agent turns can legitimately take 10-20s,\n * so this is generous (default 60000). Chat turns never retry — a timeout\n * surfaces the error once rather than re-sending the message. */\n requestTimeoutMs?: number;\n /** Append the widget to a custom element. Defaults to document.body. */\n container?: HTMLElement;\n\n // ── Prompt overrides forwarded to the agent for THIS widget instance ──\n systemPrompt?: string;\n clientPrompt?: string;\n\n // ── Lead linking ──\n /** Pre-known lead info (e.g. a logged-in user). Skips capture if complete. */\n lead?: {\n first_name?: string;\n last_name?: string;\n email?: string;\n phone?: string;\n metadata?: Record<string, unknown>;\n };\n capture?: ChatLeadCapture;\n /** Arbitrary metadata passed with each turn (page URL, UTM, …). */\n metadata?: Record<string, unknown>;\n\n // ── Callbacks ──\n onOpen?: () => void;\n onClose?: () => void;\n onMessage?: (msg: { role: \"user\" | \"bot\"; text: string }) => void;\n onLeadCaptured?: (leadId: UUID) => void;\n onError?: (err: Error) => void;\n}\n\nexport interface ChatHandle {\n open(): void;\n close(): void;\n toggle(): void;\n /** Programmatically send a user message. */\n send(text: string): Promise<void>;\n /** Tear down the widget and remove all DOM. */\n destroy(): void;\n /** Read the current session id (auto-generated on first open). */\n getSessionId(): string;\n}\n\n// ─── Internal resolved-config type ─────────────────────────────────────────\n\nexport interface ResolvedCfg {\n workflowId: UUID;\n title: string;\n subtitle: string;\n greeting: string;\n errorText: string;\n locale?: string;\n accentColor: string;\n accentTextColor: string;\n showBranding: boolean;\n position: \"bottom-right\" | \"bottom-left\";\n openOnLoad: boolean;\n debounceMs: number;\n requestTimeoutMs: number;\n container?: HTMLElement;\n systemPrompt?: string;\n clientPrompt?: string;\n lead?: ChatInitOptions[\"lead\"];\n capture: Required<ChatLeadCapture>;\n metadata?: Record<string, unknown>;\n onOpen?: () => void;\n onClose?: () => void;\n onMessage?: (msg: { role: \"user\" | \"bot\"; text: string }) => void;\n onLeadCaptured?: (leadId: UUID) => void;\n onError?: (err: Error) => void;\n}\n\n/**\n * Normalize user-provided options into a complete config object with every\n * field set. Downstream code reads from ResolvedCfg and never has to deal\n * with `undefined` for branding / behaviour fields.\n */\nexport function applyDefaults(o: ChatInitOptions): ResolvedCfg {\n return {\n workflowId: o.workflowId,\n title: o.title ?? \"Chat with us\",\n subtitle: o.subtitle ?? \"We typically reply in a few minutes\",\n greeting: o.greeting ?? \"Hi there! 👋 How can we help?\",\n errorText:\n o.errorText ?? \"Sorry, something went wrong. Please try again in a moment.\",\n locale:\n o.locale ??\n (typeof navigator !== \"undefined\" ? navigator.language : undefined),\n accentColor: o.accentColor ?? \"#111827\",\n accentTextColor: o.accentTextColor ?? \"#ffffff\",\n showBranding: o.showBranding ?? true,\n position: o.position ?? \"bottom-right\",\n openOnLoad: o.openOnLoad ?? false,\n debounceMs: o.debounceMs ?? 700,\n requestTimeoutMs: o.requestTimeoutMs ?? 60_000,\n container: o.container,\n systemPrompt: o.systemPrompt,\n clientPrompt: o.clientPrompt,\n lead: o.lead,\n capture: {\n fields: o.capture?.fields ?? [\"first_name\", \"email\"],\n mode: o.capture?.mode ?? \"before\",\n label: o.capture?.label ?? \"Tell us a bit about you to get started:\",\n submitLabel: o.capture?.submitLabel ?? \"Start chat\",\n },\n metadata: o.metadata,\n onOpen: o.onOpen,\n onClose: o.onClose,\n onMessage: o.onMessage,\n onLeadCaptured: o.onLeadCaptured,\n onError: o.onError,\n };\n}\n\n/**\n * Does this widget need to show a lead-capture form before chat?\n * - \"skip\" mode → no\n * - Pre-known lead already covers every required field → no\n * - Otherwise → yes\n */\nexport function needsCapture(cfg: ResolvedCfg): boolean {\n if (cfg.capture.mode === \"skip\") return false;\n if (cfg.lead) {\n const have = cfg.lead;\n return cfg.capture.fields.some((f) => !have[f]);\n }\n return true;\n}\n","/**\n * Browser-side session-id management for the chat widget.\n *\n * The session id links anonymous turns to the same lead across page reloads.\n * It's persisted in localStorage with a last-activity timestamp and a sliding\n * retention window: a conversation is kept for at least SESSION_RETENTION_MS\n * after the last activity, after which a fresh session (and transcript) starts.\n * If storage is unavailable (private mode, sandboxed iframe) we fall back to a\n * per-tab id and accept that a refresh starts a new conversation.\n */\n\nconst SESSION_KEY = \"nexor.chat.session_id\";\n\n/** Keep a conversation (session + transcript) this long after the last\n * activity, so a returning visitor can resume it. Reused by the transcript\n * cache in `./index`. The host (e.g. the landing widget) decides when to\n * auto-resume vs. prompt \"continue or start new\". */\nexport const SESSION_RETENTION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days\n\n/**\n * Return the persisted session id, generating one on first use or once the\n * retention window has lapsed. Reading it slides the window forward.\n */\nexport function ensureSessionId(): string {\n const now = Date.now();\n try {\n const rec = readRecord();\n if (rec && now - rec.ts < SESSION_RETENTION_MS) {\n writeRecord(rec.id, now); // slide the window on activity\n return rec.id;\n }\n } catch {\n /* localStorage may be unavailable (private mode, sandboxed iframe). */\n }\n const id = `sess_${randomHex(16)}`;\n try {\n writeRecord(id, now);\n } catch {\n /* ignore — id is still valid for this page load. */\n }\n return id;\n}\n\n/** Bump the session's last-activity timestamp (slides the retention window). */\nexport function touchSession(): void {\n try {\n const rec = readRecord();\n if (rec) writeRecord(rec.id, Date.now());\n } catch {\n /* ignore */\n }\n}\n\nfunction readRecord(): { id: string; ts: number } | null {\n const raw = window.localStorage.getItem(SESSION_KEY);\n if (!raw) return null;\n try {\n const o = JSON.parse(raw);\n if (o && typeof o.id === \"string\" && typeof o.ts === \"number\") return o;\n } catch {\n /* legacy value was a plain \"sess_…\" string — adopt it below. */\n }\n if (raw.startsWith(\"sess_\")) return { id: raw, ts: Date.now() };\n return null;\n}\n\nfunction writeRecord(id: string, ts: number): void {\n window.localStorage.setItem(SESSION_KEY, JSON.stringify({ id, ts }));\n}\n\n/**\n * Hex string from cryptographically-strong randomness when available, with\n * a Math.random() fallback for very old browsers.\n */\nfunction randomHex(bytes: number): string {\n const arr = new Uint8Array(bytes);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(arr);\n } else {\n for (let i = 0; i < bytes; i++) arr[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(arr, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n","/**\n * Scoped CSS for the chat widget.\n *\n * Everything is namespaced under `.nexor-chat` so it cannot leak into the host\n * page, and properties that hosts commonly override (color, font, line-height)\n * are set explicitly here so the host's reset can't bleed in.\n *\n * Animations are GPU-friendly (transform + opacity only — no layout-trashing\n * properties like top/left) and use modest durations so the widget feels\n * responsive on low-end devices.\n */\nexport const widgetCss = (accent: string, accentText: string): string => `\n.nexor-chat,\n.nexor-chat * {\n box-sizing: border-box;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n}\n\n.nexor-chat {\n position: fixed;\n z-index: 2147483640;\n bottom: 20px;\n right: 20px;\n font-size: 14px;\n line-height: 1.4;\n color: #111;\n}\n\n.nexor-chat[data-position=\"bottom-left\"] {\n right: auto;\n left: 20px;\n}\n\n/* ── Launcher bubble ─────────────────────────────────────────────── */\n.nexor-chat__launcher {\n width: 56px;\n height: 56px;\n border-radius: 9999px;\n background: ${accent};\n color: ${accentText};\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);\n transition: transform 160ms cubic-bezier(.2,.7,.2,1.1),\n box-shadow 160ms ease,\n background 200ms ease;\n padding: 0;\n position: relative;\n animation: nexor-launcher-in 360ms cubic-bezier(.2,.7,.2,1.1) both;\n}\n.nexor-chat__launcher:hover {\n transform: translateY(-2px) scale(1.04);\n box-shadow: 0 10px 24px rgba(0, 0, 0, 0.24);\n}\n.nexor-chat__launcher:active {\n transform: translateY(0) scale(0.97);\n transition-duration: 80ms;\n}\n.nexor-chat__launcher:focus-visible {\n outline: 2px solid ${accent};\n outline-offset: 2px;\n}\n.nexor-chat[data-open=\"true\"] .nexor-chat__launcher {\n transform: scale(0.85);\n opacity: 0.75;\n}\n.nexor-chat__launcher svg { width: 24px; height: 24px; transition: transform 200ms ease; }\n.nexor-chat[data-open=\"true\"] .nexor-chat__launcher svg { transform: rotate(8deg); }\n\n.nexor-chat__unread {\n position: absolute;\n top: -4px;\n right: -4px;\n min-width: 18px;\n height: 18px;\n background: #ef4444;\n color: #fff;\n border-radius: 9999px;\n font-size: 11px;\n font-weight: 600;\n padding: 0 5px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid #fff;\n animation: nexor-pop-in 300ms cubic-bezier(.2,.7,.2,1.1) both;\n}\n\n@keyframes nexor-launcher-in {\n from { transform: translateY(20px) scale(0.6); opacity: 0; }\n to { transform: translateY(0) scale(1); opacity: 1; }\n}\n@keyframes nexor-pop-in {\n from { transform: scale(0); }\n to { transform: scale(1); }\n}\n\n/* ── Panel ────────────────────────────────────────────────────────── */\n.nexor-chat__panel {\n position: absolute;\n bottom: 72px;\n right: 0;\n width: 360px;\n max-width: calc(100vw - 32px);\n height: 540px;\n max-height: calc(100vh - 120px);\n background: #fff;\n border-radius: 14px;\n box-shadow: 0 24px 60px rgba(0, 0, 0, 0.22);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n transform: translateY(14px) scale(0.96);\n opacity: 0;\n pointer-events: none;\n transition: transform 220ms cubic-bezier(.2,.7,.2,1.1),\n opacity 220ms ease;\n border: 1px solid rgba(0, 0, 0, 0.06);\n /* Use visibility so screen readers don't read collapsed content */\n visibility: hidden;\n}\n.nexor-chat[data-position=\"bottom-left\"] .nexor-chat__panel {\n right: auto;\n left: 0;\n}\n.nexor-chat[data-open=\"true\"] .nexor-chat__panel {\n transform: translateY(0) scale(1);\n opacity: 1;\n pointer-events: auto;\n visibility: visible;\n}\n\n.nexor-chat__header {\n background: ${accent};\n color: ${accentText};\n padding: 14px 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.nexor-chat__title {\n font-weight: 600;\n font-size: 15px;\n margin: 0;\n}\n.nexor-chat__subtitle {\n font-size: 12px;\n opacity: 0.85;\n margin: 2px 0 0;\n}\n.nexor-chat__status-dot {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 9999px;\n background: #34d399;\n margin-right: 6px;\n vertical-align: 1px;\n animation: nexor-pulse 2.4s ease-in-out infinite;\n box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.6);\n}\n@keyframes nexor-pulse {\n 0%, 100% { box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.55); }\n 50% { box-shadow: 0 0 0 6px rgba(52, 211, 153, 0); }\n}\n.nexor-chat__close {\n background: transparent;\n border: none;\n color: inherit;\n cursor: pointer;\n padding: 4px;\n border-radius: 6px;\n opacity: 0.9;\n transition: background 140ms ease, transform 140ms ease;\n}\n.nexor-chat__close:hover { background: rgba(255,255,255,0.18); opacity: 1; }\n.nexor-chat__close:active { transform: scale(0.92); }\n.nexor-chat__close svg { width: 18px; height: 18px; display: block; }\n\n/* ── Body / messages ──────────────────────────────────────────────── */\n.nexor-chat__body {\n flex: 1;\n overflow-y: auto;\n padding: 14px;\n background: #f6f7f9;\n display: flex;\n flex-direction: column;\n gap: 8px;\n scroll-behavior: smooth;\n /* Prevent layout shift when scrollbar appears */\n scrollbar-gutter: stable;\n}\n.nexor-chat__body::-webkit-scrollbar { width: 6px; }\n.nexor-chat__body::-webkit-scrollbar-thumb {\n background: rgba(0,0,0,0.15);\n border-radius: 9999px;\n}\n\n.nexor-chat__msg {\n max-width: 80%;\n padding: 8px 12px;\n border-radius: 14px;\n white-space: pre-wrap;\n word-wrap: break-word;\n animation: nexor-msg-in 240ms cubic-bezier(.2,.7,.2,1.1) both;\n will-change: transform, opacity;\n}\n.nexor-chat__msg--bot {\n background: #fff;\n align-self: flex-start;\n border: 1px solid rgba(0,0,0,0.06);\n border-bottom-left-radius: 4px;\n box-shadow: 0 1px 2px rgba(0,0,0,0.04);\n}\n.nexor-chat__msg--user {\n background: ${accent};\n color: ${accentText};\n align-self: flex-end;\n border-bottom-right-radius: 4px;\n animation-name: nexor-msg-in-user;\n}\n.nexor-chat__msg--system {\n background: transparent;\n color: #6b7280;\n font-size: 12px;\n align-self: center;\n text-align: center;\n max-width: 100%;\n animation-name: nexor-fade-in;\n}\n.nexor-chat__msg-time {\n display: block;\n margin-top: 4px;\n font-size: 10px;\n line-height: 1;\n opacity: 0.55;\n text-align: right;\n}\n.nexor-chat__msg--bot .nexor-chat__msg-time { text-align: left; }\n\n@keyframes nexor-msg-in {\n from { transform: translateY(8px) scale(0.96); opacity: 0; }\n to { transform: translateY(0) scale(1); opacity: 1; }\n}\n@keyframes nexor-msg-in-user {\n from { transform: translateY(8px) translateX(8px) scale(0.96); opacity: 0; }\n to { transform: translateY(0) translateX(0) scale(1); opacity: 1; }\n}\n@keyframes nexor-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.nexor-chat__typing {\n align-self: flex-start;\n background: #fff;\n border: 1px solid rgba(0,0,0,0.06);\n border-radius: 14px;\n border-bottom-left-radius: 4px;\n padding: 10px 14px;\n display: inline-flex;\n gap: 4px;\n animation: nexor-msg-in 240ms cubic-bezier(.2,.7,.2,1.1) both;\n}\n.nexor-chat__typing span {\n width: 6px; height: 6px; border-radius: 9999px;\n background: #9ca3af;\n display: inline-block;\n animation: nexor-blink 1.2s infinite ease-in-out;\n}\n.nexor-chat__typing span:nth-child(2) { animation-delay: 0.15s; }\n.nexor-chat__typing span:nth-child(3) { animation-delay: 0.30s; }\n@keyframes nexor-blink {\n 0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }\n 30% { opacity: 1; transform: translateY(-3px); }\n}\n\n/* ── Lead capture form ────────────────────────────────────────────── */\n.nexor-chat__capture {\n background: #fff;\n border: 1px solid rgba(0,0,0,0.06);\n border-radius: 12px;\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 4px;\n animation: nexor-msg-in 280ms cubic-bezier(.2,.7,.2,1.1) both;\n box-shadow: 0 2px 8px rgba(0,0,0,0.05);\n}\n.nexor-chat__capture-label {\n font-size: 12px;\n color: #6b7280;\n margin: 0;\n}\n.nexor-chat__capture-row {\n display: flex;\n gap: 8px;\n}\n.nexor-chat__capture-row > * { flex: 1; }\n.nexor-chat__input,\n.nexor-chat__capture input {\n font: inherit;\n padding: 8px 10px;\n border: 1px solid #d1d5db;\n border-radius: 8px;\n background: #fff;\n color: #111;\n outline: none;\n width: 100%;\n transition: border-color 140ms ease, box-shadow 140ms ease;\n}\n.nexor-chat__input:focus,\n.nexor-chat__capture input:focus {\n border-color: ${accent};\n box-shadow: 0 0 0 3px ${accent}33;\n}\n.nexor-chat__capture-submit {\n background: ${accent};\n color: ${accentText};\n border: none;\n border-radius: 8px;\n padding: 8px 12px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n transition: transform 140ms ease, filter 140ms ease;\n}\n.nexor-chat__capture-submit:hover { filter: brightness(1.08); }\n.nexor-chat__capture-submit:active { transform: scale(0.97); }\n.nexor-chat__capture-submit[disabled] { opacity: 0.6; cursor: not-allowed; }\n\n/* ── Composer ─────────────────────────────────────────────────────── */\n.nexor-chat__composer {\n display: flex;\n gap: 8px;\n padding: 10px;\n background: #fff;\n border-top: 1px solid rgba(0,0,0,0.06);\n align-items: stretch;\n}\n.nexor-chat__send {\n background: ${accent};\n color: ${accentText};\n border: none;\n border-radius: 8px;\n padding: 0 14px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n min-width: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform 140ms ease, filter 140ms ease;\n}\n.nexor-chat__send:hover { filter: brightness(1.08); }\n.nexor-chat__send:active { transform: scale(0.95); }\n.nexor-chat__send[disabled] {\n opacity: 0.6;\n cursor: not-allowed;\n transform: none;\n}\n.nexor-chat__send svg {\n width: 18px;\n height: 18px;\n transition: transform 200ms ease;\n}\n.nexor-chat__send:not([disabled]):hover svg { transform: translateX(2px); }\n\n/* Small spinner inside the send button while a turn is in flight */\n.nexor-chat__send--loading svg { display: none; }\n.nexor-chat__send--loading::after {\n content: \"\";\n width: 14px;\n height: 14px;\n border-radius: 9999px;\n border: 2px solid currentColor;\n border-top-color: transparent;\n animation: nexor-spin 700ms linear infinite;\n}\n@keyframes nexor-spin {\n to { transform: rotate(360deg); }\n}\n\n.nexor-chat__footer {\n text-align: center;\n font-size: 11px;\n color: #9ca3af;\n padding: 6px;\n background: #fff;\n border-top: 1px solid rgba(0,0,0,0.04);\n}\n.nexor-chat__footer a {\n color: inherit;\n text-decoration: none;\n font-weight: 600;\n}\n.nexor-chat__footer a:hover { text-decoration: underline; }\n\n/* Respect users who request reduced motion. */\n@media (prefers-reduced-motion: reduce) {\n .nexor-chat,\n .nexor-chat * {\n animation-duration: 0.001ms !important;\n transition-duration: 0.001ms !important;\n }\n .nexor-chat__status-dot { animation: none; }\n}\n`;\n","/**\n * Pure DOM construction for the chat widget.\n *\n * Each `build*` function returns a fully-formed sub-tree plus refs to the\n * inner nodes the orchestrator (initChat) wires events to. No side effects\n * here other than calling `injectStyles()` once per page.\n *\n * Why split this out from index.ts?\n * - The DOM layer doesn't depend on the NexorClient or sessionId — keeping\n * it pure makes the widget easy to reason about and easy to dry-run in\n * unit tests by mocking `document`.\n * - The orchestrator becomes a 150-line state machine instead of a\n * 600-line god-function.\n */\nimport { needsCapture, type ResolvedCfg } from \"./config\";\nimport { widgetCss } from \"./styles\";\n\nconst STYLE_ATTR = \"data-nexor-chat-styles\";\n\n// ─── Stylesheet (singleton per page) ───────────────────────────────────────\n\n/**\n * Inject the widget's stylesheet once per page. If multiple widgets mount,\n * they share styles and the first widget's accent colour wins. Two widgets\n * with different accents on one page is an unsupported use case.\n */\nexport function injectStyles(accent: string, accentText: string): void {\n if (document.querySelector(`style[${STYLE_ATTR}]`)) return;\n const style = document.createElement(\"style\");\n style.setAttribute(STYLE_ATTR, \"1\");\n style.textContent = widgetCss(accent, accentText);\n document.head.appendChild(style);\n}\n\n// ─── Launcher ──────────────────────────────────────────────────────────────\n\nexport interface LauncherRefs {\n el: HTMLButtonElement;\n badge: HTMLSpanElement;\n}\n\nexport function buildLauncher(): LauncherRefs {\n const el = document.createElement(\"button\");\n el.type = \"button\";\n el.className = \"nexor-chat__launcher\";\n el.setAttribute(\"aria-label\", \"Open chat\");\n // Speech-bubble icon — SVG rather than emoji for crisp rendering at any DPR.\n el.innerHTML = `\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M4 5.5C4 4.67 4.67 4 5.5 4h13c.83 0 1.5.67 1.5 1.5v9c0 .83-.67 1.5-1.5 1.5H9l-4 4V5.5Z\"\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linejoin=\"round\"/>\n </svg>\n `;\n const badge = document.createElement(\"span\");\n badge.className = \"nexor-chat__unread\";\n badge.style.display = \"none\";\n el.appendChild(badge);\n return { el, badge };\n}\n\n// ─── Panel ─────────────────────────────────────────────────────────────────\n\ntype CaptureField = \"first_name\" | \"last_name\" | \"email\" | \"phone\";\n\nexport interface PanelRefs {\n el: HTMLDivElement;\n closeBtn: HTMLButtonElement;\n body: HTMLDivElement;\n form: HTMLFormElement;\n input: HTMLInputElement;\n send: HTMLButtonElement;\n captureForm: HTMLFormElement | null;\n captureInputs: Partial<Record<CaptureField, HTMLInputElement>>;\n}\n\nexport function buildPanel(cfg: ResolvedCfg): PanelRefs {\n const el = document.createElement(\"div\");\n el.className = \"nexor-chat__panel\";\n el.setAttribute(\"role\", \"dialog\");\n el.setAttribute(\"aria-label\", cfg.title);\n\n const { header, closeBtn } = buildHeader(cfg);\n const { body, captureForm, captureInputs } = buildBody(cfg);\n const { form, input, send } = buildComposer();\n const footer = cfg.showBranding ? buildFooter() : null;\n\n el.appendChild(header);\n el.appendChild(body);\n el.appendChild(form);\n if (footer) el.appendChild(footer);\n\n return { el, closeBtn, body, form, input, send, captureForm, captureInputs };\n}\n\nfunction buildHeader(cfg: ResolvedCfg): {\n header: HTMLDivElement;\n closeBtn: HTMLButtonElement;\n} {\n const header = document.createElement(\"div\");\n header.className = \"nexor-chat__header\";\n\n // The status dot signals \"we're live and listening\" — purely cosmetic, but\n // a strong UX signal. Hidden from assistive tech because the title carries\n // the meaning already.\n const subtitleHtml = cfg.subtitle\n ? `<p class=\"nexor-chat__subtitle\">\n <span class=\"nexor-chat__status-dot\" aria-hidden=\"true\"></span>${escapeHtml(cfg.subtitle)}\n </p>`\n : \"\";\n\n header.innerHTML = `\n <div>\n <p class=\"nexor-chat__title\">${escapeHtml(cfg.title)}</p>\n ${subtitleHtml}\n </div>\n `;\n\n const closeBtn = document.createElement(\"button\");\n closeBtn.type = \"button\";\n closeBtn.className = \"nexor-chat__close\";\n closeBtn.setAttribute(\"aria-label\", \"Close chat\");\n closeBtn.innerHTML = `\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M6 6l12 12M6 18L18 6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n </svg>\n `;\n header.appendChild(closeBtn);\n return { header, closeBtn };\n}\n\nfunction buildBody(cfg: ResolvedCfg): {\n body: HTMLDivElement;\n captureForm: HTMLFormElement | null;\n captureInputs: Partial<Record<CaptureField, HTMLInputElement>>;\n} {\n const body = document.createElement(\"div\");\n body.className = \"nexor-chat__body\";\n const captureInputs: Partial<Record<CaptureField, HTMLInputElement>> = {};\n let captureForm: HTMLFormElement | null = null;\n\n if (needsCapture(cfg)) {\n captureForm = buildCaptureForm(cfg, captureInputs);\n body.appendChild(captureForm);\n }\n return { body, captureForm, captureInputs };\n}\n\nfunction buildCaptureForm(\n cfg: ResolvedCfg,\n inputsOut: Partial<Record<CaptureField, HTMLInputElement>>,\n): HTMLFormElement {\n const form = document.createElement(\"form\");\n form.className = \"nexor-chat__capture\";\n form.noValidate = true;\n\n const label = document.createElement(\"p\");\n label.className = \"nexor-chat__capture-label\";\n label.textContent = cfg.capture.label;\n form.appendChild(label);\n\n const fields = cfg.capture.fields;\n\n // first_name + last_name share a row to feel like a single \"your name\" prompt.\n if (fields.includes(\"first_name\") || fields.includes(\"last_name\")) {\n const row = document.createElement(\"div\");\n row.className = \"nexor-chat__capture-row\";\n if (fields.includes(\"first_name\")) {\n inputsOut.first_name = makeInput(\"first_name\", \"First name\", \"text\", cfg.lead?.first_name);\n row.appendChild(inputsOut.first_name);\n }\n if (fields.includes(\"last_name\")) {\n inputsOut.last_name = makeInput(\"last_name\", \"Last name\", \"text\", cfg.lead?.last_name);\n row.appendChild(inputsOut.last_name);\n }\n if (row.childElementCount > 0) form.appendChild(row);\n }\n if (fields.includes(\"email\")) {\n inputsOut.email = makeInput(\"email\", \"Email\", \"email\", cfg.lead?.email);\n form.appendChild(inputsOut.email);\n }\n if (fields.includes(\"phone\")) {\n inputsOut.phone = makeInput(\"phone\", \"Phone\", \"tel\", cfg.lead?.phone);\n form.appendChild(inputsOut.phone);\n }\n\n const submit = document.createElement(\"button\");\n submit.type = \"submit\";\n submit.className = \"nexor-chat__capture-submit\";\n submit.textContent = cfg.capture.submitLabel;\n form.appendChild(submit);\n\n return form;\n}\n\nfunction buildComposer(): {\n form: HTMLFormElement;\n input: HTMLInputElement;\n send: HTMLButtonElement;\n} {\n const form = document.createElement(\"form\");\n form.className = \"nexor-chat__composer\";\n\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.className = \"nexor-chat__input\";\n input.placeholder = \"Type a message…\";\n input.autocomplete = \"off\";\n\n const send = document.createElement(\"button\");\n send.type = \"submit\";\n send.className = \"nexor-chat__send\";\n send.setAttribute(\"aria-label\", \"Send message\");\n // Paper-airplane icon — language-agnostic, recognisable, replaces \"Send\" text.\n send.innerHTML = `\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M3.4 11.2 20.5 4.3c.7-.3 1.4.4 1.1 1.1l-6.9 17.1c-.3.8-1.4.8-1.8 0L10 14l-7.5-3c-.8-.3-.8-1.5 0-1.8Z\"\n stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linejoin=\"round\" fill=\"currentColor\"/>\n </svg>\n `;\n\n form.appendChild(input);\n form.appendChild(send);\n return { form, input, send };\n}\n\nfunction buildFooter(): HTMLDivElement {\n const el = document.createElement(\"div\");\n el.className = \"nexor-chat__footer\";\n el.innerHTML = `Powered by <a href=\"https://www.getnexor.ai\" target=\"_blank\" rel=\"noopener noreferrer\">Nexor</a>`;\n return el;\n}\n\n// ─── Message bubbles ───────────────────────────────────────────────────────\n\nexport type MessageRole = \"user\" | \"bot\" | \"system\";\n\n/**\n * Build a chat bubble. `textContent` is used (never innerHTML) so user input\n * is rendered as plain text — no XSS surface from message payloads.\n */\nexport function buildMessage(\n role: MessageRole,\n text: string,\n ts?: number,\n locale?: string,\n): HTMLDivElement {\n const el = document.createElement(\"div\");\n el.className = `nexor-chat__msg nexor-chat__msg--${role}`;\n const body = document.createElement(\"span\");\n body.className = \"nexor-chat__msg-text\";\n body.textContent = text;\n el.appendChild(body);\n // Relative time under user/bot messages (not system notices). `data-ts` lets\n // the widget refresh the label as time passes (see index.ts).\n if (role !== \"system\" && ts) {\n const time = document.createElement(\"time\");\n time.className = \"nexor-chat__msg-time\";\n time.dataset.ts = String(ts);\n time.textContent = formatRelativeTime(ts, locale);\n el.appendChild(time);\n }\n return el;\n}\n\n/**\n * Relative within the last week (\"ahora\", \"hace 3 minutos\", \"hace 2 horas\",\n * \"ayer\"), then an absolute date (\"12 may\"). Localized via Intl; falls back to\n * a plain clock time if Intl.RelativeTimeFormat is unavailable.\n */\nexport function formatRelativeTime(\n ts: number,\n locale?: string,\n now: number = Date.now(),\n): string {\n try {\n const diff = Math.max(0, now - ts);\n const min = Math.round(diff / 60000);\n const hr = Math.round(diff / 3600000);\n const day = Math.round(diff / 86400000);\n const rtf = new Intl.RelativeTimeFormat(locale, { numeric: \"auto\" });\n if (diff < 45000) return rtf.format(0, \"second\"); // \"ahora\" / \"now\"\n if (min < 60) return rtf.format(-min, \"minute\");\n if (hr < 24) return rtf.format(-hr, \"hour\");\n if (day < 7) return rtf.format(-day, \"day\"); // includes \"ayer\"/\"yesterday\"\n return new Date(ts).toLocaleDateString(locale, {\n day: \"numeric\",\n month: \"short\",\n });\n } catch {\n try {\n return new Date(ts).toLocaleTimeString(locale, {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n } catch {\n return \"\";\n }\n }\n}\n\n/** Three-dot typing indicator. Animation lives in styles.ts. */\nexport function buildTypingIndicator(): HTMLDivElement {\n const el = document.createElement(\"div\");\n el.className = \"nexor-chat__typing\";\n el.innerHTML = \"<span></span><span></span><span></span>\";\n return el;\n}\n\n// ─── Helpers ───────────────────────────────────────────────────────────────\n\nfunction makeInput(\n name: string,\n placeholder: string,\n type: string,\n value?: string,\n): HTMLInputElement {\n const i = document.createElement(\"input\");\n i.name = name;\n i.type = type;\n i.placeholder = placeholder;\n if (value) i.value = value;\n return i;\n}\n\n/**\n * Escape strings before interpolation into innerHTML. Used only for\n * SDK-controlled config values (title, subtitle) — user-typed messages\n * always go through textContent, which doesn't need escaping.\n */\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n","/**\n * Chat widget orchestrator.\n *\n * Responsibilities (everything else lives in sibling files):\n * - apply defaults / validate options (`./config`)\n * - manage session id (`./session`)\n * - build DOM (`./dom`)\n * - inject CSS (`./dom`)\n *\n * What stays here:\n * - the open/close state machine\n * - wiring DOM events to the NexorClient.chat() call\n * - lead-capture submission flow\n * - the public ChatHandle returned to callers\n */\nimport type { NexorClient } from \"../client\";\nimport type { UUID } from \"../types\";\nimport {\n applyDefaults,\n needsCapture,\n type ChatInitOptions,\n type ChatHandle,\n type ResolvedCfg,\n} from \"./config\";\nimport { ensureSessionId, touchSession, SESSION_RETENTION_MS } from \"./session\";\nimport {\n buildLauncher,\n buildMessage,\n buildPanel,\n buildTypingIndicator,\n formatRelativeTime,\n injectStyles,\n type LauncherRefs,\n type MessageRole,\n type PanelRefs,\n} from \"./dom\";\n\n// Re-export the public types so consumers can `import type { ChatHandle } from \"@getnexorai/sdk\"`.\nexport type { ChatInitOptions, ChatHandle, ChatLeadCapture } from \"./config\";\n\n/**\n * Mount the chat widget and return a handle for programmatic control.\n *\n * @param client The NexorClient instance that will send turns.\n * @param options User-facing widget configuration. See ChatInitOptions.\n * @throws If called outside a browser, or without a workflowId.\n */\nexport function initChat(\n client: NexorClient,\n options: ChatInitOptions,\n): ChatHandle {\n ensureBrowser();\n if (!options || !options.workflowId) {\n throw new Error(\"Nexor SDK: initChat() requires `workflowId`.\");\n }\n\n const cfg = applyDefaults(options);\n injectStyles(cfg.accentColor, cfg.accentTextColor);\n\n const sessionId = ensureSessionId();\n const historyKey = `nexor.chat.history.${sessionId}`;\n const root = buildRoot(cfg);\n const launcher = buildLauncher();\n const panel = buildPanel(cfg);\n root.appendChild(launcher.el);\n root.appendChild(panel.el);\n (cfg.container ?? document.body).appendChild(root);\n\n // ─── State (closure-private — no external observers needed) ─────────────\n const state = {\n isOpen: false,\n unread: 0,\n leadId: undefined as UUID | undefined,\n captured: !needsCapture(cfg),\n pendingLead: { ...(cfg.lead ?? {}) },\n history: [] as Array<{ role: \"user\" | \"bot\"; text: string; ts?: number }>,\n };\n\n // Composer/send state, hoisted above DOM wiring + history restore so the\n // call chain `restore → appendBubble → appendNode → send.typing` (and the\n // openOnLoad → appendBot greeting path) doesn't hit a TDZ. See the\n // debounced/coalesced send logic further down for how this is used.\n const send = {\n pending: [] as string[],\n timer: 0,\n inFlight: false,\n typing: null as HTMLElement | null,\n };\n\n // First-impression delay: the lazy-greet bubble used to appear instantly,\n // which read as canned. Show typing dots for ~2s first so the greeting feels\n // composed in the moment. Cancelled if the visitor sends before it fires.\n const greeting = { pending: false, timer: 0 };\n const GREETING_DELAY_MS = 2000;\n\n // ─── DOM event wiring ───────────────────────────────────────────────────\n launcher.el.addEventListener(\"click\", toggle);\n panel.closeBtn.addEventListener(\"click\", close);\n panel.form.addEventListener(\"submit\", onComposerSubmit);\n panel.captureForm?.addEventListener(\"submit\", onCaptureSubmit);\n\n syncComposerLock();\n\n // Restore a prior transcript (persisted up to SESSION_RETENTION_MS) so a\n // page reload keeps the conversation. Rendered into the (possibly closed)\n // panel; the greeting is then skipped because history is non-empty.\n const restored = loadHistory();\n if (restored.length) {\n state.history = restored;\n for (const m of restored) appendBubble(m.role === \"user\" ? \"user\" : \"bot\", m.text, m.ts);\n scrollToBottom();\n }\n\n if (cfg.openOnLoad) open();\n\n // ─── State machine ──────────────────────────────────────────────────────\n\n function open(): void {\n if (state.isOpen) return;\n state.isOpen = true;\n root.setAttribute(\"data-open\", \"true\");\n clearUnread();\n // Lazy-greet: first time the panel opens, drop the greeting message.\n if (state.history.length === 0 && cfg.greeting && state.captured) {\n scheduleGreeting();\n }\n panel.input.focus();\n cfg.onOpen?.();\n }\n\n function close(): void {\n if (!state.isOpen) return;\n state.isOpen = false;\n root.setAttribute(\"data-open\", \"false\");\n cfg.onClose?.();\n }\n\n function toggle(): void {\n state.isOpen ? close() : open();\n }\n\n // ─── Capture flow ───────────────────────────────────────────────────────\n\n function onCaptureSubmit(e: Event): void {\n e.preventDefault();\n const collected = readCaptureFields(panel);\n // Validate that all required fields are filled before unlocking the chat.\n const required = cfg.capture.fields;\n for (const f of required) {\n if (!collected[f]) {\n panel.captureInputs[f]?.focus();\n return;\n }\n }\n state.pendingLead = { ...state.pendingLead, ...collected };\n state.captured = true;\n panel.captureForm?.remove();\n syncComposerLock();\n panel.input.focus();\n // Greet once capture clears, so the greeting feels like a response to \"I'm ready\".\n if (cfg.greeting && state.history.length === 0) scheduleGreeting();\n }\n\n // ─── Composer / send (debounced + coalesced) ────────────────────────────\n //\n // Visitors fire several short messages in a row (WhatsApp-style). We mirror\n // the WhatsApp contract — batch at the source, one LLM call per batch — by\n // buffering rapid messages in the browser and POSTing them as a SINGLE\n // combined turn after a short quiet window. The composer never locks, so the\n // user can keep typing and sending freely; messages that land mid-request are\n // sent in the next batch. (`const send` itself is declared near `state`\n // above so DOM-wiring callbacks don't hit a TDZ on it.)\n\n function onComposerSubmit(e: Event): void {\n e.preventDefault();\n const text = panel.input.value.trim();\n if (!text) return;\n panel.input.value = \"\";\n queueMessage(text);\n }\n\n function queueMessage(text: string): void {\n if (!state.captured) {\n panel.captureInputs.first_name?.focus();\n return;\n }\n // Visitor engaged before the greeting fired — drop it. Showing a canned\n // greeting *after* their first real message would feel out of order.\n cancelGreeting();\n appendUser(text);\n send.pending.push(text);\n scheduleFlush();\n }\n\n function scheduleGreeting(): void {\n if (greeting.pending) return;\n greeting.pending = true;\n showTyping();\n greeting.timer = window.setTimeout(() => {\n greeting.pending = false;\n greeting.timer = 0;\n hideTyping();\n appendBot(cfg.greeting!);\n }, GREETING_DELAY_MS);\n }\n\n function cancelGreeting(): void {\n if (!greeting.pending) return;\n if (greeting.timer) window.clearTimeout(greeting.timer);\n greeting.timer = 0;\n greeting.pending = false;\n hideTyping();\n }\n\n // Show the typing dots the INSTANT a message is queued. We still hold the\n // actual send for the quiet window, but the visitor gets immediate \"I heard\n // you, I'm responding\" feedback — the dots persist across the debounce + the\n // request, so there's never a dead 2.5s gap after they hit send.\n function showTyping(): void {\n if (send.typing) {\n // Already up — keep it pinned below the latest message.\n panel.body.appendChild(send.typing);\n scrollToBottom();\n return;\n }\n send.typing = appendNode(buildTypingIndicator());\n }\n\n function hideTyping(): void {\n send.typing?.remove();\n send.typing = null;\n }\n\n function scheduleFlush(): void {\n showTyping();\n if (send.timer) window.clearTimeout(send.timer);\n send.timer = window.setTimeout(() => void flush(), cfg.debounceMs);\n }\n\n async function flush(): Promise<void> {\n if (send.inFlight || send.pending.length === 0) return;\n const batch = send.pending.splice(0, send.pending.length);\n const message = batch.join(\"\\n\");\n send.inFlight = true;\n\n try {\n const res = await client.chat(\n {\n workflow_id: cfg.workflowId,\n session_id: sessionId,\n message,\n system_prompt: cfg.systemPrompt,\n client_prompt: cfg.clientPrompt,\n lead: Object.keys(state.pendingLead).length ? state.pendingLead : undefined,\n metadata: cfg.metadata,\n },\n // A chat turn is NOT idempotent (re-sending re-runs the LLM and\n // re-inserts the message). Never retry; just give it a generous timeout.\n { timeoutMs: cfg.requestTimeoutMs, maxRetries: 0 },\n );\n if (res.lead_id && !state.leadId) {\n state.leadId = res.lead_id;\n cfg.onLeadCaptured?.(state.leadId);\n }\n const reply = (res.reply ?? \"\").trim();\n hideTyping(); // clear the dots right before the reply lands\n if (reply) {\n appendBot(reply);\n } else {\n // No text came back (empty completion / tool-only turn). Never render a\n // blank bubble — show the fallback and surface it to the host.\n appendSystem(cfg.errorText);\n cfg.onError?.(new Error(\"Nexor SDK: empty reply from server\"));\n }\n } catch (err) {\n hideTyping();\n appendSystem(cfg.errorText);\n cfg.onError?.(err as Error);\n } finally {\n hideTyping(); // safety net — never leave dangling dots\n send.inFlight = false;\n panel.input.focus();\n // Anything queued while the request was in flight goes out next.\n if (send.pending.length) scheduleFlush();\n }\n }\n\n // ─── Message rendering ──────────────────────────────────────────────────\n\n function appendUser(text: string): void {\n const ts = Date.now();\n appendBubble(\"user\", text, ts);\n state.history.push({ role: \"user\", text, ts });\n saveHistory();\n cfg.onMessage?.({ role: \"user\", text });\n }\n\n function appendBot(text: string): void {\n const ts = Date.now();\n appendBubble(\"bot\", text, ts);\n state.history.push({ role: \"bot\", text, ts });\n saveHistory();\n if (!state.isOpen) bumpUnread();\n cfg.onMessage?.({ role: \"bot\", text });\n }\n\n // ─── Transcript persistence (survives reloads up to SESSION_RETENTION_MS) ──\n\n function saveHistory(): void {\n try {\n window.localStorage.setItem(\n historyKey,\n JSON.stringify({ ts: Date.now(), history: state.history }),\n );\n touchSession(); // keep the session alive in step with the transcript\n } catch {\n /* storage unavailable — in-memory history still works for this load. */\n }\n }\n\n function loadHistory(): Array<{ role: \"user\" | \"bot\"; text: string; ts?: number }> {\n try {\n const raw = window.localStorage.getItem(historyKey);\n if (!raw) return [];\n const o = JSON.parse(raw);\n if (!o || !Array.isArray(o.history)) return [];\n if (Date.now() - (o.ts || 0) > SESSION_RETENTION_MS) {\n window.localStorage.removeItem(historyKey);\n return [];\n }\n return o.history;\n } catch {\n return [];\n }\n }\n\n function appendSystem(text: string): void {\n appendBubble(\"system\", text);\n }\n\n function appendBubble(\n role: MessageRole,\n text: string,\n ts?: number,\n ): HTMLDivElement {\n return appendNode(buildMessage(role, text, ts, cfg.locale));\n }\n\n // Keep relative timestamps (\"hace 3 minutos\") fresh while the panel is open.\n const refreshTimer = window.setInterval(() => {\n panel.body\n .querySelectorAll<HTMLTimeElement>(\".nexor-chat__msg-time[data-ts]\")\n .forEach((el) => {\n const ts = Number(el.dataset.ts);\n if (ts) el.textContent = formatRelativeTime(ts, cfg.locale);\n });\n }, 60_000);\n\n function appendNode<T extends HTMLElement>(node: T): T {\n panel.body.appendChild(node);\n // Keep the typing indicator pinned to the bottom: if the visitor fires\n // another message while we're awaiting a reply, the dots must stay BELOW\n // it (the bot hasn't \"typed\" yet), not stranded between messages.\n if (send.typing && send.typing !== node) panel.body.appendChild(send.typing);\n scrollToBottom();\n return node;\n }\n\n function scrollToBottom(): void {\n panel.body.scrollTop = panel.body.scrollHeight;\n }\n\n // ─── Unread badge ───────────────────────────────────────────────────────\n\n function bumpUnread(): void {\n state.unread++;\n launcher.badge.textContent = String(state.unread);\n launcher.badge.style.display = \"flex\";\n }\n function clearUnread(): void {\n state.unread = 0;\n launcher.badge.style.display = \"none\";\n }\n\n // ─── Composer lock (used while capture form is visible) ─────────────────\n\n function syncComposerLock(): void {\n const lock = !state.captured;\n panel.input.disabled = lock;\n panel.send.disabled = lock;\n panel.input.placeholder = lock\n ? \"Complete the form above to start…\"\n : \"Type a message…\";\n }\n\n // ─── Public handle ──────────────────────────────────────────────────────\n\n return {\n open,\n close,\n toggle,\n // Programmatic send goes through the same debounce/coalesce path. Resolves\n // immediately — the message is queued, not necessarily sent yet.\n send: (text: string) => {\n queueMessage(text);\n return Promise.resolve();\n },\n destroy: () => {\n if (send.timer) window.clearTimeout(send.timer);\n if (greeting.timer) window.clearTimeout(greeting.timer);\n window.clearInterval(refreshTimer);\n root.remove();\n },\n getSessionId: () => sessionId,\n };\n}\n\n// ─── Module-scoped helpers ────────────────────────────────────────────────\n\nfunction ensureBrowser(): void {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n throw new Error(\"Nexor SDK: initChat() requires a browser environment.\");\n }\n}\n\nfunction buildRoot(cfg: ResolvedCfg): HTMLDivElement {\n const root = document.createElement(\"div\");\n root.className = \"nexor-chat\";\n root.setAttribute(\"data-position\", cfg.position);\n root.setAttribute(\"data-open\", \"false\");\n return root;\n}\n\nfunction readCaptureFields(panel: PanelRefs): Record<string, string> {\n const out: Record<string, string> = {};\n const inputs = panel.captureInputs;\n if (inputs.first_name) out.first_name = inputs.first_name.value.trim();\n if (inputs.last_name) out.last_name = inputs.last_name.value.trim();\n if (inputs.email) out.email = inputs.email.value.trim();\n if (inputs.phone) out.phone = inputs.phone.value.trim();\n return out;\n}\n\n// Used by initChat's launcher → keeps the ref objects in scope for TS.\ntype _Refs = LauncherRefs & PanelRefs;\nvoid (null as unknown as _Refs);\n"]}
|
|
@@ -19,6 +19,10 @@ interface RequestOptions {
|
|
|
19
19
|
signal?: AbortSignal;
|
|
20
20
|
/** Override per-request timeout. */
|
|
21
21
|
timeoutMs?: number;
|
|
22
|
+
/** Override the client's retry count for this request. Set 0 to disable
|
|
23
|
+
* retries on non-idempotent calls (e.g. a chat turn, which re-runs the LLM
|
|
24
|
+
* and re-inserts the message if retried). */
|
|
25
|
+
maxRetries?: number;
|
|
22
26
|
/** Optional idempotency key (echoed back to server logs). */
|
|
23
27
|
idempotencyKey?: string;
|
|
24
28
|
}
|
|
@@ -401,6 +405,10 @@ interface ChatInitOptions {
|
|
|
401
405
|
/** Coalesce rapid-fire messages: wait this many ms of quiet, then send the
|
|
402
406
|
* buffered messages as one combined turn. Defaults to 700. 0 = per message. */
|
|
403
407
|
debounceMs?: number;
|
|
408
|
+
/** Per-turn request timeout (ms). Agent turns can legitimately take 10-20s,
|
|
409
|
+
* so this is generous (default 60000). Chat turns never retry — a timeout
|
|
410
|
+
* surfaces the error once rather than re-sending the message. */
|
|
411
|
+
requestTimeoutMs?: number;
|
|
404
412
|
/** Append the widget to a custom element. Defaults to document.body. */
|
|
405
413
|
container?: HTMLElement;
|
|
406
414
|
systemPrompt?: string;
|
|
@@ -19,6 +19,10 @@ interface RequestOptions {
|
|
|
19
19
|
signal?: AbortSignal;
|
|
20
20
|
/** Override per-request timeout. */
|
|
21
21
|
timeoutMs?: number;
|
|
22
|
+
/** Override the client's retry count for this request. Set 0 to disable
|
|
23
|
+
* retries on non-idempotent calls (e.g. a chat turn, which re-runs the LLM
|
|
24
|
+
* and re-inserts the message if retried). */
|
|
25
|
+
maxRetries?: number;
|
|
22
26
|
/** Optional idempotency key (echoed back to server logs). */
|
|
23
27
|
idempotencyKey?: string;
|
|
24
28
|
}
|
|
@@ -401,6 +405,10 @@ interface ChatInitOptions {
|
|
|
401
405
|
/** Coalesce rapid-fire messages: wait this many ms of quiet, then send the
|
|
402
406
|
* buffered messages as one combined turn. Defaults to 700. 0 = per message. */
|
|
403
407
|
debounceMs?: number;
|
|
408
|
+
/** Per-turn request timeout (ms). Agent turns can legitimately take 10-20s,
|
|
409
|
+
* so this is generous (default 60000). Chat turns never retry — a timeout
|
|
410
|
+
* surfaces the error once rather than re-sending the message. */
|
|
411
|
+
requestTimeoutMs?: number;
|
|
404
412
|
/** Append the widget to a custom element. Defaults to document.body. */
|
|
405
413
|
container?: HTMLElement;
|
|
406
414
|
systemPrompt?: string;
|
package/dist/index.cjs
CHANGED
|
@@ -93,8 +93,9 @@ async function request(cfg, req) {
|
|
|
93
93
|
body = JSON.stringify(req.body);
|
|
94
94
|
}
|
|
95
95
|
const timeoutMs = req.options?.timeoutMs ?? cfg.timeoutMs;
|
|
96
|
+
const maxRetries = req.options?.maxRetries ?? cfg.maxRetries;
|
|
96
97
|
let lastErr;
|
|
97
|
-
for (let attempt = 0; attempt <=
|
|
98
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
98
99
|
const controller = new AbortController();
|
|
99
100
|
const externalSignal = req.options?.signal;
|
|
100
101
|
const onAbort = () => controller.abort();
|
|
@@ -131,7 +132,7 @@ async function request(cfg, req) {
|
|
|
131
132
|
const errBody = await safeJson(res);
|
|
132
133
|
const message = pickMessage(errBody) ?? `HTTP ${res.status}`;
|
|
133
134
|
const err = makeError(res.status, message, errBody, requestId);
|
|
134
|
-
if (shouldRetry(res.status) && attempt <
|
|
135
|
+
if (shouldRetry(res.status) && attempt < maxRetries) {
|
|
135
136
|
await sleep(backoffMs(attempt, res));
|
|
136
137
|
lastErr = err;
|
|
137
138
|
continue;
|
|
@@ -144,7 +145,7 @@ async function request(cfg, req) {
|
|
|
144
145
|
const isAbort = err instanceof Error && (err.name === "AbortError" || /abort/i.test(err.message));
|
|
145
146
|
if (isAbort && externalSignal?.aborted) throw err;
|
|
146
147
|
lastErr = err;
|
|
147
|
-
if (attempt <
|
|
148
|
+
if (attempt < maxRetries) {
|
|
148
149
|
await sleep(backoffMs(attempt));
|
|
149
150
|
continue;
|
|
150
151
|
}
|
|
@@ -237,7 +238,7 @@ var NexorClient = class {
|
|
|
237
238
|
return request(this.cfg, {
|
|
238
239
|
method: "POST",
|
|
239
240
|
path: `${PUBLIC_PREFIX}/leads`,
|
|
240
|
-
body: input,
|
|
241
|
+
body: { ...input, skip_first_message: true },
|
|
241
242
|
options
|
|
242
243
|
});
|
|
243
244
|
}
|
|
@@ -246,7 +247,7 @@ var NexorClient = class {
|
|
|
246
247
|
return request(this.cfg, {
|
|
247
248
|
method: "POST",
|
|
248
249
|
path: `${PUBLIC_PREFIX}/leads`,
|
|
249
|
-
body: inputs,
|
|
250
|
+
body: inputs.map((i) => ({ ...i, skip_first_message: true })),
|
|
250
251
|
options
|
|
251
252
|
});
|
|
252
253
|
}
|
|
@@ -413,6 +414,7 @@ function applyDefaults(o) {
|
|
|
413
414
|
position: o.position ?? "bottom-right",
|
|
414
415
|
openOnLoad: o.openOnLoad ?? false,
|
|
415
416
|
debounceMs: o.debounceMs ?? 700,
|
|
417
|
+
requestTimeoutMs: o.requestTimeoutMs ?? 6e4,
|
|
416
418
|
container: o.container,
|
|
417
419
|
systemPrompt: o.systemPrompt,
|
|
418
420
|
clientPrompt: o.clientPrompt,
|
|
@@ -1098,7 +1100,6 @@ function escapeHtml(s) {
|
|
|
1098
1100
|
}
|
|
1099
1101
|
|
|
1100
1102
|
// src/chat/index.ts
|
|
1101
|
-
var TYPING_DELAY_MS = 220;
|
|
1102
1103
|
function initChat(client, options) {
|
|
1103
1104
|
ensureBrowser();
|
|
1104
1105
|
if (!options || !options.workflowId) {
|
|
@@ -1122,6 +1123,14 @@ function initChat(client, options) {
|
|
|
1122
1123
|
pendingLead: { ...cfg.lead ?? {} },
|
|
1123
1124
|
history: []
|
|
1124
1125
|
};
|
|
1126
|
+
const send = {
|
|
1127
|
+
pending: [],
|
|
1128
|
+
timer: 0,
|
|
1129
|
+
inFlight: false,
|
|
1130
|
+
typing: null
|
|
1131
|
+
};
|
|
1132
|
+
const greeting = { pending: false, timer: 0 };
|
|
1133
|
+
const GREETING_DELAY_MS = 2e3;
|
|
1125
1134
|
launcher.el.addEventListener("click", toggle);
|
|
1126
1135
|
panel.closeBtn.addEventListener("click", close);
|
|
1127
1136
|
panel.form.addEventListener("submit", onComposerSubmit);
|
|
@@ -1140,7 +1149,7 @@ function initChat(client, options) {
|
|
|
1140
1149
|
root.setAttribute("data-open", "true");
|
|
1141
1150
|
clearUnread();
|
|
1142
1151
|
if (state.history.length === 0 && cfg.greeting && state.captured) {
|
|
1143
|
-
|
|
1152
|
+
scheduleGreeting();
|
|
1144
1153
|
}
|
|
1145
1154
|
panel.input.focus();
|
|
1146
1155
|
cfg.onOpen?.();
|
|
@@ -1169,15 +1178,8 @@ function initChat(client, options) {
|
|
|
1169
1178
|
panel.captureForm?.remove();
|
|
1170
1179
|
syncComposerLock();
|
|
1171
1180
|
panel.input.focus();
|
|
1172
|
-
if (cfg.greeting && state.history.length === 0)
|
|
1181
|
+
if (cfg.greeting && state.history.length === 0) scheduleGreeting();
|
|
1173
1182
|
}
|
|
1174
|
-
const send = {
|
|
1175
|
-
pending: [],
|
|
1176
|
-
timer: 0,
|
|
1177
|
-
typingTimer: 0,
|
|
1178
|
-
inFlight: false,
|
|
1179
|
-
typing: null
|
|
1180
|
-
};
|
|
1181
1183
|
function onComposerSubmit(e) {
|
|
1182
1184
|
e.preventDefault();
|
|
1183
1185
|
const text = panel.input.value.trim();
|
|
@@ -1190,11 +1192,43 @@ function initChat(client, options) {
|
|
|
1190
1192
|
panel.captureInputs.first_name?.focus();
|
|
1191
1193
|
return;
|
|
1192
1194
|
}
|
|
1195
|
+
cancelGreeting();
|
|
1193
1196
|
appendUser(text);
|
|
1194
1197
|
send.pending.push(text);
|
|
1195
1198
|
scheduleFlush();
|
|
1196
1199
|
}
|
|
1200
|
+
function scheduleGreeting() {
|
|
1201
|
+
if (greeting.pending) return;
|
|
1202
|
+
greeting.pending = true;
|
|
1203
|
+
showTyping();
|
|
1204
|
+
greeting.timer = window.setTimeout(() => {
|
|
1205
|
+
greeting.pending = false;
|
|
1206
|
+
greeting.timer = 0;
|
|
1207
|
+
hideTyping();
|
|
1208
|
+
appendBot(cfg.greeting);
|
|
1209
|
+
}, GREETING_DELAY_MS);
|
|
1210
|
+
}
|
|
1211
|
+
function cancelGreeting() {
|
|
1212
|
+
if (!greeting.pending) return;
|
|
1213
|
+
if (greeting.timer) window.clearTimeout(greeting.timer);
|
|
1214
|
+
greeting.timer = 0;
|
|
1215
|
+
greeting.pending = false;
|
|
1216
|
+
hideTyping();
|
|
1217
|
+
}
|
|
1218
|
+
function showTyping() {
|
|
1219
|
+
if (send.typing) {
|
|
1220
|
+
panel.body.appendChild(send.typing);
|
|
1221
|
+
scrollToBottom();
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
send.typing = appendNode(buildTypingIndicator());
|
|
1225
|
+
}
|
|
1226
|
+
function hideTyping() {
|
|
1227
|
+
send.typing?.remove();
|
|
1228
|
+
send.typing = null;
|
|
1229
|
+
}
|
|
1197
1230
|
function scheduleFlush() {
|
|
1231
|
+
showTyping();
|
|
1198
1232
|
if (send.timer) window.clearTimeout(send.timer);
|
|
1199
1233
|
send.timer = window.setTimeout(() => void flush(), cfg.debounceMs);
|
|
1200
1234
|
}
|
|
@@ -1203,24 +1237,27 @@ function initChat(client, options) {
|
|
|
1203
1237
|
const batch = send.pending.splice(0, send.pending.length);
|
|
1204
1238
|
const message = batch.join("\n");
|
|
1205
1239
|
send.inFlight = true;
|
|
1206
|
-
send.typingTimer = window.setTimeout(() => {
|
|
1207
|
-
send.typing = appendNode(buildTypingIndicator());
|
|
1208
|
-
}, TYPING_DELAY_MS);
|
|
1209
1240
|
try {
|
|
1210
|
-
const res = await client.chat(
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1241
|
+
const res = await client.chat(
|
|
1242
|
+
{
|
|
1243
|
+
workflow_id: cfg.workflowId,
|
|
1244
|
+
session_id: sessionId,
|
|
1245
|
+
message,
|
|
1246
|
+
system_prompt: cfg.systemPrompt,
|
|
1247
|
+
client_prompt: cfg.clientPrompt,
|
|
1248
|
+
lead: Object.keys(state.pendingLead).length ? state.pendingLead : void 0,
|
|
1249
|
+
metadata: cfg.metadata
|
|
1250
|
+
},
|
|
1251
|
+
// A chat turn is NOT idempotent (re-sending re-runs the LLM and
|
|
1252
|
+
// re-inserts the message). Never retry; just give it a generous timeout.
|
|
1253
|
+
{ timeoutMs: cfg.requestTimeoutMs, maxRetries: 0 }
|
|
1254
|
+
);
|
|
1219
1255
|
if (res.lead_id && !state.leadId) {
|
|
1220
1256
|
state.leadId = res.lead_id;
|
|
1221
1257
|
cfg.onLeadCaptured?.(state.leadId);
|
|
1222
1258
|
}
|
|
1223
1259
|
const reply = (res.reply ?? "").trim();
|
|
1260
|
+
hideTyping();
|
|
1224
1261
|
if (reply) {
|
|
1225
1262
|
appendBot(reply);
|
|
1226
1263
|
} else {
|
|
@@ -1228,12 +1265,11 @@ function initChat(client, options) {
|
|
|
1228
1265
|
cfg.onError?.(new Error("Nexor SDK: empty reply from server"));
|
|
1229
1266
|
}
|
|
1230
1267
|
} catch (err) {
|
|
1268
|
+
hideTyping();
|
|
1231
1269
|
appendSystem(cfg.errorText);
|
|
1232
1270
|
cfg.onError?.(err);
|
|
1233
1271
|
} finally {
|
|
1234
|
-
|
|
1235
|
-
send.typing?.remove();
|
|
1236
|
-
send.typing = null;
|
|
1272
|
+
hideTyping();
|
|
1237
1273
|
send.inFlight = false;
|
|
1238
1274
|
panel.input.focus();
|
|
1239
1275
|
if (send.pending.length) scheduleFlush();
|
|
@@ -1293,6 +1329,7 @@ function initChat(client, options) {
|
|
|
1293
1329
|
}, 6e4);
|
|
1294
1330
|
function appendNode(node) {
|
|
1295
1331
|
panel.body.appendChild(node);
|
|
1332
|
+
if (send.typing && send.typing !== node) panel.body.appendChild(send.typing);
|
|
1296
1333
|
scrollToBottom();
|
|
1297
1334
|
return node;
|
|
1298
1335
|
}
|
|
@@ -1326,7 +1363,7 @@ function initChat(client, options) {
|
|
|
1326
1363
|
},
|
|
1327
1364
|
destroy: () => {
|
|
1328
1365
|
if (send.timer) window.clearTimeout(send.timer);
|
|
1329
|
-
if (
|
|
1366
|
+
if (greeting.timer) window.clearTimeout(greeting.timer);
|
|
1330
1367
|
window.clearInterval(refreshTimer);
|
|
1331
1368
|
root.remove();
|
|
1332
1369
|
},
|