| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Advanced AI Client</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-message.user { |
| background-color: #f3f4f6; |
| border-radius: 1rem 1rem 0 1rem; |
| } |
| .chat-message.ai { |
| background-color: #e0f2fe; |
| border-radius: 1rem 1rem 1rem 0; |
| } |
| .typing-indicator { |
| display: inline-block; |
| } |
| .typing-indicator span { |
| height: 8px; |
| width: 8px; |
| background: #9ca3af; |
| border-radius: 50%; |
| display: inline-block; |
| margin: 0 2px; |
| opacity: 0.4; |
| } |
| .typing-indicator span:nth-child(1) { |
| animation: typing 1s infinite; |
| } |
| .typing-indicator span:nth-child(2) { |
| animation: typing 1s infinite 0.2s; |
| } |
| .typing-indicator span:nth-child(3) { |
| animation: typing 1s infinite 0.4s; |
| } |
| @keyframes typing { |
| 0%, 100% { |
| opacity: 0.4; |
| transform: translateY(0); |
| } |
| 50% { |
| opacity: 1; |
| transform: translateY(-5px); |
| } |
| } |
| .slide-in { |
| animation: slideIn 0.3s ease-out forwards; |
| } |
| @keyframes slideIn { |
| from { |
| transform: translateY(20px); |
| opacity: 0; |
| } |
| to { |
| transform: translateY(0); |
| opacity: 1; |
| } |
| } |
| .file-preview { |
| max-width: 200px; |
| max-height: 200px; |
| border-radius: 0.5rem; |
| margin-top: 0.5rem; |
| } |
| .progress-bar { |
| height: 4px; |
| background-color: #e5e7eb; |
| border-radius: 2px; |
| margin-top: 4px; |
| } |
| .progress-bar-fill { |
| height: 100%; |
| background-color: #3b82f6; |
| border-radius: 2px; |
| transition: width 0.3s ease; |
| } |
| #dropzone { |
| border: 2px dashed #d1d5db; |
| border-radius: 0.5rem; |
| transition: all 0.2s ease; |
| } |
| #dropzone.active { |
| border-color: #3b82f6; |
| background-color: #eff6ff; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50 min-h-screen"> |
| <div class="container mx-auto max-w-6xl p-4"> |
| |
| <header class="flex justify-between items-center mb-6"> |
| <div class="flex items-center space-x-2"> |
| <i class="fas fa-robot text-3xl text-blue-500"></i> |
| <h1 class="text-2xl font-bold text-gray-800">Advanced AI Client</h1> |
| </div> |
| <div class="flex space-x-2"> |
| <button id="web-search-btn" class="p-2 rounded-full hover:bg-gray-200 transition" title="Web Search"> |
| <i class="fas fa-globe text-gray-600"></i> |
| </button> |
| <button id="settings-btn" class="p-2 rounded-full hover:bg-gray-200 transition" title="Settings"> |
| <i class="fas fa-cog text-gray-600"></i> |
| </button> |
| </div> |
| </header> |
|
|
| |
| <div class="flex flex-col md:flex-row gap-6"> |
| |
| <aside class="hidden md:block w-64 bg-white rounded-lg shadow p-4 h-fit sticky top-4"> |
| <div class="mb-6"> |
| <h2 class="font-semibold text-gray-700 mb-2">Conversations</h2> |
| <ul id="conversation-list" class="space-y-1"> |
| <li class="p-2 rounded hover:bg-gray-100 cursor-pointer flex items-center justify-between bg-blue-50"> |
| <span>New Chat</span> |
| <i class="fas fa-plus text-blue-500"></i> |
| </li> |
| <li class="p-2 rounded hover:bg-gray-100 cursor-pointer">API Configuration Help</li> |
| <li class="p-2 rounded hover:bg-gray-100 cursor-pointer">Creative Writing</li> |
| <li class="p-2 rounded hover:bg-gray-100 cursor-pointer">Code Assistance</li> |
| <li class="p-2 rounded hover:bg-gray-100 cursor-pointer">Multimodal Analysis</li> |
| </ul> |
| </div> |
| <div> |
| <h2 class="font-semibold text-gray-700 mb-2">Features</h2> |
| <div class="space-y-2"> |
| <div class="flex items-center p-2 rounded hover:bg-gray-100 cursor-pointer"> |
| <i class="fas fa-globe text-blue-500 mr-2"></i> |
| <span>Web Search</span> |
| </div> |
| <div class="flex items-center p-2 rounded hover:bg-gray-100 cursor-pointer"> |
| <i class="fas fa-file-upload text-blue-500 mr-2"></i> |
| <span>File Upload</span> |
| </div> |
| <div class="flex items-center p-2 rounded hover:bg-gray-100 cursor-pointer"> |
| <i class="fas fa-images text-blue-500 mr-2"></i> |
| <span>Multimodal</span> |
| </div> |
| </div> |
| </div> |
| <div class="mt-4"> |
| <h2 class="font-semibold text-gray-700 mb-2">API Status</h2> |
| <div class="flex items-center mb-2"> |
| <div class="w-3 h-3 rounded-full bg-green-500 mr-2"></div> |
| <span class="text-sm">Free API: Active</span> |
| </div> |
| <div class="flex items-center"> |
| <div class="w-3 h-3 rounded-full bg-gray-300 mr-2"></div> |
| <span class="text-sm">Custom API: Inactive</span> |
| </div> |
| </div> |
| </aside> |
|
|
| |
| <main class="flex-1 bg-white rounded-lg shadow overflow-hidden flex flex-col"> |
| <div id="chat-container" class="flex-1 p-4 overflow-y-auto space-y-4 h-96"> |
| <div class="chat-message ai p-4 max-w-[80%] slide-in"> |
| <div class="flex items-start space-x-2"> |
| <div class="bg-blue-500 text-white p-2 rounded-full"> |
| <i class="fas fa-robot"></i> |
| </div> |
| <div> |
| <p class="font-medium text-gray-700">AI Assistant</p> |
| <p class="text-gray-600 mt-1">Hello! I'm your advanced AI assistant. You can use the free API or configure your own API key in the settings. I support web search, file uploads, and multimodal capabilities. How can I help you today?</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="border-t p-4 bg-gray-50"> |
| |
| <div id="dropzone" class="p-4 mb-3 text-center hidden"> |
| <i class="fas fa-cloud-upload-alt text-3xl text-blue-500 mb-2"></i> |
| <p class="font-medium">Drop files here or click to upload</p> |
| <p class="text-sm text-gray-500 mt-1">Supports images, PDFs, and text files</p> |
| <input type="file" id="file-input" class="hidden" multiple> |
| </div> |
| |
| |
| <div id="file-previews" class="mb-3 hidden"></div> |
| |
| |
| <div id="web-search-toggle" class="flex items-center mb-3 hidden"> |
| <label class="relative inline-flex items-center cursor-pointer"> |
| <input type="checkbox" id="enable-web-search" class="sr-only peer"> |
| <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-500"></div> |
| <span class="ml-3 text-sm font-medium text-gray-700">Enable Web Search</span> |
| </label> |
| </div> |
| |
| |
| <form id="chat-form" class="flex space-x-2"> |
| <div class="flex-1 relative"> |
| <input |
| type="text" |
| id="user-input" |
| placeholder="Type your message here..." |
| class="w-full border border-gray-300 rounded-full px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 pr-10" |
| autocomplete="off" |
| > |
| <button |
| type="button" |
| id="file-upload-btn" |
| class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-blue-500" |
| title="Upload files" |
| > |
| <i class="fas fa-paperclip"></i> |
| </button> |
| </div> |
| <button |
| type="submit" |
| class="bg-blue-500 hover:bg-blue-600 text-white rounded-full p-2 w-10 h-10 flex items-center justify-center transition" |
| > |
| <i class="fas fa-paper-plane"></i> |
| </button> |
| </form> |
| <div class="mt-2 text-xs text-gray-500 flex justify-between"> |
| <span id="api-status">Using: Free API</span> |
| <span id="token-counter">Tokens: 0/1000 (Free limit)</span> |
| </div> |
| </div> |
| </main> |
| </div> |
| </div> |
|
|
| |
| <div id="settings-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 hidden z-50"> |
| <div class="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] overflow-y-auto"> |
| <div class="p-6"> |
| <div class="flex justify-between items-center mb-4"> |
| <h2 class="text-xl font-bold text-gray-800">Advanced API Configuration</h2> |
| <button id="close-settings" class="text-gray-500 hover:text-gray-700"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="space-y-6"> |
| |
| <div> |
| <h3 class="font-medium text-gray-700 mb-2">API Provider</h3> |
| <div class="grid grid-cols-2 gap-3"> |
| <label class="flex items-center space-x-2 cursor-pointer p-3 border rounded hover:bg-gray-50"> |
| <input type="radio" name="api-provider" value="free" class="text-blue-500" checked> |
| <div> |
| <span class="font-medium">Free API</span> |
| <p class="text-xs text-gray-500">Limited capabilities, no setup required</p> |
| </div> |
| </label> |
| <label class="flex items-center space-x-2 cursor-pointer p-3 border rounded hover:bg-gray-50"> |
| <input type="radio" name="api-provider" value="openai" class="text-blue-500"> |
| <div> |
| <span class="font-medium">OpenAI</span> |
| <p class="text-xs text-gray-500">GPT-4, GPT-3.5, DALL·E</p> |
| </div> |
| </label> |
| <label class="flex items-center space-x-2 cursor-pointer p-3 border rounded hover:bg-gray-50"> |
| <input type="radio" name="api-provider" value="anthropic" class="text-blue-500"> |
| <div> |
| <span class="font-medium">Anthropic</span> |
| <p class="text-xs text-gray-500">Claude models</p> |
| </div> |
| </label> |
| <label class="flex items-center space-x-2 cursor-pointer p-3 border rounded hover:bg-gray-50"> |
| <input type="radio" name="api-provider" value="google" class="text-blue-500"> |
| <div> |
| <span class="font-medium">Google</span> |
| <p class="text-xs text-gray-500">Gemini, Vertex AI</p> |
| </div> |
| </label> |
| <label class="flex items-center space-x-2 cursor-pointer p-3 border rounded hover:bg-gray-50"> |
| <input type="radio" name="api-provider" value="custom" class="text-blue-500"> |
| <div> |
| <span class="font-medium">Custom API</span> |
| <p class="text-xs text-gray-500">Fully customizable endpoint</p> |
| </div> |
| </label> |
| </div> |
| </div> |
| |
| |
| <div id="api-key-section" class="hidden"> |
| <h3 class="font-medium text-gray-700 mb-2">API Key</h3> |
| <div class="flex space-x-2"> |
| <input |
| type="password" |
| id="api-key-input" |
| placeholder="Enter your API key" |
| class="flex-1 border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" |
| > |
| <button id="toggle-api-key" class="bg-gray-100 hover:bg-gray-200 px-3 rounded"> |
| <i class="fas fa-eye"></i> |
| </button> |
| </div> |
| <p class="text-xs text-gray-500 mt-1">Your API key is stored locally in your browser and never sent to our servers.</p> |
| </div> |
| |
| |
| <div id="custom-api-section" class="hidden"> |
| <h3 class="font-medium text-gray-700 mb-2">Custom API Configuration</h3> |
| <div class="space-y-3"> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">API Endpoint</label> |
| <input |
| type="text" |
| id="api-endpoint-input" |
| placeholder="https://api.example.com/v1/chat" |
| class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" |
| > |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Request Method</label> |
| <select id="api-method-select" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| <option value="POST">POST</option> |
| <option value="GET">GET</option> |
| <option value="PUT">PUT</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Headers (JSON)</label> |
| <textarea |
| id="api-headers-input" |
| placeholder='{"Content-Type": "application/json", "Authorization": "Bearer $API_KEY"}' |
| class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 h-20" |
| ></textarea> |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Request Body Template (JSON)</label> |
| <textarea |
| id="api-body-input" |
| placeholder='{"model": "$MODEL", "messages": $MESSAGES, "max_tokens": 1000}' |
| class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 h-20" |
| ></textarea> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div> |
| <h3 class="font-medium text-gray-700 mb-2">Model Selection</h3> |
| <select id="model-select" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| <option value="gpt-3.5-turbo">GPT-3.5 Turbo (Free)</option> |
| <option value="gpt-4">GPT-4</option> |
| <option value="claude-2">Claude 2</option> |
| <option value="gemini-pro">Gemini Pro</option> |
| <option value="custom">Custom Model</option> |
| </select> |
| </div> |
| |
| |
| <div class="pt-4 border-t"> |
| <h3 class="font-medium text-gray-700 mb-2">Features</h3> |
| <div class="space-y-3"> |
| <label class="flex items-center justify-between cursor-pointer"> |
| <div> |
| <span class="font-medium">Web Search</span> |
| <p class="text-xs text-gray-500">Enable internet search for up-to-date information</p> |
| </div> |
| <label class="relative inline-flex items-center cursor-pointer"> |
| <input type="checkbox" id="enable-web-search-setting" class="sr-only peer"> |
| <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-500"></div> |
| </label> |
| </label> |
| <label class="flex items-center justify-between cursor-pointer"> |
| <div> |
| <span class="font-medium">File Upload</span> |
| <p class="text-xs text-gray-500">Process documents, images, and other files</p> |
| </div> |
| <label class="relative inline-flex items-center cursor-pointer"> |
| <input type="checkbox" id="enable-file-upload-setting" class="sr-only peer"> |
| <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-500"></div> |
| </label> |
| </label> |
| <label class="flex items-center justify-between cursor-pointer"> |
| <div> |
| <span class="font-medium">Multimodal Support</span> |
| <p class="text-xs text-gray-500">Process both text and images together</p> |
| </div> |
| <label class="relative inline-flex items-center cursor-pointer"> |
| <input type="checkbox" id="enable-multimodal-setting" class="sr-only peer"> |
| <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-500"></div> |
| </label> |
| </label> |
| </div> |
| </div> |
| |
| |
| <div class="pt-4 border-t"> |
| <button id="save-settings" class="w-full bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded transition"> |
| Save Configuration |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="web-search-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 hidden z-50"> |
| <div class="bg-white rounded-lg shadow-xl w-full max-w-md max-h-[90vh] overflow-y-auto"> |
| <div class="p-6"> |
| <div class="flex justify-between items-center mb-4"> |
| <h2 class="text-xl font-bold text-gray-800">Web Search Configuration</h2> |
| <button id="close-web-search" class="text-gray-500 hover:text-gray-700"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="space-y-4"> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Search Query</label> |
| <input |
| type="text" |
| id="search-query-input" |
| placeholder="What would you like to search for?" |
| class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" |
| > |
| </div> |
| |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Search Engine</label> |
| <select id="search-engine-select" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| <option value="google">Google</option> |
| <option value="bing">Bing</option> |
| <option value="custom">Custom</option> |
| </select> |
| </div> |
| |
| <div id="custom-search-engine-section" class="hidden"> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Custom Search API</label> |
| <input |
| type="text" |
| id="custom-search-api-input" |
| placeholder="https://api.example.com/search?q={query}" |
| class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" |
| > |
| </div> |
| |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Number of Results</label> |
| <input |
| type="range" |
| id="search-results-count" |
| min="1" |
| max="10" |
| value="3" |
| class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer" |
| > |
| <div class="flex justify-between text-xs text-gray-500"> |
| <span>1</span> |
| <span>3</span> |
| <span>5</span> |
| <span>7</span> |
| <span>10</span> |
| </div> |
| </div> |
| |
| <div class="pt-2"> |
| <button id="perform-search" class="w-full bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded transition"> |
| Perform Search |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const settingsBtn = document.getElementById('settings-btn'); |
| const closeSettings = document.getElementById('close-settings'); |
| const settingsModal = document.getElementById('settings-modal'); |
| const saveSettingsBtn = document.getElementById('save-settings'); |
| const chatForm = document.getElementById('chat-form'); |
| const userInput = document.getElementById('user-input'); |
| const chatContainer = document.getElementById('chat-container'); |
| const apiKeySection = document.getElementById('api-key-section'); |
| const customApiSection = document.getElementById('custom-api-section'); |
| const apiProviderRadios = document.querySelectorAll('input[name="api-provider"]'); |
| const modelSelect = document.getElementById('model-select'); |
| const tokenCounter = document.getElementById('token-counter'); |
| const apiStatus = document.getElementById('api-status'); |
| const fileUploadBtn = document.getElementById('file-upload-btn'); |
| const dropzone = document.getElementById('dropzone'); |
| const fileInput = document.getElementById('file-input'); |
| const filePreviews = document.getElementById('file-previews'); |
| const webSearchBtn = document.getElementById('web-search-btn'); |
| const webSearchModal = document.getElementById('web-search-modal'); |
| const closeWebSearch = document.getElementById('close-web-search'); |
| const webSearchToggle = document.getElementById('web-search-toggle'); |
| const enableWebSearch = document.getElementById('enable-web-search'); |
| const toggleApiKey = document.getElementById('toggle-api-key'); |
| const apiKeyInput = document.getElementById('api-key-input'); |
| const customSearchEngineSection = document.getElementById('custom-search-engine-section'); |
| const searchEngineSelect = document.getElementById('search-engine-select'); |
| const performSearchBtn = document.getElementById('perform-search'); |
| |
| |
| let uploadedFiles = []; |
| let isWebSearchEnabled = false; |
| let searchResults = []; |
| |
| |
| let settings = JSON.parse(localStorage.getItem('aiClientSettings')) || { |
| apiProvider: 'free', |
| apiKey: '', |
| apiEndpoint: '', |
| apiMethod: 'POST', |
| apiHeaders: '{"Content-Type": "application/json"}', |
| apiBody: '{"model": "$MODEL", "messages": $MESSAGES}', |
| model: 'gpt-3.5-turbo', |
| tokenUsage: 0, |
| features: { |
| webSearch: false, |
| fileUpload: true, |
| multimodal: false |
| } |
| }; |
| |
| |
| updateUIFromSettings(); |
| |
| |
| settingsBtn.addEventListener('click', () => { |
| settingsModal.classList.remove('hidden'); |
| }); |
| |
| closeSettings.addEventListener('click', () => { |
| settingsModal.classList.add('hidden'); |
| }); |
| |
| apiProviderRadios.forEach(radio => { |
| radio.addEventListener('change', function() { |
| updateAPIConfigurationUI(); |
| }); |
| }); |
| |
| searchEngineSelect.addEventListener('change', function() { |
| if (this.value === 'custom') { |
| customSearchEngineSection.classList.remove('hidden'); |
| } else { |
| customSearchEngineSection.classList.add('hidden'); |
| } |
| }); |
| |
| toggleApiKey.addEventListener('click', function() { |
| const type = apiKeyInput.getAttribute('type') === 'password' ? 'text' : 'password'; |
| apiKeyInput.setAttribute('type', type); |
| this.innerHTML = type === 'password' ? '<i class="fas fa-eye"></i>' : '<i class="fas fa-eye-slash"></i>'; |
| }); |
| |
| saveSettingsBtn.addEventListener('click', function() { |
| const selectedProvider = document.querySelector('input[name="api-provider"]:checked').value; |
| const apiKey = document.getElementById('api-key-input').value; |
| const apiEndpoint = document.getElementById('api-endpoint-input').value; |
| const apiMethod = document.getElementById('api-method-select').value; |
| const apiHeaders = document.getElementById('api-headers-input').value; |
| const apiBody = document.getElementById('api-body-input').value; |
| const model = document.getElementById('model-select').value; |
| const webSearchEnabled = document.getElementById('enable-web-search-setting').checked; |
| const fileUploadEnabled = document.getElementById('enable-file-upload-setting').checked; |
| const multimodalEnabled = document.getElementById('enable-multimodal-setting').checked; |
| |
| settings = { |
| apiProvider: selectedProvider, |
| apiKey: apiKey, |
| apiEndpoint: apiEndpoint, |
| apiMethod: apiMethod, |
| apiHeaders: apiHeaders, |
| apiBody: apiBody, |
| model: model, |
| tokenUsage: settings.tokenUsage, |
| features: { |
| webSearch: webSearchEnabled, |
| fileUpload: fileUploadEnabled, |
| multimodal: multimodalEnabled |
| } |
| }; |
| |
| localStorage.setItem('aiClientSettings', JSON.stringify(settings)); |
| updateUIFromSettings(); |
| settingsModal.classList.add('hidden'); |
| |
| |
| showToast('Settings saved successfully!', 'success'); |
| }); |
| |
| fileUploadBtn.addEventListener('click', function() { |
| if (settings.features.fileUpload) { |
| fileInput.click(); |
| } else { |
| showToast('File upload is disabled in settings', 'warning'); |
| } |
| }); |
| |
| fileInput.addEventListener('change', function(e) { |
| handleFiles(e.target.files); |
| }); |
| |
| |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { |
| dropzone.addEventListener(eventName, preventDefaults, false); |
| }); |
| |
| function preventDefaults(e) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| } |
| |
| ['dragenter', 'dragover'].forEach(eventName => { |
| dropzone.addEventListener(eventName, highlight, false); |
| }); |
| |
| ['dragleave', 'drop'].forEach(eventName => { |
| dropzone.addEventListener(eventName, unhighlight, false); |
| }); |
| |
| function highlight() { |
| dropzone.classList.add('active'); |
| } |
| |
| function unhighlight() { |
| dropzone.classList.remove('active'); |
| } |
| |
| dropzone.addEventListener('drop', function(e) { |
| if (settings.features.fileUpload) { |
| const dt = e.dataTransfer; |
| const files = dt.files; |
| handleFiles(files); |
| } else { |
| showToast('File upload is disabled in settings', 'warning'); |
| } |
| }); |
| |
| dropzone.addEventListener('click', function() { |
| if (settings.features.fileUpload) { |
| fileInput.click(); |
| } else { |
| showToast('File upload is disabled in settings', 'warning'); |
| } |
| }); |
| |
| webSearchBtn.addEventListener('click', function() { |
| if (settings.features.webSearch) { |
| webSearchModal.classList.remove('hidden'); |
| } else { |
| showToast('Web search is disabled in settings', 'warning'); |
| } |
| }); |
| |
| closeWebSearch.addEventListener('click', function() { |
| webSearchModal.classList.add('hidden'); |
| }); |
| |
| enableWebSearch.addEventListener('change', function() { |
| isWebSearchEnabled = this.checked; |
| if (this.checked) { |
| showToast('Web search enabled for this conversation', 'info'); |
| } |
| }); |
| |
| performSearchBtn.addEventListener('click', function() { |
| const query = document.getElementById('search-query-input').value; |
| const searchEngine = document.getElementById('search-engine-select').value; |
| const customSearchAPI = document.getElementById('custom-search-api-input').value; |
| const resultCount = document.getElementById('search-results-count').value; |
| |
| if (!query) { |
| showToast('Please enter a search query', 'warning'); |
| return; |
| } |
| |
| |
| simulateWebSearch(query, resultCount); |
| webSearchModal.classList.add('hidden'); |
| }); |
| |
| chatForm.addEventListener('submit', function(e) { |
| e.preventDefault(); |
| const message = userInput.value.trim(); |
| if (message === '' && uploadedFiles.length === 0) return; |
| |
| |
| addMessageToChat('user', message); |
| userInput.value = ''; |
| |
| |
| const typingIndicator = document.createElement('div'); |
| typingIndicator.className = 'chat-message ai p-4 max-w-[80%]'; |
| typingIndicator.innerHTML = ` |
| <div class="flex items-start space-x-2"> |
| <div class="bg-blue-500 text-white p-2 rounded-full"> |
| <i class="fas fa-robot"></i> |
| </div> |
| <div> |
| <p class="font-medium text-gray-700">AI Assistant</p> |
| <div class="typing-indicator mt-1"> |
| <span></span> |
| <span></span> |
| <span></span> |
| </div> |
| </div> |
| </div> |
| `; |
| chatContainer.appendChild(typingIndicator); |
| chatContainer.scrollTop = chatContainer.scrollHeight; |
| |
| |
| setTimeout(() => { |
| |
| typingIndicator.remove(); |
| |
| |
| let response; |
| if (settings.apiProvider === 'free') { |
| |
| const tokensUsed = Math.floor(Math.random() * 30) + 20; |
| settings.tokenUsage += tokensUsed; |
| localStorage.setItem('aiClientSettings', JSON.stringify(settings)); |
| updateTokenCounter(); |
| |
| |
| const freeResponses = [ |
| "I'm responding using the free API service. For more advanced features, consider using your own API key.", |
| "This is a sample response from the free tier. The free API has limited capabilities compared to premium services.", |
| "Free API response: I can help with basic queries. For complex tasks, you might want to configure your own API key in settings.", |
| "Using the free service, my responses are limited. Did you know you can connect your own API for better performance?", |
| "This is a simulated response from the free API. In a real implementation, this would connect to an actual AI service." |
| ]; |
| response = freeResponses[Math.floor(Math.random() * freeResponses.length)]; |
| } else { |
| |
| const premiumResponses = [ |
| "This would be the actual response from your configured API. The web app would make a real API call to the service you specified.", |
| "If you had configured your API key, this response would come directly from the AI service provider you selected.", |
| "In a production environment, this would connect to the API endpoint you specified in the settings.", |
| "With a custom API, you would receive higher quality responses with more capabilities than the free tier.", |
| "Your own API key would enable more advanced features and longer conversations." |
| ]; |
| response = premiumResponses[Math.floor(Math.random() * premiumResponses.length)]; |
| } |
| |
| |
| if (isWebSearchEnabled && searchResults.length > 0) { |
| response += "\n\nWeb search results:\n"; |
| searchResults.forEach((result, index) => { |
| response += `${index + 1}. ${result.title}\n ${result.url}\n`; |
| }); |
| searchResults = []; |
| } |
| |
| |
| if (uploadedFiles.length > 0) { |
| response += "\n\nI've processed the following files:\n"; |
| uploadedFiles.forEach(file => { |
| response += `- ${file.name} (${file.type})\n`; |
| }); |
| |
| if (settings.features.multimodal) { |
| response += "\nI've analyzed the content of these files using multimodal capabilities."; |
| } |
| |
| |
| uploadedFiles = []; |
| filePreviews.innerHTML = ''; |
| filePreviews.classList.add('hidden'); |
| dropzone.classList.add('hidden'); |
| } |
| |
| addMessageToChat('ai', response); |
| }, 1500); |
| }); |
| |
| |
| function updateAPIConfigurationUI() { |
| const selectedProvider = document.querySelector('input[name="api-provider"]:checked').value; |
| |
| if (selectedProvider === 'free') { |
| apiKeySection.classList.add('hidden'); |
| customApiSection.classList.add('hidden'); |
| modelSelect.innerHTML = ` |
| <option value="gpt-3.5-turbo">GPT-3.5 Turbo (Free)</option> |
| <option value="gpt-3.5-turbo-16k">GPT-3.5 Turbo 16k</option> |
| `; |
| } else if (selectedProvider === 'custom') { |
| apiKeySection.classList.remove('hidden'); |
| customApiSection.classList.remove('hidden'); |
| modelSelect.innerHTML = ` |
| <option value="custom">Custom Model</option> |
| `; |
| } else { |
| apiKeySection.classList.remove('hidden'); |
| customApiSection.classList.add('hidden'); |
| if (selectedProvider === 'openai') { |
| modelSelect.innerHTML = ` |
| <option value="gpt-3.5-turbo">GPT-3.5 Turbo</option> |
| <option value="gpt-4">GPT-4</option> |
| <option value="gpt-4-turbo">GPT-4 Turbo</option> |
| <option value="dall-e">DALL·E (Image Generation)</option> |
| `; |
| } else if (selectedProvider === 'anthropic') { |
| modelSelect.innerHTML = ` |
| <option value="claude-2">Claude 2</option> |
| <option value="claude-instant">Claude Instant</option> |
| `; |
| } else if (selectedProvider === 'google') { |
| modelSelect.innerHTML = ` |
| <option value="gemini-pro">Gemini Pro</option> |
| <option value="gemini-vision">Gemini Vision</option> |
| `; |
| } |
| } |
| } |
| |
| function updateUIFromSettings() { |
| |
| document.querySelector(`input[name="api-provider"][value="${settings.apiProvider}"]`).checked = true; |
| |
| |
| document.getElementById('api-key-input').value = settings.apiKey; |
| document.getElementById('api-endpoint-input').value = settings.apiEndpoint; |
| document.getElementById('api-method-select').value = settings.apiMethod; |
| document.getElementById('api-headers-input').value = settings.apiHeaders; |
| document.getElementById('api-body-input').value = settings.apiBody; |
| |
| |
| modelSelect.value = settings.model; |
| |
| |
| document.getElementById('enable-web-search-setting').checked = settings.features.webSearch; |
| document.getElementById('enable-file-upload-setting').checked = settings.features.fileUpload; |
| document.getElementById('enable-multimodal-setting').checked = settings.features.multimodal; |
| |
| |
| updateAPIConfigurationUI(); |
| |
| |
| updateTokenCounter(); |
| |
| |
| apiStatus.textContent = `Using: ${settings.apiProvider === 'free' ? 'Free API' : `${settings.apiProvider.charAt(0).toUpperCase() + settings.apiProvider.slice(1)} API`}`; |
| |
| |
| if (settings.features.webSearch) { |
| webSearchToggle.classList.remove('hidden'); |
| } else { |
| webSearchToggle.classList.add('hidden'); |
| } |
| } |
| |
| function updateTokenCounter() { |
| if (settings.apiProvider === 'free') { |
| const remaining = Math.max(0, 1000 - settings.tokenUsage); |
| tokenCounter.textContent = `Tokens: ${settings.tokenUsage}/1000 (${remaining} remaining)`; |
| |
| if (remaining < 200) { |
| tokenCounter.classList.add('text-red-500'); |
| tokenCounter.classList.remove('text-gray-500'); |
| } else { |
| tokenCounter.classList.remove('text-red-500'); |
| tokenCounter.classList.add('text-gray-500'); |
| } |
| } else { |
| tokenCounter.textContent = `Using: ${settings.apiProvider.charAt(0).toUpperCase() + settings.apiProvider.slice(1)} API`; |
| tokenCounter.classList.remove('text-red-500'); |
| tokenCounter.classList.add('text-gray-500'); |
| } |
| } |
| |
| function addMessageToChat(role, content) { |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = `chat-message ${role} p-4 max-w-[80%] slide-in`; |
| |
| if (role === 'user') { |
| messageDiv.innerHTML = ` |
| <div class="flex items-start space-x-2 justify-end"> |
| <div> |
| <p class="font-medium text-gray-700 text-right">You</p> |
| <p class="text-gray-600 mt-1">${content}</p> |
| </div> |
| <div class="bg-gray-500 text-white p-2 rounded-full"> |
| <i class="fas fa-user"></i> |
| </div> |
| </div> |
| `; |
| } else { |
| messageDiv.innerHTML = ` |
| <div class="flex items-start space-x-2"> |
| <div class="bg-blue-500 text-white p-2 rounded-full"> |
| <i class="fas fa-robot"></i> |
| </div> |
| <div> |
| <p class="font-medium text-gray-700">AI Assistant</p> |
| <p class="text-gray-600 mt-1 whitespace-pre-line">${content}</p> |
| </div> |
| </div> |
| `; |
| } |
| |
| chatContainer.appendChild(messageDiv); |
| chatContainer.scrollTop = chatContainer.scrollHeight; |
| } |
| |
| function handleFiles(files) { |
| if (!settings.features.fileUpload) { |
| showToast('File upload is disabled in settings', 'warning'); |
| return; |
| } |
| |
| for (let i = 0; i < files.length; i++) { |
| const file = files[i]; |
| if (file.size > 10 * 1024 * 1024) { |
| showToast(`File ${file.name} is too large (max 10MB)`, 'error'); |
| continue; |
| } |
| |
| uploadedFiles.push(file); |
| previewFile(file); |
| } |
| |
| if (uploadedFiles.length > 0) { |
| dropzone.classList.add('hidden'); |
| filePreviews.classList.remove('hidden'); |
| } |
| } |
| |
| function previewFile(file) { |
| const previewDiv = document.createElement('div'); |
| previewDiv.className = 'flex items-center justify-between p-2 bg-gray-100 rounded mb-2'; |
| |
| const fileInfoDiv = document.createElement('div'); |
| fileInfoDiv.className = 'flex items-center space-x-2'; |
| |
| let icon; |
| if (file.type.startsWith('image/')) { |
| icon = '<i class="fas fa-image text-blue-500"></i>'; |
| |
| |
| const reader = new FileReader(); |
| reader.onload = function(e) { |
| const imgPreview = document.createElement('img'); |
| imgPreview.src = e.target.result; |
| imgPreview.className = 'file-preview'; |
| fileInfoDiv.appendChild(imgPreview); |
| }; |
| reader.readAsDataURL(file); |
| } else if (file.type === 'application/pdf') { |
| icon = '<i class="fas fa-file-pdf text-red-500"></i>'; |
| } else if (file.type.startsWith('text/')) { |
| icon = '<i class="fas fa-file-alt text-green-500"></i>'; |
| } else { |
| icon = '<i class="fas fa-file text-gray-500"></i>'; |
| } |
| |
| fileInfoDiv.innerHTML += ` |
| ${icon} |
| <div> |
| <p class="text-sm font-medium">${file.name}</p> |
| <p class="text-xs text-gray-500">${formatFileSize(file.size)}</p> |
| <div class="progress-bar"> |
| <div class="progress-bar-fill" style="width: 100%"></div> |
| </div> |
| </div> |
| `; |
| |
| const removeBtn = document.createElement('button'); |
| removeBtn.className = 'text-red-500 hover:text-red-700'; |
| removeBtn.innerHTML = '<i class="fas fa-times"></i>'; |
| removeBtn.onclick = function() { |
| uploadedFiles = uploadedFiles.filter(f => f !== file); |
| previewDiv.remove(); |
| if (uploadedFiles.length === 0) { |
| filePreviews.classList.add('hidden'); |
| dropzone.classList.remove('hidden'); |
| } |
| }; |
| |
| previewDiv.appendChild(fileInfoDiv); |
| previewDiv.appendChild(removeBtn); |
| filePreviews.appendChild(previewDiv); |
| } |
| |
| function formatFileSize(bytes) { |
| if (bytes < 1024) return bytes + ' bytes'; |
| else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; |
| else return (bytes / 1048576).toFixed(1) + ' MB'; |
| } |
| |
| function simulateWebSearch(query, count) { |
| |
| searchResults = []; |
| for (let i = 0; i < count; i++) { |
| searchResults.push({ |
| title: `Search result for "${query}" (${i + 1})`, |
| url: `https://example.com/search?q=${encodeURIComponent(query)}&result=${i + 1}`, |
| snippet: `This is a simulated snippet for search result ${i + 1} about "${query}". In a real implementation, this would come from an actual search API.` |
| }); |
| } |
| |
| showToast(`Found ${count} web results for "${query}"`, 'success'); |
| isWebSearchEnabled = true; |
| enableWebSearch.checked = true; |
| } |
| |
| function showToast(message, type) { |
| const colors = { |
| success: 'bg-green-500', |
| error: 'bg-red-500', |
| warning: 'bg-yellow-500', |
| info: 'bg-blue-500' |
| }; |
| |
| const toast = document.createElement('div'); |
| toast.className = `fixed bottom-4 right-4 ${colors[type]} text-white px-4 py-2 rounded shadow-lg flex items-center space-x-2 animate-fade-in`; |
| toast.innerHTML = ` |
| <i class="fas ${type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-exclamation-circle' : type === 'warning' ? 'fa-exclamation-triangle' : 'fa-info-circle'}"></i> |
| <span>${message}</span> |
| `; |
| |
| document.body.appendChild(toast); |
| |
| setTimeout(() => { |
| toast.classList.add('animate-fade-out'); |
| setTimeout(() => { |
| toast.remove(); |
| }, 300); |
| }, 3000); |
| } |
| |
| |
| updateTokenCounter(); |
| if (settings.features.fileUpload) { |
| dropzone.classList.remove('hidden'); |
| } |
| }); |
| </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=chagtptmm/chatbox" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |