deepseek-clone / index.html
kamranrafi's picture
Add a Load More button, once the button is clicked, it loads more chats - Follow Up Deployment
99ebe84 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DeepSeek Chat Clone</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>
/* Custom context menu */
.context-menu {
position: absolute;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 1000;
display: none;
}
.context-menu-item {
padding: 8px 16px;
cursor: pointer;
font-size: 14px;
}
.context-menu-item:hover {
background-color: #f0f0f0;
}
/* Custom scrollbar */
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Chat bubble animation */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.chat-message {
animation: fadeIn 0.3s ease-out;
}
/* Loading dots animation */
@keyframes blink {
0% { opacity: 0.2; }
20% { opacity: 1; }
100% { opacity: 0.2; }
}
.loading-dots span {
animation-name: blink;
animation-duration: 1.4s;
animation-iteration-count: infinite;
animation-fill-mode: both;
}
.loading-dots span:nth-child(2) {
animation-delay: 0.2s;
}
.loading-dots span:nth-child(3) {
animation-delay: 0.4s;
}
.rotate-180 {
transform: rotate(180deg);
}
/* Progress bar styling */
.progress-bar {
height: 3px;
background-color: #e5e7eb;
border-radius: 3px;
overflow: hidden;
margin-top: 8px;
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(to right, #3b82f6, #8b5cf6);
width: 0%;
transition: width 0.3s ease;
}
/* Prevent text selection in context menu */
.context-menu {
user-select: none;
-webkit-user-select: none;
}
/* Code block styling */
pre {
background-color: #f3f4f6;
border-radius: 0.5rem;
padding: 1rem;
overflow-x: auto;
margin: 0.5rem 0;
}
code {
font-family: 'Courier New', Courier, monospace;
font-size: 0.9rem;
}
</style>
</head>
<body class="bg-gray-50 h-screen flex overflow-hidden">
<!-- Sidebar -->
<div class="w-64 bg-white border-r border-gray-200 flex flex-col h-full">
<!-- New Chat Button -->
<div class="p-4">
<button id="newChatBtn" class="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg border border-gray-200 hover:bg-gray-50 transition-colors text-gray-800 font-medium">
<i class="fas fa-plus"></i>
<span>New Chat</span>
</button>
</div>
<!-- Chat History -->
<div class="flex-1 overflow-y-auto custom-scrollbar">
<div id="chatHistory" class="space-y-1 px-2">
<!-- Chat history items will be added here -->
</div>
<div class="p-2 text-center">
<button id="loadMoreBtn" class="text-sm text-gray-500 hover:text-blue-500 px-3 py-1 rounded hover:bg-gray-100">
Load More
</button>
</div>
</div>
<!-- User Section -->
<div class="p-4 border-t border-gray-200">
<div class="flex items-center gap-3 p-2 rounded-lg hover:bg-gray-50 cursor-pointer">
<div class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white font-medium">U</div>
<span class="text-gray-700 font-medium">User</span>
</div>
</div>
</div>
<!-- Main Chat Area -->
<div class="flex-1 flex flex-col h-full overflow-hidden">
<!-- Chat Header -->
<div class="p-4 border-b border-gray-200 flex items-center justify-between bg-white">
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center">
<i class="fas fa-robot text-white"></i>
</div>
<h1 class="text-lg font-semibold text-gray-800">DeepSeek Chat</h1>
</div>
<button class="md:hidden p-2 rounded-lg hover:bg-gray-100">
<i class="fas fa-bars text-gray-600"></i>
</button>
</div>
<!-- Messages Container -->
<div id="messagesContainer" class="flex-1 overflow-y-auto custom-scrollbar p-4 space-y-6">
<!-- Welcome message -->
<div class="max-w-3xl mx-auto">
<div class="flex items-start gap-4">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center flex-shrink-0">
<i class="fas fa-robot text-white"></i>
</div>
<div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100 flex-1">
<h2 class="font-semibold text-gray-800 mb-2">Welcome to DeepSeek Chat</h2>
<p class="text-gray-600">I'm an AI assistant here to help you with your questions. How can I assist you today?</p>
</div>
</div>
</div>
<!-- Example conversation (will be replaced with dynamic content) -->
<div id="chatMessages">
<!-- Messages will be added here -->
</div>
<!-- Loading indicator (hidden by default) -->
<div id="loadingIndicator" class="max-w-3xl mx-auto hidden">
<div class="flex items-start gap-4">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center flex-shrink-0">
<i class="fas fa-robot text-white"></i>
</div>
<div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100 flex-1">
<div class="loading-dots flex space-x-1">
<span class="h-2 w-2 rounded-full bg-gray-400"></span>
<span class="h-2 w-2 rounded-full bg-gray-400"></span>
<span class="h-2 w-2 rounded-full bg-gray-400"></span>
</div>
</div>
</div>
</div>
</div>
<!-- Input Area -->
<div class="p-4 border-t border-gray-200 bg-white">
<div class="max-w-3xl mx-auto">
<form id="chatForm" class="relative">
<div class="flex flex-col">
<div id="filePreview" class="flex flex-wrap gap-2 mb-2"></div>
<div class="flex">
<textarea
id="userInput"
rows="1"
placeholder="Message DeepSeek Chat..."
class="w-full p-4 pr-12 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
style="min-height: 60px; max-height: 200px;"
></textarea>
<div class="absolute right-3 bottom-3 flex gap-1">
<label for="fileInput" class="p-2 rounded-lg text-gray-500 hover:bg-gray-100 cursor-pointer">
<i class="fas fa-paperclip"></i>
<input type="file" id="fileInput" class="hidden" multiple>
</label>
<button
type="submit"
class="p-2 rounded-lg bg-blue-500 text-white hover:bg-blue-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
disabled
>
<i class="fas fa-paper-plane"></i>
</button>
</div>
</div>
</div>
</form>
<p class="text-xs text-gray-500 mt-2 text-center">DeepSeek Chat may produce inaccurate information about people, places, or facts.</p>
</div>
</div>
</div>
<div id="contextMenu" class="context-menu">
<div id="copyMenuItem" class="context-menu-item">Copy</div>
<div id="pasteMenuItem" class="context-menu-item">Paste</div>
</div>
<div id="chatContextMenu" class="context-menu">
<div id="deleteChatMenuItem" class="context-menu-item">Delete Chat</div>
</div>
<script>
function toggleThinking(element) {
const content = element.nextElementSibling;
const chevron = element.querySelector('i');
content.classList.toggle('hidden');
chevron.classList.toggle('rotate-180');
}
// Track copied text
let copiedText = '';
// Handle context menu
const contextMenu = document.getElementById('contextMenu');
const copyMenuItem = document.getElementById('copyMenuItem');
const pasteMenuItem = document.getElementById('pasteMenuItem');
const userInput = document.getElementById('userInput');
// Hide default context menu
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
// Only show for content areas (excluding chat history items)
const isContentArea = (e.target.closest('#messagesContainer') ||
e.target.closest('#userInput')) &&
!e.target.closest('#chatHistory > div');
if (isContentArea) {
// Get selected text
const selection = window.getSelection();
const hasSelection = selection.toString().trim() !== '';
// Show/hide menu items based on context
copyMenuItem.style.display = hasSelection ? 'block' : 'none';
pasteMenuItem.style.display = copiedText ? 'block' : 'none';
// Position menu
contextMenu.style.display = 'block';
contextMenu.style.left = `${e.pageX}px`;
contextMenu.style.top = `${e.pageY}px`;
}
});
// Hide menu when clicking elsewhere
document.addEventListener('click', function() {
contextMenu.style.display = 'none';
});
// Copy handler
copyMenuItem.addEventListener('click', function() {
const selection = window.getSelection();
copiedText = selection.toString();
navigator.clipboard.writeText(copiedText)
.then(() => {
contextMenu.style.display = 'none';
})
.catch(err => {
console.error('Failed to copy text: ', err);
});
});
// Paste handler
pasteMenuItem.addEventListener('click', function() {
if (copiedText) {
const cursorPos = userInput.selectionStart;
const currentValue = userInput.value;
userInput.value = currentValue.substring(0, cursorPos) +
copiedText +
currentValue.substring(cursorPos);
userInput.focus();
userInput.selectionStart = cursorPos + copiedText.length;
userInput.selectionEnd = cursorPos + copiedText.length;
contextMenu.style.display = 'none';
}
});
// Track selected chat item
let selectedChatItem = null;
// Handle chat context menu
const chatContextMenu = document.getElementById('chatContextMenu');
const deleteChatMenuItem = document.getElementById('deleteChatMenuItem');
// Show chat context menu for chat history items
document.addEventListener('contextmenu', function(e) {
const chatItem = e.target.closest('#chatHistory > div');
if (chatItem) {
e.preventDefault();
selectedChatItem = chatItem;
chatContextMenu.style.display = 'block';
chatContextMenu.style.left = `${e.pageX}px`;
chatContextMenu.style.top = `${e.pageY}px`;
return;
}
});
// Delete chat handler
deleteChatMenuItem.addEventListener('click', function() {
if (selectedChatItem) {
selectedChatItem.remove();
selectedChatItem = null;
chatContextMenu.style.display = 'none';
}
});
document.addEventListener('DOMContentLoaded', function() {
// DOM elements
const chatForm = document.getElementById('chatForm');
const userInput = document.getElementById('userInput');
const chatMessages = document.getElementById('chatMessages');
const loadingIndicator = document.getElementById('loadingIndicator');
const newChatBtn = document.getElementById('newChatBtn');
const chatHistory = document.getElementById('chatHistory');
const messagesContainer = document.getElementById('messagesContainer');
// Sample chat history data
const allChats = [
{ id: 1, title: "Explain quantum computing", active: false },
{ id: 2, title: "How to make a website responsive", active: false },
{ id: 3, title: "Best practices for React", active: false },
{ id: 4, title: "Python vs JavaScript", active: false },
{ id: 5, title: "History of AI", active: false },
{ id: 6, title: "Machine learning basics", active: false },
{ id: 7, title: "CSS frameworks comparison", active: false },
{ id: 8, title: "JavaScript ES6 features", active: false },
{ id: 9, title: "Database design principles", active: false },
{ id: 10, title: "Cloud computing explained", active: false }
];
let displayedChats = 5;
const chatsPerLoad = 5;
// Initialize chat history
renderChatHistory();
// Load more button event listener
document.getElementById('loadMoreBtn').addEventListener('click', loadMoreChats);
// Enable/disable send button based on input or files
function updateSendButton() {
const sendBtn = chatForm.querySelector('button[type="submit"]');
const hasText = userInput.value.trim() !== '';
const hasFiles = document.getElementById('fileInput').files.length > 0;
sendBtn.disabled = !(hasText || hasFiles);
}
userInput.addEventListener('input', updateSendButton);
// Auto-resize textarea
userInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
});
// Handle Enter key (but allow Shift+Enter for new lines)
userInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
chatForm.dispatchEvent(new Event('submit'));
}
});
// Handle file selection
document.getElementById('fileInput').addEventListener('change', function() {
const preview = document.getElementById('filePreview');
preview.innerHTML = '';
Array.from(this.files).forEach(file => {
const previewItem = document.createElement('div');
previewItem.className = 'flex items-center gap-2 bg-gray-100 px-3 py-1 rounded-full text-sm';
if (file.type.startsWith('image/')) {
const img = document.createElement('img');
img.src = URL.createObjectURL(file);
img.className = 'w-8 h-8 object-cover rounded';
previewItem.prepend(img);
} else {
previewItem.innerHTML = `<i class="fas fa-file text-gray-500"></i>`;
}
previewItem.innerHTML += `
<span class="truncate max-w-xs">${file.name}</span>
<button type="button" class="text-gray-500 hover:text-gray-700" onclick="this.parentNode.remove(); updateSendButton()">
<i class="fas fa-times"></i>
</button>
`;
preview.appendChild(previewItem);
});
updateSendButton();
});
// Form submission
chatForm.addEventListener('submit', function(e) {
e.preventDefault();
const message = userInput.value.trim();
const files = document.getElementById('fileInput').files;
if (message || files.length > 0) {
sendMessage(message, files);
userInput.value = '';
userInput.style.height = 'auto';
document.getElementById('fileInput').value = '';
document.getElementById('filePreview').innerHTML = '';
chatForm.querySelector('button[type="submit"]').disabled = true;
}
});
// New chat button
newChatBtn.addEventListener('click', function() {
clearChat();
});
// Function to send message
function sendMessage(message, files) {
// Add user message to chat
addMessage('user', message, files);
// Show loading indicator
loadingIndicator.classList.remove('hidden');
// Scroll to bottom
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// Simulate API response after delay
setTimeout(() => {
// Hide loading indicator
loadingIndicator.classList.add('hidden');
// Add AI response
const response = generateResponse(message);
const messageDiv = addMessage('ai', response);
// Animate progress bar
const progressBar = messageDiv.querySelector('.progress-bar-fill');
let width = 0;
const interval = setInterval(() => {
if (width >= 100) {
clearInterval(interval);
} else {
width += 5;
progressBar.style.width = width + '%';
}
}, 100);
// Update chat history
if (chatMessages.children.length === 1) { // If this is the first message
addToChatHistory(message);
}
// Scroll to bottom
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}, 1500);
}
// Function to add message to chat (returns the message div)
function addMessage(sender, content, files) {
const messageDiv = document.createElement('div');
messageDiv.className = 'max-w-3xl mx-auto chat-message';
if (sender === 'user') {
let filesHtml = '';
if (files && files.length > 0) {
filesHtml = '<div class="mt-2 flex flex-wrap gap-2">';
Array.from(files).forEach(file => {
if (file.type.startsWith('image/')) {
filesHtml += `
<div class="w-32 h-32 rounded-lg overflow-hidden border border-gray-200">
<img src="${URL.createObjectURL(file)}" class="w-full h-full object-cover">
</div>
`;
} else {
filesHtml += `
<div class="flex items-center gap-2 bg-gray-100 px-3 py-2 rounded-lg">
<i class="fas fa-file text-gray-500"></i>
<span class="text-sm truncate max-w-xs">${file.name}</span>
</div>
`;
}
});
filesHtml += '</div>';
}
messageDiv.innerHTML = `
<div class="flex items-start gap-4 justify-end">
<div class="bg-blue-500 p-4 rounded-lg shadow-sm text-white" style="white-space: pre-wrap;">
${content}
${filesHtml}
</div>
<div class="w-8 h-8 rounded-full bg-gray-300 flex items-center justify-center flex-shrink-0">
<i class="fas fa-user text-gray-600"></i>
</div>
</div>
`;
} else {
// Format code blocks in the content
let formattedContent = content;
if (content.includes('```')) {
formattedContent = content.replace(/```(\w*)\n([\s\S]*?)```/g,
'<pre class="bg-gray-100 p-3 rounded-lg overflow-x-auto my-2"><code class="$1">$2</code></pre>');
}
messageDiv.innerHTML = `
<div class="flex items-start gap-4">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center flex-shrink-0">
<i class="fas fa-robot text-white"></i>
</div>
<div class="bg-white rounded-lg shadow-sm border border-gray-100 flex-1 overflow-hidden">
<div class="thinking-section p-4 border-b border-gray-100 bg-gray-50">
<div class="flex items-center justify-between cursor-pointer" onclick="toggleThinking(this)">
<span class="text-gray-500 italic">Thinking...</span>
<i class="fas fa-chevron-down text-gray-400 text-xs transition-transform duration-200"></i>
</div>
<div class="thinking-content mt-2 text-gray-500 italic hidden">
Let me think about that...
</div>
</div>
<div class="content-section p-4">
${formattedContent}
<div class="progress-bar">
<div class="progress-bar-fill"></div>
</div>
</div>
</div>
</div>
`;
}
chatMessages.appendChild(messageDiv);
return messageDiv;
}
// Function to generate a response (simulated)
function generateResponse(message) {
const responses = {
"hello": "Hello there! How can I assist you today?",
"hi": "Hi! What would you like to know?",
"how are you": "I'm just a computer program, so I don't have feelings, but I'm ready to help you with anything you need!",
"what can you do": "I can answer questions, help with coding problems, explain concepts, and assist with various topics. What would you like to know?",
"thank you": "You're welcome! Is there anything else I can help you with?",
"bye": "Goodbye! Feel free to come back if you have more questions."
};
const lowerMessage = message.toLowerCase();
for (const [key, value] of Object.entries(responses)) {
if (lowerMessage.includes(key)) {
return value;
}
}
// Default response if no match found
return `I understand you're asking about "${message}". Could you please provide more details or clarify your question?`;
}
// Function to render chat history
function renderChatHistory() {
chatHistory.innerHTML = '';
const chatsToShow = allChats.slice(0, displayedChats);
chatsToShow.forEach(chat => {
const chatItem = document.createElement('div');
chatItem.className = `p-2 rounded-lg cursor-pointer flex items-center gap-2 ${chat.active ? 'bg-gray-100' : 'hover:bg-gray-50'}`;
chatItem.innerHTML = `
<i class="fas fa-comment text-gray-400"></i>
<span class="truncate">${chat.title}</span>
`;
chatItem.addEventListener('click', () => loadChat(chat.id));
chatHistory.appendChild(chatItem);
});
// Show/hide load more button based on remaining chats
const loadMoreBtn = document.getElementById('loadMoreBtn');
if (displayedChats >= allChats.length) {
loadMoreBtn.style.display = 'none';
} else {
loadMoreBtn.style.display = 'inline-block';
}
}
// Load more chats function
function loadMoreChats() {
displayedChats = Math.min(displayedChats + chatsPerLoad, allChats.length);
renderChatHistory();
}
// Function to add to chat history
function addToChatHistory(message) {
const newChat = {
id: Date.now(),
title: message.length > 30 ? message.substring(0, 30) + '...' : message,
active: true
};
allChats.unshift(newChat);
displayedChats++; // Keep the same number of displayed chats
renderChatHistory();
}
// Function to load a chat (simulated)
function loadChat(chatId) {
// In a real app, this would load the chat history from storage
clearChat();
// Mark chat as active in history
allChats.forEach(chat => chat.active = chat.id === chatId);
renderChatHistory();
// Add sample messages for the demo
addMessage('user', sampleChats.find(c => c.id === chatId).title);
addMessage('ai', generateResponse(sampleChats.find(c => c.id === chatId).title));
// Scroll to bottom
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// Function to clear the current chat
function clearChat() {
chatMessages.innerHTML = '';
}
});
</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=kamranrafi/deepseek-clone" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>