@getnexorai/sdk 0.1.4 → 0.1.7

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.
@@ -277,6 +277,16 @@ interface IsPausedResponse {
277
277
  success: true;
278
278
  paused: boolean;
279
279
  }
280
+ /**
281
+ * A file the AI attached to a chat reply (File Library media). The API resolves
282
+ * the agent's handle(s) server-side and returns only the renderable contract —
283
+ * `image` renders inline, `document` (e.g. PDF) as a download link.
284
+ */
285
+ interface ChatMedia {
286
+ type: "image" | "document";
287
+ url: string;
288
+ filename?: string;
289
+ }
280
290
 
281
291
  /**
282
292
  * Low-level Nexor API client.
@@ -390,6 +400,8 @@ declare class NexorClient {
390
400
  workflow_run_id?: UUID;
391
401
  /** True when the API recognised an existing lead (returning visitor). */
392
402
  matched_existing?: boolean;
403
+ /** File(s) the AI attached to this reply (inline media channels). */
404
+ media?: ChatMedia[];
393
405
  }>;
394
406
  /**
395
407
  * Create (or upsert) a lead from the web chat widget and force its FIRST
@@ -640,4 +652,4 @@ interface ChatHandle {
640
652
  getSessionId(): string;
641
653
  }
642
654
 
643
- export { type AutomationInput as A, type BulkResultEntry as B, type Campaign as C, type ForceFirstMessage as F, type GetLeadHistoryParams as G, type ISODateString as I, type Lead as L, type MeetingNotesInput as M, NexorClient as N, type RequestOptions as R, type SendMessageInput as S, type UUID as U, type WhatsAppTemplate as W, type ChatChannelKey as a, type ChatChannels as b, type ChatHandle as c, type ChatInitOptions as d, type ChatLeadCapture as e, type ClientOptions as f, type CreateLeadBulkResponse as g, type CreateLeadInput as h, type CreateLeadResponse as i, type CreateMeetingInput as j, type GetLeadHistoryResponse as k, type GetLeadResponse as l, type IsPausedResponse as m, type LeadHistoryItem as n, type ListTemplatesParams as o, type ResumeAutomationInput as p, type SyncTagsInput as q, type SyncTagsResponse as r, type Workflow as s, type WorkflowRunStub as t };
655
+ export { type AutomationInput as A, type BulkResultEntry as B, type Campaign as C, type ForceFirstMessage as F, type GetLeadHistoryParams as G, type ISODateString as I, type Lead as L, type MeetingNotesInput as M, NexorClient as N, type RequestOptions as R, type SendMessageInput as S, type UUID as U, type WhatsAppTemplate as W, type ChatChannelKey as a, type ChatChannels as b, type ChatHandle as c, type ChatInitOptions as d, type ChatLeadCapture as e, type ChatMedia as f, type ClientOptions as g, type CreateLeadBulkResponse as h, type CreateLeadInput as i, type CreateLeadResponse as j, type CreateMeetingInput as k, type GetLeadHistoryResponse as l, type GetLeadResponse as m, type IsPausedResponse as n, type LeadHistoryItem as o, type ListTemplatesParams as p, type ResumeAutomationInput as q, type SyncTagsInput as r, type SyncTagsResponse as s, type Workflow as t, type WorkflowRunStub as u };
package/dist/index.cjs CHANGED
@@ -676,10 +676,21 @@ function randomHex(bytes) {
676
676
  }
677
677
 
678
678
  // src/chat/validate.ts
679
+ var EMAIL_RE = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
679
680
  function isValidEmail(value) {
680
681
  const s = (value || "").trim();
681
682
  if (s.length < 6 || s.length > 254) return false;
682
- return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(s);
683
+ const at = s.lastIndexOf("@");
684
+ if (at < 1) return false;
685
+ const local = s.slice(0, at);
686
+ const domain = s.slice(at + 1);
687
+ if (local.length > 64) return false;
688
+ if (local.startsWith(".") || local.endsWith(".") || local.includes("..")) {
689
+ return false;
690
+ }
691
+ if (domain.includes("..")) return false;
692
+ if (!/\.[a-zA-Z]{2,}$/.test(domain)) return false;
693
+ return EMAIL_RE.test(s);
683
694
  }
684
695
 
685
696
  // src/chat/template.ts
@@ -1140,6 +1151,40 @@ var widgetCss = (accent, accentText) => `
1140
1151
  }
1141
1152
  .nexor-chat__msg--bot .nexor-chat__msg-time { text-align: left; }
1142
1153
 
1154
+ /* File Library attachment: the image itself is the bubble (no padding/bg). */
1155
+ .nexor-chat__msg--media {
1156
+ padding: 0;
1157
+ background: transparent;
1158
+ border: none;
1159
+ box-shadow: none;
1160
+ overflow: hidden;
1161
+ }
1162
+ .nexor-chat__media-img {
1163
+ display: block;
1164
+ max-width: 100%;
1165
+ max-height: 260px;
1166
+ width: auto;
1167
+ height: auto;
1168
+ border-radius: 14px;
1169
+ border-bottom-left-radius: 4px;
1170
+ object-fit: cover;
1171
+ }
1172
+ .nexor-chat__media-doc {
1173
+ display: inline-flex;
1174
+ align-items: center;
1175
+ gap: 6px;
1176
+ padding: 8px 12px;
1177
+ border-radius: 14px;
1178
+ border-bottom-left-radius: 4px;
1179
+ background: #fff;
1180
+ border: 1px solid rgba(0,0,0,0.06);
1181
+ color: ${accent};
1182
+ text-decoration: none;
1183
+ font-size: 13px;
1184
+ font-weight: 500;
1185
+ }
1186
+ .nexor-chat__media-doc::before { content: "\\1F4CE"; }
1187
+
1143
1188
  @keyframes nexor-msg-in {
1144
1189
  from { transform: translateY(8px) scale(0.96); opacity: 0; }
1145
1190
  to { transform: translateY(0) scale(1); opacity: 1; }
@@ -1279,7 +1324,8 @@ var widgetCss = (accent, accentText) => `
1279
1324
  transition: border-color 140ms ease, box-shadow 140ms ease;
1280
1325
  }
1281
1326
  /* Composer textarea: auto-grows (height managed in JS) up to ~4 lines, then
1282
- scrolls. resize:none disables the manual drag handle. */
1327
+ scrolls. resize:none disables the manual drag handle. Filled style: neutral
1328
+ pill at rest that "lights up" white on focus \u2014 calmer than a glowing ring. */
1283
1329
  textarea.nexor-chat__input {
1284
1330
  display: block;
1285
1331
  resize: none;
@@ -1288,11 +1334,22 @@ textarea.nexor-chat__input {
1288
1334
  max-height: 98px; /* 4 lines (20\xD74) + 16 padding + 2 border */
1289
1335
  white-space: pre-wrap;
1290
1336
  word-break: break-word;
1337
+ background: #f3f4f6;
1338
+ border-color: transparent;
1339
+ border-radius: 19px;
1340
+ padding: 8px 14px;
1341
+ transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
1291
1342
  }
1343
+ textarea.nexor-chat__input::placeholder { color: #9ca3af; }
1292
1344
  .nexor-chat__input:focus,
1293
1345
  .nexor-chat__capture input:focus {
1294
1346
  border-color: ${accent};
1295
- box-shadow: 0 0 0 3px ${accent}33;
1347
+ box-shadow: 0 0 0 2px ${accent}1f;
1348
+ }
1349
+ textarea.nexor-chat__input:focus {
1350
+ background: #fff;
1351
+ border-color: ${accent}99;
1352
+ box-shadow: 0 1px 3px rgba(0,0,0,0.06);
1296
1353
  }
1297
1354
  .nexor-chat__input--error,
1298
1355
  .nexor-chat__capture input.nexor-chat__input--error {
@@ -1433,21 +1490,26 @@ textarea.nexor-chat__input {
1433
1490
  background: ${accent};
1434
1491
  color: ${accentText};
1435
1492
  border: none;
1436
- border-radius: 8px;
1437
- padding: 0 14px;
1493
+ border-radius: 9999px; /* circle, matches the pill composer */
1494
+ padding: 0;
1438
1495
  font: inherit;
1439
1496
  font-weight: 600;
1440
1497
  cursor: pointer;
1441
- min-width: 44px;
1498
+ width: 38px;
1442
1499
  height: 38px; /* matches the one-line textarea height */
1443
1500
  flex: none;
1444
1501
  display: flex;
1445
1502
  align-items: center;
1446
1503
  justify-content: center;
1447
- transition: transform 140ms ease, filter 140ms ease;
1504
+ box-shadow: 0 1px 3px rgba(0,0,0,0.12);
1505
+ transition: transform 140ms ease, filter 140ms ease, box-shadow 140ms ease;
1506
+ }
1507
+ .nexor-chat__send:hover {
1508
+ filter: brightness(1.08);
1509
+ box-shadow: 0 2px 6px rgba(0,0,0,0.18);
1510
+ transform: translateY(-1px);
1448
1511
  }
1449
- .nexor-chat__send:hover { filter: brightness(1.08); }
1450
- .nexor-chat__send:active { transform: scale(0.95); }
1512
+ .nexor-chat__send:active { transform: scale(0.93); }
1451
1513
  .nexor-chat__send[disabled] {
1452
1514
  opacity: 0.6;
1453
1515
  cursor: not-allowed;
@@ -1458,7 +1520,7 @@ textarea.nexor-chat__input {
1458
1520
  height: 18px;
1459
1521
  transition: transform 200ms ease;
1460
1522
  }
1461
- .nexor-chat__send:not([disabled]):hover svg { transform: translateX(2px); }
1523
+ .nexor-chat__send:not([disabled]):hover svg { transform: translateY(-1.5px); }
1462
1524
 
1463
1525
  /* Small spinner inside the send button while a turn is in flight */
1464
1526
  .nexor-chat__send--loading svg { display: none; }
@@ -1611,11 +1673,13 @@ textarea.nexor-chat__input {
1611
1673
  // src/chat/dom.ts
1612
1674
  var STYLE_ATTR = "data-nexor-chat-styles";
1613
1675
  function injectStyles(accent, accentText) {
1614
- if (document.querySelector(`style[${STYLE_ATTR}]`)) return;
1615
- const style = document.createElement("style");
1616
- style.setAttribute(STYLE_ATTR, "1");
1676
+ let style = document.querySelector(`style[${STYLE_ATTR}]`);
1677
+ if (!style) {
1678
+ style = document.createElement("style");
1679
+ style.setAttribute(STYLE_ATTR, "1");
1680
+ document.head.appendChild(style);
1681
+ }
1617
1682
  style.textContent = widgetCss(accent, accentText);
1618
- document.head.appendChild(style);
1619
1683
  }
1620
1684
  var CHAT_ICON = `
1621
1685
  <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
@@ -1978,8 +2042,8 @@ function buildComposer(cfg) {
1978
2042
  send.setAttribute("aria-label", "Send message");
1979
2043
  send.innerHTML = `
1980
2044
  <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
1981
- <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"
1982
- stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" fill="currentColor"/>
2045
+ <path d="M12 19V5.8M5.8 12 12 5.8 18.2 12"
2046
+ stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
1983
2047
  </svg>
1984
2048
  `;
1985
2049
  form.appendChild(input);
@@ -2021,6 +2085,28 @@ function buildMessage(role, text, ts, locale) {
2021
2085
  }
2022
2086
  return el;
2023
2087
  }
2088
+ function buildMediaMessage(item) {
2089
+ const el = document.createElement("div");
2090
+ el.className = "nexor-chat__msg nexor-chat__msg--bot nexor-chat__msg--media";
2091
+ if (item.type === "image") {
2092
+ const img = document.createElement("img");
2093
+ img.className = "nexor-chat__media-img";
2094
+ img.src = item.url;
2095
+ img.alt = item.filename || "";
2096
+ img.loading = "lazy";
2097
+ img.addEventListener("error", () => el.remove());
2098
+ el.appendChild(img);
2099
+ } else {
2100
+ const a = document.createElement("a");
2101
+ a.className = "nexor-chat__media-doc";
2102
+ a.href = item.url;
2103
+ a.target = "_blank";
2104
+ a.rel = "noopener noreferrer";
2105
+ a.textContent = item.filename || "Descargar archivo";
2106
+ el.appendChild(a);
2107
+ }
2108
+ return el;
2109
+ }
2024
2110
  function formatRelativeTime(ts, locale, now = Date.now()) {
2025
2111
  try {
2026
2112
  const diff = Math.max(0, now - ts);
@@ -2198,7 +2284,8 @@ function initChat(client, options) {
2198
2284
  setView("chat");
2199
2285
  renderResumeBanner();
2200
2286
  for (const m of persisted.history) {
2201
- appendBubble(m.role === "user" ? "user" : "bot", m.text, m.ts);
2287
+ if (m.media) appendNode(buildMediaMessage(m.media));
2288
+ else appendBubble(m.role === "user" ? "user" : "bot", m.text, m.ts);
2202
2289
  }
2203
2290
  scrollToBottom();
2204
2291
  }
@@ -2731,9 +2818,12 @@ function initChat(client, options) {
2731
2818
  renderResumeBanner(true);
2732
2819
  }
2733
2820
  const reply = (res.reply ?? "").trim();
2821
+ const hasMedia = Array.isArray(res.media) && res.media.length > 0;
2734
2822
  hideTyping();
2735
2823
  if (reply) {
2736
- appendBotReply(reply);
2824
+ appendBotReply(reply, hasMedia ? () => appendBotMedia(res.media) : void 0);
2825
+ } else if (hasMedia) {
2826
+ appendBotMedia(res.media);
2737
2827
  } else {
2738
2828
  appendSystem(cfg.errorText);
2739
2829
  cfg.onError?.(new Error("Nexor SDK: empty reply from server"));
@@ -2768,10 +2858,21 @@ function initChat(client, options) {
2768
2858
  if (!state.isOpen) bumpUnread();
2769
2859
  cfg.onMessage?.({ role: "bot", text });
2770
2860
  }
2771
- function appendBotReply(text) {
2861
+ function appendBotMedia(items) {
2862
+ if (!Array.isArray(items) || items.length === 0) return;
2863
+ for (const item of items) {
2864
+ if (!item || typeof item.url !== "string" || !item.url) continue;
2865
+ appendNode(buildMediaMessage(item));
2866
+ state.history.push({ role: "bot", text: "", ts: Date.now(), media: item });
2867
+ }
2868
+ saveHistory();
2869
+ if (!state.isOpen) bumpUnread();
2870
+ }
2871
+ function appendBotReply(text, after) {
2772
2872
  const parts = cfg.splitReplies ? splitReply(text) : [text];
2773
2873
  if (parts.length <= 1) {
2774
2874
  appendBot(text);
2875
+ after?.();
2775
2876
  return;
2776
2877
  }
2777
2878
  const [first, ...rest] = parts;
@@ -2779,7 +2880,10 @@ function initChat(client, options) {
2779
2880
  let i = 0;
2780
2881
  const sendNext = () => {
2781
2882
  const part = rest[i];
2782
- if (part === void 0) return;
2883
+ if (part === void 0) {
2884
+ after?.();
2885
+ return;
2886
+ }
2783
2887
  const isLast = i === rest.length - 1;
2784
2888
  i++;
2785
2889
  showTyping();