test-chat-component-per 1.0.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/LICENSE +29 -0
- package/README.md +298 -0
- package/dist/delachat-webcomponent.js +683 -0
- package/dist/delachat-webcomponent.min.js +1 -0
- package/dist/delachat-webcomponent.min.js.map +1 -0
- package/package.json +66 -0
- package/src/delachat-webcomponent.js +683 -0
- package/src/delachat.css +430 -0
- package/src/delachat.js +580 -0
- package/src/index.html +343 -0
- package/src/webcomponent-demo.html +452 -0
- package/types/index.d.ts +130 -0
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DelaChat Web Component
|
|
3
|
+
* A standards-based custom element for chat functionality
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* <dela-chat
|
|
7
|
+
* api-url="https://localhost:5001"
|
|
8
|
+
* session-uid="your-session"
|
|
9
|
+
* datasource="EPMSCA"
|
|
10
|
+
* product-id="200"
|
|
11
|
+
* token="your-token">
|
|
12
|
+
* </dela-chat>
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
class DelaChatComponent extends HTMLElement {
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
|
|
19
|
+
// Attach shadow DOM for style encapsulation
|
|
20
|
+
this.attachShadow({ mode: 'open' });
|
|
21
|
+
|
|
22
|
+
// State
|
|
23
|
+
this.state = {
|
|
24
|
+
messages: [],
|
|
25
|
+
isLoading: false,
|
|
26
|
+
isOpen: true,
|
|
27
|
+
error: null
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Observed attributes (auto-updates when changed)
|
|
32
|
+
static get observedAttributes() {
|
|
33
|
+
return ['api-url', 'session-uid', 'datasource', 'product-id', 'token', 'theme', 'height', 'width'];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Called when element is added to DOM
|
|
37
|
+
connectedCallback() {
|
|
38
|
+
this.render();
|
|
39
|
+
this.attachEventListeners();
|
|
40
|
+
this.loadHistory();
|
|
41
|
+
|
|
42
|
+
// Emit ready event
|
|
43
|
+
this.dispatchEvent(new CustomEvent('ready', {
|
|
44
|
+
bubbles: true,
|
|
45
|
+
composed: true
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Called when attributes change
|
|
50
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
51
|
+
if (oldValue !== newValue && this.shadowRoot.innerHTML) {
|
|
52
|
+
this.render();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Get configuration from attributes
|
|
57
|
+
get config() {
|
|
58
|
+
return {
|
|
59
|
+
apiUrl: this.getAttribute('api-url'),
|
|
60
|
+
sessionUid: this.getAttribute('session-uid'),
|
|
61
|
+
datasource: this.getAttribute('datasource'),
|
|
62
|
+
productId: parseInt(this.getAttribute('product-id')),
|
|
63
|
+
token: this.getAttribute('token'),
|
|
64
|
+
theme: this.getAttribute('theme') || 'light',
|
|
65
|
+
height: this.getAttribute('height') || '600px',
|
|
66
|
+
width: this.getAttribute('width') || '100%'
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Render component
|
|
71
|
+
render() {
|
|
72
|
+
const config = this.config;
|
|
73
|
+
|
|
74
|
+
this.shadowRoot.innerHTML = `
|
|
75
|
+
<style>
|
|
76
|
+
${this.getStyles()}
|
|
77
|
+
</style>
|
|
78
|
+
|
|
79
|
+
<div class="chat-window" data-theme="${config.theme}" style="height: ${config.height}; width: ${config.width};">
|
|
80
|
+
<!-- Header -->
|
|
81
|
+
<div class="chat-header">
|
|
82
|
+
<h2 class="chat-title">Ask Dela</h2>
|
|
83
|
+
<div class="chat-header-buttons">
|
|
84
|
+
<button class="btn-icon" data-action="disclaimer" title="Disclaimer">ℹ</button>
|
|
85
|
+
<button class="btn-icon" data-action="new-chat" title="New Chat">+</button>
|
|
86
|
+
<button class="btn-icon" data-action="help" title="Help">?</button>
|
|
87
|
+
<button class="btn-icon" data-action="close" title="Close">✕</button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!-- Disclaimer -->
|
|
92
|
+
<div class="disclaimer" style="display: none;">
|
|
93
|
+
<p>Dela is an AI-powered assistant. While I strive to provide accurate information, please verify important details independently.</p>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<!-- Error message -->
|
|
97
|
+
<div class="error" style="display: none;"></div>
|
|
98
|
+
|
|
99
|
+
<!-- Messages area -->
|
|
100
|
+
<div class="messages">
|
|
101
|
+
<div class="welcome">
|
|
102
|
+
<div class="ai-avatar-large">🤖</div>
|
|
103
|
+
<p>Hello! I'm Dela, your AI assistant. How can I help you today?</p>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Loading indicator -->
|
|
108
|
+
<div class="loading" style="display: none;">
|
|
109
|
+
<div class="loading-dots">
|
|
110
|
+
<span></span>
|
|
111
|
+
<span></span>
|
|
112
|
+
<span></span>
|
|
113
|
+
</div>
|
|
114
|
+
<span>Dela is thinking...</span>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<!-- Input area -->
|
|
118
|
+
<div class="input-container">
|
|
119
|
+
<div class="input-wrapper">
|
|
120
|
+
<textarea class="input" placeholder="Type your message..." rows="1"></textarea>
|
|
121
|
+
<button class="btn-send" data-action="send" title="Send message">➤</button>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Attach event listeners
|
|
129
|
+
attachEventListeners() {
|
|
130
|
+
const shadow = this.shadowRoot;
|
|
131
|
+
const input = shadow.querySelector('.input');
|
|
132
|
+
const btnSend = shadow.querySelector('[data-action="send"]');
|
|
133
|
+
const btnClose = shadow.querySelector('[data-action="close"]');
|
|
134
|
+
const btnDisclaimer = shadow.querySelector('[data-action="disclaimer"]');
|
|
135
|
+
const btnNewChat = shadow.querySelector('[data-action="new-chat"]');
|
|
136
|
+
const btnHelp = shadow.querySelector('[data-action="help"]');
|
|
137
|
+
|
|
138
|
+
// Send button
|
|
139
|
+
btnSend.addEventListener('click', () => this.handleSend());
|
|
140
|
+
|
|
141
|
+
// Enter key
|
|
142
|
+
input.addEventListener('keydown', (e) => {
|
|
143
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
this.handleSend();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Auto-resize textarea
|
|
150
|
+
input.addEventListener('input', function() {
|
|
151
|
+
this.style.height = 'auto';
|
|
152
|
+
this.style.height = (this.scrollHeight) + 'px';
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Close button
|
|
156
|
+
btnClose.addEventListener('click', () => {
|
|
157
|
+
this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }));
|
|
158
|
+
this.style.display = 'none';
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Disclaimer toggle
|
|
162
|
+
btnDisclaimer.addEventListener('click', () => {
|
|
163
|
+
const disclaimer = shadow.querySelector('.disclaimer');
|
|
164
|
+
disclaimer.style.display = disclaimer.style.display === 'none' ? 'block' : 'none';
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// New chat
|
|
168
|
+
btnNewChat.addEventListener('click', () => {
|
|
169
|
+
if (confirm('Start a new conversation? This will clear the current chat.')) {
|
|
170
|
+
this.clearHistory();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Help
|
|
175
|
+
btnHelp.addEventListener('click', () => {
|
|
176
|
+
alert('DelaChat Help\n\nType your question and press Enter or click Send.');
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Handle send message
|
|
181
|
+
handleSend() {
|
|
182
|
+
const input = this.shadowRoot.querySelector('.input');
|
|
183
|
+
const message = input.value.trim();
|
|
184
|
+
|
|
185
|
+
if (!message || this.state.isLoading) return;
|
|
186
|
+
|
|
187
|
+
this.sendMessage(message);
|
|
188
|
+
input.value = '';
|
|
189
|
+
input.style.height = 'auto';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Load chat history
|
|
193
|
+
async loadHistory() {
|
|
194
|
+
this.setLoading(true);
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const data = await this.apiRequest('GET', '/delachat/history?limit=50&offset=0');
|
|
198
|
+
this.state.messages = data.records || [];
|
|
199
|
+
this.renderMessages();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
this.handleError(error);
|
|
202
|
+
} finally {
|
|
203
|
+
this.setLoading(false);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Send message
|
|
208
|
+
async sendMessage(message) {
|
|
209
|
+
this.setLoading(true);
|
|
210
|
+
this.hideWelcome();
|
|
211
|
+
|
|
212
|
+
// Add to UI immediately
|
|
213
|
+
const tempMsg = {
|
|
214
|
+
historyId: 'temp-' + Date.now(),
|
|
215
|
+
message: message,
|
|
216
|
+
source: 0,
|
|
217
|
+
created: new Date().toISOString(),
|
|
218
|
+
userId: this.config.userName || 'User',
|
|
219
|
+
isUserMessage: true
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
this.state.messages.unshift(tempMsg);
|
|
223
|
+
this.renderMessages();
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
// Send to API
|
|
227
|
+
const data = await this.apiRequest('POST', '/delachat/history', {
|
|
228
|
+
message: message,
|
|
229
|
+
source: 0,
|
|
230
|
+
productUid: this.config.productId
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Replace temp with real message
|
|
234
|
+
this.state.messages[0] = data.record;
|
|
235
|
+
this.renderMessages();
|
|
236
|
+
|
|
237
|
+
// Emit event
|
|
238
|
+
this.dispatchEvent(new CustomEvent('message-sent', {
|
|
239
|
+
bubbles: true,
|
|
240
|
+
composed: true,
|
|
241
|
+
detail: { message }
|
|
242
|
+
}));
|
|
243
|
+
|
|
244
|
+
// Simulate AI response
|
|
245
|
+
this.simulateAIResponse(message);
|
|
246
|
+
|
|
247
|
+
} catch (error) {
|
|
248
|
+
this.handleError(error);
|
|
249
|
+
this.setLoading(false);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Simulate AI response (replace with real AI integration)
|
|
254
|
+
async simulateAIResponse(userMessage) {
|
|
255
|
+
setTimeout(async () => {
|
|
256
|
+
const aiResponse = `I received your message: "${userMessage}". This is a simulated response.`;
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const data = await this.apiRequest('POST', '/delachat/history', {
|
|
260
|
+
message: aiResponse,
|
|
261
|
+
source: 1,
|
|
262
|
+
productUid: this.config.productId
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
this.state.messages.unshift(data.record);
|
|
266
|
+
this.renderMessages();
|
|
267
|
+
this.setLoading(false);
|
|
268
|
+
|
|
269
|
+
// Emit event
|
|
270
|
+
this.dispatchEvent(new CustomEvent('message-received', {
|
|
271
|
+
bubbles: true,
|
|
272
|
+
composed: true,
|
|
273
|
+
detail: { message: aiResponse }
|
|
274
|
+
}));
|
|
275
|
+
|
|
276
|
+
} catch (error) {
|
|
277
|
+
this.handleError(error);
|
|
278
|
+
this.setLoading(false);
|
|
279
|
+
}
|
|
280
|
+
}, 1500);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Render messages
|
|
284
|
+
renderMessages() {
|
|
285
|
+
const messagesContainer = this.shadowRoot.querySelector('.messages');
|
|
286
|
+
const messagesHtml = this.state.messages.map(msg => this.renderMessage(msg)).join('');
|
|
287
|
+
messagesContainer.innerHTML = messagesHtml;
|
|
288
|
+
this.scrollToBottom();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Render single message
|
|
292
|
+
renderMessage(msg) {
|
|
293
|
+
const isUser = msg.source === 0;
|
|
294
|
+
const avatarIcon = isUser ? '👤' : '🤖';
|
|
295
|
+
const messageClass = isUser ? 'message-user' : 'message-ai';
|
|
296
|
+
|
|
297
|
+
const actions = !isUser ? `
|
|
298
|
+
<div class="message-actions">
|
|
299
|
+
<button onclick="this.getRootNode().host.copyMessage('${msg.historyId}')" class="btn-action">
|
|
300
|
+
📋 Copy
|
|
301
|
+
</button>
|
|
302
|
+
</div>
|
|
303
|
+
` : '';
|
|
304
|
+
|
|
305
|
+
return `
|
|
306
|
+
<div class="message ${messageClass}" data-id="${msg.historyId}">
|
|
307
|
+
<div class="avatar">${avatarIcon}</div>
|
|
308
|
+
<div class="message-content">
|
|
309
|
+
<div class="message-bubble">${this.escapeHtml(msg.message)}</div>
|
|
310
|
+
${actions}
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Public methods
|
|
317
|
+
copyMessage(messageId) {
|
|
318
|
+
const msg = this.state.messages.find(m => m.historyId === messageId);
|
|
319
|
+
if (msg && navigator.clipboard) {
|
|
320
|
+
navigator.clipboard.writeText(msg.message);
|
|
321
|
+
alert('Message copied!');
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
clearHistory() {
|
|
326
|
+
this.state.messages = [];
|
|
327
|
+
this.renderMessages();
|
|
328
|
+
this.shadowRoot.querySelector('.welcome').style.display = 'block';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
open() {
|
|
332
|
+
this.style.display = 'block';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
close() {
|
|
336
|
+
this.style.display = 'none';
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Utility methods
|
|
340
|
+
async apiRequest(method, endpoint, data = null) {
|
|
341
|
+
const config = this.config;
|
|
342
|
+
const url = config.apiUrl + endpoint;
|
|
343
|
+
|
|
344
|
+
const response = await fetch(url, {
|
|
345
|
+
method,
|
|
346
|
+
headers: {
|
|
347
|
+
'Content-Type': 'application/json',
|
|
348
|
+
'X-SessionUid': config.sessionUid,
|
|
349
|
+
'X-Datasource': config.datasource,
|
|
350
|
+
'X-ProductId': config.productId.toString(),
|
|
351
|
+
'X-RequestVerificationToken': config.token
|
|
352
|
+
},
|
|
353
|
+
body: data ? JSON.stringify(data) : null
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
if (!response.ok) {
|
|
357
|
+
if (response.status === 401) {
|
|
358
|
+
throw new Error('Unauthorized: Session expired or invalid token');
|
|
359
|
+
}
|
|
360
|
+
throw new Error(`Request failed: ${response.status}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return response.json();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
setLoading(isLoading) {
|
|
367
|
+
this.state.isLoading = isLoading;
|
|
368
|
+
const loading = this.shadowRoot.querySelector('.loading');
|
|
369
|
+
const input = this.shadowRoot.querySelector('.input');
|
|
370
|
+
const btnSend = this.shadowRoot.querySelector('[data-action="send"]');
|
|
371
|
+
|
|
372
|
+
loading.style.display = isLoading ? 'flex' : 'none';
|
|
373
|
+
input.disabled = isLoading;
|
|
374
|
+
btnSend.disabled = isLoading;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
handleError(error) {
|
|
378
|
+
this.state.error = error.message;
|
|
379
|
+
const errorEl = this.shadowRoot.querySelector('.error');
|
|
380
|
+
errorEl.textContent = error.message;
|
|
381
|
+
errorEl.style.display = 'block';
|
|
382
|
+
|
|
383
|
+
// Emit error event
|
|
384
|
+
this.dispatchEvent(new CustomEvent('error', {
|
|
385
|
+
bubbles: true,
|
|
386
|
+
composed: true,
|
|
387
|
+
detail: { error }
|
|
388
|
+
}));
|
|
389
|
+
|
|
390
|
+
// Auto-hide after 5 seconds
|
|
391
|
+
setTimeout(() => {
|
|
392
|
+
errorEl.style.display = 'none';
|
|
393
|
+
}, 5000);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
hideWelcome() {
|
|
397
|
+
const welcome = this.shadowRoot.querySelector('.welcome');
|
|
398
|
+
if (welcome) welcome.style.display = 'none';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
scrollToBottom() {
|
|
402
|
+
const messages = this.shadowRoot.querySelector('.messages');
|
|
403
|
+
messages.scrollTop = messages.scrollHeight;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
escapeHtml(text) {
|
|
407
|
+
const div = document.createElement('div');
|
|
408
|
+
div.textContent = text;
|
|
409
|
+
return div.innerHTML.replace(/\n/g, '<br>');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Styles (inline for encapsulation)
|
|
413
|
+
getStyles() {
|
|
414
|
+
return `
|
|
415
|
+
:host {
|
|
416
|
+
display: block;
|
|
417
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
418
|
+
font-size: 14px;
|
|
419
|
+
color: #1F2937;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.chat-window {
|
|
423
|
+
display: flex;
|
|
424
|
+
flex-direction: column;
|
|
425
|
+
background: #FFFFFF;
|
|
426
|
+
border-radius: 12px;
|
|
427
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
428
|
+
overflow: hidden;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.chat-header {
|
|
432
|
+
background: #7C3AED;
|
|
433
|
+
color: white;
|
|
434
|
+
padding: 16px;
|
|
435
|
+
display: flex;
|
|
436
|
+
align-items: center;
|
|
437
|
+
justify-content: space-between;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.chat-title {
|
|
441
|
+
font-size: 18px;
|
|
442
|
+
font-weight: 600;
|
|
443
|
+
margin: 0;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.chat-header-buttons {
|
|
447
|
+
display: flex;
|
|
448
|
+
gap: 8px;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.btn-icon {
|
|
452
|
+
background: transparent;
|
|
453
|
+
border: none;
|
|
454
|
+
color: white;
|
|
455
|
+
cursor: pointer;
|
|
456
|
+
padding: 6px;
|
|
457
|
+
border-radius: 6px;
|
|
458
|
+
width: 32px;
|
|
459
|
+
height: 32px;
|
|
460
|
+
font-size: 16px;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.btn-icon:hover {
|
|
464
|
+
background: #6D28D9;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.disclaimer {
|
|
468
|
+
background: #FEF3C7;
|
|
469
|
+
border-bottom: 1px solid #F59E0B;
|
|
470
|
+
padding: 12px 16px;
|
|
471
|
+
font-size: 13px;
|
|
472
|
+
color: #92400E;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.error {
|
|
476
|
+
background: #FEE2E2;
|
|
477
|
+
border-bottom: 1px solid #EF4444;
|
|
478
|
+
padding: 12px 16px;
|
|
479
|
+
font-size: 13px;
|
|
480
|
+
color: #991B1B;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.messages {
|
|
484
|
+
flex: 1;
|
|
485
|
+
overflow-y: auto;
|
|
486
|
+
padding: 16px;
|
|
487
|
+
display: flex;
|
|
488
|
+
flex-direction: column-reverse;
|
|
489
|
+
gap: 16px;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.welcome {
|
|
493
|
+
display: flex;
|
|
494
|
+
flex-direction: column;
|
|
495
|
+
align-items: center;
|
|
496
|
+
padding: 40px 20px;
|
|
497
|
+
text-align: center;
|
|
498
|
+
color: #6B7280;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.ai-avatar-large {
|
|
502
|
+
width: 64px;
|
|
503
|
+
height: 64px;
|
|
504
|
+
background: #7C3AED;
|
|
505
|
+
border-radius: 50%;
|
|
506
|
+
display: flex;
|
|
507
|
+
align-items: center;
|
|
508
|
+
justify-content: center;
|
|
509
|
+
font-size: 32px;
|
|
510
|
+
margin-bottom: 16px;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.message {
|
|
514
|
+
display: flex;
|
|
515
|
+
gap: 12px;
|
|
516
|
+
align-items: flex-start;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.message-user {
|
|
520
|
+
flex-direction: row-reverse;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.avatar {
|
|
524
|
+
width: 32px;
|
|
525
|
+
height: 32px;
|
|
526
|
+
background: #6B7280;
|
|
527
|
+
border-radius: 50%;
|
|
528
|
+
display: flex;
|
|
529
|
+
align-items: center;
|
|
530
|
+
justify-content: center;
|
|
531
|
+
font-size: 16px;
|
|
532
|
+
flex-shrink: 0;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.message-user .avatar {
|
|
536
|
+
background: #7C3AED;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.message-content {
|
|
540
|
+
flex: 1;
|
|
541
|
+
display: flex;
|
|
542
|
+
flex-direction: column;
|
|
543
|
+
gap: 6px;
|
|
544
|
+
max-width: 75%;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.message-user .message-content {
|
|
548
|
+
align-items: flex-end;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.message-bubble {
|
|
552
|
+
padding: 12px 16px;
|
|
553
|
+
border-radius: 12px;
|
|
554
|
+
line-height: 1.5;
|
|
555
|
+
word-wrap: break-word;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.message-ai .message-bubble {
|
|
559
|
+
background: #F3F4F6;
|
|
560
|
+
border-bottom-left-radius: 4px;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.message-user .message-bubble {
|
|
564
|
+
background: #7C3AED;
|
|
565
|
+
color: white;
|
|
566
|
+
border-bottom-right-radius: 4px;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.message-actions {
|
|
570
|
+
display: flex;
|
|
571
|
+
gap: 8px;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.btn-action {
|
|
575
|
+
background: transparent;
|
|
576
|
+
border: none;
|
|
577
|
+
color: #6B7280;
|
|
578
|
+
cursor: pointer;
|
|
579
|
+
padding: 4px 8px;
|
|
580
|
+
border-radius: 4px;
|
|
581
|
+
font-size: 12px;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.btn-action:hover {
|
|
585
|
+
background: #F9FAFB;
|
|
586
|
+
color: #7C3AED;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.loading {
|
|
590
|
+
display: flex;
|
|
591
|
+
align-items: center;
|
|
592
|
+
justify-content: center;
|
|
593
|
+
gap: 8px;
|
|
594
|
+
padding: 12px;
|
|
595
|
+
color: #6B7280;
|
|
596
|
+
font-size: 13px;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.loading-dots {
|
|
600
|
+
display: flex;
|
|
601
|
+
gap: 4px;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.loading-dots span {
|
|
605
|
+
width: 6px;
|
|
606
|
+
height: 6px;
|
|
607
|
+
background: #7C3AED;
|
|
608
|
+
border-radius: 50%;
|
|
609
|
+
animation: bounce 1.4s infinite ease-in-out both;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.loading-dots span:nth-child(2) {
|
|
613
|
+
animation-delay: -0.16s;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.loading-dots span:nth-child(3) {
|
|
617
|
+
animation-delay: -0.32s;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
@keyframes bounce {
|
|
621
|
+
0%, 80%, 100% { transform: scale(0); }
|
|
622
|
+
40% { transform: scale(1); }
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.input-container {
|
|
626
|
+
padding: 16px;
|
|
627
|
+
background: #F9FAFB;
|
|
628
|
+
border-top: 1px solid #E5E7EB;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.input-wrapper {
|
|
632
|
+
display: flex;
|
|
633
|
+
gap: 8px;
|
|
634
|
+
align-items: flex-end;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.input {
|
|
638
|
+
flex: 1;
|
|
639
|
+
padding: 10px 12px;
|
|
640
|
+
border: 1px solid #E5E7EB;
|
|
641
|
+
border-radius: 8px;
|
|
642
|
+
font-family: inherit;
|
|
643
|
+
font-size: 14px;
|
|
644
|
+
resize: none;
|
|
645
|
+
max-height: 120px;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.input:focus {
|
|
649
|
+
outline: none;
|
|
650
|
+
border-color: #7C3AED;
|
|
651
|
+
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.input:disabled {
|
|
655
|
+
background: #F9FAFB;
|
|
656
|
+
cursor: not-allowed;
|
|
657
|
+
opacity: 0.6;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.btn-send {
|
|
661
|
+
background: #7C3AED;
|
|
662
|
+
color: white;
|
|
663
|
+
border: none;
|
|
664
|
+
border-radius: 8px;
|
|
665
|
+
padding: 10px 16px;
|
|
666
|
+
cursor: pointer;
|
|
667
|
+
font-size: 16px;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.btn-send:hover:not(:disabled) {
|
|
671
|
+
background: #6D28D9;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
.btn-send:disabled {
|
|
675
|
+
opacity: 0.5;
|
|
676
|
+
cursor: not-allowed;
|
|
677
|
+
}
|
|
678
|
+
`;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Define the custom element
|
|
683
|
+
customElements.define('dela-chat', DelaChatComponent);
|