Spaces:
Sleeping
Sleeping
| /** | |
| * Guardrails Chat Interface - Frontend JavaScript | |
| * Handles chat functionality, API communication, and UI interactions | |
| */ | |
| class GuardrailsChat { | |
| constructor() { | |
| this.messageInput = document.getElementById('message-input'); | |
| this.sendButton = document.getElementById('send-button'); | |
| this.chatMessages = document.getElementById('chat-messages'); | |
| this.loadingOverlay = document.getElementById('loading-overlay'); | |
| this.configPanel = document.getElementById('config-panel'); | |
| this.configToggle = document.getElementById('config-toggle'); | |
| this.charCount = document.getElementById('char-count'); | |
| // File upload elements | |
| this.attachButton = document.getElementById('attach-button'); | |
| this.fileInput = document.getElementById('file-input'); | |
| this.fileUploadSection = document.getElementById('file-upload-section'); | |
| this.fileDropZone = document.getElementById('file-drop-zone'); | |
| this.uploadedFiles = document.getElementById('uploaded-files'); | |
| // Stats elements | |
| this.avgLatency = document.getElementById('avg-latency'); | |
| this.blocksCount = document.getElementById('blocks-count'); | |
| this.piiCount = document.getElementById('pii-count'); | |
| // State | |
| this.isLoading = false; | |
| this.messageHistory = []; | |
| this.attachments = []; // Uploaded attachments | |
| this.initializeEventListeners(); | |
| this.loadConfiguration(); | |
| this.updateStats(); | |
| } | |
| initializeEventListeners() { | |
| // Send message events | |
| this.sendButton.addEventListener('click', () => this.sendMessage()); | |
| this.messageInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| this.sendMessage(); | |
| } | |
| }); | |
| // Auto-resize textarea | |
| this.messageInput.addEventListener('input', () => { | |
| this.autoResizeTextarea(); | |
| this.updateCharCount(); | |
| }); | |
| // File upload events | |
| this.attachButton.addEventListener('click', () => this.toggleFileUpload()); | |
| this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e)); | |
| this.fileDropZone.addEventListener('click', () => this.fileInput.click()); | |
| // Drag and drop events | |
| this.fileDropZone.addEventListener('dragover', (e) => this.handleDragOver(e)); | |
| this.fileDropZone.addEventListener('dragleave', (e) => this.handleDragLeave(e)); | |
| this.fileDropZone.addEventListener('drop', (e) => this.handleFileDrop(e)); | |
| // Config panel events | |
| this.configToggle.addEventListener('click', () => this.toggleConfigPanel()); | |
| document.getElementById('close-config').addEventListener('click', () => this.closeConfigPanel()); | |
| // Click outside to close config panel | |
| document.addEventListener('click', (e) => { | |
| if (this.configPanel.classList.contains('open') && | |
| !this.configPanel.contains(e.target) && | |
| !this.configToggle.contains(e.target)) { | |
| this.closeConfigPanel(); | |
| } | |
| }); | |
| } | |
| autoResizeTextarea() { | |
| this.messageInput.style.height = 'auto'; | |
| this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 200) + 'px'; | |
| } | |
| updateCharCount() { | |
| const count = this.messageInput.value.length; | |
| this.charCount.textContent = `${count}/2000`; | |
| if (count > 1800) { | |
| this.charCount.style.color = 'var(--accent-danger)'; | |
| } else if (count > 1500) { | |
| this.charCount.style.color = 'var(--accent-warning)'; | |
| } else { | |
| this.charCount.style.color = 'var(--text-muted)'; | |
| } | |
| } | |
| async sendMessage() { | |
| const message = this.messageInput.value.trim(); | |
| // Debug logging | |
| console.log('Sending message with attachments:', this.attachments.map(att => ({ id: att.id, filename: att.filename, is_safe: att.is_safe }))); | |
| // Check if we have unsafe attachments | |
| const unsafeAttachments = this.attachments.filter(att => !att.is_safe); | |
| if (unsafeAttachments.length > 0) { | |
| console.log('Unsafe attachments detected:', unsafeAttachments.map(att => ({ id: att.id, filename: att.filename }))); | |
| this.addErrorMessage(`Cannot send message: ${unsafeAttachments.length} unsafe attachment(s) detected. Please remove them first.`); | |
| return; | |
| } | |
| if (!message && this.attachments.length === 0) return; | |
| if (this.isLoading) return; | |
| this.setLoading(true); | |
| // Add user message to chat (include attachment info) | |
| this.addUserMessage(message, this.attachments); | |
| // Clear input | |
| this.messageInput.value = ''; | |
| this.autoResizeTextarea(); | |
| this.updateCharCount(); | |
| try { | |
| const response = await fetch('/api/chat', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| message: message, | |
| attachments: this.attachments.map(att => ({ | |
| id: att.id, | |
| filename: att.filename, | |
| is_safe: att.is_safe | |
| })) | |
| }) | |
| }); | |
| const data = await response.json(); | |
| if (response.ok) { | |
| this.messageHistory.push(data); | |
| this.addBotMessage(data); | |
| this.updateStats(); | |
| // Clear attachments after successful send | |
| this.clearAttachments(); | |
| } else { | |
| this.addErrorMessage(data.message || 'An error occurred'); | |
| } | |
| } catch (error) { | |
| console.error('Error sending message:', error); | |
| this.addErrorMessage('Failed to send message. Please try again.'); | |
| } finally { | |
| this.setLoading(false); | |
| } | |
| } | |
| clearAttachments() { | |
| // Clear attachments array | |
| this.attachments = []; | |
| // Clear UI | |
| this.uploadedFiles.innerHTML = ''; | |
| // Hide upload section | |
| this.fileUploadSection.classList.remove('show'); | |
| this.attachButton.classList.remove('active'); | |
| // Reset file input | |
| this.fileInput.value = ''; | |
| } | |
| addUserMessage(message, attachments = []) { | |
| const messageId = 'user-' + Date.now(); | |
| const timestamp = new Date().toLocaleTimeString(); | |
| let attachmentHtml = ''; | |
| if (attachments.length > 0) { | |
| attachmentHtml = ` | |
| <div class="message-attachments"> | |
| <h4><i class="fas fa-paperclip"></i> Attachments (${attachments.length})</h4> | |
| <div class="attachment-list"> | |
| ${attachments.map(att => ` | |
| <div class="attachment-item ${att.is_safe ? 'safe' : 'unsafe'}"> | |
| <i class="fas ${this.getFileIcon(att.filename)}"></i> | |
| <span class="attachment-name">${this.escapeHtml(att.filename)}</span> | |
| <span class="attachment-status"> | |
| <i class="fas ${att.is_safe ? 'fa-check-circle' : 'fa-exclamation-triangle'}"></i> | |
| </span> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| `; | |
| } | |
| const messageHtml = ` | |
| <div class="message-container user-message" data-message-id="${messageId}"> | |
| <div class="message"> | |
| <div class="message-header"> | |
| <div class="message-type user"> | |
| <i class="fas fa-user"></i> | |
| <span>You</span> | |
| </div> | |
| <div class="message-meta"> | |
| <span>${timestamp}</span> | |
| </div> | |
| </div> | |
| <div class="message-content"> | |
| ${message ? `<p>${this.escapeHtml(message)}</p>` : ''} | |
| ${attachmentHtml} | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| this.chatMessages.insertAdjacentHTML('beforeend', messageHtml); | |
| this.scrollToBottom(); | |
| } | |
| addBotMessage(data) { | |
| const messageId = 'bot-' + data.message_id; | |
| const timestamp = new Date(data.timestamp).toLocaleTimeString(); | |
| const isBlocked = !data.is_safe; | |
| const messageType = isBlocked ? 'blocked' : 'assistant'; | |
| const icon = isBlocked ? 'fa-ban' : 'fa-robot'; | |
| const label = isBlocked ? 'Blocked' : 'Assistant'; | |
| const messageHtml = ` | |
| <div class="message-container bot-message" data-message-id="${messageId}"> | |
| <div class="message"> | |
| <div class="message-header" onclick="toggleMessageDetails('${messageId}')"> | |
| <div class="message-type ${messageType}"> | |
| <i class="fas ${icon}"></i> | |
| <span>${label}</span> | |
| </div> | |
| <div class="message-meta"> | |
| <span>${data.total_latency_ms}ms</span> | |
| <span>${timestamp}</span> | |
| <button class="dropdown-toggle" data-message-id="${messageId}"> | |
| <i class="fas fa-chevron-down"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="message-content"> | |
| <p>${this.escapeHtml(data.final_response)}</p> | |
| </div> | |
| <div class="message-details" id="details-${messageId}"> | |
| ${this.generateMessageDetails(data)} | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| this.chatMessages.insertAdjacentHTML('beforeend', messageHtml); | |
| this.scrollToBottom(); | |
| } | |
| generateMessageDetails(data) { | |
| let html = ''; | |
| // AI Detection Section | |
| if (data.ai_detection && Object.keys(data.ai_detection).length > 0) { | |
| const ai = data.ai_detection; | |
| const safetyClass = ai.is_safe ? 'safe' : 'unsafe'; | |
| html += ` | |
| <div class="detail-section"> | |
| <div class="detail-header"> | |
| <i class="fas fa-shield-alt"></i> | |
| AI Detection (Input Guardrails) | |
| </div> | |
| <div class="detail-grid"> | |
| <div class="detail-item"> | |
| <span class="detail-label">Safety Status</span> | |
| <span class="detail-value ${safetyClass}">${ai.safety_status || 'unknown'}</span> | |
| </div> | |
| <div class="detail-item"> | |
| <span class="detail-label">Attack Type</span> | |
| <span class="detail-value">${ai.attack_type || 'none'}</span> | |
| </div> | |
| <div class="detail-item"> | |
| <span class="detail-label">Confidence</span> | |
| <span class="detail-value">${(ai.confidence * 100).toFixed(1)}%</span> | |
| </div> | |
| <div class="detail-item"> | |
| <span class="detail-label">Latency</span> | |
| <span class="detail-value">${ai.latency_ms}ms</span> | |
| </div> | |
| <div class="detail-item"> | |
| <span class="detail-label">Model</span> | |
| <span class="detail-value">${ai.model_used || 'unknown'}</span> | |
| </div> | |
| </div> | |
| ${ai.reason ? `<p style="margin-top: 0.5rem; color: var(--text-secondary); font-size: 0.875rem;"><strong>Reason:</strong> ${this.escapeHtml(ai.reason)}</p>` : ''} | |
| </div> | |
| `; | |
| } | |
| // LLM Response Section | |
| if (data.llm_response && Object.keys(data.llm_response).length > 0) { | |
| const llm = data.llm_response; | |
| html += ` | |
| <div class="detail-section"> | |
| <div class="detail-header"> | |
| <i class="fas fa-brain"></i> | |
| LLM Generation | |
| </div> | |
| <div class="detail-grid"> | |
| <div class="detail-item"> | |
| <span class="detail-label">Provider</span> | |
| <span class="detail-value">${llm.provider || 'unknown'}</span> | |
| </div> | |
| <div class="detail-item"> | |
| <span class="detail-label">Model</span> | |
| <span class="detail-value">${llm.model || 'unknown'}</span> | |
| </div> | |
| <div class="detail-item"> | |
| <span class="detail-label">Latency</span> | |
| <span class="detail-value">${llm.latency_ms}ms</span> | |
| </div> | |
| <div class="detail-item"> | |
| <span class="detail-label">Characters</span> | |
| <span class="detail-value">${llm.character_count || 0}</span> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| // Output Guardrails Section | |
| if (data.output_guardrails && Object.keys(data.output_guardrails).length > 0) { | |
| const og = data.output_guardrails; | |
| const safetyClass = og.is_safe ? 'safe' : 'unsafe'; | |
| const modifiedClass = og.was_modified ? 'warning' : 'safe'; | |
| html += ` | |
| <div class="detail-section"> | |
| <div class="detail-header"> | |
| <i class="fas fa-filter"></i> | |
| Output Guardrails | |
| </div> | |
| <div class="detail-grid"> | |
| <div class="detail-item"> | |
| <span class="detail-label">Safety Status</span> | |
| <span class="detail-value ${safetyClass}">${og.is_safe ? 'Safe' : 'Blocked'}</span> | |
| </div> | |
| <div class="detail-item"> | |
| <span class="detail-label">Modified</span> | |
| <span class="detail-value ${modifiedClass}">${og.was_modified ? 'Yes' : 'No'}</span> | |
| </div> | |
| <div class="detail-item"> | |
| <span class="detail-label">Original Length</span> | |
| <span class="detail-value">${og.original_length}</span> | |
| </div> | |
| <div class="detail-item"> | |
| <span class="detail-label">Processed Length</span> | |
| <span class="detail-value">${og.processed_length}</span> | |
| </div> | |
| <div class="detail-item"> | |
| <span class="detail-label">Latency</span> | |
| <span class="detail-value">${og.latency_ms}ms</span> | |
| </div> | |
| </div> | |
| ${og.processing_details && og.processing_details.length > 0 ? ` | |
| <div style="margin-top: 0.5rem;"> | |
| <strong>Processing Details:</strong> | |
| <ul style="margin: 0.25rem 0; padding-left: 1rem; color: var(--text-secondary); font-size: 0.875rem;"> | |
| ${og.processing_details.map(detail => | |
| `<li>${detail.description} (${detail.characters_changed} chars changed)</li>` | |
| ).join('')} | |
| </ul> | |
| </div> | |
| ` : ''} | |
| </div> | |
| `; | |
| } | |
| return html; | |
| } | |
| addErrorMessage(message) { | |
| const timestamp = new Date().toLocaleTimeString(); | |
| const messageHtml = ` | |
| <div class="message-container bot-message"> | |
| <div class="message"> | |
| <div class="message-header"> | |
| <div class="message-type blocked"> | |
| <i class="fas fa-exclamation-triangle"></i> | |
| <span>Error</span> | |
| </div> | |
| <div class="message-meta"> | |
| <span>${timestamp}</span> | |
| </div> | |
| </div> | |
| <div class="message-content"> | |
| <p>${this.escapeHtml(message)}</p> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| this.chatMessages.insertAdjacentHTML('beforeend', messageHtml); | |
| this.scrollToBottom(); | |
| } | |
| setLoading(loading) { | |
| this.isLoading = loading; | |
| this.sendButton.disabled = loading; | |
| this.messageInput.disabled = loading; | |
| if (loading) { | |
| this.loadingOverlay.classList.add('show'); | |
| } else { | |
| this.loadingOverlay.classList.remove('show'); | |
| } | |
| } | |
| scrollToBottom() { | |
| setTimeout(() => { | |
| this.chatMessages.scrollTop = this.chatMessages.scrollHeight; | |
| }, 100); | |
| } | |
| async loadConfiguration() { | |
| try { | |
| const response = await fetch('/api/config'); | |
| const config = await response.json(); | |
| this.displayConfiguration(config); | |
| } catch (error) { | |
| console.error('Error loading configuration:', error); | |
| } | |
| } | |
| displayConfiguration(config) { | |
| const configContent = document.getElementById('config-content'); | |
| const configHtml = ` | |
| <div class="detail-section"> | |
| <div class="detail-header"> | |
| <i class="fas fa-brain"></i> | |
| LLM Configuration | |
| </div> | |
| <div class="detail-grid"> | |
| <div class="detail-item"> | |
| <span class="detail-label">Provider</span> | |
| <span class="detail-value">${config.llm_provider}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="detail-section"> | |
| <div class="detail-header"> | |
| <i class="fas fa-shield-alt"></i> | |
| AI Detection | |
| </div> | |
| <div class="detail-grid"> | |
| <div class="detail-item"> | |
| <span class="detail-label">Enabled</span> | |
| <span class="detail-value ${config.ai_detection_enabled ? 'safe' : 'unsafe'}"> | |
| ${config.ai_detection_enabled ? 'Yes' : 'No'} | |
| </span> | |
| </div> | |
| <div class="detail-item"> | |
| <span class="detail-label">Model</span> | |
| <span class="detail-value">${config.model_name}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="detail-section"> | |
| <div class="detail-header"> | |
| <i class="fas fa-filter"></i> | |
| Output Guardrails | |
| </div> | |
| <div class="detail-grid"> | |
| ${Object.entries(config.output_guardrails).map(([name, enabled]) => ` | |
| <div class="detail-item"> | |
| <span class="detail-label">${name.replace(/_/g, ' ')}</span> | |
| <span class="detail-value ${enabled ? 'safe' : 'unsafe'}"> | |
| ${enabled ? 'Enabled' : 'Disabled'} | |
| </span> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| `; | |
| configContent.innerHTML = configHtml; | |
| } | |
| async updateStats() { | |
| try { | |
| const response = await fetch('/api/stats'); | |
| const stats = await response.json(); | |
| this.avgLatency.textContent = `${stats.avg_latency}ms`; | |
| this.blocksCount.textContent = stats.blocks_count; | |
| this.piiCount.textContent = stats.pii_anonymizations; | |
| } catch (error) { | |
| console.error('Error loading stats:', error); | |
| } | |
| } | |
| toggleConfigPanel() { | |
| this.configPanel.classList.toggle('open'); | |
| } | |
| closeConfigPanel() { | |
| this.configPanel.classList.remove('open'); | |
| } | |
| escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| // File Upload Methods | |
| toggleFileUpload() { | |
| const isVisible = this.fileUploadSection.classList.contains('show'); | |
| if (isVisible) { | |
| this.fileUploadSection.classList.remove('show'); | |
| this.attachButton.classList.remove('active'); | |
| } else { | |
| this.fileUploadSection.classList.add('show'); | |
| this.attachButton.classList.add('active'); | |
| } | |
| } | |
| handleFileSelect(event) { | |
| const files = event.target.files; | |
| this.processFiles(files); | |
| } | |
| handleDragOver(event) { | |
| event.preventDefault(); | |
| this.fileDropZone.classList.add('drag-over'); | |
| } | |
| handleDragLeave(event) { | |
| event.preventDefault(); | |
| this.fileDropZone.classList.remove('drag-over'); | |
| } | |
| handleFileDrop(event) { | |
| event.preventDefault(); | |
| this.fileDropZone.classList.remove('drag-over'); | |
| const files = event.dataTransfer.files; | |
| this.processFiles(files); | |
| } | |
| async processFiles(files) { | |
| for (let file of files) { | |
| await this.uploadFile(file); | |
| } | |
| } | |
| async uploadFile(file) { | |
| const fileId = 'file-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); | |
| // Add file to UI immediately | |
| this.addFileToUI(fileId, file, 'processing'); | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| try { | |
| const response = await fetch('/api/upload', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const result = await response.json(); | |
| if (response.ok) { | |
| // Determine the final ID to use | |
| const finalId = result.attachment_id || fileId; | |
| // If backend provided a different ID, update the UI element | |
| if (result.attachment_id && result.attachment_id !== fileId) { | |
| const fileElement = document.querySelector(`[data-file-id="${fileId}"]`); | |
| if (fileElement) { | |
| fileElement.setAttribute('data-file-id', result.attachment_id); | |
| // Update the onclick handlers to use the new ID | |
| const viewBtn = fileElement.querySelector('.view'); | |
| const removeBtn = fileElement.querySelector('.remove'); | |
| if (viewBtn) viewBtn.setAttribute('onclick', `viewFileDetails('${result.attachment_id}')`); | |
| if (removeBtn) removeBtn.setAttribute('onclick', `removeFile('${result.attachment_id}')`); | |
| } | |
| } | |
| // Update file status in UI using the correct ID | |
| this.updateFileStatus(result.attachment_id || fileId, result.is_safe ? 'safe' : 'unsafe', result); | |
| // Add to attachments array with the same ID used in UI | |
| this.attachments.push({ | |
| id: finalId, | |
| filename: file.name, | |
| is_safe: result.is_safe, | |
| analysis: result | |
| }); | |
| } else { | |
| this.updateFileStatus(fileId, 'unsafe', { error: result.error }); | |
| // Add failed upload to attachments array so it can be properly removed | |
| this.attachments.push({ | |
| id: fileId, | |
| filename: file.name, | |
| is_safe: false, | |
| analysis: { error: result.error } | |
| }); | |
| } | |
| } catch (error) { | |
| console.error('Error uploading file:', error); | |
| this.updateFileStatus(fileId, 'unsafe', { error: 'Upload failed' }); | |
| // Add failed upload to attachments array so it can be properly removed | |
| this.attachments.push({ | |
| id: fileId, | |
| filename: file.name, | |
| is_safe: false, | |
| analysis: { error: 'Upload failed' } | |
| }); | |
| } | |
| } | |
| addFileToUI(fileId, file, status) { | |
| const fileElement = document.createElement('div'); | |
| fileElement.className = 'uploaded-file'; | |
| fileElement.setAttribute('data-file-id', fileId); | |
| const statusText = { | |
| 'processing': 'Analyzing...', | |
| 'safe': 'Safe', | |
| 'unsafe': 'Unsafe' | |
| }; | |
| const statusIcon = { | |
| 'processing': 'fa-spinner fa-spin', | |
| 'safe': 'fa-check-circle', | |
| 'unsafe': 'fa-exclamation-triangle' | |
| }; | |
| fileElement.innerHTML = ` | |
| <div class="file-info"> | |
| <div class="file-icon"> | |
| <i class="fas ${this.getFileIcon(file.name)}"></i> | |
| </div> | |
| <div class="file-details"> | |
| <div class="file-name">${this.escapeHtml(file.name)}</div> | |
| <div class="file-status ${status}"> | |
| <i class="fas ${statusIcon[status]}"></i> | |
| ${statusText[status]} (${(file.size / 1024).toFixed(1)}KB) | |
| </div> | |
| </div> | |
| </div> | |
| <div class="file-actions"> | |
| <button class="file-action-btn view" title="View details" onclick="viewFileDetails('${fileId}')"> | |
| <i class="fas fa-eye"></i> | |
| </button> | |
| <button class="file-action-btn remove" title="Remove file" onclick="removeFile('${fileId}')"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| `; | |
| this.uploadedFiles.appendChild(fileElement); | |
| } | |
| updateFileStatus(fileId, status, analysis) { | |
| const fileElement = document.querySelector(`[data-file-id="${fileId}"]`); | |
| if (!fileElement) return; | |
| const statusElement = fileElement.querySelector('.file-status'); | |
| const statusText = { | |
| 'safe': 'Safe', | |
| 'unsafe': 'Unsafe' | |
| }; | |
| const statusIcon = { | |
| 'safe': 'fa-check-circle', | |
| 'unsafe': 'fa-exclamation-triangle' | |
| }; | |
| statusElement.className = `file-status ${status}`; | |
| if (analysis && analysis.guardrail_analysis) { | |
| const chunks = analysis.guardrail_analysis.chunks_analyzed || 0; | |
| const unsafe = analysis.guardrail_analysis.chunks_unsafe || 0; | |
| const confidence = analysis.guardrail_analysis.max_confidence || 0; | |
| statusElement.innerHTML = ` | |
| <i class="fas ${statusIcon[status]}"></i> | |
| ${statusText[status]} ${chunks > 0 ? `(${chunks} chunks, max conf: ${(confidence * 100).toFixed(1)}%)` : ''} | |
| `; | |
| } else if (analysis && analysis.error) { | |
| statusElement.innerHTML = ` | |
| <i class="fas fa-exclamation-triangle"></i> | |
| Error: ${analysis.error} | |
| `; | |
| } else { | |
| statusElement.innerHTML = ` | |
| <i class="fas ${statusIcon[status]}"></i> | |
| ${statusText[status]} | |
| `; | |
| } | |
| } | |
| removeFile(fileId) { | |
| console.log('Removing file with ID:', fileId); | |
| console.log('Current attachments before removal:', this.attachments.map(att => ({ id: att.id, filename: att.filename, is_safe: att.is_safe }))); | |
| // Remove from UI | |
| const fileElement = document.querySelector(`[data-file-id="${fileId}"]`); | |
| if (fileElement) { | |
| fileElement.remove(); | |
| } | |
| // Remove from attachments array | |
| const originalLength = this.attachments.length; | |
| this.attachments = this.attachments.filter(att => att.id !== fileId); | |
| console.log('Attachments after removal:', this.attachments.map(att => ({ id: att.id, filename: att.filename, is_safe: att.is_safe }))); | |
| console.log(`Removed ${originalLength - this.attachments.length} attachment(s)`); | |
| // Hide upload section if no files | |
| if (this.attachments.length === 0) { | |
| this.fileUploadSection.classList.remove('show'); | |
| this.attachButton.classList.remove('active'); | |
| } | |
| } | |
| getFileIcon(filename) { | |
| const ext = filename.toLowerCase().split('.').pop(); | |
| switch(ext) { | |
| case 'pdf': | |
| return 'fa-file-pdf'; | |
| case 'docx': | |
| return 'fa-file-word'; | |
| case 'txt': | |
| case 'text': | |
| return 'fa-file-alt'; | |
| case 'md': | |
| return 'fa-file-code'; | |
| case 'rtf': | |
| return 'fa-file-word'; | |
| default: | |
| return 'fa-file'; | |
| } | |
| } | |
| viewFileDetails(fileId) { | |
| const attachment = this.attachments.find(att => att.id === fileId); | |
| if (!attachment) return; | |
| // Create a modal or detailed view - for now, just log to console | |
| console.log('File Analysis Details:', attachment.analysis); | |
| // You could create a modal here to show detailed analysis | |
| alert(`File: ${attachment.filename}\nSafe: ${attachment.is_safe}\nSee console for detailed analysis.`); | |
| } | |
| } | |
| // Global functions | |
| function toggleMessageDetails(messageId) { | |
| const detailsElement = document.getElementById(`details-${messageId}`); | |
| const toggleButton = document.querySelector(`[data-message-id="${messageId}"]`); | |
| if (detailsElement && toggleButton) { | |
| const isOpen = detailsElement.classList.contains('open'); | |
| if (isOpen) { | |
| detailsElement.classList.remove('open'); | |
| toggleButton.classList.remove('active'); | |
| } else { | |
| detailsElement.classList.add('open'); | |
| toggleButton.classList.add('active'); | |
| } | |
| } | |
| } | |
| function removeFile(fileId) { | |
| // Find the chat instance and call removeFile | |
| if (window.chatInstance) { | |
| window.chatInstance.removeFile(fileId); | |
| } | |
| } | |
| function viewFileDetails(fileId) { | |
| // Find the chat instance and call viewFileDetails | |
| if (window.chatInstance) { | |
| window.chatInstance.viewFileDetails(fileId); | |
| } | |
| } | |
| // Initialize the chat application when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', () => { | |
| window.chatInstance = new GuardrailsChat(); | |
| }); |