@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,28 @@
1
+ import type { ChatMessage, ChatContext } from '../types/index.js';
2
+ interface Props {
3
+ className?: className;
4
+ messages: ChatMessage[];
5
+ isLoading?: boolean;
6
+ context: ChatContext;
7
+ isCompleted?: boolean;
8
+ /**
9
+ * When true, heuristic code pattern detection (`detectCodePatterns`) runs
10
+ * for messages without explicit ``` fences. Default false — keeps chat UI
11
+ * clean when assistants return summaries that mention file paths or
12
+ * extensions (e.g. "Updated 4 files: ...svelte") which would otherwise be
13
+ * misclassified as JavaScript code blocks. Enable only for debugging.
14
+ */
15
+ enableAutoCodeDetection?: boolean;
16
+ /**
17
+ * When false, backend-generated "Updated N files: ..." summaries are
18
+ * hidden from the UI (they still exist in the DB as LLM context). Default
19
+ * false — noisy for end users. Typically bound to `debugMode` by hosts.
20
+ */
21
+ showGenerationSummaries?: boolean;
22
+ onSuggestionSelected?: (suggestion: string) => void;
23
+ onRestoreCheckpoint?: (messageId: string, messageIndex: number) => void;
24
+ onRetryMessage?: (content: string) => void;
25
+ }
26
+ declare const ChatMessages: import("svelte").Component<Props, {}, "">;
27
+ type ChatMessages = ReturnType<typeof ChatMessages>;
28
+ export default ChatMessages;
@@ -0,0 +1,286 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import hljs from 'highlight.js';
4
+
5
+ interface Props {
6
+ code: string;
7
+ language?: string;
8
+ showLineNumbers?: boolean;
9
+ title?: string;
10
+ copyable?: boolean;
11
+ }
12
+
13
+ let { code, language = 'plaintext', showLineNumbers = true, title, copyable = true }: Props = $props();
14
+
15
+ let codeElement: HTMLElement;
16
+ let highlightedCode = $state('');
17
+ let copied = $state(false);
18
+
19
+ onMount(() => {
20
+ // Auto-detect language if not provided
21
+ if (language === 'plaintext') {
22
+ const detected = hljs.highlightAuto(code);
23
+ highlightedCode = detected.value;
24
+ language = detected.language || 'plaintext';
25
+ } else {
26
+ try {
27
+ const highlighted = hljs.highlight(code, { language });
28
+ highlightedCode = highlighted.value;
29
+ } catch (error) {
30
+ // Fallback to auto-detect if specific language fails
31
+ const detected = hljs.highlightAuto(code);
32
+ highlightedCode = detected.value;
33
+ language = detected.language || 'plaintext';
34
+ }
35
+ }
36
+ });
37
+
38
+ async function copyToClipboard() {
39
+ try {
40
+ await navigator.clipboard.writeText(code);
41
+ copied = true;
42
+ setTimeout(() => copied = false, 2000);
43
+ } catch (error) {
44
+ console.error('Failed to copy code:', error);
45
+ }
46
+ }
47
+
48
+ function getLanguageIcon(lang: string): string {
49
+ const icons: Record<string, string> = {
50
+ javascript: '🟨',
51
+ typescript: '🔷',
52
+ python: '🐍',
53
+ java: '☕',
54
+ cpp: '⚙️',
55
+ css: '🎨',
56
+ html: '🌐',
57
+ json: '📋',
58
+ sql: '🗃️',
59
+ bash: '💻',
60
+ shell: '💻',
61
+ yaml: '📄',
62
+ xml: '📄',
63
+ markdown: '📝'
64
+ };
65
+ return icons[lang] || '📄';
66
+ }
67
+ </script>
68
+
69
+ <div class="code-block">
70
+ {#if title || language || copyable}
71
+ <div class="code-header">
72
+ <div class="code-info">
73
+ {#if title}
74
+ <span class="code-title">{title}</span>
75
+ {:else if language}
76
+ <div class="language-badge">
77
+ <span class="language-icon">{getLanguageIcon(language)}</span>
78
+ <span class="language-name">{language}</span>
79
+ </div>
80
+ {/if}
81
+ </div>
82
+
83
+ {#if copyable}
84
+ <button
85
+ class="copy-button"
86
+ class:copied
87
+ onclick={copyToClipboard}
88
+ title={copied ? 'Copied!' : 'Copy code'}
89
+ >
90
+ {#if copied}
91
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
92
+ <path d="M20 6L9 17l-5-5"/>
93
+ </svg>
94
+ Copied!
95
+ {:else}
96
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
97
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
98
+ <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
99
+ </svg>
100
+ Copy
101
+ {/if}
102
+ </button>
103
+ {/if}
104
+ </div>
105
+ {/if}
106
+
107
+ <div class="code-content">
108
+ <pre class="code-pre" class:with-line-numbers={showLineNumbers}><code
109
+ bind:this={codeElement}
110
+ class="hljs language-{language}"
111
+ >{@html highlightedCode || code}</code></pre>
112
+ </div>
113
+ </div>
114
+
115
+ <!-- Import highlight.js CSS theme -->
116
+ <svelte:head>
117
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
118
+ </svelte:head>
119
+
120
+ <style>
121
+ .code-block {
122
+ border: 1px solid #e5e7eb;
123
+ border-radius: 8px;
124
+ overflow: hidden;
125
+ background: #f9fafb;
126
+ margin: 8px 0;
127
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
128
+ }
129
+
130
+ .code-header {
131
+ display: flex;
132
+ align-items: center;
133
+ justify-content: space-between;
134
+ padding: 8px 12px;
135
+ background: #f3f4f6;
136
+ border-bottom: 1px solid #e5e7eb;
137
+ font-size: 12px;
138
+ }
139
+
140
+ .code-info {
141
+ display: flex;
142
+ align-items: center;
143
+ gap: 8px;
144
+ }
145
+
146
+ .code-title {
147
+ font-weight: 600;
148
+ color: #374151;
149
+ }
150
+
151
+ .language-badge {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 4px;
155
+ color: #6b7280;
156
+ }
157
+
158
+ .language-icon {
159
+ font-size: 14px;
160
+ }
161
+
162
+ .language-name {
163
+ font-weight: 500;
164
+ text-transform: uppercase;
165
+ font-size: 11px;
166
+ letter-spacing: 0.5px;
167
+ }
168
+
169
+ .copy-button {
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 4px;
173
+ padding: 4px 8px;
174
+ border: 1px solid #d1d5db;
175
+ border-radius: 4px;
176
+ background: white;
177
+ color: #374151;
178
+ font-size: 11px;
179
+ font-weight: 500;
180
+ cursor: pointer;
181
+ transition: all 0.2s ease;
182
+ }
183
+
184
+ .copy-button:hover {
185
+ background: #f3f4f6;
186
+ border-color: #9ca3af;
187
+ }
188
+
189
+ .copy-button.copied {
190
+ background: #dcfce7;
191
+ border-color: #86efac;
192
+ color: #166534;
193
+ }
194
+
195
+ .code-content {
196
+ overflow-x: auto;
197
+ }
198
+
199
+ .code-pre {
200
+ margin: 0;
201
+ padding: 16px;
202
+ background: white;
203
+ overflow-x: auto;
204
+ font-size: 13px;
205
+ line-height: 1.5;
206
+ }
207
+
208
+ .code-pre.with-line-numbers {
209
+ counter-reset: line;
210
+ }
211
+
212
+ .code-pre.with-line-numbers code {
213
+ counter-increment: line;
214
+ }
215
+
216
+ /* Line numbers styling */
217
+ .with-line-numbers :global(.hljs) {
218
+ padding-left: 3em;
219
+ position: relative;
220
+ }
221
+
222
+ .with-line-numbers :global(.hljs::before) {
223
+ content: counter(line);
224
+ position: absolute;
225
+ left: 0;
226
+ top: 0;
227
+ bottom: 0;
228
+ width: 2.5em;
229
+ background: #f8f9fa;
230
+ border-right: 1px solid #e9ecef;
231
+ color: #6c757d;
232
+ text-align: right;
233
+ padding-right: 8px;
234
+ font-size: 12px;
235
+ line-height: inherit;
236
+ user-select: none;
237
+ }
238
+
239
+ /* Override some highlight.js styles for better integration */
240
+ .code-pre :global(.hljs) {
241
+ background: transparent !important;
242
+ padding: 0;
243
+ }
244
+
245
+ /* Dark mode support */
246
+ @media (prefers-color-scheme: dark) {
247
+ .code-block {
248
+ background: #1f2937;
249
+ border-color: #374151;
250
+ }
251
+
252
+ .code-header {
253
+ background: #374151;
254
+ border-color: #4b5563;
255
+ }
256
+
257
+ .code-title {
258
+ color: #f3f4f6;
259
+ }
260
+
261
+ .language-badge {
262
+ color: #d1d5db;
263
+ }
264
+
265
+ .copy-button {
266
+ background: #4b5563;
267
+ border-color: #6b7280;
268
+ color: #f3f4f6;
269
+ }
270
+
271
+ .copy-button:hover {
272
+ background: #6b7280;
273
+ }
274
+
275
+ .code-pre {
276
+ background: #1f2937;
277
+ color: #f3f4f6;
278
+ }
279
+
280
+ .with-line-numbers :global(.hljs::before) {
281
+ background: #374151;
282
+ border-color: #4b5563;
283
+ color: #9ca3af;
284
+ }
285
+ }
286
+ </style>
@@ -0,0 +1,10 @@
1
+ interface Props {
2
+ code: string;
3
+ language?: string;
4
+ showLineNumbers?: boolean;
5
+ title?: string;
6
+ copyable?: boolean;
7
+ }
8
+ declare const CodeBlock: import("svelte").Component<Props, {}, "">;
9
+ type CodeBlock = ReturnType<typeof CodeBlock>;
10
+ export default CodeBlock;
@@ -0,0 +1,66 @@
1
+ <script lang="ts">
2
+ import type { ChatContext } from '../types/index.js';
3
+
4
+ interface Props {
5
+ context: ChatContext;
6
+ onclick?: () => void;
7
+ }
8
+
9
+ let { context, onclick }: Props = $props();
10
+
11
+ function handleClick() {
12
+ onclick?.();
13
+ }
14
+ </script>
15
+
16
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
17
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
18
+ <button
19
+ class="flex items-center justify-between p-3 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors shadow-sm hover:shadow-md w-full text-left"
20
+ onclick={handleClick}
21
+ aria-label="Expand {context.title || context.id} chat context"
22
+ >
23
+ <div class="flex items-center gap-3">
24
+ {#if context.thumbnail}
25
+ <img
26
+ src={context.thumbnail}
27
+ alt={context.title || context.id}
28
+ class="w-10 h-10 rounded-md object-cover"
29
+ />
30
+ {:else}
31
+ <div class="w-10 h-10 rounded-md bg-gray-100 dark:bg-gray-700 flex items-center justify-center text-lg">
32
+ {#if context.type === 'frame'}
33
+ 📱
34
+ {:else if context.type === 'text'}
35
+ 📄
36
+ {:else if context.type === 'image'}
37
+ 🖼️
38
+ {:else if context.type === 'document'}
39
+ 📋
40
+ {:else}
41
+ 💬
42
+ {/if}
43
+ </div>
44
+ {/if}
45
+
46
+ <div class="flex flex-col">
47
+ <h4 class="font-medium text-gray-900 dark:text-gray-100 text-sm truncate max-w-[180px]">
48
+ {context.title || context.id}
49
+ </h4>
50
+ <p class="text-xs text-gray-500 dark:text-gray-400 capitalize">
51
+ {context.type} chat
52
+ </p>
53
+ </div>
54
+ </div>
55
+
56
+ <div class="flex-shrink-0">
57
+ <svg
58
+ class="w-5 h-5 text-gray-400"
59
+ viewBox="0 0 24 24"
60
+ fill="none"
61
+ stroke="currentColor"
62
+ >
63
+ <path d="m18 15-6-6-6 6"/>
64
+ </svg>
65
+ </div>
66
+ </button>
@@ -0,0 +1,8 @@
1
+ import type { ChatContext } from '../types/index.js';
2
+ interface Props {
3
+ context: ChatContext;
4
+ onclick?: () => void;
5
+ }
6
+ declare const ContextPreview: import("svelte").Component<Props, {}, "">;
7
+ type ContextPreview = ReturnType<typeof ContextPreview>;
8
+ export default ContextPreview;
@@ -0,0 +1,151 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ type?: 'dots' | 'spinner' | 'pulse' | 'typing' | 'thinking';
4
+ size?: 'sm' | 'md' | 'lg';
5
+ text?: string;
6
+ color?: 'primary' | 'secondary' | 'muted';
7
+ }
8
+
9
+ let {
10
+ type = 'thinking',
11
+ size = 'md',
12
+ text = '',
13
+ color = 'muted'
14
+ }: Props = $props();
15
+
16
+ let colorClasses = $derived({
17
+ primary: 'text-blue-600',
18
+ secondary: 'text-gray-600',
19
+ muted: 'text-gray-400'
20
+ }[color]);
21
+
22
+ let sizeClasses = $derived({
23
+ sm: 'text-xs',
24
+ md: 'text-sm',
25
+ lg: 'text-base'
26
+ }[size]);
27
+
28
+ let dotSize = $derived({
29
+ sm: 'w-1 h-1',
30
+ md: 'w-1.5 h-1.5',
31
+ lg: 'w-2 h-2'
32
+ }[size]);
33
+
34
+ let spinnerSize = $derived({
35
+ sm: 'w-4 h-4',
36
+ md: 'w-5 h-5',
37
+ lg: 'w-6 h-6'
38
+ }[size]);
39
+ </script>
40
+
41
+ <div class="loading-indicator flex items-center gap-2 {colorClasses} {sizeClasses}">
42
+ {#if type === 'dots'}
43
+ <div class="flex gap-1">
44
+ <span class="{dotSize} bg-current rounded-full animate-pulse" style="animation-delay: 0ms;"></span>
45
+ <span class="{dotSize} bg-current rounded-full animate-pulse" style="animation-delay: 150ms;"></span>
46
+ <span class="{dotSize} bg-current rounded-full animate-pulse" style="animation-delay: 300ms;"></span>
47
+ </div>
48
+ {:else if type === 'spinner'}
49
+ <div class="{spinnerSize} animate-spin">
50
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
51
+ <path d="M21 12a9 9 0 11-6.219-8.56"/>
52
+ </svg>
53
+ </div>
54
+ {:else if type === 'pulse'}
55
+ <div class="{spinnerSize} animate-pulse">
56
+ <svg viewBox="0 0 24 24" fill="currentColor">
57
+ <circle cx="12" cy="12" r="10"/>
58
+ </svg>
59
+ </div>
60
+ {:else if type === 'typing'}
61
+ <!-- Typing indicator with three bouncing dots -->
62
+ <div class="flex gap-1">
63
+ <span class="{dotSize} bg-current rounded-full animate-bounce" style="animation-delay: 0ms;"></span>
64
+ <span class="{dotSize} bg-current rounded-full animate-bounce" style="animation-delay: 150ms;"></span>
65
+ <span class="{dotSize} bg-current rounded-full animate-bounce" style="animation-delay: 300ms;"></span>
66
+ </div>
67
+ {:else if type === 'thinking'}
68
+ <!-- AI thinking indicator -->
69
+ <div class="thinking-indicator flex items-center gap-2">
70
+ <div class="thinking-brain">
71
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
72
+ <path d="M12 8V4l3 3-3 3m0 0l-3-3 3-3m0 8v8"/>
73
+ <path d="M8 12h8"/>
74
+ </svg>
75
+ </div>
76
+ <div class="flex gap-1">
77
+ <span class="{dotSize} bg-current rounded-full animate-pulse" style="animation-delay: 0ms;"></span>
78
+ <span class="{dotSize} bg-current rounded-full animate-pulse" style="animation-delay: 200ms;"></span>
79
+ <span class="{dotSize} bg-current rounded-full animate-pulse" style="animation-delay: 400ms;"></span>
80
+ </div>
81
+ </div>
82
+ {/if}
83
+
84
+ {#if text}
85
+ <span class="loading-text font-medium">
86
+ {text}
87
+ </span>
88
+ {/if}
89
+ </div>
90
+
91
+ <style>
92
+ .loading-indicator {
93
+ user-select: none;
94
+ }
95
+
96
+ .thinking-brain {
97
+ opacity: 0.7;
98
+ animation: think 2s ease-in-out infinite;
99
+ }
100
+
101
+ @keyframes think {
102
+ 0%, 100% {
103
+ opacity: 0.7;
104
+ transform: scale(1);
105
+ }
106
+ 50% {
107
+ opacity: 1;
108
+ transform: scale(1.1);
109
+ }
110
+ }
111
+
112
+ @keyframes bounce {
113
+ 0%, 80%, 100% {
114
+ transform: translateY(0);
115
+ }
116
+ 40% {
117
+ transform: translateY(-6px);
118
+ }
119
+ }
120
+
121
+ @keyframes pulse {
122
+ 0%, 100% {
123
+ opacity: 1;
124
+ }
125
+ 50% {
126
+ opacity: 0.3;
127
+ }
128
+ }
129
+
130
+ @keyframes spin {
131
+ from {
132
+ transform: rotate(0deg);
133
+ }
134
+ to {
135
+ transform: rotate(360deg);
136
+ }
137
+ }
138
+
139
+ .animate-bounce {
140
+ animation: bounce 1s ease-in-out infinite;
141
+ }
142
+
143
+ .animate-pulse {
144
+ animation: pulse 1.5s ease-in-out infinite;
145
+ }
146
+
147
+ .animate-spin {
148
+ animation: spin 1s linear infinite;
149
+ }
150
+
151
+ </style>
@@ -0,0 +1,9 @@
1
+ interface Props {
2
+ type?: 'dots' | 'spinner' | 'pulse' | 'typing' | 'thinking';
3
+ size?: 'sm' | 'md' | 'lg';
4
+ text?: string;
5
+ color?: 'primary' | 'secondary' | 'muted';
6
+ }
7
+ declare const LoadingIndicator: import("svelte").Component<Props, {}, "">;
8
+ type LoadingIndicator = ReturnType<typeof LoadingIndicator>;
9
+ export default LoadingIndicator;
@@ -0,0 +1,116 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ status: 'active' | 'completed' | 'loading' | 'error';
4
+ messageCount?: number;
5
+ showText?: boolean;
6
+ size?: 'sm' | 'md' | 'lg';
7
+ }
8
+
9
+ let { status, messageCount = 0, showText = true, size = 'md' }: Props = $props();
10
+
11
+ function getStatusConfig(status: string) {
12
+ switch (status) {
13
+ case 'completed':
14
+ return {
15
+ icon: '✅',
16
+ text: 'Completed',
17
+ color: 'text-green-600',
18
+ bgColor: 'bg-green-100',
19
+ borderColor: 'border-green-200'
20
+ };
21
+ case 'loading':
22
+ return {
23
+ icon: '⏳',
24
+ text: 'Processing...',
25
+ color: 'text-blue-600',
26
+ bgColor: 'bg-blue-100',
27
+ borderColor: 'border-blue-200'
28
+ };
29
+ case 'error':
30
+ return {
31
+ icon: '❌',
32
+ text: 'Error',
33
+ color: 'text-red-600',
34
+ bgColor: 'bg-red-100',
35
+ borderColor: 'border-red-200'
36
+ };
37
+ default: // active
38
+ return {
39
+ icon: '💬',
40
+ text: 'Active',
41
+ color: 'text-gray-600',
42
+ bgColor: 'bg-gray-100',
43
+ borderColor: 'border-gray-200'
44
+ };
45
+ }
46
+ }
47
+
48
+ let config = $derived(getStatusConfig(status));
49
+ let sizeClasses = $derived({
50
+ sm: 'px-2 py-1 text-xs',
51
+ md: 'px-3 py-1.5 text-sm',
52
+ lg: 'px-4 py-2 text-base'
53
+ }[size]);
54
+ </script>
55
+
56
+ <div
57
+ class="status-indicator inline-flex items-center gap-2 rounded-full border {config.bgColor} {config.borderColor} {config.color} {sizeClasses}"
58
+ class:animate-pulse={status === 'loading'}
59
+ >
60
+ <span class="status-icon" class:animate-spin={status === 'loading'}>
61
+ {#if status === 'loading'}
62
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
63
+ <path d="M21 12a9 9 0 11-6.219-8.56"/>
64
+ </svg>
65
+ {:else}
66
+ {config.icon}
67
+ {/if}
68
+ </span>
69
+
70
+ {#if showText}
71
+ <span class="status-text font-medium">
72
+ {config.text}
73
+ {#if messageCount > 0 && status !== 'loading'}
74
+ <span class="message-count opacity-75">
75
+ ({messageCount} message{messageCount !== 1 ? 's' : ''})
76
+ </span>
77
+ {/if}
78
+ </span>
79
+ {/if}
80
+ </div>
81
+
82
+ <style>
83
+ .status-indicator {
84
+ transition: all 0.2s ease;
85
+ font-weight: 500;
86
+ user-select: none;
87
+ }
88
+
89
+ .status-icon {
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ flex-shrink: 0;
94
+ }
95
+
96
+ .message-count {
97
+ font-weight: normal;
98
+ font-size: 0.9em;
99
+ }
100
+
101
+ @keyframes spin {
102
+ from { transform: rotate(0deg); }
103
+ to { transform: rotate(360deg); }
104
+ }
105
+
106
+ .animate-spin {
107
+ animation: spin 1s linear infinite;
108
+ }
109
+
110
+ /* Dark mode support */
111
+ @media (prefers-color-scheme: dark) {
112
+ .status-indicator {
113
+ border-color: #374151;
114
+ }
115
+ }
116
+ </style>
@@ -0,0 +1,9 @@
1
+ interface Props {
2
+ status: 'active' | 'completed' | 'loading' | 'error';
3
+ messageCount?: number;
4
+ showText?: boolean;
5
+ size?: 'sm' | 'md' | 'lg';
6
+ }
7
+ declare const StatusIndicator: import("svelte").Component<Props, {}, "">;
8
+ type StatusIndicator = ReturnType<typeof StatusIndicator>;
9
+ export default StatusIndicator;