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