@kroonen-ai/librebot-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.
@@ -0,0 +1,3 @@
1
+ import { LibreBotWidget } from './widget';
2
+ import { LibreBotConfig } from './types';
3
+ export { LibreBotWidget, LibreBotConfig };
@@ -0,0 +1,4 @@
1
+ export declare const chatIcon = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"></path></svg>";
2
+ export declare const closeIcon = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>";
3
+ export declare const sendIcon = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"></line><polygon points=\"22 2 15 22 11 13 2 9 22 2\"></polygon></svg>";
4
+ export declare const botIcon = "<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>";
@@ -0,0 +1,2 @@
1
+ export { LibreBotWidget } from './widget';
2
+ export type { LibreBotConfig, Message, ChatResponse } from './types';
@@ -0,0 +1,631 @@
1
+ const getStyles = (primaryColor = '#14b8a6') => `
2
+ .librebot-widget {
3
+ --lb-primary: ${primaryColor};
4
+ --lb-primary-hover: color-mix(in srgb, ${primaryColor} 85%, white);
5
+ --lb-bg: #1a1a1a;
6
+ --lb-bg-secondary: #2a2a2a;
7
+ --lb-text: #ffffff;
8
+ --lb-text-secondary: #a0a0a0;
9
+ --lb-border: #3a3a3a;
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ font-size: 14px;
12
+ line-height: 1.5;
13
+ }
14
+
15
+ .librebot-widget.light {
16
+ --lb-bg: #ffffff;
17
+ --lb-bg-secondary: #f5f5f5;
18
+ --lb-text: #1a1a1a;
19
+ --lb-text-secondary: #666666;
20
+ --lb-border: #e0e0e0;
21
+ }
22
+
23
+ .librebot-fab {
24
+ position: fixed;
25
+ bottom: 20px;
26
+ right: 20px;
27
+ width: 56px;
28
+ height: 56px;
29
+ border-radius: 50%;
30
+ background: var(--lb-primary);
31
+ border: none;
32
+ cursor: pointer;
33
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ transition: transform 0.2s, box-shadow 0.2s;
38
+ z-index: 999998;
39
+ }
40
+
41
+ .librebot-fab:hover {
42
+ transform: scale(1.05);
43
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
44
+ }
45
+
46
+ .librebot-fab svg {
47
+ width: 24px;
48
+ height: 24px;
49
+ fill: white;
50
+ }
51
+
52
+ .librebot-fab.left {
53
+ right: auto;
54
+ left: 20px;
55
+ }
56
+
57
+ .librebot-modal {
58
+ position: fixed;
59
+ bottom: 90px;
60
+ right: 20px;
61
+ width: 380px;
62
+ max-width: calc(100vw - 40px);
63
+ height: 520px;
64
+ max-height: calc(100vh - 120px);
65
+ background: var(--lb-bg);
66
+ border: 1px solid var(--lb-border);
67
+ border-radius: 16px;
68
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
69
+ display: flex;
70
+ flex-direction: column;
71
+ overflow: hidden;
72
+ z-index: 999999;
73
+ opacity: 0;
74
+ transform: translateY(20px) scale(0.95);
75
+ transition: opacity 0.2s, transform 0.2s;
76
+ pointer-events: none;
77
+ }
78
+
79
+ .librebot-modal.open {
80
+ opacity: 1;
81
+ transform: translateY(0) scale(1);
82
+ pointer-events: auto;
83
+ }
84
+
85
+ .librebot-modal.left {
86
+ right: auto;
87
+ left: 20px;
88
+ }
89
+
90
+ .librebot-header {
91
+ padding: 16px;
92
+ border-bottom: 1px solid var(--lb-border);
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: space-between;
96
+ }
97
+
98
+ .librebot-header-content {
99
+ display: flex;
100
+ align-items: center;
101
+ gap: 12px;
102
+ }
103
+
104
+ .librebot-avatar {
105
+ width: 40px;
106
+ height: 40px;
107
+ border-radius: 10px;
108
+ background: var(--lb-primary);
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ }
113
+
114
+ .librebot-avatar svg {
115
+ width: 24px;
116
+ height: 24px;
117
+ fill: white;
118
+ }
119
+
120
+ .librebot-title {
121
+ font-weight: 600;
122
+ color: var(--lb-text);
123
+ margin: 0;
124
+ font-size: 16px;
125
+ }
126
+
127
+ .librebot-subtitle {
128
+ font-size: 12px;
129
+ color: var(--lb-text-secondary);
130
+ margin: 0;
131
+ }
132
+
133
+ .librebot-close {
134
+ width: 32px;
135
+ height: 32px;
136
+ border-radius: 8px;
137
+ border: none;
138
+ background: transparent;
139
+ cursor: pointer;
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ color: var(--lb-text-secondary);
144
+ transition: background 0.2s;
145
+ }
146
+
147
+ .librebot-close:hover {
148
+ background: var(--lb-bg-secondary);
149
+ }
150
+
151
+ .librebot-messages {
152
+ flex: 1;
153
+ overflow-y: auto;
154
+ padding: 16px;
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: 12px;
158
+ }
159
+
160
+ .librebot-message {
161
+ max-width: 85%;
162
+ padding: 10px 14px;
163
+ border-radius: 16px;
164
+ word-wrap: break-word;
165
+ }
166
+
167
+ .librebot-message.user {
168
+ align-self: flex-end;
169
+ background: var(--lb-primary);
170
+ color: white;
171
+ border-bottom-right-radius: 4px;
172
+ }
173
+
174
+ .librebot-message.assistant {
175
+ align-self: flex-start;
176
+ background: var(--lb-bg-secondary);
177
+ color: var(--lb-text);
178
+ border-bottom-left-radius: 4px;
179
+ }
180
+
181
+ .librebot-message code {
182
+ background: rgba(0, 0, 0, 0.2);
183
+ padding: 2px 6px;
184
+ border-radius: 4px;
185
+ font-family: monospace;
186
+ font-size: 13px;
187
+ }
188
+
189
+ .librebot-message pre {
190
+ background: rgba(0, 0, 0, 0.3);
191
+ padding: 10px;
192
+ border-radius: 8px;
193
+ overflow-x: auto;
194
+ margin: 8px 0;
195
+ }
196
+
197
+ .librebot-message pre code {
198
+ background: none;
199
+ padding: 0;
200
+ }
201
+
202
+ .librebot-typing {
203
+ display: flex;
204
+ gap: 4px;
205
+ padding: 12px 16px;
206
+ align-self: flex-start;
207
+ background: var(--lb-bg-secondary);
208
+ border-radius: 16px;
209
+ border-bottom-left-radius: 4px;
210
+ }
211
+
212
+ .librebot-typing span {
213
+ width: 8px;
214
+ height: 8px;
215
+ background: var(--lb-text-secondary);
216
+ border-radius: 50%;
217
+ animation: librebot-bounce 1.4s infinite ease-in-out;
218
+ }
219
+
220
+ .librebot-typing span:nth-child(1) { animation-delay: 0s; }
221
+ .librebot-typing span:nth-child(2) { animation-delay: 0.2s; }
222
+ .librebot-typing span:nth-child(3) { animation-delay: 0.4s; }
223
+
224
+ @keyframes librebot-bounce {
225
+ 0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; }
226
+ 40% { transform: scale(1); opacity: 1; }
227
+ }
228
+
229
+ .librebot-input-area {
230
+ padding: 12px 16px;
231
+ border-top: 1px solid var(--lb-border);
232
+ display: flex;
233
+ gap: 8px;
234
+ }
235
+
236
+ .librebot-input {
237
+ flex: 1;
238
+ padding: 10px 14px;
239
+ border: 1px solid var(--lb-border);
240
+ border-radius: 24px;
241
+ background: var(--lb-bg-secondary);
242
+ color: var(--lb-text);
243
+ font-size: 14px;
244
+ outline: none;
245
+ transition: border-color 0.2s;
246
+ }
247
+
248
+ .librebot-input:focus {
249
+ border-color: var(--lb-primary);
250
+ }
251
+
252
+ .librebot-input::placeholder {
253
+ color: var(--lb-text-secondary);
254
+ }
255
+
256
+ .librebot-send {
257
+ width: 40px;
258
+ height: 40px;
259
+ border-radius: 50%;
260
+ border: none;
261
+ background: var(--lb-primary);
262
+ cursor: pointer;
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: center;
266
+ transition: background 0.2s;
267
+ }
268
+
269
+ .librebot-send:hover {
270
+ background: var(--lb-primary-hover);
271
+ }
272
+
273
+ .librebot-send:disabled {
274
+ opacity: 0.5;
275
+ cursor: not-allowed;
276
+ }
277
+
278
+ .librebot-send svg {
279
+ width: 18px;
280
+ height: 18px;
281
+ fill: white;
282
+ }
283
+
284
+ .librebot-welcome {
285
+ text-align: center;
286
+ padding: 20px;
287
+ color: var(--lb-text-secondary);
288
+ }
289
+
290
+ .librebot-welcome-icon {
291
+ width: 48px;
292
+ height: 48px;
293
+ margin: 0 auto 12px;
294
+ background: var(--lb-primary);
295
+ border-radius: 12px;
296
+ display: flex;
297
+ align-items: center;
298
+ justify-content: center;
299
+ }
300
+
301
+ .librebot-welcome-icon svg {
302
+ width: 28px;
303
+ height: 28px;
304
+ fill: white;
305
+ }
306
+
307
+ .librebot-powered {
308
+ text-align: center;
309
+ padding: 8px;
310
+ font-size: 11px;
311
+ color: var(--lb-text-secondary);
312
+ border-top: 1px solid var(--lb-border);
313
+ }
314
+
315
+ .librebot-powered a {
316
+ color: var(--lb-primary);
317
+ text-decoration: none;
318
+ }
319
+
320
+ .librebot-powered a:hover {
321
+ text-decoration: underline;
322
+ }
323
+ `;
324
+
325
+ const chatIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>`;
326
+ const closeIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`;
327
+ const sendIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>`;
328
+
329
+ class LibreBotWidget {
330
+ constructor(config) {
331
+ this.container = null;
332
+ this.modal = null;
333
+ this.messagesContainer = null;
334
+ this.input = null;
335
+ this.isOpen = false;
336
+ this.messages = [];
337
+ this.isLoading = false;
338
+ this.defaultConfig = {
339
+ apiUrl: 'https://api.librebot.io',
340
+ position: 'bottom-right',
341
+ theme: 'dark',
342
+ primaryColor: '#14b8a6',
343
+ title: 'Libre Bot',
344
+ subtitle: 'AI Documentation Assistant',
345
+ placeholder: 'Ask a question...',
346
+ welcomeMessage: 'Hi! I can help you find answers in the documentation. What would you like to know?',
347
+ streaming: true,
348
+ };
349
+ if (!config.apiKey) {
350
+ console.error('LibreBot: API key is required');
351
+ return;
352
+ }
353
+ this.config = { ...this.defaultConfig, ...config };
354
+ this.init();
355
+ }
356
+ init() {
357
+ // Inject styles
358
+ this.injectStyles();
359
+ // Create widget container
360
+ this.createWidget();
361
+ // Add welcome message
362
+ if (this.config.welcomeMessage) {
363
+ this.addMessage('assistant', this.config.welcomeMessage);
364
+ }
365
+ }
366
+ injectStyles() {
367
+ const styleId = 'librebot-styles';
368
+ if (document.getElementById(styleId))
369
+ return;
370
+ const style = document.createElement('style');
371
+ style.id = styleId;
372
+ style.textContent = getStyles(this.config.primaryColor);
373
+ document.head.appendChild(style);
374
+ }
375
+ createWidget() {
376
+ // Create container
377
+ this.container = document.createElement('div');
378
+ this.container.className = `librebot-widget ${this.config.theme === 'light' ? 'light' : ''}`;
379
+ // Create FAB button
380
+ const fab = document.createElement('button');
381
+ fab.className = `librebot-fab ${this.config.position === 'bottom-left' ? 'left' : ''}`;
382
+ fab.innerHTML = chatIcon;
383
+ fab.onclick = () => this.toggle();
384
+ // Create modal
385
+ this.modal = document.createElement('div');
386
+ this.modal.className = `librebot-modal ${this.config.position === 'bottom-left' ? 'left' : ''}`;
387
+ this.modal.innerHTML = this.getModalHTML();
388
+ // Add to container
389
+ this.container.appendChild(fab);
390
+ this.container.appendChild(this.modal);
391
+ document.body.appendChild(this.container);
392
+ // Get references
393
+ this.messagesContainer = this.modal.querySelector('.librebot-messages');
394
+ this.input = this.modal.querySelector('.librebot-input');
395
+ // Add event listeners
396
+ const closeBtn = this.modal.querySelector('.librebot-close');
397
+ closeBtn?.addEventListener('click', () => this.close());
398
+ const sendBtn = this.modal.querySelector('.librebot-send');
399
+ sendBtn?.addEventListener('click', () => this.sendMessage());
400
+ this.input?.addEventListener('keypress', (e) => {
401
+ if (e.key === 'Enter' && !e.shiftKey) {
402
+ e.preventDefault();
403
+ this.sendMessage();
404
+ }
405
+ });
406
+ // Auto theme detection
407
+ if (this.config.theme === 'auto') {
408
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: light)');
409
+ this.updateTheme(mediaQuery.matches);
410
+ mediaQuery.addEventListener('change', (e) => this.updateTheme(e.matches));
411
+ }
412
+ }
413
+ getModalHTML() {
414
+ return `
415
+ <div class="librebot-header">
416
+ <div class="librebot-header-content">
417
+ <div class="librebot-avatar">${chatIcon}</div>
418
+ <div>
419
+ <h3 class="librebot-title">${this.config.title}</h3>
420
+ <p class="librebot-subtitle">${this.config.subtitle}</p>
421
+ </div>
422
+ </div>
423
+ <button class="librebot-close">${closeIcon}</button>
424
+ </div>
425
+ <div class="librebot-messages"></div>
426
+ <div class="librebot-input-area">
427
+ <input type="text" class="librebot-input" placeholder="${this.config.placeholder}" />
428
+ <button class="librebot-send">${sendIcon}</button>
429
+ </div>
430
+ <div class="librebot-powered">
431
+ Powered by <a href="https://librebot.io" target="_blank">Libre Bot</a>
432
+ </div>
433
+ `;
434
+ }
435
+ updateTheme(isLight) {
436
+ if (isLight) {
437
+ this.container?.classList.add('light');
438
+ }
439
+ else {
440
+ this.container?.classList.remove('light');
441
+ }
442
+ }
443
+ toggle() {
444
+ this.isOpen ? this.close() : this.open();
445
+ }
446
+ open() {
447
+ this.isOpen = true;
448
+ this.modal?.classList.add('open');
449
+ this.input?.focus();
450
+ }
451
+ close() {
452
+ this.isOpen = false;
453
+ this.modal?.classList.remove('open');
454
+ }
455
+ addMessage(role, content) {
456
+ const message = {
457
+ id: `msg-${Date.now()}`,
458
+ role,
459
+ content,
460
+ timestamp: new Date(),
461
+ };
462
+ this.messages.push(message);
463
+ this.renderMessage(message);
464
+ }
465
+ renderMessage(message) {
466
+ if (!this.messagesContainer)
467
+ return;
468
+ const el = document.createElement('div');
469
+ el.className = `librebot-message ${message.role}`;
470
+ el.innerHTML = this.formatMessage(message.content);
471
+ this.messagesContainer.appendChild(el);
472
+ this.scrollToBottom();
473
+ }
474
+ formatMessage(content) {
475
+ // Basic markdown-like formatting
476
+ return content
477
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
478
+ .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
479
+ .replace(/\n/g, '<br>');
480
+ }
481
+ showTyping() {
482
+ const typing = document.createElement('div');
483
+ typing.className = 'librebot-typing';
484
+ typing.innerHTML = '<span></span><span></span><span></span>';
485
+ this.messagesContainer?.appendChild(typing);
486
+ this.scrollToBottom();
487
+ return typing;
488
+ }
489
+ scrollToBottom() {
490
+ if (this.messagesContainer) {
491
+ this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
492
+ }
493
+ }
494
+ async sendMessage() {
495
+ if (!this.input || this.isLoading)
496
+ return;
497
+ const content = this.input.value.trim();
498
+ if (!content)
499
+ return;
500
+ // Add user message
501
+ this.addMessage('user', content);
502
+ this.input.value = '';
503
+ // Show typing indicator
504
+ this.isLoading = true;
505
+ const typing = this.showTyping();
506
+ try {
507
+ if (this.config.streaming) {
508
+ await this.callStreamingAPI(content, typing);
509
+ }
510
+ else {
511
+ const response = await this.callAPI(content);
512
+ typing.remove();
513
+ if (response.error) {
514
+ this.addMessage('assistant', `Sorry, I encountered an error: ${response.error}`);
515
+ }
516
+ else {
517
+ this.addMessage('assistant', response.response);
518
+ }
519
+ }
520
+ }
521
+ catch (error) {
522
+ typing.remove();
523
+ this.addMessage('assistant', 'Sorry, I had trouble connecting. Please try again.');
524
+ console.error('LibreBot error:', error);
525
+ }
526
+ finally {
527
+ this.isLoading = false;
528
+ }
529
+ }
530
+ async callStreamingAPI(message, typing) {
531
+ const response = await fetch(`${this.config.apiUrl}/api/chat`, {
532
+ method: 'POST',
533
+ headers: {
534
+ 'Content-Type': 'application/json',
535
+ Authorization: `Bearer ${this.config.apiKey}`,
536
+ },
537
+ body: JSON.stringify({
538
+ message,
539
+ context: this.getConversationContext(),
540
+ stream: true,
541
+ }),
542
+ });
543
+ if (!response.ok) {
544
+ typing.remove();
545
+ const error = await response.json().catch(() => ({ error: 'Request failed' }));
546
+ this.addMessage('assistant', `Sorry, I encountered an error: ${error.error || 'Request failed'}`);
547
+ return;
548
+ }
549
+ // Remove typing indicator and create streaming message element
550
+ typing.remove();
551
+ const messageEl = document.createElement('div');
552
+ messageEl.className = 'librebot-message assistant';
553
+ this.messagesContainer?.appendChild(messageEl);
554
+ const reader = response.body?.getReader();
555
+ if (!reader) {
556
+ this.addMessage('assistant', 'Sorry, streaming is not supported.');
557
+ return;
558
+ }
559
+ const decoder = new TextDecoder();
560
+ let fullContent = '';
561
+ try {
562
+ while (true) {
563
+ const { done, value } = await reader.read();
564
+ if (done)
565
+ break;
566
+ const chunk = decoder.decode(value, { stream: true });
567
+ const lines = chunk.split('\n').filter((line) => line.trim() !== '');
568
+ for (const line of lines) {
569
+ if (line.startsWith('data: ')) {
570
+ const data = line.slice(6);
571
+ if (data === '[DONE]')
572
+ continue;
573
+ try {
574
+ const parsed = JSON.parse(data);
575
+ if (parsed.content) {
576
+ fullContent += parsed.content;
577
+ messageEl.innerHTML = this.formatMessage(fullContent);
578
+ this.scrollToBottom();
579
+ }
580
+ }
581
+ catch {
582
+ // Skip malformed JSON
583
+ }
584
+ }
585
+ }
586
+ }
587
+ }
588
+ catch (error) {
589
+ console.error('Stream reading error:', error);
590
+ }
591
+ // Add to messages array
592
+ this.messages.push({
593
+ id: `msg-${Date.now()}`,
594
+ role: 'assistant',
595
+ content: fullContent,
596
+ timestamp: new Date(),
597
+ });
598
+ }
599
+ async callAPI(message) {
600
+ const response = await fetch(`${this.config.apiUrl}/api/chat`, {
601
+ method: 'POST',
602
+ headers: {
603
+ 'Content-Type': 'application/json',
604
+ Authorization: `Bearer ${this.config.apiKey}`,
605
+ },
606
+ body: JSON.stringify({
607
+ message,
608
+ context: this.getConversationContext(),
609
+ }),
610
+ });
611
+ if (!response.ok) {
612
+ const error = await response.json().catch(() => ({ error: 'Request failed' }));
613
+ return { response: '', error: error.error || 'Request failed' };
614
+ }
615
+ return response.json();
616
+ }
617
+ getConversationContext() {
618
+ // Get last 10 messages for context
619
+ return this.messages.slice(-10).map((m) => ({
620
+ role: m.role,
621
+ content: m.content,
622
+ }));
623
+ }
624
+ destroy() {
625
+ this.container?.remove();
626
+ document.getElementById('librebot-styles')?.remove();
627
+ }
628
+ }
629
+
630
+ export { LibreBotWidget };
631
+ //# sourceMappingURL=librebot.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"librebot.esm.js","sources":["../src/styles.ts","../src/icons.ts","../src/widget.ts"],"sourcesContent":["export const getStyles = (primaryColor: string = '#14b8a6') => `\n .librebot-widget {\n --lb-primary: ${primaryColor};\n --lb-primary-hover: color-mix(in srgb, ${primaryColor} 85%, white);\n --lb-bg: #1a1a1a;\n --lb-bg-secondary: #2a2a2a;\n --lb-text: #ffffff;\n --lb-text-secondary: #a0a0a0;\n --lb-border: #3a3a3a;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n }\n\n .librebot-widget.light {\n --lb-bg: #ffffff;\n --lb-bg-secondary: #f5f5f5;\n --lb-text: #1a1a1a;\n --lb-text-secondary: #666666;\n --lb-border: #e0e0e0;\n }\n\n .librebot-fab {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 56px;\n height: 56px;\n border-radius: 50%;\n background: var(--lb-primary);\n border: none;\n cursor: pointer;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform 0.2s, box-shadow 0.2s;\n z-index: 999998;\n }\n\n .librebot-fab:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);\n }\n\n .librebot-fab svg {\n width: 24px;\n height: 24px;\n fill: white;\n }\n\n .librebot-fab.left {\n right: auto;\n left: 20px;\n }\n\n .librebot-modal {\n position: fixed;\n bottom: 90px;\n right: 20px;\n width: 380px;\n max-width: calc(100vw - 40px);\n height: 520px;\n max-height: calc(100vh - 120px);\n background: var(--lb-bg);\n border: 1px solid var(--lb-border);\n border-radius: 16px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n z-index: 999999;\n opacity: 0;\n transform: translateY(20px) scale(0.95);\n transition: opacity 0.2s, transform 0.2s;\n pointer-events: none;\n }\n\n .librebot-modal.open {\n opacity: 1;\n transform: translateY(0) scale(1);\n pointer-events: auto;\n }\n\n .librebot-modal.left {\n right: auto;\n left: 20px;\n }\n\n .librebot-header {\n padding: 16px;\n border-bottom: 1px solid var(--lb-border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .librebot-header-content {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .librebot-avatar {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n background: var(--lb-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .librebot-avatar svg {\n width: 24px;\n height: 24px;\n fill: white;\n }\n\n .librebot-title {\n font-weight: 600;\n color: var(--lb-text);\n margin: 0;\n font-size: 16px;\n }\n\n .librebot-subtitle {\n font-size: 12px;\n color: var(--lb-text-secondary);\n margin: 0;\n }\n\n .librebot-close {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--lb-text-secondary);\n transition: background 0.2s;\n }\n\n .librebot-close:hover {\n background: var(--lb-bg-secondary);\n }\n\n .librebot-messages {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .librebot-message {\n max-width: 85%;\n padding: 10px 14px;\n border-radius: 16px;\n word-wrap: break-word;\n }\n\n .librebot-message.user {\n align-self: flex-end;\n background: var(--lb-primary);\n color: white;\n border-bottom-right-radius: 4px;\n }\n\n .librebot-message.assistant {\n align-self: flex-start;\n background: var(--lb-bg-secondary);\n color: var(--lb-text);\n border-bottom-left-radius: 4px;\n }\n\n .librebot-message code {\n background: rgba(0, 0, 0, 0.2);\n padding: 2px 6px;\n border-radius: 4px;\n font-family: monospace;\n font-size: 13px;\n }\n\n .librebot-message pre {\n background: rgba(0, 0, 0, 0.3);\n padding: 10px;\n border-radius: 8px;\n overflow-x: auto;\n margin: 8px 0;\n }\n\n .librebot-message pre code {\n background: none;\n padding: 0;\n }\n\n .librebot-typing {\n display: flex;\n gap: 4px;\n padding: 12px 16px;\n align-self: flex-start;\n background: var(--lb-bg-secondary);\n border-radius: 16px;\n border-bottom-left-radius: 4px;\n }\n\n .librebot-typing span {\n width: 8px;\n height: 8px;\n background: var(--lb-text-secondary);\n border-radius: 50%;\n animation: librebot-bounce 1.4s infinite ease-in-out;\n }\n\n .librebot-typing span:nth-child(1) { animation-delay: 0s; }\n .librebot-typing span:nth-child(2) { animation-delay: 0.2s; }\n .librebot-typing span:nth-child(3) { animation-delay: 0.4s; }\n\n @keyframes librebot-bounce {\n 0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; }\n 40% { transform: scale(1); opacity: 1; }\n }\n\n .librebot-input-area {\n padding: 12px 16px;\n border-top: 1px solid var(--lb-border);\n display: flex;\n gap: 8px;\n }\n\n .librebot-input {\n flex: 1;\n padding: 10px 14px;\n border: 1px solid var(--lb-border);\n border-radius: 24px;\n background: var(--lb-bg-secondary);\n color: var(--lb-text);\n font-size: 14px;\n outline: none;\n transition: border-color 0.2s;\n }\n\n .librebot-input:focus {\n border-color: var(--lb-primary);\n }\n\n .librebot-input::placeholder {\n color: var(--lb-text-secondary);\n }\n\n .librebot-send {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n background: var(--lb-primary);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s;\n }\n\n .librebot-send:hover {\n background: var(--lb-primary-hover);\n }\n\n .librebot-send:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .librebot-send svg {\n width: 18px;\n height: 18px;\n fill: white;\n }\n\n .librebot-welcome {\n text-align: center;\n padding: 20px;\n color: var(--lb-text-secondary);\n }\n\n .librebot-welcome-icon {\n width: 48px;\n height: 48px;\n margin: 0 auto 12px;\n background: var(--lb-primary);\n border-radius: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .librebot-welcome-icon svg {\n width: 28px;\n height: 28px;\n fill: white;\n }\n\n .librebot-powered {\n text-align: center;\n padding: 8px;\n font-size: 11px;\n color: var(--lb-text-secondary);\n border-top: 1px solid var(--lb-border);\n }\n\n .librebot-powered a {\n color: var(--lb-primary);\n text-decoration: none;\n }\n\n .librebot-powered a:hover {\n text-decoration: underline;\n }\n`;\n","export const chatIcon = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"></path></svg>`;\n\nexport const closeIcon = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>`;\n\nexport const sendIcon = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"></line><polygon points=\"22 2 15 22 11 13 2 9 22 2\"></polygon></svg>`;\n\nexport const botIcon = `<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>`;\n","import { LibreBotConfig, Message, ChatResponse } from './types';\nimport { getStyles } from './styles';\nimport { chatIcon, closeIcon, sendIcon } from './icons';\n\nexport class LibreBotWidget {\n private config!: Required<LibreBotConfig>;\n private container: HTMLDivElement | null = null;\n private modal: HTMLDivElement | null = null;\n private messagesContainer: HTMLDivElement | null = null;\n private input: HTMLInputElement | null = null;\n private isOpen = false;\n private messages: Message[] = [];\n private isLoading = false;\n\n private defaultConfig: Omit<Required<LibreBotConfig>, 'apiKey'> = {\n apiUrl: 'https://api.librebot.io',\n position: 'bottom-right',\n theme: 'dark',\n primaryColor: '#14b8a6',\n title: 'Libre Bot',\n subtitle: 'AI Documentation Assistant',\n placeholder: 'Ask a question...',\n welcomeMessage: 'Hi! I can help you find answers in the documentation. What would you like to know?',\n streaming: true,\n };\n\n constructor(config: LibreBotConfig) {\n if (!config.apiKey) {\n console.error('LibreBot: API key is required');\n return;\n }\n\n this.config = { ...this.defaultConfig, ...config } as Required<LibreBotConfig>;\n this.init();\n }\n\n private init(): void {\n // Inject styles\n this.injectStyles();\n\n // Create widget container\n this.createWidget();\n\n // Add welcome message\n if (this.config.welcomeMessage) {\n this.addMessage('assistant', this.config.welcomeMessage);\n }\n }\n\n private injectStyles(): void {\n const styleId = 'librebot-styles';\n if (document.getElementById(styleId)) return;\n\n const style = document.createElement('style');\n style.id = styleId;\n style.textContent = getStyles(this.config.primaryColor);\n document.head.appendChild(style);\n }\n\n private createWidget(): void {\n // Create container\n this.container = document.createElement('div');\n this.container.className = `librebot-widget ${this.config.theme === 'light' ? 'light' : ''}`;\n\n // Create FAB button\n const fab = document.createElement('button');\n fab.className = `librebot-fab ${this.config.position === 'bottom-left' ? 'left' : ''}`;\n fab.innerHTML = chatIcon;\n fab.onclick = () => this.toggle();\n\n // Create modal\n this.modal = document.createElement('div');\n this.modal.className = `librebot-modal ${this.config.position === 'bottom-left' ? 'left' : ''}`;\n this.modal.innerHTML = this.getModalHTML();\n\n // Add to container\n this.container.appendChild(fab);\n this.container.appendChild(this.modal);\n document.body.appendChild(this.container);\n\n // Get references\n this.messagesContainer = this.modal.querySelector('.librebot-messages');\n this.input = this.modal.querySelector('.librebot-input');\n\n // Add event listeners\n const closeBtn = this.modal.querySelector('.librebot-close');\n closeBtn?.addEventListener('click', () => this.close());\n\n const sendBtn = this.modal.querySelector('.librebot-send');\n sendBtn?.addEventListener('click', () => this.sendMessage());\n\n this.input?.addEventListener('keypress', (e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n this.sendMessage();\n }\n });\n\n // Auto theme detection\n if (this.config.theme === 'auto') {\n const mediaQuery = window.matchMedia('(prefers-color-scheme: light)');\n this.updateTheme(mediaQuery.matches);\n mediaQuery.addEventListener('change', (e) => this.updateTheme(e.matches));\n }\n }\n\n private getModalHTML(): string {\n return `\n <div class=\"librebot-header\">\n <div class=\"librebot-header-content\">\n <div class=\"librebot-avatar\">${chatIcon}</div>\n <div>\n <h3 class=\"librebot-title\">${this.config.title}</h3>\n <p class=\"librebot-subtitle\">${this.config.subtitle}</p>\n </div>\n </div>\n <button class=\"librebot-close\">${closeIcon}</button>\n </div>\n <div class=\"librebot-messages\"></div>\n <div class=\"librebot-input-area\">\n <input type=\"text\" class=\"librebot-input\" placeholder=\"${this.config.placeholder}\" />\n <button class=\"librebot-send\">${sendIcon}</button>\n </div>\n <div class=\"librebot-powered\">\n Powered by <a href=\"https://librebot.io\" target=\"_blank\">Libre Bot</a>\n </div>\n `;\n }\n\n private updateTheme(isLight: boolean): void {\n if (isLight) {\n this.container?.classList.add('light');\n } else {\n this.container?.classList.remove('light');\n }\n }\n\n public toggle(): void {\n this.isOpen ? this.close() : this.open();\n }\n\n public open(): void {\n this.isOpen = true;\n this.modal?.classList.add('open');\n this.input?.focus();\n }\n\n public close(): void {\n this.isOpen = false;\n this.modal?.classList.remove('open');\n }\n\n private addMessage(role: 'user' | 'assistant', content: string): void {\n const message: Message = {\n id: `msg-${Date.now()}`,\n role,\n content,\n timestamp: new Date(),\n };\n\n this.messages.push(message);\n this.renderMessage(message);\n }\n\n private renderMessage(message: Message): void {\n if (!this.messagesContainer) return;\n\n const el = document.createElement('div');\n el.className = `librebot-message ${message.role}`;\n el.innerHTML = this.formatMessage(message.content);\n\n this.messagesContainer.appendChild(el);\n this.scrollToBottom();\n }\n\n private formatMessage(content: string): string {\n // Basic markdown-like formatting\n return content\n .replace(/`([^`]+)`/g, '<code>$1</code>')\n .replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')\n .replace(/\\n/g, '<br>');\n }\n\n private showTyping(): HTMLDivElement {\n const typing = document.createElement('div');\n typing.className = 'librebot-typing';\n typing.innerHTML = '<span></span><span></span><span></span>';\n this.messagesContainer?.appendChild(typing);\n this.scrollToBottom();\n return typing;\n }\n\n private scrollToBottom(): void {\n if (this.messagesContainer) {\n this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;\n }\n }\n\n private async sendMessage(): Promise<void> {\n if (!this.input || this.isLoading) return;\n\n const content = this.input.value.trim();\n if (!content) return;\n\n // Add user message\n this.addMessage('user', content);\n this.input.value = '';\n\n // Show typing indicator\n this.isLoading = true;\n const typing = this.showTyping();\n\n try {\n if (this.config.streaming) {\n await this.callStreamingAPI(content, typing);\n } else {\n const response = await this.callAPI(content);\n typing.remove();\n\n if (response.error) {\n this.addMessage('assistant', `Sorry, I encountered an error: ${response.error}`);\n } else {\n this.addMessage('assistant', response.response);\n }\n }\n } catch (error) {\n typing.remove();\n this.addMessage('assistant', 'Sorry, I had trouble connecting. Please try again.');\n console.error('LibreBot error:', error);\n } finally {\n this.isLoading = false;\n }\n }\n\n private async callStreamingAPI(message: string, typing: HTMLDivElement): Promise<void> {\n const response = await fetch(`${this.config.apiUrl}/api/chat`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n message,\n context: this.getConversationContext(),\n stream: true,\n }),\n });\n\n if (!response.ok) {\n typing.remove();\n const error = await response.json().catch(() => ({ error: 'Request failed' }));\n this.addMessage('assistant', `Sorry, I encountered an error: ${error.error || 'Request failed'}`);\n return;\n }\n\n // Remove typing indicator and create streaming message element\n typing.remove();\n const messageEl = document.createElement('div');\n messageEl.className = 'librebot-message assistant';\n this.messagesContainer?.appendChild(messageEl);\n\n const reader = response.body?.getReader();\n if (!reader) {\n this.addMessage('assistant', 'Sorry, streaming is not supported.');\n return;\n }\n\n const decoder = new TextDecoder();\n let fullContent = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const chunk = decoder.decode(value, { stream: true });\n const lines = chunk.split('\\n').filter((line) => line.trim() !== '');\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const data = line.slice(6);\n if (data === '[DONE]') continue;\n\n try {\n const parsed = JSON.parse(data);\n if (parsed.content) {\n fullContent += parsed.content;\n messageEl.innerHTML = this.formatMessage(fullContent);\n this.scrollToBottom();\n }\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n } catch (error) {\n console.error('Stream reading error:', error);\n }\n\n // Add to messages array\n this.messages.push({\n id: `msg-${Date.now()}`,\n role: 'assistant',\n content: fullContent,\n timestamp: new Date(),\n });\n }\n\n private async callAPI(message: string): Promise<ChatResponse> {\n const response = await fetch(`${this.config.apiUrl}/api/chat`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n message,\n context: this.getConversationContext(),\n }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ error: 'Request failed' }));\n return { response: '', error: error.error || 'Request failed' };\n }\n\n return response.json();\n }\n\n private getConversationContext(): Array<{ role: string; content: string }> {\n // Get last 10 messages for context\n return this.messages.slice(-10).map((m) => ({\n role: m.role,\n content: m.content,\n }));\n }\n\n public destroy(): void {\n this.container?.remove();\n document.getElementById('librebot-styles')?.remove();\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var LibreBot=function(e){"use strict";const n='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>';class t{constructor(e){this.container=null,this.modal=null,this.messagesContainer=null,this.input=null,this.isOpen=!1,this.messages=[],this.isLoading=!1,this.defaultConfig={apiUrl:"https://api.librebot.io",position:"bottom-right",theme:"dark",primaryColor:"#14b8a6",title:"Libre Bot",subtitle:"AI Documentation Assistant",placeholder:"Ask a question...",welcomeMessage:"Hi! I can help you find answers in the documentation. What would you like to know?",streaming:!0},e.apiKey?(this.config={...this.defaultConfig,...e},this.init()):console.error("LibreBot: API key is required")}init(){this.injectStyles(),this.createWidget(),this.config.welcomeMessage&&this.addMessage("assistant",this.config.welcomeMessage)}injectStyles(){const e="librebot-styles";if(document.getElementById(e))return;const n=document.createElement("style");n.id=e,n.textContent=((e="#14b8a6")=>`\n .librebot-widget {\n --lb-primary: ${e};\n --lb-primary-hover: color-mix(in srgb, ${e} 85%, white);\n --lb-bg: #1a1a1a;\n --lb-bg-secondary: #2a2a2a;\n --lb-text: #ffffff;\n --lb-text-secondary: #a0a0a0;\n --lb-border: #3a3a3a;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n }\n\n .librebot-widget.light {\n --lb-bg: #ffffff;\n --lb-bg-secondary: #f5f5f5;\n --lb-text: #1a1a1a;\n --lb-text-secondary: #666666;\n --lb-border: #e0e0e0;\n }\n\n .librebot-fab {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 56px;\n height: 56px;\n border-radius: 50%;\n background: var(--lb-primary);\n border: none;\n cursor: pointer;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform 0.2s, box-shadow 0.2s;\n z-index: 999998;\n }\n\n .librebot-fab:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);\n }\n\n .librebot-fab svg {\n width: 24px;\n height: 24px;\n fill: white;\n }\n\n .librebot-fab.left {\n right: auto;\n left: 20px;\n }\n\n .librebot-modal {\n position: fixed;\n bottom: 90px;\n right: 20px;\n width: 380px;\n max-width: calc(100vw - 40px);\n height: 520px;\n max-height: calc(100vh - 120px);\n background: var(--lb-bg);\n border: 1px solid var(--lb-border);\n border-radius: 16px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n z-index: 999999;\n opacity: 0;\n transform: translateY(20px) scale(0.95);\n transition: opacity 0.2s, transform 0.2s;\n pointer-events: none;\n }\n\n .librebot-modal.open {\n opacity: 1;\n transform: translateY(0) scale(1);\n pointer-events: auto;\n }\n\n .librebot-modal.left {\n right: auto;\n left: 20px;\n }\n\n .librebot-header {\n padding: 16px;\n border-bottom: 1px solid var(--lb-border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .librebot-header-content {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .librebot-avatar {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n background: var(--lb-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .librebot-avatar svg {\n width: 24px;\n height: 24px;\n fill: white;\n }\n\n .librebot-title {\n font-weight: 600;\n color: var(--lb-text);\n margin: 0;\n font-size: 16px;\n }\n\n .librebot-subtitle {\n font-size: 12px;\n color: var(--lb-text-secondary);\n margin: 0;\n }\n\n .librebot-close {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--lb-text-secondary);\n transition: background 0.2s;\n }\n\n .librebot-close:hover {\n background: var(--lb-bg-secondary);\n }\n\n .librebot-messages {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .librebot-message {\n max-width: 85%;\n padding: 10px 14px;\n border-radius: 16px;\n word-wrap: break-word;\n }\n\n .librebot-message.user {\n align-self: flex-end;\n background: var(--lb-primary);\n color: white;\n border-bottom-right-radius: 4px;\n }\n\n .librebot-message.assistant {\n align-self: flex-start;\n background: var(--lb-bg-secondary);\n color: var(--lb-text);\n border-bottom-left-radius: 4px;\n }\n\n .librebot-message code {\n background: rgba(0, 0, 0, 0.2);\n padding: 2px 6px;\n border-radius: 4px;\n font-family: monospace;\n font-size: 13px;\n }\n\n .librebot-message pre {\n background: rgba(0, 0, 0, 0.3);\n padding: 10px;\n border-radius: 8px;\n overflow-x: auto;\n margin: 8px 0;\n }\n\n .librebot-message pre code {\n background: none;\n padding: 0;\n }\n\n .librebot-typing {\n display: flex;\n gap: 4px;\n padding: 12px 16px;\n align-self: flex-start;\n background: var(--lb-bg-secondary);\n border-radius: 16px;\n border-bottom-left-radius: 4px;\n }\n\n .librebot-typing span {\n width: 8px;\n height: 8px;\n background: var(--lb-text-secondary);\n border-radius: 50%;\n animation: librebot-bounce 1.4s infinite ease-in-out;\n }\n\n .librebot-typing span:nth-child(1) { animation-delay: 0s; }\n .librebot-typing span:nth-child(2) { animation-delay: 0.2s; }\n .librebot-typing span:nth-child(3) { animation-delay: 0.4s; }\n\n @keyframes librebot-bounce {\n 0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; }\n 40% { transform: scale(1); opacity: 1; }\n }\n\n .librebot-input-area {\n padding: 12px 16px;\n border-top: 1px solid var(--lb-border);\n display: flex;\n gap: 8px;\n }\n\n .librebot-input {\n flex: 1;\n padding: 10px 14px;\n border: 1px solid var(--lb-border);\n border-radius: 24px;\n background: var(--lb-bg-secondary);\n color: var(--lb-text);\n font-size: 14px;\n outline: none;\n transition: border-color 0.2s;\n }\n\n .librebot-input:focus {\n border-color: var(--lb-primary);\n }\n\n .librebot-input::placeholder {\n color: var(--lb-text-secondary);\n }\n\n .librebot-send {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n background: var(--lb-primary);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s;\n }\n\n .librebot-send:hover {\n background: var(--lb-primary-hover);\n }\n\n .librebot-send:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .librebot-send svg {\n width: 18px;\n height: 18px;\n fill: white;\n }\n\n .librebot-welcome {\n text-align: center;\n padding: 20px;\n color: var(--lb-text-secondary);\n }\n\n .librebot-welcome-icon {\n width: 48px;\n height: 48px;\n margin: 0 auto 12px;\n background: var(--lb-primary);\n border-radius: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .librebot-welcome-icon svg {\n width: 28px;\n height: 28px;\n fill: white;\n }\n\n .librebot-powered {\n text-align: center;\n padding: 8px;\n font-size: 11px;\n color: var(--lb-text-secondary);\n border-top: 1px solid var(--lb-border);\n }\n\n .librebot-powered a {\n color: var(--lb-primary);\n text-decoration: none;\n }\n\n .librebot-powered a:hover {\n text-decoration: underline;\n }\n`)(this.config.primaryColor),document.head.appendChild(n)}createWidget(){this.container=document.createElement("div"),this.container.className="librebot-widget "+("light"===this.config.theme?"light":"");const e=document.createElement("button");e.className="librebot-fab "+("bottom-left"===this.config.position?"left":""),e.innerHTML=n,e.onclick=()=>this.toggle(),this.modal=document.createElement("div"),this.modal.className="librebot-modal "+("bottom-left"===this.config.position?"left":""),this.modal.innerHTML=this.getModalHTML(),this.container.appendChild(e),this.container.appendChild(this.modal),document.body.appendChild(this.container),this.messagesContainer=this.modal.querySelector(".librebot-messages"),this.input=this.modal.querySelector(".librebot-input");const t=this.modal.querySelector(".librebot-close");t?.addEventListener("click",()=>this.close());const i=this.modal.querySelector(".librebot-send");if(i?.addEventListener("click",()=>this.sendMessage()),this.input?.addEventListener("keypress",e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),this.sendMessage())}),"auto"===this.config.theme){const e=window.matchMedia("(prefers-color-scheme: light)");this.updateTheme(e.matches),e.addEventListener("change",e=>this.updateTheme(e.matches))}}getModalHTML(){return`\n <div class="librebot-header">\n <div class="librebot-header-content">\n <div class="librebot-avatar">${n}</div>\n <div>\n <h3 class="librebot-title">${this.config.title}</h3>\n <p class="librebot-subtitle">${this.config.subtitle}</p>\n </div>\n </div>\n <button class="librebot-close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button>\n </div>\n <div class="librebot-messages"></div>\n <div class="librebot-input-area">\n <input type="text" class="librebot-input" placeholder="${this.config.placeholder}" />\n <button class="librebot-send"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg></button>\n </div>\n <div class="librebot-powered">\n Powered by <a href="https://librebot.io" target="_blank">Libre Bot</a>\n </div>\n `}updateTheme(e){e?this.container?.classList.add("light"):this.container?.classList.remove("light")}toggle(){this.isOpen?this.close():this.open()}open(){this.isOpen=!0,this.modal?.classList.add("open"),this.input?.focus()}close(){this.isOpen=!1,this.modal?.classList.remove("open")}addMessage(e,n){const t={id:`msg-${Date.now()}`,role:e,content:n,timestamp:new Date};this.messages.push(t),this.renderMessage(t)}renderMessage(e){if(!this.messagesContainer)return;const n=document.createElement("div");n.className=`librebot-message ${e.role}`,n.innerHTML=this.formatMessage(e.content),this.messagesContainer.appendChild(n),this.scrollToBottom()}formatMessage(e){return e.replace(/`([^`]+)`/g,"<code>$1</code>").replace(/\*\*([^*]+)\*\*/g,"<strong>$1</strong>").replace(/\n/g,"<br>")}showTyping(){const e=document.createElement("div");return e.className="librebot-typing",e.innerHTML="<span></span><span></span><span></span>",this.messagesContainer?.appendChild(e),this.scrollToBottom(),e}scrollToBottom(){this.messagesContainer&&(this.messagesContainer.scrollTop=this.messagesContainer.scrollHeight)}async sendMessage(){if(!this.input||this.isLoading)return;const e=this.input.value.trim();if(!e)return;this.addMessage("user",e),this.input.value="",this.isLoading=!0;const n=this.showTyping();try{if(this.config.streaming)await this.callStreamingAPI(e,n);else{const t=await this.callAPI(e);n.remove(),t.error?this.addMessage("assistant",`Sorry, I encountered an error: ${t.error}`):this.addMessage("assistant",t.response)}}catch(e){n.remove(),this.addMessage("assistant","Sorry, I had trouble connecting. Please try again."),console.error("LibreBot error:",e)}finally{this.isLoading=!1}}async callStreamingAPI(e,n){const t=await fetch(`${this.config.apiUrl}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({message:e,context:this.getConversationContext(),stream:!0})});if(!t.ok){n.remove();const e=await t.json().catch(()=>({error:"Request failed"}));return void this.addMessage("assistant",`Sorry, I encountered an error: ${e.error||"Request failed"}`)}n.remove();const i=document.createElement("div");i.className="librebot-message assistant",this.messagesContainer?.appendChild(i);const o=t.body?.getReader();if(!o)return void this.addMessage("assistant","Sorry, streaming is not supported.");const r=new TextDecoder;let s="";try{for(;;){const{done:e,value:n}=await o.read();if(e)break;const t=r.decode(n,{stream:!0}).split("\n").filter(e=>""!==e.trim());for(const e of t)if(e.startsWith("data: ")){const n=e.slice(6);if("[DONE]"===n)continue;try{const e=JSON.parse(n);e.content&&(s+=e.content,i.innerHTML=this.formatMessage(s),this.scrollToBottom())}catch{}}}}catch(e){console.error("Stream reading error:",e)}this.messages.push({id:`msg-${Date.now()}`,role:"assistant",content:s,timestamp:new Date})}async callAPI(e){const n=await fetch(`${this.config.apiUrl}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({message:e,context:this.getConversationContext()})});if(!n.ok){return{response:"",error:(await n.json().catch(()=>({error:"Request failed"}))).error||"Request failed"}}return n.json()}getConversationContext(){return this.messages.slice(-10).map(e=>({role:e.role,content:e.content}))}destroy(){this.container?.remove(),document.getElementById("librebot-styles")?.remove()}}return function(){const e=document.currentScript;if(!e)return void console.warn("LibreBot: Could not find script tag");const n={apiKey:e.dataset.key||e.dataset.apiKey||"",apiUrl:e.dataset.apiUrl||"https://api.librebot.io",position:e.dataset.position||"bottom-right",theme:e.dataset.theme||"dark",primaryColor:e.dataset.color||"#14b8a6",title:e.dataset.title||"Libre Bot",subtitle:e.dataset.subtitle||"AI Documentation Assistant",placeholder:e.dataset.placeholder||"Ask a question...",welcomeMessage:e.dataset.welcome||"Hi! I can help you find answers in the documentation. What would you like to know?",streaming:"false"!==e.dataset.streaming};n.apiKey?"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{window.LibreBot=new t(n)}):window.LibreBot=new t(n):console.error("LibreBot: Missing data-key attribute")}(),e.LibreBotWidget=t,e}({});
2
+ //# sourceMappingURL=librebot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"librebot.js","sources":["../src/styles.ts","../src/icons.ts","../src/widget.ts","../src/embed.ts"],"sourcesContent":["export const getStyles = (primaryColor: string = '#14b8a6') => `\n .librebot-widget {\n --lb-primary: ${primaryColor};\n --lb-primary-hover: color-mix(in srgb, ${primaryColor} 85%, white);\n --lb-bg: #1a1a1a;\n --lb-bg-secondary: #2a2a2a;\n --lb-text: #ffffff;\n --lb-text-secondary: #a0a0a0;\n --lb-border: #3a3a3a;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n }\n\n .librebot-widget.light {\n --lb-bg: #ffffff;\n --lb-bg-secondary: #f5f5f5;\n --lb-text: #1a1a1a;\n --lb-text-secondary: #666666;\n --lb-border: #e0e0e0;\n }\n\n .librebot-fab {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 56px;\n height: 56px;\n border-radius: 50%;\n background: var(--lb-primary);\n border: none;\n cursor: pointer;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform 0.2s, box-shadow 0.2s;\n z-index: 999998;\n }\n\n .librebot-fab:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);\n }\n\n .librebot-fab svg {\n width: 24px;\n height: 24px;\n fill: white;\n }\n\n .librebot-fab.left {\n right: auto;\n left: 20px;\n }\n\n .librebot-modal {\n position: fixed;\n bottom: 90px;\n right: 20px;\n width: 380px;\n max-width: calc(100vw - 40px);\n height: 520px;\n max-height: calc(100vh - 120px);\n background: var(--lb-bg);\n border: 1px solid var(--lb-border);\n border-radius: 16px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n z-index: 999999;\n opacity: 0;\n transform: translateY(20px) scale(0.95);\n transition: opacity 0.2s, transform 0.2s;\n pointer-events: none;\n }\n\n .librebot-modal.open {\n opacity: 1;\n transform: translateY(0) scale(1);\n pointer-events: auto;\n }\n\n .librebot-modal.left {\n right: auto;\n left: 20px;\n }\n\n .librebot-header {\n padding: 16px;\n border-bottom: 1px solid var(--lb-border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .librebot-header-content {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .librebot-avatar {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n background: var(--lb-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .librebot-avatar svg {\n width: 24px;\n height: 24px;\n fill: white;\n }\n\n .librebot-title {\n font-weight: 600;\n color: var(--lb-text);\n margin: 0;\n font-size: 16px;\n }\n\n .librebot-subtitle {\n font-size: 12px;\n color: var(--lb-text-secondary);\n margin: 0;\n }\n\n .librebot-close {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--lb-text-secondary);\n transition: background 0.2s;\n }\n\n .librebot-close:hover {\n background: var(--lb-bg-secondary);\n }\n\n .librebot-messages {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .librebot-message {\n max-width: 85%;\n padding: 10px 14px;\n border-radius: 16px;\n word-wrap: break-word;\n }\n\n .librebot-message.user {\n align-self: flex-end;\n background: var(--lb-primary);\n color: white;\n border-bottom-right-radius: 4px;\n }\n\n .librebot-message.assistant {\n align-self: flex-start;\n background: var(--lb-bg-secondary);\n color: var(--lb-text);\n border-bottom-left-radius: 4px;\n }\n\n .librebot-message code {\n background: rgba(0, 0, 0, 0.2);\n padding: 2px 6px;\n border-radius: 4px;\n font-family: monospace;\n font-size: 13px;\n }\n\n .librebot-message pre {\n background: rgba(0, 0, 0, 0.3);\n padding: 10px;\n border-radius: 8px;\n overflow-x: auto;\n margin: 8px 0;\n }\n\n .librebot-message pre code {\n background: none;\n padding: 0;\n }\n\n .librebot-typing {\n display: flex;\n gap: 4px;\n padding: 12px 16px;\n align-self: flex-start;\n background: var(--lb-bg-secondary);\n border-radius: 16px;\n border-bottom-left-radius: 4px;\n }\n\n .librebot-typing span {\n width: 8px;\n height: 8px;\n background: var(--lb-text-secondary);\n border-radius: 50%;\n animation: librebot-bounce 1.4s infinite ease-in-out;\n }\n\n .librebot-typing span:nth-child(1) { animation-delay: 0s; }\n .librebot-typing span:nth-child(2) { animation-delay: 0.2s; }\n .librebot-typing span:nth-child(3) { animation-delay: 0.4s; }\n\n @keyframes librebot-bounce {\n 0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; }\n 40% { transform: scale(1); opacity: 1; }\n }\n\n .librebot-input-area {\n padding: 12px 16px;\n border-top: 1px solid var(--lb-border);\n display: flex;\n gap: 8px;\n }\n\n .librebot-input {\n flex: 1;\n padding: 10px 14px;\n border: 1px solid var(--lb-border);\n border-radius: 24px;\n background: var(--lb-bg-secondary);\n color: var(--lb-text);\n font-size: 14px;\n outline: none;\n transition: border-color 0.2s;\n }\n\n .librebot-input:focus {\n border-color: var(--lb-primary);\n }\n\n .librebot-input::placeholder {\n color: var(--lb-text-secondary);\n }\n\n .librebot-send {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n background: var(--lb-primary);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s;\n }\n\n .librebot-send:hover {\n background: var(--lb-primary-hover);\n }\n\n .librebot-send:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .librebot-send svg {\n width: 18px;\n height: 18px;\n fill: white;\n }\n\n .librebot-welcome {\n text-align: center;\n padding: 20px;\n color: var(--lb-text-secondary);\n }\n\n .librebot-welcome-icon {\n width: 48px;\n height: 48px;\n margin: 0 auto 12px;\n background: var(--lb-primary);\n border-radius: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .librebot-welcome-icon svg {\n width: 28px;\n height: 28px;\n fill: white;\n }\n\n .librebot-powered {\n text-align: center;\n padding: 8px;\n font-size: 11px;\n color: var(--lb-text-secondary);\n border-top: 1px solid var(--lb-border);\n }\n\n .librebot-powered a {\n color: var(--lb-primary);\n text-decoration: none;\n }\n\n .librebot-powered a:hover {\n text-decoration: underline;\n }\n`;\n","export const chatIcon = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"></path></svg>`;\n\nexport const closeIcon = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>`;\n\nexport const sendIcon = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"></line><polygon points=\"22 2 15 22 11 13 2 9 22 2\"></polygon></svg>`;\n\nexport const botIcon = `<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>`;\n","import { LibreBotConfig, Message, ChatResponse } from './types';\nimport { getStyles } from './styles';\nimport { chatIcon, closeIcon, sendIcon } from './icons';\n\nexport class LibreBotWidget {\n private config!: Required<LibreBotConfig>;\n private container: HTMLDivElement | null = null;\n private modal: HTMLDivElement | null = null;\n private messagesContainer: HTMLDivElement | null = null;\n private input: HTMLInputElement | null = null;\n private isOpen = false;\n private messages: Message[] = [];\n private isLoading = false;\n\n private defaultConfig: Omit<Required<LibreBotConfig>, 'apiKey'> = {\n apiUrl: 'https://api.librebot.io',\n position: 'bottom-right',\n theme: 'dark',\n primaryColor: '#14b8a6',\n title: 'Libre Bot',\n subtitle: 'AI Documentation Assistant',\n placeholder: 'Ask a question...',\n welcomeMessage: 'Hi! I can help you find answers in the documentation. What would you like to know?',\n streaming: true,\n };\n\n constructor(config: LibreBotConfig) {\n if (!config.apiKey) {\n console.error('LibreBot: API key is required');\n return;\n }\n\n this.config = { ...this.defaultConfig, ...config } as Required<LibreBotConfig>;\n this.init();\n }\n\n private init(): void {\n // Inject styles\n this.injectStyles();\n\n // Create widget container\n this.createWidget();\n\n // Add welcome message\n if (this.config.welcomeMessage) {\n this.addMessage('assistant', this.config.welcomeMessage);\n }\n }\n\n private injectStyles(): void {\n const styleId = 'librebot-styles';\n if (document.getElementById(styleId)) return;\n\n const style = document.createElement('style');\n style.id = styleId;\n style.textContent = getStyles(this.config.primaryColor);\n document.head.appendChild(style);\n }\n\n private createWidget(): void {\n // Create container\n this.container = document.createElement('div');\n this.container.className = `librebot-widget ${this.config.theme === 'light' ? 'light' : ''}`;\n\n // Create FAB button\n const fab = document.createElement('button');\n fab.className = `librebot-fab ${this.config.position === 'bottom-left' ? 'left' : ''}`;\n fab.innerHTML = chatIcon;\n fab.onclick = () => this.toggle();\n\n // Create modal\n this.modal = document.createElement('div');\n this.modal.className = `librebot-modal ${this.config.position === 'bottom-left' ? 'left' : ''}`;\n this.modal.innerHTML = this.getModalHTML();\n\n // Add to container\n this.container.appendChild(fab);\n this.container.appendChild(this.modal);\n document.body.appendChild(this.container);\n\n // Get references\n this.messagesContainer = this.modal.querySelector('.librebot-messages');\n this.input = this.modal.querySelector('.librebot-input');\n\n // Add event listeners\n const closeBtn = this.modal.querySelector('.librebot-close');\n closeBtn?.addEventListener('click', () => this.close());\n\n const sendBtn = this.modal.querySelector('.librebot-send');\n sendBtn?.addEventListener('click', () => this.sendMessage());\n\n this.input?.addEventListener('keypress', (e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n this.sendMessage();\n }\n });\n\n // Auto theme detection\n if (this.config.theme === 'auto') {\n const mediaQuery = window.matchMedia('(prefers-color-scheme: light)');\n this.updateTheme(mediaQuery.matches);\n mediaQuery.addEventListener('change', (e) => this.updateTheme(e.matches));\n }\n }\n\n private getModalHTML(): string {\n return `\n <div class=\"librebot-header\">\n <div class=\"librebot-header-content\">\n <div class=\"librebot-avatar\">${chatIcon}</div>\n <div>\n <h3 class=\"librebot-title\">${this.config.title}</h3>\n <p class=\"librebot-subtitle\">${this.config.subtitle}</p>\n </div>\n </div>\n <button class=\"librebot-close\">${closeIcon}</button>\n </div>\n <div class=\"librebot-messages\"></div>\n <div class=\"librebot-input-area\">\n <input type=\"text\" class=\"librebot-input\" placeholder=\"${this.config.placeholder}\" />\n <button class=\"librebot-send\">${sendIcon}</button>\n </div>\n <div class=\"librebot-powered\">\n Powered by <a href=\"https://librebot.io\" target=\"_blank\">Libre Bot</a>\n </div>\n `;\n }\n\n private updateTheme(isLight: boolean): void {\n if (isLight) {\n this.container?.classList.add('light');\n } else {\n this.container?.classList.remove('light');\n }\n }\n\n public toggle(): void {\n this.isOpen ? this.close() : this.open();\n }\n\n public open(): void {\n this.isOpen = true;\n this.modal?.classList.add('open');\n this.input?.focus();\n }\n\n public close(): void {\n this.isOpen = false;\n this.modal?.classList.remove('open');\n }\n\n private addMessage(role: 'user' | 'assistant', content: string): void {\n const message: Message = {\n id: `msg-${Date.now()}`,\n role,\n content,\n timestamp: new Date(),\n };\n\n this.messages.push(message);\n this.renderMessage(message);\n }\n\n private renderMessage(message: Message): void {\n if (!this.messagesContainer) return;\n\n const el = document.createElement('div');\n el.className = `librebot-message ${message.role}`;\n el.innerHTML = this.formatMessage(message.content);\n\n this.messagesContainer.appendChild(el);\n this.scrollToBottom();\n }\n\n private formatMessage(content: string): string {\n // Basic markdown-like formatting\n return content\n .replace(/`([^`]+)`/g, '<code>$1</code>')\n .replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')\n .replace(/\\n/g, '<br>');\n }\n\n private showTyping(): HTMLDivElement {\n const typing = document.createElement('div');\n typing.className = 'librebot-typing';\n typing.innerHTML = '<span></span><span></span><span></span>';\n this.messagesContainer?.appendChild(typing);\n this.scrollToBottom();\n return typing;\n }\n\n private scrollToBottom(): void {\n if (this.messagesContainer) {\n this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;\n }\n }\n\n private async sendMessage(): Promise<void> {\n if (!this.input || this.isLoading) return;\n\n const content = this.input.value.trim();\n if (!content) return;\n\n // Add user message\n this.addMessage('user', content);\n this.input.value = '';\n\n // Show typing indicator\n this.isLoading = true;\n const typing = this.showTyping();\n\n try {\n if (this.config.streaming) {\n await this.callStreamingAPI(content, typing);\n } else {\n const response = await this.callAPI(content);\n typing.remove();\n\n if (response.error) {\n this.addMessage('assistant', `Sorry, I encountered an error: ${response.error}`);\n } else {\n this.addMessage('assistant', response.response);\n }\n }\n } catch (error) {\n typing.remove();\n this.addMessage('assistant', 'Sorry, I had trouble connecting. Please try again.');\n console.error('LibreBot error:', error);\n } finally {\n this.isLoading = false;\n }\n }\n\n private async callStreamingAPI(message: string, typing: HTMLDivElement): Promise<void> {\n const response = await fetch(`${this.config.apiUrl}/api/chat`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n message,\n context: this.getConversationContext(),\n stream: true,\n }),\n });\n\n if (!response.ok) {\n typing.remove();\n const error = await response.json().catch(() => ({ error: 'Request failed' }));\n this.addMessage('assistant', `Sorry, I encountered an error: ${error.error || 'Request failed'}`);\n return;\n }\n\n // Remove typing indicator and create streaming message element\n typing.remove();\n const messageEl = document.createElement('div');\n messageEl.className = 'librebot-message assistant';\n this.messagesContainer?.appendChild(messageEl);\n\n const reader = response.body?.getReader();\n if (!reader) {\n this.addMessage('assistant', 'Sorry, streaming is not supported.');\n return;\n }\n\n const decoder = new TextDecoder();\n let fullContent = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const chunk = decoder.decode(value, { stream: true });\n const lines = chunk.split('\\n').filter((line) => line.trim() !== '');\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n const data = line.slice(6);\n if (data === '[DONE]') continue;\n\n try {\n const parsed = JSON.parse(data);\n if (parsed.content) {\n fullContent += parsed.content;\n messageEl.innerHTML = this.formatMessage(fullContent);\n this.scrollToBottom();\n }\n } catch {\n // Skip malformed JSON\n }\n }\n }\n }\n } catch (error) {\n console.error('Stream reading error:', error);\n }\n\n // Add to messages array\n this.messages.push({\n id: `msg-${Date.now()}`,\n role: 'assistant',\n content: fullContent,\n timestamp: new Date(),\n });\n }\n\n private async callAPI(message: string): Promise<ChatResponse> {\n const response = await fetch(`${this.config.apiUrl}/api/chat`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n message,\n context: this.getConversationContext(),\n }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ error: 'Request failed' }));\n return { response: '', error: error.error || 'Request failed' };\n }\n\n return response.json();\n }\n\n private getConversationContext(): Array<{ role: string; content: string }> {\n // Get last 10 messages for context\n return this.messages.slice(-10).map((m) => ({\n role: m.role,\n content: m.content,\n }));\n }\n\n public destroy(): void {\n this.container?.remove();\n document.getElementById('librebot-styles')?.remove();\n }\n}\n","import { LibreBotWidget } from './widget';\nimport { LibreBotConfig } from './types';\n\n// Auto-initialize from script tag\n(function () {\n // Find the script tag\n const script = document.currentScript as HTMLScriptElement;\n\n if (!script) {\n console.warn('LibreBot: Could not find script tag');\n return;\n }\n\n // Get config from data attributes\n const config: LibreBotConfig = {\n apiKey: script.dataset.key || script.dataset.apiKey || '',\n apiUrl: script.dataset.apiUrl || 'https://api.librebot.io',\n position: (script.dataset.position as 'bottom-right' | 'bottom-left') || 'bottom-right',\n theme: (script.dataset.theme as 'light' | 'dark' | 'auto') || 'dark',\n primaryColor: script.dataset.color || '#14b8a6',\n title: script.dataset.title || 'Libre Bot',\n subtitle: script.dataset.subtitle || 'AI Documentation Assistant',\n placeholder: script.dataset.placeholder || 'Ask a question...',\n welcomeMessage: script.dataset.welcome || 'Hi! I can help you find answers in the documentation. What would you like to know?',\n streaming: script.dataset.streaming !== 'false', // default true\n };\n\n if (!config.apiKey) {\n console.error('LibreBot: Missing data-key attribute');\n return;\n }\n\n // Initialize when DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => {\n (window as any).LibreBot = new LibreBotWidget(config);\n });\n } else {\n (window as any).LibreBot = new LibreBotWidget(config);\n }\n})();\n\n// Export for manual initialization\nexport { LibreBotWidget, LibreBotConfig };\n"],"names":["chatIcon","LibreBotWidget","constructor","config","this","container","modal","messagesContainer","input","isOpen","messages","isLoading","defaultConfig","apiUrl","position","theme","primaryColor","title","subtitle","placeholder","welcomeMessage","streaming","apiKey","init","console","error","injectStyles","createWidget","addMessage","styleId","document","getElementById","style","createElement","id","textContent","getStyles","head","appendChild","className","fab","innerHTML","onclick","toggle","getModalHTML","body","querySelector","closeBtn","addEventListener","close","sendBtn","sendMessage","e","key","shiftKey","preventDefault","mediaQuery","window","matchMedia","updateTheme","matches","isLight","classList","add","remove","open","focus","role","content","message","Date","now","timestamp","push","renderMessage","el","formatMessage","scrollToBottom","replace","showTyping","typing","scrollTop","scrollHeight","value","trim","callStreamingAPI","response","callAPI","fetch","method","headers","Authorization","JSON","stringify","context","getConversationContext","stream","ok","json","catch","messageEl","reader","getReader","decoder","TextDecoder","fullContent","done","read","lines","decode","split","filter","line","startsWith","data","slice","parsed","parse","map","m","destroy","script","currentScript","warn","dataset","color","welcome","readyState","LibreBot"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const getStyles: (primaryColor?: string) => string;
@@ -0,0 +1,31 @@
1
+ export interface LibreBotConfig {
2
+ apiKey: string;
3
+ apiUrl?: string;
4
+ position?: 'bottom-right' | 'bottom-left';
5
+ theme?: 'light' | 'dark' | 'auto';
6
+ primaryColor?: string;
7
+ title?: string;
8
+ subtitle?: string;
9
+ placeholder?: string;
10
+ welcomeMessage?: string;
11
+ streaming?: boolean;
12
+ }
13
+ export interface Message {
14
+ id: string;
15
+ role: 'user' | 'assistant';
16
+ content: string;
17
+ timestamp: Date;
18
+ }
19
+ export interface ChatResponse {
20
+ response: string;
21
+ usage?: {
22
+ prompt_tokens: number;
23
+ completion_tokens: number;
24
+ total_tokens: number;
25
+ };
26
+ rateLimit?: {
27
+ remaining: number;
28
+ limit: number;
29
+ };
30
+ error?: string;
31
+ }
@@ -0,0 +1,31 @@
1
+ import { LibreBotConfig } from './types';
2
+ export declare class LibreBotWidget {
3
+ private config;
4
+ private container;
5
+ private modal;
6
+ private messagesContainer;
7
+ private input;
8
+ private isOpen;
9
+ private messages;
10
+ private isLoading;
11
+ private defaultConfig;
12
+ constructor(config: LibreBotConfig);
13
+ private init;
14
+ private injectStyles;
15
+ private createWidget;
16
+ private getModalHTML;
17
+ private updateTheme;
18
+ toggle(): void;
19
+ open(): void;
20
+ close(): void;
21
+ private addMessage;
22
+ private renderMessage;
23
+ private formatMessage;
24
+ private showTyping;
25
+ private scrollToBottom;
26
+ private sendMessage;
27
+ private callStreamingAPI;
28
+ private callAPI;
29
+ private getConversationContext;
30
+ destroy(): void;
31
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@kroonen-ai/librebot-widget",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Libre Bot - AI documentation assistant widget",
6
+ "main": "dist/librebot.js",
7
+ "module": "dist/librebot.esm.js",
8
+ "types": "dist/librebot.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "rollup -c",
14
+ "watch": "rollup -c -w",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "keywords": [
18
+ "ai",
19
+ "chatbot",
20
+ "documentation",
21
+ "assistant",
22
+ "widget"
23
+ ],
24
+ "author": "Kroonen AI, Inc.",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/rob-x-ai/librebot-widget"
29
+ },
30
+ "homepage": "https://librebot.io",
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "devDependencies": {
35
+ "@rollup/plugin-terser": "^0.4.4",
36
+ "@rollup/plugin-typescript": "^11.1.6",
37
+ "rollup": "^4.9.0",
38
+ "tslib": "^2.8.1",
39
+ "typescript": "^5.3.0"
40
+ }
41
+ }