anycoder-ad346ccf / index.html
udd542's picture
Upload folder using huggingface_hub
01dde67 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multi-Provider AI Chat Assistant</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--secondary-gradient: linear-gradient(135deg, #ff6b6b, #ee5a24);
--success-color: #27ae60;
--error-color: #e74c3c;
--warning-color: #f39c12;
--bg-light: rgba(255, 255, 255, 0.95);
--shadow-light: 0 10px 30px rgba(0, 0, 0, 0.1);
--shadow-medium: 0 20px 40px rgba(0, 0, 0, 0.15);
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: var(--primary-gradient);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: clamp(5px, 2vw, 20px);
line-height: 1.6;
}
.app-container {
width: 100%;
max-width: 1000px;
background: var(--bg-light);
backdrop-filter: blur(15px);
border-radius: clamp(15px, 2vw, 25px);
box-shadow: var(--shadow-medium);
overflow: hidden;
display: flex;
flex-direction: column;
height: 95vh;
max-height: 800px;
min-height: 600px;
}
.header {
background: var(--primary-gradient);
color: white;
padding: clamp(15px, 3vw, 25px);
text-align: center;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
animation: shimmer 3s ease-in-out infinite;
}
@keyframes shimmer {
0%, 100% { transform: translateX(-100%) translateY(-100%) rotate(0deg); }
50% { transform: translateX(100%) translateY(100%) rotate(180deg); }
}
.header h1 {
font-size: clamp(1.4rem, 3.5vw, 2.2rem);
margin-bottom: clamp(5px, 1vw, 10px);
position: relative;
z-index: 1;
}
.header p {
opacity: 0.9;
font-size: clamp(0.85rem, 2vw, 1rem);
position: relative;
z-index: 1;
}
.brand-link {
color: white;
text-decoration: none;
font-weight: bold;
display: inline-flex;
align-items: center;
gap: clamp(3px, 0.8vw, 8px);
font-size: clamp(0.75rem, 1.8vw, 0.9rem);
position: relative;
z-index: 1;
transition: all 0.3s ease;
}
.brand-link:hover {
color: #ffd700;
transform: translateY(-1px);
}
.api-config {
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
padding: clamp(12px, 2.5vw, 18px);
border-bottom: 1px solid #dee2e6;
position: relative;
}
.provider-selector {
margin-bottom: clamp(8px, 1.5vw, 12px);
}
.provider-selector label {
display: block;
font-size: clamp(0.8rem, 1.8vw, 0.9rem);
font-weight: 600;
color: #495057;
margin-bottom: clamp(4px, 1vw, 8px);
}
.provider-selector select {
width: 100%;
padding: clamp(8px, 1.8vw, 10px) clamp(10px, 2vw, 12px);
border: 2px solid #dee2e6;
border-radius: clamp(8px, 1.5vw, 10px);
font-size: clamp(0.85rem, 1.8vw, 0.9rem);
background: white;
transition: all 0.3s ease;
}
.provider-selector select:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.config-group {
display: grid;
grid-template-columns: 1fr 1fr auto;
gap: clamp(8px, 1.5vw, 12px);
align-items: end;
}
.config-group input {
padding: clamp(8px, 1.8vw, 10px) clamp(10px, 2vw, 12px);
border: 2px solid #dee2e6;
border-radius: clamp(8px, 1.5vw, 10px);
font-size: clamp(0.8rem, 1.8vw, 0.9rem);
transition: all 0.3s ease;
background: white;
}
.config-group input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.config-group input[placeholder*="API"] {
grid-column: 1 / -1;
}
.config-group button {
padding: clamp(10px, 2vw, 12px) clamp(15px, 3vw, 20px);
background: var(--primary-gradient);
color: white;
border: none;
border-radius: clamp(8px, 1.5vw, 10px);
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
font-size: clamp(0.8rem, 1.8vw, 0.9rem);
white-space: nowrap;
}
.config-group button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
}
.config-group button:active {
transform: translateY(0);
}
.status-indicator {
font-size: clamp(0.75rem, 1.6vw, 0.85rem);
color: #6c757d;
margin-top: clamp(5px, 1vw, 8px);
display: flex;
align-items: center;
gap: clamp(4px, 1vw, 8px);
}
.status-indicator.success { color: var(--success-color); }
.status-indicator.error { color: var(--error-color); }
.status-indicator.warning { color: var(--warning-color); }
.chat-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.messages {
flex: 1;
overflow-y: auto;
padding: clamp(15px, 3vw, 25px);
background: linear-gradient(135deg, #ffffff, #f8f9fa);
scroll-behavior: smooth;
}
.messages::-webkit-scrollbar {
width: 6px;
}
.messages::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.messages::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.messages::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
.message {
margin-bottom: clamp(15px, 3vw, 25px);
display: flex;
align-items: flex-start;
gap: clamp(10px, 2vw, 15px);
animation: fadeInUp 0.4s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message.user {
flex-direction: row-reverse;
}
.message-avatar {
width: clamp(35px, 6vw, 45px);
height: clamp(35px, 6vw, 45px);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
flex-shrink: 0;
font-size: clamp(0.8rem, 2vw, 1rem);
position: relative;
overflow: hidden;
}
.message.user .message-avatar {
background: var(--primary-gradient);
}
.message.assistant .message-avatar {
background: var(--secondary-gradient);
}
.message-avatar::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent, rgba(255,255,255,0.2));
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0; }
50% { opacity: 1; }
}
.message-content {
max-width: min(80%, 600px);
padding: clamp(12px, 2.5vw, 18px) clamp(15px, 3vw, 22px);
border-radius: clamp(15px, 3vw, 20px);
line-height: 1.6;
word-wrap: break-word;
position: relative;
}
.message.user .message-content {
background: var(--primary-gradient);
color: white;
}
.message.assistant .message-content {
background: #ffffff;
color: #333;
box-shadow: var(--shadow-light);
border: 1px solid #e9ecef;
}
.message.assistant .message-content:hover {
box-shadow: var(--shadow-medium);
transform: translateY(-1px);
transition: all 0.3s ease;
}
.input-container {
padding: clamp(15px, 3vw, 25px);
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
border-top: 1px solid #dee2e6;
}
.safety-notice {
background: linear-gradient(135deg, #e3f2fd, #bbdefb);
color: #1565c0;
padding: clamp(8px, 2vw, 12px) clamp(12px, 2.5vw, 16px);
border-radius: clamp(8px, 1.5vw, 12px);
font-size: clamp(0.75rem, 1.6vw, 0.85rem);
margin-bottom: clamp(10px, 2vw, 15px);
border-left: 4px solid #2196f3;
display: flex;
align-items: center;
gap: clamp(6px, 1.2vw, 10px);
}
.input-group {
display: flex;
gap: clamp(8px, 1.5vw, 12px);
align-items: flex-end;
}
.input-group textarea {
flex: 1;
min-height: clamp(45px, 8vh, 60px);
max-height: clamp(100px, 15vh, 140px);
padding: clamp(10px, 2.5vw, 15px) clamp(12px, 2.5vw, 18px);
border: 2px solid #dee2e6;
border-radius: clamp(20px, 4vw, 30px);
resize: vertical;
font-family: inherit;
font-size: clamp(0.85rem, 1.8vw, 1rem);
outline: none;
transition: all 0.3s ease;
background: white;
}
.input-group textarea:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.send-button {
width: clamp(45px, 8vw, 55px);
height: clamp(45px, 8vw, 55px);
border-radius: 50%;
background: var(--primary-gradient);
color: white;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
flex-shrink: 0;
}
.send-button:hover:not(:disabled) {
transform: translateY(-3px);
box-shadow: 0 12px 25px rgba(102, 126, 234, 0.4);
}
.send-button:active {
transform: translateY(-1px);
}
.send-button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.typing-indicator {
padding: clamp(8px, 2vw, 12px) clamp(12px, 2.5vw, 18px);
background: #ffffff;
border-radius: clamp(15px, 3vw, 20px);
display: inline-flex;
gap: clamp(4px, 1vw, 6px);
margin-bottom: clamp(15px, 3vw, 25px);
box-shadow: var(--shadow-light);
}
.typing-dot {
width: clamp(6px, 1.5vw, 8px);
height: clamp(6px, 1.5vw, 8px);
border-radius: 50%;
background: #999;
animation: typing 1.4s infinite;
}
.typing-dot:nth-child(2) {
animation-delay: 0.2s;
}
.typing-dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0%, 60%, 100% {
transform: translateY(0);
opacity: 0.3;
}
30% {
transform: translateY(-8px);
opacity: 1;
}
}
.loading {
display: flex;
align-items: center;
gap: clamp(6px, 1.2vw, 10px);
color: #666;
font-size: clamp(0.8rem, 1.6vw, 0.9rem);
padding: clamp(8px, 1.5vw, 12px);
}
.loading::after {
content: '';
width: clamp(14px, 3vw, 18px);
height: clamp(14px, 3vw, 18px);
border: 2px solid #667eea;
border-top: 2px solid transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Responsive Design */
@media (max-width: 768px) {
.app-container {
height: 100vh;
max-height: none;
border-radius: 0;
min-height: 100vh;
}
.config-group {
grid-template-columns: 1fr;
gap: clamp(8px, 2vw, 12px);
}
.config-group button {
width: 100%;
}
.message-content {
max-width: 90%;
}
.input-group {
flex-direction: column;
gap: clamp(8px, 2vw, 12px);
}
.send-button {
width: 100%;
height: clamp(45px, 8vh, 50px);
border-radius: clamp(25px, 5vw, 30px);
}
}
@media (max-width: 480px) {
.message {
gap: clamp(8px, 2vw, 10px);
}
.message-content {
max-width: 95%;
font-size: 0.9rem;
}
.input-group textarea {
min-height: clamp(50px, 10vh, 60px);
}
}
/* Print styles */
@media print {
.api-config,
.input-container {
display: none;
}
.app-container {
box-shadow: none;
border: 1px solid #ccc;
}
}
</style>
</head>
<body>
<div class="app-container">
<div class="header">
<h1><i class="fas fa-robot"></i> Multi-Provider AI Chat</h1>
<p>Unified chat interface for multiple AI providers</p>
<div style="margin-top: 10px;">
Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="brand-link" target="_blank">
<i class="fas fa-code"></i> anycoder
</a>
</div>
</div>
<div class="api-config">
<div class="provider-selector">
<label for="providerSelect"><i class="fas fa-server"></i> AI Provider:</label>
<select id="providerSelect" onchange="changeProvider()">
<option value="openai">OpenAI (GPT-3.5/GPT-4)</option>
<option value="anthropic">Anthropic Claude</option>
<option value="google">Google Gemini</option>
<option value="cohere">Cohere</option>
<option value="together">Together AI</option>
<option value="groq">Groq</option>
</select>
</div>
<div class="config-group">
<input type="text" id="apiKey" placeholder="Enter your API key..." />
<input type="text" id="modelName" placeholder="Model name" value="gpt-3.5-turbo" />
<button onclick="saveConfig()">
<i class="fas fa-save"></i> <span class="button-text">Save</span>
</button>
<input type="text" id="apiEndpoint" placeholder="Custom endpoint (optional)" style="grid-column: 1 / -1;" />
</div>
<div class="status-indicator" id="status">
<i class="fas fa-info-circle"></i> Select provider and enter API key to start
</div>
</div>
<div class="safety-notice">
<i class="fas fa-shield-alt"></i>
<span>Responsible AI usage with built-in safety guidelines across all providers</span>
</div>
<div class="chat-container">
<div class="messages" id="messages">
<div class="message assistant">
<div class="message-avatar">AI</div>
<div class="message-content">
Hello! I'm a multi-provider AI assistant. Select your preferred AI provider above, configure your API key, and let's start chatting!
<br><br>
<strong>Supported Providers:</strong><br>
<strong>OpenAI:</strong> GPT-3.5, GPT-4, GPT-4 Turbo<br>
<strong>Anthropic:</strong> Claude-3 Haiku, Sonnet, Opus<br>
<strong>Google:</strong> Gemini Pro, Gemini Pro Vision<br>
<strong>Cohere:</strong> Command, Command Light<br>
<strong>Together AI:</strong> Various open-source models<br>
<strong>Groq:</strong> Ultra-fast inference with Llama, Mixtral
</div>
</div>
</div>
</div>
<div class="input-container">
<div class="input-group">
<textarea id="messageInput" placeholder="Type your message here..."
onkeydown="handleKeyDown(event)"></textarea>
<button class="send-button" id="sendButton" onclick="sendMessage()">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</div>
</div>
<script>
class MultiProviderChatApp {
constructor() {
this.currentProvider = 'openai';
this.apiKey = '';
this.model = 'gpt-3.5-turbo';
this.apiEndpoint = '';
this.isLoading = false;
this.messages = [];
this.providerConfigs = {
openai: {
name: 'OpenAI',
models: ['gpt-3.5-turbo', 'gpt-4', 'gpt-4-turbo-preview'],
defaultModel: 'gpt-3.5-turbo',
endpoint: 'https://api.openai.com/v1/chat/completions'
},
anthropic: {
name: 'Anthropic Claude',
models: ['claude-3-haiku-20240307', 'claude-3-sonnet-20240229', 'claude-3-opus-20240229'],
defaultModel: 'claude-3-haiku-20240307',
endpoint: 'https://api.anthropic.com/v1/messages'
},
google: {
name: 'Google Gemini',
models: ['gemini-pro', 'gemini-pro-vision'],
defaultModel: 'gemini-pro',
endpoint: 'https://generativelanguage.googleapis.com/v1beta/models'
},
cohere: {
name: 'Cohere',
models: ['command', 'command-light', 'command-r', 'command-r-plus'],
defaultModel: 'command',
endpoint: 'https://api.cohere.ai/v1/chat'
},
together: {
name: 'Together AI',
models: ['meta-llama/Llama-2-7b-chat-hf', 'meta-llama/Llama-2-13b-chat-hf', 'mistralai/Mixtral-8x7B-Instruct-v0.1'],
defaultModel: 'meta-llama/Llama-2-7b-chat-hf',
endpoint: 'https://api.together.xyz/v1/chat/completions'
},
groq: {
name: 'Groq',
models: ['llama2-70b-4096', 'mixtral-8x7b-32768', 'gemma-7b-it'],
defaultModel: 'llama2-70b-4096',
endpoint: 'https://api.groq.com/openai/v1/chat/completions'
}
};
this.loadConfig();
this.initializeEventListeners();
this.updateUI();
}
loadConfig() {
this.currentProvider = localStorage.getItem('ai_provider') || 'openai';
this.apiKey = localStorage.getItem(`${this.currentProvider}_api_key`) || '';
this.model = localStorage.getItem(`${this.currentProvider}_model`) || this.providerConfigs[this.currentProvider].defaultModel;
this.apiEndpoint = localStorage.getItem(`${this.currentProvider}_endpoint`) || '';
document.getElementById('providerSelect').value = this.currentProvider;
document.getElementById('apiKey').value = this.apiKey;
document.getElementById('modelName').value = this.model;
document.getElementById('apiEndpoint').value = this.apiEndpoint;
this.updateStatus();
}
saveConfig() {
this.currentProvider = document.getElementById('providerSelect').value;
this.apiKey = document.getElementById('apiKey').value.trim();
this.model = document.getElementById('modelName').value.trim() || this.providerConfigs[this.currentProvider].defaultModel;
this.apiEndpoint = document.getElementById('apiEndpoint').value.trim();
localStorage.setItem('ai_provider', this.currentProvider);
localStorage.setItem(`${this.currentProvider}_api_key`, this.apiKey);
localStorage.setItem(`${this.currentProvider}_model`, this.model);
localStorage.setItem(`${this.currentProvider}_endpoint`, this.apiEndpoint);
this.updateStatus('Configuration saved successfully!', 'success');
this.showMessage(`Switched to ${this.providerConfigs[this.currentProvider].name}. Model: ${this.model}`, 'assistant');
}
changeProvider() {
const provider = document.getElementById('providerSelect').value;
this.currentProvider = provider;
// Load saved config for this provider
this.apiKey = localStorage.getItem(`${provider}_api_key`) || '';
this.model = localStorage.getItem(`${provider}_model`) || this.providerConfigs[provider].defaultModel;
this.apiEndpoint = localStorage.getItem(`${provider}_endpoint`) || '';
document.getElementById('apiKey').value = this.apiKey;
document.getElementById('modelName').value = this.model;
document.getElementById('apiEndpoint').value = this.apiEndpoint;
this.updateStatus(`Switched to ${this.providerConfigs[provider].name}`, 'info');
}
updateStatus(message = '', type = 'info') {
const statusEl = document.getElementById('status');
const config = this.providerConfigs[this.currentProvider];
const icon = type === 'error' ? 'exclamation-triangle' :
type === 'success' ? 'check-circle' : 'info-circle';
const colorClass = type === 'error' ? 'error' :
type === 'success' ? 'success' : '';
const defaultMessage = this.apiKey ?
`Ready to chat with ${config.name}!` :
`Enter ${config.name} API key to start chatting`;
statusEl.innerHTML = `<i class="fas fa-${icon}"></i> ${message || defaultMessage}`;
statusEl.className = `status-indicator ${colorClass}`;
}
async sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (!message || this.isLoading) return;
if (!this.apiKey) {
this.updateStatus(`Please enter ${this.providerConfigs[this.currentProvider].name} API key`, 'error');
return;
}
input.value = '';
this.isLoading = true;
this.updateUI();
// Add user message
this.addMessage(message, 'user');
// Show typing indicator
this.showTypingIndicator();
try {
const response = await this.callAPI(message);
this.removeTypingIndicator();
this.addMessage(response, 'assistant');
} catch (error) {
this.removeTypingIndicator();
this.addMessage(`Error: ${error.message}`, 'assistant');
console.error('API Error:', error);
} finally {
this.isLoading = false;
this.updateUI();
}
}
async callAPI(message) {
const config = this.providerConfigs[this.currentProvider];
const endpoint = this.apiEndpoint || config.endpoint;
let requestBody, headers, responseData;
// Build request based on provider
switch (this.currentProvider) {
case 'openai':
requestBody = this.buildOpenAIRequest(message);
headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
};
break;
case 'anthropic':
requestBody = this.buildAnthropicRequest(message);
headers = {
'Content-Type': 'application/json',
'x-api-key': this.apiKey,
'anthropic-version': '2023-06-01'
};
break;
case 'google':
return await this.callGoogleAPI(message);
case 'cohere':
requestBody = this.buildCohereRequest(message);
headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
};
break;
case 'together':
requestBody = this.buildTogetherRequest(message);
headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
};
break;
case 'groq':
requestBody = this.buildGroqRequest(message);
headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
};
break;
}
const response = await fetch(endpoint, {
method: 'POST',
headers,
body: JSON.stringify(requestBody)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message || error.message || 'API request failed');
}
responseData = await response.json();
return this.parseResponse(responseData);
}
buildOpenAIRequest(message) {
return {
model: this.model,
messages: [
{ role: 'system', content: this.getSystemPrompt() },
...this.messages.slice(-10),
{ role: 'user', content: message }
],
max_tokens: 1000,
temperature: 0.7
};
}
buildAnthropicRequest(message) {
return {
model: this.model,
max_tokens: 1000,
messages: [
{ role: 'user', content: `${this.getSystemPrompt()}\n\nHuman: ${message}\n\nAssistant:` }
]
};
}
async callGoogleAPI(message) {
const endpoint = `${this.providerConfigs.google.endpoint}/${this.model}:generateContent?key=${this.apiKey}`;
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{
parts: [{ text: `${this.getSystemPrompt()}\n\nUser: ${message}\n\nAssistant:` }]
}]
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message || 'API request failed');
}
const data = await response.json();
return data.candidates[0].content.parts[0].text;
}
buildCohereRequest(message) {
return {
message: `${this.getSystemPrompt()}\n\nUser: ${message}`,
chat_history: this.messages.slice(-10).map(msg => ({
role: msg.role === 'assistant' ? 'CHATBOT' : 'USER',
message: msg.content
}))
};
}
buildTogetherRequest(message) {
return {
model: this.model,
messages: [
{ role: 'system', content: this.getSystemPrompt() },
...this.messages.slice(-10),
{ role: 'user', content: message }
],
max_tokens: 1000,
temperature: 0.7
};
}
buildGroqRequest(message) {
return {
model: this.model,
messages: [
{ role: 'system', content: this.getSystemPrompt() },
...this.messages.slice(-10),
{ role: 'user', content: message }
],
max_tokens: 1000,
temperature: 0.7
};
}
parseResponse(data) {
switch (this.currentProvider) {
case 'openai':
case 'together':
case 'groq':
return data.choices[0].message.content;
case 'anthropic':
return data.content[0].text;
case 'cohere':
return data.text;
default:
return 'Response parsed successfully';
}
}
getSystemPrompt() {
return `You are a helpful AI assistant. Always be helpful, harmless, and honest.
Follow these guidelines:
- Provide accurate and helpful information
- Be respectful and considerate
- Admit when you don't know something
- Avoid harmful, illegal, or unethical content
- Keep responses concise and relevant`;
}
addMessage(content, sender) {
const messagesEl = document.getElementById('messages');
const messageEl = document.createElement('div');
messageEl.className = `message ${sender}`;
const avatar = sender === 'user' ? 'U' : 'AI';
messageEl.innerHTML = `
<div class="message-avatar">${avatar}</div>
<div class="message-content">${this.formatMessage(content)}</div>
`;
messagesEl.appendChild(messageEl);
messagesEl.scrollTop = messagesEl.scrollHeight;
if (sender === 'assistant') {
this.messages.push({ role: 'assistant', content: content });
}
}
formatMessage(content) {
return content
.replace(/\n/g, '<br>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/`(.*?)`/g, '<code style="background: #f4f4f4; padding: 2px 4px; border-radius: 3px; font-size: 0.9em;">$1</code>');
}
showTypingIndicator() {
const messagesEl = document.getElementById('messages');
const typingEl = document.createElement('div');
typingEl.className = 'message assistant';
typingEl.id = 'typing-indicator';
typingEl.innerHTML = `
<div class="message-avatar">AI</div>
<div class="message-content">
<div class="typing-indicator">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
`;
messagesEl.appendChild(typingEl);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
removeTypingIndicator() {
const typingEl = document.getElementById('typing-indicator');
if (typingEl) {
typingEl.remove();
}
}
updateUI() {
const sendButton = document.getElementById('sendButton');
const input = document.getElementById('messageInput');
sendButton.disabled = this.isLoading || !this.apiKey;
input.disabled = this.isLoading;
}
initializeEventListeners() {
document.getElementById('messageInput').addEventListener('input', () => {
this.updateUI();
});
}
}
// Global functions
let chatApp;
function saveConfig() {
chatApp.saveConfig();
}
function changeProvider() {
chatApp.changeProvider();
}
function sendMessage() {
chatApp.sendMessage();
}
function handleKeyDown(event) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
sendMessage();
}
}
// Initialize the application
document.addEventListener('DOMContentLoaded', () => {
chatApp = new MultiProviderChatApp();
});
</script>
</body>
</html>