citadely / index.html
Matty20's picture
Add 2 files
c5df89b verified
Raw
History Blame Contribute Delete
41 kB
<!DOCTYPE html>
<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>