Redfire-1234's picture
Update frontend/index.html
91e1079 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Google Docs Knowledge Chatbot</title>
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<div class="container">
<div class="header">
<h1>πŸ“š Google Docs Knowledge Bot</h1>
<p class="subtitle">Ask questions about your Google Drive folder</p>
</div>
<div class="index-section">
<h3>πŸ“ Folder-Based RAG System</h3>
<div class="info-box">
<p><strong>How it works:</strong></p>
<ol>
<li>Share a Google Drive folder with your service account</li>
<li>Add Google Docs to that folder</li>
<li>Click "Index All Documents" below</li>
<li>Ask questions - the bot searches across ALL your docs!</li>
</ol>
</div>
<div class="button-group">
<button onclick="indexAllDocuments()" id="index-all-btn" class="btn btn-primary">
πŸ“₯ Index All Documents
</button>
<button onclick="listDocuments()" id="list-btn" class="btn btn-secondary">
πŸ“‹ View Documents
</button>
<button onclick="reindexAll()" id="reindex-btn" class="btn btn-tertiary">
πŸ”„ Re-Index
</button>
</div>
<div id="index-status" class="status-message"></div>
<div id="documents-list" class="documents-container"></div>
</div>
<div class="chat-section">
<h3>πŸ’¬ Ask Questions</h3>
<div class="chat-container">
<div class="messages" id="messages">
<div class="message bot-message">
<div class="message-content">
πŸ‘‹ Hello! I can answer questions about all documents in your Google Drive folder.<br><br>
Click "Index All Documents" to get started!
</div>
</div>
</div>
</div>
<div class="input-area">
<input
type="text"
id="question"
placeholder="Ask a question about your documents..."
class="question-input"
onkeypress="handleKeyPress(event)"
/>
<button onclick="sendMessage()" id="send-btn" class="btn btn-secondary">
Send
</button>
</div>
</div>
</div>
<script>
let isIndexed = false;
let conversationHistory = []; // Store last 5 exchanges
async function listDocuments() {
const listBtn = document.getElementById('list-btn');
const docsList = document.getElementById('documents-list');
listBtn.disabled = true;
listBtn.textContent = 'Loading...';
docsList.innerHTML = '<div class="loading">Fetching documents...</div>';
try {
const response = await fetch('/documents');
const docs = await response.json();
if (response.ok) {
if (docs.length === 0) {
docsList.innerHTML = '<div class="info">No documents found in the configured folder.</div>';
} else {
let html = '<div class="docs-header">πŸ“„ Documents in Folder:</div><ul class="docs-list">';
docs.forEach(doc => {
const status = doc.indexed ? 'βœ…' : '⏳';
html += `<li>${status} <strong>${doc.name}</strong><br><small>Modified: ${new Date(doc.modified).toLocaleString()}</small></li>`;
});
html += '</ul>';
docsList.innerHTML = html;
}
} else {
docsList.innerHTML = `<div class="error">Error: ${docs.detail}</div>`;
}
} catch (error) {
docsList.innerHTML = `<div class="error">Error: ${error.message}</div>`;
} finally {
listBtn.disabled = false;
listBtn.textContent = 'πŸ“‹ View Documents';
}
}
async function indexAllDocuments() {
const statusDiv = document.getElementById('index-status');
const indexBtn = document.getElementById('index-all-btn');
indexBtn.disabled = true;
indexBtn.textContent = '⏳ Indexing...';
statusDiv.innerHTML = '<span class="info">πŸ”„ Indexing all documents in your folder... This may take a minute.</span>';
try {
const response = await fetch('/index-all', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
const data = await response.json();
if (response.ok) {
isIndexed = true;
let statusHtml = `<span class="success">βœ… ${data.message}<br>
πŸ“Š Documents processed: ${data.documents_processed}`;
if (data.total_documents) {
statusHtml += ` / ${data.total_documents}`;
}
statusHtml += `<br>πŸ“¦ Total chunks indexed: ${data.chunks_indexed}</span>`;
// Show warnings if any documents failed
if (data.warnings && data.warnings.failed_documents) {
statusHtml += '<div class="warning-box"><strong>⚠️ Warnings:</strong><ul>';
data.warnings.failed_documents.forEach(doc => {
statusHtml += `<li><strong>${doc.name}:</strong> ${doc.error}</li>`;
});
statusHtml += '</ul></div>';
}
statusDiv.innerHTML = statusHtml;
// Enable chat
document.getElementById('question').disabled = false;
document.getElementById('send-btn').disabled = false;
// Clear conversation history on new index
conversationHistory = [];
// Refresh document list
await listDocuments();
} else {
// Handle detailed error responses
let errorHtml = '<span class="error">';
if (data.detail && typeof data.detail === 'object') {
errorHtml += `❌ <strong>${data.detail.error || 'Error'}</strong><br>`;
errorHtml += `${data.detail.message}<br>`;
if (data.detail.steps) {
errorHtml += '<br><strong>Steps to fix:</strong><ul>';
data.detail.steps.forEach(step => {
errorHtml += `<li>${step}</li>`;
});
errorHtml += '</ul>';
}
if (data.detail.failed_documents) {
errorHtml += '<br><strong>Failed documents:</strong><ul>';
data.detail.failed_documents.forEach(doc => {
errorHtml += `<li>${doc.name}: ${doc.error}</li>`;
});
errorHtml += '</ul>';
}
} else {
errorHtml += `❌ Error: ${data.detail || 'Unknown error'}`;
}
errorHtml += '</span>';
statusDiv.innerHTML = errorHtml;
}
} catch (error) {
statusDiv.innerHTML = `<span class="error">❌ Network Error: ${error.message}<br>Please check your connection and try again.</span>`;
} finally {
indexBtn.disabled = false;
indexBtn.textContent = 'πŸ“₯ Index All Documents';
}
}
async function reindexAll() {
if (!confirm('This will re-index all documents. Continue?')) {
return;
}
const statusDiv = document.getElementById('index-status');
const reindexBtn = document.getElementById('reindex-btn');
reindexBtn.disabled = true;
reindexBtn.textContent = '⏳ Re-indexing...';
statusDiv.innerHTML = '<span class="info">πŸ”„ Re-indexing all documents...</span>';
try {
const response = await fetch('/reindex', {
method: 'POST'
});
const data = await response.json();
if (response.ok) {
isIndexed = true;
statusDiv.innerHTML = `<span class="success">βœ… Re-indexing complete!<br>
πŸ“Š Documents: ${data.documents_processed}<br>
πŸ“¦ Chunks: ${data.chunks_indexed}</span>`;
// Clear conversation history on re-index
conversationHistory = [];
await listDocuments();
} else {
statusDiv.innerHTML = `<span class="error">❌ Error: ${data.detail}</span>`;
}
} catch (error) {
statusDiv.innerHTML = `<span class="error">❌ Error: ${error.message}</span>`;
} finally {
reindexBtn.disabled = false;
reindexBtn.textContent = 'πŸ”„ Re-Index';
}
}
async function sendMessage() {
const question = document.getElementById('question').value.trim();
const sendBtn = document.getElementById('send-btn');
if (!question) {
return;
}
if (!isIndexed) {
addMessage('Please index documents first by clicking "Index All Documents"!', 'bot');
return;
}
// Add user message
addMessage(question, 'user');
document.getElementById('question').value = '';
// Disable send button
sendBtn.disabled = true;
sendBtn.textContent = 'Thinking...';
try {
const response = await fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
question: question,
conversation_history: conversationHistory
})
});
const data = await response.json();
if (response.ok) {
// Check if it's a clarification question
if (data.is_clarification) {
addMessage(data.answer, 'bot', [], true);
// Don't add clarification to history
} else {
// Show rephrased query if available
let answerText = data.answer;
if (data.rephrased_query && data.rephrased_query !== question) {
answerText = `<em>Understanding your question as: "${data.rephrased_query}"</em><br><br>${answerText}`;
}
addMessage(answerText, 'bot', data.sources);
// Update conversation history (keep last 5 exchanges)
conversationHistory.push({
role: 'user',
content: question
});
conversationHistory.push({
role: 'assistant',
content: data.answer
});
// Keep only last 10 messages (5 exchanges)
if (conversationHistory.length > 10) {
conversationHistory = conversationHistory.slice(-10);
}
}
} else {
// Handle detailed error responses
let errorMsg = 'Error: ';
if (data.detail && typeof data.detail === 'object') {
errorMsg += `<strong>${data.detail.error || 'Unknown Error'}</strong><br>${data.detail.message}`;
if (data.detail.steps) {
errorMsg += '<br><br><strong>Try this:</strong><ul>';
data.detail.steps.forEach(step => {
errorMsg += `<li>${step}</li>`;
});
errorMsg += '</ul>';
}
// Special handling for rate limits
if (data.detail.retry_after) {
errorMsg += `<br><em>Please retry after: ${data.detail.retry_after}</em>`;
}
} else {
errorMsg += data.detail || 'Unknown error occurred';
}
addMessage(errorMsg, 'bot');
}
} catch (error) {
addMessage(`Network Error: ${error.message}<br>Please check your connection and try again.`, 'bot');
} finally {
sendBtn.disabled = false;
sendBtn.textContent = 'Send';
}
}
function addMessage(text, type, sources = [], isClarification = false) {
const messagesDiv = document.getElementById('messages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${type}-message`;
if (isClarification) {
messageDiv.classList.add('clarification-message');
}
let content = `<div class="message-content">${text}</div>`;
if (sources && sources.length > 0) {
content += '<div class="sources"><strong>πŸ“š Found in these documents:</strong><ul>';
sources.forEach(source => {
content += `<li>${source}</li>`;
});
content += '</ul></div>';
}
// Add copy button for bot messages
if (type === 'bot') {
content += `<button class="copy-btn" onclick="copyMessage(this)" title="Copy message">
πŸ“‹ Copy
</button>`;
}
messageDiv.innerHTML = content;
messagesDiv.appendChild(messageDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
function copyMessage(button) {
// Get the message content (excluding sources and copy button)
const messageDiv = button.closest('.message');
const messageContent = messageDiv.querySelector('.message-content');
const textToCopy = messageContent.innerText || messageContent.textContent;
// Copy to clipboard
navigator.clipboard.writeText(textToCopy).then(() => {
// Visual feedback
const originalText = button.textContent;
button.textContent = 'βœ… Copied!';
button.style.background = '#28a745';
setTimeout(() => {
button.textContent = originalText;
button.style.background = '';
}, 2000);
}).catch(err => {
console.error('Failed to copy:', err);
button.textContent = '❌ Failed';
setTimeout(() => {
button.textContent = 'πŸ“‹ Copy';
}, 2000);
});
}
function handleKeyPress(event) {
if (event.key === 'Enter') {
sendMessage();
}
}
// Disable chat input initially
document.getElementById('question').disabled = true;
document.getElementById('send-btn').disabled = true;
// Auto-load documents on page load
window.addEventListener('load', () => {
listDocuments();
});
</script>
</body>
</html>