cevvo-widget 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # Cevvo Widget
2
+
3
+ AI-powered chat for your docs. Add an intelligent assistant to any website with a single script tag.
4
+
5
+ ## Installation
6
+
7
+ ### Via Script Tag (Recommended)
8
+
9
+ Add the following script to your HTML:
10
+
11
+ ```html
12
+ <script
13
+ src="https://cdn.cevvo.ai/cevvo-widget.js"
14
+ data-cevvo-project-id="your-project-id"
15
+ data-cevvo-api-key="your-api-key">
16
+ </script>
17
+ ```
18
+
19
+ ### Via JavaScript
20
+
21
+ ```html
22
+ <script src="https://cdn.cevvo.ai/cevvo-widget.js"></script>
23
+ <script>
24
+ CevvoWidget.init({
25
+ projectId: 'your-project-id',
26
+ apiKey: 'your-api-key'
27
+ });
28
+ </script>
29
+ ```
30
+
31
+ ## Development
32
+
33
+ ```bash
34
+ # Install dependencies
35
+ npm install
36
+
37
+ # Start development server (uses local API at localhost:9099)
38
+ npm run dev
39
+
40
+ # Build for production (uses production API)
41
+ npm run build
42
+
43
+ # Preview production build
44
+ npm run preview
45
+ ```
46
+
47
+ ## API Environments
48
+
49
+ The widget automatically connects to different API endpoints based on the environment:
50
+
51
+ | Environment | API URL |
52
+ |-------------|---------|
53
+ | Development (`npm run dev`) | `http://localhost:9099/api/v1` |
54
+ | Production (`npm run build`) | `https://prod-backend-api.cevvo.ai/api/v1` |
55
+
56
+ You can override the API URL using the `apiUrl` configuration option.
57
+
58
+ ## Configuration Options
59
+
60
+ ### Required
61
+
62
+ | Option | Type | Description |
63
+ |--------|------|-------------|
64
+ | `projectId` | `string` | Your Cevvo project ID |
65
+ | `apiKey` | `string` | Your Cevvo API key for authentication |
66
+
67
+ ### Optional
68
+
69
+ | Option | Type | Default | Description |
70
+ |--------|------|---------|-------------|
71
+ | `apiUrl` | `string` | Auto-detected | Override the API endpoint URL |
72
+ | `projectName` | `string` | `'AI Assistant'` | Display name for the assistant |
73
+ | `projectColor` | `string` | `'#2563eb'` | Accent color (hex) |
74
+ | `buttonText` | `string` | `'Ask AI'` | Text displayed on the widget button |
75
+ | `buttonBgColor` | `string` | `'#2563eb'` | Button background color |
76
+ | `buttonTextColor` | `string` | `'#ffffff'` | Button text color |
77
+ | `buttonPosition` | `string` | `'bottom-right'` | Button position (`'bottom-right'` or `'bottom-left'`) |
78
+ | `buttonOffsetX` | `string` | `'20px'` | Horizontal offset from edge |
79
+ | `buttonOffsetY` | `string` | `'20px'` | Vertical offset from edge |
80
+ | `modalTitle` | `string` | `'AI Assistant'` | Title in the chat modal header |
81
+ | `modalSubtitle` | `string` | `'Ask me anything'` | Subtitle in the chat modal header |
82
+ | `modalPlaceholder` | `string` | `'Ask a question...'` | Input placeholder text |
83
+ | `modalExampleQuestions` | `string[]` | `[]` | Example questions to show in empty state |
84
+ | `modalWidth` | `string` | `'420px'` | Chat modal width |
85
+ | `modalHeight` | `string` | `'600px'` | Chat modal height |
86
+ | `mode` | `string` | `'chat'` | Display mode (`'chat'` for bottom-right popup, `'modal'` for centered modal) |
87
+
88
+ ## Data Attributes
89
+
90
+ When using script tag initialization, use these data attributes:
91
+
92
+ | Attribute | Maps to |
93
+ |-----------|---------|
94
+ | `data-cevvo-project-id` | `projectId` |
95
+ | `data-cevvo-api-key` | `apiKey` |
96
+ | `data-cevvo-api-url` | `apiUrl` |
97
+ | `data-cevvo-project-name` | `projectName` |
98
+ | `data-cevvo-project-color` | `projectColor` |
99
+ | `data-cevvo-button-text` | `buttonText` |
100
+ | `data-cevvo-button-bg-color` | `buttonBgColor` |
101
+ | `data-cevvo-modal-title` | `modalTitle` |
102
+ | `data-cevvo-modal-placeholder` | `modalPlaceholder` |
103
+ | `data-cevvo-example-questions` | `modalExampleQuestions` (JSON array) |
104
+
105
+ ### Example with all options:
106
+
107
+ ```html
108
+ <script
109
+ src="https://cdn.cevvo.ai/cevvo-widget.js"
110
+ data-cevvo-project-id="proj_abc123"
111
+ data-cevvo-api-key="sk_live_xyz789"
112
+ data-cevvo-project-color="#8b5cf6"
113
+ data-cevvo-button-text="Get Help"
114
+ data-cevvo-modal-title="Support Assistant"
115
+ data-cevvo-modal-placeholder="How can I help you today?"
116
+ data-cevvo-example-questions='["How do I get started?", "What are the pricing plans?"]'>
117
+ </script>
118
+ ```
119
+
120
+ ## Authentication
121
+
122
+ The widget authenticates with the Cevvo API using an API key header. When you provide an `apiKey`, it's sent in the `X-API-Key` header:
123
+
124
+ ```
125
+ X-API-Key: your-api-key
126
+ ```
127
+
128
+ **Security Note:** The API key is visible in the browser. Use a publishable/public API key that has limited permissions. Never use secret/admin keys in client-side code.
129
+
130
+ ## Keyboard Shortcuts
131
+
132
+ | Shortcut | Action |
133
+ |----------|--------|
134
+ | `Cmd/Ctrl + K` | Toggle widget |
135
+ | `Escape` | Close widget |
136
+ | `Enter` | Send message |
137
+
138
+ ## API Reference
139
+
140
+ ### `CevvoWidget.init(config)`
141
+
142
+ Initialize the widget with a configuration object.
143
+
144
+ ```js
145
+ const widget = CevvoWidget.init({
146
+ projectId: 'your-project-id',
147
+ apiKey: 'your-api-key',
148
+ projectColor: '#8b5cf6',
149
+ modalExampleQuestions: [
150
+ 'How do I get started?',
151
+ 'What features are available?'
152
+ ]
153
+ });
154
+ ```
155
+
156
+ **Parameters:**
157
+ - `config` (Object): Configuration options (see Configuration Options above)
158
+
159
+ **Returns:**
160
+ - `CevvoWidget` instance
161
+
162
+ ## API Endpoints
163
+
164
+ The widget communicates with the following endpoints:
165
+
166
+ ### GET `/widget/config`
167
+
168
+ Fetches widget configuration for a project.
169
+
170
+ **Query Parameters:**
171
+ - `project_id` - Your Cevvo project ID
172
+
173
+ **Request Headers:**
174
+ ```
175
+ X-API-Key: <api-key>
176
+ ```
177
+
178
+ **Response:**
179
+ ```json
180
+ {
181
+ "mode": "chat",
182
+ "buttonText": "Ask AI",
183
+ "position": "bottom-right",
184
+ "primaryColor": "#155EEF",
185
+ "theme": "light",
186
+ "welcomeMessage": "Hi! How can I help you today?",
187
+ "exampleQuestions": [],
188
+ "modalTitle": "AI Assistant",
189
+ "modalSubtitle": "Ask me anything"
190
+ }
191
+ ```
192
+
193
+ ### POST `/widget/chat/stream`
194
+
195
+ Sends a message and receives a streamed response.
196
+
197
+ **Request Headers:**
198
+ ```
199
+ Content-Type: application/json
200
+ X-API-Key: <api-key>
201
+ ```
202
+
203
+ **Request Body:**
204
+ ```json
205
+ {
206
+ "message": "User's question",
207
+ "thread_id": null,
208
+ "project_id": "your-project-id"
209
+ }
210
+ ```
211
+
212
+ **Response:** Server-Sent Events (SSE) stream with named events:
213
+ ```
214
+ event: token
215
+ data: {"token": "Hello"}
216
+
217
+ event: token
218
+ data: {"token": " world"}
219
+
220
+ event: metadata
221
+ data: {"thread_id": 123, "sources": [{"title": "Doc Title", "source_url": "https://..."}], "confidence": 0.85}
222
+
223
+ event: done
224
+ data: {"thread_id": 123, "question_answer_id": 456, "latency_ms": 1234.5}
225
+ ```
226
+
227
+ ## License
228
+
229
+ Proprietary - Cevvo AI
@@ -0,0 +1,805 @@
1
+ (function(){const p="https://prod-backend-api.cevvo.ai/api/v1",f={projectId:"",apiKey:"",apiUrl:p,projectName:"AI Assistant",projectColor:"#2563eb",buttonText:"Ask AI",buttonBgColor:"#2563eb",buttonTextColor:"#ffffff",buttonPosition:"bottom-right",buttonOffsetX:"20px",buttonOffsetY:"20px",modalTitle:"AI Assistant",modalSubtitle:"Ask me anything",modalPlaceholder:"Ask a question...",modalExampleQuestions:[],modalWidth:"420px",modalHeight:"600px",mode:"chat"},i={cevvoLogo:`<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 32 32">
2
+ <ellipse cx="13" cy="16" rx="8" ry="10" fill="#21244A" transform="rotate(-15 13 16)"/>
3
+ <ellipse cx="19" cy="16" rx="8" ry="10" fill="#F69F06" transform="rotate(15 19 16)" opacity="0.85"/>
4
+ </svg>`,close:`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" fill="currentColor">
5
+ <path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"/>
6
+ </svg>`,send:`<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 256 256" fill="currentColor">
7
+ <path d="M231.87,114l-168-95.89A16,16,0,0,0,40.92,37.34L71.55,128,40.92,218.67A16,16,0,0,0,56,240a16.15,16.15,0,0,0,7.93-2.1l167.92-96.05a16,16,0,0,0,.05-27.89ZM56,224a.56.56,0,0,0,0-.12L85.74,136H144a8,8,0,0,0,0-16H85.74L56.06,32.16A.46.46,0,0,0,56,32l168,95.83Z"/>
8
+ </svg>`,sparkle:`<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256" fill="currentColor">
9
+ <path d="M208,144a15.78,15.78,0,0,1-10.42,14.94l-51.65,19-19,51.61a15.92,15.92,0,0,1-29.88,0L78,177.91l-51.62-19a15.92,15.92,0,0,1,0-29.88l51.65-19,19-51.61a15.92,15.92,0,0,1,29.88,0l19,51.65,51.61,19A15.78,15.78,0,0,1,208,144Z"/>
10
+ </svg>`,trash:`<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 256 256" fill="currentColor">
11
+ <path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192Z"/>
12
+ </svg>`,user:`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor">
13
+ <path d="M230.92,212c-15.23-26.33-38.7-45.21-66.09-54.16a72,72,0,1,0-73.66,0C63.78,166.78,40.31,185.66,25.08,212a8,8,0,1,0,13.85,8c18.84-32.56,52.14-52,89.07-52s70.23,19.44,89.07,52a8,8,0,1,0,13.85-8ZM72,96a56,56,0,1,1,56,56A56.06,56.06,0,0,1,72,96Z"/>
14
+ </svg>`,copy:`<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 256 256" fill="currentColor">
15
+ <path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"/>
16
+ </svg>`,check:`<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 256 256" fill="currentColor">
17
+ <path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"/>
18
+ </svg>`,thumbsUp:`<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 256 256" fill="currentColor">
19
+ <path d="M234,80.12A24,24,0,0,0,216,72H160V56a40,40,0,0,0-40-40,8,8,0,0,0-7.16,4.42L75.06,96H32a16,16,0,0,0-16,16v88a16,16,0,0,0,16,16H204a24,24,0,0,0,23.82-21.12l12-96A24,24,0,0,0,234,80.12Z"/>
20
+ </svg>`,thumbsDown:`<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 256 256" fill="currentColor">
21
+ <path d="M239.82,157.12l-12-96A24,24,0,0,0,204,40H32A16,16,0,0,0,16,56v88a16,16,0,0,0,16,16H75.06l37.78,75.58A8,8,0,0,0,120,240a40,40,0,0,0,40-40V184h56a24,24,0,0,0,23.82-26.88Z"/>
22
+ </svg>`,link:`<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 256 256" fill="currentColor">
23
+ <path d="M224,104a8,8,0,0,1-16,0V59.32l-66.33,66.34a8,8,0,0,1-11.32-11.32L196.68,48H152a8,8,0,0,1,0-16h64a8,8,0,0,1,8,8Zm-40,24a8,8,0,0,0-8,8v72H48V80h72a8,8,0,0,0,0-16H48A16,16,0,0,0,32,80V208a16,16,0,0,0,16,16H176a16,16,0,0,0,16-16V136A8,8,0,0,0,184,128Z"/>
24
+ </svg>`,brain:`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor">
25
+ <path d="M248,132a56,56,0,0,0-32-50.61V72a48,48,0,0,0-88-26.49A48,48,0,0,0,40,72v9.39a56,56,0,0,0,0,101.2V192a48,48,0,0,0,88,26.49A48,48,0,0,0,216,192v-9.41A56.09,56.09,0,0,0,248,132ZM88,216a32,32,0,0,1-32-32v-8.81a55.45,55.45,0,0,0,8,.58,56.22,56.22,0,0,0,24-5.39v13.85a8,8,0,0,0,16,0v-24a8,8,0,0,0-8-8,40,40,0,1,1,0-80,8,8,0,0,0,8-8V40a32,32,0,0,1,64,0v24.23a8,8,0,0,0,8,8,40,40,0,1,1,0,80,8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V170.39a56.22,56.22,0,0,0,24,5.39,55.45,55.45,0,0,0,8-.58V184a32,32,0,0,1-64,0V170.54a8,8,0,0,0-8-8,8,8,0,0,0-8,8V184A32,32,0,0,1,88,216Z"/>
26
+ </svg>`,arrowUp:`<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 256 256" fill="currentColor">
27
+ <path d="M205.66,117.66a8,8,0,0,1-11.32,0L136,59.31V216a8,8,0,0,1-16,0V59.31L61.66,117.66a8,8,0,0,1-11.32-11.32l72-72a8,8,0,0,1,11.32,0l72,72A8,8,0,0,1,205.66,117.66Z"/>
28
+ </svg>`},b=`
29
+ .cevvo-widget * {
30
+ box-sizing: border-box;
31
+ margin: 0;
32
+ padding: 0;
33
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
34
+ }
35
+
36
+ .cevvo-widget-btn {
37
+ position: fixed;
38
+ z-index: 9999;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ gap: 10px;
43
+ padding: 14px 18px;
44
+ border: 1px solid #e4e4e7;
45
+ border-radius: 12px;
46
+ background: #fff;
47
+ color: #18181b;
48
+ font-size: 14px;
49
+ font-weight: 600;
50
+ cursor: pointer;
51
+ box-shadow: 0 4px 12px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.04);
52
+ transition: all 0.2s ease;
53
+ }
54
+
55
+ .cevvo-widget-btn:hover {
56
+ transform: translateY(-2px);
57
+ box-shadow: 0 8px 20px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.06);
58
+ border-color: #d4d4d8;
59
+ }
60
+
61
+ .cevvo-widget-btn.bottom-right {
62
+ right: 20px;
63
+ bottom: 20px;
64
+ }
65
+
66
+ .cevvo-widget-btn.bottom-left {
67
+ left: 20px;
68
+ bottom: 20px;
69
+ }
70
+
71
+ .cevvo-widget-btn.is-open {
72
+ padding: 14px;
73
+ }
74
+
75
+ .cevvo-widget-btn svg {
76
+ flex-shrink: 0;
77
+ }
78
+
79
+ /* Modal Overlay */
80
+ .cevvo-modal-overlay {
81
+ position: fixed;
82
+ inset: 0;
83
+ z-index: 9998;
84
+ display: flex;
85
+ align-items: flex-end;
86
+ justify-content: flex-end;
87
+ padding: 20px;
88
+ background: transparent;
89
+ opacity: 0;
90
+ visibility: hidden;
91
+ transition: all 0.2s ease;
92
+ pointer-events: none;
93
+ }
94
+
95
+ .cevvo-modal-overlay.is-open {
96
+ opacity: 1;
97
+ visibility: visible;
98
+ pointer-events: auto;
99
+ }
100
+
101
+ /* Modal */
102
+ .cevvo-modal {
103
+ display: flex;
104
+ flex-direction: column;
105
+ width: 420px;
106
+ max-width: calc(100vw - 40px);
107
+ height: 600px;
108
+ max-height: calc(100vh - 100px);
109
+ background: #fff;
110
+ border: 1px solid #e4e4e7;
111
+ border-radius: 16px;
112
+ box-shadow: 0 8px 30px rgba(0,0,0,0.12), 0 4px 12px rgba(0,0,0,0.08);
113
+ overflow: hidden;
114
+ transform: translateY(20px) scale(0.95);
115
+ opacity: 0;
116
+ transition: all 0.2s ease;
117
+ }
118
+
119
+ .cevvo-modal-overlay.is-open .cevvo-modal {
120
+ transform: translateY(0) scale(1);
121
+ opacity: 1;
122
+ }
123
+
124
+ /* Header */
125
+ .cevvo-modal-header {
126
+ display: flex;
127
+ align-items: center;
128
+ justify-content: space-between;
129
+ padding: 16px 20px;
130
+ border-bottom: 1px solid #e4e4e7;
131
+ background: #fff;
132
+ }
133
+
134
+ .cevvo-header-info {
135
+ display: flex;
136
+ align-items: center;
137
+ gap: 12px;
138
+ }
139
+
140
+ .cevvo-header-logo {
141
+ display: flex;
142
+ align-items: center;
143
+ justify-content: center;
144
+ }
145
+
146
+ .cevvo-header-text h2 {
147
+ font-size: 15px;
148
+ font-weight: 600;
149
+ color: #18181b;
150
+ }
151
+
152
+ .cevvo-header-text p {
153
+ font-size: 12px;
154
+ color: #71717a;
155
+ margin-top: 2px;
156
+ }
157
+
158
+ .cevvo-header-actions {
159
+ display: flex;
160
+ gap: 4px;
161
+ }
162
+
163
+ .cevvo-header-actions button {
164
+ display: flex;
165
+ align-items: center;
166
+ justify-content: center;
167
+ width: 32px;
168
+ height: 32px;
169
+ border: none;
170
+ border-radius: 8px;
171
+ background: transparent;
172
+ color: #71717a;
173
+ cursor: pointer;
174
+ transition: all 0.15s ease;
175
+ }
176
+
177
+ .cevvo-header-actions button:hover {
178
+ background: #f4f4f5;
179
+ color: #18181b;
180
+ }
181
+
182
+ /* Body */
183
+ .cevvo-modal-body {
184
+ flex: 1;
185
+ overflow-y: auto;
186
+ padding: 20px;
187
+ }
188
+
189
+ /* Empty State */
190
+ .cevvo-empty-state {
191
+ display: flex;
192
+ flex-direction: column;
193
+ align-items: center;
194
+ justify-content: center;
195
+ height: 100%;
196
+ text-align: center;
197
+ padding: 20px;
198
+ }
199
+
200
+ .cevvo-empty-icon {
201
+ width: 64px;
202
+ height: 64px;
203
+ border-radius: 16px;
204
+ display: flex;
205
+ align-items: center;
206
+ justify-content: center;
207
+ margin-bottom: 16px;
208
+ background: #f4f4f5;
209
+ color: #18181b;
210
+ }
211
+
212
+ .cevvo-empty-state h3 {
213
+ font-size: 18px;
214
+ font-weight: 600;
215
+ color: #18181b;
216
+ margin-bottom: 8px;
217
+ }
218
+
219
+ .cevvo-empty-state p {
220
+ font-size: 14px;
221
+ color: #71717a;
222
+ margin-bottom: 24px;
223
+ max-width: 280px;
224
+ }
225
+
226
+ .cevvo-example-questions {
227
+ display: flex;
228
+ flex-direction: column;
229
+ gap: 8px;
230
+ width: 100%;
231
+ max-width: 320px;
232
+ }
233
+
234
+ .cevvo-example-btn {
235
+ padding: 12px 16px;
236
+ border: 1px solid #e4e4e7;
237
+ border-radius: 10px;
238
+ background: #fff;
239
+ color: #3f3f46;
240
+ font-size: 13px;
241
+ text-align: left;
242
+ cursor: pointer;
243
+ transition: all 0.15s ease;
244
+ }
245
+
246
+ .cevvo-example-btn:hover {
247
+ background: #fafafa;
248
+ border-color: var(--cevvo-accent, #2563eb);
249
+ }
250
+
251
+ /* Messages */
252
+ .cevvo-messages {
253
+ display: flex;
254
+ flex-direction: column;
255
+ gap: 16px;
256
+ }
257
+
258
+ .cevvo-message {
259
+ display: flex;
260
+ gap: 12px;
261
+ }
262
+
263
+ .cevvo-message.user {
264
+ flex-direction: row-reverse;
265
+ }
266
+
267
+ .cevvo-avatar {
268
+ width: 32px;
269
+ height: 32px;
270
+ border-radius: 8px;
271
+ display: flex;
272
+ align-items: center;
273
+ justify-content: center;
274
+ flex-shrink: 0;
275
+ }
276
+
277
+ .cevvo-avatar.assistant {
278
+ background: #f4f4f5;
279
+ }
280
+
281
+ .cevvo-avatar.user {
282
+ background: var(--cevvo-accent, #2563eb);
283
+ color: #fff;
284
+ }
285
+
286
+ .cevvo-message-content {
287
+ flex: 1;
288
+ min-width: 0;
289
+ max-width: 85%;
290
+ }
291
+
292
+ .cevvo-bubble {
293
+ padding: 12px 16px;
294
+ border-radius: 12px;
295
+ font-size: 14px;
296
+ line-height: 1.5;
297
+ }
298
+
299
+ .cevvo-bubble.assistant {
300
+ background: #f4f4f5;
301
+ color: #18181b;
302
+ border-bottom-left-radius: 4px;
303
+ }
304
+
305
+ .cevvo-bubble.user {
306
+ background: var(--cevvo-accent, #2563eb);
307
+ color: #fff;
308
+ border-bottom-right-radius: 4px;
309
+ }
310
+
311
+ .cevvo-bubble code {
312
+ background: rgba(0,0,0,0.08);
313
+ padding: 2px 6px;
314
+ border-radius: 4px;
315
+ font-family: 'SF Mono', Monaco, monospace;
316
+ font-size: 13px;
317
+ }
318
+
319
+ .cevvo-bubble.user code {
320
+ background: rgba(255,255,255,0.15);
321
+ }
322
+
323
+ /* Typing indicator */
324
+ .cevvo-typing {
325
+ display: flex;
326
+ gap: 4px;
327
+ padding: 4px 0;
328
+ }
329
+
330
+ .cevvo-typing span {
331
+ width: 8px;
332
+ height: 8px;
333
+ background: #a1a1aa;
334
+ border-radius: 50%;
335
+ animation: cevvo-bounce 1.4s infinite ease-in-out both;
336
+ }
337
+
338
+ .cevvo-typing span:nth-child(1) { animation-delay: -0.32s; }
339
+ .cevvo-typing span:nth-child(2) { animation-delay: -0.16s; }
340
+
341
+ @keyframes cevvo-bounce {
342
+ 0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
343
+ 40% { transform: scale(1); opacity: 1; }
344
+ }
345
+
346
+ /* Sources */
347
+ .cevvo-sources {
348
+ margin-top: 12px;
349
+ padding: 12px;
350
+ background: #fafafa;
351
+ border: 1px solid #e4e4e7;
352
+ border-radius: 10px;
353
+ }
354
+
355
+ .cevvo-sources-header {
356
+ display: flex;
357
+ align-items: center;
358
+ gap: 6px;
359
+ font-size: 12px;
360
+ font-weight: 600;
361
+ color: #71717a;
362
+ margin-bottom: 8px;
363
+ }
364
+
365
+ .cevvo-source-link {
366
+ display: flex;
367
+ align-items: center;
368
+ gap: 8px;
369
+ padding: 8px 10px;
370
+ background: #fff;
371
+ border: 1px solid #e4e4e7;
372
+ border-radius: 6px;
373
+ color: #3f3f46;
374
+ text-decoration: none;
375
+ font-size: 13px;
376
+ margin-bottom: 6px;
377
+ transition: all 0.15s ease;
378
+ }
379
+
380
+ .cevvo-source-link:last-child {
381
+ margin-bottom: 0;
382
+ }
383
+
384
+ .cevvo-source-link:hover {
385
+ background: #fafafa;
386
+ border-color: var(--cevvo-accent, #2563eb);
387
+ }
388
+
389
+ .cevvo-source-num {
390
+ width: 20px;
391
+ height: 20px;
392
+ background: #e4e4e7;
393
+ border-radius: 4px;
394
+ font-size: 11px;
395
+ font-weight: 600;
396
+ color: #71717a;
397
+ display: flex;
398
+ align-items: center;
399
+ justify-content: center;
400
+ }
401
+
402
+ .cevvo-source-title {
403
+ flex: 1;
404
+ white-space: nowrap;
405
+ overflow: hidden;
406
+ text-overflow: ellipsis;
407
+ }
408
+
409
+ .cevvo-source-link svg {
410
+ color: #a1a1aa;
411
+ flex-shrink: 0;
412
+ }
413
+
414
+ /* Actions */
415
+ .cevvo-actions {
416
+ display: flex;
417
+ gap: 4px;
418
+ margin-top: 8px;
419
+ }
420
+
421
+ .cevvo-actions button {
422
+ display: flex;
423
+ align-items: center;
424
+ justify-content: center;
425
+ width: 28px;
426
+ height: 28px;
427
+ border: none;
428
+ border-radius: 6px;
429
+ background: transparent;
430
+ color: #a1a1aa;
431
+ cursor: pointer;
432
+ transition: all 0.15s ease;
433
+ }
434
+
435
+ .cevvo-actions button:hover {
436
+ background: #f4f4f5;
437
+ color: #18181b;
438
+ }
439
+
440
+ .cevvo-actions .cevvo-thumbs-up.active {
441
+ color: #22c55e;
442
+ background: #dcfce7;
443
+ }
444
+
445
+ .cevvo-actions .cevvo-thumbs-down.active {
446
+ color: #ef4444;
447
+ background: #fee2e2;
448
+ }
449
+
450
+ /* Footer */
451
+ .cevvo-modal-footer {
452
+ padding: 16px 20px;
453
+ border-top: 1px solid #e4e4e7;
454
+ background: #fafafa;
455
+ }
456
+
457
+ .cevvo-input-form {
458
+ display: flex;
459
+ gap: 8px;
460
+ }
461
+
462
+ .cevvo-input {
463
+ flex: 1;
464
+ padding: 12px 16px;
465
+ border: 1px solid #e4e4e7;
466
+ border-radius: 10px;
467
+ font-size: 14px;
468
+ outline: none;
469
+ background: #fff;
470
+ transition: border-color 0.15s ease;
471
+ }
472
+
473
+ .cevvo-input:focus {
474
+ border-color: var(--cevvo-accent, #2563eb);
475
+ }
476
+
477
+ .cevvo-input::placeholder {
478
+ color: #a1a1aa;
479
+ }
480
+
481
+ .cevvo-send-btn {
482
+ display: flex;
483
+ align-items: center;
484
+ justify-content: center;
485
+ width: 44px;
486
+ height: 44px;
487
+ border: none;
488
+ border-radius: 10px;
489
+ background: var(--cevvo-accent, #2563eb);
490
+ color: #fff;
491
+ cursor: pointer;
492
+ transition: opacity 0.15s ease;
493
+ }
494
+
495
+ .cevvo-send-btn:disabled {
496
+ opacity: 0.4;
497
+ cursor: not-allowed;
498
+ }
499
+
500
+ .cevvo-send-btn:not(:disabled):hover {
501
+ opacity: 0.85;
502
+ }
503
+
504
+ .cevvo-powered {
505
+ text-align: center;
506
+ font-size: 11px;
507
+ color: #a1a1aa;
508
+ margin-top: 12px;
509
+ }
510
+
511
+ .cevvo-powered a {
512
+ color: #71717a;
513
+ text-decoration: none;
514
+ }
515
+
516
+ .cevvo-powered a:hover {
517
+ text-decoration: underline;
518
+ }
519
+
520
+ /* Modal Mode Styles */
521
+ .cevvo-modal-overlay.mode-modal {
522
+ align-items: flex-start;
523
+ justify-content: center;
524
+ padding: 80px 40px 40px;
525
+ }
526
+
527
+ .cevvo-modal-overlay.mode-modal .cevvo-modal {
528
+ width: 700px;
529
+ max-width: calc(100vw - 80px);
530
+ height: auto;
531
+ min-height: 500px;
532
+ max-height: calc(100vh - 80px);
533
+ border-radius: 20px;
534
+ }
535
+
536
+ .cevvo-modal-overlay.mode-modal .cevvo-modal-header {
537
+ padding: 20px 24px;
538
+ border-bottom: none;
539
+ background: linear-gradient(to bottom, #f8f7ff, #fff);
540
+ }
541
+
542
+ .cevvo-modal-overlay.mode-modal .cevvo-header-info {
543
+ flex-direction: column;
544
+ align-items: flex-start;
545
+ gap: 8px;
546
+ }
547
+
548
+ .cevvo-modal-overlay.mode-modal .cevvo-header-text h2 {
549
+ font-size: 18px;
550
+ }
551
+
552
+ .cevvo-modal-overlay.mode-modal .cevvo-modal-description {
553
+ font-size: 14px;
554
+ color: #52525b;
555
+ line-height: 1.5;
556
+ margin-top: 8px;
557
+ }
558
+
559
+ .cevvo-modal-overlay.mode-modal .cevvo-modal-description a {
560
+ color: var(--cevvo-accent, #2563eb);
561
+ text-decoration: underline;
562
+ }
563
+
564
+ .cevvo-modal-overlay.mode-modal .cevvo-modal-body {
565
+ display: none;
566
+ }
567
+
568
+ .cevvo-modal-overlay.mode-modal.has-messages .cevvo-modal-body {
569
+ display: block;
570
+ max-height: 400px;
571
+ }
572
+
573
+ .cevvo-modal-overlay.mode-modal .cevvo-modal-footer {
574
+ padding: 20px 24px;
575
+ background: #fff;
576
+ border-top: none;
577
+ }
578
+
579
+ .cevvo-modal-overlay.mode-modal .cevvo-input-wrapper {
580
+ display: flex;
581
+ flex-direction: column;
582
+ gap: 12px;
583
+ padding: 16px;
584
+ border: 1px solid #e4e4e7;
585
+ border-radius: 14px;
586
+ background: #fff;
587
+ }
588
+
589
+ .cevvo-modal-overlay.mode-modal .cevvo-input {
590
+ border: none;
591
+ padding: 0;
592
+ font-size: 15px;
593
+ background: transparent;
594
+ }
595
+
596
+ .cevvo-modal-overlay.mode-modal .cevvo-input:focus {
597
+ outline: none;
598
+ box-shadow: none;
599
+ }
600
+
601
+ .cevvo-modal-overlay.mode-modal .cevvo-input-actions {
602
+ display: flex;
603
+ align-items: center;
604
+ justify-content: space-between;
605
+ }
606
+
607
+ .cevvo-modal-overlay.mode-modal .cevvo-deep-thinking-wrapper {
608
+ position: relative;
609
+ }
610
+
611
+ .cevvo-modal-overlay.mode-modal .cevvo-deep-thinking-btn {
612
+ display: flex;
613
+ align-items: center;
614
+ gap: 8px;
615
+ padding: 8px 14px;
616
+ border: 2px solid #e4e4e7;
617
+ border-radius: 8px;
618
+ background: #fff;
619
+ color: #3f3f46;
620
+ font-size: 13px;
621
+ font-weight: 500;
622
+ cursor: pointer;
623
+ transition: all 0.15s ease;
624
+ }
625
+
626
+ .cevvo-modal-overlay.mode-modal .cevvo-deep-thinking-btn:hover {
627
+ border-color: var(--cevvo-accent, #2563eb);
628
+ color: var(--cevvo-accent, #2563eb);
629
+ }
630
+
631
+ .cevvo-modal-overlay.mode-modal .cevvo-deep-thinking-btn.active {
632
+ border-color: var(--cevvo-accent, #2563eb);
633
+ color: var(--cevvo-accent, #2563eb);
634
+ background: rgba(37, 99, 235, 0.05);
635
+ }
636
+
637
+ .cevvo-modal-overlay.mode-modal .cevvo-deep-thinking-tooltip {
638
+ position: absolute;
639
+ bottom: calc(100% + 8px);
640
+ left: 0;
641
+ width: 220px;
642
+ padding: 12px 14px;
643
+ background: #fff;
644
+ border: 1px solid #e4e4e7;
645
+ border-radius: 10px;
646
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
647
+ font-size: 13px;
648
+ line-height: 1.5;
649
+ color: #3f3f46;
650
+ opacity: 0;
651
+ visibility: hidden;
652
+ transform: translateY(4px);
653
+ transition: all 0.15s ease;
654
+ pointer-events: none;
655
+ }
656
+
657
+ .cevvo-modal-overlay.mode-modal .cevvo-deep-thinking-wrapper:hover .cevvo-deep-thinking-tooltip {
658
+ opacity: 1;
659
+ visibility: visible;
660
+ transform: translateY(0);
661
+ }
662
+
663
+ .cevvo-modal-overlay.mode-modal .cevvo-send-btn {
664
+ width: 36px;
665
+ height: 36px;
666
+ border-radius: 8px;
667
+ }
668
+
669
+ .cevvo-modal-overlay.mode-modal .cevvo-powered {
670
+ display: flex;
671
+ align-items: center;
672
+ justify-content: space-between;
673
+ margin-top: 16px;
674
+ }
675
+
676
+ .cevvo-modal-overlay.mode-modal .cevvo-powered-left {
677
+ display: flex;
678
+ align-items: center;
679
+ gap: 6px;
680
+ }
681
+
682
+ .cevvo-modal-overlay.mode-modal .cevvo-powered-left svg {
683
+ width: 16px;
684
+ height: 16px;
685
+ }
686
+ `;class h{constructor(e={}){this.config={...f,...e},this.isOpen=!1,this.messages=[],this.threadId=null,this.isLoading=!1,this.injectStyles(),this.createElements(),this.bindEvents()}injectStyles(){if(document.getElementById("cevvo-widget-styles"))return;const e=document.createElement("style");e.id="cevvo-widget-styles",e.textContent=b,document.head.appendChild(e)}createElements(){this.container=document.createElement("div"),this.container.className="cevvo-widget",this.container.style.setProperty("--cevvo-accent",this.config.projectColor),this.button=document.createElement("button"),this.button.className=`cevvo-widget-btn ${this.config.buttonPosition}`,this.button.style.background=this.config.buttonBgColor,this.button.style.color=this.config.buttonTextColor,this.button.innerHTML=`${i.cevvoLogo}<span>${this.config.buttonText}</span>`,this.overlay=document.createElement("div"),this.overlay.className=`cevvo-modal-overlay ${this.config.mode==="modal"?"mode-modal":""}`,this.overlay.innerHTML=this.config.mode==="modal"?this.renderModalMode():this.renderModal(),this.container.appendChild(this.button),this.container.appendChild(this.overlay),document.body.appendChild(this.container),this.modal=this.overlay.querySelector(".cevvo-modal"),this.messagesContainer=this.overlay.querySelector(".cevvo-modal-body"),this.input=this.overlay.querySelector(".cevvo-input"),this.form=this.overlay.querySelector(".cevvo-input-form"),this.clearBtn=this.overlay.querySelector(".cevvo-clear-btn"),this.closeBtn=this.overlay.querySelector(".cevvo-close-btn")}renderModal(){const e=this.config.modalExampleQuestions.map(t=>`<button class="cevvo-example-btn">${this.escapeHtml(t)}</button>`).join("");return`
687
+ <div class="cevvo-modal">
688
+ <div class="cevvo-modal-header">
689
+ <div class="cevvo-header-info">
690
+ <div class="cevvo-header-logo">${i.cevvoLogo}</div>
691
+ <div class="cevvo-header-text">
692
+ <h2>${this.escapeHtml(this.config.modalTitle)}</h2>
693
+ <p>${this.escapeHtml(this.config.modalSubtitle)}</p>
694
+ </div>
695
+ </div>
696
+ <div class="cevvo-header-actions">
697
+ <button class="cevvo-clear-btn" title="Clear chat" style="display:none;">${i.trash}</button>
698
+ <button class="cevvo-close-btn" title="Close">${i.close}</button>
699
+ </div>
700
+ </div>
701
+ <div class="cevvo-modal-body">
702
+ <div class="cevvo-empty-state">
703
+ <div class="cevvo-empty-icon">${i.sparkle}</div>
704
+ <h3>How can I help you?</h3>
705
+ <p>Ask me anything about our documentation and I'll help you find the answer.</p>
706
+ ${e?`<div class="cevvo-example-questions">${e}</div>`:""}
707
+ </div>
708
+ </div>
709
+ <div class="cevvo-modal-footer">
710
+ <form class="cevvo-input-form">
711
+ <input type="text" class="cevvo-input" placeholder="${this.escapeHtml(this.config.modalPlaceholder)}" />
712
+ <button type="submit" class="cevvo-send-btn" disabled>${i.send}</button>
713
+ </form>
714
+ <p class="cevvo-powered">Powered by <a href="https://cevvo.ai" target="_blank" rel="noopener">Cevvo</a></p>
715
+ </div>
716
+ </div>
717
+ `}renderModalMode(){const e=this.config.modalDescription||'This is a <a href="#">Cevvo-powered</a> AI assistant with access to all documentation, FAQs, API specs and tutorials. This is an example of the <a href="#">website widget</a> integration.';return`
718
+ <div class="cevvo-modal">
719
+ <div class="cevvo-modal-header">
720
+ <div class="cevvo-header-info">
721
+ <div class="cevvo-header-logo">${i.cevvoLogo}</div>
722
+ <div class="cevvo-header-text">
723
+ <h2>${this.escapeHtml(this.config.modalTitle)}</h2>
724
+ </div>
725
+ </div>
726
+ <div class="cevvo-header-actions">
727
+ <button class="cevvo-clear-btn" title="Clear chat" style="display:none;">${i.trash}</button>
728
+ <button class="cevvo-close-btn" title="Close">${i.close}</button>
729
+ </div>
730
+ </div>
731
+ <div class="cevvo-modal-description" style="padding: 0 24px;">
732
+ ${e}
733
+ </div>
734
+ <div class="cevvo-modal-body">
735
+ </div>
736
+ <div class="cevvo-modal-footer">
737
+ <div class="cevvo-input-wrapper">
738
+ <form class="cevvo-input-form" style="gap: 0;">
739
+ <input type="text" class="cevvo-input" placeholder="${this.escapeHtml(this.config.modalPlaceholder)}" />
740
+ </form>
741
+ <div class="cevvo-input-actions">
742
+ <div class="cevvo-deep-thinking-wrapper">
743
+ <div class="cevvo-deep-thinking-tooltip">
744
+ For harder questions. Searches longer across all sources. Takes up to ~1 minute.
745
+ </div>
746
+ <button type="button" class="cevvo-deep-thinking-btn">
747
+ ${i.brain}
748
+ <span>Deep thinking</span>
749
+ </button>
750
+ </div>
751
+ <button type="submit" class="cevvo-send-btn" disabled>${i.arrowUp}</button>
752
+ </div>
753
+ </div>
754
+ <div class="cevvo-powered">
755
+ <div class="cevvo-powered-left">
756
+ <span>Powered by</span>
757
+ ${i.cevvoLogo}
758
+ <a href="https://cevvo.ai" target="_blank" rel="noopener">cevvo.ai</a>
759
+ </div>
760
+ </div>
761
+ </div>
762
+ </div>
763
+ `}bindEvents(){this.button.addEventListener("click",()=>this.toggle()),this.overlay.addEventListener("click",t=>{t.target===this.overlay&&this.close()}),this.closeBtn.addEventListener("click",()=>this.close()),this.clearBtn.addEventListener("click",()=>this.clearMessages()),this.form.addEventListener("submit",t=>{t.preventDefault(),this.sendMessage()}),this.overlay.querySelector(".cevvo-send-btn").addEventListener("click",t=>{t.preventDefault(),this.sendMessage()}),this.input.addEventListener("input",()=>{const t=this.overlay.querySelector(".cevvo-send-btn");t.disabled=!this.input.value.trim()||this.isLoading}),this.input.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),this.sendMessage())}),this.overlay.addEventListener("click",t=>{t.target.classList.contains("cevvo-example-btn")&&(this.input.value=t.target.textContent,this.sendMessage())}),this.messagesContainer.addEventListener("click",t=>{const o=t.target.closest("button");if(!o)return;const s=o.closest(".cevvo-actions");if(!s)return;const n=s.dataset.messageId,c=o.closest(".cevvo-message"),l=c==null?void 0:c.dataset.msgId,r=this.messages.find(d=>d.id===l);if(o.classList.contains("cevvo-copy-btn")&&(r!=null&&r.content)&&(navigator.clipboard.writeText(r.content),o.innerHTML=i.check,setTimeout(()=>{o.innerHTML=i.copy},2e3)),o.classList.contains("cevvo-thumbs-up")&&n){const d=(r==null?void 0:r.feedback)==="positive"?null:"positive";r&&(r.feedback=d),this.submitFeedback(parseInt(n),5),this.renderMessages()}if(o.classList.contains("cevvo-thumbs-down")&&n){const d=(r==null?void 0:r.feedback)==="negative"?null:"negative";r&&(r.feedback=d),this.submitFeedback(parseInt(n),1),this.renderMessages()}}),document.addEventListener("keydown",t=>{(t.metaKey||t.ctrlKey)&&t.key==="k"&&(t.preventDefault(),this.toggle()),t.key==="Escape"&&this.isOpen&&this.close()})}toggle(){this.isOpen?this.close():this.open()}open(){this.isOpen=!0,this.overlay.classList.add("is-open"),this.button.style.display="none",setTimeout(()=>this.input.focus(),100)}close(){this.isOpen=!1,this.overlay.classList.remove("is-open"),this.button.style.display="flex"}updateModalState(){this.config.mode==="modal"&&(this.messages.length>0?this.overlay.classList.add("has-messages"):this.overlay.classList.remove("has-messages"))}async sendMessage(){const e=this.input.value.trim();if(!e||this.isLoading)return;if(!this.config.projectId||!this.config.apiKey){this.addMessage({role:"assistant",content:'Widget not configured. Please set Project ID and API Key, then click "Apply Changes".'});return}this.input.value="",this.overlay.querySelector(".cevvo-send-btn").disabled=!0,this.addMessage({role:"user",content:e});const t=this.addMessage({role:"assistant",content:"",isLoading:!0});this.isLoading=!0;try{const o={"Content-Type":"application/json","X-API-Key":this.config.apiKey},s=await fetch(`${this.config.apiUrl}/widget/chat/stream`,{method:"POST",headers:o,body:JSON.stringify({message:e,thread_id:this.threadId,project_id:this.config.projectId})});if(!s.ok)throw new Error("Failed to send message");const n=s.body.getReader(),c=new TextDecoder;let l="",r=[],d=null;for(;;){const{done:x,value:y}=await n.read();if(x)break;const w=c.decode(y,{stream:!0}).split(`
764
+ `);for(const u of w)if(u.startsWith("data: ")){const m=u.slice(6);if(m==="[DONE]")continue;try{const v=JSON.parse(m);v.thread_id&&(this.threadId=v.thread_id),v.token&&(l+=v.token,this.updateMessage(t,{content:l,isLoading:!1})),v.sources&&(r=v.sources),v.question_answer_id&&(d=v.question_answer_id)}catch{}}}this.updateMessage(t,{content:l||"I'm sorry, I couldn't generate a response.",sources:r,messageId:d,isLoading:!1})}catch(o){console.error("Chat error:",o),this.updateMessage(t,{content:"Sorry, something went wrong. Please try again.",isLoading:!1})}finally{this.isLoading=!1}}addMessage(e){const t=Date.now().toString();return this.messages.push({id:t,...e}),this.renderMessages(),this.clearBtn.style.display="flex",this.updateModalState(),t}updateMessage(e,t){const o=this.messages.find(s=>s.id===e);o&&(Object.assign(o,t),this.renderMessages())}clearMessages(){this.messages=[],this.threadId=null,this.clearBtn.style.display="none",this.renderMessages(),this.updateModalState()}renderMessages(){if(this.messages.length===0){this.messagesContainer.innerHTML=`
765
+ <div class="cevvo-empty-state">
766
+ <div class="cevvo-empty-icon">${i.sparkle}</div>
767
+ <h3>How can I help you?</h3>
768
+ <p>Ask me anything about our documentation and I'll help you find the answer.</p>
769
+ ${this.config.modalExampleQuestions.length?`
770
+ <div class="cevvo-example-questions">
771
+ ${this.config.modalExampleQuestions.map(e=>`<button class="cevvo-example-btn">${this.escapeHtml(e)}</button>`).join("")}
772
+ </div>
773
+ `:""}
774
+ </div>
775
+ `;return}this.messagesContainer.innerHTML=`
776
+ <div class="cevvo-messages">
777
+ ${this.messages.map(e=>this.renderMessage(e)).join("")}
778
+ </div>
779
+ `,this.messagesContainer.scrollTop=this.messagesContainer.scrollHeight}renderMessage(e){var l;const t=e.role==="assistant"?i.cevvoLogo:i.user,o=e.isLoading?'<div class="cevvo-typing"><span></span><span></span><span></span></div>':this.formatContent(e.content),s=(l=e.sources)!=null&&l.length?`
780
+ <div class="cevvo-sources">
781
+ <div class="cevvo-sources-header">${i.link} Sources</div>
782
+ ${e.sources.map((r,d)=>`
783
+ <a href="${this.escapeHtml(r.source_url||r.url||"#")}" target="_blank" rel="noopener" class="cevvo-source-link">
784
+ <span class="cevvo-source-num">${d+1}</span>
785
+ <span class="cevvo-source-title">${this.escapeHtml(r.title||"Source")}</span>
786
+ ${i.link}
787
+ </a>
788
+ `).join("")}
789
+ </div>
790
+ `:"",n=e.feedback==="positive"?"feedback-positive":e.feedback==="negative"?"feedback-negative":"",c=e.role==="assistant"&&e.content&&!e.isLoading?`
791
+ <div class="cevvo-actions ${n}" data-message-id="${e.messageId||""}">
792
+ <button class="cevvo-copy-btn" title="Copy">${i.copy}</button>
793
+ <button class="cevvo-thumbs-up ${e.feedback==="positive"?"active":""}" title="Helpful">${i.thumbsUp}</button>
794
+ <button class="cevvo-thumbs-down ${e.feedback==="negative"?"active":""}" title="Not helpful">${i.thumbsDown}</button>
795
+ </div>
796
+ `:"";return`
797
+ <div class="cevvo-message ${e.role}" data-msg-id="${e.id}">
798
+ <div class="cevvo-avatar ${e.role}">${t}</div>
799
+ <div class="cevvo-message-content">
800
+ <div class="cevvo-bubble ${e.role}">${o}</div>
801
+ ${s}
802
+ ${c}
803
+ </div>
804
+ </div>
805
+ `}async submitFeedback(e,t){if(e)try{await fetch(`${this.config.apiUrl}/widget/feedback`,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":this.config.apiKey},body:JSON.stringify({message_id:e,rating:t,project_id:this.config.projectId})})}catch(o){console.error("Feedback error:",o)}}formatContent(e){return this.escapeHtml(e).replace(/\n/g,"<br>").replace(/\*\*(.*?)\*\*/g,"<strong>$1</strong>").replace(/\*(.*?)\*/g,"<em>$1</em>").replace(/`(.*?)`/g,"<code>$1</code>")}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}}async function g(a,e,t){const o=t||p;try{const s=await fetch(`${o}/widget/config?project_id=${encodeURIComponent(a)}`,{method:"GET",headers:{"X-API-Key":e}});if(!s.ok)return console.warn("[CevvoWidget] Failed to fetch config:",s.status),null;const n=await s.json();return{mode:n.mode,buttonText:n.buttonText,buttonPosition:n.position,projectColor:n.primaryColor,buttonBgColor:n.primaryColor,modalTitle:n.modalTitle,modalSubtitle:n.modalSubtitle,modalExampleQuestions:n.exampleQuestions||[],welcomeMessage:n.welcomeMessage}}catch(s){return console.warn("[CevvoWidget] Error fetching config:",s.message),null}}window.CevvoWidget={init:a=>new h(a),initWithRemoteConfig:async(a,e,t)=>{const o=await g(a,e,t),s={projectId:a,apiKey:e,apiUrl:t,...o};return new h(s)},testCredentials:async(a,e,t)=>{const o=t||p;try{const s=await fetch(`${o}/widget/config?project_id=${encodeURIComponent(a)}`,{method:"GET",headers:{"X-API-Key":e}});return{success:s.ok,status:s.status,data:s.ok?await s.json():null}}catch(s){return{success:!1,status:0,error:s.message}}}},document.addEventListener("DOMContentLoaded",async()=>{const a=document.querySelector("script[data-cevvo-project-id]");if(a){const e=a.getAttribute("data-cevvo-project-id")||"",t=a.getAttribute("data-cevvo-api-key")||"",o=a.getAttribute("data-cevvo-api-url")||void 0;let s=null;e&&t&&(s=await g(e,t,o));const n={projectId:e,apiKey:t,apiUrl:o,...s,...a.getAttribute("data-cevvo-project-name")&&{projectName:a.getAttribute("data-cevvo-project-name")},...a.getAttribute("data-cevvo-project-color")&&{projectColor:a.getAttribute("data-cevvo-project-color")},...a.getAttribute("data-cevvo-button-text")&&{buttonText:a.getAttribute("data-cevvo-button-text")},...a.getAttribute("data-cevvo-button-bg-color")&&{buttonBgColor:a.getAttribute("data-cevvo-button-bg-color")},...a.getAttribute("data-cevvo-modal-title")&&{modalTitle:a.getAttribute("data-cevvo-modal-title")},...a.getAttribute("data-cevvo-modal-placeholder")&&{modalPlaceholder:a.getAttribute("data-cevvo-modal-placeholder")}},c=a.getAttribute("data-cevvo-example-questions");if(c)try{n.modalExampleQuestions=JSON.parse(c)}catch{}new h(n)}})})();
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "cevvo-widget",
3
+ "version": "1.0.0",
4
+ "description": "Cevvo Widget - AI-powered chat for your docs",
5
+ "type": "module",
6
+ "main": "dist/cevvo-widget.js",
7
+ "module": "dist/cevvo-widget.js",
8
+ "unpkg": "dist/cevvo-widget.js",
9
+ "jsdelivr": "dist/cevvo-widget.js",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "dev": "vite",
15
+ "build": "vite build",
16
+ "preview": "vite preview",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "cevvo",
21
+ "chat",
22
+ "widget",
23
+ "ai",
24
+ "rag",
25
+ "chatbot",
26
+ "embed"
27
+ ],
28
+ "author": "Cevvo AI",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/cevvo/cevvo.git",
33
+ "directory": "widget"
34
+ },
35
+ "homepage": "https://cevvo.ai",
36
+ "dependencies": {
37
+ "vue": "^3.4.0"
38
+ },
39
+ "devDependencies": {
40
+ "@vitejs/plugin-vue": "^5.0.0",
41
+ "typescript": "^5.3.0",
42
+ "vite": "^5.0.0",
43
+ "vite-plugin-css-injected-by-js": "^3.3.0",
44
+ "vue-tsc": "^2.0.0"
45
+ }
46
+ }