@widgetic/chat 0.1.4

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.
Files changed (72) hide show
  1. package/README.md +440 -0
  2. package/dist/adapters/index.d.ts +2 -0
  3. package/dist/adapters/index.js +2 -0
  4. package/dist/adapters/widgeticAdapter.d.ts +185 -0
  5. package/dist/adapters/widgeticAdapter.js +766 -0
  6. package/dist/components/ActionBar.svelte +342 -0
  7. package/dist/components/ActionBar.svelte.d.ts +37 -0
  8. package/dist/components/AttachmentDisplay.svelte +547 -0
  9. package/dist/components/AttachmentDisplay.svelte.d.ts +12 -0
  10. package/dist/components/Chat.svelte +1253 -0
  11. package/dist/components/Chat.svelte.d.ts +112 -0
  12. package/dist/components/ChatHeader.svelte +182 -0
  13. package/dist/components/ChatHeader.svelte.d.ts +12 -0
  14. package/dist/components/ChatInput.svelte +290 -0
  15. package/dist/components/ChatInput.svelte.d.ts +15 -0
  16. package/dist/components/ChatMessages.svelte +996 -0
  17. package/dist/components/ChatMessages.svelte.d.ts +28 -0
  18. package/dist/components/CodeBlock.svelte +286 -0
  19. package/dist/components/CodeBlock.svelte.d.ts +10 -0
  20. package/dist/components/ContextPreview.svelte +66 -0
  21. package/dist/components/ContextPreview.svelte.d.ts +8 -0
  22. package/dist/components/LoadingIndicator.svelte +151 -0
  23. package/dist/components/LoadingIndicator.svelte.d.ts +9 -0
  24. package/dist/components/StatusIndicator.svelte +116 -0
  25. package/dist/components/StatusIndicator.svelte.d.ts +9 -0
  26. package/dist/components/SuggestionButtons.svelte +244 -0
  27. package/dist/components/SuggestionButtons.svelte.d.ts +26 -0
  28. package/dist/components/index.d.ts +12 -0
  29. package/dist/components/index.js +12 -0
  30. package/dist/config.d.ts +43 -0
  31. package/dist/config.js +67 -0
  32. package/dist/constants/colors.d.ts +20 -0
  33. package/dist/constants/colors.js +16 -0
  34. package/dist/index.d.ts +11 -0
  35. package/dist/index.js +20 -0
  36. package/dist/services/chatService.d.ts +72 -0
  37. package/dist/services/chatService.js +355 -0
  38. package/dist/services/index.d.ts +2 -0
  39. package/dist/services/index.js +2 -0
  40. package/dist/stores/chatStore.d.ts +46 -0
  41. package/dist/stores/chatStore.js +219 -0
  42. package/dist/stores/index.d.ts +2 -0
  43. package/dist/stores/index.js +2 -0
  44. package/dist/types/adapter.d.ts +86 -0
  45. package/dist/types/adapter.js +1 -0
  46. package/dist/types/api-temp.d.ts +61 -0
  47. package/dist/types/api-temp.js +42 -0
  48. package/dist/types/attachment.d.ts +84 -0
  49. package/dist/types/attachment.js +33 -0
  50. package/dist/types/chat.d.ts +114 -0
  51. package/dist/types/chat.js +1 -0
  52. package/dist/types/config.d.ts +89 -0
  53. package/dist/types/config.js +63 -0
  54. package/dist/types/context.d.ts +108 -0
  55. package/dist/types/context.js +11 -0
  56. package/dist/types/conversation.d.ts +45 -0
  57. package/dist/types/conversation.js +1 -0
  58. package/dist/types/events.d.ts +141 -0
  59. package/dist/types/events.js +1 -0
  60. package/dist/types/index.d.ts +16 -0
  61. package/dist/types/index.js +10 -0
  62. package/dist/types/message.d.ts +82 -0
  63. package/dist/types/message.js +1 -0
  64. package/dist/types/state.d.ts +117 -0
  65. package/dist/types/state.js +1 -0
  66. package/dist/utils/fileUtils.d.ts +93 -0
  67. package/dist/utils/fileUtils.js +299 -0
  68. package/dist/utils/index.d.ts +3 -0
  69. package/dist/utils/index.js +4 -0
  70. package/dist/utils/logger.d.ts +4 -0
  71. package/dist/utils/logger.js +28 -0
  72. package/package.json +104 -0
@@ -0,0 +1,547 @@
1
+ <script lang="ts">
2
+ import type { ContentItemOutput } from '../types/message.js';
3
+
4
+ // Props
5
+ interface Props {
6
+ attachments: ContentItemOutput[];
7
+ variant?: 'message' | 'preview' | 'compact';
8
+ showPreview?: boolean;
9
+ maxDisplayCount?: number;
10
+ onRemove?: (attachment: ContentItemOutput) => void;
11
+ onView?: (attachment: ContentItemOutput) => void;
12
+ }
13
+
14
+ let {
15
+ attachments,
16
+ variant = 'message',
17
+ showPreview = true,
18
+ maxDisplayCount = 10,
19
+ onRemove,
20
+ onView
21
+ }: Props = $props();
22
+
23
+ // Helper functions
24
+ function getFileIcon(fileType: string): string {
25
+ if (fileType.startsWith('image/')) return '🖼️';
26
+ if (fileType.startsWith('video/')) return '🎥';
27
+ if (fileType.startsWith('audio/')) return '🎵';
28
+ if (fileType.includes('pdf')) return '📄';
29
+ if (fileType.includes('text') || fileType.includes('markdown')) return '📝';
30
+ if (fileType.includes('mermaid')) return '📊';
31
+ return '📎';
32
+ }
33
+
34
+ function formatFileSize(bytes: number | null | undefined): string {
35
+ if (!bytes || bytes === 0) return '';
36
+ const k = 1024;
37
+ const sizes = ['B', 'KB', 'MB', 'GB'];
38
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
39
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
40
+ }
41
+
42
+ function isImageType(fileType: string): boolean {
43
+ return fileType.startsWith('image/');
44
+ }
45
+
46
+ function isVideoType(fileType: string): boolean {
47
+ return fileType.startsWith('video/');
48
+ }
49
+
50
+ let lightboxAttachment: ContentItemOutput | null = $state(null);
51
+
52
+ function isViewableInLightbox(fileType: string): boolean {
53
+ return fileType.startsWith('image/') || fileType.includes('pdf');
54
+ }
55
+
56
+ function handleView(attachment: ContentItemOutput) {
57
+ if (onView) {
58
+ onView(attachment);
59
+ } else if (isViewableInLightbox(attachment.file_type)) {
60
+ lightboxAttachment = attachment;
61
+ } else if (attachment.url) {
62
+ window.open(attachment.url, '_blank', 'noopener,noreferrer');
63
+ }
64
+ }
65
+
66
+ function closeLightbox() {
67
+ lightboxAttachment = null;
68
+ }
69
+
70
+ function handleRemove(attachment: ContentItemOutput) {
71
+ if (onRemove) {
72
+ onRemove(attachment);
73
+ }
74
+ }
75
+
76
+ // Limit displayed attachments if needed
77
+ let displayedAttachments = $derived(
78
+ maxDisplayCount && attachments.length > maxDisplayCount
79
+ ? attachments.slice(0, maxDisplayCount)
80
+ : attachments
81
+ );
82
+
83
+ let hiddenCount = $derived(
84
+ maxDisplayCount && attachments.length > maxDisplayCount
85
+ ? attachments.length - maxDisplayCount
86
+ : 0
87
+ );
88
+ </script>
89
+
90
+ {#if attachments.length > 0}
91
+ <div class="attachment-display {variant}">
92
+ <!-- Message variant - inline in chat bubbles -->
93
+ {#if variant === 'message'}
94
+ <div class="message-attachments">
95
+ {#each displayedAttachments as attachment (attachment.id)}
96
+ <div class="message-attachment-item">
97
+ {#if isImageType(attachment.file_type) && showPreview}
98
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
99
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
100
+ <img
101
+ src={attachment.url}
102
+ alt={attachment.file_name || 'Attachment'}
103
+ class="attachment-image"
104
+ loading="lazy"
105
+ onclick={() => handleView(attachment)}
106
+ />
107
+ {:else if isVideoType(attachment.file_type) && showPreview}
108
+ <video
109
+ src={attachment.url}
110
+ class="attachment-video"
111
+ controls
112
+ preload="metadata"
113
+ >
114
+ <track kind="captions" />
115
+ Your browser does not support video playback.
116
+ </video>
117
+ {:else}
118
+ <button
119
+ class="attachment-file"
120
+ onclick={() => handleView(attachment)}
121
+ title="Click to view {attachment.file_name || 'file'}"
122
+ >
123
+ <span class="file-icon">{getFileIcon(attachment.file_type)}</span>
124
+ <div class="file-info">
125
+ <span class="file-name">{attachment.file_name || 'Untitled'}</span>
126
+ {#if attachment.file_size}
127
+ <span class="file-size">{formatFileSize(attachment.file_size)}</span>
128
+ {/if}
129
+ </div>
130
+ </button>
131
+ {/if}
132
+ </div>
133
+ {/each}
134
+
135
+ {#if hiddenCount > 0}
136
+ <div class="more-attachments">
137
+ +{hiddenCount} more file{hiddenCount !== 1 ? 's' : ''}
138
+ </div>
139
+ {/if}
140
+ </div>
141
+
142
+ <!-- Preview variant - larger display with remove buttons -->
143
+ {:else if variant === 'preview'}
144
+ <div class="preview-attachments">
145
+ {#each displayedAttachments as attachment (attachment.id)}
146
+ <div class="preview-attachment-item">
147
+ {#if isImageType(attachment.file_type) && showPreview}
148
+ <div class="preview-image-container">
149
+ <img
150
+ src={attachment.url}
151
+ alt={attachment.file_name || 'Attachment'}
152
+ class="preview-image"
153
+ loading="lazy"
154
+ />
155
+ {#if onRemove}
156
+ <button
157
+ class="remove-button"
158
+ onclick={() => handleRemove(attachment)}
159
+ title="Remove attachment"
160
+ >
161
+
162
+ </button>
163
+ {/if}
164
+ </div>
165
+ {:else if isVideoType(attachment.file_type) && showPreview}
166
+ <div class="preview-video-container">
167
+ <video
168
+ src={attachment.url}
169
+ class="preview-video"
170
+ controls
171
+ preload="metadata"
172
+ >
173
+ <track kind="captions" />
174
+ Your browser does not support video playback.
175
+ </video>
176
+ {#if onRemove}
177
+ <button
178
+ class="remove-button"
179
+ onclick={() => handleRemove(attachment)}
180
+ title="Remove attachment"
181
+ >
182
+
183
+ </button>
184
+ {/if}
185
+ </div>
186
+ {:else}
187
+ <div class="preview-file-container">
188
+ <div class="preview-file">
189
+ <span class="file-icon">{getFileIcon(attachment.file_type)}</span>
190
+ <div class="file-info">
191
+ <span class="file-name">{attachment.file_name || 'Untitled'}</span>
192
+ {#if attachment.file_size}
193
+ <span class="file-size">{formatFileSize(attachment.file_size)}</span>
194
+ {/if}
195
+ </div>
196
+ </div>
197
+ {#if onRemove}
198
+ <button
199
+ class="remove-button"
200
+ onclick={() => handleRemove(attachment)}
201
+ title="Remove attachment"
202
+ >
203
+
204
+ </button>
205
+ {/if}
206
+ </div>
207
+ {/if}
208
+ </div>
209
+ {/each}
210
+ </div>
211
+
212
+ <!-- Compact variant - minimal space usage -->
213
+ {:else if variant === 'compact'}
214
+ <div class="compact-attachments">
215
+ {#each displayedAttachments as attachment (attachment.id)}
216
+ <button
217
+ class="compact-attachment-item"
218
+ onclick={() => handleView(attachment)}
219
+ title="{attachment.file_name || 'Untitled'} ({formatFileSize(attachment.file_size)})"
220
+ >
221
+ <span class="file-icon">{getFileIcon(attachment.file_type)}</span>
222
+ <span class="file-name">{attachment.file_name || 'File'}</span>
223
+ </button>
224
+ {/each}
225
+
226
+ {#if hiddenCount > 0}
227
+ <span class="more-count">+{hiddenCount}</span>
228
+ {/if}
229
+ </div>
230
+ {/if}
231
+ </div>
232
+ {/if}
233
+
234
+ <!-- Lightbox overlay for images and PDFs -->
235
+ {#if lightboxAttachment}
236
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
237
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
238
+ <div class="lightbox-overlay" onclick={closeLightbox}>
239
+ <button class="lightbox-close" onclick={closeLightbox} title="Close">✕</button>
240
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
241
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
242
+ <div class="lightbox-content" onclick={(e) => e.stopPropagation()}>
243
+ {#if isImageType(lightboxAttachment.file_type)}
244
+ <img
245
+ src={lightboxAttachment.url}
246
+ alt={lightboxAttachment.file_name || 'Attachment'}
247
+ class="lightbox-image"
248
+ />
249
+ {:else if lightboxAttachment.file_type.includes('pdf')}
250
+ <iframe
251
+ src={lightboxAttachment.url}
252
+ class="lightbox-pdf"
253
+ title={lightboxAttachment.file_name || 'PDF Document'}
254
+ ></iframe>
255
+ {/if}
256
+ {#if lightboxAttachment.file_name}
257
+ <div class="lightbox-caption">{lightboxAttachment.file_name}</div>
258
+ {/if}
259
+ </div>
260
+ </div>
261
+ {/if}
262
+
263
+ <style>
264
+ .attachment-display {
265
+ width: 100%;
266
+ }
267
+
268
+ /* Message variant styles */
269
+ .message-attachments {
270
+ display: flex;
271
+ flex-wrap: wrap;
272
+ gap: 8px;
273
+ margin: 0;
274
+ padding: 8px 12px 12px;
275
+ }
276
+
277
+ .message-attachment-item {
278
+ margin: 0;
279
+ padding: 0;
280
+ flex: 0 0 auto;
281
+ }
282
+
283
+ .message-attachment-item .attachment-image {
284
+ width: auto;
285
+ height: 120px;
286
+ max-width: 200px;
287
+ max-height: 120px;
288
+ border-radius: 0.5rem;
289
+ cursor: pointer;
290
+ object-fit: cover;
291
+ transition: opacity 0.2s;
292
+ display: block;
293
+ margin: 0;
294
+ padding: 0;
295
+ border: 1px solid rgba(255, 255, 255, 0.1);
296
+ }
297
+
298
+ .message-attachment-item .attachment-image:hover {
299
+ opacity: 0.9;
300
+ }
301
+
302
+ .message-attachment-item .attachment-video {
303
+ width: 175px;
304
+ height: auto;
305
+ border-radius: 0.5rem;
306
+ border: 1px solid rgba(255, 255, 255, 0.1);
307
+ margin: 0;
308
+ padding: 0;
309
+ }
310
+
311
+ .message-attachment-item .attachment-file {
312
+ display: flex;
313
+ align-items: center;
314
+ gap: 0.5rem;
315
+ padding: 0.5rem;
316
+ background: rgba(0, 0, 0, 0.1);
317
+ border: none;
318
+ border-radius: 0.375rem;
319
+ cursor: pointer;
320
+ transition: background-color 0.2s;
321
+ text-align: left;
322
+ width: 100%;
323
+ color: inherit;
324
+ }
325
+
326
+ .message-attachment-item .attachment-file:hover {
327
+ background: rgba(0, 0, 0, 0.15);
328
+ }
329
+
330
+ .more-attachments {
331
+ font-size: 0.75rem;
332
+ color: rgba(255, 255, 255, 0.7);
333
+ padding: 0.25rem 0.5rem;
334
+ background: rgba(0, 0, 0, 0.1);
335
+ border-radius: 0.25rem;
336
+ text-align: center;
337
+ }
338
+
339
+ /* Preview variant styles */
340
+ .preview-attachments {
341
+ display: flex;
342
+ flex-wrap: wrap;
343
+ gap: 0.75rem;
344
+ }
345
+
346
+ .preview-attachment-item {
347
+ position: relative;
348
+ flex: 0 0 auto;
349
+ }
350
+
351
+ .preview-image-container,
352
+ .preview-video-container,
353
+ .preview-file-container {
354
+ position: relative;
355
+ }
356
+
357
+ .preview-image {
358
+ width: 4rem;
359
+ height: 4rem;
360
+ object-fit: cover;
361
+ border-radius: 0.5rem;
362
+ border: 1px solid #e5e7eb;
363
+ }
364
+
365
+ .preview-video {
366
+ width: 6rem;
367
+ height: 4rem;
368
+ border-radius: 0.5rem;
369
+ border: 1px solid #e5e7eb;
370
+ }
371
+
372
+ .preview-file {
373
+ display: flex;
374
+ align-items: center;
375
+ gap: 0.5rem;
376
+ padding: 0.75rem;
377
+ background: white;
378
+ border: 1px solid #e5e7eb;
379
+ border-radius: 0.5rem;
380
+ min-width: 8rem;
381
+ }
382
+
383
+ .remove-button {
384
+ position: absolute;
385
+ top: -0.25rem;
386
+ right: -0.25rem;
387
+ width: 1.25rem;
388
+ height: 1.25rem;
389
+ background: #ef4444;
390
+ color: white;
391
+ border: none;
392
+ border-radius: 50%;
393
+ cursor: pointer;
394
+ font-size: 0.75rem;
395
+ line-height: 1;
396
+ display: flex;
397
+ align-items: center;
398
+ justify-content: center;
399
+ transition: background-color 0.2s;
400
+ }
401
+
402
+ .remove-button:hover {
403
+ background: #dc2626;
404
+ }
405
+
406
+ /* Compact variant styles */
407
+ .compact-attachments {
408
+ display: flex;
409
+ flex-wrap: wrap;
410
+ gap: 0.25rem;
411
+ align-items: center;
412
+ }
413
+
414
+ .compact-attachment-item {
415
+ display: flex;
416
+ align-items: center;
417
+ gap: 0.25rem;
418
+ padding: 0.25rem 0.5rem;
419
+ background: #f3f4f6;
420
+ border: 1px solid #e5e7eb;
421
+ border-radius: 0.375rem;
422
+ font-size: 0.75rem;
423
+ cursor: pointer;
424
+ transition: background-color 0.2s;
425
+ color: #374151;
426
+ }
427
+
428
+ .compact-attachment-item:hover {
429
+ background: #e5e7eb;
430
+ }
431
+
432
+ .more-count {
433
+ font-size: 0.75rem;
434
+ color: #6b7280;
435
+ padding: 0.25rem;
436
+ }
437
+
438
+ /* Common styles */
439
+ .file-icon {
440
+ font-size: 1rem;
441
+ flex-shrink: 0;
442
+ }
443
+
444
+ .file-info {
445
+ display: flex;
446
+ flex-direction: column;
447
+ gap: 0.125rem;
448
+ min-width: 0;
449
+ }
450
+
451
+ .file-name {
452
+ font-weight: 500;
453
+ font-size: 0.875rem;
454
+ overflow: hidden;
455
+ text-overflow: ellipsis;
456
+ white-space: nowrap;
457
+ }
458
+
459
+ .file-size {
460
+ font-size: 0.75rem;
461
+ opacity: 0.7;
462
+ }
463
+
464
+ /* Responsive adjustments */
465
+ @media (max-width: 640px) {
466
+ .preview-attachments {
467
+ gap: 0.5rem;
468
+ }
469
+
470
+ .preview-image,
471
+ .preview-video {
472
+ width: 3rem;
473
+ height: 3rem;
474
+ }
475
+
476
+ .preview-file {
477
+ min-width: 6rem;
478
+ padding: 0.5rem;
479
+ }
480
+ }
481
+
482
+ /* Lightbox overlay */
483
+ .lightbox-overlay {
484
+ position: fixed;
485
+ inset: 0;
486
+ z-index: 9999;
487
+ display: flex;
488
+ align-items: center;
489
+ justify-content: center;
490
+ background: rgba(0, 0, 0, 0.85);
491
+ backdrop-filter: blur(4px);
492
+ }
493
+
494
+ .lightbox-close {
495
+ position: absolute;
496
+ top: 16px;
497
+ right: 16px;
498
+ width: 36px;
499
+ height: 36px;
500
+ border-radius: 50%;
501
+ border: none;
502
+ background: rgba(255, 255, 255, 0.15);
503
+ color: white;
504
+ font-size: 18px;
505
+ cursor: pointer;
506
+ display: flex;
507
+ align-items: center;
508
+ justify-content: center;
509
+ transition: background 0.2s;
510
+ z-index: 10;
511
+ }
512
+ .lightbox-close:hover {
513
+ background: rgba(255, 255, 255, 0.3);
514
+ }
515
+
516
+ .lightbox-content {
517
+ display: flex;
518
+ flex-direction: column;
519
+ align-items: center;
520
+ gap: 12px;
521
+ max-width: 90vw;
522
+ max-height: 90vh;
523
+ }
524
+
525
+ .lightbox-image {
526
+ max-width: 90vw;
527
+ max-height: 85vh;
528
+ object-fit: contain;
529
+ border-radius: 8px;
530
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
531
+ }
532
+
533
+ .lightbox-pdf {
534
+ width: 80vw;
535
+ height: 85vh;
536
+ border: none;
537
+ border-radius: 8px;
538
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
539
+ background: white;
540
+ }
541
+
542
+ .lightbox-caption {
543
+ color: rgba(255, 255, 255, 0.7);
544
+ font-size: 0.875rem;
545
+ text-align: center;
546
+ }
547
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { ContentItemOutput } from '../types/message.js';
2
+ interface Props {
3
+ attachments: ContentItemOutput[];
4
+ variant?: 'message' | 'preview' | 'compact';
5
+ showPreview?: boolean;
6
+ maxDisplayCount?: number;
7
+ onRemove?: (attachment: ContentItemOutput) => void;
8
+ onView?: (attachment: ContentItemOutput) => void;
9
+ }
10
+ declare const AttachmentDisplay: import("svelte").Component<Props, {}, "">;
11
+ type AttachmentDisplay = ReturnType<typeof AttachmentDisplay>;
12
+ export default AttachmentDisplay;