|
<!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> |