// --- CONFIGURATION ---
// Set this to your Railway URL
let BACKEND_URL = "https://prod-infinit-memory-production.up.railway.app";
// Safety: Remove trailing slash if the user adds one
if (BACKEND_URL.endsWith('/')) BACKEND_URL = BACKEND_URL.slice(0, -1);
const chatContainer = document.getElementById('chat-container');
const userInput = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const statsDisplay = document.getElementById('pipeline-stats');
// BYOK Elements
const settingsToggle = document.getElementById('settings-toggle');
const settingsPanel = document.getElementById('settings-panel');
const apiKeyInput = document.getElementById('api-key-input');
const llmModelInput = document.getElementById('llm-model-input');
const embedModelInput = document.getElementById('embed-model-input');
// Load saved settings from localStorage
apiKeyInput.value = localStorage.getItem('byok_api_key') || '';
llmModelInput.value = localStorage.getItem('byok_llm_model') || 'openai/gpt-4o-mini';
embedModelInput.value = localStorage.getItem('byok_embed_model') || 'qwen/qwen3-embedding-8b';
// Toggle Settings Panel
settingsToggle.addEventListener('click', () => {
settingsPanel.classList.toggle('active');
});
/**
* Adds a message bubble to the chat container
*/
function addMessage(text, role) {
// Remove welcome message if it exists
const welcome = document.querySelector('.welcome-message');
if (welcome) welcome.remove();
const msgDiv = document.createElement('div');
msgDiv.classList.add('message', `${role}-message`);
msgDiv.textContent = text;
chatContainer.appendChild(msgDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
return msgDiv;
}
/**
* Adds a visual typing indicator
*/
function addLoadingIndicator() {
const loader = document.createElement('div');
loader.classList.add('message', 'bot-message', 'loading-msg');
loader.innerHTML = `
`;
chatContainer.appendChild(loader);
chatContainer.scrollTop = chatContainer.scrollHeight;
return loader;
}
/**
* Main function to send message and get BYOK response
*/
async function sendMessage() {
const text = userInput.value.trim();
if (!text) return;
// Persist settings to localStorage
localStorage.setItem('byok_api_key', apiKeyInput.value);
localStorage.setItem('byok_llm_model', llmModelInput.value);
localStorage.setItem('byok_embed_model', embedModelInput.value);
// Clear input and add user message
userInput.value = '';
userInput.style.height = 'auto';
addMessage(text, 'user');
// Add loading indicator
const loader = addLoadingIndicator();
try {
const response = await fetch(`${BACKEND_URL}/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: text,
api_key: apiKeyInput.value || null,
llm_model: llmModelInput.value || null,
embed_model: embedModelInput.value || null
})
});
const data = await response.json();
// Remove loader and add bot response
loader.remove();
if (data.answer) {
addMessage(data.answer, 'bot');
// Update performance stats
const pipeTime = data.total_time ? data.total_time.toFixed(1) : '--';
const retTime = data.timings && data.timings.retrieval ? data.timings.retrieval.toFixed(2) : '--';
statsDisplay.textContent = `Pipeline: ${pipeTime}s | Retrieval: ${retTime}s`;
} else {
addMessage('Error: No response from engine.', 'bot');
}
} catch (err) {
if (loader) loader.remove();
addMessage(`Error: ${err.message}`, 'bot');
}
}
/**
* Helper for suggestion buttons
*/
function useSuggestion(text) {
userInput.value = text;
sendMessage();
}
// Event Listeners
sendBtn.addEventListener('click', sendMessage);
userInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// Auto-resize textarea as user types
userInput.addEventListener('input', function () {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
});