@getnexorai/sdk 0.1.1 → 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.
@@ -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
- appendBot(cfg.greeting);
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) appendBot(cfg.greeting);
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
- workflow_id: cfg.workflowId,
812
- session_id: sessionId,
813
- message,
814
- system_prompt: cfg.systemPrompt,
815
- client_prompt: cfg.clientPrompt,
816
- lead: Object.keys(state.pendingLead).length ? state.pendingLead : void 0,
817
- metadata: cfg.metadata
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
- window.clearTimeout(send.typingTimer);
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();
@@ -927,7 +962,7 @@ function initChat(client, options) {
927
962
  },
928
963
  destroy: () => {
929
964
  if (send.timer) window.clearTimeout(send.timer);
930
- if (send.typingTimer) window.clearTimeout(send.typingTimer);
965
+ if (greeting.timer) window.clearTimeout(greeting.timer);
931
966
  window.clearInterval(refreshTimer);
932
967
  root.remove();
933
968
  },
@@ -957,5 +992,5 @@ function readCaptureFields(panel) {
957
992
  }
958
993
 
959
994
  export { initChat };
960
- //# sourceMappingURL=chunk-YDWD2Q5S.js.map
961
- //# sourceMappingURL=chunk-YDWD2Q5S.js.map
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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\");\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 <= cfg.maxRetries; 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 < cfg.maxRetries) {
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 < cfg.maxRetries) {
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
- appendBot(cfg.greeting);
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) appendBot(cfg.greeting);
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
- workflow_id: cfg.workflowId,
1212
- session_id: sessionId,
1213
- message,
1214
- system_prompt: cfg.systemPrompt,
1215
- client_prompt: cfg.clientPrompt,
1216
- lead: Object.keys(state.pendingLead).length ? state.pendingLead : void 0,
1217
- metadata: cfg.metadata
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
- window.clearTimeout(send.typingTimer);
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();
@@ -1327,7 +1363,7 @@ function initChat(client, options) {
1327
1363
  },
1328
1364
  destroy: () => {
1329
1365
  if (send.timer) window.clearTimeout(send.timer);
1330
- if (send.typingTimer) window.clearTimeout(send.typingTimer);
1366
+ if (greeting.timer) window.clearTimeout(greeting.timer);
1331
1367
  window.clearInterval(refreshTimer);
1332
1368
  root.remove();
1333
1369
  },