@aikaara/chat-sdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { a as p, D as u, e as d, f as g, g as m, h as f, i as x, j as k, k as c, l as v } from "./AikaaraChatClient-C4lWcRsS.mjs";
2
- class y {
1
+ import { a as k, D as y, g as u, h as v, j as w, k as T, l as C, m as L, n as b, o as A } from "./AikaaraChatClient-Cqbcd1jb.mjs";
2
+ class E {
3
3
  client;
4
4
  panel;
5
5
  bubble;
@@ -8,8 +8,8 @@ class y {
8
8
  input;
9
9
  errorBanner;
10
10
  isOpen = !1;
11
- constructor(e, t) {
12
- this.client = new p(e), this.bubble = t.querySelector("aikaara-chat-bubble"), this.panel = t.querySelector(".aikaara-panel"), this.header = t.querySelector("aikaara-chat-header"), this.messageList = t.querySelector("aikaara-message-list"), this.input = t.querySelector("aikaara-chat-input"), this.errorBanner = t.querySelector("aikaara-error-banner"), e.welcomeMessage && this.messageList.setWelcomeMessage(e.welcomeMessage), e.showTimestamps !== void 0 && this.messageList.setShowTimestamps(e.showTimestamps), this.wireEvents();
11
+ constructor(e, t, a) {
12
+ this.client = new k(e, { uploadAdapter: a?.uploadAdapter }), this.bubble = t.querySelector("aikaara-chat-bubble"), this.panel = t.querySelector(".aikaara-panel"), this.header = t.querySelector("aikaara-chat-header"), this.messageList = t.querySelector("aikaara-message-list"), this.input = t.querySelector("aikaara-chat-input"), this.errorBanner = t.querySelector("aikaara-error-banner"), e.welcomeMessage && this.messageList.setWelcomeMessage(e.welcomeMessage), e.showTimestamps !== void 0 && this.messageList.setShowTimestamps(e.showTimestamps), this.wireEvents();
13
13
  }
14
14
  async connect() {
15
15
  try {
@@ -47,6 +47,13 @@ class y {
47
47
  this.header.setStatus(e), e === "connected" ? (this.errorBanner.hide(), this.input.disabled = !1) : e === "reconnecting" ? (this.errorBanner.show("Connection lost. Reconnecting..."), this.input.disabled = !0) : e === "disconnected" && (this.input.disabled = !0);
48
48
  }), this.client.on("error", (e) => {
49
49
  this.errorBanner.show(e.message, 5e3);
50
+ }), this.client.on("message:received", (e) => {
51
+ this.messageList.upsertMessage(e);
52
+ }), this.client.on("message:updated", (e) => {
53
+ this.messageList.upsertMessage(e);
54
+ }), this.messageList.addEventListener("message-action", (e) => {
55
+ const t = e.detail;
56
+ t?.text && this.handleSend(t.text);
50
57
  });
51
58
  }
52
59
  async handleSend(e) {
@@ -70,7 +77,7 @@ class y {
70
77
  }, 200));
71
78
  }
72
79
  }
73
- class w extends HTMLElement {
80
+ class S extends HTMLElement {
74
81
  shadow;
75
82
  controller = null;
76
83
  _config = {};
@@ -115,38 +122,45 @@ class w extends HTMLElement {
115
122
  apiKey: this.getAttribute("api-key") || this._config.apiKey,
116
123
  title: this.getAttribute("title") || this._config.title || "Chat",
117
124
  subtitle: this.getAttribute("subtitle") || this._config.subtitle,
118
- theme: this.getAttribute("theme") || this._config.theme || v,
119
- primaryColor: this.getAttribute("primary-color") || this._config.primaryColor || c,
120
- position: this.getAttribute("position") || this._config.position || k,
121
- width: Number(this.getAttribute("width")) || this._config.width || x,
122
- height: Number(this.getAttribute("height")) || this._config.height || f,
123
- fontFamily: this._config.fontFamily || m,
124
- borderRadius: this._config.borderRadius ?? g,
125
- placeholder: this.getAttribute("placeholder") || this._config.placeholder || d,
125
+ theme: this.getAttribute("theme") || this._config.theme || A,
126
+ primaryColor: this.getAttribute("primary-color") || this._config.primaryColor || b,
127
+ position: this.getAttribute("position") || this._config.position || L,
128
+ width: Number(this.getAttribute("width")) || this._config.width || C,
129
+ height: Number(this.getAttribute("height")) || this._config.height || T,
130
+ fontFamily: this._config.fontFamily || w,
131
+ borderRadius: this._config.borderRadius ?? v,
132
+ placeholder: this.getAttribute("placeholder") || this._config.placeholder || u,
126
133
  welcomeMessage: this.getAttribute("welcome-message") || this._config.welcomeMessage,
127
134
  avatarUrl: this.getAttribute("avatar-url") || this._config.avatarUrl,
128
135
  showTimestamps: this._config.showTimestamps ?? !0,
129
136
  persistConversation: this._config.persistConversation ?? !0,
130
137
  showBubble: this._config.showBubble ?? !0,
131
- offset: this._config.offset || u,
138
+ offset: this._config.offset || y,
132
139
  conversationId: this._config.conversationId,
133
140
  systemPromptId: this._config.systemPromptId,
134
141
  channel: this._config.channel,
135
142
  extUid: this._config.extUid,
143
+ transport: this._config.transport,
144
+ tiledesk: this._config.tiledesk,
145
+ tiledeskIdentity: this._config.tiledeskIdentity,
136
146
  onMessage: this._config.onMessage,
137
147
  onStatusChange: this._config.onStatusChange,
138
148
  onError: this._config.onError,
139
149
  onStreamUpdate: this._config.onStreamUpdate,
140
- onConnectionStateChange: this._config.onConnectionStateChange
150
+ onConnectionStateChange: this._config.onConnectionStateChange,
151
+ onTemplateMessage: this._config.onTemplateMessage
141
152
  };
142
153
  }
154
+ setUploadAdapter(e) {
155
+ this._config.uploadAdapter = e, this.controller?.getClient().setUploadAdapter(e);
156
+ }
143
157
  render() {
144
158
  const e = this.getConfig();
145
159
  this.shadow.innerHTML = `
146
160
  <style>
147
161
  :host {
148
162
  --aikaara-primary: ${e.primaryColor};
149
- --aikaara-primary-hover: ${this.darkenColor(e.primaryColor || c)};
163
+ --aikaara-primary-hover: ${this.darkenColor(e.primaryColor || b)};
150
164
  --aikaara-bg: #ffffff;
151
165
  --aikaara-bg-secondary: #f9fafb;
152
166
  --aikaara-text: #1f2937;
@@ -228,14 +242,16 @@ class w extends HTMLElement {
228
242
  ${e.avatarUrl ? `avatar-url="${e.avatarUrl}"` : ""}
229
243
  ></aikaara-chat-header>
230
244
  <aikaara-message-list></aikaara-message-list>
231
- <aikaara-chat-input placeholder="${e.placeholder || d}"></aikaara-chat-input>
245
+ <aikaara-chat-input placeholder="${e.placeholder || u}"></aikaara-chat-input>
232
246
  <aikaara-error-banner></aikaara-error-banner>
233
247
  </div>
234
248
  `;
235
249
  }
236
250
  async initController() {
237
251
  const e = this.getConfig();
238
- !e.baseUrl || !e.userToken || (this.controller?.disconnect(), this.controller = new y(e, this.shadow), await this.controller.connect());
252
+ !e.baseUrl || !e.userToken || (this.controller?.disconnect(), this.controller = new E(e, this.shadow, {
253
+ uploadAdapter: this._config.uploadAdapter
254
+ }), await this.controller.connect());
239
255
  }
240
256
  sendUserEvent(e, t, a) {
241
257
  this.controller?.sendUserEvent(e, t, a);
@@ -245,14 +261,14 @@ class w extends HTMLElement {
245
261
  }
246
262
  darkenColor(e) {
247
263
  try {
248
- const t = parseInt(e.replace("#", ""), 16), a = Math.max(0, (t >> 16) - 20), s = Math.max(0, (t >> 8 & 255) - 20), n = Math.max(0, (t & 255) - 20);
249
- return `#${(a << 16 | s << 8 | n).toString(16).padStart(6, "0")}`;
264
+ const t = parseInt(e.replace("#", ""), 16), a = Math.max(0, (t >> 16) - 20), i = Math.max(0, (t >> 8 & 255) - 20), r = Math.max(0, (t & 255) - 20);
265
+ return `#${(a << 16 | i << 8 | r).toString(16).padStart(6, "0")}`;
250
266
  } catch {
251
267
  return e;
252
268
  }
253
269
  }
254
270
  }
255
- class T extends HTMLElement {
271
+ class M extends HTMLElement {
256
272
  shadow;
257
273
  constructor() {
258
274
  super(), this.shadow = this.attachShadow({ mode: "open" });
@@ -305,7 +321,7 @@ class T extends HTMLElement {
305
321
  t && (t.innerHTML = e);
306
322
  }
307
323
  }
308
- class C extends HTMLElement {
324
+ class _ extends HTMLElement {
309
325
  shadow;
310
326
  static get observedAttributes() {
311
327
  return ["title", "subtitle", "avatar-url", "status"];
@@ -325,7 +341,7 @@ class C extends HTMLElement {
325
341
  });
326
342
  }
327
343
  render() {
328
- const e = this.getAttribute("title") || "Chat", t = this.getAttribute("subtitle") || "", a = this.getAttribute("avatar-url"), s = this.getAttribute("status") || "connected", n = s === "connected" ? "#10b981" : s === "connecting" || s === "reconnecting" ? "#f59e0b" : "#ef4444";
344
+ const e = this.getAttribute("title") || "Chat", t = this.getAttribute("subtitle") || "", a = this.getAttribute("avatar-url"), i = this.getAttribute("status") || "connected", r = i === "connected" ? "#10b981" : i === "connecting" || i === "reconnecting" ? "#f59e0b" : "#ef4444";
329
345
  this.shadow.innerHTML = `
330
346
  <style>
331
347
  .header {
@@ -410,7 +426,7 @@ class C extends HTMLElement {
410
426
  <div class="info">
411
427
  <div class="title">
412
428
  ${e}
413
- <span class="status-dot" style="background:${n}"></span>
429
+ <span class="status-dot" style="background:${r}"></span>
414
430
  </div>
415
431
  ${t ? `<div class="subtitle">${t}</div>` : ""}
416
432
  </div>
@@ -426,14 +442,14 @@ class C extends HTMLElement {
426
442
  this.setAttribute("status", e);
427
443
  }
428
444
  }
429
- function o(i) {
430
- let e = L(i);
431
- return e = e.replace(/```(\w*)\n([\s\S]*?)```/g, (t, a, s) => `<pre><code>${s.trim()}</code></pre>`), e = e.replace(/`([^`]+)`/g, "<code>$1</code>"), e = e.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>"), e = e.replace(/\*(.+?)\*/g, "<em>$1</em>"), e = e.replace(
445
+ function l(s) {
446
+ let e = $(s);
447
+ return e = e.replace(/```(\w*)\n([\s\S]*?)```/g, (t, a, i) => `<pre><code>${i.trim()}</code></pre>`), e = e.replace(/`([^`]+)`/g, "<code>$1</code>"), e = e.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>"), e = e.replace(/\*(.+?)\*/g, "<em>$1</em>"), e = e.replace(
432
448
  /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,
433
449
  '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
434
450
  ), e = e.replace(/\n/g, "<br>"), e;
435
451
  }
436
- function L(i) {
452
+ function $(s) {
437
453
  const e = {
438
454
  "&": "&amp;",
439
455
  "<": "&lt;",
@@ -441,9 +457,9 @@ function L(i) {
441
457
  '"': "&quot;",
442
458
  "'": "&#039;"
443
459
  };
444
- return i.replace(/[&<>"']/g, (t) => e[t]);
460
+ return s.replace(/[&<>"']/g, (t) => e[t]);
445
461
  }
446
- const A = /* @__PURE__ */ new Set([
462
+ const H = /* @__PURE__ */ new Set([
447
463
  "p",
448
464
  "br",
449
465
  "strong",
@@ -463,38 +479,38 @@ const A = /* @__PURE__ */ new Set([
463
479
  "h6",
464
480
  "span",
465
481
  "div"
466
- ]), E = {
482
+ ]), z = {
467
483
  a: /* @__PURE__ */ new Set(["href", "target", "rel"]),
468
484
  code: /* @__PURE__ */ new Set(["class"]),
469
485
  pre: /* @__PURE__ */ new Set(["class"]),
470
486
  span: /* @__PURE__ */ new Set(["class"]),
471
487
  div: /* @__PURE__ */ new Set(["class"])
472
488
  };
473
- function l(i) {
489
+ function d(s) {
474
490
  const e = document.createElement("template");
475
- return e.innerHTML = i, h(e.content), e.innerHTML;
491
+ return e.innerHTML = s, g(e.content), e.innerHTML;
476
492
  }
477
- function h(i) {
478
- const e = Array.from(i.childNodes);
493
+ function g(s) {
494
+ const e = Array.from(s.childNodes);
479
495
  for (const t of e)
480
496
  if (t.nodeType === Node.ELEMENT_NODE) {
481
- const a = t, s = a.tagName.toLowerCase();
482
- if (!A.has(s)) {
483
- const r = document.createTextNode(a.textContent || "");
484
- i.replaceChild(r, t);
497
+ const a = t, i = a.tagName.toLowerCase();
498
+ if (!H.has(i)) {
499
+ const n = document.createTextNode(a.textContent || "");
500
+ s.replaceChild(n, t);
485
501
  continue;
486
502
  }
487
- const n = E[s] || /* @__PURE__ */ new Set(), b = Array.from(a.attributes);
488
- for (const r of b)
489
- n.has(r.name) || a.removeAttribute(r.name);
503
+ const r = z[i] || /* @__PURE__ */ new Set(), o = Array.from(a.attributes);
504
+ for (const n of o)
505
+ r.has(n.name) || a.removeAttribute(n.name);
490
506
  if (a.hasAttribute("href")) {
491
- const r = a.getAttribute("href") || "";
492
- !r.startsWith("http://") && !r.startsWith("https://") && !r.startsWith("/") && a.removeAttribute("href");
507
+ const n = a.getAttribute("href") || "";
508
+ !n.startsWith("http://") && !n.startsWith("https://") && !n.startsWith("/") && a.removeAttribute("href");
493
509
  }
494
- h(t);
510
+ g(t);
495
511
  }
496
512
  }
497
- class S extends HTMLElement {
513
+ class I extends HTMLElement {
498
514
  shadow;
499
515
  container;
500
516
  welcomeMessage = "";
@@ -524,7 +540,41 @@ class S extends HTMLElement {
524
540
  .message-list::-webkit-scrollbar-thumb { background: var(--aikaara-border, #e5e7eb); border-radius: 3px; }
525
541
  .message-wrap { display: flex; flex-direction: column; }
526
542
  .message-wrap.user { align-items: flex-end; }
527
- .message-wrap.assistant { align-items: flex-start; }
543
+ .message-wrap.assistant,
544
+ .message-wrap.agent,
545
+ .message-wrap.system { align-items: flex-start; }
546
+ .message-wrap.system .bubble {
547
+ background: transparent;
548
+ color: var(--aikaara-text-secondary, #6b7280);
549
+ font-size: 12px;
550
+ font-style: italic;
551
+ padding: 4px 0;
552
+ }
553
+ .attachments {
554
+ display: flex;
555
+ flex-direction: column;
556
+ gap: 4px;
557
+ margin-top: 6px;
558
+ }
559
+ .attachment {
560
+ display: inline-flex;
561
+ align-items: center;
562
+ gap: 6px;
563
+ padding: 6px 10px;
564
+ background: rgba(0,0,0,0.05);
565
+ border-radius: 8px;
566
+ font-size: 12px;
567
+ color: inherit;
568
+ text-decoration: none;
569
+ }
570
+ .attachment:hover { background: rgba(0,0,0,0.08); }
571
+ .status-tick {
572
+ font-size: 10px;
573
+ margin-left: 4px;
574
+ color: var(--aikaara-text-secondary, #6b7280);
575
+ }
576
+ .status-tick.read { color: var(--aikaara-primary, #6366f1); }
577
+ aikaara-template-renderer { display: block; margin-top: 6px; }
528
578
  .bubble {
529
579
  max-width: 85%;
530
580
  padding: 10px 14px;
@@ -622,7 +672,7 @@ class S extends HTMLElement {
622
672
  renderMessages(e) {
623
673
  if (this.container) {
624
674
  if (this.container.innerHTML = "", e.length === 0 && this.welcomeMessage) {
625
- this.container.innerHTML = `<div class="welcome">${l(o(this.welcomeMessage))}</div>`;
675
+ this.container.innerHTML = `<div class="welcome">${d(l(this.welcomeMessage))}</div>`;
626
676
  return;
627
677
  }
628
678
  for (const t of e)
@@ -636,7 +686,7 @@ class S extends HTMLElement {
636
686
  }
637
687
  updateStreamingContent(e) {
638
688
  const t = this.container.querySelector('[data-streaming="true"] .bubble');
639
- t && (t.innerHTML = l(o(e)), t.classList.add("streaming-cursor"), this.scrollToBottom());
689
+ t && (t.innerHTML = d(l(e)), t.classList.add("streaming-cursor"), this.scrollToBottom());
640
690
  }
641
691
  finalizeStreaming() {
642
692
  const e = this.container.querySelector('[data-streaming="true"]');
@@ -652,14 +702,52 @@ class S extends HTMLElement {
652
702
  }
653
703
  appendMessageElement(e) {
654
704
  const t = document.createElement("div");
655
- t.classList.add("message-wrap", e.role), e.status === "streaming" && t.setAttribute("data-streaming", "true");
705
+ t.classList.add("message-wrap", e.role), e.status === "streaming" && t.setAttribute("data-streaming", "true"), e.externalId && (t.dataset.externalId = e.externalId), t.dataset.messageId = e.id;
656
706
  const a = document.createElement("div");
657
- if (a.classList.add("bubble", e.role), e.role === "user" ? a.textContent = e.content : (a.innerHTML = l(o(e.content || "")), e.status === "streaming" && a.classList.add("streaming-cursor")), t.appendChild(a), this.showTimestamps && e.createdAt) {
658
- const s = document.createElement("div");
659
- s.classList.add("timestamp"), s.textContent = this.formatTime(e.createdAt), t.appendChild(s);
707
+ if (a.classList.add("bubble", e.role), e.role === "user" ? a.textContent = e.content : (a.innerHTML = d(l(e.content || "")), e.status === "streaming" && a.classList.add("streaming-cursor")), e.template?.contentType) {
708
+ const i = document.createElement("aikaara-template-renderer");
709
+ i.setAttribute("content-type", e.template.contentType), e.template.templateId && i.setAttribute("template-id", e.template.templateId), i.setPayload(e.template.payload, e.role !== "user" ? e.content : void 0), a.appendChild(i);
710
+ }
711
+ if (e.attachments?.length) {
712
+ const i = document.createElement("div");
713
+ i.classList.add("attachments");
714
+ for (const r of e.attachments) {
715
+ const o = document.createElement("a");
716
+ o.classList.add("attachment"), o.href = r.fileUrl, o.target = "_blank", o.rel = "noopener noreferrer", o.textContent = `📎 ${r.fileName}`, i.appendChild(o);
717
+ }
718
+ a.appendChild(i);
719
+ }
720
+ if (t.appendChild(a), this.showTimestamps && e.createdAt || e.role === "user" && e.status) {
721
+ const i = document.createElement("div");
722
+ if (i.classList.add("timestamp"), this.showTimestamps && e.createdAt && (i.textContent = this.formatTime(e.createdAt)), e.role === "user" && e.status) {
723
+ const r = document.createElement("span");
724
+ r.classList.add("status-tick"), e.status === "read" && r.classList.add("read"), r.textContent = {
725
+ sending: " ○",
726
+ sent: " ✓",
727
+ delivered: " ✓✓",
728
+ read: " ✓✓"
729
+ }[e.status] ?? "", i.appendChild(r);
730
+ }
731
+ t.appendChild(i);
660
732
  }
661
733
  this.container.appendChild(t);
662
734
  }
735
+ /**
736
+ * Replace an existing rendered message (matched by id or externalId) in place.
737
+ * Used when a Tiledesk self-echo reconciles with an optimistic bubble or when
738
+ * a status update flips delivered → read.
739
+ */
740
+ upsertMessage(e) {
741
+ const t = this.findRenderedMessage(e);
742
+ t && t.remove(), this.appendMessageElement(e), this.scrollToBottom();
743
+ }
744
+ findRenderedMessage(e) {
745
+ if (e.externalId) {
746
+ const t = this.container.querySelector(`[data-external-id="${CSS.escape(e.externalId)}"]`);
747
+ if (t) return t;
748
+ }
749
+ return this.container.querySelector(`[data-message-id="${CSS.escape(e.id)}"]`);
750
+ }
663
751
  scrollToBottom() {
664
752
  requestAnimationFrame(() => {
665
753
  this.container.scrollTop = this.container.scrollHeight;
@@ -673,10 +761,12 @@ class S extends HTMLElement {
673
761
  }
674
762
  }
675
763
  }
676
- class M extends HTMLElement {
764
+ class U extends HTMLElement {
677
765
  shadow;
766
+ templatePayload = null;
767
+ attachments = [];
678
768
  static get observedAttributes() {
679
- return ["role", "content", "timestamp"];
769
+ return ["role", "content", "timestamp", "content-type", "template-id", "inner-message", "message-id", "status"];
680
770
  }
681
771
  constructor() {
682
772
  super(), this.shadow = this.attachShadow({ mode: "open" });
@@ -687,13 +777,21 @@ class M extends HTMLElement {
687
777
  attributeChangedCallback() {
688
778
  this.render();
689
779
  }
780
+ setTemplatePayload(e) {
781
+ this.templatePayload = e, this.render();
782
+ }
783
+ setAttachments(e) {
784
+ this.attachments = e, this.render();
785
+ }
690
786
  render() {
691
- const e = this.getAttribute("role") || "user", t = this.getAttribute("content") || "", a = this.getAttribute("timestamp") || "", s = e === "user" ? document.createTextNode(t).textContent || "" : l(o(t));
787
+ const e = this.getAttribute("role") || "user", t = this.getAttribute("content") || "", a = this.getAttribute("timestamp") || "", i = this.getAttribute("status") || "", r = this.getAttribute("content-type") || "", o = this.getAttribute("template-id") || "", n = this.getAttribute("inner-message") || "", h = e === "user" ? document.createTextNode(t).textContent || "" : d(l(t)), p = e === "user" && i, f = i === "sending" ? "○" : i === "sent" ? "✓" : i === "delivered" || i === "read" ? "✓✓" : "";
692
788
  this.shadow.innerHTML = `
693
789
  <style>
694
790
  :host { display: flex; flex-direction: column; }
695
791
  :host([role="user"]) { align-items: flex-end; }
696
- :host([role="assistant"]) { align-items: flex-start; }
792
+ :host([role="assistant"]),
793
+ :host([role="agent"]),
794
+ :host([role="system"]) { align-items: flex-start; }
697
795
  .bubble {
698
796
  max-width: 85%;
699
797
  padding: 10px 14px;
@@ -707,24 +805,90 @@ class M extends HTMLElement {
707
805
  color: #fff;
708
806
  border-bottom-right-radius: 4px;
709
807
  }
710
- .bubble.assistant {
808
+ .bubble.assistant, .bubble.agent {
711
809
  background: var(--aikaara-bg-secondary, #f9fafb);
712
810
  color: var(--aikaara-text, #1f2937);
713
811
  border-bottom-left-radius: 4px;
714
812
  }
813
+ .bubble.system {
814
+ background: transparent;
815
+ color: var(--aikaara-text-secondary, #6b7280);
816
+ font-size: 12px;
817
+ font-style: italic;
818
+ }
715
819
  .timestamp {
716
820
  font-size: 11px;
717
821
  color: var(--aikaara-text-secondary, #6b7280);
718
822
  margin-top: 2px;
719
823
  padding: 0 4px;
720
824
  }
825
+ .status {
826
+ font-size: 10px;
827
+ color: ${i === "read" ? "var(--aikaara-primary, #6366f1)" : "var(--aikaara-text-secondary, #6b7280)"};
828
+ margin-left: 4px;
829
+ }
830
+ .attachments {
831
+ display: flex;
832
+ flex-direction: column;
833
+ gap: 6px;
834
+ margin-top: 8px;
835
+ }
836
+ .attachment {
837
+ display: inline-flex;
838
+ align-items: center;
839
+ gap: 6px;
840
+ padding: 6px 10px;
841
+ background: rgba(0,0,0,0.05);
842
+ border-radius: 8px;
843
+ font-size: 12px;
844
+ color: inherit;
845
+ text-decoration: none;
846
+ max-width: 100%;
847
+ }
848
+ .attachment:hover { background: rgba(0,0,0,0.08); }
849
+ aikaara-template-renderer { display: block; margin-top: 6px; }
721
850
  </style>
722
- <div class="bubble ${e}">${s}</div>
723
- ${a ? `<div class="timestamp">${a}</div>` : ""}
851
+ <div class="bubble ${e}">
852
+ <div class="content">${h}</div>
853
+ ${r ? `<aikaara-template-renderer
854
+ content-type="${r}"
855
+ template-id="${o}"
856
+ inner-message="${m(n)}"
857
+ ></aikaara-template-renderer>` : ""}
858
+ ${this.renderAttachments()}
859
+ </div>
860
+ ${a || p ? `
861
+ <div class="timestamp">
862
+ ${a}${p ? `<span class="status">${f}</span>` : ""}
863
+ </div>
864
+ ` : ""}
724
865
  `;
866
+ const c = this.shadow.querySelector("aikaara-template-renderer");
867
+ c && this.templatePayload !== null && c.setPayload(this.templatePayload, n), c && c.addEventListener("template-action", (x) => {
868
+ this.dispatchEvent(
869
+ new CustomEvent("message-action", {
870
+ detail: x.detail,
871
+ bubbles: !0,
872
+ composed: !0
873
+ })
874
+ );
875
+ });
876
+ }
877
+ renderAttachments() {
878
+ return this.attachments.length ? `<div class="attachments">${this.attachments.map(
879
+ (t) => `<a class="attachment" href="${m(t.fileUrl)}" target="_blank" rel="noopener noreferrer">
880
+ 📎 ${q(t.fileName)}
881
+ </a>`
882
+ ).join("")}</div>` : "";
725
883
  }
726
884
  }
727
- class _ extends HTMLElement {
885
+ function m(s) {
886
+ return String(s).replace(/[&<>"']/g, (e) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[e]);
887
+ }
888
+ function q(s) {
889
+ return String(s).replace(/[&<>]/g, (e) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[e]);
890
+ }
891
+ class B extends HTMLElement {
728
892
  shadow;
729
893
  textarea;
730
894
  sendBtn;
@@ -822,7 +986,7 @@ class _ extends HTMLElement {
822
986
  this.textarea.style.height = "auto", this.textarea.style.height = Math.min(this.textarea.scrollHeight, 120) + "px";
823
987
  }
824
988
  }
825
- class H extends HTMLElement {
989
+ class D extends HTMLElement {
826
990
  shadow;
827
991
  constructor() {
828
992
  super(), this.shadow = this.attachShadow({ mode: "open" });
@@ -871,7 +1035,7 @@ class H extends HTMLElement {
871
1035
  this.removeAttribute("visible");
872
1036
  }
873
1037
  }
874
- class $ extends HTMLElement {
1038
+ class F extends HTMLElement {
875
1039
  shadow;
876
1040
  bubble;
877
1041
  constructor() {
@@ -930,13 +1094,13 @@ class $ extends HTMLElement {
930
1094
  `, this.bubble = this.shadow.querySelector(".bubble");
931
1095
  }
932
1096
  updateContent(e) {
933
- this.bubble && (this.bubble.innerHTML = l(o(e)), this.bubble.classList.add("cursor"));
1097
+ this.bubble && (this.bubble.innerHTML = d(l(e)), this.bubble.classList.add("cursor"));
934
1098
  }
935
1099
  finalize() {
936
1100
  this.bubble?.classList.remove("cursor");
937
1101
  }
938
1102
  }
939
- class z extends HTMLElement {
1103
+ class P extends HTMLElement {
940
1104
  shadow;
941
1105
  container;
942
1106
  dismissTimer = null;
@@ -982,31 +1146,89 @@ class z extends HTMLElement {
982
1146
  this.container.classList.remove("visible"), this.dismissTimer && (clearTimeout(this.dismissTimer), this.dismissTimer = null);
983
1147
  }
984
1148
  }
985
- function B() {
986
- const i = [
987
- ["aikaara-chat-widget", w],
988
- ["aikaara-chat-bubble", T],
989
- ["aikaara-chat-header", C],
990
- ["aikaara-message-list", S],
991
- ["aikaara-message-bubble", M],
992
- ["aikaara-chat-input", _],
993
- ["aikaara-typing-indicator", H],
994
- ["aikaara-streaming-message", $],
995
- ["aikaara-error-banner", z]
1149
+ class O extends HTMLElement {
1150
+ shadow;
1151
+ payloadData = null;
1152
+ innerMessage = "";
1153
+ static get observedAttributes() {
1154
+ return ["content-type", "template-id", "inner-message"];
1155
+ }
1156
+ constructor() {
1157
+ super(), this.shadow = this.attachShadow({ mode: "open" });
1158
+ }
1159
+ connectedCallback() {
1160
+ this.render();
1161
+ }
1162
+ attributeChangedCallback() {
1163
+ this.render();
1164
+ }
1165
+ setPayload(e, t) {
1166
+ this.payloadData = e, t !== void 0 && (this.innerMessage = t), this.render();
1167
+ }
1168
+ render() {
1169
+ const e = this.getAttribute("content-type") ?? "", t = this.getAttribute("template-id") ?? "", a = this.getAttribute("inner-message") ?? this.innerMessage;
1170
+ this.shadow.innerHTML = `
1171
+ <style>
1172
+ :host { display: block; }
1173
+ .inner { font-size: 14px; line-height: 1.5; color: var(--aikaara-text, #1f2937); }
1174
+ .placeholder {
1175
+ font-size: 12px;
1176
+ color: var(--aikaara-text-secondary, #6b7280);
1177
+ padding: 6px 0;
1178
+ }
1179
+ </style>
1180
+ ${a ? '<div class="inner"></div>' : ""}
1181
+ <slot></slot>
1182
+ `;
1183
+ const i = this.shadow.querySelector(".inner");
1184
+ if (i && a && (i.textContent = a), !e) return;
1185
+ const r = t ? `aikaara-template-${t}` : `aikaara-template-${e}`;
1186
+ if (customElements.get(r)) {
1187
+ const o = this.querySelector(r);
1188
+ if (o) {
1189
+ o.setPayload?.(this.payloadData);
1190
+ return;
1191
+ }
1192
+ const n = document.createElement(r);
1193
+ n.setPayload?.(this.payloadData), n.addEventListener("template-action", (h) => {
1194
+ this.dispatchEvent(
1195
+ new CustomEvent("template-action", {
1196
+ detail: h.detail,
1197
+ bubbles: !0,
1198
+ composed: !0
1199
+ })
1200
+ );
1201
+ }), this.appendChild(n);
1202
+ }
1203
+ }
1204
+ }
1205
+ function N() {
1206
+ const s = [
1207
+ ["aikaara-chat-widget", S],
1208
+ ["aikaara-chat-bubble", M],
1209
+ ["aikaara-chat-header", _],
1210
+ ["aikaara-message-list", I],
1211
+ ["aikaara-message-bubble", U],
1212
+ ["aikaara-chat-input", B],
1213
+ ["aikaara-typing-indicator", D],
1214
+ ["aikaara-streaming-message", F],
1215
+ ["aikaara-error-banner", P],
1216
+ ["aikaara-template-renderer", O]
996
1217
  ];
997
- for (const [e, t] of i)
1218
+ for (const [e, t] of s)
998
1219
  customElements.get(e) || customElements.define(e, t);
999
1220
  }
1000
- B();
1221
+ N();
1001
1222
  export {
1002
- T as AikaaraChatBubble,
1003
- C as AikaaraChatHeader,
1004
- _ as AikaaraChatInput,
1005
- w as AikaaraChatWidget,
1006
- z as AikaaraErrorBanner,
1007
- M as AikaaraMessageBubble,
1008
- S as AikaaraMessageList,
1009
- $ as AikaaraStreamingMessage,
1010
- H as AikaaraTypingIndicator,
1011
- B as registerComponents
1223
+ M as AikaaraChatBubble,
1224
+ _ as AikaaraChatHeader,
1225
+ B as AikaaraChatInput,
1226
+ S as AikaaraChatWidget,
1227
+ P as AikaaraErrorBanner,
1228
+ U as AikaaraMessageBubble,
1229
+ I as AikaaraMessageList,
1230
+ F as AikaaraStreamingMessage,
1231
+ O as AikaaraTemplateRenderer,
1232
+ D as AikaaraTypingIndicator,
1233
+ N as registerComponents
1012
1234
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikaara/chat-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Aikaara Chat SDK — embeddable chat widget and headless client",
6
6
  "license": "MIT",