@chatcops/widget 0.3.2 → 0.4.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.
@@ -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";
@@ -96,28 +96,29 @@ function renderMarkdown(input) {
96
96
  html = html.replace(/\n/g, "<br>");
97
97
  return html;
98
98
  }
99
+ 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>';
100
+ 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
101
  class Messages {
100
- constructor(parent) {
102
+ constructor(parent, actions) {
101
103
  __publicField(this, "container");
102
104
  __publicField(this, "typingEl", null);
105
+ __publicField(this, "actions");
106
+ this.actions = actions;
103
107
  this.container = document.createElement("div");
104
108
  this.container.className = "cc-messages";
105
109
  parent.appendChild(this.container);
106
110
  }
107
111
  addMessage(msg) {
108
112
  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);
113
+ const el = this.createMessageElement(msg);
113
114
  this.container.appendChild(el);
114
115
  this.scrollToBottom();
115
116
  return el;
116
117
  }
117
- updateMessage(id, content, isAssistant) {
118
- const el = this.container.querySelector(`[data-id="${id}"]`);
118
+ updateMessage(msg) {
119
+ const el = this.container.querySelector(`[data-id="${msg.id}"]`);
119
120
  if (el) {
120
- el.innerHTML = isAssistant ? renderMarkdown(content) : this.escapeHtml(content);
121
+ this.renderMessageElement(el, msg);
121
122
  this.scrollToBottom();
122
123
  }
123
124
  }
@@ -139,9 +140,108 @@ class Messages {
139
140
  this.container.innerHTML = "";
140
141
  this.typingEl = null;
141
142
  }
143
+ removeMessage(id) {
144
+ const el = this.container.querySelector(`[data-id="${id}"]`);
145
+ el == null ? void 0 : el.remove();
146
+ }
147
+ clearRegenerateButtons() {
148
+ this.container.querySelectorAll(".cc-message-regenerate").forEach((button) => button.remove());
149
+ this.cleanupEmptyActionContainers();
150
+ }
151
+ showRegenerateButton(messageId) {
152
+ var _a;
153
+ this.clearRegenerateButtons();
154
+ if (!((_a = this.actions) == null ? void 0 : _a.onRegenerate)) return;
155
+ const msgEl = this.container.querySelector(`[data-id="${messageId}"]`);
156
+ if (!msgEl || !msgEl.classList.contains("cc-message-assistant")) return;
157
+ const actions = this.ensureActionsContainer(msgEl);
158
+ actions.appendChild(this.createActionButton({
159
+ className: "cc-message-regenerate",
160
+ label: this.actions.regenerateLabel ?? "Regenerate",
161
+ icon: REGENERATE_ICON,
162
+ onClick: () => {
163
+ var _a2, _b;
164
+ return (_b = (_a2 = this.actions) == null ? void 0 : _a2.onRegenerate) == null ? void 0 : _b.call(_a2, messageId);
165
+ }
166
+ }));
167
+ }
168
+ setVisible(visible) {
169
+ this.container.classList.toggle("cc-hidden", !visible);
170
+ }
142
171
  scrollToBottom() {
143
172
  this.container.scrollTop = this.container.scrollHeight;
144
173
  }
174
+ createMessageElement(msg) {
175
+ const el = document.createElement("div");
176
+ this.renderMessageElement(el, msg);
177
+ return el;
178
+ }
179
+ renderMessageElement(el, msg) {
180
+ el.className = `cc-message cc-message-${msg.role}`;
181
+ el.dataset.id = msg.id;
182
+ if (msg.status) {
183
+ el.dataset.status = msg.status;
184
+ } else {
185
+ delete el.dataset.status;
186
+ }
187
+ if (msg.errorType) {
188
+ el.dataset.errorType = msg.errorType;
189
+ } else {
190
+ delete el.dataset.errorType;
191
+ }
192
+ el.innerHTML = "";
193
+ const content = document.createElement("div");
194
+ content.className = "cc-message-content";
195
+ content.innerHTML = msg.role === "assistant" ? renderMarkdown(msg.content) : this.escapeHtml(msg.content);
196
+ el.appendChild(content);
197
+ const actions = this.createActionsContainer(msg);
198
+ if (actions) {
199
+ el.appendChild(actions);
200
+ }
201
+ }
202
+ createActionsContainer(msg) {
203
+ var _a;
204
+ if (msg.status !== "error" || !((_a = this.actions) == null ? void 0 : _a.onRetry)) {
205
+ return null;
206
+ }
207
+ const actions = document.createElement("div");
208
+ actions.className = "cc-message-actions";
209
+ actions.appendChild(this.createActionButton({
210
+ className: "cc-message-retry",
211
+ label: this.actions.retryLabel ?? "Retry",
212
+ icon: RETRY_ICON,
213
+ onClick: () => {
214
+ var _a2, _b;
215
+ return (_b = (_a2 = this.actions) == null ? void 0 : _a2.onRetry) == null ? void 0 : _b.call(_a2, msg.id);
216
+ }
217
+ }));
218
+ return actions;
219
+ }
220
+ ensureActionsContainer(msgEl) {
221
+ const existing = msgEl.querySelector(".cc-message-actions");
222
+ if (existing) {
223
+ return existing;
224
+ }
225
+ const actions = document.createElement("div");
226
+ actions.className = "cc-message-actions";
227
+ msgEl.appendChild(actions);
228
+ return actions;
229
+ }
230
+ cleanupEmptyActionContainers() {
231
+ this.container.querySelectorAll(".cc-message-actions").forEach((container) => {
232
+ if (!container.hasChildNodes()) {
233
+ container.remove();
234
+ }
235
+ });
236
+ }
237
+ createActionButton(options) {
238
+ const button = document.createElement("button");
239
+ button.type = "button";
240
+ button.className = options.className;
241
+ button.innerHTML = `${options.icon}<span>${this.escapeHtml(options.label)}</span>`;
242
+ button.addEventListener("click", options.onClick);
243
+ return button;
244
+ }
145
245
  escapeHtml(text) {
146
246
  const div = document.createElement("div");
147
247
  div.textContent = text;
@@ -188,6 +288,9 @@ class Input {
188
288
  this.sendBtn.disabled = true;
189
289
  this.autoResize();
190
290
  }
291
+ setVisible(visible) {
292
+ this.area.classList.toggle("cc-hidden", !visible);
293
+ }
191
294
  send() {
192
295
  const text = this.textarea.value.trim();
193
296
  if (!text) return;
@@ -214,6 +317,7 @@ class Panel {
214
317
  __publicField(this, "inline");
215
318
  __publicField(this, "messages");
216
319
  __publicField(this, "input");
320
+ __publicField(this, "messagesContainer");
217
321
  this.inline = options.inline ?? false;
218
322
  this.el = document.createElement("div");
219
323
  this.el.className = "cc-panel";
@@ -250,7 +354,14 @@ class Panel {
250
354
  header.appendChild(closeBtn);
251
355
  }
252
356
  this.el.appendChild(header);
253
- this.messages = new Messages(this.el);
357
+ if (options.preChatEnabled) {
358
+ this.messagesContainer = document.createElement("div");
359
+ this.messagesContainer.className = "cc-messages-wrapper";
360
+ this.el.appendChild(this.messagesContainer);
361
+ } else {
362
+ this.messagesContainer = this.el;
363
+ }
364
+ this.messages = new Messages(this.messagesContainer, options.messageActions);
254
365
  this.input = new Input(this.el, {
255
366
  placeholder: options.placeholder,
256
367
  onSend: options.onSend
@@ -276,6 +387,12 @@ class Panel {
276
387
  addMessage(msg) {
277
388
  return this.messages.addMessage(msg);
278
389
  }
390
+ hideMessages() {
391
+ this.messages.setVisible(false);
392
+ }
393
+ showMessages() {
394
+ this.messages.setVisible(true);
395
+ }
279
396
  destroy() {
280
397
  this.el.remove();
281
398
  }
@@ -414,7 +531,11 @@ class ConversationStorage {
414
531
  }
415
532
  save(conversation) {
416
533
  try {
417
- localStorage.setItem(this.key, JSON.stringify(conversation));
534
+ const filtered = {
535
+ ...conversation,
536
+ messages: conversation.messages.filter((m) => m.status !== "error").map(({ status, ...message }) => message)
537
+ };
538
+ localStorage.setItem(this.key, JSON.stringify(filtered));
418
539
  } catch {
419
540
  }
420
541
  }
@@ -437,6 +558,19 @@ class ConversationStorage {
437
558
  return crypto.randomUUID();
438
559
  }
439
560
  }
561
+ isPreChatCompleted(sessionId) {
562
+ try {
563
+ return sessionStorage.getItem(`chatcops-prechat-${sessionId}`) === "true";
564
+ } catch {
565
+ return false;
566
+ }
567
+ }
568
+ setPreChatCompleted(sessionId) {
569
+ try {
570
+ sessionStorage.setItem(`chatcops-prechat-${sessionId}`, "true");
571
+ } catch {
572
+ }
573
+ }
440
574
  }
441
575
  function applyTheme(root, theme) {
442
576
  const host = root.host;
@@ -489,13 +623,174 @@ const en = {
489
623
  typingIndicator: "Typing...",
490
624
  poweredBy: "Powered by ChatCops",
491
625
  newConversation: "New conversation",
492
- welcomeBubbleDefault: "Need help? Chat with us!"
626
+ welcomeBubbleDefault: "Need help? Chat with us!",
627
+ preChatTitle: "Before we start...",
628
+ preChatSubtitle: "Tell us a bit about yourself",
629
+ preChatSubmit: "Start Chat",
630
+ preChatRequired: "This field is required",
631
+ preChatInvalidEmail: "Please enter a valid email",
632
+ retryButton: "Retry",
633
+ regenerateButton: "Regenerate"
493
634
  };
494
635
  const locales = { en };
495
636
  function getWidgetLocale(code, overrides) {
496
637
  const base = locales[code] ?? en;
497
638
  return overrides ? { ...base, ...overrides } : base;
498
639
  }
640
+ class PreChatForm {
641
+ constructor(parent, options) {
642
+ __publicField(this, "container");
643
+ __publicField(this, "fieldElements", /* @__PURE__ */ new Map());
644
+ __publicField(this, "errorElements", /* @__PURE__ */ new Map());
645
+ __publicField(this, "submitBtn");
646
+ __publicField(this, "options");
647
+ this.options = options;
648
+ this.container = document.createElement("div");
649
+ this.container.className = "cc-prechat";
650
+ if (options.title) {
651
+ const title = document.createElement("div");
652
+ title.className = "cc-prechat-title";
653
+ title.textContent = options.title;
654
+ this.container.appendChild(title);
655
+ }
656
+ if (options.subtitle) {
657
+ const subtitle = document.createElement("div");
658
+ subtitle.className = "cc-prechat-subtitle";
659
+ subtitle.textContent = options.subtitle;
660
+ this.container.appendChild(subtitle);
661
+ }
662
+ const form = document.createElement("form");
663
+ form.className = "cc-prechat-fields";
664
+ form.addEventListener("submit", (e) => {
665
+ e.preventDefault();
666
+ this.handleSubmit();
667
+ });
668
+ for (const field of options.fields) {
669
+ form.appendChild(this.createField(field));
670
+ }
671
+ this.submitBtn = document.createElement("button");
672
+ this.submitBtn.type = "submit";
673
+ this.submitBtn.className = "cc-prechat-submit";
674
+ this.submitBtn.textContent = options.submitLabel;
675
+ form.appendChild(this.submitBtn);
676
+ this.container.appendChild(form);
677
+ parent.appendChild(this.container);
678
+ this.updateSubmitState();
679
+ const firstInput = this.fieldElements.values().next().value;
680
+ if (firstInput) {
681
+ setTimeout(() => firstInput.focus(), 100);
682
+ }
683
+ }
684
+ createField(field) {
685
+ const wrapper = document.createElement("div");
686
+ wrapper.className = "cc-prechat-field";
687
+ const label = document.createElement("label");
688
+ label.className = "cc-prechat-label";
689
+ label.textContent = field.label;
690
+ if (field.required) {
691
+ const req = document.createElement("span");
692
+ req.className = "cc-required";
693
+ req.textContent = "*";
694
+ label.appendChild(req);
695
+ }
696
+ wrapper.appendChild(label);
697
+ let input;
698
+ if (field.type === "select") {
699
+ const select = document.createElement("select");
700
+ select.className = "cc-prechat-select";
701
+ const defaultOpt = document.createElement("option");
702
+ defaultOpt.value = "";
703
+ defaultOpt.textContent = field.placeholder ?? `Select ${field.label.toLowerCase()}...`;
704
+ defaultOpt.disabled = true;
705
+ defaultOpt.selected = true;
706
+ select.appendChild(defaultOpt);
707
+ for (const opt of field.options ?? []) {
708
+ const option = document.createElement("option");
709
+ option.value = opt;
710
+ option.textContent = opt;
711
+ select.appendChild(option);
712
+ }
713
+ input = select;
714
+ } else if (field.type === "textarea") {
715
+ const textarea = document.createElement("textarea");
716
+ textarea.className = "cc-prechat-textarea";
717
+ textarea.placeholder = field.placeholder ?? "";
718
+ textarea.rows = 3;
719
+ input = textarea;
720
+ } else {
721
+ const textInput = document.createElement("input");
722
+ textInput.type = field.type;
723
+ textInput.className = "cc-prechat-input";
724
+ textInput.placeholder = field.placeholder ?? "";
725
+ input = textInput;
726
+ }
727
+ input.name = field.name;
728
+ input.addEventListener("input", () => {
729
+ this.clearError(field.name);
730
+ this.updateSubmitState();
731
+ });
732
+ if (input instanceof HTMLSelectElement) {
733
+ input.addEventListener("change", () => {
734
+ this.clearError(field.name);
735
+ this.updateSubmitState();
736
+ });
737
+ }
738
+ wrapper.appendChild(input);
739
+ this.fieldElements.set(field.name, input);
740
+ const error = document.createElement("div");
741
+ error.className = "cc-prechat-error";
742
+ wrapper.appendChild(error);
743
+ this.errorElements.set(field.name, error);
744
+ return wrapper;
745
+ }
746
+ updateSubmitState() {
747
+ const allRequiredFilled = this.options.fields.every((field) => {
748
+ if (!field.required) return true;
749
+ const el = this.fieldElements.get(field.name);
750
+ return el ? el.value.trim().length > 0 : false;
751
+ });
752
+ this.submitBtn.disabled = !allRequiredFilled;
753
+ }
754
+ handleSubmit() {
755
+ const data = {};
756
+ let valid = true;
757
+ for (const field of this.options.fields) {
758
+ const el = this.fieldElements.get(field.name);
759
+ if (!el) continue;
760
+ const value = el.value.trim();
761
+ data[field.name] = value;
762
+ if (field.required && !value) {
763
+ this.showError(field.name, this.options.locale.preChatRequired);
764
+ valid = false;
765
+ continue;
766
+ }
767
+ if (field.type === "email" && value && !this.isValidEmail(value)) {
768
+ this.showError(field.name, this.options.locale.preChatInvalidEmail);
769
+ valid = false;
770
+ continue;
771
+ }
772
+ }
773
+ if (valid) {
774
+ this.options.onSubmit(data);
775
+ }
776
+ }
777
+ showError(fieldName, message) {
778
+ const el = this.errorElements.get(fieldName);
779
+ if (el) el.textContent = message;
780
+ }
781
+ clearError(fieldName) {
782
+ const el = this.errorElements.get(fieldName);
783
+ if (el) el.textContent = "";
784
+ }
785
+ isValidEmail(email) {
786
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
787
+ }
788
+ destroy() {
789
+ this.container.remove();
790
+ this.fieldElements.clear();
791
+ this.errorElements.clear();
792
+ }
793
+ }
499
794
  class Widget {
500
795
  constructor(config) {
501
796
  __publicField(this, "config");
@@ -503,12 +798,16 @@ class Widget {
503
798
  __publicField(this, "fab");
504
799
  __publicField(this, "panel");
505
800
  __publicField(this, "bubble");
801
+ __publicField(this, "preChatFormEl");
506
802
  __publicField(this, "client");
507
803
  __publicField(this, "storage");
508
804
  __publicField(this, "locale");
509
805
  __publicField(this, "conversationId");
510
806
  __publicField(this, "messages", []);
807
+ __publicField(this, "userData");
808
+ __publicField(this, "userDataSent", false);
511
809
  __publicField(this, "isStreaming", false);
810
+ __publicField(this, "retryRequests", /* @__PURE__ */ new Map());
512
811
  __publicField(this, "eventHandlers", /* @__PURE__ */ new Map());
513
812
  __publicField(this, "containerEl");
514
813
  this.config = config;
@@ -534,7 +833,7 @@ class Widget {
534
833
  return this.config.container;
535
834
  }
536
835
  init() {
537
- var _a, _b, _c, _d;
836
+ var _a, _b, _c, _d, _e, _f;
538
837
  if (this.isInline) {
539
838
  this.containerEl = this.resolveContainer();
540
839
  this.shadow = createShadowRoot(this.containerEl);
@@ -554,13 +853,24 @@ class Widget {
554
853
  this.panel = new Panel(this.shadow, {
555
854
  position,
556
855
  inline: this.isInline,
856
+ preChatEnabled: ((_b = this.config.preChatForm) == null ? void 0 : _b.enabled) ?? false,
557
857
  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"
858
+ name: ((_c = this.config.branding) == null ? void 0 : _c.name) ?? "AI Assistant",
859
+ avatar: (_d = this.config.branding) == null ? void 0 : _d.avatar,
860
+ subtitle: ((_e = this.config.branding) == null ? void 0 : _e.subtitle) ?? "Online"
561
861
  },
562
862
  placeholder: this.config.placeholder ?? this.locale.inputPlaceholder,
563
863
  footerText: this.locale.poweredBy,
864
+ messageActions: {
865
+ onRetry: (messageId) => {
866
+ void this.handleRetry(messageId);
867
+ },
868
+ onRegenerate: (messageId) => {
869
+ void this.handleRegenerate(messageId);
870
+ },
871
+ retryLabel: this.locale.retryButton,
872
+ regenerateLabel: this.locale.regenerateButton
873
+ },
564
874
  onSend: (text) => this.handleSend(text),
565
875
  onClose: () => this.close()
566
876
  });
@@ -574,16 +884,13 @@ class Widget {
574
884
  }
575
885
  }
576
886
  }
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);
887
+ const shouldShowPreChat = ((_f = this.config.preChatForm) == null ? void 0 : _f.enabled) && !this.isPreChatCompleted() && this.messages.length === 0;
888
+ if (shouldShowPreChat) {
889
+ this.showPreChatForm();
890
+ } else {
891
+ this.showChatView();
586
892
  }
893
+ this.showLatestRegenerateButton();
587
894
  if (!this.isInline && this.config.welcomeBubble) {
588
895
  this.bubble = new WelcomeBubble(this.shadow, {
589
896
  text: this.config.welcomeBubble.text,
@@ -629,10 +936,11 @@ class Widget {
629
936
  }
630
937
  }
631
938
  destroy() {
632
- var _a, _b;
633
- (_a = this.bubble) == null ? void 0 : _a.destroy();
939
+ var _a, _b, _c;
940
+ (_a = this.preChatFormEl) == null ? void 0 : _a.destroy();
941
+ (_b = this.bubble) == null ? void 0 : _b.destroy();
634
942
  this.panel.destroy();
635
- (_b = this.fab) == null ? void 0 : _b.destroy();
943
+ (_c = this.fab) == null ? void 0 : _c.destroy();
636
944
  destroyShadowRoot(this.containerEl);
637
945
  this.eventHandlers.clear();
638
946
  this.messages = [];
@@ -651,8 +959,56 @@ class Widget {
651
959
  var _a;
652
960
  (_a = this.eventHandlers.get(event)) == null ? void 0 : _a.forEach((h) => h(...args));
653
961
  }
962
+ isPreChatCompleted() {
963
+ return this.storage.isPreChatCompleted(this.conversationId);
964
+ }
965
+ setPreChatCompleted() {
966
+ this.storage.setPreChatCompleted(this.conversationId);
967
+ }
968
+ showPreChatForm() {
969
+ const formConfig = this.config.preChatForm;
970
+ this.panel.hideMessages();
971
+ this.panel.input.setVisible(false);
972
+ this.preChatFormEl = new PreChatForm(this.panel.messagesContainer, {
973
+ title: formConfig.title ?? this.locale.preChatTitle,
974
+ subtitle: formConfig.subtitle ?? this.locale.preChatSubtitle,
975
+ fields: formConfig.fields,
976
+ submitLabel: formConfig.submitLabel ?? this.locale.preChatSubmit,
977
+ locale: this.locale,
978
+ onSubmit: (data) => this.handlePreChatSubmit(data)
979
+ });
980
+ }
981
+ handlePreChatSubmit(data) {
982
+ var _a, _b, _c;
983
+ this.userData = data;
984
+ this.setPreChatCompleted();
985
+ (_a = this.preChatFormEl) == null ? void 0 : _a.destroy();
986
+ this.preChatFormEl = void 0;
987
+ this.panel.showMessages();
988
+ this.panel.input.setVisible(true);
989
+ this.showChatView();
990
+ (_c = (_b = this.config).onPreChatSubmit) == null ? void 0 : _c.call(_b, data);
991
+ this.emit("preChatSubmit", data);
992
+ if (data.message) {
993
+ this.handleSend(data.message);
994
+ }
995
+ }
996
+ showChatView() {
997
+ var _a;
998
+ if (this.messages.length === 0) {
999
+ const userName = (_a = this.userData) == null ? void 0 : _a.name;
1000
+ const welcomeText = userName ? `Hi ${userName}! ${this.config.welcomeMessage ?? this.locale.welcomeMessage}` : this.config.welcomeMessage ?? this.locale.welcomeMessage;
1001
+ const welcomeMsg = {
1002
+ id: "welcome",
1003
+ role: "assistant",
1004
+ content: welcomeText
1005
+ };
1006
+ this.messages.push(welcomeMsg);
1007
+ this.panel.addMessage(welcomeMsg);
1008
+ }
1009
+ }
654
1010
  async handleSend(text) {
655
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1011
+ var _a, _b;
656
1012
  if (this.isStreaming) return;
657
1013
  const userMsg = {
658
1014
  id: crypto.randomUUID(),
@@ -663,33 +1019,97 @@ class Widget {
663
1019
  this.panel.addMessage(userMsg);
664
1020
  (_b = (_a = this.config).onMessage) == null ? void 0 : _b.call(_a, userMsg);
665
1021
  this.emit("message", userMsg);
1022
+ await this.sendToServer(text, { userMessageId: userMsg.id });
1023
+ }
1024
+ async handleRetry(errorMessageId) {
1025
+ if (this.isStreaming) return;
1026
+ const errorIndex = this.messages.findIndex((message) => message.id === errorMessageId);
1027
+ if (errorIndex === -1) return;
1028
+ const retryRequest = this.retryRequests.get(errorMessageId);
1029
+ let userMessage = (retryRequest == null ? void 0 : retryRequest.userMessageId) ? this.messages.find((message) => message.id === retryRequest.userMessageId) : void 0;
1030
+ if (!userMessage) {
1031
+ for (let i = errorIndex - 1; i >= 0; i--) {
1032
+ if (this.messages[i].role === "user") {
1033
+ userMessage = this.messages[i];
1034
+ break;
1035
+ }
1036
+ }
1037
+ }
1038
+ if (!userMessage) return;
1039
+ this.panel.messages.removeMessage(errorMessageId);
1040
+ this.retryRequests.delete(errorMessageId);
1041
+ this.messages = this.messages.filter((message) => message.id !== errorMessageId);
1042
+ await this.sendToServer(userMessage.content, {
1043
+ regenerate: retryRequest == null ? void 0 : retryRequest.regenerate,
1044
+ userMessageId: (retryRequest == null ? void 0 : retryRequest.userMessageId) ?? userMessage.id
1045
+ });
1046
+ }
1047
+ async handleRegenerate(assistantMessageId) {
1048
+ if (this.isStreaming) return;
1049
+ const assistantIndex = this.messages.findIndex((message) => message.id === assistantMessageId);
1050
+ if (assistantIndex === -1) return;
1051
+ const assistantMessage = this.messages[assistantIndex];
1052
+ if (assistantMessage.role !== "assistant") return;
1053
+ const userMessage = this.findUserMessageForAssistant(assistantIndex);
1054
+ if (!userMessage) return;
1055
+ this.panel.messages.clearRegenerateButtons();
1056
+ this.panel.messages.removeMessage(assistantMessageId);
1057
+ this.retryRequests.delete(assistantMessageId);
1058
+ this.messages = this.messages.filter((message) => message.id !== assistantMessageId);
1059
+ await this.sendToServer(userMessage.content, {
1060
+ regenerate: true,
1061
+ userMessageId: userMessage.id
1062
+ });
1063
+ }
1064
+ async sendToServer(text, options = {}) {
1065
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1066
+ if (this.isStreaming) return;
666
1067
  this.isStreaming = true;
667
1068
  this.panel.input.setDisabled(true);
1069
+ this.panel.messages.clearRegenerateButtons();
668
1070
  this.panel.messages.showTyping();
669
1071
  const assistantMsg = {
670
1072
  id: crypto.randomUUID(),
671
1073
  role: "assistant",
672
- content: ""
1074
+ content: "",
1075
+ status: "streaming"
673
1076
  };
674
- const pageContext = this.config.pageContext !== false ? { url: window.location.href, title: document.title } : void 0;
1077
+ const includeUserData = !!(this.userData && !this.userDataSent);
1078
+ let firstChunk = true;
1079
+ let requestCompleted = false;
1080
+ let hasError = false;
675
1081
  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
- })) {
1082
+ for await (const chunk of this.client.sendMessage(this.createRequest(text, options, includeUserData))) {
683
1083
  if (chunk.error) {
684
- const errorKey = chunk.error === "rate_limit" ? "errorRateLimit" : chunk.error === "network" ? "errorNetwork" : "errorGeneric";
685
- assistantMsg.content = this.locale[errorKey];
1084
+ const errorType = this.resolveErrorType(chunk.error);
1085
+ assistantMsg.content = this.getErrorMessage(errorType);
1086
+ assistantMsg.status = "error";
1087
+ assistantMsg.errorType = errorType;
1088
+ hasError = true;
1089
+ this.retryRequests.set(assistantMsg.id, {
1090
+ regenerate: options.regenerate,
1091
+ userMessageId: options.userMessageId
1092
+ });
686
1093
  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));
1094
+ if (firstChunk) {
1095
+ this.panel.addMessage(assistantMsg);
1096
+ } else {
1097
+ this.panel.messages.updateMessage(assistantMsg);
1098
+ }
1099
+ this.messages.push(assistantMsg);
1100
+ const error = new Error(chunk.error);
1101
+ (_b = (_a = this.config).onError) == null ? void 0 : _b.call(_a, error);
1102
+ this.emit("error", error);
690
1103
  break;
691
1104
  }
692
- if (chunk.done) break;
1105
+ if (chunk.done) {
1106
+ requestCompleted = true;
1107
+ break;
1108
+ }
1109
+ if (chunk.leadCaptured) {
1110
+ (_d = (_c = this.config).onLeadCaptured) == null ? void 0 : _d.call(_c, chunk.leadData);
1111
+ this.emit("leadCaptured", chunk.leadData);
1112
+ }
693
1113
  if (chunk.content) {
694
1114
  if (firstChunk) {
695
1115
  this.panel.messages.removeTyping();
@@ -697,29 +1117,113 @@ class Widget {
697
1117
  firstChunk = false;
698
1118
  }
699
1119
  assistantMsg.content += chunk.content;
700
- this.panel.messages.updateMessage(assistantMsg.id, assistantMsg.content, true);
1120
+ this.panel.messages.updateMessage(assistantMsg);
701
1121
  }
702
1122
  }
703
- if (assistantMsg.content) {
1123
+ if (!hasError && !requestCompleted) {
1124
+ requestCompleted = true;
1125
+ }
1126
+ if (!hasError && firstChunk) {
1127
+ this.panel.messages.removeTyping();
1128
+ }
1129
+ if (!hasError && assistantMsg.content) {
1130
+ assistantMsg.status = "complete";
1131
+ this.panel.messages.updateMessage(assistantMsg);
704
1132
  this.messages.push(assistantMsg);
705
1133
  (_f = (_e = this.config).onMessage) == null ? void 0 : _f.call(_e, assistantMsg);
706
1134
  this.emit("message", assistantMsg);
1135
+ this.showLatestRegenerateButton();
707
1136
  if (!this.isInline && !this.panel.isVisible) {
708
1137
  (_g = this.fab) == null ? void 0 : _g.showBadge();
709
1138
  }
710
1139
  }
711
1140
  } catch (err) {
712
- this.panel.messages.removeTyping();
713
1141
  assistantMsg.content = this.locale.errorGeneric;
1142
+ assistantMsg.status = "error";
1143
+ assistantMsg.errorType = "network";
1144
+ this.retryRequests.set(assistantMsg.id, {
1145
+ regenerate: options.regenerate,
1146
+ userMessageId: options.userMessageId
1147
+ });
1148
+ this.panel.messages.removeTyping();
714
1149
  this.panel.addMessage(assistantMsg);
715
- (_i = (_h = this.config).onError) == null ? void 0 : _i.call(_h, err instanceof Error ? err : new Error(String(err)));
1150
+ this.messages.push(assistantMsg);
1151
+ const error = err instanceof Error ? err : new Error(String(err));
1152
+ (_i = (_h = this.config).onError) == null ? void 0 : _i.call(_h, error);
1153
+ this.emit("error", error);
716
1154
  } finally {
1155
+ if (includeUserData && requestCompleted) {
1156
+ this.userDataSent = true;
1157
+ }
717
1158
  this.isStreaming = false;
718
1159
  this.panel.input.setDisabled(false);
719
1160
  this.panel.input.focus();
720
1161
  this.saveHistory();
721
1162
  }
722
1163
  }
1164
+ createRequest(text, options, includeUserData) {
1165
+ const pageContext = this.config.pageContext !== false ? { url: window.location.href, title: document.title } : void 0;
1166
+ return {
1167
+ conversationId: this.conversationId,
1168
+ message: text,
1169
+ messageId: options.userMessageId,
1170
+ regenerate: options.regenerate,
1171
+ pageContext,
1172
+ locale: this.config.locale,
1173
+ ...includeUserData ? { userData: this.userData } : {}
1174
+ };
1175
+ }
1176
+ findUserMessageForAssistant(assistantIndex) {
1177
+ for (let i = assistantIndex - 1; i >= 0; i--) {
1178
+ if (this.messages[i].role === "user") {
1179
+ return this.messages[i];
1180
+ }
1181
+ }
1182
+ return void 0;
1183
+ }
1184
+ findLatestRegeneratableAssistant() {
1185
+ for (let i = this.messages.length - 1; i >= 0; i--) {
1186
+ const message = this.messages[i];
1187
+ if (message.id === "welcome") {
1188
+ continue;
1189
+ }
1190
+ if (message.role !== "assistant" || message.status === "error") {
1191
+ return void 0;
1192
+ }
1193
+ return message;
1194
+ }
1195
+ return void 0;
1196
+ }
1197
+ showLatestRegenerateButton() {
1198
+ const assistantMessage = this.findLatestRegeneratableAssistant();
1199
+ if (assistantMessage) {
1200
+ this.panel.messages.showRegenerateButton(assistantMessage.id);
1201
+ } else {
1202
+ this.panel.messages.clearRegenerateButtons();
1203
+ }
1204
+ }
1205
+ resolveErrorType(error) {
1206
+ switch (error) {
1207
+ case "rate_limit":
1208
+ case "network":
1209
+ case "provider_error":
1210
+ case "timeout":
1211
+ return error;
1212
+ default:
1213
+ return "provider_error";
1214
+ }
1215
+ }
1216
+ getErrorMessage(errorType) {
1217
+ switch (errorType) {
1218
+ case "rate_limit":
1219
+ return this.locale.errorRateLimit;
1220
+ case "network":
1221
+ case "timeout":
1222
+ return this.locale.errorNetwork;
1223
+ default:
1224
+ return this.locale.errorGeneric;
1225
+ }
1226
+ }
723
1227
  saveHistory() {
724
1228
  if (this.config.persistHistory === false) return;
725
1229
  const maxMessages = this.config.maxMessages ?? 50;
@@ -730,6 +1234,8 @@ class Widget {
730
1234
  id: m.id,
731
1235
  role: m.role,
732
1236
  content: m.content,
1237
+ status: m.status,
1238
+ errorType: m.errorType,
733
1239
  timestamp: Date.now()
734
1240
  })),
735
1241
  updatedAt: Date.now()