Priyanshi Saxena
feat: Add Gemini/Ollama toggle functionality with API error fixes
6bf47a1
let chatHistory = [];
let messageCount = 0;
let useGemini = false; // Track current LLM choice
// Initialize Gemini toggle
document.addEventListener('DOMContentLoaded', function() {
const geminiToggle = document.getElementById('geminiToggle');
const toggleLabel = document.querySelector('.toggle-label');
// Load saved preference
useGemini = localStorage.getItem('useGemini') === 'true';
geminiToggle.checked = useGemini;
updateToggleLabel();
// Handle toggle changes
geminiToggle.addEventListener('change', function() {
useGemini = this.checked;
localStorage.setItem('useGemini', useGemini.toString());
updateToggleLabel();
console.log(`Switched to ${useGemini ? 'Gemini' : 'Ollama'} mode`);
// Show confirmation
showStatus(`Switched to ${useGemini ? 'Gemini (Cloud AI)' : 'Ollama (Local AI)'} mode`, 'info');
// Refresh status to reflect changes
checkStatus();
});
});
function updateToggleLabel() {
const toggleLabel = document.querySelector('.toggle-label');
if (toggleLabel) {
toggleLabel.textContent = `AI Model: ${useGemini ? 'Gemini' : 'Ollama'}`;
}
}
async function checkStatus() {
try {
const response = await fetch('/status');
const status = await response.json();
const statusDiv = document.getElementById('status');
if (status.enabled && status.gemini_configured) {
statusDiv.className = 'status online';
statusDiv.innerHTML = '<span>Research systems online</span>' +
'<div style="margin-top: 0.5rem; font-size: 0.85rem; opacity: 0.8;">' +
'Tools: ' + status.tools_available.join(' • ') + '</div>';
} else {
statusDiv.className = 'status offline';
statusDiv.innerHTML = '<span>Limited mode - Configure GEMINI_API_KEY for full functionality</span>' +
'<div style="margin-top: 0.5rem; font-size: 0.85rem; opacity: 0.8;">' +
'Available: ' + status.tools_available.join(' • ') + '</div>';
}
} catch (error) {
const statusDiv = document.getElementById('status');
statusDiv.className = 'status offline';
statusDiv.innerHTML = '<span>Connection error</span>';
}
}
async function sendQuery() {
const input = document.getElementById('queryInput');
const sendBtn = document.getElementById('sendBtn');
const loadingIndicator = document.getElementById('loadingIndicator');
const statusIndicator = document.getElementById('statusIndicator');
const statusText = document.getElementById('statusText');
const query = input.value.trim();
if (!query) {
showStatus('Please enter a research query', 'warning');
return;
}
console.log('Sending research query');
addMessage('user', query);
input.value = '';
// Update UI states
sendBtn.disabled = true;
sendBtn.innerHTML = '<span class="loading">Processing</span>';
loadingIndicator.classList.add('active');
showStatus('Initializing research...', 'processing');
try {
console.log('Starting streaming API request...');
const requestStart = Date.now();
// Create an AbortController for manual timeout control
const controller = new AbortController();
const timeoutId = setTimeout(() => {
console.log('Manual timeout after 5 minutes');
controller.abort();
}, 300000); // 5 minute timeout instead of default browser timeout
// Use fetch with streaming for POST requests with body
const response = await fetch('/query/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'text/event-stream',
'Cache-Control': 'no-cache'
},
body: JSON.stringify({
query,
chat_history: chatHistory,
use_gemini: useGemini
}),
signal: controller.signal,
// Disable browser's default timeout behavior
keepalive: true
});
// Clear the timeout since we got a response
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error('Request failed with status ' + response.status);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop(); // Keep incomplete line in buffer
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.substring(6));
if (data.type === 'status') {
showStatus(data.message, 'processing');
updateProgress(data.progress);
// Also update the loading text
const loadingText = document.getElementById('loadingText');
if (loadingText) {
loadingText.textContent = data.message;
}
console.log('Progress: ' + data.progress + '% - ' + data.message);
} else if (data.type === 'tools') {
showStatus(data.message, 'processing');
// Update loading text for tools
const loadingText = document.getElementById('loadingText');
if (loadingText) {
loadingText.textContent = data.message;
}
console.log('Tools: ' + data.message);
} else if (data.type === 'result') {
const result = data.data;
const requestTime = Date.now() - requestStart;
console.log('Request completed in ' + requestTime + 'ms');
if (result.success) {
addMessage('assistant', result.response, result.sources, result.visualizations);
showStatus('Research complete', 'success');
console.log('Analysis completed successfully');
} else {
console.log('Analysis request failed');
addMessage('assistant', result.response || 'Analysis temporarily unavailable. Please try again.', [], []);
showStatus('Request failed', 'error');
}
} else if (data.type === 'complete') {
break;
} else if (data.type === 'error') {
throw new Error(data.message);
}
} catch (parseError) {
console.error('Parse error:', parseError);
}
}
}
}
} catch (error) {
console.error('Streaming request error:', error);
// More specific error handling
if (error.name === 'AbortError') {
addMessage('assistant', 'Request timed out after 5 minutes. Ollama may be processing a complex query. Please try a simpler question or wait and try again.');
showStatus('Request timed out', 'error');
} else if (error.message.includes('Failed to fetch') || error.message.includes('network error')) {
addMessage('assistant', 'Network connection error. Please check your internet connection and try again.');
showStatus('Connection error', 'error');
} else if (error.message.includes('ERR_HTTP2_PROTOCOL_ERROR')) {
addMessage('assistant', 'Ollama is still processing your request in the background. Please wait a moment and try again, or try a simpler query.');
showStatus('Processing - please retry', 'warning');
} else {
addMessage('assistant', 'Connection error. Please check your network and try again.');
showStatus('Connection error', 'error');
}
} finally {
// Reset UI states
sendBtn.disabled = false;
sendBtn.innerHTML = 'Research';
loadingIndicator.classList.remove('active');
input.focus();
console.log('Request completed');
// Hide status after delay
setTimeout(() => hideStatus(), 3000);
}
}
function addMessage(sender, content, sources = [], visualizations = []) {
const messagesDiv = document.getElementById('chatMessages');
// Clear welcome message
if (messageCount === 0) {
messagesDiv.innerHTML = '';
}
messageCount++;
const messageDiv = document.createElement('div');
messageDiv.className = 'message ' + sender;
let sourcesHtml = '';
if (sources && sources.length > 0) {
sourcesHtml = `
<div class="sources">
Sources: ${sources.map(s => `<span>${s}</span>`).join('')}
</div>
`;
}
let visualizationHtml = '';
if (visualizations && visualizations.length > 0) {
console.log('Processing visualizations:', visualizations.length);
visualizationHtml = visualizations.map((viz, index) => {
console.log(`Visualization ${index}:`, viz.substring(0, 100));
return `<div class="visualization-container" id="viz-${Date.now()}-${index}">${viz}</div>`;
}).join('');
}
// Format content based on sender
let formattedContent = content;
if (sender === 'assistant') {
// Convert markdown to HTML for assistant responses
try {
formattedContent = marked.parse(content);
} catch (error) {
// Fallback to basic formatting if marked.js fails
console.warn('Markdown parsing failed, using fallback:', error);
formattedContent = content
.replace(/\n/g, '<br>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/`(.*?)`/g, '<code>$1</code>');
}
} else {
// Apply markdown parsing to user messages too
try {
formattedContent = marked.parse(content);
} catch (error) {
formattedContent = content.replace(/\n/g, '<br>');
}
}
messageDiv.innerHTML = `
<div class="message-content">
${formattedContent}
${sourcesHtml}
</div>
${visualizationHtml}
<div class="message-meta">${new Date().toLocaleTimeString()}</div>
`;
messagesDiv.appendChild(messageDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
// Execute any scripts in the visualizations after DOM insertion
if (visualizations && visualizations.length > 0) {
console.log('Executing visualization scripts...');
setTimeout(() => {
const scripts = messageDiv.querySelectorAll('script');
console.log(`Found ${scripts.length} scripts to execute`);
scripts.forEach((script, index) => {
console.log(`Executing script ${index}:`, script.textContent.substring(0, 200) + '...');
try {
// Execute script in global context using Function constructor
const scriptFunction = new Function(script.textContent);
scriptFunction.call(window);
console.log(`Script ${index} executed successfully`);
} catch (error) {
console.error(`Script ${index} execution error:`, error);
console.error(`Script content preview:`, script.textContent.substring(0, 500));
}
});
console.log('All visualization scripts executed');
}, 100);
}
chatHistory.push({ role: sender, content });
if (chatHistory.length > 20) chatHistory = chatHistory.slice(-20);
}
function setQuery(query) {
document.getElementById('queryInput').value = query;
setTimeout(() => sendQuery(), 100);
}
// Status management functions
function showStatus(message, type = 'info') {
const statusIndicator = document.getElementById('statusIndicator');
const statusText = document.getElementById('statusText');
statusText.textContent = message;
statusIndicator.className = `status-indicator show ${type}`;
}
function hideStatus() {
const statusIndicator = document.getElementById('statusIndicator');
statusIndicator.classList.remove('show');
}
function updateProgress(progress) {
// Update progress bar if it exists
const progressBar = document.querySelector('.progress-bar');
if (progressBar) {
progressBar.style.width = `${progress}%`;
}
// Update loading indicator text with progress
const loadingText = document.getElementById('loadingText');
if (loadingText && progress) {
loadingText.textContent = `Processing ${progress}%...`;
}
}
// Theme toggle functionality
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
const themeIcon = document.querySelector('#themeToggle i');
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
// Update icon
if (newTheme === 'light') {
themeIcon.className = 'fas fa-sun';
} else {
themeIcon.className = 'fas fa-moon';
}
}
// Initialize theme
function initializeTheme() {
const savedTheme = localStorage.getItem('theme') || 'dark';
const themeIcon = document.querySelector('#themeToggle i');
document.documentElement.setAttribute('data-theme', savedTheme);
if (savedTheme === 'light') {
themeIcon.className = 'fas fa-sun';
} else {
themeIcon.className = 'fas fa-moon';
}
}
// Event listeners
document.getElementById('queryInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendQuery();
});
document.getElementById('sendBtn').addEventListener('click', (e) => {
console.log('Research button clicked');
e.preventDefault();
sendQuery();
});
document.getElementById('themeToggle').addEventListener('click', toggleTheme);
// Initialize
document.addEventListener('DOMContentLoaded', () => {
console.log('Application initialized');
initializeTheme();
checkStatus();
document.getElementById('queryInput').focus();
});