jjjj / index.html
Jayreddin's picture
fix settings pop up not dismissing, center model dropdown menu in header, fix hover over user icon - Follow Up Deployment
b39306b verified
<!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 -->
<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">
<!-- Options will be populated by JavaScript -->
</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>
<!-- Chat Container -->
<div id="chatContainer" class="flex-1 overflow-y-auto p-4 scrollbar-hidden">
<!-- Messages will be inserted here -->
<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>
<!-- Input Area -->
<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 -->
<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>
<!-- Settings Modal -->
<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">
<!-- UI Settings Tab -->
<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>
<!-- AI Models Tab -->
<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">
<!-- Provider sections will be generated here -->
</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>
// App state
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'
]
}
};
// DOM Elements
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')
};
// Initialize the app
function initApp() {
renderModelOptions();
setupEventListeners();
loadSettings();
updateTheme();
updateAuthStatus();
}
// Render model options in dropdown
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);
});
}
// Setup event listeners
function setupEventListeners() {
// Send message on button click
elements.sendBtn.addEventListener('click', sendMessage);
// Send message on Enter key
elements.messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
// Model selection change
elements.modelSelect.addEventListener('change', (e) => {
state.selectedModel = e.target.value;
});
// New chat
elements.newChatBtn.addEventListener('click', () => {
state.currentChat = [];
renderChat();
});
// Settings modal
elements.settingsBtn.addEventListener('click', () => {
renderSettingsModal();
elements.settingsModal.classList.remove('hidden');
});
// Auth button
elements.authBtn.addEventListener('click', toggleAuth);
// User card toggle
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) => {
// Delay hiding to allow moving to the card
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');
});
// Sign out
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');
});
// Close modal when clicking outside
elements.settingsModal.addEventListener('click', (e) => {
if (e.target === elements.settingsModal) {
elements.settingsModal.classList.add('hidden');
}
});
// Close settings when pressing Escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !elements.settingsModal.classList.contains('hidden')) {
elements.settingsModal.classList.add('hidden');
}
});
// Save settings
elements.saveSettings.addEventListener('click', () => {
saveSettings();
saveSettingsToStorage();
});
// UI Settings tab
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');
});
// AI Models tab
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');
});
// Text size slider
elements.textSizeSlider.addEventListener('input', (e) => {
elements.textSizeValue.textContent = `${e.target.value}%`;
});
// Select all/deselect all
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;
});
});
}
// Render settings modal content
function renderSettingsModal() {
// UI Settings
elements.themeSelect.value = state.theme;
elements.textSizeSlider.value = state.textSize;
elements.textSizeValue.textContent = `${state.textSize}%`;
// AI Models
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);
});
}
// Save settings
function saveSettings() {
// Save UI settings
state.theme = elements.themeSelect.value;
state.textSize = parseInt(elements.textSizeSlider.value);
// Save AI model settings
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();
}
// Apply settings
updateTheme();
updateTextSize();
}
// Update theme
function updateTheme() {
if (state.theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
// Update text size
function updateTextSize() {
document.body.style.fontSize = `${state.textSize}%`;
}
// Load settings from localStorage
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;
}
}
// Save settings to localStorage
function saveSettingsToStorage() {
const settings = {
theme: state.theme,
textSize: state.textSize,
enabledModels: state.enabledModels,
selectedModel: state.selectedModel
};
localStorage.setItem('puterAiChatSettings', JSON.stringify(settings));
}
// Send message
async function sendMessage() {
const message = elements.messageInput.value.trim();
if (!message) return;
// Check if user is authenticated
if (!puter.auth.isSignedIn()) {
const success = await puter.auth.signIn();
if (!success) {
alert('Authentication failed. Please try again.');
return;
}
updateAuthStatus();
}
// Add user message to chat
const userMessage = {
id: Date.now(),
text: message,
sender: 'user',
timestamp: new Date()
};
state.currentChat.unshift(userMessage);
// Add AI response placeholder
const aiMessage = {
id: Date.now() + 1,
text: 'Thinking...',
sender: 'ai',
model: state.selectedModel,
timestamp: new Date()
};
state.currentChat.unshift(aiMessage);
// Clear input and render chat
elements.messageInput.value = '';
renderChat();
// Call Puter AI API
puter.ai.chat(message, { model: state.selectedModel })
.then(response => {
// Update AI message with actual 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);
// Update AI message with error response
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();
}
});
}
// Render chat messages
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'
}`;
// Timestamp
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);
// Model name for AI messages
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);
}
// Message text
const textElement = document.createElement('div');
textElement.className = 'mb-2';
textElement.textContent = message.text;
messageContent.appendChild(timestamp);
messageContent.appendChild(textElement);
// Action buttons
const actions = document.createElement('div');
actions.className = 'flex justify-end space-x-2 mt-2';
// Resend button
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();
});
// Copy button
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);
// Visual feedback
const originalIcon = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="fas fa-check"></i>';
setTimeout(() => {
copyBtn.innerHTML = originalIcon;
}, 1000);
});
// Delete button
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);
});
// Scroll to bottom
elements.chatContainer.scrollTop = elements.chatContainer.scrollHeight;
}
// Format time as HH:MM
function formatTime(date) {
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
// Initialize the app when DOM is loaded
document.addEventListener('DOMContentLoaded', initApp);
// Save settings beforeunload
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>