@chatcops/widget 0.3.1 → 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.
- package/dist/api/types.d.ts +4 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/chatcops.esm.js +561 -49
- package/dist/chatcops.min.js +1 -1
- package/dist/dom/input.d.ts +1 -0
- package/dist/dom/input.d.ts.map +1 -1
- package/dist/dom/messages.d.ts +23 -2
- package/dist/dom/messages.d.ts.map +1 -1
- package/dist/dom/panel.d.ts +6 -1
- package/dist/dom/panel.d.ts.map +1 -1
- package/dist/dom/prechat-form.d.ts +26 -0
- package/dist/dom/prechat-form.d.ts.map +1 -0
- package/dist/i18n.d.ts +7 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/markdown.d.ts.map +1 -1
- package/dist/storage.d.ts +4 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/widget.d.ts +37 -1
- package/dist/widget.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/chatcops.esm.js
CHANGED
|
@@ -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";
|
|
@@ -71,6 +71,12 @@ const ESCAPE_MAP = {
|
|
|
71
71
|
function escapeHtml(text) {
|
|
72
72
|
return text.replace(/[&<>"']/g, (ch) => ESCAPE_MAP[ch] ?? ch);
|
|
73
73
|
}
|
|
74
|
+
const SAFE_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
|
|
75
|
+
function isSafeHref(href) {
|
|
76
|
+
const normalized = href.replace(/[\t\n\r]/g, "").replace(/^[\u0000-\u0020]+/, "");
|
|
77
|
+
const scheme = /^([^:/?#]+):/.exec(normalized);
|
|
78
|
+
return !scheme || SAFE_SCHEMES.has(scheme[1].toLowerCase());
|
|
79
|
+
}
|
|
74
80
|
function renderMarkdown(input) {
|
|
75
81
|
let html = escapeHtml(input);
|
|
76
82
|
html = html.replace(/```(?:\w*)\n?([\s\S]*?)```/g, (_match, code) => {
|
|
@@ -81,7 +87,7 @@ function renderMarkdown(input) {
|
|
|
81
87
|
html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
|
|
82
88
|
html = html.replace(
|
|
83
89
|
/\[([^\]]+)\]\(((?:[^()]*|\([^()]*\))*)\)/g,
|
|
84
|
-
|
|
90
|
+
(_match, text, href) => isSafeHref(href) ? `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>` : text
|
|
85
91
|
);
|
|
86
92
|
html = html.replace(/^- (.+)$/gm, "<li>$1</li>");
|
|
87
93
|
html = html.replace(/(<li>.*<\/li>\n?)+/g, (match) => `<ul>${match}</ul>`);
|
|
@@ -90,28 +96,29 @@ function renderMarkdown(input) {
|
|
|
90
96
|
html = html.replace(/\n/g, "<br>");
|
|
91
97
|
return html;
|
|
92
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>';
|
|
93
101
|
class Messages {
|
|
94
|
-
constructor(parent) {
|
|
102
|
+
constructor(parent, actions) {
|
|
95
103
|
__publicField(this, "container");
|
|
96
104
|
__publicField(this, "typingEl", null);
|
|
105
|
+
__publicField(this, "actions");
|
|
106
|
+
this.actions = actions;
|
|
97
107
|
this.container = document.createElement("div");
|
|
98
108
|
this.container.className = "cc-messages";
|
|
99
109
|
parent.appendChild(this.container);
|
|
100
110
|
}
|
|
101
111
|
addMessage(msg) {
|
|
102
112
|
this.removeTyping();
|
|
103
|
-
const el =
|
|
104
|
-
el.className = `cc-message cc-message-${msg.role}`;
|
|
105
|
-
el.dataset.id = msg.id;
|
|
106
|
-
el.innerHTML = msg.role === "assistant" ? renderMarkdown(msg.content) : this.escapeHtml(msg.content);
|
|
113
|
+
const el = this.createMessageElement(msg);
|
|
107
114
|
this.container.appendChild(el);
|
|
108
115
|
this.scrollToBottom();
|
|
109
116
|
return el;
|
|
110
117
|
}
|
|
111
|
-
updateMessage(
|
|
112
|
-
const el = this.container.querySelector(`[data-id="${id}"]`);
|
|
118
|
+
updateMessage(msg) {
|
|
119
|
+
const el = this.container.querySelector(`[data-id="${msg.id}"]`);
|
|
113
120
|
if (el) {
|
|
114
|
-
|
|
121
|
+
this.renderMessageElement(el, msg);
|
|
115
122
|
this.scrollToBottom();
|
|
116
123
|
}
|
|
117
124
|
}
|
|
@@ -133,9 +140,108 @@ class Messages {
|
|
|
133
140
|
this.container.innerHTML = "";
|
|
134
141
|
this.typingEl = null;
|
|
135
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
|
+
}
|
|
136
171
|
scrollToBottom() {
|
|
137
172
|
this.container.scrollTop = this.container.scrollHeight;
|
|
138
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
|
+
}
|
|
139
245
|
escapeHtml(text) {
|
|
140
246
|
const div = document.createElement("div");
|
|
141
247
|
div.textContent = text;
|
|
@@ -182,6 +288,9 @@ class Input {
|
|
|
182
288
|
this.sendBtn.disabled = true;
|
|
183
289
|
this.autoResize();
|
|
184
290
|
}
|
|
291
|
+
setVisible(visible) {
|
|
292
|
+
this.area.classList.toggle("cc-hidden", !visible);
|
|
293
|
+
}
|
|
185
294
|
send() {
|
|
186
295
|
const text = this.textarea.value.trim();
|
|
187
296
|
if (!text) return;
|
|
@@ -208,6 +317,7 @@ class Panel {
|
|
|
208
317
|
__publicField(this, "inline");
|
|
209
318
|
__publicField(this, "messages");
|
|
210
319
|
__publicField(this, "input");
|
|
320
|
+
__publicField(this, "messagesContainer");
|
|
211
321
|
this.inline = options.inline ?? false;
|
|
212
322
|
this.el = document.createElement("div");
|
|
213
323
|
this.el.className = "cc-panel";
|
|
@@ -244,7 +354,14 @@ class Panel {
|
|
|
244
354
|
header.appendChild(closeBtn);
|
|
245
355
|
}
|
|
246
356
|
this.el.appendChild(header);
|
|
247
|
-
|
|
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);
|
|
248
365
|
this.input = new Input(this.el, {
|
|
249
366
|
placeholder: options.placeholder,
|
|
250
367
|
onSend: options.onSend
|
|
@@ -270,6 +387,12 @@ class Panel {
|
|
|
270
387
|
addMessage(msg) {
|
|
271
388
|
return this.messages.addMessage(msg);
|
|
272
389
|
}
|
|
390
|
+
hideMessages() {
|
|
391
|
+
this.messages.setVisible(false);
|
|
392
|
+
}
|
|
393
|
+
showMessages() {
|
|
394
|
+
this.messages.setVisible(true);
|
|
395
|
+
}
|
|
273
396
|
destroy() {
|
|
274
397
|
this.el.remove();
|
|
275
398
|
}
|
|
@@ -408,7 +531,11 @@ class ConversationStorage {
|
|
|
408
531
|
}
|
|
409
532
|
save(conversation) {
|
|
410
533
|
try {
|
|
411
|
-
|
|
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));
|
|
412
539
|
} catch {
|
|
413
540
|
}
|
|
414
541
|
}
|
|
@@ -431,6 +558,19 @@ class ConversationStorage {
|
|
|
431
558
|
return crypto.randomUUID();
|
|
432
559
|
}
|
|
433
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
|
+
}
|
|
434
574
|
}
|
|
435
575
|
function applyTheme(root, theme) {
|
|
436
576
|
const host = root.host;
|
|
@@ -483,13 +623,174 @@ const en = {
|
|
|
483
623
|
typingIndicator: "Typing...",
|
|
484
624
|
poweredBy: "Powered by ChatCops",
|
|
485
625
|
newConversation: "New conversation",
|
|
486
|
-
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"
|
|
487
634
|
};
|
|
488
635
|
const locales = { en };
|
|
489
636
|
function getWidgetLocale(code, overrides) {
|
|
490
637
|
const base = locales[code] ?? en;
|
|
491
638
|
return overrides ? { ...base, ...overrides } : base;
|
|
492
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
|
+
}
|
|
493
794
|
class Widget {
|
|
494
795
|
constructor(config) {
|
|
495
796
|
__publicField(this, "config");
|
|
@@ -497,12 +798,16 @@ class Widget {
|
|
|
497
798
|
__publicField(this, "fab");
|
|
498
799
|
__publicField(this, "panel");
|
|
499
800
|
__publicField(this, "bubble");
|
|
801
|
+
__publicField(this, "preChatFormEl");
|
|
500
802
|
__publicField(this, "client");
|
|
501
803
|
__publicField(this, "storage");
|
|
502
804
|
__publicField(this, "locale");
|
|
503
805
|
__publicField(this, "conversationId");
|
|
504
806
|
__publicField(this, "messages", []);
|
|
807
|
+
__publicField(this, "userData");
|
|
808
|
+
__publicField(this, "userDataSent", false);
|
|
505
809
|
__publicField(this, "isStreaming", false);
|
|
810
|
+
__publicField(this, "retryRequests", /* @__PURE__ */ new Map());
|
|
506
811
|
__publicField(this, "eventHandlers", /* @__PURE__ */ new Map());
|
|
507
812
|
__publicField(this, "containerEl");
|
|
508
813
|
this.config = config;
|
|
@@ -528,7 +833,7 @@ class Widget {
|
|
|
528
833
|
return this.config.container;
|
|
529
834
|
}
|
|
530
835
|
init() {
|
|
531
|
-
var _a, _b, _c, _d;
|
|
836
|
+
var _a, _b, _c, _d, _e, _f;
|
|
532
837
|
if (this.isInline) {
|
|
533
838
|
this.containerEl = this.resolveContainer();
|
|
534
839
|
this.shadow = createShadowRoot(this.containerEl);
|
|
@@ -548,13 +853,24 @@ class Widget {
|
|
|
548
853
|
this.panel = new Panel(this.shadow, {
|
|
549
854
|
position,
|
|
550
855
|
inline: this.isInline,
|
|
856
|
+
preChatEnabled: ((_b = this.config.preChatForm) == null ? void 0 : _b.enabled) ?? false,
|
|
551
857
|
branding: {
|
|
552
|
-
name: ((
|
|
553
|
-
avatar: (
|
|
554
|
-
subtitle: ((
|
|
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"
|
|
555
861
|
},
|
|
556
862
|
placeholder: this.config.placeholder ?? this.locale.inputPlaceholder,
|
|
557
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
|
+
},
|
|
558
874
|
onSend: (text) => this.handleSend(text),
|
|
559
875
|
onClose: () => this.close()
|
|
560
876
|
});
|
|
@@ -568,16 +884,13 @@ class Widget {
|
|
|
568
884
|
}
|
|
569
885
|
}
|
|
570
886
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
content: welcomeText
|
|
577
|
-
};
|
|
578
|
-
this.messages.push(welcomeMsg);
|
|
579
|
-
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();
|
|
580
892
|
}
|
|
893
|
+
this.showLatestRegenerateButton();
|
|
581
894
|
if (!this.isInline && this.config.welcomeBubble) {
|
|
582
895
|
this.bubble = new WelcomeBubble(this.shadow, {
|
|
583
896
|
text: this.config.welcomeBubble.text,
|
|
@@ -623,10 +936,11 @@ class Widget {
|
|
|
623
936
|
}
|
|
624
937
|
}
|
|
625
938
|
destroy() {
|
|
626
|
-
var _a, _b;
|
|
627
|
-
(_a = this.
|
|
939
|
+
var _a, _b, _c;
|
|
940
|
+
(_a = this.preChatFormEl) == null ? void 0 : _a.destroy();
|
|
941
|
+
(_b = this.bubble) == null ? void 0 : _b.destroy();
|
|
628
942
|
this.panel.destroy();
|
|
629
|
-
(
|
|
943
|
+
(_c = this.fab) == null ? void 0 : _c.destroy();
|
|
630
944
|
destroyShadowRoot(this.containerEl);
|
|
631
945
|
this.eventHandlers.clear();
|
|
632
946
|
this.messages = [];
|
|
@@ -645,8 +959,56 @@ class Widget {
|
|
|
645
959
|
var _a;
|
|
646
960
|
(_a = this.eventHandlers.get(event)) == null ? void 0 : _a.forEach((h) => h(...args));
|
|
647
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
|
+
}
|
|
648
1010
|
async handleSend(text) {
|
|
649
|
-
var _a, _b
|
|
1011
|
+
var _a, _b;
|
|
650
1012
|
if (this.isStreaming) return;
|
|
651
1013
|
const userMsg = {
|
|
652
1014
|
id: crypto.randomUUID(),
|
|
@@ -657,33 +1019,97 @@ class Widget {
|
|
|
657
1019
|
this.panel.addMessage(userMsg);
|
|
658
1020
|
(_b = (_a = this.config).onMessage) == null ? void 0 : _b.call(_a, userMsg);
|
|
659
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;
|
|
660
1067
|
this.isStreaming = true;
|
|
661
1068
|
this.panel.input.setDisabled(true);
|
|
1069
|
+
this.panel.messages.clearRegenerateButtons();
|
|
662
1070
|
this.panel.messages.showTyping();
|
|
663
1071
|
const assistantMsg = {
|
|
664
1072
|
id: crypto.randomUUID(),
|
|
665
1073
|
role: "assistant",
|
|
666
|
-
content: ""
|
|
1074
|
+
content: "",
|
|
1075
|
+
status: "streaming"
|
|
667
1076
|
};
|
|
668
|
-
const
|
|
1077
|
+
const includeUserData = !!(this.userData && !this.userDataSent);
|
|
1078
|
+
let firstChunk = true;
|
|
1079
|
+
let requestCompleted = false;
|
|
1080
|
+
let hasError = false;
|
|
669
1081
|
try {
|
|
670
|
-
|
|
671
|
-
for await (const chunk of this.client.sendMessage({
|
|
672
|
-
conversationId: this.conversationId,
|
|
673
|
-
message: text,
|
|
674
|
-
pageContext,
|
|
675
|
-
locale: this.config.locale
|
|
676
|
-
})) {
|
|
1082
|
+
for await (const chunk of this.client.sendMessage(this.createRequest(text, options, includeUserData))) {
|
|
677
1083
|
if (chunk.error) {
|
|
678
|
-
const
|
|
679
|
-
assistantMsg.content = this.
|
|
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
|
+
});
|
|
680
1093
|
this.panel.messages.removeTyping();
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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);
|
|
684
1103
|
break;
|
|
685
1104
|
}
|
|
686
|
-
if (chunk.done)
|
|
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
|
+
}
|
|
687
1113
|
if (chunk.content) {
|
|
688
1114
|
if (firstChunk) {
|
|
689
1115
|
this.panel.messages.removeTyping();
|
|
@@ -691,29 +1117,113 @@ class Widget {
|
|
|
691
1117
|
firstChunk = false;
|
|
692
1118
|
}
|
|
693
1119
|
assistantMsg.content += chunk.content;
|
|
694
|
-
this.panel.messages.updateMessage(assistantMsg
|
|
1120
|
+
this.panel.messages.updateMessage(assistantMsg);
|
|
695
1121
|
}
|
|
696
1122
|
}
|
|
697
|
-
if (
|
|
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);
|
|
698
1132
|
this.messages.push(assistantMsg);
|
|
699
1133
|
(_f = (_e = this.config).onMessage) == null ? void 0 : _f.call(_e, assistantMsg);
|
|
700
1134
|
this.emit("message", assistantMsg);
|
|
1135
|
+
this.showLatestRegenerateButton();
|
|
701
1136
|
if (!this.isInline && !this.panel.isVisible) {
|
|
702
1137
|
(_g = this.fab) == null ? void 0 : _g.showBadge();
|
|
703
1138
|
}
|
|
704
1139
|
}
|
|
705
1140
|
} catch (err) {
|
|
706
|
-
this.panel.messages.removeTyping();
|
|
707
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();
|
|
708
1149
|
this.panel.addMessage(assistantMsg);
|
|
709
|
-
|
|
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);
|
|
710
1154
|
} finally {
|
|
1155
|
+
if (includeUserData && requestCompleted) {
|
|
1156
|
+
this.userDataSent = true;
|
|
1157
|
+
}
|
|
711
1158
|
this.isStreaming = false;
|
|
712
1159
|
this.panel.input.setDisabled(false);
|
|
713
1160
|
this.panel.input.focus();
|
|
714
1161
|
this.saveHistory();
|
|
715
1162
|
}
|
|
716
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
|
+
}
|
|
717
1227
|
saveHistory() {
|
|
718
1228
|
if (this.config.persistHistory === false) return;
|
|
719
1229
|
const maxMessages = this.config.maxMessages ?? 50;
|
|
@@ -724,6 +1234,8 @@ class Widget {
|
|
|
724
1234
|
id: m.id,
|
|
725
1235
|
role: m.role,
|
|
726
1236
|
content: m.content,
|
|
1237
|
+
status: m.status,
|
|
1238
|
+
errorType: m.errorType,
|
|
727
1239
|
timestamp: Date.now()
|
|
728
1240
|
})),
|
|
729
1241
|
updatedAt: Date.now()
|