@chatcops/widget 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CoderCops
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
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
@@ -0,0 +1,12 @@
1
+ import type { WidgetChatRequest, WidgetChatChunk } from './types.js';
2
+ export interface ChatClientOptions {
3
+ apiUrl: string;
4
+ timeout?: number;
5
+ }
6
+ export declare class ChatClient {
7
+ private apiUrl;
8
+ private timeout;
9
+ constructor(options: ChatClientOptions);
10
+ sendMessage(request: WidgetChatRequest): AsyncGenerator<WidgetChatChunk>;
11
+ }
12
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,iBAAiB;IAK/B,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAAC,eAAe,CAAC;CAmEhF"}
@@ -0,0 +1,19 @@
1
+ export interface WidgetChatRequest {
2
+ conversationId: string;
3
+ message: string;
4
+ pageContext?: {
5
+ url: string;
6
+ title: string;
7
+ description?: string;
8
+ contentSnippet?: string;
9
+ };
10
+ locale?: string;
11
+ }
12
+ export interface WidgetChatChunk {
13
+ content?: string;
14
+ done?: boolean;
15
+ error?: string;
16
+ leadCaptured?: boolean;
17
+ suggestedActions?: string[];
18
+ }
19
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B"}
@@ -1,19 +1,24 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- const widgetStyles = ":host{--cc-accent: #6366f1;--cc-accent-hover: #4f46e5;--cc-text: #ffffff;--cc-bg: #0a0a0a;--cc-bg-surface: #1a1a1a;--cc-bg-input: #2a2a2a;--cc-border: #333333;--cc-text-secondary: #a0a0a0;--cc-font: system-ui, -apple-system, sans-serif;--cc-radius: 12px;--cc-shadow: 0 8px 32px rgba(0, 0, 0, .4);--cc-fab-size: 56px;--cc-panel-width: 400px;--cc-panel-height: 560px;all:initial;font-family:var(--cc-font);font-size:14px;line-height:1.5;color:var(--cc-text);-webkit-font-smoothing:antialiased}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}.cc-fab{position:fixed;bottom:20px;right:20px;width:var(--cc-fab-size);height:var(--cc-fab-size);border-radius:50%;background:var(--cc-accent);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px #0000004d;transition:transform .2s ease,box-shadow .2s ease;z-index:10000;outline:none}.cc-fab:hover{transform:scale(1.05);box-shadow:0 6px 24px #0006}.cc-fab:active{transform:scale(.95)}.cc-fab.cc-left{right:auto;left:20px}.cc-fab svg{width:24px;height:24px;fill:var(--cc-text);transition:transform .3s ease}.cc-fab.cc-open svg{transform:rotate(90deg)}.cc-fab-badge{position:absolute;top:-2px;right:-2px;width:12px;height:12px;border-radius:50%;background:#ef4444;border:2px solid var(--cc-bg);display:none}.cc-fab-badge.cc-visible{display:block}@keyframes cc-pulse{0%,to{box-shadow:0 4px 16px #0000004d}50%{box-shadow:0 4px 16px #6366f180}}.cc-fab.cc-pulse{animation:cc-pulse 2s ease-in-out 3}.cc-panel{position:fixed;bottom:88px;right:20px;width:var(--cc-panel-width);height:var(--cc-panel-height);max-height:calc(100vh - 100px);background:var(--cc-bg);border-radius:var(--cc-radius);box-shadow:var(--cc-shadow);border:1px solid var(--cc-border);display:flex;flex-direction:column;overflow:hidden;z-index:9999;opacity:0;transform:translateY(16px) scale(.95);pointer-events:none;transition:opacity .2s ease,transform .2s ease}.cc-panel.cc-visible{opacity:1;transform:translateY(0) scale(1);pointer-events:auto}.cc-panel.cc-left{right:auto;left:20px}.cc-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{width:100%;height:100%;max-height:100%;bottom:0;right:0;left:0;border-radius:0;border:none}.cc-panel.cc-left{left:0}.cc-bubble{bottom:84px;right:16px;left:16px;max-width:none}}";
5
- function createShadowRoot() {
4
+ const widgetStyles = ":host{--cc-accent: #6366f1;--cc-accent-hover: #4f46e5;--cc-text: #ffffff;--cc-bg: #0a0a0a;--cc-bg-surface: #1a1a1a;--cc-bg-input: #2a2a2a;--cc-border: #333333;--cc-text-secondary: #a0a0a0;--cc-font: system-ui, -apple-system, sans-serif;--cc-radius: 12px;--cc-shadow: 0 8px 32px rgba(0, 0, 0, .4);--cc-fab-size: 56px;--cc-panel-width: 400px;--cc-panel-height: 560px;all:initial;font-family:var(--cc-font);font-size:14px;line-height:1.5;color:var(--cc-text);-webkit-font-smoothing:antialiased}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}.cc-fab{position:fixed;bottom:20px;right:20px;width:var(--cc-fab-size);height:var(--cc-fab-size);border-radius:50%;background:var(--cc-accent);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px #0000004d;transition:transform .2s ease,box-shadow .2s ease;z-index:10000;outline:none}.cc-fab:hover{transform:scale(1.05);box-shadow:0 6px 24px #0006}.cc-fab:active{transform:scale(.95)}.cc-fab.cc-left{right:auto;left:20px}.cc-fab svg{width:24px;height:24px;fill:var(--cc-text);transition:transform .3s ease}.cc-fab.cc-open svg{transform:rotate(90deg)}.cc-fab-badge{position:absolute;top:-2px;right:-2px;width:12px;height:12px;border-radius:50%;background:#ef4444;border:2px solid var(--cc-bg);display:none}.cc-fab-badge.cc-visible{display:block}@keyframes cc-pulse{0%,to{box-shadow:0 4px 16px #0000004d}50%{box-shadow:0 4px 16px #6366f180}}.cc-fab.cc-pulse{animation:cc-pulse 2s ease-in-out 3}.cc-panel{position:fixed;bottom:88px;right:20px;width:var(--cc-panel-width);height:var(--cc-panel-height);max-height:calc(100vh - 100px);background:var(--cc-bg);border-radius:var(--cc-radius);box-shadow:var(--cc-shadow);border:1px solid var(--cc-border);display:flex;flex-direction:column;overflow:hidden;z-index:9999;opacity:0;transform:translateY(16px) scale(.95);pointer-events:none;transition:opacity .2s ease,transform .2s ease}.cc-panel.cc-visible{opacity:1;transform:translateY(0) scale(1);pointer-events:auto}.cc-panel.cc-left{right:auto;left:20px}.cc-panel.cc-inline{position:relative;bottom:auto;right:auto;left:auto;width:100%;height:100%;max-height:100%;z-index:auto;opacity:1;transform:none;pointer-events:auto;transition:none}.cc-panel.cc-inline.cc-visible{transform:none}.cc-header{display:flex;align-items:center;padding:16px;background:var(--cc-bg-surface);border-bottom:1px solid var(--cc-border);flex-shrink:0}.cc-header-avatar{width:36px;height:36px;border-radius:50%;margin-right:12px;object-fit:cover}.cc-header-avatar-placeholder{width:36px;height:36px;border-radius:50%;margin-right:12px;background:var(--cc-accent);display:flex;align-items:center;justify-content:center}.cc-header-avatar-placeholder svg{width:20px;height:20px;fill:var(--cc-text)}.cc-header-info{flex:1}.cc-header-name{font-size:15px;font-weight:600;color:var(--cc-text)}.cc-header-subtitle{font-size:12px;color:var(--cc-text-secondary)}.cc-header-close{width:32px;height:32px;border-radius:8px;background:transparent;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease}.cc-header-close:hover{background:var(--cc-bg-input)}.cc-header-close svg{width:16px;height:16px;fill:var(--cc-text-secondary)}.cc-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.cc-messages::-webkit-scrollbar{width:4px}.cc-messages::-webkit-scrollbar-track{background:transparent}.cc-messages::-webkit-scrollbar-thumb{background:var(--cc-border);border-radius:2px}.cc-message{max-width:85%;padding:10px 14px;border-radius:var(--cc-radius);word-wrap:break-word;overflow-wrap:break-word;font-size:14px;line-height:1.5}.cc-message-user{align-self:flex-end;background:var(--cc-accent);color:var(--cc-text);border-bottom-right-radius:4px}.cc-message-assistant{align-self:flex-start;background:var(--cc-bg-surface);color:var(--cc-text);border-bottom-left-radius:4px}.cc-message a{color:var(--cc-accent);text-decoration:underline}.cc-message-user a{color:var(--cc-text)}.cc-message code{background:#0003;padding:1px 4px;border-radius:4px;font-family:SF Mono,Cascadia Code,Consolas,monospace;font-size:13px}.cc-message pre{background:#0000004d;padding:8px 12px;border-radius:8px;overflow-x:auto;margin:4px 0}.cc-message pre code{background:transparent;padding:0}.cc-message ul,.cc-message ol{padding-left:20px;margin:4px 0}.cc-typing{display:flex;align-items:center;gap:4px;padding:10px 14px;align-self:flex-start;background:var(--cc-bg-surface);border-radius:var(--cc-radius);border-bottom-left-radius:4px}.cc-typing-dot{width:6px;height:6px;border-radius:50%;background:var(--cc-text-secondary);animation:cc-bounce 1.2s infinite}.cc-typing-dot:nth-child(2){animation-delay:.2s}.cc-typing-dot:nth-child(3){animation-delay:.4s}@keyframes cc-bounce{0%,60%,to{transform:translateY(0)}30%{transform:translateY(-4px)}}.cc-input-area{display:flex;align-items:flex-end;padding:12px 16px;border-top:1px solid var(--cc-border);background:var(--cc-bg);gap:8px}.cc-input{flex:1;background:var(--cc-bg-input);border:1px solid var(--cc-border);border-radius:8px;padding:8px 12px;color:var(--cc-text);font-family:var(--cc-font);font-size:14px;line-height:1.5;resize:none;outline:none;max-height:96px;overflow-y:auto;transition:border-color .15s ease}.cc-input:focus{border-color:var(--cc-accent);outline:2px solid var(--cc-accent);outline-offset:1px}.cc-input::placeholder{color:var(--cc-text-secondary)}.cc-send-btn{width:36px;height:36px;border-radius:8px;background:var(--cc-accent);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s ease,opacity .15s ease}.cc-send-btn:hover{background:var(--cc-accent-hover)}.cc-send-btn:disabled{opacity:.4;cursor:not-allowed}.cc-send-btn svg{width:16px;height:16px;fill:var(--cc-text)}.cc-footer{padding:6px 16px;text-align:center;font-size:11px;color:var(--cc-text-secondary);border-top:1px solid var(--cc-border)}.cc-footer a{color:var(--cc-text-secondary);text-decoration:none}.cc-footer a:hover{text-decoration:underline}.cc-bubble{position:fixed;bottom:84px;right:20px;background:var(--cc-bg-surface);color:var(--cc-text);padding:10px 16px;border-radius:var(--cc-radius);box-shadow:var(--cc-shadow);border:1px solid var(--cc-border);max-width:260px;font-size:13px;line-height:1.4;z-index:9998;opacity:0;transform:translateY(8px);pointer-events:none;transition:opacity .3s ease,transform .3s ease;cursor:pointer}.cc-bubble.cc-visible{opacity:1;transform:translateY(0);pointer-events:auto}.cc-bubble.cc-left{right:auto;left:20px}.cc-bubble-close{position:absolute;top:-8px;right:-8px;width:20px;height:20px;border-radius:50%;background:var(--cc-bg);border:1px solid var(--cc-border);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:10px;color:var(--cc-text-secondary)}@media(max-width:768px){.cc-panel:not(.cc-inline){width:100%;height:100%;max-height:100%;bottom:0;right:0;left:0;border-radius:0;border:none}.cc-panel.cc-left:not(.cc-inline){left:0}.cc-bubble{bottom:84px;right:16px;left:16px;max-width:none}}";
5
+ function createShadowRoot(container) {
6
6
  const host = document.createElement("div");
7
7
  host.id = "chatcops-root";
8
- document.body.appendChild(host);
8
+ if (container) {
9
+ container.appendChild(host);
10
+ } else {
11
+ document.body.appendChild(host);
12
+ }
9
13
  const shadow = host.attachShadow({ mode: "open" });
10
14
  const style = document.createElement("style");
11
15
  style.textContent = widgetStyles;
12
16
  shadow.appendChild(style);
13
17
  return shadow;
14
18
  }
15
- function destroyShadowRoot() {
16
- const host = document.getElementById("chatcops-root");
19
+ function destroyShadowRoot(container) {
20
+ const searchRoot = container ?? document.body;
21
+ const host = searchRoot.querySelector("#chatcops-root") ?? document.getElementById("chatcops-root");
17
22
  if (host) host.remove();
18
23
  }
19
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>`;
@@ -200,10 +205,15 @@ const CLOSE_ICON = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
200
205
  class Panel {
201
206
  constructor(root, options) {
202
207
  __publicField(this, "el");
208
+ __publicField(this, "inline");
203
209
  __publicField(this, "messages");
204
210
  __publicField(this, "input");
211
+ this.inline = options.inline ?? false;
205
212
  this.el = document.createElement("div");
206
213
  this.el.className = "cc-panel";
214
+ if (options.inline) {
215
+ this.el.classList.add("cc-inline");
216
+ }
207
217
  if (options.position === "bottom-left") {
208
218
  this.el.classList.add("cc-left");
209
219
  }
@@ -225,12 +235,14 @@ class Panel {
225
235
  info.className = "cc-header-info";
226
236
  info.innerHTML = `<div class="cc-header-name">${this.escapeHtml(options.branding.name)}</div><div class="cc-header-subtitle">${this.escapeHtml(options.branding.subtitle)}</div>`;
227
237
  header.appendChild(info);
228
- const closeBtn = document.createElement("button");
229
- closeBtn.className = "cc-header-close";
230
- closeBtn.setAttribute("aria-label", "Close chat");
231
- closeBtn.innerHTML = CLOSE_ICON;
232
- closeBtn.addEventListener("click", options.onClose);
233
- header.appendChild(closeBtn);
238
+ if (!options.inline) {
239
+ const closeBtn = document.createElement("button");
240
+ closeBtn.className = "cc-header-close";
241
+ closeBtn.setAttribute("aria-label", "Close chat");
242
+ closeBtn.innerHTML = CLOSE_ICON;
243
+ closeBtn.addEventListener("click", options.onClose);
244
+ header.appendChild(closeBtn);
245
+ }
234
246
  this.el.appendChild(header);
235
247
  this.messages = new Messages(this.el);
236
248
  this.input = new Input(this.el, {
@@ -245,7 +257,9 @@ class Panel {
245
257
  }
246
258
  show() {
247
259
  this.el.classList.add("cc-visible");
248
- this.input.focus();
260
+ if (!this.inline) {
261
+ this.input.focus();
262
+ }
249
263
  }
250
264
  hide() {
251
265
  this.el.classList.remove("cc-visible");
@@ -490,25 +504,50 @@ class Widget {
490
504
  __publicField(this, "messages", []);
491
505
  __publicField(this, "isStreaming", false);
492
506
  __publicField(this, "eventHandlers", /* @__PURE__ */ new Map());
507
+ __publicField(this, "containerEl");
493
508
  this.config = config;
494
509
  this.client = new ChatClient({ apiUrl: config.apiUrl });
495
510
  this.storage = new ConversationStorage();
496
511
  this.locale = getWidgetLocale(config.locale ?? "en", config.strings);
497
512
  this.conversationId = this.storage.getSessionId();
498
513
  }
514
+ get isInline() {
515
+ return this.config.mode === "inline";
516
+ }
517
+ resolveContainer() {
518
+ if (!this.config.container) {
519
+ throw new Error('ChatCops: "container" is required when mode is "inline"');
520
+ }
521
+ if (typeof this.config.container === "string") {
522
+ const el = document.querySelector(this.config.container);
523
+ if (!el) {
524
+ throw new Error(`ChatCops: container element not found: ${this.config.container}`);
525
+ }
526
+ return el;
527
+ }
528
+ return this.config.container;
529
+ }
499
530
  init() {
500
531
  var _a, _b, _c, _d;
501
- this.shadow = createShadowRoot();
532
+ if (this.isInline) {
533
+ this.containerEl = this.resolveContainer();
534
+ this.shadow = createShadowRoot(this.containerEl);
535
+ } else {
536
+ this.shadow = createShadowRoot();
537
+ }
502
538
  if (this.config.theme) {
503
539
  applyTheme(this.shadow, this.config.theme);
504
540
  }
505
541
  const position = ((_a = this.config.theme) == null ? void 0 : _a.position) ?? "bottom-right";
506
- this.fab = new FAB(this.shadow, {
507
- position,
508
- onClick: () => this.toggle()
509
- });
542
+ if (!this.isInline) {
543
+ this.fab = new FAB(this.shadow, {
544
+ position,
545
+ onClick: () => this.toggle()
546
+ });
547
+ }
510
548
  this.panel = new Panel(this.shadow, {
511
549
  position,
550
+ inline: this.isInline,
512
551
  branding: {
513
552
  name: ((_b = this.config.branding) == null ? void 0 : _b.name) ?? "AI Assistant",
514
553
  avatar: (_c = this.config.branding) == null ? void 0 : _c.avatar,
@@ -539,7 +578,7 @@ class Widget {
539
578
  this.messages.push(welcomeMsg);
540
579
  this.panel.addMessage(welcomeMsg);
541
580
  }
542
- if (this.config.welcomeBubble) {
581
+ if (!this.isInline && this.config.welcomeBubble) {
543
582
  this.bubble = new WelcomeBubble(this.shadow, {
544
583
  text: this.config.welcomeBubble.text,
545
584
  delay: this.config.welcomeBubble.delay,
@@ -548,21 +587,32 @@ class Widget {
548
587
  onClick: () => this.open()
549
588
  });
550
589
  }
590
+ if (this.isInline) {
591
+ this.panel.show();
592
+ }
593
+ if (!this.isInline && this.config.autoOpen !== void 0 && this.config.autoOpen !== false) {
594
+ const delay = typeof this.config.autoOpen === "number" ? this.config.autoOpen : 0;
595
+ setTimeout(() => this.open(), delay);
596
+ }
551
597
  }
552
598
  open() {
553
- var _a, _b, _c;
599
+ var _a, _b, _c, _d, _e;
554
600
  this.panel.show();
555
- this.fab.setOpen(true);
556
- this.fab.hideBadge();
557
- (_a = this.bubble) == null ? void 0 : _a.hide();
558
- (_c = (_b = this.config).onOpen) == null ? void 0 : _c.call(_b);
601
+ if (!this.isInline) {
602
+ (_a = this.fab) == null ? void 0 : _a.setOpen(true);
603
+ (_b = this.fab) == null ? void 0 : _b.hideBadge();
604
+ (_c = this.bubble) == null ? void 0 : _c.hide();
605
+ }
606
+ (_e = (_d = this.config).onOpen) == null ? void 0 : _e.call(_d);
559
607
  this.emit("open");
560
608
  }
561
609
  close() {
562
- var _a, _b;
610
+ var _a, _b, _c;
563
611
  this.panel.hide();
564
- this.fab.setOpen(false);
565
- (_b = (_a = this.config).onClose) == null ? void 0 : _b.call(_a);
612
+ if (!this.isInline) {
613
+ (_a = this.fab) == null ? void 0 : _a.setOpen(false);
614
+ }
615
+ (_c = (_b = this.config).onClose) == null ? void 0 : _c.call(_b);
566
616
  this.emit("close");
567
617
  }
568
618
  toggle() {
@@ -573,11 +623,11 @@ class Widget {
573
623
  }
574
624
  }
575
625
  destroy() {
576
- var _a;
626
+ var _a, _b;
577
627
  (_a = this.bubble) == null ? void 0 : _a.destroy();
578
628
  this.panel.destroy();
579
- this.fab.destroy();
580
- destroyShadowRoot();
629
+ (_b = this.fab) == null ? void 0 : _b.destroy();
630
+ destroyShadowRoot(this.containerEl);
581
631
  this.eventHandlers.clear();
582
632
  this.messages = [];
583
633
  }
@@ -596,7 +646,7 @@ class Widget {
596
646
  (_a = this.eventHandlers.get(event)) == null ? void 0 : _a.forEach((h) => h(...args));
597
647
  }
598
648
  async handleSend(text) {
599
- var _a, _b, _c, _d, _e, _f, _g, _h;
649
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
600
650
  if (this.isStreaming) return;
601
651
  const userMsg = {
602
652
  id: crypto.randomUUID(),
@@ -648,15 +698,15 @@ class Widget {
648
698
  this.messages.push(assistantMsg);
649
699
  (_f = (_e = this.config).onMessage) == null ? void 0 : _f.call(_e, assistantMsg);
650
700
  this.emit("message", assistantMsg);
651
- if (!this.panel.isVisible) {
652
- this.fab.showBadge();
701
+ if (!this.isInline && !this.panel.isVisible) {
702
+ (_g = this.fab) == null ? void 0 : _g.showBadge();
653
703
  }
654
704
  }
655
705
  } catch (err) {
656
706
  this.panel.messages.removeTyping();
657
707
  assistantMsg.content = this.locale.errorGeneric;
658
708
  this.panel.addMessage(assistantMsg);
659
- (_h = (_g = this.config).onError) == null ? void 0 : _h.call(_g, err instanceof Error ? err : new Error(String(err)));
709
+ (_i = (_h = this.config).onError) == null ? void 0 : _i.call(_h, err instanceof Error ? err : new Error(String(err)));
660
710
  } finally {
661
711
  this.isStreaming = false;
662
712
  this.panel.input.setDisabled(false);
@@ -712,8 +762,12 @@ function autoInit() {
712
762
  if (!script) return;
713
763
  const apiUrl = script.dataset.apiUrl;
714
764
  if (!apiUrl) return;
765
+ const mode = script.dataset.mode;
766
+ const container = script.dataset.container;
715
767
  ChatCops.init({
716
768
  apiUrl,
769
+ mode: mode || void 0,
770
+ container: container || void 0,
717
771
  theme: {
718
772
  accent: script.dataset.accent,
719
773
  textColor: script.dataset.textColor,
@@ -732,6 +786,7 @@ function autoInit() {
732
786
  text: script.dataset.welcomeBubble,
733
787
  delay: script.dataset.welcomeBubbleDelay ? parseInt(script.dataset.welcomeBubbleDelay, 10) : void 0
734
788
  } : void 0,
789
+ autoOpen: script.dataset.autoOpen !== void 0 ? script.dataset.autoOpen === "" || script.dataset.autoOpen === "true" ? true : parseInt(script.dataset.autoOpen, 10) || true : void 0,
735
790
  placeholder: script.dataset.placeholder,
736
791
  locale: script.dataset.locale
737
792
  });
@@ -748,5 +803,6 @@ if (typeof window !== "undefined") {
748
803
  window.ChatCops = ChatCops;
749
804
  }
750
805
  export {
806
+ Widget,
751
807
  ChatCops as default
752
808
  };
@@ -1 +1 @@
1
- var ChatCops=function(){"use strict";var e=Object.defineProperty,t=(t,s,a)=>((t,s,a)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:a}):t[s]=a)(t,"symbol"!=typeof s?s+"":s,a);const s='<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 a{constructor(e,a){t(this,"el"),t(this,"badge"),t(this,"isOpen",!1),this.el=document.createElement("button"),this.el.className="cc-fab cc-pulse",this.el.setAttribute("aria-label","Open chat"),"bottom-left"===a.position&&this.el.classList.add("cc-left"),this.el.innerHTML=s,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"),a.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>':s,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 i={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function n(e){let t=e.replace(/[&<>"']/g,e=>i[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 o{constructor(e){t(this,"container"),t(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?n(e.content):this.escapeHtml(e.content),this.container.appendChild(t),this.scrollToBottom(),t}updateMessage(e,t,s){const a=this.container.querySelector(`[data-id="${e}"]`);a&&(a.innerHTML=s?n(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 c{constructor(e,s){t(this,"area"),t(this,"textarea"),t(this,"sendBtn"),t(this,"onSend"),this.onSend=s.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=s.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 r{constructor(e,s){t(this,"el"),t(this,"messages"),t(this,"input"),this.el=document.createElement("div"),this.el.className="cc-panel","bottom-left"===s.position&&this.el.classList.add("cc-left");const a=document.createElement("div");if(a.className="cc-header",s.branding.avatar){const e=document.createElement("img");e.className="cc-header-avatar",e.src=s.branding.avatar,e.alt=s.branding.name,a.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>',a.appendChild(e)}const i=document.createElement("div");i.className="cc-header-info",i.innerHTML=`<div class="cc-header-name">${this.escapeHtml(s.branding.name)}</div><div class="cc-header-subtitle">${this.escapeHtml(s.branding.subtitle)}</div>`,a.appendChild(i);const n=document.createElement("button");n.className="cc-header-close",n.setAttribute("aria-label","Close chat"),n.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>',n.addEventListener("click",s.onClose),a.appendChild(n),this.el.appendChild(a),this.messages=new o(this.el),this.input=new c(this.el,{placeholder:s.placeholder,onSend:s.onSend});const r=document.createElement("div");r.className="cc-footer",r.innerHTML=s.footerText,this.el.appendChild(r),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 l="chatcops_bubble_shown";class d{constructor(e,s){t(this,"el"),t(this,"timerId",null),this.el=document.createElement("div"),this.el.className="cc-bubble","bottom-left"===s.position&&this.el.classList.add("cc-left"),this.el.textContent=s.text;const a=document.createElement("button");if(a.className="cc-bubble-close",a.innerHTML="&times;",a.addEventListener("click",e=>{e.stopPropagation(),this.hide()}),this.el.appendChild(a),this.el.addEventListener("click",()=>{this.hide(),s.onClick()}),e.appendChild(this.el),s.showOnce)try{if(sessionStorage.getItem(l))return;sessionStorage.setItem(l,"1")}catch{}this.timerId=setTimeout(()=>{this.el.classList.add("cc-visible")},s.delay??3e3)}hide(){this.el.classList.remove("cc-visible"),this.timerId&&(clearTimeout(this.timerId),this.timerId=null)}destroy(){this.hide(),this.el.remove()}}class h{constructor(e){t(this,"apiUrl"),t(this,"timeout"),this.apiUrl=e.apiUrl,this.timeout=e.timeout??3e4}async*sendMessage(e){var t;const s=new AbortController,a=setTimeout(()=>s.abort(),this.timeout);try{const a=await fetch(this.apiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:s.signal});if(!a.ok)return 429===a.status?void(yield{error:"rate_limit"}):void(yield{error:`HTTP ${a.status}`});const i=null==(t=a.body)?void 0:t.getReader();if(!i)return void(yield{error:"No response body"});const n=new TextDecoder;let o="";for(;;){const{done:e,value:t}=await i.read();if(e)break;o+=n.decode(t,{stream:!0});const s=o.split("\n");o=s.pop()??"";for(const a of s){const e=a.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(i){i instanceof DOMException&&"AbortError"===i.name?yield{error:"timeout"}:yield{error:"network"}}finally{clearTimeout(a)}}}const p="chatcops_conversation";class g{constructor(e){t(this,"key"),this.key=e?`${p}_${e}`:p}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 u(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,a,i]=m(e),n=1-t/100;return b(Math.round(s*n),Math.round(a*n),Math.round(i*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",f(t.bgColor,8)),s.style.setProperty("--cc-bg-input",f(t.bgColor,14))),t.fontFamily&&s.style.setProperty("--cc-font",t.fontFamily),t.borderRadius&&s.style.setProperty("--cc-radius",t.borderRadius)}function m(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 b(e,t,s){return"#"+[e,t,s].map(e=>Math.max(0,Math.min(255,e)).toString(16).padStart(2,"0")).join("")}function f(e,t){const[s,a,i]=m(e),n=t/100;return b(Math.round(s+(255-s)*n),Math.round(a+(255-a)*n),Math.round(i+(255-i)*n))}const x={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!"},v={en:x};class y{constructor(e){t(this,"config"),t(this,"shadow"),t(this,"fab"),t(this,"panel"),t(this,"bubble"),t(this,"client"),t(this,"storage"),t(this,"locale"),t(this,"conversationId"),t(this,"messages",[]),t(this,"isStreaming",!1),t(this,"eventHandlers",new Map),this.config=e,this.client=new h({apiUrl:e.apiUrl}),this.storage=new g,this.locale=function(e,t){const s=v[e]??x;return t?{...s,...t}:s}(e.locale??"en",e.strings),this.conversationId=this.storage.getSessionId()}init(){var e,t,s,i;this.shadow=function(){const e=document.createElement("div");e.id="chatcops-root",document.body.appendChild(e);const t=e.attachShadow({mode:"open"}),s=document.createElement("style");return s.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-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{width:100%;height:100%;max-height:100%;bottom:0;right:0;left:0;border-radius:0;border:none}.cc-panel.cc-left{left:0}.cc-bubble{bottom:84px;right:16px;left:16px;max-width:none}}",t.appendChild(s),t}(),this.config.theme&&u(this.shadow,this.config.theme);const n=(null==(e=this.config.theme)?void 0:e.position)??"bottom-right";if(this.fab=new a(this.shadow,{position:n,onClick:()=>this.toggle()}),this.panel=new r(this.shadow,{position:n,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==(i=this.config.branding)?void 0:i.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)}this.config.welcomeBubble&&(this.bubble=new d(this.shadow,{text:this.config.welcomeBubble.text,delay:this.config.welcomeBubble.delay,showOnce:this.config.welcomeBubble.showOnce??!0,position:n,onClick:()=>this.open()}))}open(){var e,t,s;this.panel.show(),this.fab.setOpen(!0),this.fab.hideBadge(),null==(e=this.bubble)||e.hide(),null==(s=(t=this.config).onOpen)||s.call(t),this.emit("open")}close(){var e,t;this.panel.hide(),this.fab.setOpen(!1),null==(t=(e=this.config).onClose)||t.call(e),this.emit("close")}toggle(){this.panel.isVisible?this.close():this.open()}destroy(){var e;null==(e=this.bubble)||e.destroy(),this.panel.destroy(),this.fab.destroy(),function(){const e=document.getElementById("chatcops-root");e&&e.remove()}(),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,a,i,n,o,c,r;if(this.isStreaming)return;const l={id:crypto.randomUUID(),role:"user",content:e};this.messages.push(l),this.panel.addMessage(l),null==(s=(t=this.config).onMessage)||s.call(t,l),this.emit("message",l),this.isStreaming=!0,this.panel.input.setDisabled(!0),this.panel.messages.showTyping();const d={id:crypto.randomUUID(),role:"assistant",content:""},h=!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:h,locale:this.config.locale})){if(s.error){const e="rate_limit"===s.error?"errorRateLimit":"network"===s.error?"errorNetwork":"errorGeneric";d.content=this.locale[e],this.panel.messages.removeTyping(),this.panel.addMessage(d),null==(i=(a=this.config).onError)||i.call(a,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(d),t=!1),d.content+=s.content,this.panel.messages.updateMessage(d.id,d.content,!0))}d.content&&(this.messages.push(d),null==(o=(n=this.config).onMessage)||o.call(n,d),this.emit("message",d),this.panel.isVisible||this.fab.showBadge())}catch(p){this.panel.messages.removeTyping(),d.content=this.locale.errorGeneric,this.panel.addMessage(d),null==(r=(c=this.config).onError)||r.call(c,p instanceof Error?p:new Error(String(p)))}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 w=null;const k={init:e=>(w&&w.destroy(),w=new y(e),w.init(),w),open(){null==w||w.open()},close(){null==w||w.close()},destroy(){null==w||w.destroy(),w=null},on(e,t){null==w||w.on(e,t)},off(e,t){null==w||w.off(e,t)}};function C(){const e=document.currentScript;if(!e)return;const t=e.dataset.apiUrl;t&&k.init({apiUrl:t,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,placeholder:e.dataset.placeholder,locale:e.dataset.locale})}const M=document.currentScript;return M&&M.dataset.apiUrl&&("loading"===document.readyState?document.addEventListener("DOMContentLoaded",C):C()),"undefined"!=typeof window&&(window.ChatCops=k),k}();
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={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};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="&times;",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}({});
@@ -0,0 +1,15 @@
1
+ export interface BubbleOptions {
2
+ text: string;
3
+ delay?: number;
4
+ showOnce?: boolean;
5
+ position: 'bottom-right' | 'bottom-left';
6
+ onClick: () => void;
7
+ }
8
+ export declare class WelcomeBubble {
9
+ private el;
10
+ private timerId;
11
+ constructor(root: ShadowRoot, options: BubbleOptions);
12
+ hide(): void;
13
+ destroy(): void;
14
+ }
15
+ //# sourceMappingURL=bubble.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bubble.d.ts","sourceRoot":"","sources":["../../src/dom/bubble.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,cAAc,GAAG,aAAa,CAAC;IACzC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAID,qBAAa,aAAa;IACxB,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,OAAO,CAA8C;gBAEjD,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa;IAuCpD,IAAI,IAAI,IAAI;IAQZ,OAAO,IAAI,IAAI;CAIhB"}
@@ -0,0 +1,15 @@
1
+ export interface FABOptions {
2
+ position: 'bottom-right' | 'bottom-left';
3
+ onClick: () => void;
4
+ }
5
+ export declare class FAB {
6
+ private el;
7
+ private badge;
8
+ private isOpen;
9
+ constructor(root: ShadowRoot, options: FABOptions);
10
+ setOpen(open: boolean): void;
11
+ showBadge(): void;
12
+ hideBadge(): void;
13
+ destroy(): void;
14
+ }
15
+ //# sourceMappingURL=fab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fab.d.ts","sourceRoot":"","sources":["../../src/dom/fab.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,cAAc,GAAG,aAAa,CAAC;IACzC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,qBAAa,GAAG;IACd,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,MAAM,CAAS;gBAEX,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU;IAqBjD,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAQ5B,SAAS,IAAI,IAAI;IAIjB,SAAS,IAAI,IAAI;IAIjB,OAAO,IAAI,IAAI;CAGhB"}
@@ -0,0 +1,18 @@
1
+ export interface InputOptions {
2
+ placeholder: string;
3
+ onSend: (text: string) => void;
4
+ }
5
+ export declare class Input {
6
+ private area;
7
+ private textarea;
8
+ private sendBtn;
9
+ private onSend;
10
+ constructor(parent: HTMLElement, options: InputOptions);
11
+ focus(): void;
12
+ setDisabled(disabled: boolean): void;
13
+ clear(): void;
14
+ private send;
15
+ private handleKeydown;
16
+ private autoResize;
17
+ }
18
+ //# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/dom/input.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,qBAAa,KAAK;IAChB,OAAO,CAAC,IAAI,CAAiB;IAC7B,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,MAAM,CAAyB;gBAE3B,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY;IA6BtD,KAAK,IAAI,IAAI;IAIb,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAKpC,KAAK,IAAI,IAAI;IAMb,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,UAAU;CAKnB"}
@@ -0,0 +1,18 @@
1
+ export interface MessageData {
2
+ id: string;
3
+ role: 'user' | 'assistant';
4
+ content: string;
5
+ }
6
+ export declare class Messages {
7
+ private container;
8
+ private typingEl;
9
+ constructor(parent: HTMLElement);
10
+ addMessage(msg: MessageData): HTMLDivElement;
11
+ updateMessage(id: string, content: string, isAssistant: boolean): void;
12
+ showTyping(): void;
13
+ removeTyping(): void;
14
+ clear(): void;
15
+ private scrollToBottom;
16
+ private escapeHtml;
17
+ }
18
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/dom/messages.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,QAAQ,CAA+B;gBAEnC,MAAM,EAAE,WAAW;IAM/B,UAAU,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc;IAW5C,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,GAAG,IAAI;IAQtE,UAAU,IAAI,IAAI;IASlB,YAAY,IAAI,IAAI;IAOpB,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,UAAU;CAKnB"}
@@ -0,0 +1,29 @@
1
+ import { Messages, type MessageData } from './messages.js';
2
+ import { Input } from './input.js';
3
+ export interface PanelOptions {
4
+ position: 'bottom-right' | 'bottom-left';
5
+ inline?: boolean;
6
+ branding: {
7
+ name: string;
8
+ avatar?: string;
9
+ subtitle: string;
10
+ };
11
+ placeholder: string;
12
+ footerText: string;
13
+ onSend: (text: string) => void;
14
+ onClose: () => void;
15
+ }
16
+ export declare class Panel {
17
+ private el;
18
+ private inline;
19
+ messages: Messages;
20
+ input: Input;
21
+ constructor(root: ShadowRoot, options: PanelOptions);
22
+ show(): void;
23
+ hide(): void;
24
+ get isVisible(): boolean;
25
+ addMessage(msg: MessageData): HTMLDivElement;
26
+ destroy(): void;
27
+ private escapeHtml;
28
+ }
29
+ //# sourceMappingURL=panel.d.ts.map
@@ -0,0 +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,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"}
@@ -0,0 +1,3 @@
1
+ export declare function createShadowRoot(container?: HTMLElement): ShadowRoot;
2
+ export declare function destroyShadowRoot(container?: HTMLElement): void;
3
+ //# sourceMappingURL=shadow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow.d.ts","sourceRoot":"","sources":["../../src/dom/shadow.ts"],"names":[],"mappings":"AAEA,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,WAAW,GAAG,UAAU,CAiBpE;AAED,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,WAAW,GAAG,IAAI,CAI/D"}
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export interface WidgetLocaleStrings {
2
+ welcomeMessage: string;
3
+ inputPlaceholder: string;
4
+ sendButton: string;
5
+ closeButton: string;
6
+ errorGeneric: string;
7
+ errorNetwork: string;
8
+ errorRateLimit: string;
9
+ typingIndicator: string;
10
+ poweredBy: string;
11
+ newConversation: string;
12
+ welcomeBubbleDefault: string;
13
+ }
14
+ export declare function getWidgetLocale(code: string, overrides?: Partial<WidgetLocaleStrings>): WidgetLocaleStrings;
15
+ export declare function registerWidgetLocale(code: string, strings: WidgetLocaleStrings): void;
16
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAkBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,CAG3G;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAErF"}
@@ -0,0 +1,12 @@
1
+ import { Widget, type WidgetConfig } from './widget.js';
2
+ export { Widget, type WidgetConfig } from './widget.js';
3
+ declare const ChatCops: {
4
+ init(config: WidgetConfig): Widget;
5
+ open(): void;
6
+ close(): void;
7
+ destroy(): void;
8
+ on(event: string, handler: (...args: unknown[]) => void): void;
9
+ off(event: string, handler: (...args: unknown[]) => void): void;
10
+ };
11
+ export default ChatCops;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD,QAAA,MAAM,QAAQ;iBACC,YAAY,GAAG,MAAM;YAS1B,IAAI;aAIH,IAAI;eAIF,IAAI;cAKL,MAAM,WAAW,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI;eAInD,MAAM,WAAW,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI;CAGhE,CAAC;AA8DF,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function renderMarkdown(input: string): string;
2
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAYA,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAmCpD"}
@@ -0,0 +1,20 @@
1
+ interface StoredConversation {
2
+ id: string;
3
+ messages: Array<{
4
+ id: string;
5
+ role: 'user' | 'assistant';
6
+ content: string;
7
+ timestamp: number;
8
+ }>;
9
+ updatedAt: number;
10
+ }
11
+ export declare class ConversationStorage {
12
+ private key;
13
+ constructor(siteId?: string);
14
+ load(): StoredConversation | null;
15
+ save(conversation: StoredConversation): void;
16
+ clear(): void;
17
+ getSessionId(): string;
18
+ }
19
+ export {};
20
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,UAAU,kBAAkB;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,KAAK,CAAC;QACd,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,GAAG,CAAS;gBAER,MAAM,CAAC,EAAE,MAAM;IAI3B,IAAI,IAAI,kBAAkB,GAAG,IAAI;IAUjC,IAAI,CAAC,YAAY,EAAE,kBAAkB,GAAG,IAAI;IAQ5C,KAAK,IAAI,IAAI;IAQb,YAAY,IAAI,MAAM;CAavB"}
@@ -0,0 +1,10 @@
1
+ export interface ThemeConfig {
2
+ accent?: string;
3
+ textColor?: string;
4
+ bgColor?: string;
5
+ fontFamily?: string;
6
+ borderRadius?: string;
7
+ position?: 'bottom-right' | 'bottom-left';
8
+ }
9
+ export declare function applyTheme(root: ShadowRoot, theme: ThemeConfig): void;
10
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,GAAG,aAAa,CAAC;CAC3C;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAoBrE"}
@@ -0,0 +1,69 @@
1
+ import { type WidgetLocaleStrings } from './i18n.js';
2
+ import type { MessageData } from './dom/messages.js';
3
+ export interface WidgetConfig {
4
+ apiUrl: string;
5
+ mode?: 'popup' | 'inline';
6
+ container?: string | HTMLElement;
7
+ theme?: {
8
+ accent?: string;
9
+ textColor?: string;
10
+ bgColor?: string;
11
+ fontFamily?: string;
12
+ borderRadius?: string;
13
+ position?: 'bottom-right' | 'bottom-left';
14
+ };
15
+ branding?: {
16
+ name?: string;
17
+ avatar?: string;
18
+ subtitle?: string;
19
+ };
20
+ welcomeMessage?: string;
21
+ welcomeBubble?: {
22
+ text: string;
23
+ delay?: number;
24
+ showOnce?: boolean;
25
+ };
26
+ placeholder?: string;
27
+ persistHistory?: boolean;
28
+ maxMessages?: number;
29
+ pageContext?: boolean;
30
+ autoOpen?: boolean | number;
31
+ locale?: string;
32
+ strings?: Partial<WidgetLocaleStrings>;
33
+ onOpen?: () => void;
34
+ onClose?: () => void;
35
+ onMessage?: (message: MessageData) => void;
36
+ onError?: (error: Error) => void;
37
+ }
38
+ type WidgetEventType = 'open' | 'close' | 'message' | 'error';
39
+ type WidgetEventHandler = (...args: unknown[]) => void;
40
+ export declare class Widget {
41
+ private config;
42
+ private shadow;
43
+ private fab?;
44
+ private panel;
45
+ private bubble?;
46
+ private client;
47
+ private storage;
48
+ private locale;
49
+ private conversationId;
50
+ private messages;
51
+ private isStreaming;
52
+ private eventHandlers;
53
+ private containerEl?;
54
+ get isInline(): boolean;
55
+ constructor(config: WidgetConfig);
56
+ private resolveContainer;
57
+ init(): void;
58
+ open(): void;
59
+ close(): void;
60
+ toggle(): void;
61
+ destroy(): void;
62
+ on(event: WidgetEventType, handler: WidgetEventHandler): void;
63
+ off(event: WidgetEventType, handler: WidgetEventHandler): void;
64
+ private emit;
65
+ private handleSend;
66
+ private saveHistory;
67
+ }
68
+ export {};
69
+ //# sourceMappingURL=widget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.d.ts","sourceRoot":"","sources":["../src/widget.ts"],"names":[],"mappings":"AAOA,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACjC,KAAK,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,cAAc,GAAG,aAAa,CAAC;KAC3C,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC3C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,KAAK,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAC9D,KAAK,kBAAkB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;AAEvD,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,GAAG,CAAC,CAAM;IAClB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAuD;IAC5E,OAAO,CAAC,WAAW,CAAC,CAAc;IAElC,IAAI,QAAQ,IAAI,OAAO,CAEtB;gBAEW,MAAM,EAAE,YAAY;IAQhC,OAAO,CAAC,gBAAgB;IAcxB,IAAI,IAAI,IAAI;IAmFZ,IAAI,IAAI,IAAI;IAWZ,KAAK,IAAI,IAAI;IASb,MAAM,IAAI,IAAI;IAQd,OAAO,IAAI,IAAI;IASf,EAAE,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAO7D,GAAG,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAI9D,OAAO,CAAC,IAAI;YAIE,UAAU;IAkFxB,OAAO,CAAC,WAAW;CAiBpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatcops/widget",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Embeddable AI chatbot widget - zero dependencies, Shadow DOM isolated",
5
5
  "type": "module",
6
6
  "main": "dist/chatcops.min.js",
@@ -16,14 +16,6 @@
16
16
  "files": [
17
17
  "dist"
18
18
  ],
19
- "scripts": {
20
- "dev": "vite build --watch",
21
- "build": "vite build && tsc --emitDeclarationOnly",
22
- "typecheck": "tsc --noEmit",
23
- "test": "vitest run",
24
- "test:watch": "vitest",
25
- "clean": "rm -rf dist *.tsbuildinfo"
26
- },
27
19
  "devDependencies": {
28
20
  "typescript": "^5.7.0",
29
21
  "vite": "^6.0.0",
@@ -31,8 +23,21 @@
31
23
  "vitest": "^3.0.0",
32
24
  "happy-dom": "^16.0.0"
33
25
  },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/codercops/chatcops",
29
+ "directory": "packages/widget"
30
+ },
34
31
  "publishConfig": {
35
32
  "access": "public"
36
33
  },
37
- "license": "MIT"
38
- }
34
+ "license": "MIT",
35
+ "scripts": {
36
+ "dev": "vite build --watch",
37
+ "build": "vite build && tsc --emitDeclarationOnly",
38
+ "typecheck": "tsc --noEmit",
39
+ "test": "vitest run",
40
+ "test:watch": "vitest",
41
+ "clean": "rm -rf dist *.tsbuildinfo"
42
+ }
43
+ }