| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Puter AI Chat</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"> |
| <script src="https://js.puter.com/v2/"></script> |
| <script> |
| tailwind.config = { |
| theme: { |
| extend: { |
| colors: { |
| dark: { |
| 900: '#121212', |
| 800: '#1e1e1e', |
| 700: '#2d2d2d', |
| 600: '#3d3d3d', |
| } |
| } |
| } |
| } |
| } |
| </script> |
| <style> |
| .scrollbar-hidden::-webkit-scrollbar { |
| display: none; |
| } |
| .scrollbar-hidden { |
| -ms-overflow-style: none; |
| scrollbar-width: none; |
| } |
| .fade-in { |
| animation: fadeIn 0.3s ease-in; |
| } |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| .model-card { |
| transition: all 0.2s ease; |
| } |
| .model-card:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); |
| } |
| .settings-tab { |
| transition: all 0.3s ease; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100 dark:bg-dark-900 text-gray-900 dark:text-gray-100 min-h-screen flex flex-col"> |
| |
| <header class="bg-white dark:bg-dark-800 shadow-md p-4 flex items-center justify-between z-10"> |
| <div class="flex items-center space-x-3"> |
| <button id="newChatBtn" class="p-2 rounded-full bg-blue-500 text-white hover:bg-blue-600 transition-colors"> |
| <i class="fas fa-plus"></i> |
| </button> |
| <h1 class="text-xl font-bold">Puter AI Chat</h1> |
| </div> |
| |
| <div class="flex items-center space-x-3"> |
| <div class="relative flex justify-center"> |
| <select id="modelSelect" class="bg-white dark:bg-dark-700 border border-gray-300 dark:border-dark-600 rounded-lg py-2 pl-3 pr-8 appearance-none focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| |
| </select> |
| <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700 dark:text-gray-300"> |
| <i class="fas fa-chevron-down"></i> |
| </div> |
| </div> |
| |
| <button id="settingsBtn" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-dark-700 transition-colors"> |
| <i class="fas fa-cog text-xl"></i> |
| </button> |
| <div class="relative"> |
| <button id="authBtn" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-dark-700 transition-colors"> |
| <i class="fas fa-user text-xl"></i> |
| </button> |
| <div id="userCard" class="absolute right-0 mt-2 w-48 bg-white dark:bg-dark-800 rounded-lg shadow-lg p-4 hidden z-50"> |
| <div class="flex items-center mb-3"> |
| <i class="fas fa-user-circle text-2xl mr-2"></i> |
| <span id="username" class="font-medium"></span> |
| </div> |
| <button id="signOutBtn" class="w-full text-left px-3 py-2 text-sm text-red-500 hover:bg-red-50 dark:hover:bg-dark-700 rounded"> |
| <i class="fas fa-sign-out-alt mr-2"></i>Sign Out |
| </button> |
| </div> |
| </div> |
| </div> |
| </header> |
|
|
| |
| <div id="chatContainer" class="flex-1 overflow-y-auto p-4 scrollbar-hidden"> |
| |
| <div class="flex justify-center items-center h-full text-gray-500 dark:text-gray-400"> |
| <div class="text-center"> |
| <i class="fas fa-robot text-4xl mb-2"></i> |
| <p>Start a conversation with Puter AI</p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-white dark:bg-dark-800 border-t border-gray-200 dark:border-dark-700 p-4"> |
| <div class="max-w-4xl mx-auto flex"> |
| <input |
| type="text" |
| id="messageInput" |
| placeholder="Type your message..." |
| class="flex-1 bg-gray-100 dark:bg-dark-700 border border-gray-300 dark:border-dark-600 rounded-l-lg py-3 px-4 focus:outline-none focus:ring-2 focus:ring-blue-500" |
| > |
| <button |
| id="sendBtn" |
| class="bg-blue-500 hover:bg-blue-600 text-white px-6 rounded-r-lg transition-colors" |
| > |
| <i class="fas fa-paper-plane"></i> |
| </button> |
| </div> |
| </div> |
|
|
| |
| <footer class="bg-white dark:bg-dark-800 border-t border-gray-200 dark:border-dark-700 py-3 text-center text-sm text-gray-600 dark:text-gray-400"> |
| Created By <a href="https://jayreddin.github.io/" target="_blank" class="text-blue-500 hover:underline">Jamie Reddin</a> |
| using <a href="https://puter.com/" target="_blank" class="text-blue-500 hover:underline">Puter.com</a> | Version: 1.0 |
| </footer> |
|
|
| |
| <div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 hidden"> |
| <div class="bg-white dark:bg-dark-800 rounded-xl w-full max-w-2xl max-h-[90vh] overflow-hidden flex flex-col"> |
| <div class="p-5 border-b border-gray-200 dark:border-dark-700 flex justify-between items-center"> |
| <h2 class="text-xl font-bold">Settings</h2> |
| <button id="closeSettings" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="flex border-b border-gray-200 dark:border-dark-700"> |
| <button class="settings-tab px-5 py-3 font-medium border-b-2 border-blue-500 text-blue-500">UI Settings</button> |
| <button class="settings-tab px-5 py-3 font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">AI Models</button> |
| </div> |
| |
| <div class="flex-1 overflow-y-auto p-5"> |
| |
| <div id="uiSettingsTab"> |
| <div class="mb-6"> |
| <label class="block mb-2 font-medium">Theme</label> |
| <select id="themeSelect" class="w-full bg-white dark:bg-dark-700 border border-gray-300 dark:border-dark-600 rounded-lg py-2 px-3 focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| <option value="light">Light</option> |
| <option value="dark">Dark</option> |
| </select> |
| </div> |
| |
| <div> |
| <label class="block mb-2 font-medium">Text Size: <span id="textSizeValue">50%</span></label> |
| <input |
| type="range" |
| id="textSizeSlider" |
| min="1" |
| max="100" |
| value="50" |
| class="w-full h-2 bg-gray-200 dark:bg-dark-700 rounded-lg appearance-none cursor-pointer" |
| > |
| </div> |
| </div> |
| |
| |
| <div id="aiModelsTab" class="hidden"> |
| <div class="mb-4"> |
| <p class="text-gray-600 dark:text-gray-400 mb-3">Select models to include in the dropdown:</p> |
| <button id="selectAllBtn" class="text-sm bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded mr-2">Select All</button> |
| <button id="deselectAllBtn" class="text-sm bg-gray-500 hover:bg-gray-600 text-white px-3 py-1 rounded">Deselect All</button> |
| </div> |
| |
| <div class="space-y-6"> |
| |
| </div> |
| </div> |
| </div> |
| |
| <div class="p-5 border-t border-gray-200 dark:border-dark-700 flex justify-end space-x-3"> |
| <button id="cancelSettings" class="px-4 py-2 border border-gray-300 dark:border-dark-600 rounded-lg hover:bg-gray-100 dark:hover:bg-dark-700">Cancel</button> |
| <button id="saveSettings" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg">Save Changes</button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| const state = { |
| currentChat: [], |
| selectedModel: 'gpt-4o', |
| enabledModels: [ |
| 'gpt-5', 'gpt-4.1', 'gpt-4o', |
| 'claude-sonnet-4', 'google/gemini-2.0-flash-exp:free', |
| 'qwen/qwen3-235b-a22b-07-25', 'meta-llama/llama-4-maverick', |
| 'deepseek-chat', 'x-ai/grok-4' |
| ], |
| theme: 'light', |
| textSize: 50, |
| models: { |
| 'OpenAI': [ |
| 'gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'gpt-5-chat-latest', |
| 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano', 'gpt-4.5-preview', |
| 'gpt-4o', 'gpt-4o-mini', 'o1', 'o1-mini', 'o1-pro', 'o3', 'o3-mini', 'o4-mini' |
| ], |
| 'Claude': [ |
| 'claude-sonnet-4', 'claude-opus-4', 'claude-3-7-sonnet', 'claude-3-5-sonnet' |
| ], |
| 'DeepSeek': [ |
| 'deepseek-chat', 'deepseek-reasoner' |
| ], |
| 'Gemini': [ |
| 'google/gemini-2.0-flash-001', 'google/gemini-2.0-flash-exp:free', |
| 'google/gemini-2.0-flash-lite-001', 'google/gemini-2.5-flash', |
| 'google/gemini-2.5-flash-lite-preview-06-17', 'google/gemini-2.5-flash-preview', |
| 'google/gemini-2.5-flash-preview-05-20', 'google/gemini-2.5-flash-preview-05-20:thinking', |
| 'google/gemini-2.5-flash-preview:thinking', 'google/gemini-2.5-pro', |
| 'google/gemini-2.5-pro-exp-03-25', 'google/gemini-2.5-pro-preview', |
| 'google/gemini-2.5-pro-preview-05-06', 'google/gemini-flash-1.5', |
| 'google/gemini-flash-1.5-8b', 'google/gemini-pro-1.5' |
| ], |
| 'Llama': [ |
| 'meta-llama/llama-3-70b-instruct', 'meta-llama/llama-3-8b-instruct', |
| 'meta-llama/llama-3.1-405b', 'meta-llama/llama-3.1-405b-instruct', |
| 'meta-llama/llama-3.1-70b-instruct', 'meta-llama/llama-3.1-8b-instruct', |
| 'meta-llama/llama-3.2-11b-vision-instruct', 'meta-llama/llama-3.2-11b-vision-instruct:free', |
| 'meta-llama/llama-3.2-1b-instruct', 'meta-llama/llama-3.2-3b-instruct', |
| 'meta-llama/llama-3.2-90b-vision-instruct', 'meta-llama/llama-3.3-70b-instruct', |
| 'meta-llama/llama-3.3-70b-instruct:free', 'meta-llama/llama-4-maverick', |
| 'meta-llama/llama-4-maverick:free', 'meta-llama/llama-4-scout', |
| 'meta-llama/llama-4-scout:free', 'meta-llama/llama-guard-2-8b', |
| 'meta-llama/llama-guard-3-8b', 'meta-llama/llama-guard-4-12b' |
| ], |
| 'Grok': [ |
| 'x-ai/grok-2-1212', 'x-ai/grok-2-vision-1212', 'x-ai/grok-3', |
| 'x-ai/grok-3-beta', 'x-ai/grok-3-mini', 'x-ai/grok-3-mini-beta', |
| 'x-ai/grok-4', 'x-ai/grok-vision-beta' |
| ], |
| 'Mistral': [ |
| 'mistral-large-latest', 'mistral-medium-latest', 'mistral-small-latest', |
| 'open-mistral-nemo', 'codestral-latest' |
| ], |
| 'Qwen': [ |
| 'qwen/qwen-2-72b-instruct', 'qwen/qwen-2.5-72b-instruct', |
| 'qwen/qwen-2.5-72b-instruct:free', 'qwen/qwen-2.5-7b-instruct', |
| 'qwen/qwen-2.5-coder-32b-instruct', 'qwen/qwen-2.5-coder-32b-instruct:free', |
| 'qwen/qwen-2.5-vl-7b-instruct', 'qwen/qwen-max', 'qwen/qwen-plus', |
| 'qwen/qwen-turbo', 'qwen/qwen-vl-max', 'qwen/qwen-vl-plus', |
| 'qwen/qwen2.5-vl-32b-instruct', 'qwen/qwen2.5-vl-32b-instruct:free', |
| 'qwen/qwen2.5-vl-72b-instruct', 'qwen/qwen2.5-vl-72b-instruct:free', |
| 'qwen/qwen3-14b', 'qwen/qwen3-14b:free', 'qwen/qwen3-235b-a22b', |
| 'qwen/qwen3-235b-a22b-07-25', 'qwen/qwen3-235b-a22b-07-25:free', |
| 'qwen/qwen3-235b-a22b:free', 'qwen/qwen3-30b-a3b', 'qwen/qwen3-30b-a3b:free', |
| 'qwen/qwen3-32b', 'qwen/qwen3-4b:free', 'qwen/qwen3-8b', 'qwen/qwen3-8b:free', |
| 'qwen/qwen3-coder', 'qwen/qwq-32b', 'qwen/qwq-32b-preview', 'qwen/qwq-32b:free' |
| ] |
| } |
| }; |
| |
| |
| const elements = { |
| chatContainer: document.getElementById('chatContainer'), |
| messageInput: document.getElementById('messageInput'), |
| sendBtn: document.getElementById('sendBtn'), |
| modelSelect: document.getElementById('modelSelect'), |
| newChatBtn: document.getElementById('newChatBtn'), |
| settingsBtn: document.getElementById('settingsBtn'), |
| settingsModal: document.getElementById('settingsModal'), |
| closeSettings: document.getElementById('closeSettings'), |
| cancelSettings: document.getElementById('cancelSettings'), |
| saveSettings: document.getElementById('saveSettings'), |
| uiSettingsTab: document.getElementById('uiSettingsTab'), |
| aiModelsTab: document.getElementById('aiModelsTab'), |
| themeSelect: document.getElementById('themeSelect'), |
| textSizeSlider: document.getElementById('textSizeSlider'), |
| textSizeValue: document.getElementById('textSizeValue'), |
| selectAllBtn: document.getElementById('selectAllBtn'), |
| deselectAllBtn: document.getElementById('deselectAllBtn'), |
| authBtn: document.getElementById('authBtn'), |
| userCard: document.getElementById('userCard'), |
| username: document.getElementById('username'), |
| signOutBtn: document.getElementById('signOutBtn') |
| }; |
| |
| |
| function initApp() { |
| renderModelOptions(); |
| setupEventListeners(); |
| loadSettings(); |
| updateTheme(); |
| updateAuthStatus(); |
| } |
| |
| |
| function renderModelOptions() { |
| elements.modelSelect.innerHTML = ''; |
| state.enabledModels.forEach(model => { |
| const option = document.createElement('option'); |
| option.value = model; |
| option.textContent = model; |
| if (model === state.selectedModel) { |
| option.selected = true; |
| } |
| elements.modelSelect.appendChild(option); |
| }); |
| } |
| |
| |
| function setupEventListeners() { |
| |
| elements.sendBtn.addEventListener('click', sendMessage); |
| |
| |
| elements.messageInput.addEventListener('keypress', (e) => { |
| if (e.key === 'Enter') { |
| sendMessage(); |
| } |
| }); |
| |
| |
| elements.modelSelect.addEventListener('change', (e) => { |
| state.selectedModel = e.target.value; |
| }); |
| |
| |
| elements.newChatBtn.addEventListener('click', () => { |
| state.currentChat = []; |
| renderChat(); |
| }); |
| |
| |
| elements.settingsBtn.addEventListener('click', () => { |
| renderSettingsModal(); |
| elements.settingsModal.classList.remove('hidden'); |
| }); |
| |
| |
| elements.authBtn.addEventListener('click', toggleAuth); |
| |
| |
| elements.authBtn.addEventListener('mouseenter', () => { |
| if (puter.auth.isSignedIn()) { |
| elements.username.textContent = puter.auth.getUser().username; |
| elements.userCard.classList.remove('hidden'); |
| } |
| }); |
| |
| elements.authBtn.addEventListener('mouseleave', (e) => { |
| |
| setTimeout(() => { |
| if (!elements.userCard.matches(':hover') && !elements.authBtn.matches(':hover')) { |
| elements.userCard.classList.add('hidden'); |
| } |
| }, 100); |
| }); |
| |
| elements.userCard.addEventListener('mouseleave', () => { |
| elements.userCard.classList.add('hidden'); |
| }); |
| |
| |
| elements.signOutBtn.addEventListener('click', () => { |
| puter.auth.signOut(); |
| elements.userCard.classList.add('hidden'); |
| updateAuthStatus(); |
| }); |
| |
| elements.closeSettings.addEventListener('click', () => { |
| elements.settingsModal.classList.add('hidden'); |
| }); |
| |
| elements.cancelSettings.addEventListener('click', () => { |
| elements.settingsModal.classList.add('hidden'); |
| }); |
| |
| |
| elements.settingsModal.addEventListener('click', (e) => { |
| if (e.target === elements.settingsModal) { |
| elements.settingsModal.classList.add('hidden'); |
| } |
| }); |
| |
| |
| document.addEventListener('keydown', (e) => { |
| if (e.key === 'Escape' && !elements.settingsModal.classList.contains('hidden')) { |
| elements.settingsModal.classList.add('hidden'); |
| } |
| }); |
| |
| |
| elements.saveSettings.addEventListener('click', () => { |
| saveSettings(); |
| saveSettingsToStorage(); |
| }); |
| |
| |
| document.querySelectorAll('.settings-tab')[0].addEventListener('click', () => { |
| elements.uiSettingsTab.classList.remove('hidden'); |
| elements.aiModelsTab.classList.add('hidden'); |
| document.querySelectorAll('.settings-tab')[0].classList.add('border-blue-500', 'text-blue-500'); |
| document.querySelectorAll('.settings-tab')[0].classList.remove('text-gray-500', 'dark:text-gray-400'); |
| document.querySelectorAll('.settings-tab')[1].classList.remove('border-blue-500', 'text-blue-500'); |
| document.querySelectorAll('.settings-tab')[1].classList.add('text-gray-500', 'dark:text-gray-400'); |
| }); |
| |
| |
| document.querySelectorAll('.settings-tab')[1].addEventListener('click', () => { |
| elements.uiSettingsTab.classList.add('hidden'); |
| elements.aiModelsTab.classList.remove('hidden'); |
| document.querySelectorAll('.settings-tab')[1].classList.add('border-blue-500', 'text-blue-500'); |
| document.querySelectorAll('.settings-tab')[1].classList.remove('text-gray-500', 'dark:text-gray-400'); |
| document.querySelectorAll('.settings-tab')[0].classList.remove('border-blue-500', 'text-blue-500'); |
| document.querySelectorAll('.settings-tab')[0].classList.add('text-gray-500', 'dark:text-gray-400'); |
| }); |
| |
| |
| elements.textSizeSlider.addEventListener('input', (e) => { |
| elements.textSizeValue.textContent = `${e.target.value}%`; |
| }); |
| |
| |
| elements.selectAllBtn.addEventListener('click', () => { |
| document.querySelectorAll('.model-checkbox').forEach(checkbox => { |
| checkbox.checked = true; |
| }); |
| }); |
| |
| elements.deselectAllBtn.addEventListener('click', () => { |
| document.querySelectorAll('.model-checkbox').forEach(checkbox => { |
| checkbox.checked = false; |
| }); |
| }); |
| } |
| |
| |
| function renderSettingsModal() { |
| |
| elements.themeSelect.value = state.theme; |
| elements.textSizeSlider.value = state.textSize; |
| elements.textSizeValue.textContent = `${state.textSize}%`; |
| |
| |
| const aiModelsContainer = elements.aiModelsTab.querySelector('div'); |
| aiModelsContainer.innerHTML = ''; |
| |
| Object.keys(state.models).forEach(provider => { |
| const providerSection = document.createElement('div'); |
| providerSection.className = 'model-card bg-gray-100 dark:bg-dark-700 rounded-lg p-4'; |
| |
| const providerTitle = document.createElement('h3'); |
| providerTitle.className = 'font-bold mb-3 text-lg'; |
| providerTitle.textContent = provider; |
| providerSection.appendChild(providerTitle); |
| |
| const modelsGrid = document.createElement('div'); |
| modelsGrid.className = 'grid grid-cols-1 md:grid-cols-2 gap-2'; |
| |
| state.models[provider].forEach(model => { |
| const modelItem = document.createElement('div'); |
| modelItem.className = 'flex items-center p-2 hover:bg-gray-200 dark:hover:bg-dark-600 rounded'; |
| |
| const checkbox = document.createElement('input'); |
| checkbox.type = 'checkbox'; |
| checkbox.className = 'model-checkbox mr-2 h-4 w-4 text-blue-500 rounded'; |
| checkbox.id = `model-${model}`; |
| checkbox.value = model; |
| checkbox.checked = state.enabledModels.includes(model); |
| |
| const label = document.createElement('label'); |
| label.className = 'text-sm flex-1 cursor-pointer'; |
| label.htmlFor = `model-${model}`; |
| label.textContent = model; |
| |
| modelItem.appendChild(checkbox); |
| modelItem.appendChild(label); |
| modelsGrid.appendChild(modelItem); |
| }); |
| |
| providerSection.appendChild(modelsGrid); |
| aiModelsContainer.appendChild(providerSection); |
| }); |
| } |
| |
| |
| function saveSettings() { |
| |
| state.theme = elements.themeSelect.value; |
| state.textSize = parseInt(elements.textSizeSlider.value); |
| |
| |
| const selectedModels = []; |
| document.querySelectorAll('.model-checkbox:checked').forEach(checkbox => { |
| selectedModels.push(checkbox.value); |
| }); |
| |
| if (selectedModels.length > 0) { |
| state.enabledModels = selectedModels; |
| state.selectedModel = selectedModels[0]; |
| renderModelOptions(); |
| } |
| |
| |
| updateTheme(); |
| updateTextSize(); |
| } |
| |
| |
| function updateTheme() { |
| if (state.theme === 'dark') { |
| document.documentElement.classList.add('dark'); |
| } else { |
| document.documentElement.classList.remove('dark'); |
| } |
| } |
| |
| |
| function updateTextSize() { |
| document.body.style.fontSize = `${state.textSize}%`; |
| } |
| |
| |
| function loadSettings() { |
| const savedSettings = localStorage.getItem('puterAiChatSettings'); |
| if (savedSettings) { |
| const settings = JSON.parse(savedSettings); |
| state.theme = settings.theme || 'light'; |
| state.textSize = settings.textSize || 50; |
| state.enabledModels = settings.enabledModels || state.enabledModels; |
| state.selectedModel = settings.selectedModel || state.selectedModel; |
| } |
| } |
| |
| |
| function saveSettingsToStorage() { |
| const settings = { |
| theme: state.theme, |
| textSize: state.textSize, |
| enabledModels: state.enabledModels, |
| selectedModel: state.selectedModel |
| }; |
| localStorage.setItem('puterAiChatSettings', JSON.stringify(settings)); |
| } |
| |
| |
| async function sendMessage() { |
| const message = elements.messageInput.value.trim(); |
| if (!message) return; |
| |
| |
| if (!puter.auth.isSignedIn()) { |
| const success = await puter.auth.signIn(); |
| if (!success) { |
| alert('Authentication failed. Please try again.'); |
| return; |
| } |
| updateAuthStatus(); |
| } |
| |
| |
| const userMessage = { |
| id: Date.now(), |
| text: message, |
| sender: 'user', |
| timestamp: new Date() |
| }; |
| |
| state.currentChat.unshift(userMessage); |
| |
| |
| const aiMessage = { |
| id: Date.now() + 1, |
| text: 'Thinking...', |
| sender: 'ai', |
| model: state.selectedModel, |
| timestamp: new Date() |
| }; |
| |
| state.currentChat.unshift(aiMessage); |
| |
| |
| elements.messageInput.value = ''; |
| renderChat(); |
| |
| |
| puter.ai.chat(message, { model: state.selectedModel }) |
| .then(response => { |
| |
| const aiMessageIndex = state.currentChat.findIndex(msg => msg.id === aiMessage.id); |
| if (aiMessageIndex !== -1) { |
| state.currentChat[aiMessageIndex].text = response; |
| state.currentChat[aiMessageIndex].timestamp = new Date(); |
| renderChat(); |
| } |
| }) |
| .catch(error => { |
| console.error('AI API Error:', error); |
| |
| const aiMessageIndex = state.currentChat.findIndex(msg => msg.id === aiMessage.id); |
| if (aiMessageIndex !== -1) { |
| state.currentChat[aiMessageIndex].text = "Sorry, I encountered an error processing your request."; |
| state.currentChat[aiMessageIndex].timestamp = new Date(); |
| renderChat(); |
| } |
| }); |
| } |
| |
| |
| function renderChat() { |
| if (state.currentChat.length === 0) { |
| elements.chatContainer.innerHTML = ` |
| <div class="flex justify-center items-center h-full text-gray-500 dark:text-gray-400"> |
| <div class="text-center"> |
| <i class="fas fa-robot text-4xl mb-2"></i> |
| <p>Start a conversation with Puter AI</p> |
| </div> |
| </div> |
| `; |
| return; |
| } |
| |
| elements.chatContainer.innerHTML = ''; |
| |
| state.currentChat.forEach(message => { |
| const messageElement = document.createElement('div'); |
| messageElement.className = `fade-in mb-6 ${message.sender === 'user' ? 'flex justify-end' : 'flex justify-start'}`; |
| |
| const messageContent = document.createElement('div'); |
| messageContent.className = `max-w-[80%] rounded-2xl p-4 ${ |
| message.sender === 'user' |
| ? 'bg-blue-500 text-white rounded-br-none' |
| : 'bg-gray-200 dark:bg-dark-700 rounded-bl-none' |
| }`; |
| |
| |
| const timestamp = document.createElement('div'); |
| timestamp.className = `text-xs mb-1 ${ |
| message.sender === 'user' ? 'text-blue-100' : 'text-gray-500 dark:text-gray-400' |
| }`; |
| timestamp.textContent = formatTime(message.timestamp); |
| |
| |
| if (message.sender === 'ai') { |
| const modelLabel = document.createElement('div'); |
| modelLabel.className = 'text-xs font-bold mb-1 text-blue-500'; |
| modelLabel.textContent = message.model; |
| messageContent.appendChild(modelLabel); |
| } |
| |
| |
| const textElement = document.createElement('div'); |
| textElement.className = 'mb-2'; |
| textElement.textContent = message.text; |
| messageContent.appendChild(timestamp); |
| messageContent.appendChild(textElement); |
| |
| |
| const actions = document.createElement('div'); |
| actions.className = 'flex justify-end space-x-2 mt-2'; |
| |
| |
| const resendBtn = document.createElement('button'); |
| resendBtn.className = 'text-xs p-1 rounded hover:bg-black/10 dark:hover:bg-white/10'; |
| resendBtn.innerHTML = '<i class="fas fa-redo"></i>'; |
| resendBtn.title = 'Resend'; |
| resendBtn.addEventListener('click', () => { |
| elements.messageInput.value = message.text; |
| elements.messageInput.focus(); |
| }); |
| |
| |
| const copyBtn = document.createElement('button'); |
| copyBtn.className = 'text-xs p-1 rounded hover:bg-black/10 dark:hover:bg-white/10'; |
| copyBtn.innerHTML = '<i class="fas fa-copy"></i>'; |
| copyBtn.title = 'Copy'; |
| copyBtn.addEventListener('click', () => { |
| navigator.clipboard.writeText(message.text); |
| |
| const originalIcon = copyBtn.innerHTML; |
| copyBtn.innerHTML = '<i class="fas fa-check"></i>'; |
| setTimeout(() => { |
| copyBtn.innerHTML = originalIcon; |
| }, 1000); |
| }); |
| |
| |
| const deleteBtn = document.createElement('button'); |
| deleteBtn.className = 'text-xs p-1 rounded hover:bg-black/10 dark:hover:bg-white/10'; |
| deleteBtn.innerHTML = '<i class="fas fa-trash"></i>'; |
| deleteBtn.title = 'Delete'; |
| deleteBtn.addEventListener('click', () => { |
| state.currentChat = state.currentChat.filter(msg => msg.id !== message.id); |
| renderChat(); |
| }); |
| |
| actions.appendChild(resendBtn); |
| actions.appendChild(copyBtn); |
| actions.appendChild(deleteBtn); |
| messageContent.appendChild(actions); |
| |
| messageElement.appendChild(messageContent); |
| elements.chatContainer.appendChild(messageElement); |
| }); |
| |
| |
| elements.chatContainer.scrollTop = elements.chatContainer.scrollHeight; |
| } |
| |
| |
| function formatTime(date) { |
| return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', initApp); |
| |
| |
| window.addEventListener('beforeunload', saveSettingsToStorage); |
| </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=Jayreddin/jjjj" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |