Download index.html from Matty20/citadely: direct link, hf CLI and curl.
- Browser
- Download file 41 kB
-
https://huggingface.co/spaces/Matty20/citadely/resolve/main/index.html
- Command line
-
hf download hf://spaces/Matty20/citadely/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Matty20/citadely/resolve/main/index.html
41 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>RAG System Manager</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .file-upload { | |
| border: 2px dashed #cbd5e0; | |
| transition: all 0.3s ease; | |
| } | |
| .file-upload:hover { | |
| border-color: #4f46e5; | |
| background-color: #f8fafc; | |
| } | |
| .file-upload.dragover { | |
| border-color: #4f46e5; | |
| background-color: #e0e7ff; | |
| } | |
| .chat-message.user { | |
| background-color: #e0e7ff; | |
| border-radius: 1rem 1rem 0 1rem; | |
| } | |
| .chat-message.assistant { | |
| background-color: #f1f5f9; | |
| border-radius: 1rem 1rem 1rem 0; | |
| } | |
| .progress-bar { | |
| transition: width 0.3s ease; | |
| } | |
| #knowledge-graph { | |
| height: 400px; | |
| background-color: #f8fafc; | |
| border-radius: 0.5rem; | |
| } | |
| .document-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| } | |
| .animate-pulse { | |
| animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0.5; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="mb-8"> | |
| <div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4"> | |
| <div> | |
| <h1 class="text-3xl font-bold text-indigo-700">RAG System Manager</h1> | |
| <p class="text-gray-600">Upload documents to enhance your LLM's knowledge</p> | |
| </div> | |
| <div class="flex items-center gap-4"> | |
| <div class="relative"> | |
| <select id="model-selector" class="appearance-none bg-white border border-gray-300 rounded-md py-2 pl-3 pr-8 text-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500"> | |
| <option value="gpt-4">GPT-4</option> | |
| <option value="gpt-3.5" selected>GPT-3.5</option> | |
| <option value="claude-2">Claude 2</option> | |
| <option value="llama-2">Llama 2</option> | |
| </select> | |
| <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"> | |
| <i class="fas fa-chevron-down"></i> | |
| </div> | |
| </div> | |
| <button id="clear-chat" class="bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded-md transition flex items-center gap-2"> | |
| <i class="fas fa-trash-alt"></i> Clear | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
| <!-- Left Panel - Document Management --> | |
| <div class="lg:col-span-1 space-y-6"> | |
| <!-- Upload Section --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Upload Documents</h2> | |
| <div id="drop-area" class="file-upload rounded-lg p-6 text-center cursor-pointer"> | |
| <input type="file" id="file-input" class="hidden" multiple accept=".pdf,.txt,.docx,.pptx,.xlsx,.csv"> | |
| <div class="flex flex-col items-center justify-center py-8"> | |
| <i class="fas fa-cloud-upload-alt text-4xl text-indigo-500 mb-3"></i> | |
| <p class="text-gray-600 mb-2">Drag & drop files here or click to browse</p> | |
| <p class="text-sm text-gray-500">Supported formats: PDF, TXT, DOCX, PPTX, XLSX, CSV</p> | |
| <button id="browse-btn" class="mt-4 bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md transition"> | |
| Select Files | |
| </button> | |
| </div> | |
| </div> | |
| <div id="upload-progress" class="mt-4 hidden"> | |
| <div class="flex justify-between text-sm text-gray-600 mb-1"> | |
| <span>Uploading...</span> | |
| <span id="progress-percent">0%</span> | |
| </div> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5"> | |
| <div id="progress-bar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Document List --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold text-gray-800">Knowledge Base</h2> | |
| <div class="text-sm text-gray-500" id="document-count">0 documents</div> | |
| </div> | |
| <div id="document-list" class="space-y-3 max-h-96 overflow-y-auto"> | |
| <div class="text-center py-8 text-gray-500" id="empty-state"> | |
| <i class="fas fa-folder-open text-3xl mb-2"></i> | |
| <p>No documents uploaded yet</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Knowledge Graph Visualization --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Knowledge Graph</h2> | |
| <div id="knowledge-graph" class="flex items-center justify-center"> | |
| <div class="text-center text-gray-500"> | |
| <i class="fas fa-project-diagram text-3xl mb-2"></i> | |
| <p>Graph will appear after document processing</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Panel - Chat Interface --> | |
| <div class="lg:col-span-2"> | |
| <div class="bg-white rounded-xl shadow-md h-full flex flex-col"> | |
| <!-- Chat Header --> | |
| <div class="border-b border-gray-200 p-4"> | |
| <div class="flex items-center justify-between"> | |
| <h2 class="text-xl font-semibold text-gray-800">RAG Chat</h2> | |
| <div class="flex items-center gap-2"> | |
| <span id="rag-status" class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800"> | |
| <span class="w-2 h-2 rounded-full bg-green-500 mr-1"></span> | |
| RAG Enabled | |
| </span> | |
| <button id="toggle-rag" class="text-indigo-600 hover:text-indigo-800"> | |
| <i class="fas fa-toggle-on"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <p class="text-sm text-gray-500 mt-1">Ask questions based on your uploaded documents</p> | |
| </div> | |
| <!-- Chat Messages --> | |
| <div id="chat-container" class="flex-1 p-4 overflow-y-auto space-y-4"> | |
| <div class="chat-message assistant p-4 max-w-[80%]"> | |
| <div class="flex items-start gap-3"> | |
| <div class="flex-shrink-0 bg-indigo-100 text-indigo-800 rounded-full w-8 h-8 flex items-center justify-center"> | |
| <i class="fas fa-robot"></i> | |
| </div> | |
| <div> | |
| <p class="font-medium text-gray-800">RAG Assistant</p> | |
| <p class="text-gray-700 mt-1">Hello! I'm your RAG assistant. Upload documents to enhance my knowledge, then ask me anything about their content.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Chat Input --> | |
| <div class="border-t border-gray-200 p-4"> | |
| <form id="chat-form" class="flex gap-2"> | |
| <div class="relative flex-1"> | |
| <textarea id="message-input" rows="1" class="w-full border border-gray-300 rounded-md py-2 pl-4 pr-10 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 resize-none" placeholder="Type your question..."></textarea> | |
| <button type="button" id="attach-file" class="absolute right-2 bottom-2 text-gray-500 hover:text-indigo-600"> | |
| <i class="fas fa-paperclip"></i> | |
| </button> | |
| </div> | |
| <button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md transition flex items-center justify-center w-12 h-12"> | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </form> | |
| <div class="flex justify-between mt-2"> | |
| <div class="text-xs text-gray-500"> | |
| <span id="char-count">0</span>/1000 | |
| </div> | |
| <div class="text-xs text-gray-500"> | |
| <i class="fas fa-info-circle mr-1"></i> Press Shift+Enter for new line | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Document Preview Modal --> | |
| <div id="preview-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white rounded-xl shadow-xl w-full max-w-4xl max-h-[90vh] flex flex-col"> | |
| <div class="flex justify-between items-center border-b border-gray-200 p-4"> | |
| <h3 class="text-lg font-semibold text-gray-800" id="preview-title">Document Preview</h3> | |
| <button id="close-preview" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div id="preview-content" class="flex-1 overflow-auto p-4"> | |
| <!-- Content will be loaded here --> | |
| </div> | |
| <div class="border-t border-gray-200 p-4 flex justify-end"> | |
| <button id="delete-document" class="bg-red-100 hover:bg-red-200 text-red-700 py-2 px-4 rounded-md transition flex items-center gap-2"> | |
| <i class="fas fa-trash-alt"></i> Delete Document | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // DOM Elements | |
| const dropArea = document.getElementById('drop-area'); | |
| const fileInput = document.getElementById('file-input'); | |
| const browseBtn = document.getElementById('browse-btn'); | |
| const uploadProgress = document.getElementById('upload-progress'); | |
| const progressBar = document.getElementById('progress-bar'); | |
| const progressPercent = document.getElementById('progress-percent'); | |
| const documentList = document.getElementById('document-list'); | |
| const emptyState = document.getElementById('empty-state'); | |
| const documentCount = document.getElementById('document-count'); | |
| const chatForm = document.getElementById('chat-form'); | |
| const messageInput = document.getElementById('message-input'); | |
| const chatContainer = document.getElementById('chat-container'); | |
| const charCount = document.getElementById('char-count'); | |
| const attachFile = document.getElementById('attach-file'); | |
| const clearChat = document.getElementById('clear-chat'); | |
| const toggleRag = document.getElementById('toggle-rag'); | |
| const ragStatus = document.getElementById('rag-status'); | |
| const previewModal = document.getElementById('preview-modal'); | |
| const closePreview = document.getElementById('close-preview'); | |
| const previewContent = document.getElementById('preview-content'); | |
| const previewTitle = document.getElementById('preview-title'); | |
| const deleteDocument = document.getElementById('delete-document'); | |
| const modelSelector = document.getElementById('model-selector'); | |
| // State | |
| let documents = []; | |
| let ragEnabled = true; | |
| let currentPreviewDocId = null; | |
| // Event Listeners | |
| browseBtn.addEventListener('click', () => fileInput.click()); | |
| fileInput.addEventListener('change', handleFiles); | |
| // Drag and drop events | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| dropArea.addEventListener(eventName, preventDefaults, false); | |
| }); | |
| function preventDefaults(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| dropArea.addEventListener(eventName, highlight, false); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| dropArea.addEventListener(eventName, unhighlight, false); | |
| }); | |
| function highlight() { | |
| dropArea.classList.add('dragover'); | |
| } | |
| function unhighlight() { | |
| dropArea.classList.remove('dragover'); | |
| } | |
| dropArea.addEventListener('drop', handleDrop, false); | |
| function handleDrop(e) { | |
| const dt = e.dataTransfer; | |
| const files = dt.files; | |
| handleFiles({ target: { files } }); | |
| } | |
| // Handle file uploads | |
| function handleFiles(e) { | |
| const files = Array.from(e.target.files); | |
| if (files.length === 0) return; | |
| // Simulate upload process | |
| uploadProgress.classList.remove('hidden'); | |
| let loaded = 0; | |
| const total = files.length * 100; // Each file has 100 "units" to load | |
| files.forEach((file, index) => { | |
| // Simulate processing each file | |
| setTimeout(() => { | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| const content = e.target.result; | |
| // Create document object | |
| const doc = { | |
| id: Date.now() + index, | |
| name: file.name, | |
| type: file.type, | |
| size: formatFileSize(file.size), | |
| uploadDate: new Date().toLocaleDateString(), | |
| content: content.substring(0, 500) + '...', // Store preview | |
| fullContent: content, // Store full content (simulated) | |
| embeddings: generateRandomEmbeddings(), // Simulate embeddings | |
| processed: true | |
| }; | |
| documents.push(doc); | |
| renderDocument(doc); | |
| updateDocumentCount(); | |
| // Update progress | |
| loaded += 100; | |
| const percent = Math.min(Math.round((loaded / total) * 100), 100); | |
| progressBar.style.width = `${percent}%`; | |
| progressPercent.textContent = `${percent}%`; | |
| if (loaded >= total) { | |
| setTimeout(() => { | |
| uploadProgress.classList.add('hidden'); | |
| progressBar.style.width = '0%'; | |
| progressPercent.textContent = '0%'; | |
| }, 500); | |
| } | |
| }; | |
| // Simulate different processing for different file types | |
| if (file.type === 'application/pdf') { | |
| setTimeout(() => reader.readAsDataURL(file), 1000); | |
| } else if (file.type.includes('text') || file.name.endsWith('.txt')) { | |
| setTimeout(() => reader.readAsText(file), 800); | |
| } else { | |
| setTimeout(() => reader.readAsDataURL(file), 1200); | |
| } | |
| }, index * 300); | |
| }); | |
| } | |
| // Format file size | |
| function formatFileSize(bytes) { | |
| if (bytes === 0) return '0 Bytes'; | |
| const k = 1024; | |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| // Generate random embeddings (simulated) | |
| function generateRandomEmbeddings() { | |
| const embeddings = []; | |
| for (let i = 0; i < 5; i++) { | |
| embeddings.push({ | |
| text: `Key concept ${i+1}`, | |
| score: (Math.random() * 0.5 + 0.5).toFixed(2) | |
| }); | |
| } | |
| return embeddings; | |
| } | |
| // Render document to the list | |
| function renderDocument(doc) { | |
| emptyState.classList.add('hidden'); | |
| const docElement = document.createElement('div'); | |
| docElement.className = 'document-card bg-white border border-gray-200 rounded-lg p-4 hover:shadow-md transition cursor-pointer'; | |
| docElement.innerHTML = ` | |
| <div class="flex justify-between items-start"> | |
| <div class="flex-1 min-w-0"> | |
| <div class="flex items-center gap-2 mb-1"> | |
| <i class="${getFileIcon(doc.name)} text-indigo-500"></i> | |
| <p class="text-sm font-medium text-gray-900 truncate">${doc.name}</p> | |
| </div> | |
| <div class="flex items-center gap-3 text-xs text-gray-500"> | |
| <span>${doc.size}</span> | |
| <span>•</span> | |
| <span>${doc.uploadDate}</span> | |
| <span>•</span> | |
| <span class="flex items-center ${doc.processed ? 'text-green-600' : 'text-yellow-600'}"> | |
| <i class="fas ${doc.processed ? 'fa-check-circle' : 'fa-spinner fa-spin'} mr-1"></i> | |
| ${doc.processed ? 'Processed' : 'Processing'} | |
| </span> | |
| </div> | |
| </div> | |
| <button class="text-gray-400 hover:text-indigo-600 preview-btn" data-id="${doc.id}"> | |
| <i class="fas fa-eye"></i> | |
| </button> | |
| </div> | |
| ${doc.processed ? ` | |
| <div class="mt-3"> | |
| <div class="text-xs text-gray-500 mb-1">Key concepts:</div> | |
| <div class="flex flex-wrap gap-1"> | |
| ${doc.embeddings.slice(0, 3).map(e => ` | |
| <span class="bg-indigo-100 text-indigo-800 text-xs px-2 py-0.5 rounded-full"> | |
| ${e.text} | |
| </span> | |
| `).join('')} | |
| ${doc.embeddings.length > 3 ? `<span class="bg-gray-100 text-gray-800 text-xs px-2 py-0.5 rounded-full">+${doc.embeddings.length - 3}</span>` : ''} | |
| </div> | |
| </div> | |
| ` : ''} | |
| `; | |
| documentList.prepend(docElement); | |
| // Add event listener to preview button | |
| docElement.querySelector('.preview-btn').addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| showDocumentPreview(doc.id); | |
| }); | |
| docElement.addEventListener('click', () => { | |
| showDocumentPreview(doc.id); | |
| }); | |
| } | |
| // Get file icon based on extension | |
| function getFileIcon(filename) { | |
| const extension = filename.split('.').pop().toLowerCase(); | |
| switch(extension) { | |
| case 'pdf': return 'fas fa-file-pdf'; | |
| case 'txt': return 'fas fa-file-alt'; | |
| case 'docx': return 'fas fa-file-word'; | |
| case 'xlsx': return 'fas fa-file-excel'; | |
| case 'pptx': return 'fas fa-file-powerpoint'; | |
| case 'csv': return 'fas fa-file-csv'; | |
| default: return 'fas fa-file'; | |
| } | |
| } | |
| // Update document count | |
| function updateDocumentCount() { | |
| const count = documents.length; | |
| documentCount.textContent = `${count} document${count !== 1 ? 's' : ''}`; | |
| // Update knowledge graph placeholder | |
| const graphPlaceholder = document.querySelector('#knowledge-graph > div'); | |
| if (count > 0) { | |
| graphPlaceholder.innerHTML = ` | |
| <div class="text-center"> | |
| <div class="animate-pulse mb-4"> | |
| <div class="mx-auto bg-indigo-200 rounded-full w-24 h-24 flex items-center justify-center"> | |
| <i class="fas fa-project-diagram text-3xl text-indigo-600"></i> | |
| </div> | |
| </div> | |
| <p>Building knowledge graph...</p> | |
| <p class="text-sm text-gray-500 mt-1">${count} document${count !== 1 ? 's' : ''} processed</p> | |
| </div> | |
| `; | |
| // Simulate graph generation | |
| setTimeout(() => { | |
| graphPlaceholder.innerHTML = ` | |
| <div class="w-full h-full flex items-center justify-center"> | |
| <div class="text-center"> | |
| <img src="https://via.placeholder.com/400x300/4f46e5/ffffff?text=Knowledge+Graph" alt="Knowledge Graph" class="rounded-lg mb-2"> | |
| <p class="text-sm text-gray-600">Visual representation of document relationships</p> | |
| </div> | |
| </div> | |
| `; | |
| }, 2000); | |
| } | |
| } | |
| // Show document preview | |
| function showDocumentPreview(docId) { | |
| const doc = documents.find(d => d.id === docId); | |
| if (!doc) return; | |
| currentPreviewDocId = docId; | |
| previewTitle.textContent = doc.name; | |
| // Display different content based on file type | |
| if (doc.type === 'application/pdf') { | |
| previewContent.innerHTML = ` | |
| <div class="text-center py-8"> | |
| <i class="fas fa-file-pdf text-5xl text-red-500 mb-4"></i> | |
| <p class="text-gray-700">PDF preview would be displayed here</p> | |
| <p class="text-sm text-gray-500 mt-2">Content extract:</p> | |
| <div class="mt-2 p-3 bg-gray-50 rounded text-left text-sm text-gray-700"> | |
| ${doc.content} | |
| </div> | |
| </div> | |
| `; | |
| } else if (doc.type.includes('text') || doc.name.endsWith('.txt')) { | |
| previewContent.innerHTML = ` | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <pre class="text-sm text-gray-800 whitespace-pre-wrap">${doc.fullContent.substring(0, 2000)}${doc.fullContent.length > 2000 ? '...' : ''}</pre> | |
| </div> | |
| ${doc.fullContent.length > 2000 ? `<p class="text-xs text-gray-500 mt-2">Document truncated - showing first 2000 characters</p>` : ''} | |
| `; | |
| } else { | |
| previewContent.innerHTML = ` | |
| <div class="text-center py-8"> | |
| <i class="${getFileIcon(doc.name)} text-5xl text-indigo-500 mb-4"></i> | |
| <p class="text-gray-700">Preview for ${doc.name}</p> | |
| <p class="text-sm text-gray-500 mt-2">Content extract:</p> | |
| <div class="mt-2 p-3 bg-gray-50 rounded text-left text-sm text-gray-700"> | |
| ${doc.content} | |
| </div> | |
| </div> | |
| `; | |
| } | |
| previewModal.classList.remove('hidden'); | |
| } | |
| // Close preview modal | |
| closePreview.addEventListener('click', () => { | |
| previewModal.classList.add('hidden'); | |
| currentPreviewDocId = null; | |
| }); | |
| // Delete document | |
| deleteDocument.addEventListener('click', () => { | |
| if (!currentPreviewDocId) return; | |
| documents = documents.filter(d => d.id !== currentPreviewDocId); | |
| const docElement = document.querySelector(`.preview-btn[data-id="${currentPreviewDocId}"]`)?.closest('.document-card'); | |
| if (docElement) docElement.remove(); | |
| if (documents.length === 0) { | |
| emptyState.classList.remove('hidden'); | |
| } | |
| updateDocumentCount(); | |
| previewModal.classList.add('hidden'); | |
| currentPreviewDocId = null; | |
| // Show notification | |
| showNotification('Document deleted successfully', 'success'); | |
| }); | |
| // Chat functionality | |
| chatForm.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const message = messageInput.value.trim(); | |
| if (!message) return; | |
| // Add user message to chat | |
| addMessageToChat(message, 'user'); | |
| messageInput.value = ''; | |
| updateCharCount(); | |
| // Show typing indicator | |
| const typingId = showTypingIndicator(); | |
| // Generate response (simulated) | |
| setTimeout(() => { | |
| removeTypingIndicator(typingId); | |
| // Check if we have documents for RAG | |
| const hasDocuments = documents.length > 0; | |
| const useRag = ragEnabled && hasDocuments; | |
| // Generate response based on context | |
| let response; | |
| if (useRag) { | |
| // Simulate RAG response with document context | |
| const relevantDocs = documents.slice(0, Math.min(2, documents.length)); | |
| const docContext = relevantDocs.map(doc => | |
| `Document: ${doc.name}\nExtract: ${doc.content.substring(0, 200)}...` | |
| ).join('\n\n'); | |
| response = { | |
| text: `Based on the documents you've provided, I can answer your question about "${message}". Here's what I found:\n\n${generateSimulatedAnswer(message)}\n\nI've used information from ${relevantDocs.length} document${relevantDocs.length !== 1 ? 's' : ''} in my response.`, | |
| sources: relevantDocs.map(doc => doc.name), | |
| confidence: (Math.random() * 0.3 + 0.7).toFixed(2) | |
| }; | |
| } else { | |
| // Standard response without RAG | |
| response = { | |
| text: ragEnabled && !hasDocuments | |
| ? `I can't use my RAG capabilities because you haven't uploaded any documents yet. Please upload relevant documents to get more accurate answers about: "${message}".\n\nHere's my general knowledge response:\n${generateSimulatedAnswer(message)}` | |
| : generateSimulatedAnswer(message), | |
| sources: [], | |
| confidence: (Math.random() * 0.2 + 0.5).toFixed(2) | |
| }; | |
| } | |
| addMessageToChat(response.text, 'assistant', response.sources); | |
| }, 1500 + Math.random() * 2000); | |
| }); | |
| // Add message to chat | |
| function addMessageToChat(text, role, sources = []) { | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `chat-message ${role} p-4 max-w-[80%] ${role === 'user' ? 'ml-auto' : ''}`; | |
| messageDiv.innerHTML = ` | |
| <div class="flex items-start gap-3"> | |
| <div class="flex-shrink-0 ${role === 'user' ? 'bg-indigo-100 text-indigo-800' : 'bg-gray-200 text-gray-800'} rounded-full w-8 h-8 flex items-center justify-center"> | |
| <i class="fas ${role === 'user' ? 'fa-user' : 'fa-robot'}"></i> | |
| </div> | |
| <div class="flex-1 min-w-0"> | |
| <p class="font-medium text-gray-800">${role === 'user' ? 'You' : 'RAG Assistant'}</p> | |
| <p class="text-gray-700 mt-1 whitespace-pre-wrap">${text}</p> | |
| ${sources.length > 0 ? ` | |
| <div class="mt-3 pt-2 border-t border-gray-200"> | |
| <p class="text-xs text-gray-500 mb-1">Sources used:</p> | |
| <div class="flex flex-wrap gap-1"> | |
| ${sources.map(src => ` | |
| <span class="bg-gray-100 text-gray-800 text-xs px-2 py-0.5 rounded-full flex items-center"> | |
| <i class="fas fa-file-alt mr-1 text-gray-500"></i> | |
| ${src} | |
| </span> | |
| `).join('')} | |
| </div> | |
| </div> | |
| ` : ''} | |
| </div> | |
| </div> | |
| `; | |
| chatContainer.appendChild(messageDiv); | |
| messageDiv.scrollIntoView({ behavior: 'smooth' }); | |
| } | |
| // Generate simulated answer | |
| function generateSimulatedAnswer(question) { | |
| const answers = [ | |
| `The answer to "${question}" depends on several factors that need to be considered. Based on available information, the most likely explanation is related to the contextual data provided in the documents.`, | |
| `When examining "${question}", we find that the key aspects involve multiple interacting components. The documents suggest a correlation between these factors.`, | |
| `Analysis of "${question}" reveals interesting patterns. The data indicates a strong relationship between the variables mentioned in your query.`, | |
| `Regarding "${question}", the evidence points toward a comprehensive solution that addresses all aspects of the problem space.`, | |
| `The question about "${question}" is quite insightful. My understanding is that this involves a complex system with many moving parts.` | |
| ]; | |
| return answers[Math.floor(Math.random() * answers.length)]; | |
| } | |
| // Show typing indicator | |
| function showTypingIndicator() { | |
| const id = 'typing-' + Date.now(); | |
| const typingDiv = document.createElement('div'); | |
| typingDiv.id = id; | |
| typingDiv.className = 'chat-message assistant p-4 max-w-[80%]'; | |
| typingDiv.innerHTML = ` | |
| <div class="flex items-start gap-3"> | |
| <div class="flex-shrink-0 bg-gray-200 text-gray-800 rounded-full w-8 h-8 flex items-center justify-center"> | |
| <i class="fas fa-robot"></i> | |
| </div> | |
| <div> | |
| <p class="font-medium text-gray-800">RAG Assistant</p> | |
| <div class="flex space-x-1 mt-2"> | |
| <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div> | |
| <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.2s"></div> | |
| <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.4s"></div> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| chatContainer.appendChild(typingDiv); | |
| typingDiv.scrollIntoView({ behavior: 'smooth' }); | |
| return id; | |
| } | |
| // Remove typing indicator | |
| function removeTypingIndicator(id) { | |
| const element = document.getElementById(id); | |
| if (element) element.remove(); | |
| } | |
| // Character count for message input | |
| messageInput.addEventListener('input', updateCharCount); | |
| function updateCharCount() { | |
| const count = messageInput.value.length; | |
| charCount.textContent = count; | |
| if (count > 1000) { | |
| charCount.classList.add('text-red-600'); | |
| charCount.classList.remove('text-gray-500'); | |
| } else { | |
| charCount.classList.add('text-gray-500'); | |
| charCount.classList.remove('text-red-600'); | |
| } | |
| } | |
| // Allow Shift+Enter for new line | |
| messageInput.addEventListener('keydown', function(e) { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| chatForm.dispatchEvent(new Event('submit')); | |
| } | |
| }); | |
| // Attach file to chat | |
| attachFile.addEventListener('click', () => { | |
| fileInput.click(); | |
| fileInput.onchange = function() { | |
| const files = Array.from(fileInput.files); | |
| if (files.length === 0) return; | |
| files.forEach(file => { | |
| addMessageToChat(`Attached file: ${file.name} (${formatFileSize(file.size)})`, 'user'); | |
| }); | |
| }; | |
| }); | |
| // Clear chat | |
| clearChat.addEventListener('click', () => { | |
| chatContainer.innerHTML = ` | |
| <div class="chat-message assistant p-4 max-w-[80%]"> | |
| <div class="flex items-start gap-3"> | |
| <div class="flex-shrink-0 bg-indigo-100 text-indigo-800 rounded-full w-8 h-8 flex items-center justify-center"> | |
| <i class="fas fa-robot"></i> | |
| </div> | |
| <div> | |
| <p class="font-medium text-gray-800">RAG Assistant</p> | |
| <p class="text-gray-700 mt-1">Hello! I'm your RAG assistant. Upload documents to enhance my knowledge, then ask me anything about their content.</p> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| // Toggle RAG | |
| toggleRag.addEventListener('click', () => { | |
| ragEnabled = !ragEnabled; | |
| if (ragEnabled) { | |
| toggleRag.innerHTML = '<i class="fas fa-toggle-on"></i>'; | |
| ragStatus.className = 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800'; | |
| ragStatus.innerHTML = '<span class="w-2 h-2 rounded-full bg-green-500 mr-1"></span> RAG Enabled'; | |
| showNotification('RAG mode enabled', 'success'); | |
| } else { | |
| toggleRag.innerHTML = '<i class="fas fa-toggle-off"></i>'; | |
| ragStatus.className = 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800'; | |
| ragStatus.innerHTML = '<span class="w-2 h-2 rounded-full bg-gray-500 mr-1"></span> RAG Disabled'; | |
| showNotification('RAG mode disabled - using standard responses', 'info'); | |
| } | |
| }); | |
| // Show notification | |
| function showNotification(message, type = 'info') { | |
| const notification = document.createElement('div'); | |
| notification.className = `fixed bottom-4 right-4 px-4 py-2 rounded-md shadow-md text-white ${ | |
| type === 'success' ? 'bg-green-500' : | |
| type === 'error' ? 'bg-red-500' : | |
| type === 'warning' ? 'bg-yellow-500' : 'bg-indigo-500' | |
| } flex items-center gap-2`; | |
| notification.innerHTML = ` | |
| <i class="fas ${ | |
| type === 'success' ? 'fa-check-circle' : | |
| type === 'error' ? 'fa-exclamation-circle' : | |
| type === 'warning' ? 'fa-exclamation-triangle' : 'fa-info-circle' | |
| }"></i> | |
| <span>${message}</span> | |
| `; | |
| document.body.appendChild(notification); | |
| setTimeout(() => { | |
| notification.classList.add('opacity-0', 'transition-opacity', 'duration-300'); | |
| setTimeout(() => notification.remove(), 300); | |
| }, 3000); | |
| } | |
| // Initialize | |
| updateCharCount(); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Matty20/citadely" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |