mini-chat-bot-widget 0.2.0 → 0.8.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.
- package/dist/chat-widget.esm.js +2860 -80
- package/dist/chat-widget.esm.min.js +1 -1
- package/dist/chat-widget.umd.js +2860 -80
- package/dist/chat-widget.umd.min.js +1 -1
- package/index.html +56 -0
- package/managed_context/metadata.json +1 -0
- package/package.json +13 -7
- package/src/audio-chat-screen.js +1408 -0
- package/src/bhashiniApi.js +363 -0
- package/src/chat-widget.js +2490 -86
- package/src/text-chat-screen.js +1286 -0
- package/test_suite_analysis/metadata.json +1 -0
|
@@ -0,0 +1,1286 @@
|
|
|
1
|
+
// text-chat-screen.js - Text chat screen component
|
|
2
|
+
|
|
3
|
+
class TextChatScreen {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.placeholder = options.placeholder || "Type your message...";
|
|
6
|
+
this.primaryColor = options.primaryColor || "#1a5c4b";
|
|
7
|
+
this.title = options.title || "Chat Assistant";
|
|
8
|
+
this.onMessage = options.onMessage || (() => { });
|
|
9
|
+
this.onBack = options.onBack || (() => { });
|
|
10
|
+
this.onOpenDrawer = options.onOpenDrawer || (() => { });
|
|
11
|
+
this.onClose = options.onClose || (() => { });
|
|
12
|
+
this.container = null;
|
|
13
|
+
this.messages = options.messages
|
|
14
|
+
this.sendMessage = options.sendMessage || null;
|
|
15
|
+
this.contentBlocks = options.contentBlocks || [];
|
|
16
|
+
this.onContentBlocksChange = options.onContentBlocksChange || (() => { });
|
|
17
|
+
this.navigateToAudioScreen = options.navigateToAudioScreen
|
|
18
|
+
// Supported file types
|
|
19
|
+
this.SUPPORTED_FILE_TYPES = [
|
|
20
|
+
"image/jpeg",
|
|
21
|
+
"image/png",
|
|
22
|
+
"image/gif",
|
|
23
|
+
"image/webp",
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
render(container) {
|
|
28
|
+
this.container = container;
|
|
29
|
+
this._applyStyles();
|
|
30
|
+
container.innerHTML = `
|
|
31
|
+
<div class="text-chat-screen">
|
|
32
|
+
<div class="chat-header">
|
|
33
|
+
<div class="chat-header-content">
|
|
34
|
+
<button class="chat-back" id="text-chat-menu">
|
|
35
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-menu-icon lucide-menu"><path d="M4 5h16"/><path d="M4 12h16"/><path d="M4 19h16"/></svg>
|
|
36
|
+
</button>
|
|
37
|
+
|
|
38
|
+
<div class="chat-header-text">
|
|
39
|
+
<div class="chat-title">${this.title}</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<button class="chat-close" id="navigate-to-audio-screen">
|
|
43
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mic-icon lucide-mic"><path d="M12 19v3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><rect x="9" y="2" width="6" height="13" rx="3"/></svg>
|
|
44
|
+
</button>
|
|
45
|
+
​ ​ ​
|
|
46
|
+
<button class="chat-close" id="text-chat-close">
|
|
47
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
48
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
49
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
50
|
+
</svg>
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="chat-messages" id="chat-messages">
|
|
54
|
+
<div class="chat-welcome">
|
|
55
|
+
<div class="welcome-text">👋 Hello! I'm your AI assistant. How can I help you today?</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="file-attachments-container" id="file-attachments-container" style="display: none;"></div>
|
|
59
|
+
<div class="chat-input-wrapper">
|
|
60
|
+
<div class="file-upload-controls">
|
|
61
|
+
<input
|
|
62
|
+
type="file"
|
|
63
|
+
id="file-upload-input"
|
|
64
|
+
accept="image/jpeg,image/png,image/gif,image/webp"
|
|
65
|
+
style="display: none;"
|
|
66
|
+
/>
|
|
67
|
+
<button id="file-upload-button" class="file-upload-button" title="Attach files">
|
|
68
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
69
|
+
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
|
|
70
|
+
</svg>
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
<input type="text" id="chat-input" placeholder="${this.placeholder}" autocomplete="off" />
|
|
74
|
+
<button id="chat-send" disabled>
|
|
75
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
76
|
+
<line x1="22" y1="2" x2="11" y2="13"></line>
|
|
77
|
+
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
|
78
|
+
</svg>
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
// Bind header events
|
|
85
|
+
const menuBtn = container.querySelector("#text-chat-menu");
|
|
86
|
+
const closeBtn = container.querySelector("#text-chat-close");
|
|
87
|
+
const navigateToAudioBtn = container.querySelector("#navigate-to-audio-screen")
|
|
88
|
+
|
|
89
|
+
if (menuBtn) {
|
|
90
|
+
menuBtn.addEventListener("click", () => {
|
|
91
|
+
if (this.onOpenDrawer) {
|
|
92
|
+
this.onOpenDrawer();
|
|
93
|
+
} else if (this.onBack) {
|
|
94
|
+
this.onBack();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (closeBtn) {
|
|
100
|
+
closeBtn.addEventListener("click", () => {
|
|
101
|
+
if (this.onClose) this.onClose();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (navigateToAudioBtn) {
|
|
106
|
+
navigateToAudioBtn.addEventListener("click", () => {
|
|
107
|
+
if (this.navigateToAudioScreen) this.navigateToAudioScreen()
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Bind text chat events
|
|
112
|
+
const input = container.querySelector("#chat-input");
|
|
113
|
+
const sendBtn = container.querySelector("#chat-send");
|
|
114
|
+
const fileInput = container.querySelector("#file-upload-input");
|
|
115
|
+
const fileUploadBtn = container.querySelector("#file-upload-button");
|
|
116
|
+
|
|
117
|
+
// File upload handler
|
|
118
|
+
fileUploadBtn.addEventListener("click", () => {
|
|
119
|
+
fileInput.click();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
fileInput.addEventListener("change", (e) => {
|
|
123
|
+
this.handleFileUpload(e);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Update send button state based on input and contentBlocks
|
|
127
|
+
const updateSendButton = () => {
|
|
128
|
+
const hasText = input.value.trim().length > 0;
|
|
129
|
+
const hasFiles = this.contentBlocks.length > 0;
|
|
130
|
+
sendBtn.disabled = !(hasText || hasFiles);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
input.addEventListener("input", () => {
|
|
134
|
+
updateSendButton();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const send = async () => {
|
|
138
|
+
const text = input.value.trim();
|
|
139
|
+
if (!text && this.contentBlocks.length === 0) return;
|
|
140
|
+
|
|
141
|
+
// Add user message to UI immediately
|
|
142
|
+
this.addMessage(text || "📎 File attachments", true);
|
|
143
|
+
input.value = "";
|
|
144
|
+
sendBtn.disabled = true;
|
|
145
|
+
|
|
146
|
+
this.hideTypingIndicator();
|
|
147
|
+
|
|
148
|
+
// Use the sendMessage function from widget if available
|
|
149
|
+
if (this.sendMessage) {
|
|
150
|
+
try {
|
|
151
|
+
await this.sendMessage(text, this.contentBlocks);
|
|
152
|
+
// Content blocks are cleared in widget's sendMessage, but ensure UI is updated
|
|
153
|
+
this.contentBlocks = [];
|
|
154
|
+
this.onContentBlocksChange(this.contentBlocks);
|
|
155
|
+
this.renderFilePreview(); // Update preview
|
|
156
|
+
this.updateSendButton();
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error("Error sending message:", error);
|
|
159
|
+
this.addMessage("Sorry, I encountered an error. Please try again.", false);
|
|
160
|
+
} finally {
|
|
161
|
+
this.hideTypingIndicator();
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
// Fallback to old behavior
|
|
165
|
+
setTimeout(() => {
|
|
166
|
+
this.hideTypingIndicator();
|
|
167
|
+
this.handleUserMessage(text);
|
|
168
|
+
}, 1000 + Math.random() * 1000);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
sendBtn.addEventListener("click", send);
|
|
173
|
+
input.addEventListener("keypress", (e) => {
|
|
174
|
+
if (e.key === "Enter" && !sendBtn.disabled) send();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Focus input when screen loads
|
|
178
|
+
setTimeout(() => input.focus(), 100);
|
|
179
|
+
|
|
180
|
+
// Render file preview if contentBlocks exist
|
|
181
|
+
this.renderFilePreview();
|
|
182
|
+
|
|
183
|
+
// Sync existing messages on render
|
|
184
|
+
if (this.messages && this.messages.length > 0) {
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
this._syncMessages(this.messages);
|
|
187
|
+
}, 50);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Convert file to content block
|
|
192
|
+
fileToContentBlock(file) {
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
const reader = new FileReader();
|
|
195
|
+
|
|
196
|
+
reader.onload = () => {
|
|
197
|
+
const base64Data = reader.result.split(",")[1]; // Remove data:mime;base64, prefix
|
|
198
|
+
|
|
199
|
+
if (this.SUPPORTED_FILE_TYPES.includes(file.type)) {
|
|
200
|
+
resolve({
|
|
201
|
+
type: "image",
|
|
202
|
+
source_type: "base64",
|
|
203
|
+
mime_type: file.type,
|
|
204
|
+
data: base64Data,
|
|
205
|
+
metadata: {
|
|
206
|
+
name: file.name,
|
|
207
|
+
size: file.size,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
} else {
|
|
211
|
+
reject(new Error(`Unsupported file type: ${file.type}`));
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
reader.onerror = () => {
|
|
216
|
+
reject(new Error(`Failed to read file: ${file.name}`));
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
reader.readAsDataURL(file);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check if file is duplicate
|
|
224
|
+
isDuplicateFile(file, existingBlocks) {
|
|
225
|
+
if (this.SUPPORTED_FILE_TYPES.includes(file.type)) {
|
|
226
|
+
return existingBlocks.some(
|
|
227
|
+
(block) =>
|
|
228
|
+
block.type === "image" &&
|
|
229
|
+
block.metadata?.name === file.name &&
|
|
230
|
+
block.mime_type === file.type
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Format file size
|
|
238
|
+
formatFileSize(bytes) {
|
|
239
|
+
if (bytes === 0) return "0 Bytes";
|
|
240
|
+
|
|
241
|
+
const k = 1024;
|
|
242
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
243
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
244
|
+
|
|
245
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Get file type display name
|
|
249
|
+
getFileTypeDisplay(mimeType) {
|
|
250
|
+
switch (mimeType) {
|
|
251
|
+
case "image/jpeg":
|
|
252
|
+
case "image/jpg":
|
|
253
|
+
return "JPEG Image";
|
|
254
|
+
case "image/png":
|
|
255
|
+
return "PNG Image";
|
|
256
|
+
case "image/gif":
|
|
257
|
+
return "GIF Image";
|
|
258
|
+
case "image/webp":
|
|
259
|
+
return "WebP Image";
|
|
260
|
+
default:
|
|
261
|
+
return "Unknown File";
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Handle file upload
|
|
266
|
+
async handleFileUpload(e) {
|
|
267
|
+
const files = e.target.files;
|
|
268
|
+
if (!files) return;
|
|
269
|
+
|
|
270
|
+
const fileArray = Array.from(files);
|
|
271
|
+
const validFiles = fileArray.filter((file) =>
|
|
272
|
+
this.SUPPORTED_FILE_TYPES.includes(file.type)
|
|
273
|
+
);
|
|
274
|
+
const invalidFiles = fileArray.filter(
|
|
275
|
+
(file) => !this.SUPPORTED_FILE_TYPES.includes(file.type)
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
if (invalidFiles.length > 0) {
|
|
279
|
+
console.warn(
|
|
280
|
+
"Invalid file type. Please upload a JPEG, PNG, GIF, or WEBP image."
|
|
281
|
+
);
|
|
282
|
+
// Could show a toast/alert here
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Since we only allow 1 file, we take the first valid one and replace existing
|
|
286
|
+
const fileToProcess = validFiles[0];
|
|
287
|
+
|
|
288
|
+
if (fileToProcess) {
|
|
289
|
+
try {
|
|
290
|
+
const newBlocks = await this.fileToContentBlock(fileToProcess);
|
|
291
|
+
// Replace existing blocks with new one
|
|
292
|
+
this.contentBlocks = [newBlocks];
|
|
293
|
+
this.onContentBlocksChange(this.contentBlocks);
|
|
294
|
+
this.renderFilePreview();
|
|
295
|
+
this.updateSendButton();
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.error(`Failed to process files: ${error.message}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Clear the input
|
|
302
|
+
e.target.value = "";
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Remove content block
|
|
306
|
+
removeBlock(index) {
|
|
307
|
+
this.contentBlocks = this.contentBlocks.filter((_, i) => i !== index);
|
|
308
|
+
this.onContentBlocksChange(this.contentBlocks);
|
|
309
|
+
this.renderFilePreview();
|
|
310
|
+
this.updateSendButton();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Update send button state
|
|
314
|
+
updateSendButton() {
|
|
315
|
+
if (!this.container) return;
|
|
316
|
+
const input = this.container.querySelector("#chat-input");
|
|
317
|
+
const sendBtn = this.container.querySelector("#chat-send");
|
|
318
|
+
if (!input || !sendBtn) return;
|
|
319
|
+
|
|
320
|
+
const hasText = input.value.trim().length > 0;
|
|
321
|
+
const hasFiles = this.contentBlocks.length > 0;
|
|
322
|
+
sendBtn.disabled = !(hasText || hasFiles);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Render file preview
|
|
326
|
+
renderFilePreview() {
|
|
327
|
+
if (!this.container) return;
|
|
328
|
+
|
|
329
|
+
const container = this.container.querySelector("#file-attachments-container");
|
|
330
|
+
if (!container) return;
|
|
331
|
+
|
|
332
|
+
if (!this.contentBlocks || this.contentBlocks.length === 0) {
|
|
333
|
+
container.innerHTML = "";
|
|
334
|
+
container.style.display = "none";
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
container.style.display = "flex";
|
|
339
|
+
|
|
340
|
+
container.innerHTML = this.contentBlocks
|
|
341
|
+
.map((block, index) => {
|
|
342
|
+
const displayName =
|
|
343
|
+
block.metadata?.filename || block.metadata?.name || "file";
|
|
344
|
+
const size = this.formatFileSize(block.metadata?.size || 0);
|
|
345
|
+
const isImage = block.type === "image";
|
|
346
|
+
const src = isImage
|
|
347
|
+
? `data:${block.mime_type};base64,${block.data}`
|
|
348
|
+
: null;
|
|
349
|
+
|
|
350
|
+
return `
|
|
351
|
+
<div class="file-attachment ${isImage ? "has-thumbnail" : ""}" data-index="${index}">
|
|
352
|
+
${isImage
|
|
353
|
+
? `
|
|
354
|
+
<div class="file-thumbnail">
|
|
355
|
+
<img src="${src}" alt="${displayName}" class="file-thumbnail-image" />
|
|
356
|
+
</div>
|
|
357
|
+
`
|
|
358
|
+
: '<div class="file-attachment-icon">📄</div>'
|
|
359
|
+
}
|
|
360
|
+
<div class="file-attachment-info">
|
|
361
|
+
<div class="file-attachment-name" title="${displayName}">
|
|
362
|
+
${displayName}
|
|
363
|
+
</div>
|
|
364
|
+
<div class="file-attachment-size">${size}</div>
|
|
365
|
+
</div>
|
|
366
|
+
<button
|
|
367
|
+
class="file-attachment-remove"
|
|
368
|
+
data-index="${index}"
|
|
369
|
+
title="Remove file"
|
|
370
|
+
>
|
|
371
|
+
✕
|
|
372
|
+
</button>
|
|
373
|
+
</div>
|
|
374
|
+
`;
|
|
375
|
+
})
|
|
376
|
+
.join("");
|
|
377
|
+
|
|
378
|
+
// Bind remove buttons
|
|
379
|
+
container.querySelectorAll(".file-attachment-remove").forEach((btn) => {
|
|
380
|
+
btn.addEventListener("click", (e) => {
|
|
381
|
+
e.stopPropagation();
|
|
382
|
+
const index = parseInt(btn.getAttribute("data-index"));
|
|
383
|
+
this.removeBlock(index);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Bind image click for expansion (optional - can add modal later)
|
|
388
|
+
container.querySelectorAll(".file-thumbnail-image").forEach((img) => {
|
|
389
|
+
img.addEventListener("click", (e) => {
|
|
390
|
+
e.stopPropagation();
|
|
391
|
+
// Could add image expansion modal here
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
_formatTime(date) {
|
|
397
|
+
let hours = date.getHours();
|
|
398
|
+
const minutes = date.getMinutes();
|
|
399
|
+
const ampm = hours >= 12 ? 'PM' : 'AM';
|
|
400
|
+
hours = hours % 12;
|
|
401
|
+
hours = hours ? hours : 12;
|
|
402
|
+
const strTime = hours.toString().padStart(2, '0') + ':' + minutes.toString().padStart(2, '0') + ' ' + ampm;
|
|
403
|
+
return strTime;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
addMessage(text, isUser, messageData = null) {
|
|
407
|
+
console.log("Is user", isUser)
|
|
408
|
+
if (!this.container) return;
|
|
409
|
+
|
|
410
|
+
const messagesContainer = this.container.querySelector("#chat-messages");
|
|
411
|
+
if (!messagesContainer) return;
|
|
412
|
+
|
|
413
|
+
const welcome = messagesContainer.querySelector(".chat-welcome");
|
|
414
|
+
if (welcome) welcome.remove();
|
|
415
|
+
|
|
416
|
+
// Check if message already exists (for syncing)
|
|
417
|
+
const messageId = messageData?.id || Date.now();
|
|
418
|
+
console.log("Message ID", messageId)
|
|
419
|
+
const existingMessage = messagesContainer.querySelector(`[data-message-id="${messageId}"]`);
|
|
420
|
+
if (existingMessage && messageData) {
|
|
421
|
+
// Update existing message
|
|
422
|
+
const bubble = existingMessage.querySelector(".message-bubble");
|
|
423
|
+
if (bubble) {
|
|
424
|
+
bubble.innerHTML = this._escapeHtml(messageData.content || text);
|
|
425
|
+
}
|
|
426
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const messageEl = document.createElement("div");
|
|
431
|
+
messageEl.className = `chat-message ${isUser ? "user" : "bot"}`;
|
|
432
|
+
messageEl.setAttribute("data-message-id", messageId);
|
|
433
|
+
|
|
434
|
+
// Handle different message types
|
|
435
|
+
let content = text;
|
|
436
|
+
if (messageData) {
|
|
437
|
+
if (messageData.isStreaming) {
|
|
438
|
+
content = messageData.content || "Thinking...";
|
|
439
|
+
} else if (messageData.isError) {
|
|
440
|
+
content = messageData.content || "Error occurred";
|
|
441
|
+
messageEl.classList.add("error");
|
|
442
|
+
} else {
|
|
443
|
+
content = messageData.content || text;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Build attachments HTML if present
|
|
448
|
+
let attachmentsHTML = "";
|
|
449
|
+
if (messageData && messageData.attachments && messageData.attachments.length > 0) {
|
|
450
|
+
attachmentsHTML = `
|
|
451
|
+
<div class="message-attachments">
|
|
452
|
+
${messageData.attachments.map((attachment, index) => {
|
|
453
|
+
const displayName = attachment.metadata?.name || attachment.metadata?.filename || "Unknown file";
|
|
454
|
+
const isImage = attachment.type === "image";
|
|
455
|
+
const imageSrc = isImage && attachment.data
|
|
456
|
+
? `data:${attachment.mime_type};base64,${attachment.data}`
|
|
457
|
+
: null;
|
|
458
|
+
|
|
459
|
+
return `
|
|
460
|
+
<div class="message-attachment ${isImage ? "image" : "pdf"}" data-attachment-index="${index}">
|
|
461
|
+
<span class="message-attachment-icon">
|
|
462
|
+
${isImage ? "🖼️" : "📄"}
|
|
463
|
+
</span>
|
|
464
|
+
<div class="message-attachment-info">
|
|
465
|
+
<div
|
|
466
|
+
class="message-attachment-name ${isImage ? "clickable" : ""}"
|
|
467
|
+
${isImage && imageSrc ? `data-image-src="${imageSrc}" data-image-alt="${displayName}"` : ""}
|
|
468
|
+
style="cursor: ${isImage ? "pointer" : "default"};"
|
|
469
|
+
>
|
|
470
|
+
${this._escapeHtml(displayName)}
|
|
471
|
+
</div>
|
|
472
|
+
<div class="message-attachment-size">
|
|
473
|
+
${this.formatFileSize(attachment.metadata?.size || 0)} • ${this.getFileTypeDisplay(attachment.mime_type)}
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
`;
|
|
478
|
+
}).join("")}
|
|
479
|
+
</div>
|
|
480
|
+
`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const timestamp = messageData?.timestamp ? new Date(messageData.timestamp) : new Date();
|
|
484
|
+
const formattedTime = this._formatTime(timestamp);
|
|
485
|
+
|
|
486
|
+
messageEl.innerHTML = `
|
|
487
|
+
<div class="message-content">
|
|
488
|
+
${attachmentsHTML}
|
|
489
|
+
<div class="message-bubble">
|
|
490
|
+
${this._escapeHtml(content)}
|
|
491
|
+
</div>
|
|
492
|
+
<div class="message-time">${formattedTime}</div>
|
|
493
|
+
</div>
|
|
494
|
+
`;
|
|
495
|
+
|
|
496
|
+
// Bind image click handlers for expansion
|
|
497
|
+
if (messageData && messageData.attachments) {
|
|
498
|
+
messageEl.querySelectorAll(".message-attachment-name.clickable").forEach((el) => {
|
|
499
|
+
el.addEventListener("click", (e) => {
|
|
500
|
+
e.stopPropagation();
|
|
501
|
+
const imageSrc = el.getAttribute("data-image-src");
|
|
502
|
+
const imageAlt = el.getAttribute("data-image-alt");
|
|
503
|
+
if (imageSrc) {
|
|
504
|
+
this.showExpandedImage(imageSrc, imageAlt);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
messagesContainer.appendChild(messageEl);
|
|
511
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Sync messages from shared array
|
|
515
|
+
_syncMessages(messages) {
|
|
516
|
+
if (!this.container) return;
|
|
517
|
+
|
|
518
|
+
const messagesContainer = this.container.querySelector("#chat-messages");
|
|
519
|
+
if (!messagesContainer) return;
|
|
520
|
+
|
|
521
|
+
// Remove welcome message if there are any messages
|
|
522
|
+
if (messages.length > 0) {
|
|
523
|
+
const welcome = messagesContainer.querySelector(".chat-welcome");
|
|
524
|
+
if (welcome) welcome.remove();
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Get existing message elements
|
|
528
|
+
const existingMessages = messagesContainer.querySelectorAll("[data-message-id]");
|
|
529
|
+
const existingIds = new Set(Array.from(existingMessages).map(el => el.getAttribute("data-message-id")));
|
|
530
|
+
|
|
531
|
+
// Add or update messages
|
|
532
|
+
messages.forEach((msg) => {
|
|
533
|
+
const msgId = String(msg.id);
|
|
534
|
+
if (existingIds.has(msgId)) {
|
|
535
|
+
// Update existing message
|
|
536
|
+
const existingEl = messagesContainer.querySelector(`[data-message-id="${msgId}"]`);
|
|
537
|
+
if (existingEl) {
|
|
538
|
+
const bubble = existingEl.querySelector(".message-bubble");
|
|
539
|
+
if (bubble) {
|
|
540
|
+
bubble.innerHTML = this._escapeHtml(msg.content || "");
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Update attachments if they exist
|
|
544
|
+
const messageContent = existingEl.querySelector(".message-content");
|
|
545
|
+
if (messageContent && msg.attachments && msg.attachments.length > 0) {
|
|
546
|
+
let attachmentsHTML = `
|
|
547
|
+
<div class="message-attachments">
|
|
548
|
+
${msg.attachments.map((attachment, index) => {
|
|
549
|
+
const displayName = attachment.metadata?.name || attachment.metadata?.filename || "Unknown file";
|
|
550
|
+
const isImage = attachment.type === "image";
|
|
551
|
+
const imageSrc = isImage && attachment.data
|
|
552
|
+
? `data:${attachment.mime_type};base64,${attachment.data}`
|
|
553
|
+
: null;
|
|
554
|
+
|
|
555
|
+
return `
|
|
556
|
+
<div class="message-attachment ${isImage ? "image" : "pdf"}" data-attachment-index="${index}">
|
|
557
|
+
<span class="message-attachment-icon">
|
|
558
|
+
${isImage ? "🖼️" : "📄"}
|
|
559
|
+
</span>
|
|
560
|
+
<div class="message-attachment-info">
|
|
561
|
+
<div
|
|
562
|
+
class="message-attachment-name ${isImage ? "clickable" : ""}"
|
|
563
|
+
${isImage && imageSrc ? `data-image-src="${imageSrc}" data-image-alt="${displayName}"` : ""}
|
|
564
|
+
style="cursor: ${isImage ? "pointer" : "default"};"
|
|
565
|
+
>
|
|
566
|
+
${this._escapeHtml(displayName)}
|
|
567
|
+
</div>
|
|
568
|
+
<div class="message-attachment-size">
|
|
569
|
+
${this.formatFileSize(attachment.metadata?.size || 0)} • ${this.getFileTypeDisplay(attachment.mime_type)}
|
|
570
|
+
</div>
|
|
571
|
+
</div>
|
|
572
|
+
</div>
|
|
573
|
+
`;
|
|
574
|
+
}).join("")}
|
|
575
|
+
</div>
|
|
576
|
+
`;
|
|
577
|
+
|
|
578
|
+
// Insert or update attachments
|
|
579
|
+
const existingAttachments = messageContent.querySelector(".message-attachments");
|
|
580
|
+
if (existingAttachments) {
|
|
581
|
+
existingAttachments.outerHTML = attachmentsHTML;
|
|
582
|
+
} else {
|
|
583
|
+
messageContent.insertAdjacentHTML("afterbegin", attachmentsHTML);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Re-bind image click handlers
|
|
587
|
+
messageContent.querySelectorAll(".message-attachment-name.clickable").forEach((el) => {
|
|
588
|
+
// Remove existing listeners by cloning
|
|
589
|
+
const newEl = el.cloneNode(true);
|
|
590
|
+
el.parentNode.replaceChild(newEl, el);
|
|
591
|
+
|
|
592
|
+
newEl.addEventListener("click", (e) => {
|
|
593
|
+
e.stopPropagation();
|
|
594
|
+
const imageSrc = newEl.getAttribute("data-image-src");
|
|
595
|
+
const imageAlt = newEl.getAttribute("data-image-alt");
|
|
596
|
+
if (imageSrc) {
|
|
597
|
+
this.showExpandedImage(imageSrc, imageAlt);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Update classes based on message state
|
|
604
|
+
if (msg.isStreaming) {
|
|
605
|
+
existingEl.classList.add("streaming");
|
|
606
|
+
} else {
|
|
607
|
+
existingEl.classList.remove("streaming");
|
|
608
|
+
}
|
|
609
|
+
if (msg.isError) {
|
|
610
|
+
existingEl.classList.add("error");
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
// Add new message
|
|
615
|
+
this.addMessage(
|
|
616
|
+
msg.content || "",
|
|
617
|
+
msg.sender === "user",
|
|
618
|
+
msg
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
// Remove messages that are no longer in the array
|
|
624
|
+
existingMessages.forEach((el) => {
|
|
625
|
+
const msgId = el.getAttribute("data-message-id");
|
|
626
|
+
const exists = messages.some(msg => String(msg.id) === msgId);
|
|
627
|
+
if (!exists) {
|
|
628
|
+
el.remove();
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
showTypingIndicator() {
|
|
636
|
+
if (!this.container) return;
|
|
637
|
+
|
|
638
|
+
const messagesContainer = this.container.querySelector("#chat-messages");
|
|
639
|
+
if (!messagesContainer) return;
|
|
640
|
+
|
|
641
|
+
const indicator = document.createElement("div");
|
|
642
|
+
indicator.className = "chat-message bot";
|
|
643
|
+
indicator.id = "typing-indicator";
|
|
644
|
+
indicator.innerHTML = `
|
|
645
|
+
<div class="typing-indicator">
|
|
646
|
+
<div class="typing-dot"></div>
|
|
647
|
+
<div class="typing-dot"></div>
|
|
648
|
+
<div class="typing-dot"></div>
|
|
649
|
+
</div>
|
|
650
|
+
`;
|
|
651
|
+
messagesContainer.appendChild(indicator);
|
|
652
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
hideTypingIndicator() {
|
|
656
|
+
if (!this.container) return;
|
|
657
|
+
const indicator = this.container.querySelector("#typing-indicator");
|
|
658
|
+
if (indicator) indicator.remove();
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
handleUserMessage(text) {
|
|
662
|
+
// Call the callback if provided, otherwise use default echo
|
|
663
|
+
if (this.onMessage) {
|
|
664
|
+
this.onMessage(text, (response) => {
|
|
665
|
+
this.addMessage(response, false);
|
|
666
|
+
});
|
|
667
|
+
} else {
|
|
668
|
+
// Default echo response
|
|
669
|
+
this.addMessage(`You said: "${text}"`, false);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
_escapeHtml(text) {
|
|
674
|
+
const div = document.createElement("div");
|
|
675
|
+
div.textContent = text;
|
|
676
|
+
return div.innerHTML;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Show expanded image modal
|
|
680
|
+
showExpandedImage(src, alt) {
|
|
681
|
+
// Remove existing modal if any
|
|
682
|
+
const existingModal = document.querySelector(".expanded-image-modal");
|
|
683
|
+
if (existingModal) {
|
|
684
|
+
existingModal.remove();
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const modal = document.createElement("div");
|
|
688
|
+
modal.className = "expanded-image-modal";
|
|
689
|
+
modal.innerHTML = `
|
|
690
|
+
<div class="expanded-image-container">
|
|
691
|
+
<button class="expanded-image-close" title="Close">✕</button>
|
|
692
|
+
<img src="${src}" alt="${alt || "Image"}" class="expanded-image" />
|
|
693
|
+
<div class="expanded-image-caption">${this._escapeHtml(alt || "Image preview")}</div>
|
|
694
|
+
</div>
|
|
695
|
+
`;
|
|
696
|
+
|
|
697
|
+
// Close handlers
|
|
698
|
+
const closeModal = (e) => {
|
|
699
|
+
e?.stopPropagation();
|
|
700
|
+
modal.remove();
|
|
701
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
const handleClickOutside = (e) => {
|
|
705
|
+
if (!e.target.closest(".expanded-image-container")) {
|
|
706
|
+
closeModal(e);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
modal.querySelector(".expanded-image-close").addEventListener("click", closeModal);
|
|
711
|
+
modal.addEventListener("click", (e) => {
|
|
712
|
+
if (e.target === modal) {
|
|
713
|
+
closeModal(e);
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
717
|
+
|
|
718
|
+
document.body.appendChild(modal);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
_applyStyles() {
|
|
722
|
+
// Check if styles already applied
|
|
723
|
+
if (document.getElementById('text-chat-screen-styles')) return;
|
|
724
|
+
|
|
725
|
+
const style = document.createElement("style");
|
|
726
|
+
style.id = 'text-chat-screen-styles';
|
|
727
|
+
style.textContent = `
|
|
728
|
+
.text-chat-screen {
|
|
729
|
+
flex: 1;
|
|
730
|
+
display: flex;
|
|
731
|
+
flex-direction: column;
|
|
732
|
+
overflow: hidden;
|
|
733
|
+
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.text-chat-screen .chat-header {
|
|
737
|
+
background: transparent;
|
|
738
|
+
color: ${this.primaryColor};
|
|
739
|
+
padding: 20px;
|
|
740
|
+
display: flex;
|
|
741
|
+
align-items: center;
|
|
742
|
+
justify-content: space-between;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.text-chat-screen .chat-header-content {
|
|
746
|
+
display: flex;
|
|
747
|
+
align-items: center;
|
|
748
|
+
gap: 12px;
|
|
749
|
+
flex: 1;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.text-chat-screen .chat-back {
|
|
753
|
+
background: ${this.primaryColor};
|
|
754
|
+
border: none;
|
|
755
|
+
color: white;
|
|
756
|
+
cursor: pointer;
|
|
757
|
+
padding: 8px;
|
|
758
|
+
border-radius: 50%;
|
|
759
|
+
display: flex;
|
|
760
|
+
align-items: center;
|
|
761
|
+
justify-content: center;
|
|
762
|
+
transition: all 0.2s;
|
|
763
|
+
margin-right: 8px;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
.text-chat-screen .chat-avatar {
|
|
769
|
+
width: 40px;
|
|
770
|
+
height: 40px;
|
|
771
|
+
border-radius: 50%;
|
|
772
|
+
background: rgba(255, 255, 255, 0.2);
|
|
773
|
+
backdrop-filter: blur(10px);
|
|
774
|
+
display: flex;
|
|
775
|
+
align-items: center;
|
|
776
|
+
justify-content: center;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
.text-chat-screen .chat-header-text {
|
|
780
|
+
display: flex;
|
|
781
|
+
flex-direction: column;
|
|
782
|
+
gap: 2px;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.text-chat-screen .chat-title {
|
|
786
|
+
font-weight: 600;
|
|
787
|
+
font-size: 20px;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
.text-chat-screen .chat-status {
|
|
791
|
+
font-size: 12px;
|
|
792
|
+
opacity: 0.9;
|
|
793
|
+
display: flex;
|
|
794
|
+
align-items: center;
|
|
795
|
+
gap: 4px;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.text-chat-screen .chat-close {
|
|
799
|
+
background: ${this.primaryColor};
|
|
800
|
+
border: none;
|
|
801
|
+
color: white;
|
|
802
|
+
cursor: pointer;
|
|
803
|
+
padding: 8px;
|
|
804
|
+
border-radius: 50%;
|
|
805
|
+
display: flex;
|
|
806
|
+
align-items: center;
|
|
807
|
+
justify-content: center;
|
|
808
|
+
transition: all 0.2s;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
.text-chat-screen .chat-messages {
|
|
814
|
+
flex: 1;
|
|
815
|
+
padding: 20px;
|
|
816
|
+
overflow-y: auto;
|
|
817
|
+
background: linear-gradient(180deg, white 10%, #E1EFCC );
|
|
818
|
+
display: flex;
|
|
819
|
+
flex-direction: column;
|
|
820
|
+
gap: 12px;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
.text-chat-screen .chat-messages::-webkit-scrollbar {
|
|
824
|
+
width: 6px;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.text-chat-screen .chat-messages::-webkit-scrollbar-track {
|
|
828
|
+
background: transparent;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.text-chat-screen .chat-messages::-webkit-scrollbar-thumb {
|
|
832
|
+
background: #cbd5e1;
|
|
833
|
+
border-radius: 3px;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.text-chat-screen .chat-welcome {
|
|
837
|
+
text-align: center;
|
|
838
|
+
padding: 100px 20px;
|
|
839
|
+
color: #64748b;
|
|
840
|
+
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
.text-chat-screen .chat-welcome .welcome-icon {
|
|
844
|
+
font-size: 48px;
|
|
845
|
+
margin-bottom: 12px;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.text-chat-screen .welcome-text {
|
|
849
|
+
font-size: 15px;
|
|
850
|
+
font-weight: 500;
|
|
851
|
+
color: #475569;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.text-chat-screen .chat-message {
|
|
855
|
+
display: flex;
|
|
856
|
+
gap: 8px;
|
|
857
|
+
animation: messageSlide 0.3s ease-out;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
@keyframes messageSlide {
|
|
861
|
+
from {
|
|
862
|
+
opacity: 0;
|
|
863
|
+
transform: translateY(10px);
|
|
864
|
+
}
|
|
865
|
+
to {
|
|
866
|
+
opacity: 1;
|
|
867
|
+
transform: translateY(0);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
.text-chat-screen .chat-message.user {
|
|
872
|
+
flex-direction: row-reverse;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.text-chat-screen .message-content {
|
|
876
|
+
max-width: 75%;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.text-chat-screen .message-bubble {
|
|
880
|
+
padding: 12px 16px;
|
|
881
|
+
border-radius: 16px;
|
|
882
|
+
font-size: 14px;
|
|
883
|
+
line-height: 1.5;
|
|
884
|
+
word-wrap: break-word;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.text-chat-screen .chat-message.user .message-bubble {
|
|
888
|
+
background: #e9f5d7;
|
|
889
|
+
color: ${this.primaryColor};
|
|
890
|
+
border-bottom-right-radius: 4px;
|
|
891
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.text-chat-screen .chat-message.bot .message-bubble {
|
|
895
|
+
background: white;
|
|
896
|
+
color: #1e293b;
|
|
897
|
+
border-bottom-left-radius: 4px;
|
|
898
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.text-chat-screen .message-time {
|
|
902
|
+
font-size: 10px;
|
|
903
|
+
color: #94a3b8;
|
|
904
|
+
margin-top: 4px;
|
|
905
|
+
text-align: right;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.text-chat-screen .typing-indicator {
|
|
909
|
+
display: flex;
|
|
910
|
+
gap: 4px;
|
|
911
|
+
padding: 12px 16px;
|
|
912
|
+
background: white;
|
|
913
|
+
border-radius: 16px;
|
|
914
|
+
border-bottom-left-radius: 4px;
|
|
915
|
+
max-width: 60px;
|
|
916
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
.text-chat-screen .typing-dot {
|
|
920
|
+
width: 8px;
|
|
921
|
+
height: 8px;
|
|
922
|
+
border-radius: 50%;
|
|
923
|
+
background: #94a3b8;
|
|
924
|
+
animation: typing 1.4s infinite;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.text-chat-screen .typing-dot:nth-child(2) {
|
|
928
|
+
animation-delay: 0.2s;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.text-chat-screen .typing-dot:nth-child(3) {
|
|
932
|
+
animation-delay: 0.4s;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
@keyframes typing {
|
|
936
|
+
0%, 60%, 100% {
|
|
937
|
+
transform: translateY(0);
|
|
938
|
+
opacity: 0.7;
|
|
939
|
+
}
|
|
940
|
+
30% {
|
|
941
|
+
transform: translateY(-10px);
|
|
942
|
+
opacity: 1;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
.text-chat-screen .file-attachments-container {
|
|
947
|
+
padding: 8px 12px;
|
|
948
|
+
background: white;
|
|
949
|
+
border-top: 1px solid #e2e8f0;
|
|
950
|
+
display: flex;
|
|
951
|
+
flex-wrap: wrap;
|
|
952
|
+
gap: 8px;
|
|
953
|
+
max-height: 120px;
|
|
954
|
+
overflow-y: auto;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.text-chat-screen .file-attachments-container::-webkit-scrollbar {
|
|
958
|
+
width: 4px;
|
|
959
|
+
height: 4px;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
.text-chat-screen .file-attachments-container::-webkit-scrollbar-track {
|
|
963
|
+
background: transparent;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
.text-chat-screen .file-attachments-container::-webkit-scrollbar-thumb {
|
|
967
|
+
background: #cbd5e1;
|
|
968
|
+
border-radius: 2px;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
.text-chat-screen .file-attachment {
|
|
972
|
+
display: flex;
|
|
973
|
+
align-items: center;
|
|
974
|
+
gap: 8px;
|
|
975
|
+
padding: 6px 10px;
|
|
976
|
+
background: #f1f5f9;
|
|
977
|
+
border: 1px solid #e2e8f0;
|
|
978
|
+
border-radius: 8px;
|
|
979
|
+
font-size: 12px;
|
|
980
|
+
max-width: 200px;
|
|
981
|
+
position: relative;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.text-chat-screen .file-attachment.has-thumbnail {
|
|
985
|
+
padding: 4px;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
.text-chat-screen .file-thumbnail {
|
|
989
|
+
width: 40px;
|
|
990
|
+
height: 40px;
|
|
991
|
+
border-radius: 6px;
|
|
992
|
+
overflow: hidden;
|
|
993
|
+
flex-shrink: 0;
|
|
994
|
+
background: #e2e8f0;
|
|
995
|
+
display: flex;
|
|
996
|
+
align-items: center;
|
|
997
|
+
justify-content: center;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
.text-chat-screen .file-thumbnail-image {
|
|
1001
|
+
width: 100%;
|
|
1002
|
+
height: 100%;
|
|
1003
|
+
object-fit: cover;
|
|
1004
|
+
cursor: pointer;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
.text-chat-screen .file-attachment-icon {
|
|
1008
|
+
font-size: 24px;
|
|
1009
|
+
flex-shrink: 0;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
.text-chat-screen .file-attachment-info {
|
|
1013
|
+
flex: 1;
|
|
1014
|
+
min-width: 0;
|
|
1015
|
+
display: flex;
|
|
1016
|
+
flex-direction: column;
|
|
1017
|
+
gap: 2px;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
.text-chat-screen .file-attachment-name {
|
|
1021
|
+
font-weight: 500;
|
|
1022
|
+
color: #1e293b;
|
|
1023
|
+
white-space: nowrap;
|
|
1024
|
+
overflow: hidden;
|
|
1025
|
+
text-overflow: ellipsis;
|
|
1026
|
+
max-width: 120px;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.text-chat-screen .file-attachment-size {
|
|
1030
|
+
font-size: 11px;
|
|
1031
|
+
color: #64748b;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
.text-chat-screen .file-attachment-remove {
|
|
1035
|
+
background: transparent;
|
|
1036
|
+
border: none;
|
|
1037
|
+
color: #64748b;
|
|
1038
|
+
cursor: pointer;
|
|
1039
|
+
padding: 4px;
|
|
1040
|
+
border-radius: 4px;
|
|
1041
|
+
font-size: 16px;
|
|
1042
|
+
line-height: 1;
|
|
1043
|
+
display: flex;
|
|
1044
|
+
align-items: center;
|
|
1045
|
+
justify-content: center;
|
|
1046
|
+
transition: all 0.2s;
|
|
1047
|
+
flex-shrink: 0;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
.text-chat-screen .file-attachment-remove:hover {
|
|
1051
|
+
background: #fee2e2;
|
|
1052
|
+
color: #dc2626;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
.text-chat-screen .chat-input-wrapper {
|
|
1056
|
+
display: flex;
|
|
1057
|
+
padding: 10px;
|
|
1058
|
+
gap: 8px;
|
|
1059
|
+
background: white;
|
|
1060
|
+
border-top: 1px solid #e2e8f0;
|
|
1061
|
+
align-items: center;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
.text-chat-screen .file-upload-controls {
|
|
1065
|
+
display: flex;
|
|
1066
|
+
align-items: center;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
.text-chat-screen .file-upload-button {
|
|
1070
|
+
background: transparent;
|
|
1071
|
+
border: none;
|
|
1072
|
+
color: ${this.primaryColor};
|
|
1073
|
+
cursor: pointer;
|
|
1074
|
+
padding: 8px;
|
|
1075
|
+
border-radius: 8px;
|
|
1076
|
+
display: flex;
|
|
1077
|
+
align-items: center;
|
|
1078
|
+
justify-content: center;
|
|
1079
|
+
transition: all 0.2s;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.text-chat-screen .file-upload-button:hover {
|
|
1083
|
+
background: rgba(26, 92, 75, 0.1);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
.text-chat-screen .file-upload-button:active {
|
|
1087
|
+
transform: scale(0.95);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
.text-chat-screen #chat-input {
|
|
1091
|
+
flex: 1;
|
|
1092
|
+
border: 2px solid #e2e8f0;
|
|
1093
|
+
border-radius: 12px;
|
|
1094
|
+
padding: 12px 16px;
|
|
1095
|
+
font-size: 14px;
|
|
1096
|
+
outline: none;
|
|
1097
|
+
transition: all 0.2s;
|
|
1098
|
+
font-family: inherit;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.text-chat-screen #chat-input:focus {
|
|
1102
|
+
border-color: ${this.primaryColor};
|
|
1103
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
.text-chat-screen #chat-send {
|
|
1107
|
+
background: ${this.primaryColor};
|
|
1108
|
+
color: white;
|
|
1109
|
+
border: none;
|
|
1110
|
+
border-radius: 12px;
|
|
1111
|
+
padding: 12px 16px;
|
|
1112
|
+
cursor: pointer;
|
|
1113
|
+
display: flex;
|
|
1114
|
+
align-items: center;
|
|
1115
|
+
justify-content: center;
|
|
1116
|
+
transition: all 0.2s;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
.text-chat-screen #chat-send:disabled {
|
|
1120
|
+
opacity: 0.5;
|
|
1121
|
+
cursor: not-allowed;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
.text-chat-screen #chat-send:not(:disabled):hover {
|
|
1125
|
+
transform: scale(1.05);
|
|
1126
|
+
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
.text-chat-screen #chat-send:not(:disabled):active {
|
|
1130
|
+
transform: scale(0.95);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/* Message Attachments Styles */
|
|
1134
|
+
.text-chat-screen .message-attachments {
|
|
1135
|
+
display: flex;
|
|
1136
|
+
flex-direction: column;
|
|
1137
|
+
gap: 8px;
|
|
1138
|
+
margin-bottom: 8px;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
.text-chat-screen .message-attachment {
|
|
1142
|
+
display: flex;
|
|
1143
|
+
align-items: center;
|
|
1144
|
+
gap: 8px;
|
|
1145
|
+
padding: 8px 12px;
|
|
1146
|
+
background: #f1f5f9;
|
|
1147
|
+
border: 1px solid #e2e8f0;
|
|
1148
|
+
border-radius: 8px;
|
|
1149
|
+
font-size: 12px;
|
|
1150
|
+
max-width: 100%;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
.text-chat-screen .message-attachment.image {
|
|
1154
|
+
background: #f8fafc;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
.text-chat-screen .message-attachment.pdf {
|
|
1158
|
+
background: #fef2f2;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
.text-chat-screen .message-attachment-icon {
|
|
1162
|
+
font-size: 20px;
|
|
1163
|
+
flex-shrink: 0;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
.text-chat-screen .message-attachment-info {
|
|
1167
|
+
flex: 1;
|
|
1168
|
+
min-width: 0;
|
|
1169
|
+
display: flex;
|
|
1170
|
+
flex-direction: column;
|
|
1171
|
+
gap: 2px;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
.text-chat-screen .message-attachment-name {
|
|
1175
|
+
font-weight: 500;
|
|
1176
|
+
color: #1e293b;
|
|
1177
|
+
white-space: nowrap;
|
|
1178
|
+
overflow: hidden;
|
|
1179
|
+
text-overflow: ellipsis;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
.text-chat-screen .message-attachment-name.clickable:hover {
|
|
1183
|
+
color: ${this.primaryColor};
|
|
1184
|
+
text-decoration: underline;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
.text-chat-screen .message-attachment-size {
|
|
1188
|
+
font-size: 11px;
|
|
1189
|
+
color: #64748b;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/* Expanded Image Modal Styles */
|
|
1193
|
+
.expanded-image-modal {
|
|
1194
|
+
position: fixed;
|
|
1195
|
+
top: 0;
|
|
1196
|
+
left: 0;
|
|
1197
|
+
right: 0;
|
|
1198
|
+
bottom: 0;
|
|
1199
|
+
background: rgba(0, 0, 0, 0.8);
|
|
1200
|
+
display: flex;
|
|
1201
|
+
align-items: center;
|
|
1202
|
+
justify-content: center;
|
|
1203
|
+
z-index: 10000;
|
|
1204
|
+
animation: fadeIn 0.2s ease;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
@keyframes fadeIn {
|
|
1208
|
+
from {
|
|
1209
|
+
opacity: 0;
|
|
1210
|
+
}
|
|
1211
|
+
to {
|
|
1212
|
+
opacity: 1;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
.expanded-image-container {
|
|
1217
|
+
position: relative;
|
|
1218
|
+
max-width: 90%;
|
|
1219
|
+
max-height: 90vh;
|
|
1220
|
+
background: #fff;
|
|
1221
|
+
border-radius: 8px;
|
|
1222
|
+
overflow: hidden;
|
|
1223
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
1224
|
+
animation: scaleIn 0.2s ease;
|
|
1225
|
+
display: flex;
|
|
1226
|
+
flex-direction: column;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
@keyframes scaleIn {
|
|
1230
|
+
from {
|
|
1231
|
+
transform: scale(0.9);
|
|
1232
|
+
opacity: 0;
|
|
1233
|
+
}
|
|
1234
|
+
to {
|
|
1235
|
+
transform: scale(1);
|
|
1236
|
+
opacity: 1;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
.expanded-image-close {
|
|
1241
|
+
position: absolute;
|
|
1242
|
+
top: 12px;
|
|
1243
|
+
right: 12px;
|
|
1244
|
+
background: rgba(0, 0, 0, 0.6);
|
|
1245
|
+
border: none;
|
|
1246
|
+
color: white;
|
|
1247
|
+
width: 32px;
|
|
1248
|
+
height: 32px;
|
|
1249
|
+
border-radius: 50%;
|
|
1250
|
+
cursor: pointer;
|
|
1251
|
+
display: flex;
|
|
1252
|
+
align-items: center;
|
|
1253
|
+
justify-content: center;
|
|
1254
|
+
font-size: 18px;
|
|
1255
|
+
z-index: 1;
|
|
1256
|
+
transition: all 0.2s;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
.expanded-image-close:hover {
|
|
1260
|
+
background: rgba(0, 0, 0, 0.8);
|
|
1261
|
+
transform: scale(1.1);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
.expanded-image {
|
|
1265
|
+
max-width: 100%;
|
|
1266
|
+
max-height: calc(90vh - 60px);
|
|
1267
|
+
object-fit: contain;
|
|
1268
|
+
display: block;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
.expanded-image-caption {
|
|
1272
|
+
padding: 12px 16px;
|
|
1273
|
+
background: #fff;
|
|
1274
|
+
color: #1e293b;
|
|
1275
|
+
font-size: 14px;
|
|
1276
|
+
text-align: center;
|
|
1277
|
+
border-top: 1px solid #e2e8f0;
|
|
1278
|
+
}
|
|
1279
|
+
`;
|
|
1280
|
+
document.head.appendChild(style);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// Export as ESM (Rollup will handle UMD conversion)
|
|
1285
|
+
export default TextChatScreen;
|
|
1286
|
+
|