@chatcops/widget 0.4.4 → 0.6.0

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