@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.
package/dist/chat.cjs CHANGED
@@ -237,10 +237,21 @@ function randomHex(bytes) {
237
237
  }
238
238
 
239
239
  // src/chat/validate.ts
240
+ 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])?)+$/;
240
241
  function isValidEmail(value) {
241
242
  const s = (value || "").trim();
242
243
  if (s.length < 6 || s.length > 254) return false;
243
- return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(s);
244
+ const at = s.lastIndexOf("@");
245
+ if (at < 1) return false;
246
+ const local = s.slice(0, at);
247
+ const domain = s.slice(at + 1);
248
+ if (local.length > 64) return false;
249
+ if (local.startsWith(".") || local.endsWith(".") || local.includes("..")) {
250
+ return false;
251
+ }
252
+ if (domain.includes("..")) return false;
253
+ if (!/\.[a-zA-Z]{2,}$/.test(domain)) return false;
254
+ return EMAIL_RE.test(s);
244
255
  }
245
256
 
246
257
  // src/chat/template.ts
@@ -701,6 +712,40 @@ var widgetCss = (accent, accentText) => `
701
712
  }
702
713
  .nexor-chat__msg--bot .nexor-chat__msg-time { text-align: left; }
703
714
 
715
+ /* File Library attachment: the image itself is the bubble (no padding/bg). */
716
+ .nexor-chat__msg--media {
717
+ padding: 0;
718
+ background: transparent;
719
+ border: none;
720
+ box-shadow: none;
721
+ overflow: hidden;
722
+ }
723
+ .nexor-chat__media-img {
724
+ display: block;
725
+ max-width: 100%;
726
+ max-height: 260px;
727
+ width: auto;
728
+ height: auto;
729
+ border-radius: 14px;
730
+ border-bottom-left-radius: 4px;
731
+ object-fit: cover;
732
+ }
733
+ .nexor-chat__media-doc {
734
+ display: inline-flex;
735
+ align-items: center;
736
+ gap: 6px;
737
+ padding: 8px 12px;
738
+ border-radius: 14px;
739
+ border-bottom-left-radius: 4px;
740
+ background: #fff;
741
+ border: 1px solid rgba(0,0,0,0.06);
742
+ color: ${accent};
743
+ text-decoration: none;
744
+ font-size: 13px;
745
+ font-weight: 500;
746
+ }
747
+ .nexor-chat__media-doc::before { content: "\\1F4CE"; }
748
+
704
749
  @keyframes nexor-msg-in {
705
750
  from { transform: translateY(8px) scale(0.96); opacity: 0; }
706
751
  to { transform: translateY(0) scale(1); opacity: 1; }
@@ -840,7 +885,8 @@ var widgetCss = (accent, accentText) => `
840
885
  transition: border-color 140ms ease, box-shadow 140ms ease;
841
886
  }
842
887
  /* Composer textarea: auto-grows (height managed in JS) up to ~4 lines, then
843
- scrolls. resize:none disables the manual drag handle. */
888
+ scrolls. resize:none disables the manual drag handle. Filled style: neutral
889
+ pill at rest that "lights up" white on focus \u2014 calmer than a glowing ring. */
844
890
  textarea.nexor-chat__input {
845
891
  display: block;
846
892
  resize: none;
@@ -849,11 +895,22 @@ textarea.nexor-chat__input {
849
895
  max-height: 98px; /* 4 lines (20\xD74) + 16 padding + 2 border */
850
896
  white-space: pre-wrap;
851
897
  word-break: break-word;
898
+ background: #f3f4f6;
899
+ border-color: transparent;
900
+ border-radius: 19px;
901
+ padding: 8px 14px;
902
+ transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
852
903
  }
904
+ textarea.nexor-chat__input::placeholder { color: #9ca3af; }
853
905
  .nexor-chat__input:focus,
854
906
  .nexor-chat__capture input:focus {
855
907
  border-color: ${accent};
856
- box-shadow: 0 0 0 3px ${accent}33;
908
+ box-shadow: 0 0 0 2px ${accent}1f;
909
+ }
910
+ textarea.nexor-chat__input:focus {
911
+ background: #fff;
912
+ border-color: ${accent}99;
913
+ box-shadow: 0 1px 3px rgba(0,0,0,0.06);
857
914
  }
858
915
  .nexor-chat__input--error,
859
916
  .nexor-chat__capture input.nexor-chat__input--error {
@@ -994,21 +1051,26 @@ textarea.nexor-chat__input {
994
1051
  background: ${accent};
995
1052
  color: ${accentText};
996
1053
  border: none;
997
- border-radius: 8px;
998
- padding: 0 14px;
1054
+ border-radius: 9999px; /* circle, matches the pill composer */
1055
+ padding: 0;
999
1056
  font: inherit;
1000
1057
  font-weight: 600;
1001
1058
  cursor: pointer;
1002
- min-width: 44px;
1059
+ width: 38px;
1003
1060
  height: 38px; /* matches the one-line textarea height */
1004
1061
  flex: none;
1005
1062
  display: flex;
1006
1063
  align-items: center;
1007
1064
  justify-content: center;
1008
- transition: transform 140ms ease, filter 140ms ease;
1065
+ box-shadow: 0 1px 3px rgba(0,0,0,0.12);
1066
+ transition: transform 140ms ease, filter 140ms ease, box-shadow 140ms ease;
1067
+ }
1068
+ .nexor-chat__send:hover {
1069
+ filter: brightness(1.08);
1070
+ box-shadow: 0 2px 6px rgba(0,0,0,0.18);
1071
+ transform: translateY(-1px);
1009
1072
  }
1010
- .nexor-chat__send:hover { filter: brightness(1.08); }
1011
- .nexor-chat__send:active { transform: scale(0.95); }
1073
+ .nexor-chat__send:active { transform: scale(0.93); }
1012
1074
  .nexor-chat__send[disabled] {
1013
1075
  opacity: 0.6;
1014
1076
  cursor: not-allowed;
@@ -1019,7 +1081,7 @@ textarea.nexor-chat__input {
1019
1081
  height: 18px;
1020
1082
  transition: transform 200ms ease;
1021
1083
  }
1022
- .nexor-chat__send:not([disabled]):hover svg { transform: translateX(2px); }
1084
+ .nexor-chat__send:not([disabled]):hover svg { transform: translateY(-1.5px); }
1023
1085
 
1024
1086
  /* Small spinner inside the send button while a turn is in flight */
1025
1087
  .nexor-chat__send--loading svg { display: none; }
@@ -1172,11 +1234,13 @@ textarea.nexor-chat__input {
1172
1234
  // src/chat/dom.ts
1173
1235
  var STYLE_ATTR = "data-nexor-chat-styles";
1174
1236
  function injectStyles(accent, accentText) {
1175
- if (document.querySelector(`style[${STYLE_ATTR}]`)) return;
1176
- const style = document.createElement("style");
1177
- style.setAttribute(STYLE_ATTR, "1");
1237
+ let style = document.querySelector(`style[${STYLE_ATTR}]`);
1238
+ if (!style) {
1239
+ style = document.createElement("style");
1240
+ style.setAttribute(STYLE_ATTR, "1");
1241
+ document.head.appendChild(style);
1242
+ }
1178
1243
  style.textContent = widgetCss(accent, accentText);
1179
- document.head.appendChild(style);
1180
1244
  }
1181
1245
  var CHAT_ICON = `
1182
1246
  <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
@@ -1539,8 +1603,8 @@ function buildComposer(cfg) {
1539
1603
  send.setAttribute("aria-label", "Send message");
1540
1604
  send.innerHTML = `
1541
1605
  <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
1542
- <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"
1543
- stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" fill="currentColor"/>
1606
+ <path d="M12 19V5.8M5.8 12 12 5.8 18.2 12"
1607
+ stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
1544
1608
  </svg>
1545
1609
  `;
1546
1610
  form.appendChild(input);
@@ -1582,6 +1646,28 @@ function buildMessage(role, text, ts, locale) {
1582
1646
  }
1583
1647
  return el;
1584
1648
  }
1649
+ function buildMediaMessage(item) {
1650
+ const el = document.createElement("div");
1651
+ el.className = "nexor-chat__msg nexor-chat__msg--bot nexor-chat__msg--media";
1652
+ if (item.type === "image") {
1653
+ const img = document.createElement("img");
1654
+ img.className = "nexor-chat__media-img";
1655
+ img.src = item.url;
1656
+ img.alt = item.filename || "";
1657
+ img.loading = "lazy";
1658
+ img.addEventListener("error", () => el.remove());
1659
+ el.appendChild(img);
1660
+ } else {
1661
+ const a = document.createElement("a");
1662
+ a.className = "nexor-chat__media-doc";
1663
+ a.href = item.url;
1664
+ a.target = "_blank";
1665
+ a.rel = "noopener noreferrer";
1666
+ a.textContent = item.filename || "Descargar archivo";
1667
+ el.appendChild(a);
1668
+ }
1669
+ return el;
1670
+ }
1585
1671
  function formatRelativeTime(ts, locale, now = Date.now()) {
1586
1672
  try {
1587
1673
  const diff = Math.max(0, now - ts);
@@ -1759,7 +1845,8 @@ function initChat(client, options) {
1759
1845
  setView("chat");
1760
1846
  renderResumeBanner();
1761
1847
  for (const m of persisted.history) {
1762
- appendBubble(m.role === "user" ? "user" : "bot", m.text, m.ts);
1848
+ if (m.media) appendNode(buildMediaMessage(m.media));
1849
+ else appendBubble(m.role === "user" ? "user" : "bot", m.text, m.ts);
1763
1850
  }
1764
1851
  scrollToBottom();
1765
1852
  }
@@ -2292,9 +2379,12 @@ function initChat(client, options) {
2292
2379
  renderResumeBanner(true);
2293
2380
  }
2294
2381
  const reply = (res.reply ?? "").trim();
2382
+ const hasMedia = Array.isArray(res.media) && res.media.length > 0;
2295
2383
  hideTyping();
2296
2384
  if (reply) {
2297
- appendBotReply(reply);
2385
+ appendBotReply(reply, hasMedia ? () => appendBotMedia(res.media) : void 0);
2386
+ } else if (hasMedia) {
2387
+ appendBotMedia(res.media);
2298
2388
  } else {
2299
2389
  appendSystem(cfg.errorText);
2300
2390
  cfg.onError?.(new Error("Nexor SDK: empty reply from server"));
@@ -2329,10 +2419,21 @@ function initChat(client, options) {
2329
2419
  if (!state.isOpen) bumpUnread();
2330
2420
  cfg.onMessage?.({ role: "bot", text });
2331
2421
  }
2332
- function appendBotReply(text) {
2422
+ function appendBotMedia(items) {
2423
+ if (!Array.isArray(items) || items.length === 0) return;
2424
+ for (const item of items) {
2425
+ if (!item || typeof item.url !== "string" || !item.url) continue;
2426
+ appendNode(buildMediaMessage(item));
2427
+ state.history.push({ role: "bot", text: "", ts: Date.now(), media: item });
2428
+ }
2429
+ saveHistory();
2430
+ if (!state.isOpen) bumpUnread();
2431
+ }
2432
+ function appendBotReply(text, after) {
2333
2433
  const parts = cfg.splitReplies ? splitReply(text) : [text];
2334
2434
  if (parts.length <= 1) {
2335
2435
  appendBot(text);
2436
+ after?.();
2336
2437
  return;
2337
2438
  }
2338
2439
  const [first, ...rest] = parts;
@@ -2340,7 +2441,10 @@ function initChat(client, options) {
2340
2441
  let i = 0;
2341
2442
  const sendNext = () => {
2342
2443
  const part = rest[i];
2343
- if (part === void 0) return;
2444
+ if (part === void 0) {
2445
+ after?.();
2446
+ return;
2447
+ }
2344
2448
  const isLast = i === rest.length - 1;
2345
2449
  i++;
2346
2450
  showTyping();