@chatcops/widget 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -0
- package/dist/chatcops.esm.js +5 -1
- package/dist/chatcops.min.js +1 -1
- package/dist/dom/panel.d.ts +1 -0
- package/dist/dom/panel.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# @chatcops/widget
|
|
2
|
+
|
|
3
|
+
Embeddable AI chatbot widget. Zero dependencies, Shadow DOM isolated, ~50KB gzipped.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
### Script Tag (easiest)
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script
|
|
11
|
+
src="https://cdn.jsdelivr.net/npm/@chatcops/widget/dist/chatcops.min.js"
|
|
12
|
+
data-api-url="https://your-api.com/chat"
|
|
13
|
+
data-accent="#6366f1">
|
|
14
|
+
</script>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The widget auto-initializes from `data-*` attributes on `DOMContentLoaded`.
|
|
18
|
+
|
|
19
|
+
### npm
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @chatcops/widget
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import ChatCops from '@chatcops/widget';
|
|
27
|
+
|
|
28
|
+
ChatCops.init({
|
|
29
|
+
apiUrl: 'https://your-api.com/chat',
|
|
30
|
+
theme: { accent: '#6366f1' },
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Modes
|
|
35
|
+
|
|
36
|
+
### Popup (default)
|
|
37
|
+
|
|
38
|
+
Floating action button (FAB) in the corner. Clicking it opens a chat panel.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
ChatCops.init({
|
|
42
|
+
apiUrl: '/chat',
|
|
43
|
+
mode: 'popup', // default
|
|
44
|
+
theme: { position: 'bottom-right' }, // or 'bottom-left'
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Inline
|
|
49
|
+
|
|
50
|
+
Renders directly inside a container element. No FAB, no welcome bubble.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
ChatCops.init({
|
|
54
|
+
apiUrl: '/chat',
|
|
55
|
+
mode: 'inline',
|
|
56
|
+
container: '#chat-container', // CSS selector or HTMLElement
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
ChatCops.init({
|
|
64
|
+
// Required
|
|
65
|
+
apiUrl: 'https://your-api.com/chat',
|
|
66
|
+
|
|
67
|
+
// Display mode
|
|
68
|
+
mode: 'popup', // 'popup' | 'inline'
|
|
69
|
+
container: '#chat', // required for inline mode
|
|
70
|
+
|
|
71
|
+
// Theme
|
|
72
|
+
theme: {
|
|
73
|
+
accent: '#6366f1', // primary color
|
|
74
|
+
textColor: '#ffffff',
|
|
75
|
+
bgColor: '#0a0a0a',
|
|
76
|
+
fontFamily: 'system-ui, sans-serif',
|
|
77
|
+
borderRadius: '12px',
|
|
78
|
+
position: 'bottom-right', // 'bottom-right' | 'bottom-left'
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// Branding
|
|
82
|
+
branding: {
|
|
83
|
+
name: 'Support Bot',
|
|
84
|
+
avatar: 'https://example.com/avatar.png',
|
|
85
|
+
subtitle: 'Online',
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// Messages
|
|
89
|
+
welcomeMessage: 'Hi! How can I help?',
|
|
90
|
+
welcomeBubble: {
|
|
91
|
+
text: 'Need help? Chat with us!',
|
|
92
|
+
delay: 3000, // ms before showing
|
|
93
|
+
showOnce: true, // per session
|
|
94
|
+
},
|
|
95
|
+
placeholder: 'Type a message...',
|
|
96
|
+
|
|
97
|
+
// Behavior
|
|
98
|
+
persistHistory: true, // localStorage, default: true
|
|
99
|
+
maxMessages: 50, // max persisted messages
|
|
100
|
+
pageContext: true, // send page URL/title to API
|
|
101
|
+
autoOpen: 5000, // auto-open after ms (popup only)
|
|
102
|
+
locale: 'en',
|
|
103
|
+
|
|
104
|
+
// Callbacks
|
|
105
|
+
onOpen: () => {},
|
|
106
|
+
onClose: () => {},
|
|
107
|
+
onMessage: (msg) => {},
|
|
108
|
+
onError: (err) => {},
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## API
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
ChatCops.init(config) // Initialize the widget
|
|
116
|
+
ChatCops.open() // Open the chat panel (popup mode)
|
|
117
|
+
ChatCops.close() // Close the chat panel
|
|
118
|
+
ChatCops.destroy() // Remove widget and clean up
|
|
119
|
+
ChatCops.on(event, handler) // Subscribe to events
|
|
120
|
+
ChatCops.off(event, handler) // Unsubscribe
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Events: `open`, `close`, `message`, `error`.
|
|
124
|
+
|
|
125
|
+
## Script Tag Attributes
|
|
126
|
+
|
|
127
|
+
All configuration can be set via `data-*` attributes:
|
|
128
|
+
|
|
129
|
+
```html
|
|
130
|
+
<script
|
|
131
|
+
src="https://cdn.jsdelivr.net/npm/@chatcops/widget/dist/chatcops.min.js"
|
|
132
|
+
data-api-url="/chat"
|
|
133
|
+
data-mode="popup"
|
|
134
|
+
data-accent="#6366f1"
|
|
135
|
+
data-text-color="#ffffff"
|
|
136
|
+
data-bg-color="#0a0a0a"
|
|
137
|
+
data-font-family="system-ui"
|
|
138
|
+
data-border-radius="12px"
|
|
139
|
+
data-position="bottom-right"
|
|
140
|
+
data-brand-name="Support Bot"
|
|
141
|
+
data-brand-avatar="https://example.com/avatar.png"
|
|
142
|
+
data-brand-subtitle="Online"
|
|
143
|
+
data-welcome-message="Hi! How can I help?"
|
|
144
|
+
data-welcome-bubble="Need help?"
|
|
145
|
+
data-welcome-bubble-delay="3000"
|
|
146
|
+
data-placeholder="Type here..."
|
|
147
|
+
data-locale="en"
|
|
148
|
+
data-auto-open="true">
|
|
149
|
+
</script>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Features
|
|
153
|
+
|
|
154
|
+
- **Shadow DOM** - Fully isolated CSS/DOM, no conflicts with host site
|
|
155
|
+
- **SSE Streaming** - Real-time streaming responses via Server-Sent Events
|
|
156
|
+
- **Markdown** - Renders bold, italic, code blocks, links, and lists
|
|
157
|
+
- **Conversation Persistence** - localStorage for history, sessionStorage for session ID
|
|
158
|
+
- **Theming** - CSS custom properties, auto-derived surface colors from `bgColor`
|
|
159
|
+
- **Mobile Responsive** - Full-screen panel on screens below 768px
|
|
160
|
+
- **i18n** - Built-in locale strings with custom override support
|
|
161
|
+
- **Accessible** - ARIA labels, semantic HTML, keyboard navigation
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT
|
package/dist/chatcops.esm.js
CHANGED
|
@@ -205,8 +205,10 @@ const CLOSE_ICON = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
|
205
205
|
class Panel {
|
|
206
206
|
constructor(root, options) {
|
|
207
207
|
__publicField(this, "el");
|
|
208
|
+
__publicField(this, "inline");
|
|
208
209
|
__publicField(this, "messages");
|
|
209
210
|
__publicField(this, "input");
|
|
211
|
+
this.inline = options.inline ?? false;
|
|
210
212
|
this.el = document.createElement("div");
|
|
211
213
|
this.el.className = "cc-panel";
|
|
212
214
|
if (options.inline) {
|
|
@@ -255,7 +257,9 @@ class Panel {
|
|
|
255
257
|
}
|
|
256
258
|
show() {
|
|
257
259
|
this.el.classList.add("cc-visible");
|
|
258
|
-
this.
|
|
260
|
+
if (!this.inline) {
|
|
261
|
+
this.input.focus();
|
|
262
|
+
}
|
|
259
263
|
}
|
|
260
264
|
hide() {
|
|
261
265
|
this.el.classList.remove("cc-visible");
|
package/dist/chatcops.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var ChatCops=function(e){"use strict";var t=Object.defineProperty,s=(e,s,i)=>((e,s,i)=>s in e?t(e,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[s]=i)(e,"symbol"!=typeof s?s+"":s,i);function i(e){const t=document.createElement("div");t.id="chatcops-root",e?e.appendChild(t):document.body.appendChild(t);const s=t.attachShadow({mode:"open"}),i=document.createElement("style");return i.textContent=":host{--cc-accent: #6366f1;--cc-accent-hover: #4f46e5;--cc-text: #ffffff;--cc-bg: #0a0a0a;--cc-bg-surface: #1a1a1a;--cc-bg-input: #2a2a2a;--cc-border: #333333;--cc-text-secondary: #a0a0a0;--cc-font: system-ui, -apple-system, sans-serif;--cc-radius: 12px;--cc-shadow: 0 8px 32px rgba(0, 0, 0, .4);--cc-fab-size: 56px;--cc-panel-width: 400px;--cc-panel-height: 560px;all:initial;font-family:var(--cc-font);font-size:14px;line-height:1.5;color:var(--cc-text);-webkit-font-smoothing:antialiased}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}.cc-fab{position:fixed;bottom:20px;right:20px;width:var(--cc-fab-size);height:var(--cc-fab-size);border-radius:50%;background:var(--cc-accent);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px #0000004d;transition:transform .2s ease,box-shadow .2s ease;z-index:10000;outline:none}.cc-fab:hover{transform:scale(1.05);box-shadow:0 6px 24px #0006}.cc-fab:active{transform:scale(.95)}.cc-fab.cc-left{right:auto;left:20px}.cc-fab svg{width:24px;height:24px;fill:var(--cc-text);transition:transform .3s ease}.cc-fab.cc-open svg{transform:rotate(90deg)}.cc-fab-badge{position:absolute;top:-2px;right:-2px;width:12px;height:12px;border-radius:50%;background:#ef4444;border:2px solid var(--cc-bg);display:none}.cc-fab-badge.cc-visible{display:block}@keyframes cc-pulse{0%,to{box-shadow:0 4px 16px #0000004d}50%{box-shadow:0 4px 16px #6366f180}}.cc-fab.cc-pulse{animation:cc-pulse 2s ease-in-out 3}.cc-panel{position:fixed;bottom:88px;right:20px;width:var(--cc-panel-width);height:var(--cc-panel-height);max-height:calc(100vh - 100px);background:var(--cc-bg);border-radius:var(--cc-radius);box-shadow:var(--cc-shadow);border:1px solid var(--cc-border);display:flex;flex-direction:column;overflow:hidden;z-index:9999;opacity:0;transform:translateY(16px) scale(.95);pointer-events:none;transition:opacity .2s ease,transform .2s ease}.cc-panel.cc-visible{opacity:1;transform:translateY(0) scale(1);pointer-events:auto}.cc-panel.cc-left{right:auto;left:20px}.cc-panel.cc-inline{position:relative;bottom:auto;right:auto;left:auto;width:100%;height:100%;max-height:100%;z-index:auto;opacity:1;transform:none;pointer-events:auto;transition:none}.cc-panel.cc-inline.cc-visible{transform:none}.cc-header{display:flex;align-items:center;padding:16px;background:var(--cc-bg-surface);border-bottom:1px solid var(--cc-border);flex-shrink:0}.cc-header-avatar{width:36px;height:36px;border-radius:50%;margin-right:12px;object-fit:cover}.cc-header-avatar-placeholder{width:36px;height:36px;border-radius:50%;margin-right:12px;background:var(--cc-accent);display:flex;align-items:center;justify-content:center}.cc-header-avatar-placeholder svg{width:20px;height:20px;fill:var(--cc-text)}.cc-header-info{flex:1}.cc-header-name{font-size:15px;font-weight:600;color:var(--cc-text)}.cc-header-subtitle{font-size:12px;color:var(--cc-text-secondary)}.cc-header-close{width:32px;height:32px;border-radius:8px;background:transparent;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease}.cc-header-close:hover{background:var(--cc-bg-input)}.cc-header-close svg{width:16px;height:16px;fill:var(--cc-text-secondary)}.cc-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.cc-messages::-webkit-scrollbar{width:4px}.cc-messages::-webkit-scrollbar-track{background:transparent}.cc-messages::-webkit-scrollbar-thumb{background:var(--cc-border);border-radius:2px}.cc-message{max-width:85%;padding:10px 14px;border-radius:var(--cc-radius);word-wrap:break-word;overflow-wrap:break-word;font-size:14px;line-height:1.5}.cc-message-user{align-self:flex-end;background:var(--cc-accent);color:var(--cc-text);border-bottom-right-radius:4px}.cc-message-assistant{align-self:flex-start;background:var(--cc-bg-surface);color:var(--cc-text);border-bottom-left-radius:4px}.cc-message a{color:var(--cc-accent);text-decoration:underline}.cc-message-user a{color:var(--cc-text)}.cc-message code{background:#0003;padding:1px 4px;border-radius:4px;font-family:SF Mono,Cascadia Code,Consolas,monospace;font-size:13px}.cc-message pre{background:#0000004d;padding:8px 12px;border-radius:8px;overflow-x:auto;margin:4px 0}.cc-message pre code{background:transparent;padding:0}.cc-message ul,.cc-message ol{padding-left:20px;margin:4px 0}.cc-typing{display:flex;align-items:center;gap:4px;padding:10px 14px;align-self:flex-start;background:var(--cc-bg-surface);border-radius:var(--cc-radius);border-bottom-left-radius:4px}.cc-typing-dot{width:6px;height:6px;border-radius:50%;background:var(--cc-text-secondary);animation:cc-bounce 1.2s infinite}.cc-typing-dot:nth-child(2){animation-delay:.2s}.cc-typing-dot:nth-child(3){animation-delay:.4s}@keyframes cc-bounce{0%,60%,to{transform:translateY(0)}30%{transform:translateY(-4px)}}.cc-input-area{display:flex;align-items:flex-end;padding:12px 16px;border-top:1px solid var(--cc-border);background:var(--cc-bg);gap:8px}.cc-input{flex:1;background:var(--cc-bg-input);border:1px solid var(--cc-border);border-radius:8px;padding:8px 12px;color:var(--cc-text);font-family:var(--cc-font);font-size:14px;line-height:1.5;resize:none;outline:none;max-height:96px;overflow-y:auto;transition:border-color .15s ease}.cc-input:focus{border-color:var(--cc-accent);outline:2px solid var(--cc-accent);outline-offset:1px}.cc-input::placeholder{color:var(--cc-text-secondary)}.cc-send-btn{width:36px;height:36px;border-radius:8px;background:var(--cc-accent);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s ease,opacity .15s ease}.cc-send-btn:hover{background:var(--cc-accent-hover)}.cc-send-btn:disabled{opacity:.4;cursor:not-allowed}.cc-send-btn svg{width:16px;height:16px;fill:var(--cc-text)}.cc-footer{padding:6px 16px;text-align:center;font-size:11px;color:var(--cc-text-secondary);border-top:1px solid var(--cc-border)}.cc-footer a{color:var(--cc-text-secondary);text-decoration:none}.cc-footer a:hover{text-decoration:underline}.cc-bubble{position:fixed;bottom:84px;right:20px;background:var(--cc-bg-surface);color:var(--cc-text);padding:10px 16px;border-radius:var(--cc-radius);box-shadow:var(--cc-shadow);border:1px solid var(--cc-border);max-width:260px;font-size:13px;line-height:1.4;z-index:9998;opacity:0;transform:translateY(8px);pointer-events:none;transition:opacity .3s ease,transform .3s ease;cursor:pointer}.cc-bubble.cc-visible{opacity:1;transform:translateY(0);pointer-events:auto}.cc-bubble.cc-left{right:auto;left:20px}.cc-bubble-close{position:absolute;top:-8px;right:-8px;width:20px;height:20px;border-radius:50%;background:var(--cc-bg);border:1px solid var(--cc-border);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:10px;color:var(--cc-text-secondary)}@media(max-width:768px){.cc-panel:not(.cc-inline){width:100%;height:100%;max-height:100%;bottom:0;right:0;left:0;border-radius:0;border:none}.cc-panel.cc-left:not(.cc-inline){left:0}.cc-bubble{bottom:84px;right:16px;left:16px;max-width:none}}",s.appendChild(i),s}const a='<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>';class n{constructor(e,t){s(this,"el"),s(this,"badge"),s(this,"isOpen",!1),this.el=document.createElement("button"),this.el.className="cc-fab cc-pulse",this.el.setAttribute("aria-label","Open chat"),"bottom-left"===t.position&&this.el.classList.add("cc-left"),this.el.innerHTML=a,this.badge=document.createElement("div"),this.badge.className="cc-fab-badge",this.el.appendChild(this.badge),this.el.addEventListener("click",()=>{this.el.classList.remove("cc-pulse"),t.onClick()}),e.appendChild(this.el)}setOpen(e){this.isOpen=e,this.el.innerHTML=e?'<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>':a,e||this.el.appendChild(this.badge),this.el.classList.toggle("cc-open",e),this.el.setAttribute("aria-label",e?"Close chat":"Open chat")}showBadge(){this.badge.classList.add("cc-visible")}hideBadge(){this.badge.classList.remove("cc-visible")}destroy(){this.el.remove()}}const o={"&":"&","<":"<",">":">",'"':""","'":"'"};function r(e){let t=e.replace(/[&<>"']/g,e=>o[e]??e);return t=t.replace(/```(?:\w*)\n?([\s\S]*?)```/g,(e,t)=>`<pre><code>${t.trim()}</code></pre>`),t=t.replace(/`([^`]+)`/g,"<code>$1</code>"),t=t.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),t=t.replace(/\*(.+?)\*/g,"<em>$1</em>"),t=t.replace(/\[([^\]]+)\]\(((?:[^()]*|\([^()]*\))*)\)/g,'<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'),t=t.replace(/^- (.+)$/gm,"<li>$1</li>"),t=t.replace(/(<li>.*<\/li>\n?)+/g,e=>`<ul>${e}</ul>`),t=t.replace(/^\d+\. (.+)$/gm,"<li>$1</li>"),t=t.replace(/\n\n/g,"<br><br>"),t=t.replace(/\n/g,"<br>"),t}class c{constructor(e){s(this,"container"),s(this,"typingEl",null),this.container=document.createElement("div"),this.container.className="cc-messages",e.appendChild(this.container)}addMessage(e){this.removeTyping();const t=document.createElement("div");return t.className=`cc-message cc-message-${e.role}`,t.dataset.id=e.id,t.innerHTML="assistant"===e.role?r(e.content):this.escapeHtml(e.content),this.container.appendChild(t),this.scrollToBottom(),t}updateMessage(e,t,s){const i=this.container.querySelector(`[data-id="${e}"]`);i&&(i.innerHTML=s?r(t):this.escapeHtml(t),this.scrollToBottom())}showTyping(){this.typingEl||(this.typingEl=document.createElement("div"),this.typingEl.className="cc-typing",this.typingEl.innerHTML='<div class="cc-typing-dot"></div><div class="cc-typing-dot"></div><div class="cc-typing-dot"></div>',this.container.appendChild(this.typingEl),this.scrollToBottom())}removeTyping(){this.typingEl&&(this.typingEl.remove(),this.typingEl=null)}clear(){this.container.innerHTML="",this.typingEl=null}scrollToBottom(){this.container.scrollTop=this.container.scrollHeight}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}}class l{constructor(e,t){s(this,"area"),s(this,"textarea"),s(this,"sendBtn"),s(this,"onSend"),this.onSend=t.onSend,this.area=document.createElement("div"),this.area.className="cc-input-area",this.textarea=document.createElement("textarea"),this.textarea.className="cc-input",this.textarea.placeholder=t.placeholder,this.textarea.rows=1,this.textarea.addEventListener("input",()=>this.autoResize()),this.textarea.addEventListener("keydown",e=>this.handleKeydown(e)),this.sendBtn=document.createElement("button"),this.sendBtn.className="cc-send-btn",this.sendBtn.disabled=!0,this.sendBtn.setAttribute("aria-label","Send message"),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>',this.sendBtn.addEventListener("click",()=>this.send()),this.textarea.addEventListener("input",()=>{this.sendBtn.disabled=0===this.textarea.value.trim().length}),this.area.appendChild(this.textarea),this.area.appendChild(this.sendBtn),e.appendChild(this.area)}focus(){this.textarea.focus()}setDisabled(e){this.textarea.disabled=e,this.sendBtn.disabled=e||0===this.textarea.value.trim().length}clear(){this.textarea.value="",this.sendBtn.disabled=!0,this.autoResize()}send(){const e=this.textarea.value.trim();e&&(this.onSend(e),this.clear())}handleKeydown(e){"Enter"!==e.key||e.shiftKey||(e.preventDefault(),this.send())}autoResize(){this.textarea.style.height="auto";this.textarea.style.height=Math.min(this.textarea.scrollHeight,96)+"px"}}class d{constructor(e,t){s(this,"el"),s(this,"messages"),s(this,"input"),this.el=document.createElement("div"),this.el.className="cc-panel",t.inline&&this.el.classList.add("cc-inline"),"bottom-left"===t.position&&this.el.classList.add("cc-left");const i=document.createElement("div");if(i.className="cc-header",t.branding.avatar){const e=document.createElement("img");e.className="cc-header-avatar",e.src=t.branding.avatar,e.alt=t.branding.name,i.appendChild(e)}else{const e=document.createElement("div");e.className="cc-header-avatar-placeholder",e.innerHTML='<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>',i.appendChild(e)}const a=document.createElement("div");if(a.className="cc-header-info",a.innerHTML=`<div class="cc-header-name">${this.escapeHtml(t.branding.name)}</div><div class="cc-header-subtitle">${this.escapeHtml(t.branding.subtitle)}</div>`,i.appendChild(a),!t.inline){const e=document.createElement("button");e.className="cc-header-close",e.setAttribute("aria-label","Close chat"),e.innerHTML='<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>',e.addEventListener("click",t.onClose),i.appendChild(e)}this.el.appendChild(i),this.messages=new c(this.el),this.input=new l(this.el,{placeholder:t.placeholder,onSend:t.onSend});const n=document.createElement("div");n.className="cc-footer",n.innerHTML=t.footerText,this.el.appendChild(n),e.appendChild(this.el)}show(){this.el.classList.add("cc-visible"),this.input.focus()}hide(){this.el.classList.remove("cc-visible")}get isVisible(){return this.el.classList.contains("cc-visible")}addMessage(e){return this.messages.addMessage(e)}destroy(){this.el.remove()}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}}const h="chatcops_bubble_shown";class p{constructor(e,t){s(this,"el"),s(this,"timerId",null),this.el=document.createElement("div"),this.el.className="cc-bubble","bottom-left"===t.position&&this.el.classList.add("cc-left"),this.el.textContent=t.text;const i=document.createElement("button");if(i.className="cc-bubble-close",i.innerHTML="×",i.addEventListener("click",e=>{e.stopPropagation(),this.hide()}),this.el.appendChild(i),this.el.addEventListener("click",()=>{this.hide(),t.onClick()}),e.appendChild(this.el),t.showOnce)try{if(sessionStorage.getItem(h))return;sessionStorage.setItem(h,"1")}catch{}this.timerId=setTimeout(()=>{this.el.classList.add("cc-visible")},t.delay??3e3)}hide(){this.el.classList.remove("cc-visible"),this.timerId&&(clearTimeout(this.timerId),this.timerId=null)}destroy(){this.hide(),this.el.remove()}}class g{constructor(e){s(this,"apiUrl"),s(this,"timeout"),this.apiUrl=e.apiUrl,this.timeout=e.timeout??3e4}async*sendMessage(e){var t;const s=new AbortController,i=setTimeout(()=>s.abort(),this.timeout);try{const i=await fetch(this.apiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:s.signal});if(!i.ok)return 429===i.status?void(yield{error:"rate_limit"}):void(yield{error:`HTTP ${i.status}`});const a=null==(t=i.body)?void 0:t.getReader();if(!a)return void(yield{error:"No response body"});const n=new TextDecoder;let o="";for(;;){const{done:e,value:t}=await a.read();if(e)break;o+=n.decode(t,{stream:!0});const s=o.split("\n");o=s.pop()??"";for(const i of s){const e=i.trim();if(!e||!e.startsWith("data: "))continue;const t=e.slice(6);if("[DONE]"===t)return void(yield{done:!0});try{yield JSON.parse(t)}catch{}}}yield{done:!0}}catch(a){a instanceof DOMException&&"AbortError"===a.name?yield{error:"timeout"}:yield{error:"network"}}finally{clearTimeout(i)}}}const u="chatcops_conversation";class m{constructor(e){s(this,"key"),this.key=e?`${u}_${e}`:u}load(){try{const e=localStorage.getItem(this.key);return e?JSON.parse(e):null}catch{return null}}save(e){try{localStorage.setItem(this.key,JSON.stringify(e))}catch{}}clear(){try{localStorage.removeItem(this.key)}catch{}}getSessionId(){const e="chatcops_session_id";try{let t=sessionStorage.getItem(e);return t||(t=crypto.randomUUID(),sessionStorage.setItem(e,t)),t}catch{return crypto.randomUUID()}}}function b(e,t){const s=e.host;t.accent&&(s.style.setProperty("--cc-accent",t.accent),s.style.setProperty("--cc-accent-hover",function(e,t){const[s,i,a]=f(e),n=1-t/100;return v(Math.round(s*n),Math.round(i*n),Math.round(a*n))}(t.accent,10))),t.textColor&&s.style.setProperty("--cc-text",t.textColor),t.bgColor&&(s.style.setProperty("--cc-bg",t.bgColor),s.style.setProperty("--cc-bg-surface",x(t.bgColor,8)),s.style.setProperty("--cc-bg-input",x(t.bgColor,14))),t.fontFamily&&s.style.setProperty("--cc-font",t.fontFamily),t.borderRadius&&s.style.setProperty("--cc-radius",t.borderRadius)}function f(e){if(!/^#?[\da-f]{3}(?:[\da-f]{3})?$/i.test(e))return[0,0,0];const t=e.replace("#",""),s=parseInt(3===t.length?t.split("").map(e=>e+e).join(""):t,16);return[s>>16&255,s>>8&255,255&s]}function v(e,t,s){return"#"+[e,t,s].map(e=>Math.max(0,Math.min(255,e)).toString(16).padStart(2,"0")).join("")}function x(e,t){const[s,i,a]=f(e),n=t/100;return v(Math.round(s+(255-s)*n),Math.round(i+(255-i)*n),Math.round(a+(255-a)*n))}const y={welcomeMessage:"Hi! How can I help you today?",inputPlaceholder:"Type a message...",sendButton:"Send",closeButton:"Close",errorGeneric:"Something went wrong. Please try again.",errorNetwork:"Connection error. Please check your network.",errorRateLimit:"Too many messages. Please wait a moment.",typingIndicator:"Typing...",poweredBy:"Powered by ChatCops",newConversation:"New conversation",welcomeBubbleDefault:"Need help? Chat with us!"},w={en:y};class C{constructor(e){s(this,"config"),s(this,"shadow"),s(this,"fab"),s(this,"panel"),s(this,"bubble"),s(this,"client"),s(this,"storage"),s(this,"locale"),s(this,"conversationId"),s(this,"messages",[]),s(this,"isStreaming",!1),s(this,"eventHandlers",new Map),s(this,"containerEl"),this.config=e,this.client=new g({apiUrl:e.apiUrl}),this.storage=new m,this.locale=function(e,t){const s=w[e]??y;return t?{...s,...t}:s}(e.locale??"en",e.strings),this.conversationId=this.storage.getSessionId()}get isInline(){return"inline"===this.config.mode}resolveContainer(){if(!this.config.container)throw new Error('ChatCops: "container" is required when mode is "inline"');if("string"==typeof this.config.container){const e=document.querySelector(this.config.container);if(!e)throw new Error(`ChatCops: container element not found: ${this.config.container}`);return e}return this.config.container}init(){var e,t,s,a;this.isInline?(this.containerEl=this.resolveContainer(),this.shadow=i(this.containerEl)):this.shadow=i(),this.config.theme&&b(this.shadow,this.config.theme);const o=(null==(e=this.config.theme)?void 0:e.position)??"bottom-right";if(this.isInline||(this.fab=new n(this.shadow,{position:o,onClick:()=>this.toggle()})),this.panel=new d(this.shadow,{position:o,inline:this.isInline,branding:{name:(null==(t=this.config.branding)?void 0:t.name)??"AI Assistant",avatar:null==(s=this.config.branding)?void 0:s.avatar,subtitle:(null==(a=this.config.branding)?void 0:a.subtitle)??"Online"},placeholder:this.config.placeholder??this.locale.inputPlaceholder,footerText:this.locale.poweredBy,onSend:e=>this.handleSend(e),onClose:()=>this.close()}),!1!==this.config.persistHistory){const e=this.storage.load();if(e){this.conversationId=e.id;for(const t of e.messages)this.messages.push(t),this.panel.addMessage(t)}}if(0===this.messages.length){const e={id:"welcome",role:"assistant",content:this.config.welcomeMessage??this.locale.welcomeMessage};this.messages.push(e),this.panel.addMessage(e)}if(!this.isInline&&this.config.welcomeBubble&&(this.bubble=new p(this.shadow,{text:this.config.welcomeBubble.text,delay:this.config.welcomeBubble.delay,showOnce:this.config.welcomeBubble.showOnce??!0,position:o,onClick:()=>this.open()})),this.isInline&&this.panel.show(),!this.isInline&&void 0!==this.config.autoOpen&&!1!==this.config.autoOpen){const e="number"==typeof this.config.autoOpen?this.config.autoOpen:0;setTimeout(()=>this.open(),e)}}open(){var e,t,s,i,a;this.panel.show(),this.isInline||(null==(e=this.fab)||e.setOpen(!0),null==(t=this.fab)||t.hideBadge(),null==(s=this.bubble)||s.hide()),null==(a=(i=this.config).onOpen)||a.call(i),this.emit("open")}close(){var e,t,s;this.panel.hide(),this.isInline||null==(e=this.fab)||e.setOpen(!1),null==(s=(t=this.config).onClose)||s.call(t),this.emit("close")}toggle(){this.panel.isVisible?this.close():this.open()}destroy(){var e,t;null==(e=this.bubble)||e.destroy(),this.panel.destroy(),null==(t=this.fab)||t.destroy(),function(e){const t=(e??document.body).querySelector("#chatcops-root")??document.getElementById("chatcops-root");t&&t.remove()}(this.containerEl),this.eventHandlers.clear(),this.messages=[]}on(e,t){this.eventHandlers.has(e)||this.eventHandlers.set(e,new Set),this.eventHandlers.get(e).add(t)}off(e,t){var s;null==(s=this.eventHandlers.get(e))||s.delete(t)}emit(e,...t){var s;null==(s=this.eventHandlers.get(e))||s.forEach(e=>e(...t))}async handleSend(e){var t,s,i,a,n,o,r,c,l;if(this.isStreaming)return;const d={id:crypto.randomUUID(),role:"user",content:e};this.messages.push(d),this.panel.addMessage(d),null==(s=(t=this.config).onMessage)||s.call(t,d),this.emit("message",d),this.isStreaming=!0,this.panel.input.setDisabled(!0),this.panel.messages.showTyping();const h={id:crypto.randomUUID(),role:"assistant",content:""},p=!1!==this.config.pageContext?{url:window.location.href,title:document.title}:void 0;try{let t=!0;for await(const s of this.client.sendMessage({conversationId:this.conversationId,message:e,pageContext:p,locale:this.config.locale})){if(s.error){const e="rate_limit"===s.error?"errorRateLimit":"network"===s.error?"errorNetwork":"errorGeneric";h.content=this.locale[e],this.panel.messages.removeTyping(),this.panel.addMessage(h),null==(a=(i=this.config).onError)||a.call(i,new Error(s.error)),this.emit("error",new Error(s.error));break}if(s.done)break;s.content&&(t&&(this.panel.messages.removeTyping(),this.panel.addMessage(h),t=!1),h.content+=s.content,this.panel.messages.updateMessage(h.id,h.content,!0))}h.content&&(this.messages.push(h),null==(o=(n=this.config).onMessage)||o.call(n,h),this.emit("message",h),this.isInline||this.panel.isVisible||null==(r=this.fab)||r.showBadge())}catch(g){this.panel.messages.removeTyping(),h.content=this.locale.errorGeneric,this.panel.addMessage(h),null==(l=(c=this.config).onError)||l.call(c,g instanceof Error?g:new Error(String(g)))}finally{this.isStreaming=!1,this.panel.input.setDisabled(!1),this.panel.input.focus(),this.saveHistory()}}saveHistory(){if(!1===this.config.persistHistory)return;const e=this.config.maxMessages??50,t=this.messages.filter(e=>"welcome"!==e.id).slice(-e);this.storage.save({id:this.conversationId,messages:t.map(e=>({id:e.id,role:e.role,content:e.content,timestamp:Date.now()})),updatedAt:Date.now()})}}let k=null;const E={init:e=>(k&&k.destroy(),k=new C(e),k.init(),k),open(){null==k||k.open()},close(){null==k||k.close()},destroy(){null==k||k.destroy(),k=null},on(e,t){null==k||k.on(e,t)},off(e,t){null==k||k.off(e,t)}};function M(){const e=document.currentScript;if(!e)return;const t=e.dataset.apiUrl;if(!t)return;const s=e.dataset.mode,i=e.dataset.container;E.init({apiUrl:t,mode:s||void 0,container:i||void 0,theme:{accent:e.dataset.accent,textColor:e.dataset.textColor,bgColor:e.dataset.bgColor,fontFamily:e.dataset.fontFamily,borderRadius:e.dataset.borderRadius,position:e.dataset.position||void 0},branding:{name:e.dataset.brandName,avatar:e.dataset.brandAvatar,subtitle:e.dataset.brandSubtitle},welcomeMessage:e.dataset.welcomeMessage,welcomeBubble:e.dataset.welcomeBubble?{text:e.dataset.welcomeBubble,delay:e.dataset.welcomeBubbleDelay?parseInt(e.dataset.welcomeBubbleDelay,10):void 0}:void 0,autoOpen:void 0!==e.dataset.autoOpen?""===e.dataset.autoOpen||"true"===e.dataset.autoOpen||(parseInt(e.dataset.autoOpen,10)||!0):void 0,placeholder:e.dataset.placeholder,locale:e.dataset.locale})}const S=document.currentScript;return S&&S.dataset.apiUrl&&("loading"===document.readyState?document.addEventListener("DOMContentLoaded",M):M()),"undefined"!=typeof window&&(window.ChatCops=E),e.Widget=C,e.default=E,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}}),e}({});
|
|
1
|
+
var ChatCops=function(e){"use strict";var t=Object.defineProperty,s=(e,s,i)=>((e,s,i)=>s in e?t(e,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[s]=i)(e,"symbol"!=typeof s?s+"":s,i);function i(e){const t=document.createElement("div");t.id="chatcops-root",e?e.appendChild(t):document.body.appendChild(t);const s=t.attachShadow({mode:"open"}),i=document.createElement("style");return i.textContent=":host{--cc-accent: #6366f1;--cc-accent-hover: #4f46e5;--cc-text: #ffffff;--cc-bg: #0a0a0a;--cc-bg-surface: #1a1a1a;--cc-bg-input: #2a2a2a;--cc-border: #333333;--cc-text-secondary: #a0a0a0;--cc-font: system-ui, -apple-system, sans-serif;--cc-radius: 12px;--cc-shadow: 0 8px 32px rgba(0, 0, 0, .4);--cc-fab-size: 56px;--cc-panel-width: 400px;--cc-panel-height: 560px;all:initial;font-family:var(--cc-font);font-size:14px;line-height:1.5;color:var(--cc-text);-webkit-font-smoothing:antialiased}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}.cc-fab{position:fixed;bottom:20px;right:20px;width:var(--cc-fab-size);height:var(--cc-fab-size);border-radius:50%;background:var(--cc-accent);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px #0000004d;transition:transform .2s ease,box-shadow .2s ease;z-index:10000;outline:none}.cc-fab:hover{transform:scale(1.05);box-shadow:0 6px 24px #0006}.cc-fab:active{transform:scale(.95)}.cc-fab.cc-left{right:auto;left:20px}.cc-fab svg{width:24px;height:24px;fill:var(--cc-text);transition:transform .3s ease}.cc-fab.cc-open svg{transform:rotate(90deg)}.cc-fab-badge{position:absolute;top:-2px;right:-2px;width:12px;height:12px;border-radius:50%;background:#ef4444;border:2px solid var(--cc-bg);display:none}.cc-fab-badge.cc-visible{display:block}@keyframes cc-pulse{0%,to{box-shadow:0 4px 16px #0000004d}50%{box-shadow:0 4px 16px #6366f180}}.cc-fab.cc-pulse{animation:cc-pulse 2s ease-in-out 3}.cc-panel{position:fixed;bottom:88px;right:20px;width:var(--cc-panel-width);height:var(--cc-panel-height);max-height:calc(100vh - 100px);background:var(--cc-bg);border-radius:var(--cc-radius);box-shadow:var(--cc-shadow);border:1px solid var(--cc-border);display:flex;flex-direction:column;overflow:hidden;z-index:9999;opacity:0;transform:translateY(16px) scale(.95);pointer-events:none;transition:opacity .2s ease,transform .2s ease}.cc-panel.cc-visible{opacity:1;transform:translateY(0) scale(1);pointer-events:auto}.cc-panel.cc-left{right:auto;left:20px}.cc-panel.cc-inline{position:relative;bottom:auto;right:auto;left:auto;width:100%;height:100%;max-height:100%;z-index:auto;opacity:1;transform:none;pointer-events:auto;transition:none}.cc-panel.cc-inline.cc-visible{transform:none}.cc-header{display:flex;align-items:center;padding:16px;background:var(--cc-bg-surface);border-bottom:1px solid var(--cc-border);flex-shrink:0}.cc-header-avatar{width:36px;height:36px;border-radius:50%;margin-right:12px;object-fit:cover}.cc-header-avatar-placeholder{width:36px;height:36px;border-radius:50%;margin-right:12px;background:var(--cc-accent);display:flex;align-items:center;justify-content:center}.cc-header-avatar-placeholder svg{width:20px;height:20px;fill:var(--cc-text)}.cc-header-info{flex:1}.cc-header-name{font-size:15px;font-weight:600;color:var(--cc-text)}.cc-header-subtitle{font-size:12px;color:var(--cc-text-secondary)}.cc-header-close{width:32px;height:32px;border-radius:8px;background:transparent;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease}.cc-header-close:hover{background:var(--cc-bg-input)}.cc-header-close svg{width:16px;height:16px;fill:var(--cc-text-secondary)}.cc-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.cc-messages::-webkit-scrollbar{width:4px}.cc-messages::-webkit-scrollbar-track{background:transparent}.cc-messages::-webkit-scrollbar-thumb{background:var(--cc-border);border-radius:2px}.cc-message{max-width:85%;padding:10px 14px;border-radius:var(--cc-radius);word-wrap:break-word;overflow-wrap:break-word;font-size:14px;line-height:1.5}.cc-message-user{align-self:flex-end;background:var(--cc-accent);color:var(--cc-text);border-bottom-right-radius:4px}.cc-message-assistant{align-self:flex-start;background:var(--cc-bg-surface);color:var(--cc-text);border-bottom-left-radius:4px}.cc-message a{color:var(--cc-accent);text-decoration:underline}.cc-message-user a{color:var(--cc-text)}.cc-message code{background:#0003;padding:1px 4px;border-radius:4px;font-family:SF Mono,Cascadia Code,Consolas,monospace;font-size:13px}.cc-message pre{background:#0000004d;padding:8px 12px;border-radius:8px;overflow-x:auto;margin:4px 0}.cc-message pre code{background:transparent;padding:0}.cc-message ul,.cc-message ol{padding-left:20px;margin:4px 0}.cc-typing{display:flex;align-items:center;gap:4px;padding:10px 14px;align-self:flex-start;background:var(--cc-bg-surface);border-radius:var(--cc-radius);border-bottom-left-radius:4px}.cc-typing-dot{width:6px;height:6px;border-radius:50%;background:var(--cc-text-secondary);animation:cc-bounce 1.2s infinite}.cc-typing-dot:nth-child(2){animation-delay:.2s}.cc-typing-dot:nth-child(3){animation-delay:.4s}@keyframes cc-bounce{0%,60%,to{transform:translateY(0)}30%{transform:translateY(-4px)}}.cc-input-area{display:flex;align-items:flex-end;padding:12px 16px;border-top:1px solid var(--cc-border);background:var(--cc-bg);gap:8px}.cc-input{flex:1;background:var(--cc-bg-input);border:1px solid var(--cc-border);border-radius:8px;padding:8px 12px;color:var(--cc-text);font-family:var(--cc-font);font-size:14px;line-height:1.5;resize:none;outline:none;max-height:96px;overflow-y:auto;transition:border-color .15s ease}.cc-input:focus{border-color:var(--cc-accent);outline:2px solid var(--cc-accent);outline-offset:1px}.cc-input::placeholder{color:var(--cc-text-secondary)}.cc-send-btn{width:36px;height:36px;border-radius:8px;background:var(--cc-accent);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s ease,opacity .15s ease}.cc-send-btn:hover{background:var(--cc-accent-hover)}.cc-send-btn:disabled{opacity:.4;cursor:not-allowed}.cc-send-btn svg{width:16px;height:16px;fill:var(--cc-text)}.cc-footer{padding:6px 16px;text-align:center;font-size:11px;color:var(--cc-text-secondary);border-top:1px solid var(--cc-border)}.cc-footer a{color:var(--cc-text-secondary);text-decoration:none}.cc-footer a:hover{text-decoration:underline}.cc-bubble{position:fixed;bottom:84px;right:20px;background:var(--cc-bg-surface);color:var(--cc-text);padding:10px 16px;border-radius:var(--cc-radius);box-shadow:var(--cc-shadow);border:1px solid var(--cc-border);max-width:260px;font-size:13px;line-height:1.4;z-index:9998;opacity:0;transform:translateY(8px);pointer-events:none;transition:opacity .3s ease,transform .3s ease;cursor:pointer}.cc-bubble.cc-visible{opacity:1;transform:translateY(0);pointer-events:auto}.cc-bubble.cc-left{right:auto;left:20px}.cc-bubble-close{position:absolute;top:-8px;right:-8px;width:20px;height:20px;border-radius:50%;background:var(--cc-bg);border:1px solid var(--cc-border);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:10px;color:var(--cc-text-secondary)}@media(max-width:768px){.cc-panel:not(.cc-inline){width:100%;height:100%;max-height:100%;bottom:0;right:0;left:0;border-radius:0;border:none}.cc-panel.cc-left:not(.cc-inline){left:0}.cc-bubble{bottom:84px;right:16px;left:16px;max-width:none}}",s.appendChild(i),s}const a='<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>';class n{constructor(e,t){s(this,"el"),s(this,"badge"),s(this,"isOpen",!1),this.el=document.createElement("button"),this.el.className="cc-fab cc-pulse",this.el.setAttribute("aria-label","Open chat"),"bottom-left"===t.position&&this.el.classList.add("cc-left"),this.el.innerHTML=a,this.badge=document.createElement("div"),this.badge.className="cc-fab-badge",this.el.appendChild(this.badge),this.el.addEventListener("click",()=>{this.el.classList.remove("cc-pulse"),t.onClick()}),e.appendChild(this.el)}setOpen(e){this.isOpen=e,this.el.innerHTML=e?'<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>':a,e||this.el.appendChild(this.badge),this.el.classList.toggle("cc-open",e),this.el.setAttribute("aria-label",e?"Close chat":"Open chat")}showBadge(){this.badge.classList.add("cc-visible")}hideBadge(){this.badge.classList.remove("cc-visible")}destroy(){this.el.remove()}}const o={"&":"&","<":"<",">":">",'"':""","'":"'"};function r(e){let t=e.replace(/[&<>"']/g,e=>o[e]??e);return t=t.replace(/```(?:\w*)\n?([\s\S]*?)```/g,(e,t)=>`<pre><code>${t.trim()}</code></pre>`),t=t.replace(/`([^`]+)`/g,"<code>$1</code>"),t=t.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),t=t.replace(/\*(.+?)\*/g,"<em>$1</em>"),t=t.replace(/\[([^\]]+)\]\(((?:[^()]*|\([^()]*\))*)\)/g,'<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'),t=t.replace(/^- (.+)$/gm,"<li>$1</li>"),t=t.replace(/(<li>.*<\/li>\n?)+/g,e=>`<ul>${e}</ul>`),t=t.replace(/^\d+\. (.+)$/gm,"<li>$1</li>"),t=t.replace(/\n\n/g,"<br><br>"),t=t.replace(/\n/g,"<br>"),t}class c{constructor(e){s(this,"container"),s(this,"typingEl",null),this.container=document.createElement("div"),this.container.className="cc-messages",e.appendChild(this.container)}addMessage(e){this.removeTyping();const t=document.createElement("div");return t.className=`cc-message cc-message-${e.role}`,t.dataset.id=e.id,t.innerHTML="assistant"===e.role?r(e.content):this.escapeHtml(e.content),this.container.appendChild(t),this.scrollToBottom(),t}updateMessage(e,t,s){const i=this.container.querySelector(`[data-id="${e}"]`);i&&(i.innerHTML=s?r(t):this.escapeHtml(t),this.scrollToBottom())}showTyping(){this.typingEl||(this.typingEl=document.createElement("div"),this.typingEl.className="cc-typing",this.typingEl.innerHTML='<div class="cc-typing-dot"></div><div class="cc-typing-dot"></div><div class="cc-typing-dot"></div>',this.container.appendChild(this.typingEl),this.scrollToBottom())}removeTyping(){this.typingEl&&(this.typingEl.remove(),this.typingEl=null)}clear(){this.container.innerHTML="",this.typingEl=null}scrollToBottom(){this.container.scrollTop=this.container.scrollHeight}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}}class l{constructor(e,t){s(this,"area"),s(this,"textarea"),s(this,"sendBtn"),s(this,"onSend"),this.onSend=t.onSend,this.area=document.createElement("div"),this.area.className="cc-input-area",this.textarea=document.createElement("textarea"),this.textarea.className="cc-input",this.textarea.placeholder=t.placeholder,this.textarea.rows=1,this.textarea.addEventListener("input",()=>this.autoResize()),this.textarea.addEventListener("keydown",e=>this.handleKeydown(e)),this.sendBtn=document.createElement("button"),this.sendBtn.className="cc-send-btn",this.sendBtn.disabled=!0,this.sendBtn.setAttribute("aria-label","Send message"),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>',this.sendBtn.addEventListener("click",()=>this.send()),this.textarea.addEventListener("input",()=>{this.sendBtn.disabled=0===this.textarea.value.trim().length}),this.area.appendChild(this.textarea),this.area.appendChild(this.sendBtn),e.appendChild(this.area)}focus(){this.textarea.focus()}setDisabled(e){this.textarea.disabled=e,this.sendBtn.disabled=e||0===this.textarea.value.trim().length}clear(){this.textarea.value="",this.sendBtn.disabled=!0,this.autoResize()}send(){const e=this.textarea.value.trim();e&&(this.onSend(e),this.clear())}handleKeydown(e){"Enter"!==e.key||e.shiftKey||(e.preventDefault(),this.send())}autoResize(){this.textarea.style.height="auto";this.textarea.style.height=Math.min(this.textarea.scrollHeight,96)+"px"}}class d{constructor(e,t){s(this,"el"),s(this,"inline"),s(this,"messages"),s(this,"input"),this.inline=t.inline??!1,this.el=document.createElement("div"),this.el.className="cc-panel",t.inline&&this.el.classList.add("cc-inline"),"bottom-left"===t.position&&this.el.classList.add("cc-left");const i=document.createElement("div");if(i.className="cc-header",t.branding.avatar){const e=document.createElement("img");e.className="cc-header-avatar",e.src=t.branding.avatar,e.alt=t.branding.name,i.appendChild(e)}else{const e=document.createElement("div");e.className="cc-header-avatar-placeholder",e.innerHTML='<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>',i.appendChild(e)}const a=document.createElement("div");if(a.className="cc-header-info",a.innerHTML=`<div class="cc-header-name">${this.escapeHtml(t.branding.name)}</div><div class="cc-header-subtitle">${this.escapeHtml(t.branding.subtitle)}</div>`,i.appendChild(a),!t.inline){const e=document.createElement("button");e.className="cc-header-close",e.setAttribute("aria-label","Close chat"),e.innerHTML='<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>',e.addEventListener("click",t.onClose),i.appendChild(e)}this.el.appendChild(i),this.messages=new c(this.el),this.input=new l(this.el,{placeholder:t.placeholder,onSend:t.onSend});const n=document.createElement("div");n.className="cc-footer",n.innerHTML=t.footerText,this.el.appendChild(n),e.appendChild(this.el)}show(){this.el.classList.add("cc-visible"),this.inline||this.input.focus()}hide(){this.el.classList.remove("cc-visible")}get isVisible(){return this.el.classList.contains("cc-visible")}addMessage(e){return this.messages.addMessage(e)}destroy(){this.el.remove()}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}}const h="chatcops_bubble_shown";class p{constructor(e,t){s(this,"el"),s(this,"timerId",null),this.el=document.createElement("div"),this.el.className="cc-bubble","bottom-left"===t.position&&this.el.classList.add("cc-left"),this.el.textContent=t.text;const i=document.createElement("button");if(i.className="cc-bubble-close",i.innerHTML="×",i.addEventListener("click",e=>{e.stopPropagation(),this.hide()}),this.el.appendChild(i),this.el.addEventListener("click",()=>{this.hide(),t.onClick()}),e.appendChild(this.el),t.showOnce)try{if(sessionStorage.getItem(h))return;sessionStorage.setItem(h,"1")}catch{}this.timerId=setTimeout(()=>{this.el.classList.add("cc-visible")},t.delay??3e3)}hide(){this.el.classList.remove("cc-visible"),this.timerId&&(clearTimeout(this.timerId),this.timerId=null)}destroy(){this.hide(),this.el.remove()}}class g{constructor(e){s(this,"apiUrl"),s(this,"timeout"),this.apiUrl=e.apiUrl,this.timeout=e.timeout??3e4}async*sendMessage(e){var t;const s=new AbortController,i=setTimeout(()=>s.abort(),this.timeout);try{const i=await fetch(this.apiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:s.signal});if(!i.ok)return 429===i.status?void(yield{error:"rate_limit"}):void(yield{error:`HTTP ${i.status}`});const a=null==(t=i.body)?void 0:t.getReader();if(!a)return void(yield{error:"No response body"});const n=new TextDecoder;let o="";for(;;){const{done:e,value:t}=await a.read();if(e)break;o+=n.decode(t,{stream:!0});const s=o.split("\n");o=s.pop()??"";for(const i of s){const e=i.trim();if(!e||!e.startsWith("data: "))continue;const t=e.slice(6);if("[DONE]"===t)return void(yield{done:!0});try{yield JSON.parse(t)}catch{}}}yield{done:!0}}catch(a){a instanceof DOMException&&"AbortError"===a.name?yield{error:"timeout"}:yield{error:"network"}}finally{clearTimeout(i)}}}const u="chatcops_conversation";class m{constructor(e){s(this,"key"),this.key=e?`${u}_${e}`:u}load(){try{const e=localStorage.getItem(this.key);return e?JSON.parse(e):null}catch{return null}}save(e){try{localStorage.setItem(this.key,JSON.stringify(e))}catch{}}clear(){try{localStorage.removeItem(this.key)}catch{}}getSessionId(){const e="chatcops_session_id";try{let t=sessionStorage.getItem(e);return t||(t=crypto.randomUUID(),sessionStorage.setItem(e,t)),t}catch{return crypto.randomUUID()}}}function b(e,t){const s=e.host;t.accent&&(s.style.setProperty("--cc-accent",t.accent),s.style.setProperty("--cc-accent-hover",function(e,t){const[s,i,a]=f(e),n=1-t/100;return v(Math.round(s*n),Math.round(i*n),Math.round(a*n))}(t.accent,10))),t.textColor&&s.style.setProperty("--cc-text",t.textColor),t.bgColor&&(s.style.setProperty("--cc-bg",t.bgColor),s.style.setProperty("--cc-bg-surface",x(t.bgColor,8)),s.style.setProperty("--cc-bg-input",x(t.bgColor,14))),t.fontFamily&&s.style.setProperty("--cc-font",t.fontFamily),t.borderRadius&&s.style.setProperty("--cc-radius",t.borderRadius)}function f(e){if(!/^#?[\da-f]{3}(?:[\da-f]{3})?$/i.test(e))return[0,0,0];const t=e.replace("#",""),s=parseInt(3===t.length?t.split("").map(e=>e+e).join(""):t,16);return[s>>16&255,s>>8&255,255&s]}function v(e,t,s){return"#"+[e,t,s].map(e=>Math.max(0,Math.min(255,e)).toString(16).padStart(2,"0")).join("")}function x(e,t){const[s,i,a]=f(e),n=t/100;return v(Math.round(s+(255-s)*n),Math.round(i+(255-i)*n),Math.round(a+(255-a)*n))}const y={welcomeMessage:"Hi! How can I help you today?",inputPlaceholder:"Type a message...",sendButton:"Send",closeButton:"Close",errorGeneric:"Something went wrong. Please try again.",errorNetwork:"Connection error. Please check your network.",errorRateLimit:"Too many messages. Please wait a moment.",typingIndicator:"Typing...",poweredBy:"Powered by ChatCops",newConversation:"New conversation",welcomeBubbleDefault:"Need help? Chat with us!"},w={en:y};class C{constructor(e){s(this,"config"),s(this,"shadow"),s(this,"fab"),s(this,"panel"),s(this,"bubble"),s(this,"client"),s(this,"storage"),s(this,"locale"),s(this,"conversationId"),s(this,"messages",[]),s(this,"isStreaming",!1),s(this,"eventHandlers",new Map),s(this,"containerEl"),this.config=e,this.client=new g({apiUrl:e.apiUrl}),this.storage=new m,this.locale=function(e,t){const s=w[e]??y;return t?{...s,...t}:s}(e.locale??"en",e.strings),this.conversationId=this.storage.getSessionId()}get isInline(){return"inline"===this.config.mode}resolveContainer(){if(!this.config.container)throw new Error('ChatCops: "container" is required when mode is "inline"');if("string"==typeof this.config.container){const e=document.querySelector(this.config.container);if(!e)throw new Error(`ChatCops: container element not found: ${this.config.container}`);return e}return this.config.container}init(){var e,t,s,a;this.isInline?(this.containerEl=this.resolveContainer(),this.shadow=i(this.containerEl)):this.shadow=i(),this.config.theme&&b(this.shadow,this.config.theme);const o=(null==(e=this.config.theme)?void 0:e.position)??"bottom-right";if(this.isInline||(this.fab=new n(this.shadow,{position:o,onClick:()=>this.toggle()})),this.panel=new d(this.shadow,{position:o,inline:this.isInline,branding:{name:(null==(t=this.config.branding)?void 0:t.name)??"AI Assistant",avatar:null==(s=this.config.branding)?void 0:s.avatar,subtitle:(null==(a=this.config.branding)?void 0:a.subtitle)??"Online"},placeholder:this.config.placeholder??this.locale.inputPlaceholder,footerText:this.locale.poweredBy,onSend:e=>this.handleSend(e),onClose:()=>this.close()}),!1!==this.config.persistHistory){const e=this.storage.load();if(e){this.conversationId=e.id;for(const t of e.messages)this.messages.push(t),this.panel.addMessage(t)}}if(0===this.messages.length){const e={id:"welcome",role:"assistant",content:this.config.welcomeMessage??this.locale.welcomeMessage};this.messages.push(e),this.panel.addMessage(e)}if(!this.isInline&&this.config.welcomeBubble&&(this.bubble=new p(this.shadow,{text:this.config.welcomeBubble.text,delay:this.config.welcomeBubble.delay,showOnce:this.config.welcomeBubble.showOnce??!0,position:o,onClick:()=>this.open()})),this.isInline&&this.panel.show(),!this.isInline&&void 0!==this.config.autoOpen&&!1!==this.config.autoOpen){const e="number"==typeof this.config.autoOpen?this.config.autoOpen:0;setTimeout(()=>this.open(),e)}}open(){var e,t,s,i,a;this.panel.show(),this.isInline||(null==(e=this.fab)||e.setOpen(!0),null==(t=this.fab)||t.hideBadge(),null==(s=this.bubble)||s.hide()),null==(a=(i=this.config).onOpen)||a.call(i),this.emit("open")}close(){var e,t,s;this.panel.hide(),this.isInline||null==(e=this.fab)||e.setOpen(!1),null==(s=(t=this.config).onClose)||s.call(t),this.emit("close")}toggle(){this.panel.isVisible?this.close():this.open()}destroy(){var e,t;null==(e=this.bubble)||e.destroy(),this.panel.destroy(),null==(t=this.fab)||t.destroy(),function(e){const t=(e??document.body).querySelector("#chatcops-root")??document.getElementById("chatcops-root");t&&t.remove()}(this.containerEl),this.eventHandlers.clear(),this.messages=[]}on(e,t){this.eventHandlers.has(e)||this.eventHandlers.set(e,new Set),this.eventHandlers.get(e).add(t)}off(e,t){var s;null==(s=this.eventHandlers.get(e))||s.delete(t)}emit(e,...t){var s;null==(s=this.eventHandlers.get(e))||s.forEach(e=>e(...t))}async handleSend(e){var t,s,i,a,n,o,r,c,l;if(this.isStreaming)return;const d={id:crypto.randomUUID(),role:"user",content:e};this.messages.push(d),this.panel.addMessage(d),null==(s=(t=this.config).onMessage)||s.call(t,d),this.emit("message",d),this.isStreaming=!0,this.panel.input.setDisabled(!0),this.panel.messages.showTyping();const h={id:crypto.randomUUID(),role:"assistant",content:""},p=!1!==this.config.pageContext?{url:window.location.href,title:document.title}:void 0;try{let t=!0;for await(const s of this.client.sendMessage({conversationId:this.conversationId,message:e,pageContext:p,locale:this.config.locale})){if(s.error){const e="rate_limit"===s.error?"errorRateLimit":"network"===s.error?"errorNetwork":"errorGeneric";h.content=this.locale[e],this.panel.messages.removeTyping(),this.panel.addMessage(h),null==(a=(i=this.config).onError)||a.call(i,new Error(s.error)),this.emit("error",new Error(s.error));break}if(s.done)break;s.content&&(t&&(this.panel.messages.removeTyping(),this.panel.addMessage(h),t=!1),h.content+=s.content,this.panel.messages.updateMessage(h.id,h.content,!0))}h.content&&(this.messages.push(h),null==(o=(n=this.config).onMessage)||o.call(n,h),this.emit("message",h),this.isInline||this.panel.isVisible||null==(r=this.fab)||r.showBadge())}catch(g){this.panel.messages.removeTyping(),h.content=this.locale.errorGeneric,this.panel.addMessage(h),null==(l=(c=this.config).onError)||l.call(c,g instanceof Error?g:new Error(String(g)))}finally{this.isStreaming=!1,this.panel.input.setDisabled(!1),this.panel.input.focus(),this.saveHistory()}}saveHistory(){if(!1===this.config.persistHistory)return;const e=this.config.maxMessages??50,t=this.messages.filter(e=>"welcome"!==e.id).slice(-e);this.storage.save({id:this.conversationId,messages:t.map(e=>({id:e.id,role:e.role,content:e.content,timestamp:Date.now()})),updatedAt:Date.now()})}}let k=null;const E={init:e=>(k&&k.destroy(),k=new C(e),k.init(),k),open(){null==k||k.open()},close(){null==k||k.close()},destroy(){null==k||k.destroy(),k=null},on(e,t){null==k||k.on(e,t)},off(e,t){null==k||k.off(e,t)}};function M(){const e=document.currentScript;if(!e)return;const t=e.dataset.apiUrl;if(!t)return;const s=e.dataset.mode,i=e.dataset.container;E.init({apiUrl:t,mode:s||void 0,container:i||void 0,theme:{accent:e.dataset.accent,textColor:e.dataset.textColor,bgColor:e.dataset.bgColor,fontFamily:e.dataset.fontFamily,borderRadius:e.dataset.borderRadius,position:e.dataset.position||void 0},branding:{name:e.dataset.brandName,avatar:e.dataset.brandAvatar,subtitle:e.dataset.brandSubtitle},welcomeMessage:e.dataset.welcomeMessage,welcomeBubble:e.dataset.welcomeBubble?{text:e.dataset.welcomeBubble,delay:e.dataset.welcomeBubbleDelay?parseInt(e.dataset.welcomeBubbleDelay,10):void 0}:void 0,autoOpen:void 0!==e.dataset.autoOpen?""===e.dataset.autoOpen||"true"===e.dataset.autoOpen||(parseInt(e.dataset.autoOpen,10)||!0):void 0,placeholder:e.dataset.placeholder,locale:e.dataset.locale})}const S=document.currentScript;return S&&S.dataset.apiUrl&&("loading"===document.readyState?document.addEventListener("DOMContentLoaded",M):M()),"undefined"!=typeof window&&(window.ChatCops=E),e.Widget=C,e.default=E,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}}),e}({});
|
package/dist/dom/panel.d.ts
CHANGED
package/dist/dom/panel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/dom/panel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAKnC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,cAAc,GAAG,aAAa,CAAC;IACzC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,qBAAa,KAAK;IAChB,OAAO,CAAC,EAAE,CAAiB;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;gBAED,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY;
|
|
1
|
+
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/dom/panel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAKnC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,cAAc,GAAG,aAAa,CAAC;IACzC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,qBAAa,KAAK;IAChB,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,MAAM,CAAU;IACxB,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;gBAED,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY;IA8DnD,IAAI,IAAI,IAAI;IAOZ,IAAI,IAAI,IAAI;IAIZ,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,UAAU,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc;IAI5C,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,UAAU;CAKnB"}
|