@aspect-ops/exon-ui 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +341 -16
- package/dist/components/Accordion/Accordion.svelte +2 -2
- package/dist/components/Accordion/AccordionItem.svelte +2 -2
- package/dist/components/Chatbot/ChatMessage.svelte +143 -0
- package/dist/components/Chatbot/ChatMessage.svelte.d.ts +8 -0
- package/dist/components/Chatbot/Chatbot.svelte +640 -0
- package/dist/components/Chatbot/Chatbot.svelte.d.ts +22 -0
- package/dist/components/Chatbot/index.d.ts +3 -0
- package/dist/components/Chatbot/index.js +2 -0
- package/dist/components/Chatbot/types.d.ts +48 -0
- package/dist/components/Chatbot/types.js +2 -0
- package/dist/components/ContactForm/ContactForm.svelte +564 -0
- package/dist/components/ContactForm/ContactForm.svelte.d.ts +44 -0
- package/dist/components/ContactForm/index.d.ts +1 -0
- package/dist/components/ContactForm/index.js +1 -0
- package/dist/components/DoughnutChart/DoughnutChart.svelte +372 -0
- package/dist/components/DoughnutChart/DoughnutChart.svelte.d.ts +25 -0
- package/dist/components/DoughnutChart/index.d.ts +1 -0
- package/dist/components/DoughnutChart/index.js +1 -0
- package/dist/components/FAB/FAB.svelte +5 -1
- package/dist/components/FAB/FABGroup.svelte +10 -2
- package/dist/components/FileUpload/FileUpload.svelte +12 -12
- package/dist/components/Mermaid/Mermaid.svelte +206 -0
- package/dist/components/Mermaid/Mermaid.svelte.d.ts +28 -0
- package/dist/components/Mermaid/index.d.ts +1 -0
- package/dist/components/Mermaid/index.js +1 -0
- package/dist/components/Mermaid/mermaid.d.ts +21 -0
- package/dist/components/NumberInput/NumberInput.svelte +293 -0
- package/dist/components/NumberInput/NumberInput.svelte.d.ts +16 -0
- package/dist/components/NumberInput/index.d.ts +1 -0
- package/dist/components/NumberInput/index.js +1 -0
- package/dist/components/Pagination/Pagination.svelte +243 -0
- package/dist/components/Pagination/Pagination.svelte.d.ts +10 -0
- package/dist/components/Pagination/index.d.ts +1 -0
- package/dist/components/Pagination/index.js +1 -0
- package/dist/components/Popover/PopoverTrigger.svelte +1 -3
- package/dist/components/ToggleGroup/ToggleGroup.svelte +91 -0
- package/dist/components/ToggleGroup/ToggleGroup.svelte.d.ts +13 -0
- package/dist/components/ToggleGroup/ToggleGroupItem.svelte +158 -0
- package/dist/components/ToggleGroup/ToggleGroupItem.svelte.d.ts +9 -0
- package/dist/components/ToggleGroup/index.d.ts +3 -0
- package/dist/components/ToggleGroup/index.js +2 -0
- package/dist/components/ViewCounter/ViewCounter.svelte +157 -0
- package/dist/components/ViewCounter/ViewCounter.svelte.d.ts +17 -0
- package/dist/components/ViewCounter/index.d.ts +1 -0
- package/dist/components/ViewCounter/index.js +1 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +12 -0
- package/dist/styles/tokens.css +2 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/input.d.ts +35 -0
- package/package.json +2 -1
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy } from 'svelte';
|
|
3
|
+
import ChatMessage from './ChatMessage.svelte';
|
|
4
|
+
import type { ChatMessage as ChatMessageType, LeadData } from './types.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
initialMessages?: ChatMessageType[];
|
|
8
|
+
sessionId?: string;
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
welcomeMessage?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
subtitle?: string;
|
|
13
|
+
defaultOpen?: boolean;
|
|
14
|
+
inactivityTimeout?: number;
|
|
15
|
+
showTypingIndicator?: boolean;
|
|
16
|
+
position?: 'bottom-right' | 'bottom-left';
|
|
17
|
+
class?: string;
|
|
18
|
+
onSendMessage?: (message: string, sessionId: string) => Promise<string | null>;
|
|
19
|
+
onEscalate?: (sessionId: string, messages: ChatMessageType[]) => Promise<void>;
|
|
20
|
+
onLeadCapture?: (data: LeadData, sessionId: string) => Promise<void>;
|
|
21
|
+
onOpen?: () => void;
|
|
22
|
+
onClose?: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
initialMessages = [],
|
|
27
|
+
sessionId: propSessionId,
|
|
28
|
+
placeholder = 'Type your message...',
|
|
29
|
+
welcomeMessage = 'Hello! How can I help you today?',
|
|
30
|
+
title = 'Chat Support',
|
|
31
|
+
subtitle = 'We typically reply within minutes',
|
|
32
|
+
defaultOpen = false,
|
|
33
|
+
inactivityTimeout = 40000,
|
|
34
|
+
showTypingIndicator = true,
|
|
35
|
+
position = 'bottom-right',
|
|
36
|
+
class: className = '',
|
|
37
|
+
onSendMessage,
|
|
38
|
+
onEscalate,
|
|
39
|
+
onLeadCapture,
|
|
40
|
+
onOpen,
|
|
41
|
+
onClose
|
|
42
|
+
}: Props = $props();
|
|
43
|
+
|
|
44
|
+
// State - intentionally capture initial values (not reactive to prop changes)
|
|
45
|
+
// eslint-disable-next-line svelte/valid-compile
|
|
46
|
+
let isOpen = $state(defaultOpen);
|
|
47
|
+
let isMinimized = $state(false);
|
|
48
|
+
// eslint-disable-next-line svelte/valid-compile
|
|
49
|
+
let messages = $state<ChatMessageType[]>([...initialMessages]);
|
|
50
|
+
let inputValue = $state('');
|
|
51
|
+
let isTyping = $state(false);
|
|
52
|
+
// eslint-disable-next-line svelte/valid-compile
|
|
53
|
+
let sessionId = $state(propSessionId || generateSessionId());
|
|
54
|
+
let messagesContainer: HTMLDivElement | undefined = $state();
|
|
55
|
+
let textareaRef: HTMLTextAreaElement | undefined = $state();
|
|
56
|
+
let inactivityTimer: ReturnType<typeof setTimeout> | null = null;
|
|
57
|
+
|
|
58
|
+
// Generate session ID
|
|
59
|
+
function generateSessionId(): string {
|
|
60
|
+
return `chat_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Create message helper
|
|
64
|
+
function createMessage(role: ChatMessageType['role'], content: string): ChatMessageType {
|
|
65
|
+
return {
|
|
66
|
+
id: `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
67
|
+
role,
|
|
68
|
+
content,
|
|
69
|
+
timestamp: new Date()
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Auto-resize textarea
|
|
74
|
+
function autoResize() {
|
|
75
|
+
if (textareaRef) {
|
|
76
|
+
textareaRef.style.height = 'auto';
|
|
77
|
+
textareaRef.style.height = Math.min(textareaRef.scrollHeight, 120) + 'px';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Scroll to bottom
|
|
82
|
+
function scrollToBottom() {
|
|
83
|
+
if (messagesContainer) {
|
|
84
|
+
setTimeout(() => {
|
|
85
|
+
if (messagesContainer) {
|
|
86
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
87
|
+
}
|
|
88
|
+
}, 50);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Handle send message
|
|
93
|
+
async function handleSend() {
|
|
94
|
+
const content = inputValue.trim();
|
|
95
|
+
if (!content) return;
|
|
96
|
+
|
|
97
|
+
// Add user message
|
|
98
|
+
const userMessage = createMessage('user', content);
|
|
99
|
+
messages = [...messages, userMessage];
|
|
100
|
+
inputValue = '';
|
|
101
|
+
if (textareaRef) {
|
|
102
|
+
textareaRef.style.height = 'auto';
|
|
103
|
+
}
|
|
104
|
+
scrollToBottom();
|
|
105
|
+
|
|
106
|
+
// Show typing indicator
|
|
107
|
+
if (showTypingIndicator && onSendMessage) {
|
|
108
|
+
isTyping = true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Call onSendMessage callback
|
|
112
|
+
if (onSendMessage) {
|
|
113
|
+
try {
|
|
114
|
+
const response = await onSendMessage(content, sessionId);
|
|
115
|
+
if (response) {
|
|
116
|
+
const assistantMessage = createMessage('assistant', response);
|
|
117
|
+
messages = [...messages, assistantMessage];
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
const errorMessage = createMessage(
|
|
121
|
+
'system',
|
|
122
|
+
'Sorry, something went wrong. Please try again.'
|
|
123
|
+
);
|
|
124
|
+
messages = [...messages, errorMessage];
|
|
125
|
+
} finally {
|
|
126
|
+
isTyping = false;
|
|
127
|
+
scrollToBottom();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle key press
|
|
133
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
134
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
handleSend();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Handle escalation
|
|
141
|
+
async function handleEscalate() {
|
|
142
|
+
if (onEscalate) {
|
|
143
|
+
const systemMessage = createMessage('system', 'Connecting you to a human agent...');
|
|
144
|
+
messages = [...messages, systemMessage];
|
|
145
|
+
scrollToBottom();
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
await onEscalate(sessionId, messages);
|
|
149
|
+
const successMessage = createMessage('system', 'A human agent will be with you shortly.');
|
|
150
|
+
messages = [...messages, successMessage];
|
|
151
|
+
} catch {
|
|
152
|
+
const errorMessage = createMessage(
|
|
153
|
+
'system',
|
|
154
|
+
'Unable to connect to an agent. Please try again later.'
|
|
155
|
+
);
|
|
156
|
+
messages = [...messages, errorMessage];
|
|
157
|
+
}
|
|
158
|
+
scrollToBottom();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Open chat
|
|
163
|
+
function openChat() {
|
|
164
|
+
isOpen = true;
|
|
165
|
+
isMinimized = false;
|
|
166
|
+
|
|
167
|
+
// Add welcome message if no messages
|
|
168
|
+
if (messages.length === 0 && welcomeMessage) {
|
|
169
|
+
const welcome = createMessage('assistant', welcomeMessage);
|
|
170
|
+
messages = [welcome];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
onOpen?.();
|
|
174
|
+
scrollToBottom();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Close chat
|
|
178
|
+
function closeChat() {
|
|
179
|
+
isOpen = false;
|
|
180
|
+
isMinimized = false;
|
|
181
|
+
onClose?.();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Minimize chat
|
|
185
|
+
function minimizeChat() {
|
|
186
|
+
isMinimized = true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Restore from minimized
|
|
190
|
+
function restoreChat() {
|
|
191
|
+
isMinimized = false;
|
|
192
|
+
scrollToBottom();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Reset inactivity timer
|
|
196
|
+
function resetInactivityTimer() {
|
|
197
|
+
if (inactivityTimer) {
|
|
198
|
+
clearTimeout(inactivityTimer);
|
|
199
|
+
inactivityTimer = null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (inactivityTimeout > 0 && !isOpen) {
|
|
203
|
+
inactivityTimer = setTimeout(() => {
|
|
204
|
+
openChat();
|
|
205
|
+
}, inactivityTimeout);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Setup inactivity detection
|
|
210
|
+
onMount(() => {
|
|
211
|
+
if (inactivityTimeout > 0 && !defaultOpen) {
|
|
212
|
+
resetInactivityTimer();
|
|
213
|
+
|
|
214
|
+
const events = ['mousemove', 'keydown', 'scroll', 'click'];
|
|
215
|
+
events.forEach((event) => {
|
|
216
|
+
document.addEventListener(event, resetInactivityTimer, { passive: true });
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return () => {
|
|
220
|
+
events.forEach((event) => {
|
|
221
|
+
document.removeEventListener(event, resetInactivityTimer);
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
onDestroy(() => {
|
|
228
|
+
if (inactivityTimer) {
|
|
229
|
+
clearTimeout(inactivityTimer);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Scroll on new messages
|
|
234
|
+
$effect(() => {
|
|
235
|
+
if (messages.length > 0) {
|
|
236
|
+
scrollToBottom();
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
</script>
|
|
240
|
+
|
|
241
|
+
<div
|
|
242
|
+
class="chatbot chatbot--{position} {className}"
|
|
243
|
+
class:chatbot--open={isOpen}
|
|
244
|
+
class:chatbot--minimized={isMinimized}
|
|
245
|
+
>
|
|
246
|
+
{#if isOpen}
|
|
247
|
+
<div class="chatbot__window">
|
|
248
|
+
<!-- Header -->
|
|
249
|
+
<div class="chatbot__header">
|
|
250
|
+
<div class="chatbot__header-info">
|
|
251
|
+
<div class="chatbot__title">{title}</div>
|
|
252
|
+
<div class="chatbot__subtitle">{subtitle}</div>
|
|
253
|
+
</div>
|
|
254
|
+
<div class="chatbot__header-actions">
|
|
255
|
+
<button
|
|
256
|
+
type="button"
|
|
257
|
+
class="chatbot__header-btn"
|
|
258
|
+
onclick={minimizeChat}
|
|
259
|
+
aria-label="Minimize chat"
|
|
260
|
+
>
|
|
261
|
+
<svg
|
|
262
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
263
|
+
width="18"
|
|
264
|
+
height="18"
|
|
265
|
+
viewBox="0 0 24 24"
|
|
266
|
+
fill="none"
|
|
267
|
+
stroke="currentColor"
|
|
268
|
+
stroke-width="2"
|
|
269
|
+
stroke-linecap="round"
|
|
270
|
+
stroke-linejoin="round"
|
|
271
|
+
>
|
|
272
|
+
<path d="M5 12h14" />
|
|
273
|
+
</svg>
|
|
274
|
+
</button>
|
|
275
|
+
<button
|
|
276
|
+
type="button"
|
|
277
|
+
class="chatbot__header-btn"
|
|
278
|
+
onclick={closeChat}
|
|
279
|
+
aria-label="Close chat"
|
|
280
|
+
>
|
|
281
|
+
<svg
|
|
282
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
283
|
+
width="18"
|
|
284
|
+
height="18"
|
|
285
|
+
viewBox="0 0 24 24"
|
|
286
|
+
fill="none"
|
|
287
|
+
stroke="currentColor"
|
|
288
|
+
stroke-width="2"
|
|
289
|
+
stroke-linecap="round"
|
|
290
|
+
stroke-linejoin="round"
|
|
291
|
+
>
|
|
292
|
+
<path d="M18 6L6 18" />
|
|
293
|
+
<path d="M6 6l12 12" />
|
|
294
|
+
</svg>
|
|
295
|
+
</button>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
{#if !isMinimized}
|
|
300
|
+
<!-- Messages -->
|
|
301
|
+
<div class="chatbot__messages" bind:this={messagesContainer}>
|
|
302
|
+
{#each messages as message (message.id)}
|
|
303
|
+
<ChatMessage {message} />
|
|
304
|
+
{/each}
|
|
305
|
+
|
|
306
|
+
{#if isTyping}
|
|
307
|
+
<div class="chatbot__typing">
|
|
308
|
+
<div class="chatbot__typing-dot"></div>
|
|
309
|
+
<div class="chatbot__typing-dot"></div>
|
|
310
|
+
<div class="chatbot__typing-dot"></div>
|
|
311
|
+
</div>
|
|
312
|
+
{/if}
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<!-- Quick Actions -->
|
|
316
|
+
{#if onEscalate}
|
|
317
|
+
<div class="chatbot__actions">
|
|
318
|
+
<button type="button" class="chatbot__action-btn" onclick={handleEscalate}>
|
|
319
|
+
Talk to human
|
|
320
|
+
</button>
|
|
321
|
+
</div>
|
|
322
|
+
{/if}
|
|
323
|
+
|
|
324
|
+
<!-- Input -->
|
|
325
|
+
<div class="chatbot__input-container">
|
|
326
|
+
<textarea
|
|
327
|
+
bind:this={textareaRef}
|
|
328
|
+
bind:value={inputValue}
|
|
329
|
+
{placeholder}
|
|
330
|
+
class="chatbot__input"
|
|
331
|
+
rows="1"
|
|
332
|
+
oninput={autoResize}
|
|
333
|
+
onkeydown={handleKeyDown}
|
|
334
|
+
></textarea>
|
|
335
|
+
<button
|
|
336
|
+
type="button"
|
|
337
|
+
class="chatbot__send-btn"
|
|
338
|
+
onclick={handleSend}
|
|
339
|
+
disabled={!inputValue.trim()}
|
|
340
|
+
aria-label="Send message"
|
|
341
|
+
>
|
|
342
|
+
<svg
|
|
343
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
344
|
+
width="20"
|
|
345
|
+
height="20"
|
|
346
|
+
viewBox="0 0 24 24"
|
|
347
|
+
fill="none"
|
|
348
|
+
stroke="currentColor"
|
|
349
|
+
stroke-width="2"
|
|
350
|
+
stroke-linecap="round"
|
|
351
|
+
stroke-linejoin="round"
|
|
352
|
+
>
|
|
353
|
+
<path d="M22 2L11 13" />
|
|
354
|
+
<path d="M22 2l-7 20-4-9-9-4 20-7z" />
|
|
355
|
+
</svg>
|
|
356
|
+
</button>
|
|
357
|
+
</div>
|
|
358
|
+
{/if}
|
|
359
|
+
</div>
|
|
360
|
+
{:else}
|
|
361
|
+
<!-- Launcher Button -->
|
|
362
|
+
<button type="button" class="chatbot__launcher" onclick={openChat} aria-label="Open chat">
|
|
363
|
+
<svg
|
|
364
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
365
|
+
width="24"
|
|
366
|
+
height="24"
|
|
367
|
+
viewBox="0 0 24 24"
|
|
368
|
+
fill="none"
|
|
369
|
+
stroke="currentColor"
|
|
370
|
+
stroke-width="2"
|
|
371
|
+
stroke-linecap="round"
|
|
372
|
+
stroke-linejoin="round"
|
|
373
|
+
>
|
|
374
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
375
|
+
</svg>
|
|
376
|
+
</button>
|
|
377
|
+
{/if}
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
<style>
|
|
381
|
+
.chatbot {
|
|
382
|
+
position: fixed;
|
|
383
|
+
z-index: 9999;
|
|
384
|
+
font-family: inherit;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.chatbot--bottom-right {
|
|
388
|
+
bottom: var(--space-lg, 1.5rem);
|
|
389
|
+
right: var(--space-lg, 1.5rem);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.chatbot--bottom-left {
|
|
393
|
+
bottom: var(--space-lg, 1.5rem);
|
|
394
|
+
left: var(--space-lg, 1.5rem);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* Launcher */
|
|
398
|
+
.chatbot__launcher {
|
|
399
|
+
width: 3.5rem;
|
|
400
|
+
height: 3.5rem;
|
|
401
|
+
border-radius: var(--radius-full, 9999px);
|
|
402
|
+
border: none;
|
|
403
|
+
background: var(--color-primary, #3b82f6);
|
|
404
|
+
color: var(--color-text-inverse, #ffffff);
|
|
405
|
+
cursor: pointer;
|
|
406
|
+
display: flex;
|
|
407
|
+
align-items: center;
|
|
408
|
+
justify-content: center;
|
|
409
|
+
box-shadow:
|
|
410
|
+
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
|
411
|
+
0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
|
412
|
+
transition: all var(--transition-base, 200ms ease);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.chatbot__launcher:hover {
|
|
416
|
+
background: var(--color-primary-hover, #2563eb);
|
|
417
|
+
transform: scale(1.05);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.chatbot__launcher:focus-visible {
|
|
421
|
+
outline: 2px solid var(--color-primary, #3b82f6);
|
|
422
|
+
outline-offset: 2px;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* Window */
|
|
426
|
+
.chatbot__window {
|
|
427
|
+
width: 380px;
|
|
428
|
+
max-width: calc(100vw - 2rem);
|
|
429
|
+
height: 500px;
|
|
430
|
+
max-height: calc(100vh - 6rem);
|
|
431
|
+
background: var(--color-bg, #ffffff);
|
|
432
|
+
border-radius: var(--radius-xl, 1rem);
|
|
433
|
+
box-shadow:
|
|
434
|
+
0 25px 50px -12px rgba(0, 0, 0, 0.25),
|
|
435
|
+
0 0 0 1px rgba(0, 0, 0, 0.05);
|
|
436
|
+
display: flex;
|
|
437
|
+
flex-direction: column;
|
|
438
|
+
overflow: hidden;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.chatbot--minimized .chatbot__window {
|
|
442
|
+
height: auto;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* Header */
|
|
446
|
+
.chatbot__header {
|
|
447
|
+
display: flex;
|
|
448
|
+
align-items: center;
|
|
449
|
+
justify-content: space-between;
|
|
450
|
+
padding: var(--space-md, 1rem);
|
|
451
|
+
background: var(--color-primary, #3b82f6);
|
|
452
|
+
color: var(--color-text-inverse, #ffffff);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.chatbot__header-info {
|
|
456
|
+
flex: 1;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.chatbot__title {
|
|
460
|
+
font-size: var(--text-base, 1rem);
|
|
461
|
+
font-weight: var(--font-semibold, 600);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.chatbot__subtitle {
|
|
465
|
+
font-size: var(--text-xs, 0.75rem);
|
|
466
|
+
opacity: 0.9;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.chatbot__header-actions {
|
|
470
|
+
display: flex;
|
|
471
|
+
gap: var(--space-xs, 0.25rem);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.chatbot__header-btn {
|
|
475
|
+
width: 2rem;
|
|
476
|
+
height: 2rem;
|
|
477
|
+
border: none;
|
|
478
|
+
background: transparent;
|
|
479
|
+
color: inherit;
|
|
480
|
+
cursor: pointer;
|
|
481
|
+
display: flex;
|
|
482
|
+
align-items: center;
|
|
483
|
+
justify-content: center;
|
|
484
|
+
border-radius: var(--radius-md, 0.375rem);
|
|
485
|
+
transition: background var(--transition-fast, 150ms ease);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.chatbot__header-btn:hover {
|
|
489
|
+
background: rgba(255, 255, 255, 0.2);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/* Messages */
|
|
493
|
+
.chatbot__messages {
|
|
494
|
+
flex: 1;
|
|
495
|
+
overflow-y: auto;
|
|
496
|
+
padding: var(--space-md, 1rem);
|
|
497
|
+
display: flex;
|
|
498
|
+
flex-direction: column;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/* Typing indicator */
|
|
502
|
+
.chatbot__typing {
|
|
503
|
+
display: flex;
|
|
504
|
+
gap: 4px;
|
|
505
|
+
padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
|
|
506
|
+
background: var(--color-bg-muted, #f3f4f6);
|
|
507
|
+
border-radius: var(--radius-lg, 0.75rem);
|
|
508
|
+
width: fit-content;
|
|
509
|
+
margin-bottom: var(--space-sm, 0.5rem);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.chatbot__typing-dot {
|
|
513
|
+
width: 8px;
|
|
514
|
+
height: 8px;
|
|
515
|
+
border-radius: var(--radius-full, 9999px);
|
|
516
|
+
background: var(--color-text-muted, #6b7280);
|
|
517
|
+
animation: typing 1.4s infinite ease-in-out both;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.chatbot__typing-dot:nth-child(1) {
|
|
521
|
+
animation-delay: 0s;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.chatbot__typing-dot:nth-child(2) {
|
|
525
|
+
animation-delay: 0.2s;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.chatbot__typing-dot:nth-child(3) {
|
|
529
|
+
animation-delay: 0.4s;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
@keyframes typing {
|
|
533
|
+
0%,
|
|
534
|
+
80%,
|
|
535
|
+
100% {
|
|
536
|
+
transform: scale(0.6);
|
|
537
|
+
opacity: 0.4;
|
|
538
|
+
}
|
|
539
|
+
40% {
|
|
540
|
+
transform: scale(1);
|
|
541
|
+
opacity: 1;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/* Quick Actions */
|
|
546
|
+
.chatbot__actions {
|
|
547
|
+
padding: 0 var(--space-md, 1rem);
|
|
548
|
+
padding-bottom: var(--space-sm, 0.5rem);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.chatbot__action-btn {
|
|
552
|
+
padding: var(--space-xs, 0.25rem) var(--space-sm, 0.5rem);
|
|
553
|
+
border: 1px solid var(--color-border, #e5e7eb);
|
|
554
|
+
border-radius: var(--radius-full, 9999px);
|
|
555
|
+
background: transparent;
|
|
556
|
+
color: var(--color-text-muted, #6b7280);
|
|
557
|
+
font-size: var(--text-xs, 0.75rem);
|
|
558
|
+
cursor: pointer;
|
|
559
|
+
transition: all var(--transition-fast, 150ms ease);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.chatbot__action-btn:hover {
|
|
563
|
+
background: var(--color-bg-muted, #f3f4f6);
|
|
564
|
+
border-color: var(--color-border-hover, #d1d5db);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/* Input */
|
|
568
|
+
.chatbot__input-container {
|
|
569
|
+
display: flex;
|
|
570
|
+
align-items: flex-end;
|
|
571
|
+
gap: var(--space-sm, 0.5rem);
|
|
572
|
+
padding: var(--space-md, 1rem);
|
|
573
|
+
border-top: 1px solid var(--color-border, #e5e7eb);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.chatbot__input {
|
|
577
|
+
flex: 1;
|
|
578
|
+
min-height: 2.5rem;
|
|
579
|
+
max-height: 120px;
|
|
580
|
+
padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
|
|
581
|
+
border: 1px solid var(--color-border, #e5e7eb);
|
|
582
|
+
border-radius: var(--radius-lg, 0.75rem);
|
|
583
|
+
background: var(--color-bg, #ffffff);
|
|
584
|
+
color: var(--color-text, #1f2937);
|
|
585
|
+
font-family: inherit;
|
|
586
|
+
font-size: var(--text-sm, 0.875rem);
|
|
587
|
+
line-height: 1.5;
|
|
588
|
+
resize: none;
|
|
589
|
+
transition: border-color var(--transition-fast, 150ms ease);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.chatbot__input:focus {
|
|
593
|
+
outline: none;
|
|
594
|
+
border-color: var(--color-primary, #3b82f6);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.chatbot__input::placeholder {
|
|
598
|
+
color: var(--color-text-muted, #9ca3af);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.chatbot__send-btn {
|
|
602
|
+
width: 2.5rem;
|
|
603
|
+
height: 2.5rem;
|
|
604
|
+
border: none;
|
|
605
|
+
border-radius: var(--radius-full, 9999px);
|
|
606
|
+
background: var(--color-primary, #3b82f6);
|
|
607
|
+
color: var(--color-text-inverse, #ffffff);
|
|
608
|
+
cursor: pointer;
|
|
609
|
+
display: flex;
|
|
610
|
+
align-items: center;
|
|
611
|
+
justify-content: center;
|
|
612
|
+
transition: all var(--transition-fast, 150ms ease);
|
|
613
|
+
flex-shrink: 0;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.chatbot__send-btn:hover:not(:disabled) {
|
|
617
|
+
background: var(--color-primary-hover, #2563eb);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.chatbot__send-btn:disabled {
|
|
621
|
+
opacity: 0.5;
|
|
622
|
+
cursor: not-allowed;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/* Mobile */
|
|
626
|
+
@media (max-width: 480px) {
|
|
627
|
+
.chatbot--bottom-right,
|
|
628
|
+
.chatbot--bottom-left {
|
|
629
|
+
bottom: var(--space-md, 1rem);
|
|
630
|
+
right: var(--space-md, 1rem);
|
|
631
|
+
left: var(--space-md, 1rem);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.chatbot__window {
|
|
635
|
+
width: 100%;
|
|
636
|
+
max-width: none;
|
|
637
|
+
height: calc(100vh - 6rem);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ChatMessage as ChatMessageType, LeadData } from './types.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
initialMessages?: ChatMessageType[];
|
|
4
|
+
sessionId?: string;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
welcomeMessage?: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
defaultOpen?: boolean;
|
|
10
|
+
inactivityTimeout?: number;
|
|
11
|
+
showTypingIndicator?: boolean;
|
|
12
|
+
position?: 'bottom-right' | 'bottom-left';
|
|
13
|
+
class?: string;
|
|
14
|
+
onSendMessage?: (message: string, sessionId: string) => Promise<string | null>;
|
|
15
|
+
onEscalate?: (sessionId: string, messages: ChatMessageType[]) => Promise<void>;
|
|
16
|
+
onLeadCapture?: (data: LeadData, sessionId: string) => Promise<void>;
|
|
17
|
+
onOpen?: () => void;
|
|
18
|
+
onClose?: () => void;
|
|
19
|
+
}
|
|
20
|
+
declare const Chatbot: import("svelte").Component<Props, {}, "">;
|
|
21
|
+
type Chatbot = ReturnType<typeof Chatbot>;
|
|
22
|
+
export default Chatbot;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type MessageRole = 'user' | 'assistant' | 'system';
|
|
2
|
+
export interface ChatMessage {
|
|
3
|
+
id: string;
|
|
4
|
+
role: MessageRole;
|
|
5
|
+
content: string;
|
|
6
|
+
timestamp?: Date;
|
|
7
|
+
}
|
|
8
|
+
export interface LeadData {
|
|
9
|
+
name?: string;
|
|
10
|
+
email?: string;
|
|
11
|
+
phone?: string;
|
|
12
|
+
company?: string;
|
|
13
|
+
[key: string]: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface ChatbotProps {
|
|
16
|
+
/** Initial messages to display */
|
|
17
|
+
initialMessages?: ChatMessage[];
|
|
18
|
+
/** Session ID for conversation tracking */
|
|
19
|
+
sessionId?: string;
|
|
20
|
+
/** Placeholder text for input */
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
/** Welcome message shown when chat opens */
|
|
23
|
+
welcomeMessage?: string;
|
|
24
|
+
/** Title shown in header */
|
|
25
|
+
title?: string;
|
|
26
|
+
/** Subtitle shown in header */
|
|
27
|
+
subtitle?: string;
|
|
28
|
+
/** Whether chat starts open */
|
|
29
|
+
defaultOpen?: boolean;
|
|
30
|
+
/** Inactivity timeout in ms before auto-opening (0 to disable) */
|
|
31
|
+
inactivityTimeout?: number;
|
|
32
|
+
/** Whether to show typing indicator when waiting for response */
|
|
33
|
+
showTypingIndicator?: boolean;
|
|
34
|
+
/** Position of the chat widget */
|
|
35
|
+
position?: 'bottom-right' | 'bottom-left';
|
|
36
|
+
/** Custom class */
|
|
37
|
+
class?: string;
|
|
38
|
+
/** Called when user sends a message. Should return AI response or null */
|
|
39
|
+
onSendMessage?: (message: string, sessionId: string) => Promise<string | null>;
|
|
40
|
+
/** Called when user requests human agent */
|
|
41
|
+
onEscalate?: (sessionId: string, messages: ChatMessage[]) => Promise<void>;
|
|
42
|
+
/** Called when lead data is captured */
|
|
43
|
+
onLeadCapture?: (data: LeadData, sessionId: string) => Promise<void>;
|
|
44
|
+
/** Called when chat is opened */
|
|
45
|
+
onOpen?: () => void;
|
|
46
|
+
/** Called when chat is closed */
|
|
47
|
+
onClose?: () => void;
|
|
48
|
+
}
|