Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>LLM Chat Interface</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> | |
.chat-container { | |
height: calc(100vh - 200px); | |
} | |
.message-animation { | |
animation: fadeIn 0.3s ease-in-out; | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; transform: translateY(10px); } | |
to { opacity: 1; transform: translateY(0); } | |
} | |
.slide-in { | |
animation: slideIn 0.3s ease-out; | |
} | |
@keyframes slideIn { | |
from { transform: translateX(100%); } | |
to { transform: translateX(0); } | |
} | |
.pulse { | |
animation: pulse 1.5s infinite; | |
} | |
@keyframes pulse { | |
0%, 100% { opacity: 1; } | |
50% { opacity: 0.5; } | |
} | |
</style> | |
</head> | |
<body class="bg-gray-100 font-sans"> | |
<div class="container mx-auto max-w-6xl p-4"> | |
<div class="bg-white rounded-xl shadow-lg overflow-hidden"> | |
<!-- Header --> | |
<div class="bg-indigo-600 text-white p-4 flex justify-between items-center"> | |
<h1 class="text-xl font-bold">LLM Chat Interface</h1> | |
<button id="config-btn" class="bg-indigo-700 hover:bg-indigo-800 px-4 py-2 rounded-lg transition"> | |
<i class="fas fa-cog mr-2"></i>Configure | |
</button> | |
</div> | |
<!-- Configuration Panel --> | |
<div id="config-panel" class="hidden bg-gray-50 p-4 border-b border-gray-200 slide-in"> | |
<div class="mb-4"> | |
<label class="block text-gray-700 font-medium mb-2">LLM Provider</label> | |
<select id="provider-select" class="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
<option value="">Select a provider</option> | |
<option value="azure-openai">Azure OpenAI</option> | |
<option value="groq">Groq</option> | |
<option value="openai">OpenAI</option> | |
<option value="anthropic">Anthropic</option> | |
<option value="cohere">Cohere</option> | |
</select> | |
</div> | |
<!-- Dynamic Credential Form --> | |
<div id="credential-form" class="space-y-4"> | |
<!-- Form will be dynamically populated based on provider --> | |
</div> | |
<div class="flex justify-end mt-4"> | |
<button id="save-config" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg transition"> | |
Save Configuration | |
</button> | |
</div> | |
</div> | |
<!-- Chat Area --> | |
<div class="flex flex-col"> | |
<div id="chat-messages" class="chat-container p-4 overflow-y-auto space-y-4"> | |
<div class="text-center text-gray-500 py-10"> | |
<i class="fas fa-comments text-4xl mb-2"></i> | |
<p>Configure your LLM provider to start chatting</p> | |
</div> | |
</div> | |
<!-- Input Area --> | |
<div class="p-4 border-t border-gray-200 bg-white"> | |
<div id="connection-status" class="hidden mb-2 flex items-center"> | |
<span class="w-3 h-3 rounded-full mr-2 bg-gray-400"></span> | |
<span class="text-sm text-gray-600">Disconnected</span> | |
</div> | |
<div class="flex space-x-2"> | |
<input id="message-input" type="text" placeholder="Type your message..." | |
class="flex-1 p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 disabled:opacity-50" disabled> | |
<button id="send-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg transition disabled:opacity-50" disabled> | |
<i class="fas fa-paper-plane"></i> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// DOM Elements | |
const configBtn = document.getElementById('config-btn'); | |
const configPanel = document.getElementById('config-panel'); | |
const providerSelect = document.getElementById('provider-select'); | |
const credentialForm = document.getElementById('credential-form'); | |
const saveConfigBtn = document.getElementById('save-config'); | |
const chatMessages = document.getElementById('chat-messages'); | |
const messageInput = document.getElementById('message-input'); | |
const sendBtn = document.getElementById('send-btn'); | |
const connectionStatus = document.getElementById('connection-status'); | |
// Current configuration | |
let currentConfig = { | |
provider: null, | |
credentials: null, | |
connected: false | |
}; | |
// Provider configuration templates | |
const providerConfigs = { | |
'azure-openai': { | |
name: 'Azure OpenAI', | |
fields: [ | |
{ id: 'api-key', label: 'API Key', type: 'password', required: true }, | |
{ id: 'endpoint', label: 'Endpoint URL', type: 'text', placeholder: 'https://your-resource.openai.azure.com/', required: true }, | |
{ id: 'deployment-name', label: 'Deployment Name', type: 'text', required: true }, | |
{ id: 'api-version', label: 'API Version', type: 'text', value: '2023-05-15', required: true } | |
] | |
}, | |
'groq': { | |
name: 'Groq', | |
fields: [ | |
{ id: 'api-key', label: 'API Key', type: 'password', required: true }, | |
{ id: 'model', label: 'Model', type: 'text', value: 'mixtral-8x7b-32768', required: true } | |
] | |
}, | |
'openai': { | |
name: 'OpenAI', | |
fields: [ | |
{ id: 'api-key', label: 'API Key', type: 'password', required: true }, | |
{ id: 'model', label: 'Model', type: 'text', value: 'gpt-4', required: true } | |
] | |
}, | |
'anthropic': { | |
name: 'Anthropic', | |
fields: [ | |
{ id: 'api-key', label: 'API Key', type: 'password', required: true }, | |
{ id: 'model', label: 'Model', type: 'text', value: 'claude-2', required: true } | |
] | |
}, | |
'cohere': { | |
name: 'Cohere', | |
fields: [ | |
{ id: 'api-key', label: 'API Key', type: 'password', required: true }, | |
{ id: 'model', label: 'Model', type: 'text', value: 'command', required: true } | |
] | |
} | |
}; | |
// Toggle configuration panel | |
configBtn.addEventListener('click', function() { | |
configPanel.classList.toggle('hidden'); | |
}); | |
// Handle provider selection change | |
providerSelect.addEventListener('change', function() { | |
const selectedProvider = this.value; | |
if (!selectedProvider) { | |
credentialForm.innerHTML = ''; | |
return; | |
} | |
const provider = providerConfigs[selectedProvider]; | |
let formHTML = `<h3 class="font-medium text-gray-700 mb-3">${provider.name} Configuration</h3>`; | |
provider.fields.forEach(field => { | |
formHTML += ` | |
<div class="mb-3"> | |
<label for="${field.id}" class="block text-sm font-medium text-gray-700 mb-1">${field.label}</label> | |
<input type="${field.type}" id="${field.id}" | |
class="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" | |
${field.placeholder ? `placeholder="${field.placeholder}"` : ''} | |
${field.value ? `value="${field.value}"` : ''} | |
${field.required ? 'required' : ''}> | |
</div> | |
`; | |
}); | |
credentialForm.innerHTML = formHTML; | |
}); | |
// Save configuration | |
saveConfigBtn.addEventListener('click', function() { | |
const selectedProvider = providerSelect.value; | |
if (!selectedProvider) { | |
alert('Please select a provider'); | |
return; | |
} | |
const provider = providerConfigs[selectedProvider]; | |
const credentials = {}; | |
let isValid = true; | |
provider.fields.forEach(field => { | |
const input = document.getElementById(field.id); | |
if (field.required && !input.value.trim()) { | |
input.classList.add('border-red-500'); | |
isValid = false; | |
} else { | |
input.classList.remove('border-red-500'); | |
credentials[field.id] = input.value.trim(); | |
} | |
}); | |
if (!isValid) { | |
alert('Please fill in all required fields'); | |
return; | |
} | |
// Save configuration | |
currentConfig.provider = selectedProvider; | |
currentConfig.credentials = credentials; | |
// Test connection (simulated) | |
testConnection(); | |
// Close config panel | |
configPanel.classList.add('hidden'); | |
// Add system message | |
addMessage('system', `Configuration saved for ${provider.name}. You can now start chatting.`); | |
}); | |
// Simulate connection test | |
function testConnection() { | |
connectionStatus.classList.remove('hidden'); | |
const statusDot = connectionStatus.querySelector('span:first-child'); | |
const statusText = connectionStatus.querySelector('span:last-child'); | |
statusDot.classList.remove('bg-green-500', 'bg-red-500'); | |
statusDot.classList.add('bg-yellow-500', 'pulse'); | |
statusText.textContent = 'Connecting...'; | |
// Simulate API call | |
setTimeout(() => { | |
statusDot.classList.remove('bg-yellow-500', 'pulse'); | |
// For demo purposes, we'll assume connection is successful | |
currentConfig.connected = true; | |
statusDot.classList.add('bg-green-500'); | |
statusText.textContent = 'Connected'; | |
// Enable chat | |
messageInput.disabled = false; | |
sendBtn.disabled = false; | |
}, 1500); | |
} | |
// Add message to chat | |
function addMessage(role, content) { | |
const messageDiv = document.createElement('div'); | |
messageDiv.classList.add('message-animation'); | |
if (role === 'user') { | |
messageDiv.innerHTML = ` | |
<div class="flex justify-end"> | |
<div class="bg-indigo-600 text-white rounded-lg p-3 max-w-3/4"> | |
<p>${content}</p> | |
</div> | |
</div> | |
`; | |
} else if (role === 'assistant') { | |
messageDiv.innerHTML = ` | |
<div class="flex justify-start"> | |
<div class="bg-gray-200 text-gray-800 rounded-lg p-3 max-w-3/4"> | |
<p>${content}</p> | |
</div> | |
</div> | |
`; | |
} else { // system | |
messageDiv.innerHTML = ` | |
<div class="flex justify-center"> | |
<div class="bg-gray-100 text-gray-600 text-sm rounded-lg p-2 max-w-3/4"> | |
<p>${content}</p> | |
</div> | |
</div> | |
`; | |
} | |
chatMessages.appendChild(messageDiv); | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
} | |
// Handle send message | |
sendBtn.addEventListener('click', sendMessage); | |
messageInput.addEventListener('keypress', function(e) { | |
if (e.key === 'Enter') { | |
sendMessage(); | |
} | |
}); | |
function sendMessage() { | |
const message = messageInput.value.trim(); | |
if (!message || !currentConfig.connected) return; | |
// Add user message | |
addMessage('user', message); | |
messageInput.value = ''; | |
// Show typing indicator | |
const typingIndicator = document.createElement('div'); | |
typingIndicator.id = 'typing-indicator'; | |
typingIndicator.innerHTML = ` | |
<div class="flex justify-start"> | |
<div class="bg-gray-200 text-gray-800 rounded-lg p-3 max-w-3/4"> | |
<div class="flex space-x-2"> | |
<div class="w-2 h-2 rounded-full bg-gray-500 animate-bounce" style="animation-delay: 0s"></div> | |
<div class="w-2 h-2 rounded-full bg-gray-500 animate-bounce" style="animation-delay: 0.2s"></div> | |
<div class="w-2 h-2 rounded-full bg-gray-500 animate-bounce" style="animation-delay: 0.4s"></div> | |
</div> | |
</div> | |
</div> | |
`; | |
chatMessages.appendChild(typingIndicator); | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
// Simulate API call to backend | |
const apiEndpoint = '/api/chat'; // Your FastAPI endpoint | |
const requestData = { | |
provider: currentConfig.provider, | |
credentials: currentConfig.credentials, | |
message: message | |
}; | |
// In a real implementation, you would use fetch or axios | |
console.log('Sending to backend:', requestData); | |
// Simulate API response after delay | |
setTimeout(() => { | |
// Remove typing indicator | |
const indicator = document.getElementById('typing-indicator'); | |
if (indicator) chatMessages.removeChild(indicator); | |
// For demo purposes, we'll use a mock response | |
const mockResponses = { | |
'azure-openai': `I'm your Azure OpenAI assistant. How can I help you today?`, | |
'groq': `I'm powered by Groq's ultra-fast LLMs. What would you like to know?`, | |
'openai': `Hello! I'm an OpenAI model. How can I assist you?`, | |
'anthropic': `Greetings! I'm Claude from Anthropic. How may I help?`, | |
'cohere': `I'm using Cohere's language model. What's on your mind?` | |
}; | |
const response = mockResponses[currentConfig.provider] || | |
"I'm your AI assistant. How can I help you today?"; | |
addMessage('assistant', response); | |
}, 1500); | |
} | |
// Initial setup | |
if (localStorage.getItem('llmConfig')) { | |
try { | |
currentConfig = JSON.parse(localStorage.getItem('llmConfig')); | |
if (currentConfig.provider) { | |
providerSelect.value = currentConfig.provider; | |
const event = new Event('change'); | |
providerSelect.dispatchEvent(event); | |
// Populate fields with saved values | |
if (currentConfig.credentials) { | |
for (const [key, value] of Object.entries(currentConfig.credentials)) { | |
const input = document.getElementById(key); | |
if (input) input.value = value; | |
} | |
} | |
// Test connection | |
testConnection(); | |
} | |
} catch (e) { | |
console.error('Failed to load config:', e); | |
} | |
} | |
}); | |
</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=rajkumar218/llm-chat" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |