Spaces:
Running
Running
| <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> |