@chatcops/widget 0.6.0 → 1.0.0-rc.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 +2 -1
- package/dist/api/client.d.ts +17 -2
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/protocol.d.ts +7 -1
- package/dist/api/protocol.d.ts.map +1 -1
- package/dist/api/turnstile.d.ts.map +1 -1
- package/dist/api/types.d.ts +7 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/chatcops.esm.js +1094 -499
- package/dist/chatcops.full.min.js +1 -0
- package/dist/chatcops.min.js +1 -1
- package/dist/chatcops.panel.js +1 -0
- package/dist/chunks/prechat-form-DiHgHUnU.js +1 -0
- package/dist/chunks/turnstile-CAnVL3fK.js +1 -0
- package/dist/dom/bubble.d.ts +3 -0
- package/dist/dom/bubble.d.ts.map +1 -1
- package/dist/dom/fab.d.ts +4 -1
- package/dist/dom/fab.d.ts.map +1 -1
- package/dist/dom/icons.d.ts +4 -0
- package/dist/dom/icons.d.ts.map +1 -0
- package/dist/dom/input.d.ts +1 -0
- package/dist/dom/input.d.ts.map +1 -1
- package/dist/dom/messages.d.ts +38 -1
- package/dist/dom/messages.d.ts.map +1 -1
- package/dist/dom/panel.d.ts +8 -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 +10 -1
- package/dist/dom/shadow.d.ts.map +1 -1
- package/dist/i18n.d.ts +7 -1
- package/dist/i18n.d.ts.map +1 -1
- package/dist/index.d.ts +41 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/loader/index.d.ts +2 -0
- package/dist/loader/index.d.ts.map +1 -0
- package/dist/loader/loader.d.ts +71 -0
- package/dist/loader/loader.d.ts.map +1 -0
- package/dist/locales/ar.d.ts +4 -0
- package/dist/locales/ar.d.ts.map +1 -0
- package/dist/locales/ar.js +1 -0
- package/dist/locales/de.d.ts +4 -0
- package/dist/locales/de.d.ts.map +1 -0
- package/dist/locales/de.js +1 -0
- package/dist/locales/es.d.ts +4 -0
- package/dist/locales/es.d.ts.map +1 -0
- package/dist/locales/es.js +1 -0
- package/dist/locales/fr.d.ts +4 -0
- package/dist/locales/fr.d.ts.map +1 -0
- package/dist/locales/fr.js +1 -0
- package/dist/locales/he.d.ts +4 -0
- package/dist/locales/he.d.ts.map +1 -0
- package/dist/locales/he.js +1 -0
- package/dist/locales/hi.d.ts +4 -0
- package/dist/locales/hi.d.ts.map +1 -0
- package/dist/locales/hi.js +1 -0
- package/dist/locales/it.d.ts +4 -0
- package/dist/locales/it.d.ts.map +1 -0
- package/dist/locales/it.js +1 -0
- package/dist/locales/ja.d.ts +4 -0
- package/dist/locales/ja.d.ts.map +1 -0
- package/dist/locales/ja.js +1 -0
- package/dist/locales/ko.d.ts +4 -0
- package/dist/locales/ko.d.ts.map +1 -0
- package/dist/locales/ko.js +1 -0
- package/dist/locales/nl.d.ts +4 -0
- package/dist/locales/nl.d.ts.map +1 -0
- package/dist/locales/nl.js +1 -0
- package/dist/locales/pt.d.ts +4 -0
- package/dist/locales/pt.d.ts.map +1 -0
- package/dist/locales/pt.js +1 -0
- package/dist/locales/ur.d.ts +4 -0
- package/dist/locales/ur.d.ts.map +1 -0
- package/dist/locales/ur.js +1 -0
- package/dist/locales/zh.d.ts +4 -0
- package/dist/locales/zh.d.ts.map +1 -0
- package/dist/locales/zh.js +1 -0
- package/dist/panel.d.ts +6 -0
- package/dist/panel.d.ts.map +1 -0
- package/dist/script-config.d.ts +5 -0
- package/dist/script-config.d.ts.map +1 -0
- package/dist/storage.d.ts +30 -10
- package/dist/storage.d.ts.map +1 -1
- package/dist/theme.d.ts +15 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/widget.d.ts +124 -15
- package/dist/widget.d.ts.map +1 -1
- package/package.json +7 -3
package/dist/chatcops.esm.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
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}}";
|
|
1
|
+
const launcherStyles = ':host{--cc-accent: var(--cc-theme-accent, #4f46e5);--cc-accent-hover: var(--cc-theme-accent-hover, #4338ca);--cc-on-accent: var(--cc-theme-on-accent, #ffffff);--cc-accent-contrast: var(--cc-theme-accent-contrast, #4f46e5);--cc-text: #1a1d23;--cc-bg: #ffffff;--cc-bg-surface: #f4f5f7;--cc-bg-input: #ffffff;--cc-border: #d9dce1;--cc-text-secondary: #5b616e;--cc-danger: #c62828;--cc-code-bg: rgb(0 0 0 / .07);--cc-pre-bg: rgb(0 0 0 / .05);--cc-shadow: 0 12px 40px rgb(16 24 40 / .18);--cc-font: system-ui, -apple-system, sans-serif;--cc-radius: 12px;--cc-fab-size: 56px;--cc-panel-width: 400px;--cc-panel-height: 560px;--cc-focus-ring: 0 0 0 2px var(--cc-bg), 0 0 0 4px var(--cc-accent);all:initial;color-scheme:light;font-family:var(--cc-font);font-size:14px;line-height:1.5;color:var(--cc-text);-webkit-font-smoothing:antialiased}:host([data-cc-mode="dark"]){--cc-accent: var(--cc-theme-accent-dark, var(--cc-theme-accent, #6366f1));--cc-accent-hover: var(--cc-theme-accent-dark-hover, var(--cc-theme-accent-hover, #4f46e5));--cc-on-accent: var(--cc-theme-on-accent-dark, var(--cc-theme-on-accent, #ffffff));--cc-accent-contrast: var(--cc-theme-accent-dark-contrast, var(--cc-theme-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-danger: #ef4444;--cc-code-bg: rgba(0, 0, 0, .2);--cc-pre-bg: rgba(0, 0, 0, .3);--cc-shadow: 0 8px 32px rgba(0,0,0,.4);color-scheme:dark}@media(prefers-color-scheme:dark){:host(:not([data-cc-mode])),:host([data-cc-mode="auto"]){--cc-accent: var(--cc-theme-accent-dark, var(--cc-theme-accent, #6366f1));--cc-accent-hover: var(--cc-theme-accent-dark-hover, var(--cc-theme-accent-hover, #4f46e5));--cc-on-accent: var(--cc-theme-on-accent-dark, var(--cc-theme-on-accent, #ffffff));--cc-accent-contrast: var(--cc-theme-accent-dark-contrast, var(--cc-theme-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-danger: #ef4444;--cc-code-bg: rgba(0, 0, 0, .2);--cc-pre-bg: rgba(0, 0, 0, .3);--cc-shadow: 0 8px 32px rgba(0,0,0,.4);color-scheme:dark}}*,*: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;inset-inline-end:-2px;min-width:18px;height:18px;padding:0 4px;border-radius:9px;background:#ef4444;border:2px solid var(--cc-bg);color:#fff;font-size:11px;font-weight:700;line-height:1;display:none;align-items:center;justify-content:center;pointer-events:none}.cc-fab-badge.cc-visible{display:flex}@keyframes cc-pulse{0%,to{box-shadow:0 4px 16px #0000004d}50%{box-shadow:0 4px 16px color-mix(in srgb,var(--cc-accent) 50%,transparent)}}.cc-fab.cc-pulse{animation:cc-pulse 2s ease-in-out 3}.cc-fab:focus-visible{outline:2px solid transparent;box-shadow:var(--cc-focus-ring),0 4px 16px #0000004d;animation:none}@media(prefers-reduced-motion:reduce){.cc-fab.cc-pulse{animation:none}}';
|
|
2
|
+
const widgetStyles = '.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;inset-inline: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-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,.cc-header-avatar-placeholder{width:36px;height:36px;border-radius:50%;margin-inline-end:12px}.cc-header-avatar{object-fit:cover}.cc-header-avatar-placeholder{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-end-end-radius:4px}.cc-message-assistant{align-self:flex-start;background:var(--cc-bg-surface);color:var(--cc-text);border-end-start-radius:4px}.cc-suggestions{display:flex;flex-wrap:wrap;gap:8px;padding:0 16px 12px}.cc-suggestion{border:1px solid var(--cc-border);border-radius:var(--cc-radius);padding:8px 12px;background:var(--cc-bg-surface);color:var(--cc-text);cursor:pointer;font:inherit}.cc-suggestion:hover{border-color:var(--cc-accent)}.cc-message a{color:var(--cc-accent);text-decoration:underline}.cc-message-user a{color:var(--cc-on-accent)}.cc-disclosure{margin-block-end:4px;padding:8px 12px;border:1px solid var(--cc-border);border-radius:10px;font-size:12px;color:var(--cc-text-secondary)}.cc-disclosure a{color:inherit;text-decoration:underline}.cc-message-error-note{margin-top:6px;font-size:12px;color:var(--cc-text-secondary)}.cc-message-sources{margin-block-start:8px;font-size:12px;color:var(--cc-text-secondary)}.cc-message-sources ol{margin:0;padding-inline-start:1.2em}.cc-message-sources a{color:var(--cc-text-secondary);text-decoration:underline}.cc-message code{background:var(--cc-code-bg);padding:1px 4px;border-radius:4px;font-family:SF Mono,Cascadia Code,Consolas,monospace;font-size:13px}.cc-message pre{background:var(--cc-pre-bg);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-inline-start: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:var(--cc-danger);color:var(--cc-danger)}.cc-message-retry:hover{background:color-mix(in srgb,var(--cc-danger) 10%,transparent);color:var(--cc-danger)}.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-end-start-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)}.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)}:host([dir="rtl"]) .cc-send-btn svg{transform:scaleX(-1)}.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}.cc-bubble.cc-visible{opacity:1;transform:translateY(0);pointer-events:auto}.cc-bubble.cc-left{right:auto;left:20px}.cc-bubble-trigger{-webkit-appearance:none;-moz-appearance:none;appearance:none;display:block;width:100%;padding:0;margin:0;border:0;background:transparent;color:inherit;font:inherit;line-height:inherit;text-align:start;cursor:pointer}.cc-bubble-close{position:absolute;top:-8px;inset-inline-end:-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}@media(max-width:640px){.cc-fab.cc-open{display:none}.cc-panel:not(.cc-inline){width:100%;height:100%;height:100dvh;max-height:100%;bottom:0;inset-inline:0;border-radius:0;border:none;padding-top:env(safe-area-inset-top);padding-bottom:env(safe-area-inset-bottom)}.cc-panel.cc-left:not(.cc-inline){left:0}.cc-input{font-size:16px}.cc-bubble{bottom:84px;inset-inline:16px;max-width:none}}.cc-header-close:focus-visible,.cc-send-btn:focus-visible,.cc-suggestion:focus-visible,.cc-message-retry:focus-visible,.cc-message-regenerate:focus-visible,.cc-bubble-trigger:focus-visible,.cc-bubble-close:focus-visible,.cc-input:focus-visible{outline:2px solid transparent;box-shadow:var(--cc-focus-ring)}@media(prefers-reduced-motion:reduce){.cc-typing-dot{animation:none}.cc-panel,.cc-bubble{transition:none}}';
|
|
2
3
|
const HOST_ID = "chatcops-root";
|
|
3
|
-
|
|
4
|
+
const RTL_LANGUAGES = /* @__PURE__ */ new Set(["ar", "he", "fa", "ur"]);
|
|
5
|
+
function setLanguageDirection(code, dir) {
|
|
6
|
+
const language = code.toLowerCase().split(/[-_]/)[0];
|
|
7
|
+
if (dir === "rtl") RTL_LANGUAGES.add(language);
|
|
8
|
+
else RTL_LANGUAGES.delete(language);
|
|
9
|
+
}
|
|
10
|
+
function resolveDirection(configured, locale) {
|
|
11
|
+
if (configured) return configured;
|
|
12
|
+
const language = locale == null ? void 0 : locale.toLowerCase().split(/[-_]/)[0];
|
|
13
|
+
if (language && RTL_LANGUAGES.has(language)) return "rtl";
|
|
14
|
+
return (document.dir || document.documentElement.dir) === "rtl" ? "rtl" : "ltr";
|
|
15
|
+
}
|
|
16
|
+
function createShadowRoot(container, options = {}) {
|
|
4
17
|
const host = document.createElement("div");
|
|
5
18
|
host.id = nextHostId();
|
|
6
19
|
host.className = HOST_ID;
|
|
20
|
+
host.dir = resolveDirection(options.dir, options.locale);
|
|
21
|
+
if (options.locale) host.lang = options.locale;
|
|
7
22
|
if (container) {
|
|
8
23
|
container.appendChild(host);
|
|
9
24
|
} else {
|
|
@@ -11,7 +26,7 @@ function createShadowRoot(container) {
|
|
|
11
26
|
}
|
|
12
27
|
const shadow = host.attachShadow({ mode: "open" });
|
|
13
28
|
const style = document.createElement("style");
|
|
14
|
-
style.textContent = widgetStyles;
|
|
29
|
+
style.textContent = launcherStyles + widgetStyles;
|
|
15
30
|
shadow.appendChild(style);
|
|
16
31
|
return shadow;
|
|
17
32
|
}
|
|
@@ -24,6 +39,8 @@ function nextHostId() {
|
|
|
24
39
|
while (document.getElementById(`${HOST_ID}-${n}`)) n += 1;
|
|
25
40
|
return `${HOST_ID}-${n}`;
|
|
26
41
|
}
|
|
42
|
+
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>`;
|
|
43
|
+
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>`;
|
|
27
44
|
const ESCAPE_MAP = {
|
|
28
45
|
"&": "&",
|
|
29
46
|
"<": "<",
|
|
@@ -88,29 +105,108 @@ function renderMarkdown(input) {
|
|
|
88
105
|
}
|
|
89
106
|
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>';
|
|
90
107
|
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>';
|
|
108
|
+
const FOLLOW_SLACK = 48;
|
|
109
|
+
const STREAM_INTERVAL_MS = 50;
|
|
91
110
|
class Messages {
|
|
92
|
-
constructor(parent, actions) {
|
|
111
|
+
constructor(parent, actions, options = {}) {
|
|
112
|
+
this.streams = /* @__PURE__ */ new Map();
|
|
93
113
|
this.typingEl = null;
|
|
114
|
+
this.suggestionsEl = null;
|
|
115
|
+
this.disclosureEl = null;
|
|
94
116
|
this.actions = actions;
|
|
117
|
+
this.streamIntervalMs = options.streamIntervalMs ?? STREAM_INTERVAL_MS;
|
|
95
118
|
this.container = document.createElement("div");
|
|
96
119
|
this.container.className = "cc-messages";
|
|
97
120
|
this.container.setAttribute("role", "log");
|
|
98
121
|
this.container.setAttribute("aria-live", "polite");
|
|
99
122
|
parent.appendChild(this.container);
|
|
123
|
+
this.announcement = document.createElement("div");
|
|
124
|
+
this.announcement.className = "cc-reply-announcement cc-sr-only";
|
|
125
|
+
this.announcement.setAttribute("aria-live", "polite");
|
|
126
|
+
this.announcement.setAttribute("aria-atomic", "true");
|
|
127
|
+
parent.appendChild(this.announcement);
|
|
128
|
+
}
|
|
129
|
+
beginReply() {
|
|
130
|
+
this.announcement.textContent = "";
|
|
131
|
+
this.container.setAttribute("aria-live", "off");
|
|
132
|
+
}
|
|
133
|
+
finishReply(msg) {
|
|
134
|
+
var _a, _b, _c, _d;
|
|
135
|
+
if ((msg == null ? void 0 : msg.role) === "assistant" && (msg.status === "complete" || msg.status === "error")) {
|
|
136
|
+
const rendered = this.findMessage(msg.id);
|
|
137
|
+
const content = ((_b = (_a = rendered == null ? void 0 : rendered.querySelector(".cc-message-content")) == null ? void 0 : _a.textContent) == null ? void 0 : _b.trim()) ?? "";
|
|
138
|
+
const error = ((_d = (_c = rendered == null ? void 0 : rendered.querySelector(".cc-message-error-note")) == null ? void 0 : _c.textContent) == null ? void 0 : _d.trim()) ?? "";
|
|
139
|
+
this.announcement.textContent = [content, error && error !== content ? error : ""].filter(Boolean).join(" ");
|
|
140
|
+
}
|
|
141
|
+
this.container.setAttribute("aria-live", "polite");
|
|
100
142
|
}
|
|
101
143
|
addMessage(msg) {
|
|
144
|
+
const follow = msg.role === "user" || this.nearBottom();
|
|
102
145
|
this.removeTyping();
|
|
146
|
+
if (msg.role === "user") this.removeSuggestions();
|
|
103
147
|
const el = this.createMessageElement(msg);
|
|
104
148
|
this.container.appendChild(el);
|
|
105
|
-
this.scrollToBottom();
|
|
149
|
+
if (follow) this.scrollToBottom();
|
|
106
150
|
return el;
|
|
107
151
|
}
|
|
108
152
|
updateMessage(msg) {
|
|
109
153
|
const el = this.findMessage(msg.id);
|
|
110
|
-
if (el)
|
|
111
|
-
|
|
112
|
-
this.
|
|
154
|
+
if (!el) return;
|
|
155
|
+
if (msg.role === "assistant" && msg.status === "streaming" && el.dataset.status === "streaming") {
|
|
156
|
+
this.stream(el, msg);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
this.endStream(msg.id);
|
|
160
|
+
const follow = this.nearBottom();
|
|
161
|
+
this.renderMessageElement(el, msg);
|
|
162
|
+
if (follow) this.scrollToBottom();
|
|
163
|
+
}
|
|
164
|
+
/** Takes the reply's text so far, and renders what changed on the next frame. */
|
|
165
|
+
stream(el, msg) {
|
|
166
|
+
let stream = this.streams.get(msg.id);
|
|
167
|
+
if (!stream || !appended(stream.text, msg.content)) {
|
|
168
|
+
this.endStream(msg.id);
|
|
169
|
+
const content = el.querySelector(".cc-message-content");
|
|
170
|
+
if (!content) return;
|
|
171
|
+
content.innerHTML = "";
|
|
172
|
+
const tail = document.createElement("div");
|
|
173
|
+
tail.className = "cc-stream-tail";
|
|
174
|
+
content.appendChild(tail);
|
|
175
|
+
stream = { text: msg.content, committed: 0, tail, renderedAt: 0 };
|
|
176
|
+
this.streams.set(msg.id, stream);
|
|
177
|
+
this.renderStream(stream);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
stream.text = msg.content;
|
|
181
|
+
if (stream.frame === void 0) this.schedule(stream);
|
|
182
|
+
}
|
|
183
|
+
/** Renders on the next frame that's at least the interval after the last render. */
|
|
184
|
+
schedule(stream) {
|
|
185
|
+
stream.frame = nextFrame(() => {
|
|
186
|
+
stream.frame = void 0;
|
|
187
|
+
if (performance.now() - stream.renderedAt < this.streamIntervalMs) this.schedule(stream);
|
|
188
|
+
else this.renderStream(stream);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
renderStream(stream) {
|
|
192
|
+
stream.renderedAt = performance.now();
|
|
193
|
+
const follow = this.nearBottom();
|
|
194
|
+
const boundary = lastBlockBoundary(stream.text, stream.committed);
|
|
195
|
+
if (boundary > stream.committed) {
|
|
196
|
+
stream.tail.insertAdjacentHTML("beforebegin", `<div class="cc-block">${renderMarkdown(stream.text.slice(stream.committed, boundary))}</div>`);
|
|
197
|
+
stream.committed = boundary;
|
|
113
198
|
}
|
|
199
|
+
stream.tail.innerHTML = renderMarkdown(stream.text.slice(stream.committed));
|
|
200
|
+
if (follow) this.scrollToBottom();
|
|
201
|
+
}
|
|
202
|
+
endStream(id) {
|
|
203
|
+
const stream = this.streams.get(id);
|
|
204
|
+
if ((stream == null ? void 0 : stream.frame) !== void 0) cancelFrame(stream.frame);
|
|
205
|
+
this.streams.delete(id);
|
|
206
|
+
}
|
|
207
|
+
nearBottom() {
|
|
208
|
+
const { scrollHeight, scrollTop, clientHeight } = this.container;
|
|
209
|
+
return scrollHeight - scrollTop - clientHeight <= FOLLOW_SLACK;
|
|
114
210
|
}
|
|
115
211
|
showTyping() {
|
|
116
212
|
var _a;
|
|
@@ -127,11 +223,61 @@ class Messages {
|
|
|
127
223
|
this.typingEl = null;
|
|
128
224
|
}
|
|
129
225
|
}
|
|
226
|
+
showSuggestions(suggestions, label, onSelect) {
|
|
227
|
+
this.removeSuggestions();
|
|
228
|
+
if (suggestions.length === 0) return;
|
|
229
|
+
const group = document.createElement("div");
|
|
230
|
+
group.className = "cc-suggestions";
|
|
231
|
+
group.setAttribute("role", "group");
|
|
232
|
+
group.setAttribute("aria-label", label);
|
|
233
|
+
for (const suggestion of suggestions) {
|
|
234
|
+
const button = document.createElement("button");
|
|
235
|
+
button.type = "button";
|
|
236
|
+
button.className = "cc-suggestion";
|
|
237
|
+
button.textContent = suggestion;
|
|
238
|
+
button.addEventListener("click", () => onSelect(suggestion));
|
|
239
|
+
group.appendChild(button);
|
|
240
|
+
}
|
|
241
|
+
this.container.appendChild(group);
|
|
242
|
+
this.suggestionsEl = group;
|
|
243
|
+
}
|
|
244
|
+
removeSuggestions() {
|
|
245
|
+
var _a;
|
|
246
|
+
(_a = this.suggestionsEl) == null ? void 0 : _a.remove();
|
|
247
|
+
this.suggestionsEl = null;
|
|
248
|
+
}
|
|
130
249
|
clear() {
|
|
250
|
+
for (const id of [...this.streams.keys()]) this.endStream(id);
|
|
131
251
|
this.container.innerHTML = "";
|
|
132
252
|
this.typingEl = null;
|
|
253
|
+
if (this.disclosureEl) this.container.prepend(this.disclosureEl);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* The note that visitors are talking to an AI, always first in the log.
|
|
257
|
+
* Only http(s) links become a "Learn more" link.
|
|
258
|
+
*/
|
|
259
|
+
setDisclosure(text, learnMore) {
|
|
260
|
+
var _a;
|
|
261
|
+
(_a = this.disclosureEl) == null ? void 0 : _a.remove();
|
|
262
|
+
this.disclosureEl = null;
|
|
263
|
+
if (!text) return;
|
|
264
|
+
const note = document.createElement("div");
|
|
265
|
+
note.className = "cc-disclosure";
|
|
266
|
+
note.setAttribute("role", "note");
|
|
267
|
+
note.textContent = text;
|
|
268
|
+
if (learnMore && /^https?:\/\//i.test(learnMore.url)) {
|
|
269
|
+
const link = document.createElement("a");
|
|
270
|
+
link.href = learnMore.url;
|
|
271
|
+
link.target = "_blank";
|
|
272
|
+
link.rel = "noopener noreferrer";
|
|
273
|
+
link.textContent = learnMore.label;
|
|
274
|
+
note.append(" ", link);
|
|
275
|
+
}
|
|
276
|
+
this.disclosureEl = note;
|
|
277
|
+
this.container.prepend(note);
|
|
133
278
|
}
|
|
134
279
|
removeMessage(id) {
|
|
280
|
+
this.endStream(id);
|
|
135
281
|
const el = this.findMessage(id);
|
|
136
282
|
el == null ? void 0 : el.remove();
|
|
137
283
|
}
|
|
@@ -174,6 +320,7 @@ class Messages {
|
|
|
174
320
|
return el;
|
|
175
321
|
}
|
|
176
322
|
renderMessageElement(el, msg) {
|
|
323
|
+
var _a;
|
|
177
324
|
el.className = `cc-message cc-message-${msg.role}`;
|
|
178
325
|
el.dataset.id = msg.id;
|
|
179
326
|
if (msg.role === "assistant" && msg.id !== "welcome") {
|
|
@@ -199,6 +346,31 @@ class Messages {
|
|
|
199
346
|
content.className = "cc-message-content";
|
|
200
347
|
content.innerHTML = msg.role === "assistant" ? renderMarkdown(msg.content) : this.escapeHtml(msg.content);
|
|
201
348
|
el.appendChild(content);
|
|
349
|
+
if (msg.role === "assistant" && Array.isArray(msg.sources) && msg.sources.length > 0) {
|
|
350
|
+
const safeUrls = msg.sources.filter((s) => typeof s.url === "string" && (s.url.startsWith("http://") || s.url.startsWith("https://")));
|
|
351
|
+
if (safeUrls.length > 0) {
|
|
352
|
+
const nav = document.createElement("nav");
|
|
353
|
+
nav.className = "cc-message-sources";
|
|
354
|
+
nav.setAttribute("aria-label", ((_a = this.actions) == null ? void 0 : _a.sourcesLabel) ?? "Sources");
|
|
355
|
+
const ol = document.createElement("ol");
|
|
356
|
+
for (const source of safeUrls) {
|
|
357
|
+
const li = document.createElement("li");
|
|
358
|
+
const a = document.createElement("a");
|
|
359
|
+
a.href = source.url;
|
|
360
|
+
a.target = "_blank";
|
|
361
|
+
a.rel = "noopener noreferrer";
|
|
362
|
+
try {
|
|
363
|
+
a.textContent = source.title ?? new URL(source.url).hostname;
|
|
364
|
+
} catch {
|
|
365
|
+
a.textContent = source.title ?? source.url;
|
|
366
|
+
}
|
|
367
|
+
li.appendChild(a);
|
|
368
|
+
ol.appendChild(li);
|
|
369
|
+
}
|
|
370
|
+
nav.appendChild(ol);
|
|
371
|
+
el.appendChild(nav);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
202
374
|
if (msg.status === "error" && msg.errorText) {
|
|
203
375
|
const note = document.createElement("div");
|
|
204
376
|
note.className = "cc-message-error-note";
|
|
@@ -259,6 +431,31 @@ class Messages {
|
|
|
259
431
|
return div.innerHTML;
|
|
260
432
|
}
|
|
261
433
|
}
|
|
434
|
+
function lastBlockBoundary(text, from) {
|
|
435
|
+
let inFence = false;
|
|
436
|
+
let last = from;
|
|
437
|
+
let i = from;
|
|
438
|
+
while (i < text.length) {
|
|
439
|
+
if (text.startsWith("```", i)) {
|
|
440
|
+
inFence = !inFence;
|
|
441
|
+
i += 3;
|
|
442
|
+
} else if (!inFence && text.startsWith("\n\n", i)) {
|
|
443
|
+
i += 2;
|
|
444
|
+
while (text[i] === "\n") i++;
|
|
445
|
+
last = i;
|
|
446
|
+
} else {
|
|
447
|
+
i++;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return last;
|
|
451
|
+
}
|
|
452
|
+
function appended(previous, next) {
|
|
453
|
+
if (next.length < previous.length) return false;
|
|
454
|
+
const from = Math.max(0, previous.length - 32);
|
|
455
|
+
return next.slice(from, previous.length) === previous.slice(from);
|
|
456
|
+
}
|
|
457
|
+
const nextFrame = (fn) => typeof requestAnimationFrame === "function" ? requestAnimationFrame(fn) : setTimeout(fn, 16);
|
|
458
|
+
const cancelFrame = (id) => typeof cancelAnimationFrame === "function" ? cancelAnimationFrame(id) : clearTimeout(id);
|
|
262
459
|
const MAX_MESSAGE_LENGTH = 1e4;
|
|
263
460
|
class Input {
|
|
264
461
|
constructor(parent, options) {
|
|
@@ -286,6 +483,11 @@ class Input {
|
|
|
286
483
|
this.area.appendChild(this.sendBtn);
|
|
287
484
|
parent.appendChild(this.area);
|
|
288
485
|
}
|
|
486
|
+
setLabels(placeholder, sendLabel) {
|
|
487
|
+
this.textarea.placeholder = placeholder;
|
|
488
|
+
this.textarea.setAttribute("aria-label", placeholder);
|
|
489
|
+
this.sendBtn.setAttribute("aria-label", sendLabel);
|
|
490
|
+
}
|
|
289
491
|
focus() {
|
|
290
492
|
this.textarea.focus();
|
|
291
493
|
}
|
|
@@ -322,7 +524,7 @@ class Input {
|
|
|
322
524
|
}
|
|
323
525
|
const PANEL_ID = "cc-panel";
|
|
324
526
|
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
|
|
527
|
+
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>`;
|
|
326
528
|
class Panel {
|
|
327
529
|
constructor(root, options) {
|
|
328
530
|
this.inline = options.inline ?? false;
|
|
@@ -337,39 +539,38 @@ class Panel {
|
|
|
337
539
|
if (!options.inline) {
|
|
338
540
|
this.el.id = PANEL_ID;
|
|
339
541
|
this.el.setAttribute("role", "dialog");
|
|
340
|
-
this.el.setAttribute("aria-
|
|
542
|
+
this.el.setAttribute("aria-modal", "true");
|
|
341
543
|
this.el.addEventListener("keydown", (e) => {
|
|
342
|
-
if (e.key === "Escape")
|
|
544
|
+
if (e.key === "Escape") {
|
|
545
|
+
options.onClose();
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (e.key !== "Tab" || this.el.hasAttribute("inert")) return;
|
|
549
|
+
const focusable = Array.from(this.el.querySelectorAll("*")).filter(
|
|
550
|
+
(element) => element.tabIndex >= 0 && !element.matches(":disabled") && !element.closest('[hidden], [inert], [aria-hidden="true"], .cc-hidden')
|
|
551
|
+
);
|
|
552
|
+
if (focusable.length === 0) {
|
|
553
|
+
e.preventDefault();
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const activeElement = this.el.getRootNode().activeElement;
|
|
557
|
+
const first = focusable[0];
|
|
558
|
+
const last = focusable[focusable.length - 1];
|
|
559
|
+
if (e.shiftKey && (activeElement === first || !this.el.contains(activeElement))) {
|
|
560
|
+
e.preventDefault();
|
|
561
|
+
last.focus();
|
|
562
|
+
} else if (!e.shiftKey && (activeElement === last || !this.el.contains(activeElement))) {
|
|
563
|
+
e.preventDefault();
|
|
564
|
+
first.focus();
|
|
565
|
+
}
|
|
343
566
|
});
|
|
344
567
|
this.setHidden(true);
|
|
345
568
|
}
|
|
346
|
-
|
|
347
|
-
header
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
img.src = options.branding.avatar;
|
|
352
|
-
img.alt = options.branding.name;
|
|
353
|
-
header.appendChild(img);
|
|
354
|
-
} else {
|
|
355
|
-
const placeholder = document.createElement("div");
|
|
356
|
-
placeholder.className = "cc-header-avatar-placeholder";
|
|
357
|
-
placeholder.innerHTML = BOT_ICON;
|
|
358
|
-
header.appendChild(placeholder);
|
|
359
|
-
}
|
|
360
|
-
const info = document.createElement("div");
|
|
361
|
-
info.className = "cc-header-info";
|
|
362
|
-
info.innerHTML = `<div class="cc-header-name">${this.escapeHtml(options.branding.name)}</div><div class="cc-header-subtitle">${this.escapeHtml(options.branding.subtitle)}</div>`;
|
|
363
|
-
header.appendChild(info);
|
|
364
|
-
if (!options.inline) {
|
|
365
|
-
const closeBtn = document.createElement("button");
|
|
366
|
-
closeBtn.className = "cc-header-close";
|
|
367
|
-
closeBtn.setAttribute("aria-label", options.closeLabel);
|
|
368
|
-
closeBtn.innerHTML = CLOSE_ICON$1;
|
|
369
|
-
closeBtn.addEventListener("click", options.onClose);
|
|
370
|
-
header.appendChild(closeBtn);
|
|
371
|
-
}
|
|
372
|
-
this.el.appendChild(header);
|
|
569
|
+
this.options = options;
|
|
570
|
+
this.header = document.createElement("div");
|
|
571
|
+
this.header.className = "cc-header";
|
|
572
|
+
this.renderHeader();
|
|
573
|
+
this.el.appendChild(this.header);
|
|
373
574
|
if (options.preChatEnabled) {
|
|
374
575
|
this.messagesContainer = document.createElement("div");
|
|
375
576
|
this.messagesContainer.className = "cc-messages-wrapper";
|
|
@@ -383,12 +584,51 @@ class Panel {
|
|
|
383
584
|
sendLabel: options.sendLabel,
|
|
384
585
|
onSend: options.onSend
|
|
385
586
|
});
|
|
386
|
-
|
|
387
|
-
footer.className = "cc-footer";
|
|
388
|
-
footer.innerHTML = options.footerText;
|
|
389
|
-
this.el.appendChild(footer);
|
|
587
|
+
this.footer = document.createElement("div");
|
|
588
|
+
this.footer.className = "cc-footer";
|
|
589
|
+
this.footer.innerHTML = options.footerText;
|
|
590
|
+
this.el.appendChild(this.footer);
|
|
390
591
|
root.appendChild(this.el);
|
|
391
592
|
}
|
|
593
|
+
/** New wording or branding, applied in place. */
|
|
594
|
+
update(labels, actionLabels) {
|
|
595
|
+
Object.assign(this.options, labels);
|
|
596
|
+
if (this.options.messageActions) Object.assign(this.options.messageActions, actionLabels);
|
|
597
|
+
this.renderHeader();
|
|
598
|
+
this.input.setLabels(labels.placeholder, labels.sendLabel);
|
|
599
|
+
this.footer.innerHTML = labels.footerText;
|
|
600
|
+
}
|
|
601
|
+
renderHeader() {
|
|
602
|
+
const { branding, closeLabel, inline, onClose } = this.options;
|
|
603
|
+
const hadFocus = this.header.contains(this.el.getRootNode().activeElement);
|
|
604
|
+
this.header.replaceChildren();
|
|
605
|
+
if (!inline) this.el.setAttribute("aria-label", branding.name);
|
|
606
|
+
if (branding.avatar) {
|
|
607
|
+
const img = document.createElement("img");
|
|
608
|
+
img.className = "cc-header-avatar";
|
|
609
|
+
img.src = branding.avatar;
|
|
610
|
+
img.alt = branding.name;
|
|
611
|
+
this.header.appendChild(img);
|
|
612
|
+
} else {
|
|
613
|
+
const placeholder = document.createElement("div");
|
|
614
|
+
placeholder.className = "cc-header-avatar-placeholder";
|
|
615
|
+
placeholder.innerHTML = BOT_ICON;
|
|
616
|
+
this.header.appendChild(placeholder);
|
|
617
|
+
}
|
|
618
|
+
const info = document.createElement("div");
|
|
619
|
+
info.className = "cc-header-info";
|
|
620
|
+
info.innerHTML = `<div class="cc-header-name">${this.escapeHtml(branding.name)}</div><div class="cc-header-subtitle">${this.escapeHtml(branding.subtitle)}</div>`;
|
|
621
|
+
this.header.appendChild(info);
|
|
622
|
+
if (!inline) {
|
|
623
|
+
const closeBtn = document.createElement("button");
|
|
624
|
+
closeBtn.className = "cc-header-close";
|
|
625
|
+
closeBtn.setAttribute("aria-label", closeLabel);
|
|
626
|
+
closeBtn.innerHTML = CLOSE_ICON;
|
|
627
|
+
closeBtn.addEventListener("click", onClose);
|
|
628
|
+
this.header.appendChild(closeBtn);
|
|
629
|
+
if (hadFocus) closeBtn.focus();
|
|
630
|
+
}
|
|
631
|
+
}
|
|
392
632
|
show() {
|
|
393
633
|
this.el.classList.add("cc-visible");
|
|
394
634
|
if (!this.inline) {
|
|
@@ -430,12 +670,11 @@ class Panel {
|
|
|
430
670
|
return div.innerHTML;
|
|
431
671
|
}
|
|
432
672
|
}
|
|
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
673
|
class FAB {
|
|
436
674
|
constructor(root, options) {
|
|
437
675
|
this.openLabel = options.openLabel;
|
|
438
676
|
this.closeLabel = options.closeLabel;
|
|
677
|
+
this.unreadLabel = options.unreadLabel;
|
|
439
678
|
this.el = document.createElement("button");
|
|
440
679
|
this.el.className = "cc-fab cc-pulse";
|
|
441
680
|
this.el.setAttribute("aria-label", this.openLabel);
|
|
@@ -455,20 +694,45 @@ class FAB {
|
|
|
455
694
|
root.appendChild(this.el);
|
|
456
695
|
}
|
|
457
696
|
setOpen(open) {
|
|
458
|
-
|
|
697
|
+
if (open) this.hideBadge();
|
|
698
|
+
this.el.innerHTML = open ? CLOSE_ICON$1 : CHAT_ICON;
|
|
459
699
|
if (!open) this.el.appendChild(this.badge);
|
|
460
700
|
this.el.classList.toggle("cc-open", open);
|
|
461
701
|
this.el.setAttribute("aria-label", open ? this.closeLabel : this.openLabel);
|
|
462
702
|
this.el.setAttribute("aria-expanded", String(open));
|
|
463
703
|
}
|
|
704
|
+
setLabels(openLabel, closeLabel, unreadLabel) {
|
|
705
|
+
this.openLabel = openLabel;
|
|
706
|
+
this.closeLabel = closeLabel;
|
|
707
|
+
this.unreadLabel = unreadLabel;
|
|
708
|
+
const shown = this.badge.textContent;
|
|
709
|
+
this.el.setAttribute(
|
|
710
|
+
"aria-label",
|
|
711
|
+
this.el.classList.contains("cc-open") ? closeLabel : shown ? `${openLabel}, ${unreadLabel.replace("{count}", shown)}` : openLabel
|
|
712
|
+
);
|
|
713
|
+
}
|
|
464
714
|
focus() {
|
|
465
715
|
this.el.focus();
|
|
466
716
|
}
|
|
467
|
-
showBadge() {
|
|
717
|
+
showBadge(count = 1) {
|
|
718
|
+
if (count <= 0) {
|
|
719
|
+
this.hideBadge();
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const display = count > 9 ? "9+" : String(count);
|
|
723
|
+
this.badge.textContent = display;
|
|
724
|
+
if (!this.el.classList.contains("cc-open")) {
|
|
725
|
+
const unread = this.unreadLabel.replace("{count}", display);
|
|
726
|
+
this.el.setAttribute("aria-label", `${this.openLabel}, ${unread}`);
|
|
727
|
+
}
|
|
468
728
|
this.badge.classList.add("cc-visible");
|
|
469
729
|
}
|
|
470
730
|
hideBadge() {
|
|
471
731
|
this.badge.classList.remove("cc-visible");
|
|
732
|
+
this.badge.textContent = "";
|
|
733
|
+
if (!this.el.classList.contains("cc-open")) {
|
|
734
|
+
this.el.setAttribute("aria-label", this.openLabel);
|
|
735
|
+
}
|
|
472
736
|
}
|
|
473
737
|
destroy() {
|
|
474
738
|
this.el.remove();
|
|
@@ -483,8 +747,15 @@ class WelcomeBubble {
|
|
|
483
747
|
if (options.position === "bottom-left") {
|
|
484
748
|
this.el.classList.add("cc-left");
|
|
485
749
|
}
|
|
486
|
-
this.el.textContent = options.text;
|
|
487
750
|
this.setHidden(true);
|
|
751
|
+
const trigger = document.createElement("button");
|
|
752
|
+
trigger.type = "button";
|
|
753
|
+
trigger.className = "cc-bubble-trigger";
|
|
754
|
+
trigger.textContent = options.text;
|
|
755
|
+
trigger.addEventListener("click", () => {
|
|
756
|
+
this.hide();
|
|
757
|
+
options.onClick();
|
|
758
|
+
});
|
|
488
759
|
const closeBtn = document.createElement("button");
|
|
489
760
|
closeBtn.type = "button";
|
|
490
761
|
closeBtn.className = "cc-bubble-close";
|
|
@@ -494,20 +765,13 @@ class WelcomeBubble {
|
|
|
494
765
|
e.stopPropagation();
|
|
495
766
|
this.hide();
|
|
496
767
|
});
|
|
497
|
-
this.el.
|
|
498
|
-
this.el.addEventListener("click", () => {
|
|
499
|
-
this.hide();
|
|
500
|
-
options.onClick();
|
|
501
|
-
});
|
|
768
|
+
this.el.append(trigger, closeBtn);
|
|
502
769
|
root.appendChild(this.el);
|
|
503
|
-
if (options.showOnce) {
|
|
504
|
-
try {
|
|
505
|
-
if (sessionStorage.getItem(BUBBLE_SHOWN_KEY)) return;
|
|
506
|
-
sessionStorage.setItem(BUBBLE_SHOWN_KEY, "1");
|
|
507
|
-
} catch {
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
770
|
this.timerId = setTimeout(() => {
|
|
771
|
+
if (options.showOnce) {
|
|
772
|
+
if (options.store.get(BUBBLE_SHOWN_KEY)) return;
|
|
773
|
+
options.store.set(BUBBLE_SHOWN_KEY, "1");
|
|
774
|
+
}
|
|
511
775
|
this.el.classList.add("cc-visible");
|
|
512
776
|
this.setHidden(false);
|
|
513
777
|
}, options.delay ?? 3e3);
|
|
@@ -530,43 +794,126 @@ class WelcomeBubble {
|
|
|
530
794
|
this.el.remove();
|
|
531
795
|
}
|
|
532
796
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
797
|
+
const KEY_PREFIX = "chatcops_conversation";
|
|
798
|
+
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
799
|
+
function keyStore(persistence) {
|
|
800
|
+
if (persistence === "none") {
|
|
801
|
+
const memory = /* @__PURE__ */ new Map();
|
|
802
|
+
return {
|
|
803
|
+
get: (key) => memory.get(key) ?? null,
|
|
804
|
+
set: (key, value) => void memory.set(key, value),
|
|
805
|
+
remove: (key) => void memory.delete(key)
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const storage = () => persistence === "local" ? localStorage : sessionStorage;
|
|
809
|
+
return {
|
|
810
|
+
get(key) {
|
|
811
|
+
try {
|
|
812
|
+
return storage().getItem(key);
|
|
813
|
+
} catch {
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
set(key, value) {
|
|
818
|
+
try {
|
|
819
|
+
storage().setItem(key, value);
|
|
820
|
+
} catch {
|
|
821
|
+
}
|
|
822
|
+
},
|
|
823
|
+
remove(key) {
|
|
824
|
+
try {
|
|
825
|
+
storage().removeItem(key);
|
|
826
|
+
} catch {
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
class ConversationStorage {
|
|
832
|
+
/** Without a siteId the key is the one the widget has always used. */
|
|
833
|
+
constructor(siteId, persistence = "local", historyTtlDays = 30) {
|
|
834
|
+
this.store = keyStore(persistence);
|
|
835
|
+
this.key = siteId ? `${KEY_PREFIX}_${siteId}` : KEY_PREFIX;
|
|
836
|
+
this.ttlMs = (Number.isFinite(historyTtlDays) && historyTtlDays >= 0 ? historyTtlDays : 30) * DAY_MS;
|
|
837
|
+
}
|
|
838
|
+
/** The saved conversation, or null when there is none, it's unreadable, or it expired. */
|
|
839
|
+
load(now = Date.now()) {
|
|
840
|
+
const raw = this.store.get(this.key);
|
|
841
|
+
if (raw === null) return null;
|
|
842
|
+
let parsed;
|
|
843
|
+
try {
|
|
844
|
+
parsed = JSON.parse(raw);
|
|
845
|
+
} catch {
|
|
846
|
+
this.clear();
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || typeof parsed.id !== "string" || !Array.isArray(parsed.messages)) {
|
|
850
|
+
this.clear();
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
const record = parsed;
|
|
854
|
+
if (typeof record.updatedAt === "number" && now - record.updatedAt > this.ttlMs) {
|
|
855
|
+
this.clear();
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
const messages = record.messages.flatMap(
|
|
859
|
+
(msg) => {
|
|
860
|
+
if (typeof msg !== "object" || msg === null || Array.isArray(msg) || msg.role !== "user" && msg.role !== "assistant" || typeof msg.content !== "string") {
|
|
861
|
+
return [];
|
|
862
|
+
}
|
|
863
|
+
const m = msg;
|
|
864
|
+
let sources;
|
|
865
|
+
if (Array.isArray(m.sources)) {
|
|
866
|
+
const clean = m.sources.filter(
|
|
867
|
+
(s) => typeof s === "object" && s !== null && !Array.isArray(s) && typeof s.url === "string" && (typeof s.title === "undefined" || typeof s.title === "string")
|
|
868
|
+
);
|
|
869
|
+
if (clean.length > 0) sources = clean;
|
|
870
|
+
}
|
|
871
|
+
return [{ ...m, sources }];
|
|
872
|
+
}
|
|
873
|
+
);
|
|
874
|
+
const userData = record.userData;
|
|
875
|
+
return {
|
|
876
|
+
...record,
|
|
877
|
+
id: record.id,
|
|
878
|
+
messages,
|
|
879
|
+
userData: userData && typeof userData === "object" && !Array.isArray(userData) ? userData : void 0,
|
|
880
|
+
preChatDone: record.preChatDone === true
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
save(conversation) {
|
|
884
|
+
const filtered = {
|
|
885
|
+
...conversation,
|
|
886
|
+
messages: conversation.messages.filter((m) => m.status !== "error").map(({ status, ...message }) => message)
|
|
887
|
+
};
|
|
888
|
+
this.store.set(this.key, JSON.stringify(filtered));
|
|
889
|
+
}
|
|
890
|
+
clear() {
|
|
891
|
+
this.store.remove(this.key);
|
|
557
892
|
}
|
|
558
893
|
}
|
|
559
894
|
class ChatClient {
|
|
560
895
|
constructor(options) {
|
|
561
|
-
this.saved = {};
|
|
562
896
|
this.apiUrl = options.apiUrl;
|
|
563
897
|
this.turnstileSiteKey = options.turnstileSiteKey;
|
|
898
|
+
this.userToken = options.userToken;
|
|
564
899
|
this.timeout = options.timeout ?? 3e4;
|
|
565
900
|
this.sessionKey = `chatcops-session:${options.apiUrl}`;
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
901
|
+
this.store = options.store ?? keyStore("local");
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* The signed-in visitor's identity token for the next session, or none.
|
|
905
|
+
* The session token from before is dropped, so the next message gets one
|
|
906
|
+
* for the new identity (the server starts a new conversation for a
|
|
907
|
+
* different user).
|
|
908
|
+
*/
|
|
909
|
+
setUserToken(token) {
|
|
910
|
+
if (token === this.userToken) return;
|
|
911
|
+
this.userToken = token;
|
|
912
|
+
this.saved = {};
|
|
913
|
+
this.store.remove(this.sessionKey);
|
|
914
|
+
}
|
|
915
|
+
get identity() {
|
|
916
|
+
return this.userToken;
|
|
570
917
|
}
|
|
571
918
|
async *sendMessage(request, options = {}) {
|
|
572
919
|
var _a;
|
|
@@ -617,22 +964,29 @@ class ChatClient {
|
|
|
617
964
|
body: JSON.stringify(body),
|
|
618
965
|
signal: controller.signal
|
|
619
966
|
});
|
|
620
|
-
|
|
967
|
+
let saved = this.saved;
|
|
968
|
+
if (!saved) {
|
|
969
|
+
try {
|
|
970
|
+
saved = JSON.parse(this.store.get(this.sessionKey) || "{}") ?? {};
|
|
971
|
+
} catch {
|
|
972
|
+
saved = {};
|
|
973
|
+
}
|
|
974
|
+
this.saved = saved;
|
|
975
|
+
}
|
|
621
976
|
const resumable = saved.conversationId === request.conversationId;
|
|
622
977
|
let response = await post(this.apiUrl, { protocol: 1, ...request }, resumable ? saved.token : void 0);
|
|
623
978
|
if (response.status === 401) {
|
|
624
979
|
const minted = await post(this.apiUrl.replace(/\/?(\?.*)?$/, "/session$1"), {
|
|
625
980
|
conversationId: request.conversationId,
|
|
626
981
|
previousToken: saved.token,
|
|
627
|
-
|
|
982
|
+
userToken: this.userToken,
|
|
983
|
+
// Its own chunk: only sites with a Turnstile key download it.
|
|
984
|
+
turnstileToken: this.turnstileSiteKey && !resumable ? await (await Promise.resolve().then(() => turnstile)).turnstileToken(this.turnstileSiteKey) : void 0
|
|
628
985
|
});
|
|
629
986
|
if (minted.ok) {
|
|
630
987
|
const session = await minted.json();
|
|
631
988
|
this.saved = session;
|
|
632
|
-
|
|
633
|
-
localStorage.setItem(this.sessionKey, JSON.stringify(session));
|
|
634
|
-
} catch {
|
|
635
|
-
}
|
|
989
|
+
this.store.set(this.sessionKey, JSON.stringify(session));
|
|
636
990
|
if (session.conversationId !== request.conversationId) {
|
|
637
991
|
request = { ...request, conversationId: session.conversationId };
|
|
638
992
|
yield { conversationId: session.conversationId };
|
|
@@ -722,6 +1076,11 @@ function fromPart(part) {
|
|
|
722
1076
|
return { content: part.delta };
|
|
723
1077
|
case "lead-captured":
|
|
724
1078
|
return { leadCaptured: true, leadData: part.lead };
|
|
1079
|
+
case "source": {
|
|
1080
|
+
const { url, title } = part;
|
|
1081
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) return null;
|
|
1082
|
+
return { source: { url, title } };
|
|
1083
|
+
}
|
|
725
1084
|
case "error":
|
|
726
1085
|
return errorChunk(part.code === "budget_exceeded" && part.scope !== "conversation" ? "budget_daily" : part.code, part.retryAfterMs);
|
|
727
1086
|
case "finish":
|
|
@@ -730,107 +1089,6 @@ function fromPart(part) {
|
|
|
730
1089
|
return null;
|
|
731
1090
|
}
|
|
732
1091
|
}
|
|
733
|
-
const KEY_PREFIX = "chatcops_conversation";
|
|
734
|
-
const SESSION_KEY_PREFIX = "chatcops_session_id";
|
|
735
|
-
class ConversationStorage {
|
|
736
|
-
/** Without a siteId the keys are the ones the widget has always used. */
|
|
737
|
-
constructor(siteId) {
|
|
738
|
-
this.key = siteId ? `${KEY_PREFIX}_${siteId}` : KEY_PREFIX;
|
|
739
|
-
this.sessionKey = siteId ? `${SESSION_KEY_PREFIX}_${siteId}` : SESSION_KEY_PREFIX;
|
|
740
|
-
}
|
|
741
|
-
load() {
|
|
742
|
-
try {
|
|
743
|
-
const raw = localStorage.getItem(this.key);
|
|
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
|
-
};
|
|
766
|
-
} catch {
|
|
767
|
-
return null;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
save(conversation) {
|
|
771
|
-
try {
|
|
772
|
-
const filtered = {
|
|
773
|
-
...conversation,
|
|
774
|
-
messages: conversation.messages.filter((m) => m.status !== "error").map(({ status, ...message }) => message)
|
|
775
|
-
};
|
|
776
|
-
localStorage.setItem(this.key, JSON.stringify(filtered));
|
|
777
|
-
} catch {
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
clear() {
|
|
781
|
-
try {
|
|
782
|
-
localStorage.removeItem(this.key);
|
|
783
|
-
} catch {
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
getSessionId() {
|
|
787
|
-
try {
|
|
788
|
-
let id = sessionStorage.getItem(this.sessionKey);
|
|
789
|
-
if (!id) {
|
|
790
|
-
id = crypto.randomUUID();
|
|
791
|
-
sessionStorage.setItem(this.sessionKey, id);
|
|
792
|
-
}
|
|
793
|
-
return id;
|
|
794
|
-
} catch {
|
|
795
|
-
return crypto.randomUUID();
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
isPreChatCompleted(sessionId) {
|
|
799
|
-
try {
|
|
800
|
-
return sessionStorage.getItem(`chatcops-prechat-${sessionId}`) === "true";
|
|
801
|
-
} catch {
|
|
802
|
-
return false;
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
setPreChatCompleted(sessionId, data) {
|
|
806
|
-
try {
|
|
807
|
-
sessionStorage.setItem(`chatcops-prechat-${sessionId}`, "true");
|
|
808
|
-
} catch {
|
|
809
|
-
}
|
|
810
|
-
if (data) {
|
|
811
|
-
this.setPreChatData(sessionId, data);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
getPreChatData(sessionId) {
|
|
815
|
-
try {
|
|
816
|
-
const raw = sessionStorage.getItem(`chatcops-prechat-data-${sessionId}`);
|
|
817
|
-
if (!raw) return null;
|
|
818
|
-
const parsed = JSON.parse(raw);
|
|
819
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
820
|
-
return null;
|
|
821
|
-
}
|
|
822
|
-
return parsed;
|
|
823
|
-
} catch {
|
|
824
|
-
return null;
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
setPreChatData(sessionId, data) {
|
|
828
|
-
try {
|
|
829
|
-
sessionStorage.setItem(`chatcops-prechat-data-${sessionId}`, JSON.stringify(data));
|
|
830
|
-
} catch {
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
1092
|
const DEFAULT = "";
|
|
835
1093
|
const CLAIMS_KEY = Symbol.for("chatcops.storageClaims");
|
|
836
1094
|
const store = globalThis;
|
|
@@ -867,19 +1125,14 @@ function claimNamespace(siteId, mode, isLive) {
|
|
|
867
1125
|
}
|
|
868
1126
|
};
|
|
869
1127
|
}
|
|
1128
|
+
const MODES = ["light", "dark", "auto"];
|
|
870
1129
|
function applyTheme(root, theme) {
|
|
871
1130
|
const host = root.host;
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
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
|
-
}
|
|
882
|
-
}
|
|
1131
|
+
const mode = themeMode(theme);
|
|
1132
|
+
if (mode) host.dataset.ccMode = mode;
|
|
1133
|
+
else delete host.dataset.ccMode;
|
|
1134
|
+
if (theme.accent) setAccent(host, "", theme.accent);
|
|
1135
|
+
if (theme.accentDark) setAccent(host, "-dark", theme.accentDark);
|
|
883
1136
|
if (theme.textColor) {
|
|
884
1137
|
host.style.setProperty("--cc-text", theme.textColor);
|
|
885
1138
|
}
|
|
@@ -896,6 +1149,23 @@ function applyTheme(root, theme) {
|
|
|
896
1149
|
if (radius) host.style.setProperty("--cc-radius", radius);
|
|
897
1150
|
}
|
|
898
1151
|
}
|
|
1152
|
+
function themeMode(theme) {
|
|
1153
|
+
if (theme.mode && MODES.includes(theme.mode)) return theme.mode;
|
|
1154
|
+
if (theme.bgColor) return isHex(theme.bgColor) && relativeLuminance(theme.bgColor) > 0.4 ? "light" : "dark";
|
|
1155
|
+
if (theme.textColor) return isHex(theme.textColor) && relativeLuminance(theme.textColor) < 0.4 ? "light" : "dark";
|
|
1156
|
+
return void 0;
|
|
1157
|
+
}
|
|
1158
|
+
function setAccent(host, suffix, accent) {
|
|
1159
|
+
host.style.setProperty(`--cc-theme-accent${suffix}`, accent);
|
|
1160
|
+
host.style.setProperty(`--cc-theme-accent${suffix}-hover`, darken(accent, 10));
|
|
1161
|
+
const pair = accentTextColors(accent);
|
|
1162
|
+
if (pair) {
|
|
1163
|
+
host.style.setProperty(`--cc-theme-on-accent${suffix}`, pair.onAccent);
|
|
1164
|
+
host.style.setProperty(`--cc-theme-accent${suffix}-contrast`, pair.background);
|
|
1165
|
+
} else {
|
|
1166
|
+
host.style.setProperty(`--cc-theme-accent${suffix}-contrast`, accent);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
899
1169
|
function toCssLength(value) {
|
|
900
1170
|
if (typeof value === "number" && !Number.isFinite(value)) return "";
|
|
901
1171
|
const text = String(value).trim();
|
|
@@ -977,188 +1247,20 @@ const en = {
|
|
|
977
1247
|
preChatRequired: "This field is required",
|
|
978
1248
|
preChatInvalidEmail: "Please enter a valid email",
|
|
979
1249
|
retryButton: "Retry",
|
|
980
|
-
regenerateButton: "Regenerate"
|
|
1250
|
+
regenerateButton: "Regenerate",
|
|
1251
|
+
suggestionsLabel: "Suggested questions",
|
|
1252
|
+
unreadCount: "{count} unread",
|
|
1253
|
+
sourcesLabel: "Sources",
|
|
1254
|
+
disclosure: "You're chatting with an AI assistant. It can make mistakes.",
|
|
1255
|
+
disclosureLearnMore: "Learn more"
|
|
981
1256
|
};
|
|
982
1257
|
const locales = { en };
|
|
983
1258
|
function getWidgetLocale(code, overrides) {
|
|
984
1259
|
const base = locales[code] ?? en;
|
|
985
1260
|
return overrides ? { ...base, ...overrides } : base;
|
|
986
1261
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
constructor(parent, options) {
|
|
990
|
-
this.fieldElements = /* @__PURE__ */ new Map();
|
|
991
|
-
this.errorElements = /* @__PURE__ */ new Map();
|
|
992
|
-
this.options = options;
|
|
993
|
-
this.container = document.createElement("div");
|
|
994
|
-
this.container.className = "cc-prechat";
|
|
995
|
-
if (options.title) {
|
|
996
|
-
const title = document.createElement("div");
|
|
997
|
-
title.className = "cc-prechat-title";
|
|
998
|
-
title.textContent = options.title;
|
|
999
|
-
this.container.appendChild(title);
|
|
1000
|
-
}
|
|
1001
|
-
if (options.subtitle) {
|
|
1002
|
-
const subtitle = document.createElement("div");
|
|
1003
|
-
subtitle.className = "cc-prechat-subtitle";
|
|
1004
|
-
subtitle.textContent = options.subtitle;
|
|
1005
|
-
this.container.appendChild(subtitle);
|
|
1006
|
-
}
|
|
1007
|
-
const form = document.createElement("form");
|
|
1008
|
-
form.className = "cc-prechat-fields";
|
|
1009
|
-
form.noValidate = true;
|
|
1010
|
-
form.addEventListener("submit", (e) => {
|
|
1011
|
-
e.preventDefault();
|
|
1012
|
-
this.handleSubmit();
|
|
1013
|
-
});
|
|
1014
|
-
for (const field of options.fields) {
|
|
1015
|
-
form.appendChild(this.createField(field));
|
|
1016
|
-
}
|
|
1017
|
-
this.submitBtn = document.createElement("button");
|
|
1018
|
-
this.submitBtn.type = "submit";
|
|
1019
|
-
this.submitBtn.className = "cc-prechat-submit";
|
|
1020
|
-
this.submitBtn.textContent = options.submitLabel;
|
|
1021
|
-
form.appendChild(this.submitBtn);
|
|
1022
|
-
this.container.appendChild(form);
|
|
1023
|
-
parent.appendChild(this.container);
|
|
1024
|
-
this.updateSubmitState();
|
|
1025
|
-
const firstInput = this.fieldElements.values().next().value;
|
|
1026
|
-
if (firstInput) {
|
|
1027
|
-
setTimeout(() => firstInput.focus(), 100);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
createField(field) {
|
|
1031
|
-
const wrapper = document.createElement("div");
|
|
1032
|
-
wrapper.className = "cc-prechat-field";
|
|
1033
|
-
const inputId = `cc-prechat-${field.name}`;
|
|
1034
|
-
const errorId = `cc-prechat-${field.name}-error`;
|
|
1035
|
-
const label = document.createElement("label");
|
|
1036
|
-
label.className = "cc-prechat-label";
|
|
1037
|
-
label.htmlFor = inputId;
|
|
1038
|
-
label.textContent = field.label;
|
|
1039
|
-
if (field.required) {
|
|
1040
|
-
const req = document.createElement("span");
|
|
1041
|
-
req.className = "cc-required";
|
|
1042
|
-
req.textContent = "*";
|
|
1043
|
-
label.appendChild(req);
|
|
1044
|
-
}
|
|
1045
|
-
wrapper.appendChild(label);
|
|
1046
|
-
let input;
|
|
1047
|
-
if (field.type === "select") {
|
|
1048
|
-
const select = document.createElement("select");
|
|
1049
|
-
select.className = "cc-prechat-select";
|
|
1050
|
-
const defaultOpt = document.createElement("option");
|
|
1051
|
-
defaultOpt.value = "";
|
|
1052
|
-
defaultOpt.textContent = field.placeholder ?? this.options.locale.selectPlaceholder.replace("{label}", field.label.toLowerCase());
|
|
1053
|
-
defaultOpt.disabled = true;
|
|
1054
|
-
defaultOpt.selected = true;
|
|
1055
|
-
select.appendChild(defaultOpt);
|
|
1056
|
-
for (const opt of field.options ?? []) {
|
|
1057
|
-
const option = document.createElement("option");
|
|
1058
|
-
option.value = opt;
|
|
1059
|
-
option.textContent = opt;
|
|
1060
|
-
select.appendChild(option);
|
|
1061
|
-
}
|
|
1062
|
-
input = select;
|
|
1063
|
-
} else if (field.type === "textarea") {
|
|
1064
|
-
const textarea = document.createElement("textarea");
|
|
1065
|
-
textarea.className = "cc-prechat-textarea";
|
|
1066
|
-
textarea.placeholder = field.placeholder ?? "";
|
|
1067
|
-
textarea.rows = 3;
|
|
1068
|
-
textarea.maxLength = MAX_PRECHAT_FIELD_LENGTH;
|
|
1069
|
-
input = textarea;
|
|
1070
|
-
} else {
|
|
1071
|
-
const textInput = document.createElement("input");
|
|
1072
|
-
textInput.type = field.type;
|
|
1073
|
-
textInput.className = "cc-prechat-input";
|
|
1074
|
-
textInput.maxLength = MAX_PRECHAT_FIELD_LENGTH;
|
|
1075
|
-
textInput.placeholder = field.placeholder ?? "";
|
|
1076
|
-
input = textInput;
|
|
1077
|
-
}
|
|
1078
|
-
input.name = field.name;
|
|
1079
|
-
input.id = inputId;
|
|
1080
|
-
if (field.required) {
|
|
1081
|
-
input.setAttribute("aria-required", "true");
|
|
1082
|
-
}
|
|
1083
|
-
input.addEventListener("input", () => {
|
|
1084
|
-
this.clearError(field.name);
|
|
1085
|
-
this.updateSubmitState();
|
|
1086
|
-
});
|
|
1087
|
-
if (input instanceof HTMLSelectElement) {
|
|
1088
|
-
input.addEventListener("change", () => {
|
|
1089
|
-
this.clearError(field.name);
|
|
1090
|
-
this.updateSubmitState();
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
wrapper.appendChild(input);
|
|
1094
|
-
this.fieldElements.set(field.name, input);
|
|
1095
|
-
const error = document.createElement("div");
|
|
1096
|
-
error.className = "cc-prechat-error";
|
|
1097
|
-
error.id = errorId;
|
|
1098
|
-
wrapper.appendChild(error);
|
|
1099
|
-
this.errorElements.set(field.name, error);
|
|
1100
|
-
return wrapper;
|
|
1101
|
-
}
|
|
1102
|
-
updateSubmitState() {
|
|
1103
|
-
const allRequiredFilled = this.options.fields.every((field) => {
|
|
1104
|
-
if (!field.required) return true;
|
|
1105
|
-
const el = this.fieldElements.get(field.name);
|
|
1106
|
-
return el ? el.value.trim().length > 0 : false;
|
|
1107
|
-
});
|
|
1108
|
-
this.submitBtn.disabled = !allRequiredFilled;
|
|
1109
|
-
}
|
|
1110
|
-
handleSubmit() {
|
|
1111
|
-
const data = {};
|
|
1112
|
-
let valid = true;
|
|
1113
|
-
for (const field of this.options.fields) {
|
|
1114
|
-
const el = this.fieldElements.get(field.name);
|
|
1115
|
-
if (!el) continue;
|
|
1116
|
-
const value = el.value.trim();
|
|
1117
|
-
data[field.name] = value;
|
|
1118
|
-
if (field.required && !value) {
|
|
1119
|
-
this.showError(field.name, this.options.locale.preChatRequired);
|
|
1120
|
-
valid = false;
|
|
1121
|
-
continue;
|
|
1122
|
-
}
|
|
1123
|
-
if (field.type === "email" && value && !this.isValidEmail(value)) {
|
|
1124
|
-
this.showError(field.name, this.options.locale.preChatInvalidEmail);
|
|
1125
|
-
valid = false;
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
if (valid) {
|
|
1129
|
-
this.options.onSubmit(data);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
showError(fieldName, message) {
|
|
1133
|
-
const el = this.errorElements.get(fieldName);
|
|
1134
|
-
if (el) el.textContent = message;
|
|
1135
|
-
const fieldEl = this.fieldElements.get(fieldName);
|
|
1136
|
-
if (fieldEl && el) {
|
|
1137
|
-
fieldEl.setAttribute("aria-invalid", "true");
|
|
1138
|
-
fieldEl.setAttribute("aria-describedby", el.id);
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
clearError(fieldName) {
|
|
1142
|
-
const el = this.errorElements.get(fieldName);
|
|
1143
|
-
if (el) el.textContent = "";
|
|
1144
|
-
const fieldEl = this.fieldElements.get(fieldName);
|
|
1145
|
-
if (fieldEl && el) {
|
|
1146
|
-
if (fieldEl.getAttribute("aria-describedby") === el.id) {
|
|
1147
|
-
fieldEl.removeAttribute("aria-describedby");
|
|
1148
|
-
}
|
|
1149
|
-
if (fieldEl.getAttribute("aria-invalid") === "true") {
|
|
1150
|
-
fieldEl.removeAttribute("aria-invalid");
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
isValidEmail(email) {
|
|
1155
|
-
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
1156
|
-
}
|
|
1157
|
-
destroy() {
|
|
1158
|
-
this.container.remove();
|
|
1159
|
-
this.fieldElements.clear();
|
|
1160
|
-
this.errorElements.clear();
|
|
1161
|
-
}
|
|
1262
|
+
function registerWidgetLocale(code, strings) {
|
|
1263
|
+
locales[code] = { ...en, ...strings };
|
|
1162
1264
|
}
|
|
1163
1265
|
const ERROR_MESSAGES = {
|
|
1164
1266
|
network: "errorNetwork",
|
|
@@ -1169,25 +1271,44 @@ const ERROR_MESSAGES = {
|
|
|
1169
1271
|
unavailable: "errorUnavailable",
|
|
1170
1272
|
cut_off: "errorCutOff"
|
|
1171
1273
|
};
|
|
1274
|
+
let preChatChunk;
|
|
1275
|
+
function loadPreChatForm() {
|
|
1276
|
+
preChatChunk ?? (preChatChunk = Promise.resolve().then(() => prechatForm).catch((error) => {
|
|
1277
|
+
preChatChunk = void 0;
|
|
1278
|
+
throw error;
|
|
1279
|
+
}));
|
|
1280
|
+
return preChatChunk;
|
|
1281
|
+
}
|
|
1282
|
+
function prefetch(config) {
|
|
1283
|
+
var _a;
|
|
1284
|
+
if ((_a = config.preChatForm) == null ? void 0 : _a.enabled) loadPreChatForm().catch(() => void 0);
|
|
1285
|
+
}
|
|
1172
1286
|
class Widget {
|
|
1173
1287
|
constructor(config) {
|
|
1174
1288
|
this.mounted = false;
|
|
1289
|
+
this.conversationId = crypto.randomUUID();
|
|
1290
|
+
this.preChatDone = false;
|
|
1291
|
+
this.hydrated = false;
|
|
1175
1292
|
this.messages = [];
|
|
1176
1293
|
this.isStreaming = false;
|
|
1177
1294
|
this.v1 = false;
|
|
1178
1295
|
this.destroyed = false;
|
|
1296
|
+
this.generation = 0;
|
|
1297
|
+
this.userFromSite = false;
|
|
1179
1298
|
this.retryRequests = /* @__PURE__ */ new Map();
|
|
1180
1299
|
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
1300
|
+
this.unreadReplies = 0;
|
|
1301
|
+
if ("persistHistory" in config) throw new Error("ChatCops: persistHistory was removed in 1.0. Use persistence: 'none'.");
|
|
1181
1302
|
this.config = config;
|
|
1182
|
-
this.client = new ChatClient({ apiUrl: config.apiUrl, timeout: config.timeout, turnstileSiteKey: config.turnstileSiteKey });
|
|
1183
1303
|
this.namespace = claimNamespace(
|
|
1184
1304
|
config.siteId,
|
|
1185
1305
|
config.mode ?? "popup",
|
|
1186
1306
|
() => !this.destroyed && (!this.mounted || this.shadow.host.isConnected)
|
|
1187
1307
|
);
|
|
1188
|
-
|
|
1308
|
+
const persistence = config.persistence ?? "local";
|
|
1309
|
+
this.storage = new ConversationStorage(this.namespace.siteId, persistence, config.historyTtlDays);
|
|
1310
|
+
this.client = new ChatClient({ ...config, store: this.storage.store });
|
|
1189
1311
|
this.locale = getWidgetLocale(config.locale ?? "en", config.strings);
|
|
1190
|
-
this.conversationId = this.storage.getSessionId();
|
|
1191
1312
|
}
|
|
1192
1313
|
get isInline() {
|
|
1193
1314
|
return this.config.mode === "inline";
|
|
@@ -1206,12 +1327,13 @@ class Widget {
|
|
|
1206
1327
|
return this.config.container;
|
|
1207
1328
|
}
|
|
1208
1329
|
init() {
|
|
1209
|
-
var _a, _b
|
|
1330
|
+
var _a, _b;
|
|
1331
|
+
prefetch(this.config);
|
|
1210
1332
|
if (this.isInline) {
|
|
1211
1333
|
this.containerEl = this.resolveContainer();
|
|
1212
|
-
this.shadow = createShadowRoot(this.containerEl);
|
|
1334
|
+
this.shadow = createShadowRoot(this.containerEl, { dir: this.config.dir, locale: this.config.locale });
|
|
1213
1335
|
} else {
|
|
1214
|
-
this.shadow = createShadowRoot();
|
|
1336
|
+
this.shadow = createShadowRoot(void 0, { dir: this.config.dir, locale: this.config.locale });
|
|
1215
1337
|
}
|
|
1216
1338
|
this.mounted = true;
|
|
1217
1339
|
if (this.config.theme) {
|
|
@@ -1223,6 +1345,7 @@ class Widget {
|
|
|
1223
1345
|
position,
|
|
1224
1346
|
openLabel: this.locale.openChat,
|
|
1225
1347
|
closeLabel: this.locale.closeButton,
|
|
1348
|
+
unreadLabel: this.locale.unreadCount,
|
|
1226
1349
|
onClick: () => this.toggle()
|
|
1227
1350
|
});
|
|
1228
1351
|
}
|
|
@@ -1230,15 +1353,7 @@ class Widget {
|
|
|
1230
1353
|
position,
|
|
1231
1354
|
inline: this.isInline,
|
|
1232
1355
|
preChatEnabled: ((_b = this.config.preChatForm) == null ? void 0 : _b.enabled) ?? false,
|
|
1233
|
-
|
|
1234
|
-
name: ((_c = this.config.branding) == null ? void 0 : _c.name) ?? this.locale.defaultName,
|
|
1235
|
-
avatar: (_d = this.config.branding) == null ? void 0 : _d.avatar,
|
|
1236
|
-
subtitle: ((_e = this.config.branding) == null ? void 0 : _e.subtitle) ?? this.locale.defaultSubtitle
|
|
1237
|
-
},
|
|
1238
|
-
placeholder: this.config.placeholder ?? this.locale.inputPlaceholder,
|
|
1239
|
-
footerText: this.locale.poweredBy,
|
|
1240
|
-
closeLabel: this.locale.closeButton,
|
|
1241
|
-
sendLabel: this.locale.sendButton,
|
|
1356
|
+
...this.panelLabels(),
|
|
1242
1357
|
messageActions: {
|
|
1243
1358
|
onRetry: (messageId) => {
|
|
1244
1359
|
void this.handleRetry(messageId);
|
|
@@ -1246,41 +1361,24 @@ class Widget {
|
|
|
1246
1361
|
onRegenerate: (messageId) => {
|
|
1247
1362
|
void this.handleRegenerate(messageId);
|
|
1248
1363
|
},
|
|
1249
|
-
|
|
1250
|
-
regenerateLabel: this.locale.regenerateButton,
|
|
1251
|
-
typingLabel: this.locale.typingIndicator
|
|
1364
|
+
...this.actionLabels()
|
|
1252
1365
|
},
|
|
1253
1366
|
onSend: (text) => this.handleSend(text),
|
|
1254
1367
|
onClose: () => this.close()
|
|
1255
1368
|
});
|
|
1256
|
-
if (this.config.
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
for (const msg of saved.messages) {
|
|
1261
|
-
this.messages.push(msg);
|
|
1262
|
-
this.panel.addMessage(msg);
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
if ((_f = this.config.preChatForm) == null ? void 0 : _f.enabled) {
|
|
1267
|
-
const savedUserData = this.storage.getPreChatData(this.conversationId);
|
|
1268
|
-
if (savedUserData) {
|
|
1269
|
-
this.userData = savedUserData;
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
const shouldShowPreChat = ((_g = this.config.preChatForm) == null ? void 0 : _g.enabled) && !this.isPreChatCompleted() && this.messages.length === 0;
|
|
1273
|
-
if (shouldShowPreChat) {
|
|
1274
|
-
this.showPreChatForm();
|
|
1275
|
-
} else {
|
|
1276
|
-
this.showChatView();
|
|
1369
|
+
if (this.config.disclosure === false) {
|
|
1370
|
+
console.info(
|
|
1371
|
+
"[chatcops] The AI disclosure is off (disclosure: false). Under the EU AI Act (Article 50), telling visitors they're talking to an AI is then up to you."
|
|
1372
|
+
);
|
|
1277
1373
|
}
|
|
1278
|
-
this.
|
|
1374
|
+
this.renderDisclosure();
|
|
1375
|
+
if (this.isInline) this.hydrate();
|
|
1279
1376
|
if (!this.isInline && this.config.welcomeBubble) {
|
|
1280
1377
|
this.bubble = new WelcomeBubble(this.shadow, {
|
|
1281
1378
|
text: this.config.welcomeBubble.text,
|
|
1282
1379
|
delay: this.config.welcomeBubble.delay,
|
|
1283
1380
|
showOnce: this.config.welcomeBubble.showOnce ?? true,
|
|
1381
|
+
store: this.storage.store,
|
|
1284
1382
|
position,
|
|
1285
1383
|
dismissLabel: this.locale.dismissBubble,
|
|
1286
1384
|
onClick: () => this.open()
|
|
@@ -1294,15 +1392,39 @@ class Widget {
|
|
|
1294
1392
|
this.autoOpenTimer = setTimeout(() => this.open(), delay);
|
|
1295
1393
|
}
|
|
1296
1394
|
}
|
|
1395
|
+
/** Reads the saved conversation and shows it, or the pre-chat form. Runs once. */
|
|
1396
|
+
hydrate() {
|
|
1397
|
+
var _a, _b;
|
|
1398
|
+
if (this.hydrated) return;
|
|
1399
|
+
this.hydrated = true;
|
|
1400
|
+
const saved = this.storage.load();
|
|
1401
|
+
if (saved) {
|
|
1402
|
+
this.conversationId = saved.id;
|
|
1403
|
+
this.preChatDone = saved.preChatDone ?? false;
|
|
1404
|
+
if ((_a = this.config.preChatForm) == null ? void 0 : _a.enabled) this.userData = saved.userData;
|
|
1405
|
+
for (const msg of saved.messages) {
|
|
1406
|
+
this.messages.push(msg);
|
|
1407
|
+
this.panel.addMessage(msg);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
if (((_b = this.config.preChatForm) == null ? void 0 : _b.enabled) && !this.preChatDone && this.messages.length === 0) {
|
|
1411
|
+
this.showPreChatForm();
|
|
1412
|
+
} else {
|
|
1413
|
+
this.showChatView();
|
|
1414
|
+
}
|
|
1415
|
+
this.showLatestRegenerateButton();
|
|
1416
|
+
}
|
|
1297
1417
|
open() {
|
|
1298
|
-
var _a, _b, _c, _d
|
|
1418
|
+
var _a, _b, _c, _d;
|
|
1419
|
+
this.unreadReplies = 0;
|
|
1420
|
+
this.hydrate();
|
|
1299
1421
|
this.panel.show();
|
|
1300
1422
|
if (!this.isInline) {
|
|
1301
1423
|
(_a = this.fab) == null ? void 0 : _a.setOpen(true);
|
|
1302
|
-
(_b = this.
|
|
1303
|
-
(_c = this.bubble) == null ? void 0 : _c.hide();
|
|
1424
|
+
(_b = this.bubble) == null ? void 0 : _b.hide();
|
|
1304
1425
|
}
|
|
1305
|
-
|
|
1426
|
+
this.lockScroll();
|
|
1427
|
+
(_d = (_c = this.config).onOpen) == null ? void 0 : _d.call(_c);
|
|
1306
1428
|
this.emit("open");
|
|
1307
1429
|
}
|
|
1308
1430
|
close() {
|
|
@@ -1313,6 +1435,7 @@ class Widget {
|
|
|
1313
1435
|
(_a = this.fab) == null ? void 0 : _a.setOpen(false);
|
|
1314
1436
|
if (hadFocus) (_b = this.fab) == null ? void 0 : _b.focus();
|
|
1315
1437
|
}
|
|
1438
|
+
this.restoreScroll();
|
|
1316
1439
|
(_d = (_c = this.config).onClose) == null ? void 0 : _d.call(_c);
|
|
1317
1440
|
this.emit("close");
|
|
1318
1441
|
}
|
|
@@ -1323,12 +1446,157 @@ class Widget {
|
|
|
1323
1446
|
this.open();
|
|
1324
1447
|
}
|
|
1325
1448
|
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Sends `text` as the visitor, as if they typed it, and opens the chat
|
|
1451
|
+
* first unless `open: false`. Resolves when the reply has finished. A
|
|
1452
|
+
* message sent while a reply is still coming is ignored, as in the input.
|
|
1453
|
+
*/
|
|
1454
|
+
async sendMessage(text, options = {}) {
|
|
1455
|
+
if (options.open === false) this.hydrate();
|
|
1456
|
+
else this.open();
|
|
1457
|
+
await this.handleSend(text);
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Starts a fresh conversation: a new id, and the log back to the welcome
|
|
1461
|
+
* message. A reply that's still coming is stopped. Pre-chat answers stay.
|
|
1462
|
+
*/
|
|
1463
|
+
newConversation() {
|
|
1464
|
+
this.reset(false);
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Like newConversation(), and forgets what this browser saved: the
|
|
1468
|
+
* transcript and the pre-chat answers, so the form shows again.
|
|
1469
|
+
*/
|
|
1470
|
+
clear() {
|
|
1471
|
+
this.reset(true);
|
|
1472
|
+
}
|
|
1473
|
+
reset(forget) {
|
|
1474
|
+
var _a, _b, _c;
|
|
1475
|
+
this.generation++;
|
|
1476
|
+
(_a = this.abortController) == null ? void 0 : _a.abort();
|
|
1477
|
+
this.abortController = void 0;
|
|
1478
|
+
this.isStreaming = false;
|
|
1479
|
+
this.retryRequests.clear();
|
|
1480
|
+
this.messages = [];
|
|
1481
|
+
this.conversationId = crypto.randomUUID();
|
|
1482
|
+
this.hydrated = true;
|
|
1483
|
+
if (forget) {
|
|
1484
|
+
this.userData = void 0;
|
|
1485
|
+
this.preChatDone = false;
|
|
1486
|
+
this.storage.clear();
|
|
1487
|
+
}
|
|
1488
|
+
(_b = this.preChatFormEl) == null ? void 0 : _b.destroy();
|
|
1489
|
+
this.preChatFormEl = void 0;
|
|
1490
|
+
this.panel.messages.clear();
|
|
1491
|
+
this.panel.input.setDisabled(false);
|
|
1492
|
+
if (((_c = this.config.preChatForm) == null ? void 0 : _c.enabled) && !this.preChatDone) this.showPreChatForm();
|
|
1493
|
+
else this.showChatView();
|
|
1494
|
+
if (!forget) this.saveHistory();
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* The signed-in visitor. `token` is the identity token for the server
|
|
1498
|
+
* (signed-in visitors); `name` and `email` are sent as visitor details, and
|
|
1499
|
+
* the name greets them. Switching to another user, or signing out with
|
|
1500
|
+
* `null`, clears the chat, so one person's conversation never shows for
|
|
1501
|
+
* the next.
|
|
1502
|
+
*/
|
|
1503
|
+
setUser(user) {
|
|
1504
|
+
const previous = this.client.identity;
|
|
1505
|
+
const token = user == null ? void 0 : user.token;
|
|
1506
|
+
if (previous !== void 0 && previous !== token) this.clear();
|
|
1507
|
+
this.client.setUserToken(token);
|
|
1508
|
+
if (!user) return;
|
|
1509
|
+
const details = Object.fromEntries(
|
|
1510
|
+
Object.entries({ name: user.name, email: user.email }).filter((entry) => typeof entry[1] === "string" && entry[1] !== "")
|
|
1511
|
+
);
|
|
1512
|
+
if (Object.keys(details).length) {
|
|
1513
|
+
this.userData = { ...this.userData, ...details };
|
|
1514
|
+
this.userFromSite = true;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Details about the page or the visitor that the bot should know for the
|
|
1519
|
+
* next messages, such as `{ plan: 'Team' }`. Sent with each message as
|
|
1520
|
+
* page context; `null` removes them. Ignored when `pageContext: false`.
|
|
1521
|
+
*/
|
|
1522
|
+
setContext(data) {
|
|
1523
|
+
this.pageData = data && Object.keys(data).length ? { ...data } : void 0;
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Changes the look or wording of the running chat without loading it
|
|
1527
|
+
* again: the theme (not `position`), branding, strings, placeholder,
|
|
1528
|
+
* disclosure, welcome message and suggestions. What you leave out stays
|
|
1529
|
+
* as it is. A conversation that has only the welcome message shows the
|
|
1530
|
+
* new one; otherwise the welcome message and suggestions are for the
|
|
1531
|
+
* next conversation.
|
|
1532
|
+
*/
|
|
1533
|
+
update(changes) {
|
|
1534
|
+
var _a, _b;
|
|
1535
|
+
const current = this.config;
|
|
1536
|
+
const theme = { ...current.theme, ...changes.theme, position: (_a = current.theme) == null ? void 0 : _a.position };
|
|
1537
|
+
this.config = {
|
|
1538
|
+
...current,
|
|
1539
|
+
...changes,
|
|
1540
|
+
theme,
|
|
1541
|
+
branding: { ...current.branding, ...changes.branding },
|
|
1542
|
+
strings: { ...current.strings, ...changes.strings }
|
|
1543
|
+
};
|
|
1544
|
+
this.locale = getWidgetLocale(this.config.locale ?? "en", this.config.strings);
|
|
1545
|
+
if (!this.mounted) return;
|
|
1546
|
+
if (changes.theme) applyTheme(this.shadow, theme);
|
|
1547
|
+
const l = this.locale;
|
|
1548
|
+
(_b = this.fab) == null ? void 0 : _b.setLabels(l.openChat, l.closeButton, l.unreadCount);
|
|
1549
|
+
this.panel.update(this.panelLabels(), this.actionLabels());
|
|
1550
|
+
this.renderDisclosure();
|
|
1551
|
+
if (this.messages.length === 1 && this.messages[0].id === "welcome") {
|
|
1552
|
+
this.messages = [];
|
|
1553
|
+
this.panel.messages.clear();
|
|
1554
|
+
this.showChatView();
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
panelLabels() {
|
|
1558
|
+
const { branding, placeholder } = this.config;
|
|
1559
|
+
const l = this.locale;
|
|
1560
|
+
return {
|
|
1561
|
+
branding: {
|
|
1562
|
+
name: (branding == null ? void 0 : branding.name) ?? l.defaultName,
|
|
1563
|
+
avatar: branding == null ? void 0 : branding.avatar,
|
|
1564
|
+
subtitle: (branding == null ? void 0 : branding.subtitle) ?? l.defaultSubtitle
|
|
1565
|
+
},
|
|
1566
|
+
placeholder: placeholder ?? l.inputPlaceholder,
|
|
1567
|
+
footerText: l.poweredBy,
|
|
1568
|
+
closeLabel: l.closeButton,
|
|
1569
|
+
sendLabel: l.sendButton
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
actionLabels() {
|
|
1573
|
+
const l = this.locale;
|
|
1574
|
+
return {
|
|
1575
|
+
retryLabel: l.retryButton,
|
|
1576
|
+
regenerateLabel: l.regenerateButton,
|
|
1577
|
+
typingLabel: l.typingIndicator,
|
|
1578
|
+
sourcesLabel: l.sourcesLabel
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
renderDisclosure() {
|
|
1582
|
+
const { disclosure } = this.config;
|
|
1583
|
+
if (disclosure === false) this.panel.messages.setDisclosure();
|
|
1584
|
+
else
|
|
1585
|
+
this.panel.messages.setDisclosure(
|
|
1586
|
+
(disclosure == null ? void 0 : disclosure.text) ?? this.locale.disclosure,
|
|
1587
|
+
(disclosure == null ? void 0 : disclosure.learnMoreUrl) ? { url: disclosure.learnMoreUrl, label: this.locale.disclosureLearnMore } : void 0
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
isStale(generation) {
|
|
1591
|
+
return this.destroyed || generation !== this.generation;
|
|
1592
|
+
}
|
|
1326
1593
|
destroy() {
|
|
1327
1594
|
var _a, _b, _c, _d;
|
|
1328
1595
|
this.destroyed = true;
|
|
1329
1596
|
if (this.autoOpenTimer !== void 0) clearTimeout(this.autoOpenTimer);
|
|
1330
1597
|
(_a = this.abortController) == null ? void 0 : _a.abort();
|
|
1331
1598
|
this.abortController = void 0;
|
|
1599
|
+
this.restoreScroll();
|
|
1332
1600
|
(_b = this.preChatFormEl) == null ? void 0 : _b.destroy();
|
|
1333
1601
|
(_c = this.bubble) == null ? void 0 : _c.destroy();
|
|
1334
1602
|
this.panel.destroy();
|
|
@@ -1339,6 +1607,28 @@ class Widget {
|
|
|
1339
1607
|
this.messages = [];
|
|
1340
1608
|
this.userData = void 0;
|
|
1341
1609
|
}
|
|
1610
|
+
lockScroll() {
|
|
1611
|
+
var _a, _b;
|
|
1612
|
+
if (this.savedOverflow !== void 0 || this.isInline) return;
|
|
1613
|
+
if (typeof window !== "undefined" && ((_b = (_a = window.matchMedia) == null ? void 0 : _a.call(window, "(max-width: 640px)")) == null ? void 0 : _b.matches)) {
|
|
1614
|
+
const root = document.documentElement;
|
|
1615
|
+
this.savedOverflow = {
|
|
1616
|
+
value: root.style.getPropertyValue("overflow"),
|
|
1617
|
+
priority: root.style.getPropertyPriority("overflow")
|
|
1618
|
+
};
|
|
1619
|
+
root.style.setProperty("overflow", "hidden");
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
restoreScroll() {
|
|
1623
|
+
if (this.savedOverflow === void 0) return;
|
|
1624
|
+
const { value, priority } = this.savedOverflow;
|
|
1625
|
+
if (value) {
|
|
1626
|
+
document.documentElement.style.setProperty("overflow", value, priority);
|
|
1627
|
+
} else {
|
|
1628
|
+
document.documentElement.style.removeProperty("overflow");
|
|
1629
|
+
}
|
|
1630
|
+
this.savedOverflow = void 0;
|
|
1631
|
+
}
|
|
1342
1632
|
on(event, handler) {
|
|
1343
1633
|
if (!this.eventHandlers.has(event)) {
|
|
1344
1634
|
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
@@ -1357,30 +1647,39 @@ class Widget {
|
|
|
1357
1647
|
h(...args);
|
|
1358
1648
|
});
|
|
1359
1649
|
}
|
|
1360
|
-
isPreChatCompleted() {
|
|
1361
|
-
return this.storage.isPreChatCompleted(this.conversationId);
|
|
1362
|
-
}
|
|
1363
|
-
setPreChatCompleted(data) {
|
|
1364
|
-
this.storage.setPreChatCompleted(this.conversationId, data);
|
|
1365
|
-
}
|
|
1366
1650
|
showPreChatForm() {
|
|
1367
1651
|
const formConfig = this.config.preChatForm;
|
|
1368
1652
|
this.panel.hideMessages();
|
|
1369
1653
|
this.panel.input.setVisible(false);
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1654
|
+
loadPreChatForm().then(
|
|
1655
|
+
({ PreChatForm: PreChatForm2 }) => {
|
|
1656
|
+
if (this.destroyed || this.preChatDone || this.preChatFormEl) return;
|
|
1657
|
+
this.preChatFormEl = new PreChatForm2(this.panel.messagesContainer, {
|
|
1658
|
+
title: formConfig.title ?? this.locale.preChatTitle,
|
|
1659
|
+
subtitle: formConfig.subtitle ?? this.locale.preChatSubtitle,
|
|
1660
|
+
fields: formConfig.fields,
|
|
1661
|
+
submitLabel: formConfig.submitLabel ?? this.locale.preChatSubmit,
|
|
1662
|
+
locale: this.locale,
|
|
1663
|
+
onSubmit: (data) => this.handlePreChatSubmit(data)
|
|
1664
|
+
});
|
|
1665
|
+
},
|
|
1666
|
+
(error) => {
|
|
1667
|
+
console.error("[chatcops] The pre-chat form could not load:", error instanceof Error ? error.message : error);
|
|
1668
|
+
if (this.destroyed || this.preChatDone) return;
|
|
1669
|
+
this.preChatDone = true;
|
|
1670
|
+
this.panel.showMessages();
|
|
1671
|
+
this.panel.input.setVisible(true);
|
|
1672
|
+
this.showChatView();
|
|
1673
|
+
}
|
|
1674
|
+
);
|
|
1378
1675
|
}
|
|
1379
1676
|
handlePreChatSubmit(data) {
|
|
1380
1677
|
var _a, _b, _c, _d;
|
|
1381
1678
|
if (!((_a = this.config.preChatForm) == null ? void 0 : _a.enabled)) return;
|
|
1679
|
+
this.hydrate();
|
|
1382
1680
|
this.userData = data;
|
|
1383
|
-
this.
|
|
1681
|
+
this.preChatDone = true;
|
|
1682
|
+
this.saveHistory();
|
|
1384
1683
|
(_b = this.preChatFormEl) == null ? void 0 : _b.destroy();
|
|
1385
1684
|
this.preChatFormEl = void 0;
|
|
1386
1685
|
this.panel.showMessages();
|
|
@@ -1405,11 +1704,23 @@ class Widget {
|
|
|
1405
1704
|
};
|
|
1406
1705
|
this.messages.push(welcomeMsg);
|
|
1407
1706
|
this.panel.addMessage(welcomeMsg);
|
|
1707
|
+
this.renderSuggestions();
|
|
1408
1708
|
}
|
|
1409
1709
|
}
|
|
1710
|
+
renderSuggestions(suggestions = this.config.suggestions ?? []) {
|
|
1711
|
+
if (this.messages.some((message) => message.role === "user")) return;
|
|
1712
|
+
this.panel.messages.showSuggestions(
|
|
1713
|
+
suggestions,
|
|
1714
|
+
this.locale.suggestionsLabel,
|
|
1715
|
+
(suggestion) => {
|
|
1716
|
+
void this.handleSend(suggestion);
|
|
1717
|
+
}
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1410
1720
|
async handleSend(text) {
|
|
1411
1721
|
var _a, _b;
|
|
1412
1722
|
if (this.isStreaming) return;
|
|
1723
|
+
this.hydrate();
|
|
1413
1724
|
const userMsg = {
|
|
1414
1725
|
id: crypto.randomUUID(),
|
|
1415
1726
|
role: "user",
|
|
@@ -1465,15 +1776,18 @@ class Widget {
|
|
|
1465
1776
|
async sendToServer(text, options = {}) {
|
|
1466
1777
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1467
1778
|
if (this.isStreaming) return;
|
|
1779
|
+
const generation = this.generation;
|
|
1468
1780
|
this.isStreaming = true;
|
|
1469
1781
|
this.panel.input.setDisabled(true);
|
|
1470
1782
|
this.panel.messages.clearRegenerateButtons();
|
|
1783
|
+
this.panel.messages.beginReply();
|
|
1471
1784
|
this.panel.messages.showTyping();
|
|
1472
1785
|
const assistantMsg = {
|
|
1473
1786
|
id: crypto.randomUUID(),
|
|
1474
1787
|
role: "assistant",
|
|
1475
1788
|
content: "",
|
|
1476
|
-
status: "streaming"
|
|
1789
|
+
status: "streaming",
|
|
1790
|
+
sources: []
|
|
1477
1791
|
};
|
|
1478
1792
|
let firstChunk = true;
|
|
1479
1793
|
let requestCompleted = false;
|
|
@@ -1485,7 +1799,7 @@ class Widget {
|
|
|
1485
1799
|
this.createRequest(text, options),
|
|
1486
1800
|
{ signal: abortController.signal }
|
|
1487
1801
|
)) {
|
|
1488
|
-
if (this.
|
|
1802
|
+
if (this.isStale(generation)) break;
|
|
1489
1803
|
if (chunk.error) {
|
|
1490
1804
|
const errorType = this.resolveErrorType(chunk.error);
|
|
1491
1805
|
this.markFailed(assistantMsg, this.getErrorMessage(errorType, chunk.retryAfterMs));
|
|
@@ -1520,6 +1834,10 @@ class Widget {
|
|
|
1520
1834
|
(_d = (_c = this.config).onLeadCaptured) == null ? void 0 : _d.call(_c, chunk.leadData);
|
|
1521
1835
|
this.emit("leadCaptured", chunk.leadData);
|
|
1522
1836
|
}
|
|
1837
|
+
if (chunk.source) {
|
|
1838
|
+
assistantMsg.sources ?? (assistantMsg.sources = []);
|
|
1839
|
+
assistantMsg.sources.push(chunk.source);
|
|
1840
|
+
}
|
|
1523
1841
|
if (chunk.content) {
|
|
1524
1842
|
if (firstChunk) {
|
|
1525
1843
|
this.panel.messages.removeTyping();
|
|
@@ -1530,7 +1848,7 @@ class Widget {
|
|
|
1530
1848
|
this.panel.messages.updateMessage(assistantMsg);
|
|
1531
1849
|
}
|
|
1532
1850
|
}
|
|
1533
|
-
if (this.
|
|
1851
|
+
if (this.isStale(generation)) return;
|
|
1534
1852
|
if (!hasError && !requestCompleted) {
|
|
1535
1853
|
requestCompleted = true;
|
|
1536
1854
|
}
|
|
@@ -1545,11 +1863,12 @@ class Widget {
|
|
|
1545
1863
|
this.emit("message", assistantMsg);
|
|
1546
1864
|
this.showLatestRegenerateButton();
|
|
1547
1865
|
if (!this.isInline && !this.panel.isVisible) {
|
|
1548
|
-
|
|
1866
|
+
this.unreadReplies++;
|
|
1867
|
+
(_g = this.fab) == null ? void 0 : _g.showBadge(this.unreadReplies);
|
|
1549
1868
|
}
|
|
1550
1869
|
}
|
|
1551
1870
|
} catch (err) {
|
|
1552
|
-
if (this.
|
|
1871
|
+
if (this.isStale(generation)) return;
|
|
1553
1872
|
this.markFailed(assistantMsg, this.locale.errorGeneric);
|
|
1554
1873
|
assistantMsg.errorType = "network";
|
|
1555
1874
|
this.retryRequests.set(assistantMsg.id, {
|
|
@@ -1570,7 +1889,8 @@ class Widget {
|
|
|
1570
1889
|
if (this.abortController === abortController) {
|
|
1571
1890
|
this.abortController = void 0;
|
|
1572
1891
|
}
|
|
1573
|
-
if (!this.
|
|
1892
|
+
if (!this.isStale(generation)) {
|
|
1893
|
+
this.panel.messages.finishReply(assistantMsg);
|
|
1574
1894
|
this.isStreaming = false;
|
|
1575
1895
|
this.panel.input.setDisabled(false);
|
|
1576
1896
|
this.panel.input.focus();
|
|
@@ -1580,8 +1900,11 @@ class Widget {
|
|
|
1580
1900
|
}
|
|
1581
1901
|
createRequest(text, options) {
|
|
1582
1902
|
var _a;
|
|
1583
|
-
const pageContext = this.config.pageContext !== false ? { url: window.location.href, title: document.title } : void 0;
|
|
1584
|
-
const includeUserData = Boolean(
|
|
1903
|
+
const pageContext = this.config.pageContext !== false ? { url: window.location.href, title: document.title, ...this.pageData ? { data: this.pageData } : {} } : void 0;
|
|
1904
|
+
const includeUserData = Boolean(
|
|
1905
|
+
(((_a = this.config.preChatForm) == null ? void 0 : _a.enabled) || this.userFromSite) && this.userData && (!this.v1 || this.userDataSentWith !== this.conversationId)
|
|
1906
|
+
);
|
|
1907
|
+
if (includeUserData) this.userDataSentWith = this.conversationId;
|
|
1585
1908
|
return {
|
|
1586
1909
|
conversationId: this.conversationId,
|
|
1587
1910
|
message: text,
|
|
@@ -1643,7 +1966,6 @@ class Widget {
|
|
|
1643
1966
|
return this.locale[ERROR_MESSAGES[errorType] ?? "errorGeneric"];
|
|
1644
1967
|
}
|
|
1645
1968
|
saveHistory() {
|
|
1646
|
-
if (this.config.persistHistory === false) return;
|
|
1647
1969
|
const maxMessages = this.config.maxMessages ?? 50;
|
|
1648
1970
|
const toSave = this.messages.filter((m) => m.id !== "welcome").slice(-maxMessages);
|
|
1649
1971
|
this.storage.save({
|
|
@@ -1654,14 +1976,15 @@ class Widget {
|
|
|
1654
1976
|
content: m.content,
|
|
1655
1977
|
status: m.status,
|
|
1656
1978
|
errorType: m.errorType,
|
|
1657
|
-
timestamp: Date.now()
|
|
1979
|
+
timestamp: Date.now(),
|
|
1980
|
+
...m.sources && m.sources.length > 0 ? { sources: m.sources } : {}
|
|
1658
1981
|
})),
|
|
1982
|
+
...this.userData ? { userData: this.userData } : {},
|
|
1983
|
+
...this.preChatDone ? { preChatDone: true } : {},
|
|
1659
1984
|
updatedAt: Date.now()
|
|
1660
1985
|
});
|
|
1661
1986
|
}
|
|
1662
1987
|
}
|
|
1663
|
-
let instance = null;
|
|
1664
|
-
const persistentHandlers = /* @__PURE__ */ new Map();
|
|
1665
1988
|
function parseAutoOpen(value) {
|
|
1666
1989
|
if (value === void 0) return void 0;
|
|
1667
1990
|
if (value === "" || value === "true") return true;
|
|
@@ -1669,26 +1992,109 @@ function parseAutoOpen(value) {
|
|
|
1669
1992
|
if (/^\d+$/.test(value)) return Number(value);
|
|
1670
1993
|
return void 0;
|
|
1671
1994
|
}
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1995
|
+
function configFromScript(script) {
|
|
1996
|
+
const apiUrl = script.dataset.apiUrl;
|
|
1997
|
+
if (!apiUrl) return void 0;
|
|
1998
|
+
const mode = script.dataset.mode;
|
|
1999
|
+
const container = script.dataset.container;
|
|
2000
|
+
return {
|
|
2001
|
+
apiUrl,
|
|
2002
|
+
turnstileSiteKey: script.dataset.turnstileSiteKey,
|
|
2003
|
+
userToken: script.dataset.userToken,
|
|
2004
|
+
mode: mode || void 0,
|
|
2005
|
+
container: container || void 0,
|
|
2006
|
+
theme: {
|
|
2007
|
+
mode: script.dataset.themeMode || void 0,
|
|
2008
|
+
accent: script.dataset.accent,
|
|
2009
|
+
accentDark: script.dataset.accentDark,
|
|
2010
|
+
textColor: script.dataset.textColor,
|
|
2011
|
+
bgColor: script.dataset.bgColor,
|
|
2012
|
+
fontFamily: script.dataset.fontFamily,
|
|
2013
|
+
borderRadius: script.dataset.borderRadius,
|
|
2014
|
+
position: script.dataset.position || void 0
|
|
2015
|
+
},
|
|
2016
|
+
branding: {
|
|
2017
|
+
name: script.dataset.brandName,
|
|
2018
|
+
avatar: script.dataset.brandAvatar,
|
|
2019
|
+
subtitle: script.dataset.brandSubtitle
|
|
2020
|
+
},
|
|
2021
|
+
welcomeMessage: script.dataset.welcomeMessage,
|
|
2022
|
+
welcomeBubble: script.dataset.welcomeBubble ? {
|
|
2023
|
+
text: script.dataset.welcomeBubble,
|
|
2024
|
+
delay: /^\d+$/.test(script.dataset.welcomeBubbleDelay ?? "") ? Number(script.dataset.welcomeBubbleDelay) : void 0
|
|
2025
|
+
} : void 0,
|
|
2026
|
+
autoOpen: parseAutoOpen(script.dataset.autoOpen),
|
|
2027
|
+
placeholder: script.dataset.placeholder,
|
|
2028
|
+
disclosure: script.dataset.disclosure === "false" ? false : { text: script.dataset.disclosureText, learnMoreUrl: script.dataset.disclosureUrl },
|
|
2029
|
+
locale: script.dataset.locale,
|
|
2030
|
+
dir: ["ltr", "rtl", "auto"].find((dir) => dir === script.dataset.dir),
|
|
2031
|
+
persistence: ["local", "session", "none"].find((mode2) => mode2 === script.dataset.persistence),
|
|
2032
|
+
historyTtlDays: /^\d+$/.test(script.dataset.historyTtlDays ?? "") ? Number(script.dataset.historyTtlDays) : void 0
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
const VERSION = "1.0.0-rc.0";
|
|
2036
|
+
let instance = null;
|
|
2037
|
+
const persistentHandlers = /* @__PURE__ */ new Map();
|
|
2038
|
+
const ChatCops = {
|
|
2039
|
+
init(config) {
|
|
2040
|
+
if (instance) {
|
|
2041
|
+
instance.destroy();
|
|
2042
|
+
}
|
|
2043
|
+
instance = new Widget(config);
|
|
2044
|
+
for (const [event, handlers] of persistentHandlers) {
|
|
2045
|
+
for (const handler of handlers) {
|
|
2046
|
+
instance.on(event, handler);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
instance.init();
|
|
2050
|
+
return instance;
|
|
2051
|
+
},
|
|
1686
2052
|
open() {
|
|
1687
2053
|
instance == null ? void 0 : instance.open();
|
|
1688
2054
|
},
|
|
1689
2055
|
close() {
|
|
1690
2056
|
instance == null ? void 0 : instance.close();
|
|
1691
2057
|
},
|
|
2058
|
+
toggle() {
|
|
2059
|
+
instance == null ? void 0 : instance.toggle();
|
|
2060
|
+
},
|
|
2061
|
+
/** Sends `text` as the visitor (opening the chat unless `open: false`); resolves when the reply is done. */
|
|
2062
|
+
sendMessage(text, options) {
|
|
2063
|
+
return (instance == null ? void 0 : instance.sendMessage(text, options)) ?? Promise.resolve();
|
|
2064
|
+
},
|
|
2065
|
+
/** A fresh conversation: new id, the log back to the welcome message. */
|
|
2066
|
+
newConversation() {
|
|
2067
|
+
instance == null ? void 0 : instance.newConversation();
|
|
2068
|
+
},
|
|
2069
|
+
/** A fresh conversation, and the saved transcript and pre-chat answers removed from this browser. */
|
|
2070
|
+
clear() {
|
|
2071
|
+
instance == null ? void 0 : instance.clear();
|
|
2072
|
+
},
|
|
2073
|
+
/**
|
|
2074
|
+
* Adds a language, or replaces one: its strings, with English for any it
|
|
2075
|
+
* leaves out, and `dir: 'rtl'` for right-to-left. Use the code as `locale`.
|
|
2076
|
+
*/
|
|
2077
|
+
registerLocale(code, strings, options) {
|
|
2078
|
+
registerWidgetLocale(code, strings);
|
|
2079
|
+
if (options == null ? void 0 : options.dir) setLanguageDirection(code, options.dir);
|
|
2080
|
+
},
|
|
2081
|
+
/**
|
|
2082
|
+
* The signed-in visitor: `token` for the server, `name` and `email` as
|
|
2083
|
+
* visitor details. Another user, or `null` to sign out, clears the chat.
|
|
2084
|
+
*/
|
|
2085
|
+
setUser(user) {
|
|
2086
|
+
instance == null ? void 0 : instance.setUser(user);
|
|
2087
|
+
},
|
|
2088
|
+
/** Details the bot should know for the next messages, such as `{ plan: 'Team' }`. `null` removes them. */
|
|
2089
|
+
setContext(data) {
|
|
2090
|
+
instance == null ? void 0 : instance.setContext(data);
|
|
2091
|
+
},
|
|
2092
|
+
/** Changes the theme, branding or wording of the running chat. What you leave out stays as it is. */
|
|
2093
|
+
update(changes) {
|
|
2094
|
+
instance == null ? void 0 : instance.update(changes);
|
|
2095
|
+
},
|
|
2096
|
+
/** The widget's version. */
|
|
2097
|
+
version: VERSION,
|
|
1692
2098
|
destroy() {
|
|
1693
2099
|
instance == null ? void 0 : instance.destroy();
|
|
1694
2100
|
instance = null;
|
|
@@ -1712,38 +2118,8 @@ const ChatCops = {
|
|
|
1712
2118
|
}
|
|
1713
2119
|
};
|
|
1714
2120
|
function autoInit(script) {
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
if (!apiUrl) return;
|
|
1718
|
-
const mode = script.dataset.mode;
|
|
1719
|
-
const container = script.dataset.container;
|
|
1720
|
-
ChatCops.init({
|
|
1721
|
-
apiUrl,
|
|
1722
|
-
turnstileSiteKey: script.dataset.turnstileSiteKey,
|
|
1723
|
-
mode: mode || void 0,
|
|
1724
|
-
container: container || void 0,
|
|
1725
|
-
theme: {
|
|
1726
|
-
accent: script.dataset.accent,
|
|
1727
|
-
textColor: script.dataset.textColor,
|
|
1728
|
-
bgColor: script.dataset.bgColor,
|
|
1729
|
-
fontFamily: script.dataset.fontFamily,
|
|
1730
|
-
borderRadius: script.dataset.borderRadius,
|
|
1731
|
-
position: script.dataset.position || void 0
|
|
1732
|
-
},
|
|
1733
|
-
branding: {
|
|
1734
|
-
name: script.dataset.brandName,
|
|
1735
|
-
avatar: script.dataset.brandAvatar,
|
|
1736
|
-
subtitle: script.dataset.brandSubtitle
|
|
1737
|
-
},
|
|
1738
|
-
welcomeMessage: script.dataset.welcomeMessage,
|
|
1739
|
-
welcomeBubble: script.dataset.welcomeBubble ? {
|
|
1740
|
-
text: script.dataset.welcomeBubble,
|
|
1741
|
-
delay: script.dataset.welcomeBubbleDelay ? parseInt(script.dataset.welcomeBubbleDelay, 10) : void 0
|
|
1742
|
-
} : void 0,
|
|
1743
|
-
autoOpen: parseAutoOpen(script.dataset.autoOpen),
|
|
1744
|
-
placeholder: script.dataset.placeholder,
|
|
1745
|
-
locale: script.dataset.locale
|
|
1746
|
-
});
|
|
2121
|
+
const config = script && configFromScript(script);
|
|
2122
|
+
if (config) ChatCops.init(config);
|
|
1747
2123
|
}
|
|
1748
2124
|
const currentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
1749
2125
|
if (currentScript && currentScript.dataset.apiUrl) {
|
|
@@ -1756,6 +2132,225 @@ if (currentScript && currentScript.dataset.apiUrl) {
|
|
|
1756
2132
|
if (typeof window !== "undefined") {
|
|
1757
2133
|
window.ChatCops = ChatCops;
|
|
1758
2134
|
}
|
|
2135
|
+
let loading;
|
|
2136
|
+
async function turnstileToken(siteKey) {
|
|
2137
|
+
loading ?? (loading = new Promise((resolve, reject) => {
|
|
2138
|
+
const script = document.createElement("script");
|
|
2139
|
+
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
2140
|
+
script.onload = resolve;
|
|
2141
|
+
script.onerror = reject;
|
|
2142
|
+
document.head.append(script);
|
|
2143
|
+
}));
|
|
2144
|
+
await loading;
|
|
2145
|
+
const box = document.createElement("div");
|
|
2146
|
+
box.style.cssText = "position:fixed;bottom:96px;right:24px;z-index:2147483647";
|
|
2147
|
+
document.body.append(box);
|
|
2148
|
+
try {
|
|
2149
|
+
return await new Promise((resolve, reject) => {
|
|
2150
|
+
window.turnstile.render(box, {
|
|
2151
|
+
sitekey: siteKey,
|
|
2152
|
+
appearance: "interaction-only",
|
|
2153
|
+
callback: resolve,
|
|
2154
|
+
"error-callback": reject
|
|
2155
|
+
});
|
|
2156
|
+
});
|
|
2157
|
+
} finally {
|
|
2158
|
+
box.remove();
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
const turnstile = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2162
|
+
__proto__: null,
|
|
2163
|
+
turnstileToken
|
|
2164
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
2165
|
+
const prechatStyles = ".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:var(--cc-danger);margin-inline-start: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:var(--cc-danger);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}.cc-prechat-input:focus-visible,.cc-prechat-select:focus-visible,.cc-prechat-textarea:focus-visible,.cc-prechat-submit:focus-visible{outline:2px solid transparent;box-shadow:var(--cc-focus-ring)}@media(max-width:640px){.cc-prechat-input,.cc-prechat-select,.cc-prechat-textarea{font-size:16px}}";
|
|
2166
|
+
const MAX_PRECHAT_FIELD_LENGTH = 500;
|
|
2167
|
+
class PreChatForm {
|
|
2168
|
+
constructor(parent, options) {
|
|
2169
|
+
this.fieldElements = /* @__PURE__ */ new Map();
|
|
2170
|
+
this.errorElements = /* @__PURE__ */ new Map();
|
|
2171
|
+
this.options = options;
|
|
2172
|
+
this.container = document.createElement("div");
|
|
2173
|
+
this.container.className = "cc-prechat";
|
|
2174
|
+
if (options.title) {
|
|
2175
|
+
const title = document.createElement("div");
|
|
2176
|
+
title.className = "cc-prechat-title";
|
|
2177
|
+
title.textContent = options.title;
|
|
2178
|
+
this.container.appendChild(title);
|
|
2179
|
+
}
|
|
2180
|
+
if (options.subtitle) {
|
|
2181
|
+
const subtitle = document.createElement("div");
|
|
2182
|
+
subtitle.className = "cc-prechat-subtitle";
|
|
2183
|
+
subtitle.textContent = options.subtitle;
|
|
2184
|
+
this.container.appendChild(subtitle);
|
|
2185
|
+
}
|
|
2186
|
+
const form = document.createElement("form");
|
|
2187
|
+
form.className = "cc-prechat-fields";
|
|
2188
|
+
form.noValidate = true;
|
|
2189
|
+
form.addEventListener("submit", (e) => {
|
|
2190
|
+
e.preventDefault();
|
|
2191
|
+
this.handleSubmit();
|
|
2192
|
+
});
|
|
2193
|
+
for (const field of options.fields) {
|
|
2194
|
+
form.appendChild(this.createField(field));
|
|
2195
|
+
}
|
|
2196
|
+
this.submitBtn = document.createElement("button");
|
|
2197
|
+
this.submitBtn.type = "submit";
|
|
2198
|
+
this.submitBtn.className = "cc-prechat-submit";
|
|
2199
|
+
this.submitBtn.textContent = options.submitLabel;
|
|
2200
|
+
form.appendChild(this.submitBtn);
|
|
2201
|
+
this.container.appendChild(form);
|
|
2202
|
+
parent.appendChild(this.container);
|
|
2203
|
+
addStyles(parent.getRootNode());
|
|
2204
|
+
this.updateSubmitState();
|
|
2205
|
+
const firstInput = this.fieldElements.values().next().value;
|
|
2206
|
+
if (firstInput) {
|
|
2207
|
+
setTimeout(() => firstInput.focus(), 100);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
createField(field) {
|
|
2211
|
+
const wrapper = document.createElement("div");
|
|
2212
|
+
wrapper.className = "cc-prechat-field";
|
|
2213
|
+
const inputId = `cc-prechat-${field.name}`;
|
|
2214
|
+
const errorId = `cc-prechat-${field.name}-error`;
|
|
2215
|
+
const label = document.createElement("label");
|
|
2216
|
+
label.className = "cc-prechat-label";
|
|
2217
|
+
label.htmlFor = inputId;
|
|
2218
|
+
label.textContent = field.label;
|
|
2219
|
+
if (field.required) {
|
|
2220
|
+
const req = document.createElement("span");
|
|
2221
|
+
req.className = "cc-required";
|
|
2222
|
+
req.textContent = "*";
|
|
2223
|
+
label.appendChild(req);
|
|
2224
|
+
}
|
|
2225
|
+
wrapper.appendChild(label);
|
|
2226
|
+
let input;
|
|
2227
|
+
if (field.type === "select") {
|
|
2228
|
+
const select = document.createElement("select");
|
|
2229
|
+
select.className = "cc-prechat-select";
|
|
2230
|
+
const defaultOpt = document.createElement("option");
|
|
2231
|
+
defaultOpt.value = "";
|
|
2232
|
+
defaultOpt.textContent = field.placeholder ?? this.options.locale.selectPlaceholder.replace("{label}", field.label.toLowerCase());
|
|
2233
|
+
defaultOpt.disabled = true;
|
|
2234
|
+
defaultOpt.selected = true;
|
|
2235
|
+
select.appendChild(defaultOpt);
|
|
2236
|
+
for (const opt of field.options ?? []) {
|
|
2237
|
+
const option = document.createElement("option");
|
|
2238
|
+
option.value = opt;
|
|
2239
|
+
option.textContent = opt;
|
|
2240
|
+
select.appendChild(option);
|
|
2241
|
+
}
|
|
2242
|
+
input = select;
|
|
2243
|
+
} else if (field.type === "textarea") {
|
|
2244
|
+
const textarea = document.createElement("textarea");
|
|
2245
|
+
textarea.className = "cc-prechat-textarea";
|
|
2246
|
+
textarea.placeholder = field.placeholder ?? "";
|
|
2247
|
+
textarea.rows = 3;
|
|
2248
|
+
textarea.maxLength = MAX_PRECHAT_FIELD_LENGTH;
|
|
2249
|
+
input = textarea;
|
|
2250
|
+
} else {
|
|
2251
|
+
const textInput = document.createElement("input");
|
|
2252
|
+
textInput.type = field.type;
|
|
2253
|
+
textInput.className = "cc-prechat-input";
|
|
2254
|
+
textInput.maxLength = MAX_PRECHAT_FIELD_LENGTH;
|
|
2255
|
+
textInput.placeholder = field.placeholder ?? "";
|
|
2256
|
+
input = textInput;
|
|
2257
|
+
}
|
|
2258
|
+
input.name = field.name;
|
|
2259
|
+
input.id = inputId;
|
|
2260
|
+
if (field.required) {
|
|
2261
|
+
input.setAttribute("aria-required", "true");
|
|
2262
|
+
}
|
|
2263
|
+
input.addEventListener("input", () => {
|
|
2264
|
+
this.clearError(field.name);
|
|
2265
|
+
this.updateSubmitState();
|
|
2266
|
+
});
|
|
2267
|
+
if (input instanceof HTMLSelectElement) {
|
|
2268
|
+
input.addEventListener("change", () => {
|
|
2269
|
+
this.clearError(field.name);
|
|
2270
|
+
this.updateSubmitState();
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
wrapper.appendChild(input);
|
|
2274
|
+
this.fieldElements.set(field.name, input);
|
|
2275
|
+
const error = document.createElement("div");
|
|
2276
|
+
error.className = "cc-prechat-error";
|
|
2277
|
+
error.id = errorId;
|
|
2278
|
+
wrapper.appendChild(error);
|
|
2279
|
+
this.errorElements.set(field.name, error);
|
|
2280
|
+
return wrapper;
|
|
2281
|
+
}
|
|
2282
|
+
updateSubmitState() {
|
|
2283
|
+
const allRequiredFilled = this.options.fields.every((field) => {
|
|
2284
|
+
if (!field.required) return true;
|
|
2285
|
+
const el = this.fieldElements.get(field.name);
|
|
2286
|
+
return el ? el.value.trim().length > 0 : false;
|
|
2287
|
+
});
|
|
2288
|
+
this.submitBtn.disabled = !allRequiredFilled;
|
|
2289
|
+
}
|
|
2290
|
+
handleSubmit() {
|
|
2291
|
+
const data = {};
|
|
2292
|
+
let valid = true;
|
|
2293
|
+
for (const field of this.options.fields) {
|
|
2294
|
+
const el = this.fieldElements.get(field.name);
|
|
2295
|
+
if (!el) continue;
|
|
2296
|
+
const value = el.value.trim();
|
|
2297
|
+
data[field.name] = value;
|
|
2298
|
+
if (field.required && !value) {
|
|
2299
|
+
this.showError(field.name, this.options.locale.preChatRequired);
|
|
2300
|
+
valid = false;
|
|
2301
|
+
continue;
|
|
2302
|
+
}
|
|
2303
|
+
if (field.type === "email" && value && !this.isValidEmail(value)) {
|
|
2304
|
+
this.showError(field.name, this.options.locale.preChatInvalidEmail);
|
|
2305
|
+
valid = false;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
if (valid) {
|
|
2309
|
+
this.options.onSubmit(data);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
showError(fieldName, message) {
|
|
2313
|
+
const el = this.errorElements.get(fieldName);
|
|
2314
|
+
if (el) el.textContent = message;
|
|
2315
|
+
const fieldEl = this.fieldElements.get(fieldName);
|
|
2316
|
+
if (fieldEl && el) {
|
|
2317
|
+
fieldEl.setAttribute("aria-invalid", "true");
|
|
2318
|
+
fieldEl.setAttribute("aria-describedby", el.id);
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
clearError(fieldName) {
|
|
2322
|
+
const el = this.errorElements.get(fieldName);
|
|
2323
|
+
if (el) el.textContent = "";
|
|
2324
|
+
const fieldEl = this.fieldElements.get(fieldName);
|
|
2325
|
+
if (fieldEl && el) {
|
|
2326
|
+
if (fieldEl.getAttribute("aria-describedby") === el.id) {
|
|
2327
|
+
fieldEl.removeAttribute("aria-describedby");
|
|
2328
|
+
}
|
|
2329
|
+
if (fieldEl.getAttribute("aria-invalid") === "true") {
|
|
2330
|
+
fieldEl.removeAttribute("aria-invalid");
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
isValidEmail(email) {
|
|
2335
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
2336
|
+
}
|
|
2337
|
+
destroy() {
|
|
2338
|
+
this.container.remove();
|
|
2339
|
+
this.fieldElements.clear();
|
|
2340
|
+
this.errorElements.clear();
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
function addStyles(root) {
|
|
2344
|
+
if (!(root instanceof ShadowRoot) || root.querySelector("style[data-cc-prechat]")) return;
|
|
2345
|
+
const style = document.createElement("style");
|
|
2346
|
+
style.dataset.ccPrechat = "";
|
|
2347
|
+
style.textContent = prechatStyles;
|
|
2348
|
+
root.appendChild(style);
|
|
2349
|
+
}
|
|
2350
|
+
const prechatForm = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2351
|
+
__proto__: null,
|
|
2352
|
+
PreChatForm
|
|
2353
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1759
2354
|
if (typeof window !== "undefined" && window.ChatCops && window.ChatCops.default) {
|
|
1760
2355
|
Object.assign(window.ChatCops, window.ChatCops.default);
|
|
1761
2356
|
}
|