@chatcops/widget 0.4.4 → 0.6.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/README.md +4 -4
- package/dist/api/client.d.ts +6 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/protocol.d.ts +33 -0
- package/dist/api/protocol.d.ts.map +1 -0
- package/dist/api/turnstile.d.ts +8 -0
- package/dist/api/turnstile.d.ts.map +1 -0
- package/dist/api/types.d.ts +10 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/chatcops.esm.js +404 -151
- package/dist/chatcops.min.js +1 -1
- package/dist/dom/bubble.d.ts +1 -0
- package/dist/dom/bubble.d.ts.map +1 -1
- package/dist/dom/fab.d.ts +1 -1
- package/dist/dom/fab.d.ts.map +1 -1
- package/dist/dom/messages.d.ts +4 -1
- package/dist/dom/messages.d.ts.map +1 -1
- package/dist/dom/panel.d.ts +3 -0
- package/dist/dom/panel.d.ts.map +1 -1
- package/dist/dom/prechat-form.d.ts.map +1 -1
- package/dist/dom/shadow.d.ts +8 -1
- package/dist/dom/shadow.d.ts.map +1 -1
- package/dist/i18n.d.ts +6 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/markdown.d.ts.map +1 -1
- package/dist/namespace.d.ts +7 -0
- package/dist/namespace.d.ts.map +1 -0
- package/dist/storage.d.ts +3 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/theme.d.ts +14 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/widget.d.ts +23 -4
- package/dist/widget.d.ts.map +1 -1
- package/package.json +21 -7
package/dist/chatcops.esm.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
|
|
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;transform:none;transition:none}.cc-panel.cc-inline:not(.cc-visible){visibility:hidden}.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 h1,.cc-message h2,.cc-message h3,.cc-message h4,.cc-message h5,.cc-message h6{font-size:15px;font-weight:600;line-height:1.4;margin:4px 0 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:focus-within .cc-message-regenerate,.cc-message-regenerate:focus-visible{opacity:1}@media(hover:none){.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-sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}.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-fab.cc-open{display:none}.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}}.cc-fab:focus-visible,.cc-header-close:focus-visible,.cc-send-btn:focus-visible,.cc-message-retry:focus-visible,.cc-message-regenerate:focus-visible,.cc-bubble-close:focus-visible,.cc-prechat-submit:focus-visible{outline:2px solid var(--cc-accent);outline-offset:3px}@media(prefers-reduced-motion:reduce){.cc-fab.cc-pulse,.cc-typing-dot{animation:none}.cc-panel,.cc-bubble{transition:none}}";
|
|
1
|
+
const widgetStyles = ":host{--cc-accent: #6366f1;--cc-accent-hover: #4f46e5;--cc-on-accent: #ffffff;--cc-accent-contrast: #6164ec;--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,box-shadow .2s;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-on-accent);transition:transform .3s}.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,transform .2s}.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;transform:none;transition:none}.cc-panel.cc-inline:not(.cc-visible){visibility:hidden}.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-on-accent)}.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}.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-contrast);color:var(--cc-on-accent);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-on-accent)}.cc-message-error-note{margin-top:6px;font-size:12px;color:var(--cc-text-secondary)}.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 h1,.cc-message h2,.cc-message h3,.cc-message h4,.cc-message h5,.cc-message h6{font-size:15px;font-weight:600;line-height:1.4;margin:4px 0 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,color .15s,opacity .15s}.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:focus-within .cc-message-regenerate,.cc-message-regenerate:focus-visible{opacity:1}@media(hover:none){.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}.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,opacity .15s}.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-on-accent)}.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,transform .3s;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-sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}.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}.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-contrast);color:var(--cc-on-accent);border:none;border-radius:8px;padding:10px 16px;font-family:var(--cc-font);font-size:14px;font-weight:500;cursor:pointer;transition:background .15s;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-fab.cc-open{display:none}.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}}.cc-fab:focus-visible,.cc-header-close:focus-visible,.cc-send-btn:focus-visible,.cc-message-retry:focus-visible,.cc-message-regenerate:focus-visible,.cc-bubble-close:focus-visible,.cc-prechat-submit:focus-visible{outline:2px solid var(--cc-accent);outline-offset:3px}@media(prefers-reduced-motion:reduce){.cc-fab.cc-pulse,.cc-typing-dot{animation:none}.cc-panel,.cc-bubble{transition:none}}";
|
|
2
|
+
const HOST_ID = "chatcops-root";
|
|
5
3
|
function createShadowRoot(container) {
|
|
6
4
|
const host = document.createElement("div");
|
|
7
|
-
host.id =
|
|
5
|
+
host.id = nextHostId();
|
|
6
|
+
host.className = HOST_ID;
|
|
8
7
|
if (container) {
|
|
9
8
|
container.appendChild(host);
|
|
10
9
|
} else {
|
|
@@ -16,54 +15,14 @@ function createShadowRoot(container) {
|
|
|
16
15
|
shadow.appendChild(style);
|
|
17
16
|
return shadow;
|
|
18
17
|
}
|
|
19
|
-
function destroyShadowRoot(
|
|
20
|
-
|
|
21
|
-
const host = searchRoot.querySelector("#chatcops-root") ?? document.getElementById("chatcops-root");
|
|
22
|
-
if (host) host.remove();
|
|
18
|
+
function destroyShadowRoot(shadow) {
|
|
19
|
+
shadow == null ? void 0 : shadow.host.remove();
|
|
23
20
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
__publicField(this, "badge");
|
|
30
|
-
__publicField(this, "isOpen", false);
|
|
31
|
-
__publicField(this, "openLabel");
|
|
32
|
-
__publicField(this, "closeLabel");
|
|
33
|
-
this.openLabel = options.openLabel;
|
|
34
|
-
this.closeLabel = options.closeLabel;
|
|
35
|
-
this.el = document.createElement("button");
|
|
36
|
-
this.el.className = "cc-fab cc-pulse";
|
|
37
|
-
this.el.setAttribute("aria-label", this.openLabel);
|
|
38
|
-
if (options.position === "bottom-left") {
|
|
39
|
-
this.el.classList.add("cc-left");
|
|
40
|
-
}
|
|
41
|
-
this.el.innerHTML = CHAT_ICON;
|
|
42
|
-
this.badge = document.createElement("div");
|
|
43
|
-
this.badge.className = "cc-fab-badge";
|
|
44
|
-
this.el.appendChild(this.badge);
|
|
45
|
-
this.el.addEventListener("click", () => {
|
|
46
|
-
this.el.classList.remove("cc-pulse");
|
|
47
|
-
options.onClick();
|
|
48
|
-
});
|
|
49
|
-
root.appendChild(this.el);
|
|
50
|
-
}
|
|
51
|
-
setOpen(open) {
|
|
52
|
-
this.isOpen = open;
|
|
53
|
-
this.el.innerHTML = open ? CLOSE_ICON$1 : CHAT_ICON;
|
|
54
|
-
if (!open) this.el.appendChild(this.badge);
|
|
55
|
-
this.el.classList.toggle("cc-open", open);
|
|
56
|
-
this.el.setAttribute("aria-label", open ? this.closeLabel : this.openLabel);
|
|
57
|
-
}
|
|
58
|
-
showBadge() {
|
|
59
|
-
this.badge.classList.add("cc-visible");
|
|
60
|
-
}
|
|
61
|
-
hideBadge() {
|
|
62
|
-
this.badge.classList.remove("cc-visible");
|
|
63
|
-
}
|
|
64
|
-
destroy() {
|
|
65
|
-
this.el.remove();
|
|
66
|
-
}
|
|
21
|
+
function nextHostId() {
|
|
22
|
+
if (!document.getElementById(HOST_ID)) return HOST_ID;
|
|
23
|
+
let n = 2;
|
|
24
|
+
while (document.getElementById(`${HOST_ID}-${n}`)) n += 1;
|
|
25
|
+
return `${HOST_ID}-${n}`;
|
|
67
26
|
}
|
|
68
27
|
const ESCAPE_MAP = {
|
|
69
28
|
"&": "&",
|
|
@@ -131,9 +90,7 @@ const RETRY_ICON = '<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden=
|
|
|
131
90
|
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>';
|
|
132
91
|
class Messages {
|
|
133
92
|
constructor(parent, actions) {
|
|
134
|
-
|
|
135
|
-
__publicField(this, "typingEl", null);
|
|
136
|
-
__publicField(this, "actions");
|
|
93
|
+
this.typingEl = null;
|
|
137
94
|
this.actions = actions;
|
|
138
95
|
this.container = document.createElement("div");
|
|
139
96
|
this.container.className = "cc-messages";
|
|
@@ -149,7 +106,7 @@ class Messages {
|
|
|
149
106
|
return el;
|
|
150
107
|
}
|
|
151
108
|
updateMessage(msg) {
|
|
152
|
-
const el = this.
|
|
109
|
+
const el = this.findMessage(msg.id);
|
|
153
110
|
if (el) {
|
|
154
111
|
this.renderMessageElement(el, msg);
|
|
155
112
|
this.scrollToBottom();
|
|
@@ -175,18 +132,20 @@ class Messages {
|
|
|
175
132
|
this.typingEl = null;
|
|
176
133
|
}
|
|
177
134
|
removeMessage(id) {
|
|
178
|
-
const el = this.
|
|
135
|
+
const el = this.findMessage(id);
|
|
179
136
|
el == null ? void 0 : el.remove();
|
|
180
137
|
}
|
|
181
138
|
clearRegenerateButtons() {
|
|
182
|
-
this.container.querySelectorAll(".cc-message-regenerate").forEach((button) =>
|
|
139
|
+
this.container.querySelectorAll(".cc-message-regenerate").forEach((button) => {
|
|
140
|
+
button.remove();
|
|
141
|
+
});
|
|
183
142
|
this.cleanupEmptyActionContainers();
|
|
184
143
|
}
|
|
185
144
|
showRegenerateButton(messageId) {
|
|
186
145
|
var _a;
|
|
187
146
|
this.clearRegenerateButtons();
|
|
188
147
|
if (!((_a = this.actions) == null ? void 0 : _a.onRegenerate)) return;
|
|
189
|
-
const msgEl = this.
|
|
148
|
+
const msgEl = this.findMessage(messageId);
|
|
190
149
|
if (!msgEl || !msgEl.classList.contains("cc-message-assistant")) return;
|
|
191
150
|
const actions = this.ensureActionsContainer(msgEl);
|
|
192
151
|
actions.appendChild(this.createActionButton({
|
|
@@ -202,6 +161,10 @@ class Messages {
|
|
|
202
161
|
setVisible(visible) {
|
|
203
162
|
this.container.classList.toggle("cc-hidden", !visible);
|
|
204
163
|
}
|
|
164
|
+
findMessage(id) {
|
|
165
|
+
const found = Array.from(this.container.children).find((el) => el.dataset.id === id);
|
|
166
|
+
return found ?? null;
|
|
167
|
+
}
|
|
205
168
|
scrollToBottom() {
|
|
206
169
|
this.container.scrollTop = this.container.scrollHeight;
|
|
207
170
|
}
|
|
@@ -213,6 +176,9 @@ class Messages {
|
|
|
213
176
|
renderMessageElement(el, msg) {
|
|
214
177
|
el.className = `cc-message cc-message-${msg.role}`;
|
|
215
178
|
el.dataset.id = msg.id;
|
|
179
|
+
if (msg.role === "assistant" && msg.id !== "welcome") {
|
|
180
|
+
el.dataset.aiGenerated = "true";
|
|
181
|
+
}
|
|
216
182
|
if (msg.status) {
|
|
217
183
|
el.dataset.status = msg.status;
|
|
218
184
|
} else {
|
|
@@ -233,6 +199,12 @@ class Messages {
|
|
|
233
199
|
content.className = "cc-message-content";
|
|
234
200
|
content.innerHTML = msg.role === "assistant" ? renderMarkdown(msg.content) : this.escapeHtml(msg.content);
|
|
235
201
|
el.appendChild(content);
|
|
202
|
+
if (msg.status === "error" && msg.errorText) {
|
|
203
|
+
const note = document.createElement("div");
|
|
204
|
+
note.className = "cc-message-error-note";
|
|
205
|
+
note.textContent = msg.errorText;
|
|
206
|
+
el.appendChild(note);
|
|
207
|
+
}
|
|
236
208
|
const actions = this.createActionsContainer(msg);
|
|
237
209
|
if (actions) {
|
|
238
210
|
el.appendChild(actions);
|
|
@@ -290,10 +262,6 @@ class Messages {
|
|
|
290
262
|
const MAX_MESSAGE_LENGTH = 1e4;
|
|
291
263
|
class Input {
|
|
292
264
|
constructor(parent, options) {
|
|
293
|
-
__publicField(this, "area");
|
|
294
|
-
__publicField(this, "textarea");
|
|
295
|
-
__publicField(this, "sendBtn");
|
|
296
|
-
__publicField(this, "onSend");
|
|
297
265
|
this.onSend = options.onSend;
|
|
298
266
|
this.area = document.createElement("div");
|
|
299
267
|
this.area.className = "cc-input-area";
|
|
@@ -309,7 +277,7 @@ class Input {
|
|
|
309
277
|
this.sendBtn.className = "cc-send-btn";
|
|
310
278
|
this.sendBtn.disabled = true;
|
|
311
279
|
this.sendBtn.setAttribute("aria-label", options.sendLabel);
|
|
312
|
-
this.sendBtn.innerHTML = '<svg viewBox="0 0 24 24"
|
|
280
|
+
this.sendBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>';
|
|
313
281
|
this.sendBtn.addEventListener("click", () => this.send());
|
|
314
282
|
this.textarea.addEventListener("input", () => {
|
|
315
283
|
this.sendBtn.disabled = this.textarea.value.trim().length === 0;
|
|
@@ -352,15 +320,11 @@ class Input {
|
|
|
352
320
|
this.textarea.style.height = Math.min(this.textarea.scrollHeight, maxHeight) + "px";
|
|
353
321
|
}
|
|
354
322
|
}
|
|
355
|
-
const
|
|
356
|
-
const
|
|
323
|
+
const PANEL_ID = "cc-panel";
|
|
324
|
+
const BOT_ICON = `<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/></svg>`;
|
|
325
|
+
const CLOSE_ICON$1 = `<svg viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>`;
|
|
357
326
|
class Panel {
|
|
358
327
|
constructor(root, options) {
|
|
359
|
-
__publicField(this, "el");
|
|
360
|
-
__publicField(this, "inline");
|
|
361
|
-
__publicField(this, "messages");
|
|
362
|
-
__publicField(this, "input");
|
|
363
|
-
__publicField(this, "messagesContainer");
|
|
364
328
|
this.inline = options.inline ?? false;
|
|
365
329
|
this.el = document.createElement("div");
|
|
366
330
|
this.el.className = "cc-panel";
|
|
@@ -370,6 +334,15 @@ class Panel {
|
|
|
370
334
|
if (options.position === "bottom-left") {
|
|
371
335
|
this.el.classList.add("cc-left");
|
|
372
336
|
}
|
|
337
|
+
if (!options.inline) {
|
|
338
|
+
this.el.id = PANEL_ID;
|
|
339
|
+
this.el.setAttribute("role", "dialog");
|
|
340
|
+
this.el.setAttribute("aria-label", options.branding.name);
|
|
341
|
+
this.el.addEventListener("keydown", (e) => {
|
|
342
|
+
if (e.key === "Escape") options.onClose();
|
|
343
|
+
});
|
|
344
|
+
this.setHidden(true);
|
|
345
|
+
}
|
|
373
346
|
const header = document.createElement("div");
|
|
374
347
|
header.className = "cc-header";
|
|
375
348
|
if (options.branding.avatar) {
|
|
@@ -392,7 +365,7 @@ class Panel {
|
|
|
392
365
|
const closeBtn = document.createElement("button");
|
|
393
366
|
closeBtn.className = "cc-header-close";
|
|
394
367
|
closeBtn.setAttribute("aria-label", options.closeLabel);
|
|
395
|
-
closeBtn.innerHTML = CLOSE_ICON;
|
|
368
|
+
closeBtn.innerHTML = CLOSE_ICON$1;
|
|
396
369
|
closeBtn.addEventListener("click", options.onClose);
|
|
397
370
|
header.appendChild(closeBtn);
|
|
398
371
|
}
|
|
@@ -419,11 +392,16 @@ class Panel {
|
|
|
419
392
|
show() {
|
|
420
393
|
this.el.classList.add("cc-visible");
|
|
421
394
|
if (!this.inline) {
|
|
395
|
+
this.setHidden(false);
|
|
422
396
|
this.input.focus();
|
|
423
397
|
}
|
|
424
398
|
}
|
|
425
399
|
hide() {
|
|
426
400
|
this.el.classList.remove("cc-visible");
|
|
401
|
+
if (!this.inline) this.setHidden(true);
|
|
402
|
+
}
|
|
403
|
+
containsFocus() {
|
|
404
|
+
return this.el.contains(this.el.getRootNode().activeElement);
|
|
427
405
|
}
|
|
428
406
|
get isVisible() {
|
|
429
407
|
return this.el.classList.contains("cc-visible");
|
|
@@ -440,23 +418,73 @@ class Panel {
|
|
|
440
418
|
destroy() {
|
|
441
419
|
this.el.remove();
|
|
442
420
|
}
|
|
421
|
+
// A closed popup can't be focused or read by screen readers.
|
|
422
|
+
setHidden(hidden) {
|
|
423
|
+
this.el.toggleAttribute("inert", hidden);
|
|
424
|
+
if (hidden) this.el.setAttribute("aria-hidden", "true");
|
|
425
|
+
else this.el.removeAttribute("aria-hidden");
|
|
426
|
+
}
|
|
443
427
|
escapeHtml(text) {
|
|
444
428
|
const div = document.createElement("div");
|
|
445
429
|
div.textContent = text;
|
|
446
430
|
return div.innerHTML;
|
|
447
431
|
}
|
|
448
432
|
}
|
|
433
|
+
const CHAT_ICON = `<svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H5.17L4 17.17V4h16v12z"/><path d="M7 9h10v2H7zm0-3h10v2H7z"/></svg>`;
|
|
434
|
+
const CLOSE_ICON = `<svg viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>`;
|
|
435
|
+
class FAB {
|
|
436
|
+
constructor(root, options) {
|
|
437
|
+
this.openLabel = options.openLabel;
|
|
438
|
+
this.closeLabel = options.closeLabel;
|
|
439
|
+
this.el = document.createElement("button");
|
|
440
|
+
this.el.className = "cc-fab cc-pulse";
|
|
441
|
+
this.el.setAttribute("aria-label", this.openLabel);
|
|
442
|
+
this.el.setAttribute("aria-expanded", "false");
|
|
443
|
+
this.el.setAttribute("aria-controls", PANEL_ID);
|
|
444
|
+
if (options.position === "bottom-left") {
|
|
445
|
+
this.el.classList.add("cc-left");
|
|
446
|
+
}
|
|
447
|
+
this.el.innerHTML = CHAT_ICON;
|
|
448
|
+
this.badge = document.createElement("div");
|
|
449
|
+
this.badge.className = "cc-fab-badge";
|
|
450
|
+
this.el.appendChild(this.badge);
|
|
451
|
+
this.el.addEventListener("click", () => {
|
|
452
|
+
this.el.classList.remove("cc-pulse");
|
|
453
|
+
options.onClick();
|
|
454
|
+
});
|
|
455
|
+
root.appendChild(this.el);
|
|
456
|
+
}
|
|
457
|
+
setOpen(open) {
|
|
458
|
+
this.el.innerHTML = open ? CLOSE_ICON : CHAT_ICON;
|
|
459
|
+
if (!open) this.el.appendChild(this.badge);
|
|
460
|
+
this.el.classList.toggle("cc-open", open);
|
|
461
|
+
this.el.setAttribute("aria-label", open ? this.closeLabel : this.openLabel);
|
|
462
|
+
this.el.setAttribute("aria-expanded", String(open));
|
|
463
|
+
}
|
|
464
|
+
focus() {
|
|
465
|
+
this.el.focus();
|
|
466
|
+
}
|
|
467
|
+
showBadge() {
|
|
468
|
+
this.badge.classList.add("cc-visible");
|
|
469
|
+
}
|
|
470
|
+
hideBadge() {
|
|
471
|
+
this.badge.classList.remove("cc-visible");
|
|
472
|
+
}
|
|
473
|
+
destroy() {
|
|
474
|
+
this.el.remove();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
449
477
|
const BUBBLE_SHOWN_KEY = "chatcops_bubble_shown";
|
|
450
478
|
class WelcomeBubble {
|
|
451
479
|
constructor(root, options) {
|
|
452
|
-
|
|
453
|
-
__publicField(this, "timerId", null);
|
|
480
|
+
this.timerId = null;
|
|
454
481
|
this.el = document.createElement("div");
|
|
455
482
|
this.el.className = "cc-bubble";
|
|
456
483
|
if (options.position === "bottom-left") {
|
|
457
484
|
this.el.classList.add("cc-left");
|
|
458
485
|
}
|
|
459
486
|
this.el.textContent = options.text;
|
|
487
|
+
this.setHidden(true);
|
|
460
488
|
const closeBtn = document.createElement("button");
|
|
461
489
|
closeBtn.type = "button";
|
|
462
490
|
closeBtn.className = "cc-bubble-close";
|
|
@@ -481,26 +509,64 @@ class WelcomeBubble {
|
|
|
481
509
|
}
|
|
482
510
|
this.timerId = setTimeout(() => {
|
|
483
511
|
this.el.classList.add("cc-visible");
|
|
512
|
+
this.setHidden(false);
|
|
484
513
|
}, options.delay ?? 3e3);
|
|
485
514
|
}
|
|
486
515
|
hide() {
|
|
487
516
|
this.el.classList.remove("cc-visible");
|
|
517
|
+
this.setHidden(true);
|
|
488
518
|
if (this.timerId) {
|
|
489
519
|
clearTimeout(this.timerId);
|
|
490
520
|
this.timerId = null;
|
|
491
521
|
}
|
|
492
522
|
}
|
|
523
|
+
setHidden(hidden) {
|
|
524
|
+
this.el.toggleAttribute("inert", hidden);
|
|
525
|
+
if (hidden) this.el.setAttribute("aria-hidden", "true");
|
|
526
|
+
else this.el.removeAttribute("aria-hidden");
|
|
527
|
+
}
|
|
493
528
|
destroy() {
|
|
494
529
|
this.hide();
|
|
495
530
|
this.el.remove();
|
|
496
531
|
}
|
|
497
532
|
}
|
|
533
|
+
let loading;
|
|
534
|
+
async function turnstileToken(siteKey) {
|
|
535
|
+
loading ?? (loading = new Promise((resolve, reject) => {
|
|
536
|
+
const script = document.createElement("script");
|
|
537
|
+
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
538
|
+
script.onload = resolve;
|
|
539
|
+
script.onerror = reject;
|
|
540
|
+
document.head.append(script);
|
|
541
|
+
}));
|
|
542
|
+
await loading;
|
|
543
|
+
const box = document.createElement("div");
|
|
544
|
+
box.style.cssText = "position:fixed;bottom:96px;right:24px;z-index:2147483647";
|
|
545
|
+
document.body.append(box);
|
|
546
|
+
try {
|
|
547
|
+
return await new Promise((resolve, reject) => {
|
|
548
|
+
window.turnstile.render(box, {
|
|
549
|
+
sitekey: siteKey,
|
|
550
|
+
appearance: "interaction-only",
|
|
551
|
+
callback: resolve,
|
|
552
|
+
"error-callback": reject
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
} finally {
|
|
556
|
+
box.remove();
|
|
557
|
+
}
|
|
558
|
+
}
|
|
498
559
|
class ChatClient {
|
|
499
560
|
constructor(options) {
|
|
500
|
-
|
|
501
|
-
__publicField(this, "timeout");
|
|
561
|
+
this.saved = {};
|
|
502
562
|
this.apiUrl = options.apiUrl;
|
|
563
|
+
this.turnstileSiteKey = options.turnstileSiteKey;
|
|
503
564
|
this.timeout = options.timeout ?? 3e4;
|
|
565
|
+
this.sessionKey = `chatcops-session:${options.apiUrl}`;
|
|
566
|
+
try {
|
|
567
|
+
this.saved = JSON.parse(localStorage.getItem(this.sessionKey) || "{}");
|
|
568
|
+
} catch {
|
|
569
|
+
}
|
|
504
570
|
}
|
|
505
571
|
async *sendMessage(request, options = {}) {
|
|
506
572
|
var _a;
|
|
@@ -545,19 +611,41 @@ class ChatClient {
|
|
|
545
611
|
}
|
|
546
612
|
resetTimeout();
|
|
547
613
|
try {
|
|
548
|
-
const
|
|
614
|
+
const post = (url, body, token) => fetch(url, {
|
|
549
615
|
method: "POST",
|
|
550
|
-
headers: { "Content-Type": "application/json" },
|
|
551
|
-
body: JSON.stringify(
|
|
616
|
+
headers: { "Content-Type": "application/json", ...token && { Authorization: `Bearer ${token}` } },
|
|
617
|
+
body: JSON.stringify(body),
|
|
552
618
|
signal: controller.signal
|
|
553
619
|
});
|
|
620
|
+
const saved = this.saved;
|
|
621
|
+
const resumable = saved.conversationId === request.conversationId;
|
|
622
|
+
let response = await post(this.apiUrl, { protocol: 1, ...request }, resumable ? saved.token : void 0);
|
|
623
|
+
if (response.status === 401) {
|
|
624
|
+
const minted = await post(this.apiUrl.replace(/\/?(\?.*)?$/, "/session$1"), {
|
|
625
|
+
conversationId: request.conversationId,
|
|
626
|
+
previousToken: saved.token,
|
|
627
|
+
turnstileToken: this.turnstileSiteKey && !resumable ? await turnstileToken(this.turnstileSiteKey) : void 0
|
|
628
|
+
});
|
|
629
|
+
if (minted.ok) {
|
|
630
|
+
const session = await minted.json();
|
|
631
|
+
this.saved = session;
|
|
632
|
+
try {
|
|
633
|
+
localStorage.setItem(this.sessionKey, JSON.stringify(session));
|
|
634
|
+
} catch {
|
|
635
|
+
}
|
|
636
|
+
if (session.conversationId !== request.conversationId) {
|
|
637
|
+
request = { ...request, conversationId: session.conversationId };
|
|
638
|
+
yield { conversationId: session.conversationId };
|
|
639
|
+
}
|
|
640
|
+
response = await post(this.apiUrl, { protocol: 1, ...request }, session.token);
|
|
641
|
+
} else {
|
|
642
|
+
this.saved = {};
|
|
643
|
+
}
|
|
644
|
+
}
|
|
554
645
|
if (!response.ok) {
|
|
646
|
+
const { error } = await response.json().catch(() => ({}));
|
|
555
647
|
await cleanup();
|
|
556
|
-
|
|
557
|
-
yield { error: "rate_limit" };
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
yield { error: `HTTP ${response.status}` };
|
|
648
|
+
yield (error == null ? void 0 : error.code) ? errorChunk(error.code, error.retryAfterMs) : { error: response.status === 429 ? "rate_limit" : `HTTP ${response.status}` };
|
|
561
649
|
return;
|
|
562
650
|
}
|
|
563
651
|
reader = (_a = response.body) == null ? void 0 : _a.getReader();
|
|
@@ -568,6 +656,8 @@ class ChatClient {
|
|
|
568
656
|
}
|
|
569
657
|
const decoder = new TextDecoder();
|
|
570
658
|
let buffer = "";
|
|
659
|
+
let v1 = false;
|
|
660
|
+
let finished = false;
|
|
571
661
|
while (true) {
|
|
572
662
|
const { done, value } = await reader.read();
|
|
573
663
|
if (done) break;
|
|
@@ -581,17 +671,27 @@ class ChatClient {
|
|
|
581
671
|
const data = trimmed.slice(6);
|
|
582
672
|
if (data === "[DONE]") {
|
|
583
673
|
await cleanup();
|
|
584
|
-
yield { done: true };
|
|
674
|
+
yield v1 && !finished ? { error: "cut_off" } : { done: true };
|
|
585
675
|
return;
|
|
586
676
|
}
|
|
677
|
+
let chunk;
|
|
587
678
|
try {
|
|
588
|
-
|
|
679
|
+
chunk = JSON.parse(data);
|
|
589
680
|
} catch {
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if ("type" in chunk) {
|
|
684
|
+
v1 = true;
|
|
685
|
+
if (chunk.type === "finish") finished = true;
|
|
686
|
+
const mapped = fromPart(chunk);
|
|
687
|
+
if (mapped) yield mapped;
|
|
688
|
+
} else {
|
|
689
|
+
yield chunk;
|
|
590
690
|
}
|
|
591
691
|
}
|
|
592
692
|
}
|
|
593
693
|
await cleanup();
|
|
594
|
-
yield { done: true };
|
|
694
|
+
yield buffer.trim() === "data: [DONE]" ? { done: true } : { error: v1 ? "cut_off" : "network" };
|
|
595
695
|
} catch (err) {
|
|
596
696
|
await cleanup();
|
|
597
697
|
if (err instanceof DOMException && err.name === "AbortError") {
|
|
@@ -604,17 +704,65 @@ class ChatClient {
|
|
|
604
704
|
}
|
|
605
705
|
}
|
|
606
706
|
}
|
|
707
|
+
const ERROR_TYPES = {
|
|
708
|
+
rate_limited: "rate_limit",
|
|
709
|
+
timeout: "timeout",
|
|
710
|
+
budget_exceeded: "budget",
|
|
711
|
+
budget_daily: "budget_daily",
|
|
712
|
+
provider_unavailable: "unavailable"
|
|
713
|
+
};
|
|
714
|
+
function errorChunk(code, retryAfterMs) {
|
|
715
|
+
return { error: ERROR_TYPES[code] ?? "provider_error", retryAfterMs };
|
|
716
|
+
}
|
|
717
|
+
function fromPart(part) {
|
|
718
|
+
switch (part.type) {
|
|
719
|
+
case "start":
|
|
720
|
+
return { messageId: part.messageId };
|
|
721
|
+
case "text-delta":
|
|
722
|
+
return { content: part.delta };
|
|
723
|
+
case "lead-captured":
|
|
724
|
+
return { leadCaptured: true, leadData: part.lead };
|
|
725
|
+
case "error":
|
|
726
|
+
return errorChunk(part.code === "budget_exceeded" && part.scope !== "conversation" ? "budget_daily" : part.code, part.retryAfterMs);
|
|
727
|
+
case "finish":
|
|
728
|
+
return part.reason === "error" || part.reason === "aborted" ? null : { done: true };
|
|
729
|
+
default:
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
607
733
|
const KEY_PREFIX = "chatcops_conversation";
|
|
734
|
+
const SESSION_KEY_PREFIX = "chatcops_session_id";
|
|
608
735
|
class ConversationStorage {
|
|
736
|
+
/** Without a siteId the keys are the ones the widget has always used. */
|
|
609
737
|
constructor(siteId) {
|
|
610
|
-
__publicField(this, "key");
|
|
611
738
|
this.key = siteId ? `${KEY_PREFIX}_${siteId}` : KEY_PREFIX;
|
|
739
|
+
this.sessionKey = siteId ? `${SESSION_KEY_PREFIX}_${siteId}` : SESSION_KEY_PREFIX;
|
|
612
740
|
}
|
|
613
741
|
load() {
|
|
614
742
|
try {
|
|
615
743
|
const raw = localStorage.getItem(this.key);
|
|
616
|
-
if (
|
|
617
|
-
|
|
744
|
+
if (raw === null) return null;
|
|
745
|
+
let parsed;
|
|
746
|
+
try {
|
|
747
|
+
parsed = JSON.parse(raw);
|
|
748
|
+
} catch {
|
|
749
|
+
this.clear();
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
752
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || typeof parsed.id !== "string" || !Array.isArray(parsed.messages)) {
|
|
753
|
+
this.clear();
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
const record = parsed;
|
|
757
|
+
const rawMessages = record.messages;
|
|
758
|
+
const messages = rawMessages.filter(
|
|
759
|
+
(msg) => typeof msg === "object" && msg !== null && !Array.isArray(msg) && (msg.role === "user" || msg.role === "assistant") && typeof msg.content === "string"
|
|
760
|
+
);
|
|
761
|
+
return {
|
|
762
|
+
...record,
|
|
763
|
+
id: record.id,
|
|
764
|
+
messages
|
|
765
|
+
};
|
|
618
766
|
} catch {
|
|
619
767
|
return null;
|
|
620
768
|
}
|
|
@@ -636,12 +784,11 @@ class ConversationStorage {
|
|
|
636
784
|
}
|
|
637
785
|
}
|
|
638
786
|
getSessionId() {
|
|
639
|
-
const SESSION_KEY = "chatcops_session_id";
|
|
640
787
|
try {
|
|
641
|
-
let id = sessionStorage.getItem(
|
|
788
|
+
let id = sessionStorage.getItem(this.sessionKey);
|
|
642
789
|
if (!id) {
|
|
643
790
|
id = crypto.randomUUID();
|
|
644
|
-
sessionStorage.setItem(
|
|
791
|
+
sessionStorage.setItem(this.sessionKey, id);
|
|
645
792
|
}
|
|
646
793
|
return id;
|
|
647
794
|
} catch {
|
|
@@ -684,11 +831,54 @@ class ConversationStorage {
|
|
|
684
831
|
}
|
|
685
832
|
}
|
|
686
833
|
}
|
|
834
|
+
const DEFAULT = "";
|
|
835
|
+
const CLAIMS_KEY = Symbol.for("chatcops.storageClaims");
|
|
836
|
+
const store = globalThis;
|
|
837
|
+
store[CLAIMS_KEY] ?? (store[CLAIMS_KEY] = /* @__PURE__ */ new Map());
|
|
838
|
+
const claims = store[CLAIMS_KEY];
|
|
839
|
+
function inUse(name) {
|
|
840
|
+
const live = claims.get(name);
|
|
841
|
+
if (!live) return false;
|
|
842
|
+
for (const isLive of live) {
|
|
843
|
+
if (!isLive()) live.delete(isLive);
|
|
844
|
+
}
|
|
845
|
+
if (live.size === 0) claims.delete(name);
|
|
846
|
+
return live.size > 0;
|
|
847
|
+
}
|
|
848
|
+
function claimNamespace(siteId, mode, isLive) {
|
|
849
|
+
let name = siteId ?? DEFAULT;
|
|
850
|
+
if (siteId === void 0 && inUse(DEFAULT)) {
|
|
851
|
+
name = mode;
|
|
852
|
+
for (let n = 2; inUse(name); n += 1) name = `${mode}-${n}`;
|
|
853
|
+
console.warn(
|
|
854
|
+
`[chatcops] Another widget on this page already uses the default storage, so this one keeps its conversation under "${name}". Set a \`siteId\` on each widget to name them yourself.`
|
|
855
|
+
);
|
|
856
|
+
} else if (siteId !== void 0 && inUse(siteId)) {
|
|
857
|
+
console.warn(`[chatcops] Two widgets on this page use the siteId "${siteId}", so they share one conversation.`);
|
|
858
|
+
}
|
|
859
|
+
if (!claims.has(name)) claims.set(name, /* @__PURE__ */ new Set());
|
|
860
|
+
claims.get(name).add(isLive);
|
|
861
|
+
return {
|
|
862
|
+
siteId: name === DEFAULT ? void 0 : name,
|
|
863
|
+
release() {
|
|
864
|
+
var _a, _b;
|
|
865
|
+
(_a = claims.get(name)) == null ? void 0 : _a.delete(isLive);
|
|
866
|
+
if (((_b = claims.get(name)) == null ? void 0 : _b.size) === 0) claims.delete(name);
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
}
|
|
687
870
|
function applyTheme(root, theme) {
|
|
688
871
|
const host = root.host;
|
|
689
872
|
if (theme.accent) {
|
|
690
873
|
host.style.setProperty("--cc-accent", theme.accent);
|
|
691
874
|
host.style.setProperty("--cc-accent-hover", darken(theme.accent, 10));
|
|
875
|
+
const pair = accentTextColors(theme.accent);
|
|
876
|
+
if (pair) {
|
|
877
|
+
host.style.setProperty("--cc-on-accent", pair.onAccent);
|
|
878
|
+
host.style.setProperty("--cc-accent-contrast", pair.background);
|
|
879
|
+
} else {
|
|
880
|
+
host.style.setProperty("--cc-accent-contrast", theme.accent);
|
|
881
|
+
}
|
|
692
882
|
}
|
|
693
883
|
if (theme.textColor) {
|
|
694
884
|
host.style.setProperty("--cc-text", theme.textColor);
|
|
@@ -711,6 +901,34 @@ function toCssLength(value) {
|
|
|
711
901
|
const text = String(value).trim();
|
|
712
902
|
return /^(?:\d+(?:\.\d+)?|\.\d+)$/.test(text) ? `${text}px` : text;
|
|
713
903
|
}
|
|
904
|
+
const WHITE = "#ffffff";
|
|
905
|
+
const NEAR_BLACK = "#0a0a0a";
|
|
906
|
+
const MIN_TEXT_CONTRAST = 4.5;
|
|
907
|
+
function isHex(color) {
|
|
908
|
+
return /^#?[\da-f]{3}(?:[\da-f]{3})?$/i.test(color.trim());
|
|
909
|
+
}
|
|
910
|
+
function relativeLuminance(hex) {
|
|
911
|
+
const channels = parseHex(hex).map((value) => {
|
|
912
|
+
const c = value / 255;
|
|
913
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
914
|
+
});
|
|
915
|
+
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
|
|
916
|
+
}
|
|
917
|
+
function contrastRatio(a, b) {
|
|
918
|
+
const [light, dark] = [relativeLuminance(a), relativeLuminance(b)].sort((x, y) => y - x);
|
|
919
|
+
return (light + 0.05) / (dark + 0.05);
|
|
920
|
+
}
|
|
921
|
+
function accentTextColors(accent) {
|
|
922
|
+
if (!isHex(accent)) return null;
|
|
923
|
+
const color = accent.trim().startsWith("#") ? accent.trim() : `#${accent.trim()}`;
|
|
924
|
+
if (contrastRatio(WHITE, color) >= MIN_TEXT_CONTRAST) return { onAccent: WHITE, background: color };
|
|
925
|
+
if (contrastRatio(NEAR_BLACK, color) >= MIN_TEXT_CONTRAST) return { onAccent: NEAR_BLACK, background: color };
|
|
926
|
+
for (let percent = 2; percent <= 60; percent += 2) {
|
|
927
|
+
const darker = darken(color, percent);
|
|
928
|
+
if (contrastRatio(WHITE, darker) >= MIN_TEXT_CONTRAST) return { onAccent: WHITE, background: darker };
|
|
929
|
+
}
|
|
930
|
+
return { onAccent: WHITE, background: NEAR_BLACK };
|
|
931
|
+
}
|
|
714
932
|
function parseHex(hex) {
|
|
715
933
|
if (!/^#?[\da-f]{3}(?:[\da-f]{3})?$/i.test(hex)) return [0, 0, 0];
|
|
716
934
|
const h = hex.replace("#", "");
|
|
@@ -744,6 +962,11 @@ const en = {
|
|
|
744
962
|
errorGeneric: "Something went wrong. Please try again.",
|
|
745
963
|
errorNetwork: "Connection error. Please check your network.",
|
|
746
964
|
errorRateLimit: "Too many messages. Please wait a moment.",
|
|
965
|
+
errorRateLimitWait: "Too many messages. Please try again in {seconds} seconds.",
|
|
966
|
+
errorBudget: "This chat has reached its limit.",
|
|
967
|
+
errorBudgetDaily: "The assistant has reached its limit for today. Please try again later.",
|
|
968
|
+
errorUnavailable: "The assistant is unavailable right now. Please try again later.",
|
|
969
|
+
errorCutOff: "The reply was cut off.",
|
|
747
970
|
typingIndicator: "Typing...",
|
|
748
971
|
poweredBy: "Powered by ChatCops",
|
|
749
972
|
newConversation: "New conversation",
|
|
@@ -764,11 +987,8 @@ function getWidgetLocale(code, overrides) {
|
|
|
764
987
|
const MAX_PRECHAT_FIELD_LENGTH = 500;
|
|
765
988
|
class PreChatForm {
|
|
766
989
|
constructor(parent, options) {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
__publicField(this, "errorElements", /* @__PURE__ */ new Map());
|
|
770
|
-
__publicField(this, "submitBtn");
|
|
771
|
-
__publicField(this, "options");
|
|
990
|
+
this.fieldElements = /* @__PURE__ */ new Map();
|
|
991
|
+
this.errorElements = /* @__PURE__ */ new Map();
|
|
772
992
|
this.options = options;
|
|
773
993
|
this.container = document.createElement("div");
|
|
774
994
|
this.container.className = "cc-prechat";
|
|
@@ -903,7 +1123,6 @@ class PreChatForm {
|
|
|
903
1123
|
if (field.type === "email" && value && !this.isValidEmail(value)) {
|
|
904
1124
|
this.showError(field.name, this.options.locale.preChatInvalidEmail);
|
|
905
1125
|
valid = false;
|
|
906
|
-
continue;
|
|
907
1126
|
}
|
|
908
1127
|
}
|
|
909
1128
|
if (valid) {
|
|
@@ -941,30 +1160,32 @@ class PreChatForm {
|
|
|
941
1160
|
this.errorElements.clear();
|
|
942
1161
|
}
|
|
943
1162
|
}
|
|
1163
|
+
const ERROR_MESSAGES = {
|
|
1164
|
+
network: "errorNetwork",
|
|
1165
|
+
timeout: "errorNetwork",
|
|
1166
|
+
provider_error: "errorGeneric",
|
|
1167
|
+
budget: "errorBudget",
|
|
1168
|
+
budget_daily: "errorBudgetDaily",
|
|
1169
|
+
unavailable: "errorUnavailable",
|
|
1170
|
+
cut_off: "errorCutOff"
|
|
1171
|
+
};
|
|
944
1172
|
class Widget {
|
|
945
1173
|
constructor(config) {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
__publicField(this, "storage");
|
|
954
|
-
__publicField(this, "locale");
|
|
955
|
-
__publicField(this, "conversationId");
|
|
956
|
-
__publicField(this, "messages", []);
|
|
957
|
-
__publicField(this, "userData");
|
|
958
|
-
__publicField(this, "isStreaming", false);
|
|
959
|
-
__publicField(this, "destroyed", false);
|
|
960
|
-
__publicField(this, "abortController");
|
|
961
|
-
__publicField(this, "retryRequests", /* @__PURE__ */ new Map());
|
|
962
|
-
__publicField(this, "eventHandlers", /* @__PURE__ */ new Map());
|
|
963
|
-
__publicField(this, "containerEl");
|
|
964
|
-
__publicField(this, "autoOpenTimer");
|
|
1174
|
+
this.mounted = false;
|
|
1175
|
+
this.messages = [];
|
|
1176
|
+
this.isStreaming = false;
|
|
1177
|
+
this.v1 = false;
|
|
1178
|
+
this.destroyed = false;
|
|
1179
|
+
this.retryRequests = /* @__PURE__ */ new Map();
|
|
1180
|
+
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
965
1181
|
this.config = config;
|
|
966
|
-
this.client = new ChatClient({ apiUrl: config.apiUrl, timeout: config.timeout });
|
|
967
|
-
this.
|
|
1182
|
+
this.client = new ChatClient({ apiUrl: config.apiUrl, timeout: config.timeout, turnstileSiteKey: config.turnstileSiteKey });
|
|
1183
|
+
this.namespace = claimNamespace(
|
|
1184
|
+
config.siteId,
|
|
1185
|
+
config.mode ?? "popup",
|
|
1186
|
+
() => !this.destroyed && (!this.mounted || this.shadow.host.isConnected)
|
|
1187
|
+
);
|
|
1188
|
+
this.storage = new ConversationStorage(this.namespace.siteId);
|
|
968
1189
|
this.locale = getWidgetLocale(config.locale ?? "en", config.strings);
|
|
969
1190
|
this.conversationId = this.storage.getSessionId();
|
|
970
1191
|
}
|
|
@@ -992,6 +1213,7 @@ class Widget {
|
|
|
992
1213
|
} else {
|
|
993
1214
|
this.shadow = createShadowRoot();
|
|
994
1215
|
}
|
|
1216
|
+
this.mounted = true;
|
|
995
1217
|
if (this.config.theme) {
|
|
996
1218
|
applyTheme(this.shadow, this.config.theme);
|
|
997
1219
|
}
|
|
@@ -1084,12 +1306,14 @@ class Widget {
|
|
|
1084
1306
|
this.emit("open");
|
|
1085
1307
|
}
|
|
1086
1308
|
close() {
|
|
1087
|
-
var _a, _b, _c;
|
|
1309
|
+
var _a, _b, _c, _d;
|
|
1310
|
+
const hadFocus = this.panel.containsFocus();
|
|
1088
1311
|
this.panel.hide();
|
|
1089
1312
|
if (!this.isInline) {
|
|
1090
1313
|
(_a = this.fab) == null ? void 0 : _a.setOpen(false);
|
|
1314
|
+
if (hadFocus) (_b = this.fab) == null ? void 0 : _b.focus();
|
|
1091
1315
|
}
|
|
1092
|
-
(
|
|
1316
|
+
(_d = (_c = this.config).onClose) == null ? void 0 : _d.call(_c);
|
|
1093
1317
|
this.emit("close");
|
|
1094
1318
|
}
|
|
1095
1319
|
toggle() {
|
|
@@ -1109,7 +1333,8 @@ class Widget {
|
|
|
1109
1333
|
(_c = this.bubble) == null ? void 0 : _c.destroy();
|
|
1110
1334
|
this.panel.destroy();
|
|
1111
1335
|
(_d = this.fab) == null ? void 0 : _d.destroy();
|
|
1112
|
-
destroyShadowRoot(this.
|
|
1336
|
+
destroyShadowRoot(this.shadow);
|
|
1337
|
+
this.namespace.release();
|
|
1113
1338
|
this.eventHandlers.clear();
|
|
1114
1339
|
this.messages = [];
|
|
1115
1340
|
this.userData = void 0;
|
|
@@ -1119,6 +1344,7 @@ class Widget {
|
|
|
1119
1344
|
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
1120
1345
|
}
|
|
1121
1346
|
this.eventHandlers.get(event).add(handler);
|
|
1347
|
+
return () => this.off(event, handler);
|
|
1122
1348
|
}
|
|
1123
1349
|
off(event, handler) {
|
|
1124
1350
|
var _a;
|
|
@@ -1127,7 +1353,9 @@ class Widget {
|
|
|
1127
1353
|
emit(event, ...args) {
|
|
1128
1354
|
var _a;
|
|
1129
1355
|
if (this.destroyed) return;
|
|
1130
|
-
(_a = this.eventHandlers.get(event)) == null ? void 0 : _a.forEach((h) =>
|
|
1356
|
+
(_a = this.eventHandlers.get(event)) == null ? void 0 : _a.forEach((h) => {
|
|
1357
|
+
h(...args);
|
|
1358
|
+
});
|
|
1131
1359
|
}
|
|
1132
1360
|
isPreChatCompleted() {
|
|
1133
1361
|
return this.storage.isPreChatCompleted(this.conversationId);
|
|
@@ -1229,7 +1457,8 @@ class Widget {
|
|
|
1229
1457
|
this.retryRequests.delete(assistantMessageId);
|
|
1230
1458
|
this.messages = this.messages.filter((message) => message.id !== assistantMessageId);
|
|
1231
1459
|
await this.sendToServer(userMessage.content, {
|
|
1232
|
-
|
|
1460
|
+
// A v1 server saved the reply under this id; a 0.5 server only knows `true`.
|
|
1461
|
+
regenerate: this.v1 ? { assistantMessageId } : true,
|
|
1233
1462
|
userMessageId: userMessage.id
|
|
1234
1463
|
});
|
|
1235
1464
|
}
|
|
@@ -1259,8 +1488,7 @@ class Widget {
|
|
|
1259
1488
|
if (this.destroyed) break;
|
|
1260
1489
|
if (chunk.error) {
|
|
1261
1490
|
const errorType = this.resolveErrorType(chunk.error);
|
|
1262
|
-
assistantMsg
|
|
1263
|
-
assistantMsg.status = "error";
|
|
1491
|
+
this.markFailed(assistantMsg, this.getErrorMessage(errorType, chunk.retryAfterMs));
|
|
1264
1492
|
assistantMsg.errorType = errorType;
|
|
1265
1493
|
hasError = true;
|
|
1266
1494
|
this.retryRequests.set(assistantMsg.id, {
|
|
@@ -1283,6 +1511,11 @@ class Widget {
|
|
|
1283
1511
|
requestCompleted = true;
|
|
1284
1512
|
break;
|
|
1285
1513
|
}
|
|
1514
|
+
if (chunk.conversationId) this.conversationId = chunk.conversationId;
|
|
1515
|
+
if (chunk.messageId) {
|
|
1516
|
+
assistantMsg.id = chunk.messageId;
|
|
1517
|
+
this.v1 = true;
|
|
1518
|
+
}
|
|
1286
1519
|
if (chunk.leadCaptured) {
|
|
1287
1520
|
(_d = (_c = this.config).onLeadCaptured) == null ? void 0 : _d.call(_c, chunk.leadData);
|
|
1288
1521
|
this.emit("leadCaptured", chunk.leadData);
|
|
@@ -1317,15 +1550,18 @@ class Widget {
|
|
|
1317
1550
|
}
|
|
1318
1551
|
} catch (err) {
|
|
1319
1552
|
if (this.destroyed) return;
|
|
1320
|
-
assistantMsg
|
|
1321
|
-
assistantMsg.status = "error";
|
|
1553
|
+
this.markFailed(assistantMsg, this.locale.errorGeneric);
|
|
1322
1554
|
assistantMsg.errorType = "network";
|
|
1323
1555
|
this.retryRequests.set(assistantMsg.id, {
|
|
1324
1556
|
regenerate: options.regenerate,
|
|
1325
1557
|
userMessageId: options.userMessageId
|
|
1326
1558
|
});
|
|
1327
1559
|
this.panel.messages.removeTyping();
|
|
1328
|
-
|
|
1560
|
+
if (firstChunk) {
|
|
1561
|
+
this.panel.addMessage(assistantMsg);
|
|
1562
|
+
} else {
|
|
1563
|
+
this.panel.messages.updateMessage(assistantMsg);
|
|
1564
|
+
}
|
|
1329
1565
|
this.messages.push(assistantMsg);
|
|
1330
1566
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1331
1567
|
(_i = (_h = this.config).onError) == null ? void 0 : _i.call(_h, error);
|
|
@@ -1345,7 +1581,7 @@ class Widget {
|
|
|
1345
1581
|
createRequest(text, options) {
|
|
1346
1582
|
var _a;
|
|
1347
1583
|
const pageContext = this.config.pageContext !== false ? { url: window.location.href, title: document.title } : void 0;
|
|
1348
|
-
const includeUserData = Boolean(((_a = this.config.preChatForm) == null ? void 0 : _a.enabled) && this.userData);
|
|
1584
|
+
const includeUserData = Boolean(((_a = this.config.preChatForm) == null ? void 0 : _a.enabled) && this.userData && !this.v1);
|
|
1349
1585
|
return {
|
|
1350
1586
|
conversationId: this.conversationId,
|
|
1351
1587
|
message: text,
|
|
@@ -1386,26 +1622,25 @@ class Widget {
|
|
|
1386
1622
|
}
|
|
1387
1623
|
}
|
|
1388
1624
|
resolveErrorType(error) {
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
default:
|
|
1407
|
-
return this.locale.errorGeneric;
|
|
1625
|
+
return error in ERROR_MESSAGES || error === "rate_limit" ? error : "provider_error";
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Marks a reply as failed. Text that already streamed stays, and the error
|
|
1629
|
+
* shows as a note under it; an empty reply shows the error as its content.
|
|
1630
|
+
*/
|
|
1631
|
+
markFailed(message, errorMessage) {
|
|
1632
|
+
message.status = "error";
|
|
1633
|
+
if (message.content.trim()) {
|
|
1634
|
+
message.errorText = errorMessage;
|
|
1635
|
+
} else {
|
|
1636
|
+
message.content = errorMessage;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
getErrorMessage(errorType, retryAfterMs) {
|
|
1640
|
+
if (errorType === "rate_limit") {
|
|
1641
|
+
return retryAfterMs ? this.locale.errorRateLimitWait.replace("{seconds}", String(Math.ceil(retryAfterMs / 1e3))) : this.locale.errorRateLimit;
|
|
1408
1642
|
}
|
|
1643
|
+
return this.locale[ERROR_MESSAGES[errorType] ?? "errorGeneric"];
|
|
1409
1644
|
}
|
|
1410
1645
|
saveHistory() {
|
|
1411
1646
|
if (this.config.persistHistory === false) return;
|
|
@@ -1426,6 +1661,7 @@ class Widget {
|
|
|
1426
1661
|
}
|
|
1427
1662
|
}
|
|
1428
1663
|
let instance = null;
|
|
1664
|
+
const persistentHandlers = /* @__PURE__ */ new Map();
|
|
1429
1665
|
function parseAutoOpen(value) {
|
|
1430
1666
|
if (value === void 0) return void 0;
|
|
1431
1667
|
if (value === "" || value === "true") return true;
|
|
@@ -1439,6 +1675,11 @@ const ChatCops = {
|
|
|
1439
1675
|
instance.destroy();
|
|
1440
1676
|
}
|
|
1441
1677
|
instance = new Widget(config);
|
|
1678
|
+
for (const [event, handlers] of persistentHandlers) {
|
|
1679
|
+
for (const handler of handlers) {
|
|
1680
|
+
instance.on(event, handler);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1442
1683
|
instance.init();
|
|
1443
1684
|
return instance;
|
|
1444
1685
|
},
|
|
@@ -1453,9 +1694,20 @@ const ChatCops = {
|
|
|
1453
1694
|
instance = null;
|
|
1454
1695
|
},
|
|
1455
1696
|
on(event, handler) {
|
|
1697
|
+
let handlers = persistentHandlers.get(event);
|
|
1698
|
+
if (!handlers) {
|
|
1699
|
+
handlers = /* @__PURE__ */ new Set();
|
|
1700
|
+
persistentHandlers.set(event, handlers);
|
|
1701
|
+
}
|
|
1702
|
+
handlers.add(handler);
|
|
1456
1703
|
instance == null ? void 0 : instance.on(event, handler);
|
|
1704
|
+
return () => {
|
|
1705
|
+
ChatCops.off(event, handler);
|
|
1706
|
+
};
|
|
1457
1707
|
},
|
|
1458
1708
|
off(event, handler) {
|
|
1709
|
+
var _a;
|
|
1710
|
+
(_a = persistentHandlers.get(event)) == null ? void 0 : _a.delete(handler);
|
|
1459
1711
|
instance == null ? void 0 : instance.off(event, handler);
|
|
1460
1712
|
}
|
|
1461
1713
|
};
|
|
@@ -1467,6 +1719,7 @@ function autoInit(script) {
|
|
|
1467
1719
|
const container = script.dataset.container;
|
|
1468
1720
|
ChatCops.init({
|
|
1469
1721
|
apiUrl,
|
|
1722
|
+
turnstileSiteKey: script.dataset.turnstileSiteKey,
|
|
1470
1723
|
mode: mode || void 0,
|
|
1471
1724
|
container: container || void 0,
|
|
1472
1725
|
theme: {
|
|
@@ -1495,7 +1748,7 @@ function autoInit(script) {
|
|
|
1495
1748
|
const currentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
1496
1749
|
if (currentScript && currentScript.dataset.apiUrl) {
|
|
1497
1750
|
if (document.readyState === "loading") {
|
|
1498
|
-
document.addEventListener("DOMContentLoaded", () => autoInit(currentScript));
|
|
1751
|
+
document.addEventListener("DOMContentLoaded", () => autoInit(currentScript), { once: true });
|
|
1499
1752
|
} else {
|
|
1500
1753
|
autoInit(currentScript);
|
|
1501
1754
|
}
|