@chatcops/widget 0.3.2 → 0.4.1

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.
@@ -1,7 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- const widgetStyles = ":host{--cc-accent: #6366f1;--cc-accent-hover: #4f46e5;--cc-text: #ffffff;--cc-bg: #0a0a0a;--cc-bg-surface: #1a1a1a;--cc-bg-input: #2a2a2a;--cc-border: #333333;--cc-text-secondary: #a0a0a0;--cc-font: system-ui, -apple-system, sans-serif;--cc-radius: 12px;--cc-shadow: 0 8px 32px rgba(0, 0, 0, .4);--cc-fab-size: 56px;--cc-panel-width: 400px;--cc-panel-height: 560px;all:initial;font-family:var(--cc-font);font-size:14px;line-height:1.5;color:var(--cc-text);-webkit-font-smoothing:antialiased}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}.cc-fab{position:fixed;bottom:20px;right:20px;width:var(--cc-fab-size);height:var(--cc-fab-size);border-radius:50%;background:var(--cc-accent);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px #0000004d;transition:transform .2s ease,box-shadow .2s ease;z-index:10000;outline:none}.cc-fab:hover{transform:scale(1.05);box-shadow:0 6px 24px #0006}.cc-fab:active{transform:scale(.95)}.cc-fab.cc-left{right:auto;left:20px}.cc-fab svg{width:24px;height:24px;fill:var(--cc-text);transition:transform .3s ease}.cc-fab.cc-open svg{transform:rotate(90deg)}.cc-fab-badge{position:absolute;top:-2px;right:-2px;width:12px;height:12px;border-radius:50%;background:#ef4444;border:2px solid var(--cc-bg);display:none}.cc-fab-badge.cc-visible{display:block}@keyframes cc-pulse{0%,to{box-shadow:0 4px 16px #0000004d}50%{box-shadow:0 4px 16px #6366f180}}.cc-fab.cc-pulse{animation:cc-pulse 2s ease-in-out 3}.cc-panel{position:fixed;bottom:88px;right:20px;width:var(--cc-panel-width);height:var(--cc-panel-height);max-height:calc(100vh - 100px);background:var(--cc-bg);border-radius:var(--cc-radius);box-shadow:var(--cc-shadow);border:1px solid var(--cc-border);display:flex;flex-direction:column;overflow:hidden;z-index:9999;opacity:0;transform:translateY(16px) scale(.95);pointer-events:none;transition:opacity .2s ease,transform .2s ease}.cc-panel.cc-visible{opacity:1;transform:translateY(0) scale(1);pointer-events:auto}.cc-panel.cc-left{right:auto;left:20px}.cc-panel.cc-inline{position:relative;bottom:auto;right:auto;left:auto;width:100%;height:100%;max-height:100%;z-index:auto;opacity:1;transform:none;pointer-events:auto;transition:none}.cc-panel.cc-inline.cc-visible{transform:none}.cc-header{display:flex;align-items:center;padding:16px;background:var(--cc-bg-surface);border-bottom:1px solid var(--cc-border);flex-shrink:0}.cc-header-avatar{width:36px;height:36px;border-radius:50%;margin-right:12px;object-fit:cover}.cc-header-avatar-placeholder{width:36px;height:36px;border-radius:50%;margin-right:12px;background:var(--cc-accent);display:flex;align-items:center;justify-content:center}.cc-header-avatar-placeholder svg{width:20px;height:20px;fill:var(--cc-text)}.cc-header-info{flex:1}.cc-header-name{font-size:15px;font-weight:600;color:var(--cc-text)}.cc-header-subtitle{font-size:12px;color:var(--cc-text-secondary)}.cc-header-close{width:32px;height:32px;border-radius:8px;background:transparent;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease}.cc-header-close:hover{background:var(--cc-bg-input)}.cc-header-close svg{width:16px;height:16px;fill:var(--cc-text-secondary)}.cc-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.cc-messages::-webkit-scrollbar{width:4px}.cc-messages::-webkit-scrollbar-track{background:transparent}.cc-messages::-webkit-scrollbar-thumb{background:var(--cc-border);border-radius:2px}.cc-message{max-width:85%;padding:10px 14px;border-radius:var(--cc-radius);word-wrap:break-word;overflow-wrap:break-word;font-size:14px;line-height:1.5}.cc-message-user{align-self:flex-end;background:var(--cc-accent);color:var(--cc-text);border-bottom-right-radius:4px}.cc-message-assistant{align-self:flex-start;background:var(--cc-bg-surface);color:var(--cc-text);border-bottom-left-radius:4px}.cc-message a{color:var(--cc-accent);text-decoration:underline}.cc-message-user a{color:var(--cc-text)}.cc-message code{background:#0003;padding:1px 4px;border-radius:4px;font-family:SF Mono,Cascadia Code,Consolas,monospace;font-size:13px}.cc-message pre{background:#0000004d;padding:8px 12px;border-radius:8px;overflow-x:auto;margin:4px 0}.cc-message pre code{background:transparent;padding:0}.cc-message ul,.cc-message ol{padding-left:20px;margin:4px 0}.cc-typing{display:flex;align-items:center;gap:4px;padding:10px 14px;align-self:flex-start;background:var(--cc-bg-surface);border-radius:var(--cc-radius);border-bottom-left-radius:4px}.cc-typing-dot{width:6px;height:6px;border-radius:50%;background:var(--cc-text-secondary);animation:cc-bounce 1.2s infinite}.cc-typing-dot:nth-child(2){animation-delay:.2s}.cc-typing-dot:nth-child(3){animation-delay:.4s}@keyframes cc-bounce{0%,60%,to{transform:translateY(0)}30%{transform:translateY(-4px)}}.cc-input-area{display:flex;align-items:flex-end;padding:12px 16px;border-top:1px solid var(--cc-border);background:var(--cc-bg);gap:8px}.cc-input{flex:1;background:var(--cc-bg-input);border:1px solid var(--cc-border);border-radius:8px;padding:8px 12px;color:var(--cc-text);font-family:var(--cc-font);font-size:14px;line-height:1.5;resize:none;outline:none;max-height:96px;overflow-y:auto;transition:border-color .15s ease}.cc-input:focus{border-color:var(--cc-accent);outline:2px solid var(--cc-accent);outline-offset:1px}.cc-input::placeholder{color:var(--cc-text-secondary)}.cc-send-btn{width:36px;height:36px;border-radius:8px;background:var(--cc-accent);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s ease,opacity .15s ease}.cc-send-btn:hover{background:var(--cc-accent-hover)}.cc-send-btn:disabled{opacity:.4;cursor:not-allowed}.cc-send-btn svg{width:16px;height:16px;fill:var(--cc-text)}.cc-footer{padding:6px 16px;text-align:center;font-size:11px;color:var(--cc-text-secondary);border-top:1px solid var(--cc-border)}.cc-footer a{color:var(--cc-text-secondary);text-decoration:none}.cc-footer a:hover{text-decoration:underline}.cc-bubble{position:fixed;bottom:84px;right:20px;background:var(--cc-bg-surface);color:var(--cc-text);padding:10px 16px;border-radius:var(--cc-radius);box-shadow:var(--cc-shadow);border:1px solid var(--cc-border);max-width:260px;font-size:13px;line-height:1.4;z-index:9998;opacity:0;transform:translateY(8px);pointer-events:none;transition:opacity .3s ease,transform .3s ease;cursor:pointer}.cc-bubble.cc-visible{opacity:1;transform:translateY(0);pointer-events:auto}.cc-bubble.cc-left{right:auto;left:20px}.cc-bubble-close{position:absolute;top:-8px;right:-8px;width:20px;height:20px;border-radius:50%;background:var(--cc-bg);border:1px solid var(--cc-border);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:10px;color:var(--cc-text-secondary)}@media(max-width:768px){.cc-panel:not(.cc-inline){width:100%;height:100%;max-height:100%;bottom:0;right:0;left:0;border-radius:0;border:none}.cc-panel.cc-left:not(.cc-inline){left:0}.cc-bubble{bottom:84px;right:16px;left:16px;max-width:none}}";
4
+ const widgetStyles = ":host{--cc-accent: #6366f1;--cc-accent-hover: #4f46e5;--cc-text: #ffffff;--cc-bg: #0a0a0a;--cc-bg-surface: #1a1a1a;--cc-bg-input: #2a2a2a;--cc-border: #333333;--cc-text-secondary: #a0a0a0;--cc-font: system-ui, -apple-system, sans-serif;--cc-radius: 12px;--cc-shadow: 0 8px 32px rgba(0, 0, 0, .4);--cc-fab-size: 56px;--cc-panel-width: 400px;--cc-panel-height: 560px;all:initial;font-family:var(--cc-font);font-size:14px;line-height:1.5;color:var(--cc-text);-webkit-font-smoothing:antialiased}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}.cc-fab{position:fixed;bottom:20px;right:20px;width:var(--cc-fab-size);height:var(--cc-fab-size);border-radius:50%;background:var(--cc-accent);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px #0000004d;transition:transform .2s ease,box-shadow .2s ease;z-index:10000;outline:none}.cc-fab:hover{transform:scale(1.05);box-shadow:0 6px 24px #0006}.cc-fab:active{transform:scale(.95)}.cc-fab.cc-left{right:auto;left:20px}.cc-fab svg{width:24px;height:24px;fill:var(--cc-text);transition:transform .3s ease}.cc-fab.cc-open svg{transform:rotate(90deg)}.cc-fab-badge{position:absolute;top:-2px;right:-2px;width:12px;height:12px;border-radius:50%;background:#ef4444;border:2px solid var(--cc-bg);display:none}.cc-fab-badge.cc-visible{display:block}@keyframes cc-pulse{0%,to{box-shadow:0 4px 16px #0000004d}50%{box-shadow:0 4px 16px #6366f180}}.cc-fab.cc-pulse{animation:cc-pulse 2s ease-in-out 3}.cc-panel{position:fixed;bottom:88px;right:20px;width:var(--cc-panel-width);height:var(--cc-panel-height);max-height:calc(100vh - 100px);background:var(--cc-bg);border-radius:var(--cc-radius);box-shadow:var(--cc-shadow);border:1px solid var(--cc-border);display:flex;flex-direction:column;overflow:hidden;z-index:9999;opacity:0;transform:translateY(16px) scale(.95);pointer-events:none;transition:opacity .2s ease,transform .2s ease}.cc-panel.cc-visible{opacity:1;transform:translateY(0) scale(1);pointer-events:auto}.cc-panel.cc-left{right:auto;left:20px}.cc-panel.cc-inline{position:relative;bottom:auto;right:auto;left:auto;width:100%;height:100%;max-height:100%;z-index:auto;opacity:1;transform:none;pointer-events:auto;transition:none}.cc-panel.cc-inline.cc-visible{transform:none}.cc-header{display:flex;align-items:center;padding:16px;background:var(--cc-bg-surface);border-bottom:1px solid var(--cc-border);flex-shrink:0}.cc-header-avatar{width:36px;height:36px;border-radius:50%;margin-right:12px;object-fit:cover}.cc-header-avatar-placeholder{width:36px;height:36px;border-radius:50%;margin-right:12px;background:var(--cc-accent);display:flex;align-items:center;justify-content:center}.cc-header-avatar-placeholder svg{width:20px;height:20px;fill:var(--cc-text)}.cc-header-info{flex:1}.cc-header-name{font-size:15px;font-weight:600;color:var(--cc-text)}.cc-header-subtitle{font-size:12px;color:var(--cc-text-secondary)}.cc-header-close{width:32px;height:32px;border-radius:8px;background:transparent;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease}.cc-header-close:hover{background:var(--cc-bg-input)}.cc-header-close svg{width:16px;height:16px;fill:var(--cc-text-secondary)}.cc-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.cc-messages::-webkit-scrollbar{width:4px}.cc-messages::-webkit-scrollbar-track{background:transparent}.cc-messages::-webkit-scrollbar-thumb{background:var(--cc-border);border-radius:2px}.cc-message{max-width:85%;padding:10px 14px;border-radius:var(--cc-radius);word-wrap:break-word;overflow-wrap:break-word;font-size:14px;line-height:1.5;display:flex;flex-direction:column;gap:8px}.cc-message-user{align-self:flex-end;background:var(--cc-accent);color:var(--cc-text);border-bottom-right-radius:4px}.cc-message-assistant{align-self:flex-start;background:var(--cc-bg-surface);color:var(--cc-text);border-bottom-left-radius:4px}.cc-message a{color:var(--cc-accent);text-decoration:underline}.cc-message-user a{color:var(--cc-text)}.cc-message code{background:#0003;padding:1px 4px;border-radius:4px;font-family:SF Mono,Cascadia Code,Consolas,monospace;font-size:13px}.cc-message pre{background:#0000004d;padding:8px 12px;border-radius:8px;overflow-x:auto;margin:4px 0}.cc-message pre code{background:transparent;padding:0}.cc-message ul,.cc-message ol{padding-left:20px;margin:4px 0}.cc-message-actions{display:flex;align-items:center}.cc-message-retry,.cc-message-regenerate{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;background:transparent;border:1px solid var(--cc-border);border-radius:6px;color:var(--cc-text-secondary);font-family:var(--cc-font);font-size:12px;cursor:pointer;transition:background .15s ease,color .15s ease,opacity .15s ease}.cc-message-retry:hover,.cc-message-regenerate:hover{background:var(--cc-bg-input);color:var(--cc-text)}.cc-message-retry svg,.cc-message-regenerate svg{fill:currentColor}.cc-message-regenerate{opacity:0}.cc-message:hover .cc-message-regenerate{opacity:1}.cc-message-retry{border-color:#ef4444;color:#ef4444}.cc-message-retry:hover{background:#ef44441a;color:#ef4444}.cc-typing{display:flex;align-items:center;gap:4px;padding:10px 14px;align-self:flex-start;background:var(--cc-bg-surface);border-radius:var(--cc-radius);border-bottom-left-radius:4px}.cc-typing-dot{width:6px;height:6px;border-radius:50%;background:var(--cc-text-secondary);animation:cc-bounce 1.2s infinite}.cc-typing-dot:nth-child(2){animation-delay:.2s}.cc-typing-dot:nth-child(3){animation-delay:.4s}@keyframes cc-bounce{0%,60%,to{transform:translateY(0)}30%{transform:translateY(-4px)}}.cc-input-area{display:flex;align-items:flex-end;padding:12px 16px;border-top:1px solid var(--cc-border);background:var(--cc-bg);gap:8px}.cc-input{flex:1;background:var(--cc-bg-input);border:1px solid var(--cc-border);border-radius:8px;padding:8px 12px;color:var(--cc-text);font-family:var(--cc-font);font-size:14px;line-height:1.5;resize:none;outline:none;max-height:96px;overflow-y:auto;transition:border-color .15s ease}.cc-input:focus{border-color:var(--cc-accent);outline:2px solid var(--cc-accent);outline-offset:1px}.cc-input::placeholder{color:var(--cc-text-secondary)}.cc-send-btn{width:36px;height:36px;border-radius:8px;background:var(--cc-accent);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s ease,opacity .15s ease}.cc-send-btn:hover{background:var(--cc-accent-hover)}.cc-send-btn:disabled{opacity:.4;cursor:not-allowed}.cc-send-btn svg{width:16px;height:16px;fill:var(--cc-text)}.cc-footer{padding:6px 16px;text-align:center;font-size:11px;color:var(--cc-text-secondary);border-top:1px solid var(--cc-border)}.cc-footer a{color:var(--cc-text-secondary);text-decoration:none}.cc-footer a:hover{text-decoration:underline}.cc-bubble{position:fixed;bottom:84px;right:20px;background:var(--cc-bg-surface);color:var(--cc-text);padding:10px 16px;border-radius:var(--cc-radius);box-shadow:var(--cc-shadow);border:1px solid var(--cc-border);max-width:260px;font-size:13px;line-height:1.4;z-index:9998;opacity:0;transform:translateY(8px);pointer-events:none;transition:opacity .3s ease,transform .3s ease;cursor:pointer}.cc-bubble.cc-visible{opacity:1;transform:translateY(0);pointer-events:auto}.cc-bubble.cc-left{right:auto;left:20px}.cc-bubble-close{position:absolute;top:-8px;right:-8px;width:20px;height:20px;border-radius:50%;background:var(--cc-bg);border:1px solid var(--cc-border);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:10px;color:var(--cc-text-secondary)}.cc-hidden{display:none}.cc-messages-wrapper{flex:1;display:flex;flex-direction:column;min-height:0}.cc-prechat{flex:1;overflow-y:auto;padding:24px 16px;display:flex;flex-direction:column;gap:16px}.cc-prechat-title{font-size:18px;font-weight:600;color:var(--cc-text)}.cc-prechat-subtitle{font-size:13px;color:var(--cc-text-secondary);margin-top:-8px}.cc-prechat-fields{display:flex;flex-direction:column;gap:14px}.cc-prechat-field{display:flex;flex-direction:column;gap:4px}.cc-prechat-label{font-size:13px;font-weight:500;color:var(--cc-text)}.cc-prechat-label .cc-required{color:#ef4444;margin-left:2px}.cc-prechat-input,.cc-prechat-select,.cc-prechat-textarea{background:var(--cc-bg-input);border:1px solid var(--cc-border);border-radius:8px;padding:8px 12px;color:var(--cc-text);font-family:var(--cc-font);font-size:14px;outline:none;transition:border-color .15s ease}.cc-prechat-input:focus,.cc-prechat-select:focus,.cc-prechat-textarea:focus{border-color:var(--cc-accent)}.cc-prechat-select{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}.cc-prechat-textarea{resize:none;line-height:1.5}.cc-prechat-error{font-size:12px;color:#ef4444;min-height:0}.cc-prechat-error:empty{display:none}.cc-prechat-submit{background:var(--cc-accent);color:var(--cc-text);border:none;border-radius:8px;padding:10px 16px;font-family:var(--cc-font);font-size:14px;font-weight:500;cursor:pointer;transition:background .15s ease;margin-top:8px}.cc-prechat-submit:hover{background:var(--cc-accent-hover)}.cc-prechat-submit:disabled{opacity:.5;cursor:not-allowed}@media(max-width:768px){.cc-panel:not(.cc-inline){width:100%;height:100%;max-height:100%;bottom:0;right:0;left:0;border-radius:0;border:none}.cc-panel.cc-left:not(.cc-inline){left:0}.cc-bubble{bottom:84px;right:16px;left:16px;max-width:none}}";
5
5
  function createShadowRoot(container) {
6
6
  const host = document.createElement("div");
7
7
  host.id = "chatcops-root";
@@ -89,35 +89,44 @@ function renderMarkdown(input) {
89
89
  /\[([^\]]+)\]\(((?:[^()]*|\([^()]*\))*)\)/g,
90
90
  (_match, text, href) => isSafeHref(href) ? `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>` : text
91
91
  );
92
- html = html.replace(/^- (.+)$/gm, "<li>$1</li>");
93
- html = html.replace(/(<li>.*<\/li>\n?)+/g, (match) => `<ul>${match}</ul>`);
94
- html = html.replace(/^\d+\. (.+)$/gm, "<li>$1</li>");
92
+ html = html.replace(/^- (.+)$/gm, '<li data-md="ul">$1</li>');
93
+ html = html.replace(/^\d+\. (.+)$/gm, '<li data-md="ol">$1</li>');
94
+ html = html.replace(
95
+ /(?:<li data-md="ul">.*?<\/li>\n?)+|(?:<li data-md="ol">.*?<\/li>\n?)+/g,
96
+ (run) => {
97
+ const ordered = run.includes('data-md="ol"');
98
+ const tag = ordered ? "ol" : "ul";
99
+ const items = run.replace(/ data-md="(?:ul|ol)"/g, "").replace(/\n/g, "");
100
+ return `<${tag}>${items}</${tag}>`;
101
+ }
102
+ );
95
103
  html = html.replace(/\n\n/g, "<br><br>");
96
104
  html = html.replace(/\n/g, "<br>");
97
105
  return html;
98
106
  }
107
+ const RETRY_ICON = '<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M17.65 6.35A7.96 7.96 0 0012 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0112 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>';
108
+ const REGENERATE_ICON = '<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0020 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74A7.93 7.93 0 004 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>';
99
109
  class Messages {
100
- constructor(parent) {
110
+ constructor(parent, actions) {
101
111
  __publicField(this, "container");
102
112
  __publicField(this, "typingEl", null);
113
+ __publicField(this, "actions");
114
+ this.actions = actions;
103
115
  this.container = document.createElement("div");
104
116
  this.container.className = "cc-messages";
105
117
  parent.appendChild(this.container);
106
118
  }
107
119
  addMessage(msg) {
108
120
  this.removeTyping();
109
- const el = document.createElement("div");
110
- el.className = `cc-message cc-message-${msg.role}`;
111
- el.dataset.id = msg.id;
112
- el.innerHTML = msg.role === "assistant" ? renderMarkdown(msg.content) : this.escapeHtml(msg.content);
121
+ const el = this.createMessageElement(msg);
113
122
  this.container.appendChild(el);
114
123
  this.scrollToBottom();
115
124
  return el;
116
125
  }
117
- updateMessage(id, content, isAssistant) {
118
- const el = this.container.querySelector(`[data-id="${id}"]`);
126
+ updateMessage(msg) {
127
+ const el = this.container.querySelector(`[data-id="${msg.id}"]`);
119
128
  if (el) {
120
- el.innerHTML = isAssistant ? renderMarkdown(content) : this.escapeHtml(content);
129
+ this.renderMessageElement(el, msg);
121
130
  this.scrollToBottom();
122
131
  }
123
132
  }
@@ -139,9 +148,108 @@ class Messages {
139
148
  this.container.innerHTML = "";
140
149
  this.typingEl = null;
141
150
  }
151
+ removeMessage(id) {
152
+ const el = this.container.querySelector(`[data-id="${id}"]`);
153
+ el == null ? void 0 : el.remove();
154
+ }
155
+ clearRegenerateButtons() {
156
+ this.container.querySelectorAll(".cc-message-regenerate").forEach((button) => button.remove());
157
+ this.cleanupEmptyActionContainers();
158
+ }
159
+ showRegenerateButton(messageId) {
160
+ var _a;
161
+ this.clearRegenerateButtons();
162
+ if (!((_a = this.actions) == null ? void 0 : _a.onRegenerate)) return;
163
+ const msgEl = this.container.querySelector(`[data-id="${messageId}"]`);
164
+ if (!msgEl || !msgEl.classList.contains("cc-message-assistant")) return;
165
+ const actions = this.ensureActionsContainer(msgEl);
166
+ actions.appendChild(this.createActionButton({
167
+ className: "cc-message-regenerate",
168
+ label: this.actions.regenerateLabel ?? "Regenerate",
169
+ icon: REGENERATE_ICON,
170
+ onClick: () => {
171
+ var _a2, _b;
172
+ return (_b = (_a2 = this.actions) == null ? void 0 : _a2.onRegenerate) == null ? void 0 : _b.call(_a2, messageId);
173
+ }
174
+ }));
175
+ }
176
+ setVisible(visible) {
177
+ this.container.classList.toggle("cc-hidden", !visible);
178
+ }
142
179
  scrollToBottom() {
143
180
  this.container.scrollTop = this.container.scrollHeight;
144
181
  }
182
+ createMessageElement(msg) {
183
+ const el = document.createElement("div");
184
+ this.renderMessageElement(el, msg);
185
+ return el;
186
+ }
187
+ renderMessageElement(el, msg) {
188
+ el.className = `cc-message cc-message-${msg.role}`;
189
+ el.dataset.id = msg.id;
190
+ if (msg.status) {
191
+ el.dataset.status = msg.status;
192
+ } else {
193
+ delete el.dataset.status;
194
+ }
195
+ if (msg.errorType) {
196
+ el.dataset.errorType = msg.errorType;
197
+ } else {
198
+ delete el.dataset.errorType;
199
+ }
200
+ el.innerHTML = "";
201
+ const content = document.createElement("div");
202
+ content.className = "cc-message-content";
203
+ content.innerHTML = msg.role === "assistant" ? renderMarkdown(msg.content) : this.escapeHtml(msg.content);
204
+ el.appendChild(content);
205
+ const actions = this.createActionsContainer(msg);
206
+ if (actions) {
207
+ el.appendChild(actions);
208
+ }
209
+ }
210
+ createActionsContainer(msg) {
211
+ var _a;
212
+ if (msg.status !== "error" || !((_a = this.actions) == null ? void 0 : _a.onRetry)) {
213
+ return null;
214
+ }
215
+ const actions = document.createElement("div");
216
+ actions.className = "cc-message-actions";
217
+ actions.appendChild(this.createActionButton({
218
+ className: "cc-message-retry",
219
+ label: this.actions.retryLabel ?? "Retry",
220
+ icon: RETRY_ICON,
221
+ onClick: () => {
222
+ var _a2, _b;
223
+ return (_b = (_a2 = this.actions) == null ? void 0 : _a2.onRetry) == null ? void 0 : _b.call(_a2, msg.id);
224
+ }
225
+ }));
226
+ return actions;
227
+ }
228
+ ensureActionsContainer(msgEl) {
229
+ const existing = msgEl.querySelector(".cc-message-actions");
230
+ if (existing) {
231
+ return existing;
232
+ }
233
+ const actions = document.createElement("div");
234
+ actions.className = "cc-message-actions";
235
+ msgEl.appendChild(actions);
236
+ return actions;
237
+ }
238
+ cleanupEmptyActionContainers() {
239
+ this.container.querySelectorAll(".cc-message-actions").forEach((container) => {
240
+ if (!container.hasChildNodes()) {
241
+ container.remove();
242
+ }
243
+ });
244
+ }
245
+ createActionButton(options) {
246
+ const button = document.createElement("button");
247
+ button.type = "button";
248
+ button.className = options.className;
249
+ button.innerHTML = `${options.icon}<span>${this.escapeHtml(options.label)}</span>`;
250
+ button.addEventListener("click", options.onClick);
251
+ return button;
252
+ }
145
253
  escapeHtml(text) {
146
254
  const div = document.createElement("div");
147
255
  div.textContent = text;
@@ -188,6 +296,9 @@ class Input {
188
296
  this.sendBtn.disabled = true;
189
297
  this.autoResize();
190
298
  }
299
+ setVisible(visible) {
300
+ this.area.classList.toggle("cc-hidden", !visible);
301
+ }
191
302
  send() {
192
303
  const text = this.textarea.value.trim();
193
304
  if (!text) return;
@@ -214,6 +325,7 @@ class Panel {
214
325
  __publicField(this, "inline");
215
326
  __publicField(this, "messages");
216
327
  __publicField(this, "input");
328
+ __publicField(this, "messagesContainer");
217
329
  this.inline = options.inline ?? false;
218
330
  this.el = document.createElement("div");
219
331
  this.el.className = "cc-panel";
@@ -250,7 +362,14 @@ class Panel {
250
362
  header.appendChild(closeBtn);
251
363
  }
252
364
  this.el.appendChild(header);
253
- this.messages = new Messages(this.el);
365
+ if (options.preChatEnabled) {
366
+ this.messagesContainer = document.createElement("div");
367
+ this.messagesContainer.className = "cc-messages-wrapper";
368
+ this.el.appendChild(this.messagesContainer);
369
+ } else {
370
+ this.messagesContainer = this.el;
371
+ }
372
+ this.messages = new Messages(this.messagesContainer, options.messageActions);
254
373
  this.input = new Input(this.el, {
255
374
  placeholder: options.placeholder,
256
375
  onSend: options.onSend
@@ -276,6 +395,12 @@ class Panel {
276
395
  addMessage(msg) {
277
396
  return this.messages.addMessage(msg);
278
397
  }
398
+ hideMessages() {
399
+ this.messages.setVisible(false);
400
+ }
401
+ showMessages() {
402
+ this.messages.setVisible(true);
403
+ }
279
404
  destroy() {
280
405
  this.el.remove();
281
406
  }
@@ -414,7 +539,11 @@ class ConversationStorage {
414
539
  }
415
540
  save(conversation) {
416
541
  try {
417
- localStorage.setItem(this.key, JSON.stringify(conversation));
542
+ const filtered = {
543
+ ...conversation,
544
+ messages: conversation.messages.filter((m) => m.status !== "error").map(({ status, ...message }) => message)
545
+ };
546
+ localStorage.setItem(this.key, JSON.stringify(filtered));
418
547
  } catch {
419
548
  }
420
549
  }
@@ -437,6 +566,19 @@ class ConversationStorage {
437
566
  return crypto.randomUUID();
438
567
  }
439
568
  }
569
+ isPreChatCompleted(sessionId) {
570
+ try {
571
+ return sessionStorage.getItem(`chatcops-prechat-${sessionId}`) === "true";
572
+ } catch {
573
+ return false;
574
+ }
575
+ }
576
+ setPreChatCompleted(sessionId) {
577
+ try {
578
+ sessionStorage.setItem(`chatcops-prechat-${sessionId}`, "true");
579
+ } catch {
580
+ }
581
+ }
440
582
  }
441
583
  function applyTheme(root, theme) {
442
584
  const host = root.host;
@@ -489,13 +631,174 @@ const en = {
489
631
  typingIndicator: "Typing...",
490
632
  poweredBy: "Powered by ChatCops",
491
633
  newConversation: "New conversation",
492
- welcomeBubbleDefault: "Need help? Chat with us!"
634
+ welcomeBubbleDefault: "Need help? Chat with us!",
635
+ preChatTitle: "Before we start...",
636
+ preChatSubtitle: "Tell us a bit about yourself",
637
+ preChatSubmit: "Start Chat",
638
+ preChatRequired: "This field is required",
639
+ preChatInvalidEmail: "Please enter a valid email",
640
+ retryButton: "Retry",
641
+ regenerateButton: "Regenerate"
493
642
  };
494
643
  const locales = { en };
495
644
  function getWidgetLocale(code, overrides) {
496
645
  const base = locales[code] ?? en;
497
646
  return overrides ? { ...base, ...overrides } : base;
498
647
  }
648
+ class PreChatForm {
649
+ constructor(parent, options) {
650
+ __publicField(this, "container");
651
+ __publicField(this, "fieldElements", /* @__PURE__ */ new Map());
652
+ __publicField(this, "errorElements", /* @__PURE__ */ new Map());
653
+ __publicField(this, "submitBtn");
654
+ __publicField(this, "options");
655
+ this.options = options;
656
+ this.container = document.createElement("div");
657
+ this.container.className = "cc-prechat";
658
+ if (options.title) {
659
+ const title = document.createElement("div");
660
+ title.className = "cc-prechat-title";
661
+ title.textContent = options.title;
662
+ this.container.appendChild(title);
663
+ }
664
+ if (options.subtitle) {
665
+ const subtitle = document.createElement("div");
666
+ subtitle.className = "cc-prechat-subtitle";
667
+ subtitle.textContent = options.subtitle;
668
+ this.container.appendChild(subtitle);
669
+ }
670
+ const form = document.createElement("form");
671
+ form.className = "cc-prechat-fields";
672
+ form.addEventListener("submit", (e) => {
673
+ e.preventDefault();
674
+ this.handleSubmit();
675
+ });
676
+ for (const field of options.fields) {
677
+ form.appendChild(this.createField(field));
678
+ }
679
+ this.submitBtn = document.createElement("button");
680
+ this.submitBtn.type = "submit";
681
+ this.submitBtn.className = "cc-prechat-submit";
682
+ this.submitBtn.textContent = options.submitLabel;
683
+ form.appendChild(this.submitBtn);
684
+ this.container.appendChild(form);
685
+ parent.appendChild(this.container);
686
+ this.updateSubmitState();
687
+ const firstInput = this.fieldElements.values().next().value;
688
+ if (firstInput) {
689
+ setTimeout(() => firstInput.focus(), 100);
690
+ }
691
+ }
692
+ createField(field) {
693
+ const wrapper = document.createElement("div");
694
+ wrapper.className = "cc-prechat-field";
695
+ const label = document.createElement("label");
696
+ label.className = "cc-prechat-label";
697
+ label.textContent = field.label;
698
+ if (field.required) {
699
+ const req = document.createElement("span");
700
+ req.className = "cc-required";
701
+ req.textContent = "*";
702
+ label.appendChild(req);
703
+ }
704
+ wrapper.appendChild(label);
705
+ let input;
706
+ if (field.type === "select") {
707
+ const select = document.createElement("select");
708
+ select.className = "cc-prechat-select";
709
+ const defaultOpt = document.createElement("option");
710
+ defaultOpt.value = "";
711
+ defaultOpt.textContent = field.placeholder ?? `Select ${field.label.toLowerCase()}...`;
712
+ defaultOpt.disabled = true;
713
+ defaultOpt.selected = true;
714
+ select.appendChild(defaultOpt);
715
+ for (const opt of field.options ?? []) {
716
+ const option = document.createElement("option");
717
+ option.value = opt;
718
+ option.textContent = opt;
719
+ select.appendChild(option);
720
+ }
721
+ input = select;
722
+ } else if (field.type === "textarea") {
723
+ const textarea = document.createElement("textarea");
724
+ textarea.className = "cc-prechat-textarea";
725
+ textarea.placeholder = field.placeholder ?? "";
726
+ textarea.rows = 3;
727
+ input = textarea;
728
+ } else {
729
+ const textInput = document.createElement("input");
730
+ textInput.type = field.type;
731
+ textInput.className = "cc-prechat-input";
732
+ textInput.placeholder = field.placeholder ?? "";
733
+ input = textInput;
734
+ }
735
+ input.name = field.name;
736
+ input.addEventListener("input", () => {
737
+ this.clearError(field.name);
738
+ this.updateSubmitState();
739
+ });
740
+ if (input instanceof HTMLSelectElement) {
741
+ input.addEventListener("change", () => {
742
+ this.clearError(field.name);
743
+ this.updateSubmitState();
744
+ });
745
+ }
746
+ wrapper.appendChild(input);
747
+ this.fieldElements.set(field.name, input);
748
+ const error = document.createElement("div");
749
+ error.className = "cc-prechat-error";
750
+ wrapper.appendChild(error);
751
+ this.errorElements.set(field.name, error);
752
+ return wrapper;
753
+ }
754
+ updateSubmitState() {
755
+ const allRequiredFilled = this.options.fields.every((field) => {
756
+ if (!field.required) return true;
757
+ const el = this.fieldElements.get(field.name);
758
+ return el ? el.value.trim().length > 0 : false;
759
+ });
760
+ this.submitBtn.disabled = !allRequiredFilled;
761
+ }
762
+ handleSubmit() {
763
+ const data = {};
764
+ let valid = true;
765
+ for (const field of this.options.fields) {
766
+ const el = this.fieldElements.get(field.name);
767
+ if (!el) continue;
768
+ const value = el.value.trim();
769
+ data[field.name] = value;
770
+ if (field.required && !value) {
771
+ this.showError(field.name, this.options.locale.preChatRequired);
772
+ valid = false;
773
+ continue;
774
+ }
775
+ if (field.type === "email" && value && !this.isValidEmail(value)) {
776
+ this.showError(field.name, this.options.locale.preChatInvalidEmail);
777
+ valid = false;
778
+ continue;
779
+ }
780
+ }
781
+ if (valid) {
782
+ this.options.onSubmit(data);
783
+ }
784
+ }
785
+ showError(fieldName, message) {
786
+ const el = this.errorElements.get(fieldName);
787
+ if (el) el.textContent = message;
788
+ }
789
+ clearError(fieldName) {
790
+ const el = this.errorElements.get(fieldName);
791
+ if (el) el.textContent = "";
792
+ }
793
+ isValidEmail(email) {
794
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
795
+ }
796
+ destroy() {
797
+ this.container.remove();
798
+ this.fieldElements.clear();
799
+ this.errorElements.clear();
800
+ }
801
+ }
499
802
  class Widget {
500
803
  constructor(config) {
501
804
  __publicField(this, "config");
@@ -503,12 +806,16 @@ class Widget {
503
806
  __publicField(this, "fab");
504
807
  __publicField(this, "panel");
505
808
  __publicField(this, "bubble");
809
+ __publicField(this, "preChatFormEl");
506
810
  __publicField(this, "client");
507
811
  __publicField(this, "storage");
508
812
  __publicField(this, "locale");
509
813
  __publicField(this, "conversationId");
510
814
  __publicField(this, "messages", []);
815
+ __publicField(this, "userData");
816
+ __publicField(this, "userDataSent", false);
511
817
  __publicField(this, "isStreaming", false);
818
+ __publicField(this, "retryRequests", /* @__PURE__ */ new Map());
512
819
  __publicField(this, "eventHandlers", /* @__PURE__ */ new Map());
513
820
  __publicField(this, "containerEl");
514
821
  this.config = config;
@@ -534,7 +841,7 @@ class Widget {
534
841
  return this.config.container;
535
842
  }
536
843
  init() {
537
- var _a, _b, _c, _d;
844
+ var _a, _b, _c, _d, _e, _f;
538
845
  if (this.isInline) {
539
846
  this.containerEl = this.resolveContainer();
540
847
  this.shadow = createShadowRoot(this.containerEl);
@@ -554,13 +861,24 @@ class Widget {
554
861
  this.panel = new Panel(this.shadow, {
555
862
  position,
556
863
  inline: this.isInline,
864
+ preChatEnabled: ((_b = this.config.preChatForm) == null ? void 0 : _b.enabled) ?? false,
557
865
  branding: {
558
- name: ((_b = this.config.branding) == null ? void 0 : _b.name) ?? "AI Assistant",
559
- avatar: (_c = this.config.branding) == null ? void 0 : _c.avatar,
560
- subtitle: ((_d = this.config.branding) == null ? void 0 : _d.subtitle) ?? "Online"
866
+ name: ((_c = this.config.branding) == null ? void 0 : _c.name) ?? "AI Assistant",
867
+ avatar: (_d = this.config.branding) == null ? void 0 : _d.avatar,
868
+ subtitle: ((_e = this.config.branding) == null ? void 0 : _e.subtitle) ?? "Online"
561
869
  },
562
870
  placeholder: this.config.placeholder ?? this.locale.inputPlaceholder,
563
871
  footerText: this.locale.poweredBy,
872
+ messageActions: {
873
+ onRetry: (messageId) => {
874
+ void this.handleRetry(messageId);
875
+ },
876
+ onRegenerate: (messageId) => {
877
+ void this.handleRegenerate(messageId);
878
+ },
879
+ retryLabel: this.locale.retryButton,
880
+ regenerateLabel: this.locale.regenerateButton
881
+ },
564
882
  onSend: (text) => this.handleSend(text),
565
883
  onClose: () => this.close()
566
884
  });
@@ -574,16 +892,13 @@ class Widget {
574
892
  }
575
893
  }
576
894
  }
577
- if (this.messages.length === 0) {
578
- const welcomeText = this.config.welcomeMessage ?? this.locale.welcomeMessage;
579
- const welcomeMsg = {
580
- id: "welcome",
581
- role: "assistant",
582
- content: welcomeText
583
- };
584
- this.messages.push(welcomeMsg);
585
- this.panel.addMessage(welcomeMsg);
895
+ const shouldShowPreChat = ((_f = this.config.preChatForm) == null ? void 0 : _f.enabled) && !this.isPreChatCompleted() && this.messages.length === 0;
896
+ if (shouldShowPreChat) {
897
+ this.showPreChatForm();
898
+ } else {
899
+ this.showChatView();
586
900
  }
901
+ this.showLatestRegenerateButton();
587
902
  if (!this.isInline && this.config.welcomeBubble) {
588
903
  this.bubble = new WelcomeBubble(this.shadow, {
589
904
  text: this.config.welcomeBubble.text,
@@ -629,10 +944,11 @@ class Widget {
629
944
  }
630
945
  }
631
946
  destroy() {
632
- var _a, _b;
633
- (_a = this.bubble) == null ? void 0 : _a.destroy();
947
+ var _a, _b, _c;
948
+ (_a = this.preChatFormEl) == null ? void 0 : _a.destroy();
949
+ (_b = this.bubble) == null ? void 0 : _b.destroy();
634
950
  this.panel.destroy();
635
- (_b = this.fab) == null ? void 0 : _b.destroy();
951
+ (_c = this.fab) == null ? void 0 : _c.destroy();
636
952
  destroyShadowRoot(this.containerEl);
637
953
  this.eventHandlers.clear();
638
954
  this.messages = [];
@@ -651,8 +967,56 @@ class Widget {
651
967
  var _a;
652
968
  (_a = this.eventHandlers.get(event)) == null ? void 0 : _a.forEach((h) => h(...args));
653
969
  }
970
+ isPreChatCompleted() {
971
+ return this.storage.isPreChatCompleted(this.conversationId);
972
+ }
973
+ setPreChatCompleted() {
974
+ this.storage.setPreChatCompleted(this.conversationId);
975
+ }
976
+ showPreChatForm() {
977
+ const formConfig = this.config.preChatForm;
978
+ this.panel.hideMessages();
979
+ this.panel.input.setVisible(false);
980
+ this.preChatFormEl = new PreChatForm(this.panel.messagesContainer, {
981
+ title: formConfig.title ?? this.locale.preChatTitle,
982
+ subtitle: formConfig.subtitle ?? this.locale.preChatSubtitle,
983
+ fields: formConfig.fields,
984
+ submitLabel: formConfig.submitLabel ?? this.locale.preChatSubmit,
985
+ locale: this.locale,
986
+ onSubmit: (data) => this.handlePreChatSubmit(data)
987
+ });
988
+ }
989
+ handlePreChatSubmit(data) {
990
+ var _a, _b, _c;
991
+ this.userData = data;
992
+ this.setPreChatCompleted();
993
+ (_a = this.preChatFormEl) == null ? void 0 : _a.destroy();
994
+ this.preChatFormEl = void 0;
995
+ this.panel.showMessages();
996
+ this.panel.input.setVisible(true);
997
+ this.showChatView();
998
+ (_c = (_b = this.config).onPreChatSubmit) == null ? void 0 : _c.call(_b, data);
999
+ this.emit("preChatSubmit", data);
1000
+ if (data.message) {
1001
+ this.handleSend(data.message);
1002
+ }
1003
+ }
1004
+ showChatView() {
1005
+ var _a;
1006
+ if (this.messages.length === 0) {
1007
+ const userName = (_a = this.userData) == null ? void 0 : _a.name;
1008
+ const welcomeText = userName ? `Hi ${userName}! ${this.config.welcomeMessage ?? this.locale.welcomeMessage}` : this.config.welcomeMessage ?? this.locale.welcomeMessage;
1009
+ const welcomeMsg = {
1010
+ id: "welcome",
1011
+ role: "assistant",
1012
+ content: welcomeText
1013
+ };
1014
+ this.messages.push(welcomeMsg);
1015
+ this.panel.addMessage(welcomeMsg);
1016
+ }
1017
+ }
654
1018
  async handleSend(text) {
655
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1019
+ var _a, _b;
656
1020
  if (this.isStreaming) return;
657
1021
  const userMsg = {
658
1022
  id: crypto.randomUUID(),
@@ -663,33 +1027,97 @@ class Widget {
663
1027
  this.panel.addMessage(userMsg);
664
1028
  (_b = (_a = this.config).onMessage) == null ? void 0 : _b.call(_a, userMsg);
665
1029
  this.emit("message", userMsg);
1030
+ await this.sendToServer(text, { userMessageId: userMsg.id });
1031
+ }
1032
+ async handleRetry(errorMessageId) {
1033
+ if (this.isStreaming) return;
1034
+ const errorIndex = this.messages.findIndex((message) => message.id === errorMessageId);
1035
+ if (errorIndex === -1) return;
1036
+ const retryRequest = this.retryRequests.get(errorMessageId);
1037
+ let userMessage = (retryRequest == null ? void 0 : retryRequest.userMessageId) ? this.messages.find((message) => message.id === retryRequest.userMessageId) : void 0;
1038
+ if (!userMessage) {
1039
+ for (let i = errorIndex - 1; i >= 0; i--) {
1040
+ if (this.messages[i].role === "user") {
1041
+ userMessage = this.messages[i];
1042
+ break;
1043
+ }
1044
+ }
1045
+ }
1046
+ if (!userMessage) return;
1047
+ this.panel.messages.removeMessage(errorMessageId);
1048
+ this.retryRequests.delete(errorMessageId);
1049
+ this.messages = this.messages.filter((message) => message.id !== errorMessageId);
1050
+ await this.sendToServer(userMessage.content, {
1051
+ regenerate: retryRequest == null ? void 0 : retryRequest.regenerate,
1052
+ userMessageId: (retryRequest == null ? void 0 : retryRequest.userMessageId) ?? userMessage.id
1053
+ });
1054
+ }
1055
+ async handleRegenerate(assistantMessageId) {
1056
+ if (this.isStreaming) return;
1057
+ const assistantIndex = this.messages.findIndex((message) => message.id === assistantMessageId);
1058
+ if (assistantIndex === -1) return;
1059
+ const assistantMessage = this.messages[assistantIndex];
1060
+ if (assistantMessage.role !== "assistant") return;
1061
+ const userMessage = this.findUserMessageForAssistant(assistantIndex);
1062
+ if (!userMessage) return;
1063
+ this.panel.messages.clearRegenerateButtons();
1064
+ this.panel.messages.removeMessage(assistantMessageId);
1065
+ this.retryRequests.delete(assistantMessageId);
1066
+ this.messages = this.messages.filter((message) => message.id !== assistantMessageId);
1067
+ await this.sendToServer(userMessage.content, {
1068
+ regenerate: true,
1069
+ userMessageId: userMessage.id
1070
+ });
1071
+ }
1072
+ async sendToServer(text, options = {}) {
1073
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1074
+ if (this.isStreaming) return;
666
1075
  this.isStreaming = true;
667
1076
  this.panel.input.setDisabled(true);
1077
+ this.panel.messages.clearRegenerateButtons();
668
1078
  this.panel.messages.showTyping();
669
1079
  const assistantMsg = {
670
1080
  id: crypto.randomUUID(),
671
1081
  role: "assistant",
672
- content: ""
1082
+ content: "",
1083
+ status: "streaming"
673
1084
  };
674
- const pageContext = this.config.pageContext !== false ? { url: window.location.href, title: document.title } : void 0;
1085
+ const includeUserData = !!(this.userData && !this.userDataSent);
1086
+ let firstChunk = true;
1087
+ let requestCompleted = false;
1088
+ let hasError = false;
675
1089
  try {
676
- let firstChunk = true;
677
- for await (const chunk of this.client.sendMessage({
678
- conversationId: this.conversationId,
679
- message: text,
680
- pageContext,
681
- locale: this.config.locale
682
- })) {
1090
+ for await (const chunk of this.client.sendMessage(this.createRequest(text, options, includeUserData))) {
683
1091
  if (chunk.error) {
684
- const errorKey = chunk.error === "rate_limit" ? "errorRateLimit" : chunk.error === "network" ? "errorNetwork" : "errorGeneric";
685
- assistantMsg.content = this.locale[errorKey];
1092
+ const errorType = this.resolveErrorType(chunk.error);
1093
+ assistantMsg.content = this.getErrorMessage(errorType);
1094
+ assistantMsg.status = "error";
1095
+ assistantMsg.errorType = errorType;
1096
+ hasError = true;
1097
+ this.retryRequests.set(assistantMsg.id, {
1098
+ regenerate: options.regenerate,
1099
+ userMessageId: options.userMessageId
1100
+ });
686
1101
  this.panel.messages.removeTyping();
687
- this.panel.addMessage(assistantMsg);
688
- (_d = (_c = this.config).onError) == null ? void 0 : _d.call(_c, new Error(chunk.error));
689
- this.emit("error", new Error(chunk.error));
1102
+ if (firstChunk) {
1103
+ this.panel.addMessage(assistantMsg);
1104
+ } else {
1105
+ this.panel.messages.updateMessage(assistantMsg);
1106
+ }
1107
+ this.messages.push(assistantMsg);
1108
+ const error = new Error(chunk.error);
1109
+ (_b = (_a = this.config).onError) == null ? void 0 : _b.call(_a, error);
1110
+ this.emit("error", error);
1111
+ break;
1112
+ }
1113
+ if (chunk.done) {
1114
+ requestCompleted = true;
690
1115
  break;
691
1116
  }
692
- if (chunk.done) break;
1117
+ if (chunk.leadCaptured) {
1118
+ (_d = (_c = this.config).onLeadCaptured) == null ? void 0 : _d.call(_c, chunk.leadData);
1119
+ this.emit("leadCaptured", chunk.leadData);
1120
+ }
693
1121
  if (chunk.content) {
694
1122
  if (firstChunk) {
695
1123
  this.panel.messages.removeTyping();
@@ -697,29 +1125,113 @@ class Widget {
697
1125
  firstChunk = false;
698
1126
  }
699
1127
  assistantMsg.content += chunk.content;
700
- this.panel.messages.updateMessage(assistantMsg.id, assistantMsg.content, true);
1128
+ this.panel.messages.updateMessage(assistantMsg);
701
1129
  }
702
1130
  }
703
- if (assistantMsg.content) {
1131
+ if (!hasError && !requestCompleted) {
1132
+ requestCompleted = true;
1133
+ }
1134
+ if (!hasError && firstChunk) {
1135
+ this.panel.messages.removeTyping();
1136
+ }
1137
+ if (!hasError && assistantMsg.content) {
1138
+ assistantMsg.status = "complete";
1139
+ this.panel.messages.updateMessage(assistantMsg);
704
1140
  this.messages.push(assistantMsg);
705
1141
  (_f = (_e = this.config).onMessage) == null ? void 0 : _f.call(_e, assistantMsg);
706
1142
  this.emit("message", assistantMsg);
1143
+ this.showLatestRegenerateButton();
707
1144
  if (!this.isInline && !this.panel.isVisible) {
708
1145
  (_g = this.fab) == null ? void 0 : _g.showBadge();
709
1146
  }
710
1147
  }
711
1148
  } catch (err) {
712
- this.panel.messages.removeTyping();
713
1149
  assistantMsg.content = this.locale.errorGeneric;
1150
+ assistantMsg.status = "error";
1151
+ assistantMsg.errorType = "network";
1152
+ this.retryRequests.set(assistantMsg.id, {
1153
+ regenerate: options.regenerate,
1154
+ userMessageId: options.userMessageId
1155
+ });
1156
+ this.panel.messages.removeTyping();
714
1157
  this.panel.addMessage(assistantMsg);
715
- (_i = (_h = this.config).onError) == null ? void 0 : _i.call(_h, err instanceof Error ? err : new Error(String(err)));
1158
+ this.messages.push(assistantMsg);
1159
+ const error = err instanceof Error ? err : new Error(String(err));
1160
+ (_i = (_h = this.config).onError) == null ? void 0 : _i.call(_h, error);
1161
+ this.emit("error", error);
716
1162
  } finally {
1163
+ if (includeUserData && requestCompleted) {
1164
+ this.userDataSent = true;
1165
+ }
717
1166
  this.isStreaming = false;
718
1167
  this.panel.input.setDisabled(false);
719
1168
  this.panel.input.focus();
720
1169
  this.saveHistory();
721
1170
  }
722
1171
  }
1172
+ createRequest(text, options, includeUserData) {
1173
+ const pageContext = this.config.pageContext !== false ? { url: window.location.href, title: document.title } : void 0;
1174
+ return {
1175
+ conversationId: this.conversationId,
1176
+ message: text,
1177
+ messageId: options.userMessageId,
1178
+ regenerate: options.regenerate,
1179
+ pageContext,
1180
+ locale: this.config.locale,
1181
+ ...includeUserData ? { userData: this.userData } : {}
1182
+ };
1183
+ }
1184
+ findUserMessageForAssistant(assistantIndex) {
1185
+ for (let i = assistantIndex - 1; i >= 0; i--) {
1186
+ if (this.messages[i].role === "user") {
1187
+ return this.messages[i];
1188
+ }
1189
+ }
1190
+ return void 0;
1191
+ }
1192
+ findLatestRegeneratableAssistant() {
1193
+ for (let i = this.messages.length - 1; i >= 0; i--) {
1194
+ const message = this.messages[i];
1195
+ if (message.id === "welcome") {
1196
+ continue;
1197
+ }
1198
+ if (message.role !== "assistant" || message.status === "error") {
1199
+ return void 0;
1200
+ }
1201
+ return message;
1202
+ }
1203
+ return void 0;
1204
+ }
1205
+ showLatestRegenerateButton() {
1206
+ const assistantMessage = this.findLatestRegeneratableAssistant();
1207
+ if (assistantMessage) {
1208
+ this.panel.messages.showRegenerateButton(assistantMessage.id);
1209
+ } else {
1210
+ this.panel.messages.clearRegenerateButtons();
1211
+ }
1212
+ }
1213
+ resolveErrorType(error) {
1214
+ switch (error) {
1215
+ case "rate_limit":
1216
+ case "network":
1217
+ case "provider_error":
1218
+ case "timeout":
1219
+ return error;
1220
+ default:
1221
+ return "provider_error";
1222
+ }
1223
+ }
1224
+ getErrorMessage(errorType) {
1225
+ switch (errorType) {
1226
+ case "rate_limit":
1227
+ return this.locale.errorRateLimit;
1228
+ case "network":
1229
+ case "timeout":
1230
+ return this.locale.errorNetwork;
1231
+ default:
1232
+ return this.locale.errorGeneric;
1233
+ }
1234
+ }
723
1235
  saveHistory() {
724
1236
  if (this.config.persistHistory === false) return;
725
1237
  const maxMessages = this.config.maxMessages ?? 50;
@@ -730,6 +1242,8 @@ class Widget {
730
1242
  id: m.id,
731
1243
  role: m.role,
732
1244
  content: m.content,
1245
+ status: m.status,
1246
+ errorType: m.errorType,
733
1247
  timestamp: Date.now()
734
1248
  })),
735
1249
  updatedAt: Date.now()