Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Qwen AI Chatbot</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: #3b82f6; | |
| color: white; | |
| border-radius: 1rem 1rem 0 1rem; | |
| } | |
| .chat-message.ai { | |
| background-color: #f3f4f6; | |
| color: #1f2937; | |
| border-radius: 1rem 1rem 1rem 0; | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.3s ease-in-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .slide-in { | |
| animation: slideIn 0.3s ease-out; | |
| } | |
| @keyframes slideIn { | |
| from { transform: translateX(100%); } | |
| to { transform: translateX(0); } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 font-sans"> | |
| <div id="app" class="min-h-screen flex flex-col"> | |
| <!-- Auth Modal --> | |
| <div id="auth-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white rounded-lg p-6 w-full max-w-md"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-bold" id="auth-modal-title">Sign In</h2> | |
| <button id="close-auth-modal" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div id="auth-tabs" class="flex border-b mb-4"> | |
| <button class="auth-tab py-2 px-4 font-medium text-blue-500 border-b-2 border-blue-500" data-tab="signin">Sign In</button> | |
| <button class="auth-tab py-2 px-4 font-medium text-gray-500" data-tab="signup">Sign Up</button> | |
| </div> | |
| <div id="signin-form"> | |
| <div class="mb-4"> | |
| <label for="signin-email" class="block text-sm font-medium text-gray-700 mb-1">Email</label> | |
| <input type="email" id="signin-email" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="signin-password" class="block text-sm font-medium text-gray-700 mb-1">Password</label> | |
| <input type="password" id="signin-password" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <button id="signin-btn" class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">Sign In</button> | |
| </div> | |
| <div id="signup-form" class="hidden"> | |
| <div class="mb-4"> | |
| <label for="signup-name" class="block text-sm font-medium text-gray-700 mb-1">Name</label> | |
| <input type="text" id="signup-name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="signup-email" class="block text-sm font-medium text-gray-700 mb-1">Email</label> | |
| <input type="email" id="signup-email" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="signup-password" class="block text-sm font-medium text-gray-700 mb-1">Password</label> | |
| <input type="password" id="signup-password" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="signup-confirm-password" class="block text-sm font-medium text-gray-700 mb-1">Confirm Password</label> | |
| <input type="password" id="signup-confirm-password" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <button id="signup-btn" class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">Sign Up</button> | |
| </div> | |
| <div id="auth-message" class="mt-4 text-sm text-red-500 hidden"></div> | |
| </div> | |
| </div> | |
| <!-- Context Modal --> | |
| <div id="context-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white rounded-lg p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-bold">Chat Context</h2> | |
| <button id="close-context-modal" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">User Information</label> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <div> | |
| <label for="context-name" class="block text-xs text-gray-500 mb-1">Name</label> | |
| <input type="text" id="context-name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <div> | |
| <label for="context-location" class="block text-xs text-gray-500 mb-1">Location</label> | |
| <input type="text" id="context-location" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <div> | |
| <label for="context-pets" class="block text-xs text-gray-500 mb-1">Pets</label> | |
| <input type="text" id="context-pets" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <div> | |
| <label for="context-interests" class="block text-xs text-gray-500 mb-1">Interests</label> | |
| <input type="text" id="context-interests" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="context-info" class="block text-sm font-medium text-gray-700 mb-1">Additional Information</label> | |
| <textarea id="context-info" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="context-instructions" class="block text-sm font-medium text-gray-700 mb-1">Chat Instructions</label> | |
| <textarea id="context-instructions" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea> | |
| <p class="text-xs text-gray-500 mt-1">Tell the AI how you want it to respond (e.g., "Be concise", "Explain like I'm 5", etc.)</p> | |
| </div> | |
| <div class="flex justify-end space-x-2"> | |
| <button id="cancel-context-btn" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50">Cancel</button> | |
| <button id="save-context-btn" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">Save Context</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Rename Chat Modal --> | |
| <div id="rename-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white rounded-lg p-6 w-full max-w-md"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-bold">Rename Chat</h2> | |
| <button id="close-rename-modal" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="new-chat-name" class="block text-sm font-medium text-gray-700 mb-1">New Chat Name</label> | |
| <input type="text" id="new-chat-name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <div class="flex justify-end space-x-2"> | |
| <button id="cancel-rename-btn" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50">Cancel</button> | |
| <button id="save-rename-btn" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">Rename</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Header --> | |
| <header class="bg-white shadow-sm"> | |
| <div class="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8 flex justify-between items-center"> | |
| <h1 class="text-xl font-bold text-gray-900">Qwen AI Chatbot</h1> | |
| <div id="auth-buttons" class="flex items-center space-x-4"> | |
| <button id="signin-btn-header" class="text-gray-600 hover:text-gray-900">Sign In</button> | |
| <button id="signup-btn-header" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">Sign Up</button> | |
| </div> | |
| <div id="user-info" class="hidden flex items-center space-x-4"> | |
| <span id="username-display" class="font-medium"></span> | |
| <button id="signout-btn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300">Sign Out</button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="flex-1 flex overflow-hidden"> | |
| <!-- Sidebar --> | |
| <div id="sidebar" class="hidden md:block w-64 bg-white border-r border-gray-200 overflow-y-auto"> | |
| <div class="p-4"> | |
| <button id="new-chat-btn" class="w-full flex items-center justify-center px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 mb-4"> | |
| <i class="fas fa-plus mr-2"></i> New Chat | |
| </button> | |
| <div class="flex items-center justify-between mb-2"> | |
| <h2 class="font-medium text-gray-700">Your Chats</h2> | |
| <button id="edit-chats-btn" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| </div> | |
| <div id="chat-list" class="space-y-1"> | |
| <!-- Chats will be populated here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Mobile Sidebar Toggle --> | |
| <button id="sidebar-toggle" class="md:hidden fixed bottom-4 right-4 bg-white p-3 rounded-full shadow-lg z-40"> | |
| <i class="fas fa-bars text-gray-700"></i> | |
| </button> | |
| <!-- Mobile Sidebar --> | |
| <div id="mobile-sidebar" class="fixed inset-0 bg-white z-30 transform translate-x-full transition-transform duration-300 ease-in-out md:hidden"> | |
| <div class="p-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-lg font-bold">Your Chats</h2> | |
| <button id="close-mobile-sidebar" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <button id="new-chat-btn-mobile" class="w-full flex items-center justify-center px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 mb-4"> | |
| <i class="fas fa-plus mr-2"></i> New Chat | |
| </button> | |
| <div id="mobile-chat-list" class="space-y-1"> | |
| <!-- Chats will be populated here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Chat Area --> | |
| <div class="flex-1 flex flex-col overflow-hidden"> | |
| <!-- Chat Header --> | |
| <div class="bg-white border-b border-gray-200 p-4 flex justify-between items-center"> | |
| <div class="flex items-center"> | |
| <button id="sidebar-toggle-inline" class="md:hidden mr-4 text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-bars"></i> | |
| </button> | |
| <h2 id="current-chat-title" class="text-lg font-medium">New Chat</h2> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <button id="edit-chat-btn" class="p-2 text-gray-500 hover:text-gray-700" title="Rename Chat"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| <button id="context-btn" class="p-2 text-gray-500 hover:text-gray-700" title="Chat Context"> | |
| <i class="fas fa-cog"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Messages --> | |
| <div id="messages" class="flex-1 overflow-y-auto p-4 space-y-4"> | |
| <div class="text-center py-8 text-gray-500"> | |
| <i class="fas fa-comments text-4xl mb-2"></i> | |
| <p>Start a conversation with Qwen AI</p> | |
| </div> | |
| </div> | |
| <!-- Input Area --> | |
| <div class="bg-white border-t border-gray-200 p-4"> | |
| <form id="message-form" class="flex space-x-2"> | |
| <input | |
| id="message-input" | |
| type="text" | |
| placeholder="Type your message..." | |
| class="flex-1 px-4 py-2 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500" | |
| autocomplete="off" | |
| > | |
| <button | |
| type="submit" | |
| id="send-btn" | |
| class="px-4 py-2 bg-blue-500 text-white rounded-full hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" | |
| > | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </form> | |
| <p class="text-xs text-gray-500 mt-2">Qwen AI may produce inaccurate information about people, places, or facts.</p> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <script> | |
| // API Configuration | |
| const API_KEY = 'sk_ROxVP2yyiAWaOM5X3Ghjp-eqn71Eyh27dX-m43irL8Y'; | |
| const API_URL = 'https://api.novita.ai/v3/inference'; | |
| // State Management | |
| let state = { | |
| currentUser: null, | |
| currentChatId: null, | |
| chats: [], | |
| editMode: false, | |
| isGenerating: false | |
| }; | |
| // DOM Elements | |
| const elements = { | |
| authModal: document.getElementById('auth-modal'), | |
| authModalTitle: document.getElementById('auth-modal-title'), | |
| authTabs: document.getElementById('auth-tabs'), | |
| authTabsButtons: document.querySelectorAll('.auth-tab'), | |
| signinForm: document.getElementById('signin-form'), | |
| signupForm: document.getElementById('signup-form'), | |
| signinEmail: document.getElementById('signin-email'), | |
| signinPassword: document.getElementById('signin-password'), | |
| signinBtn: document.getElementById('signin-btn'), | |
| signupName: document.getElementById('signup-name'), | |
| signupEmail: document.getElementById('signup-email'), | |
| signupPassword: document.getElementById('signup-password'), | |
| signupConfirmPassword: document.getElementById('signup-confirm-password'), | |
| signupBtn: document.getElementById('signup-btn'), | |
| authMessage: document.getElementById('auth-message'), | |
| closeAuthModal: document.getElementById('close-auth-modal'), | |
| contextModal: document.getElementById('context-modal'), | |
| closeContextModal: document.getElementById('close-context-modal'), | |
| cancelContextBtn: document.getElementById('cancel-context-btn'), | |
| saveContextBtn: document.getElementById('save-context-btn'), | |
| renameModal: document.getElementById('rename-modal'), | |
| closeRenameModal: document.getElementById('close-rename-modal'), | |
| cancelRenameBtn: document.getElementById('cancel-rename-btn'), | |
| saveRenameBtn: document.getElementById('save-rename-btn'), | |
| newChatName: document.getElementById('new-chat-name'), | |
| authButtons: document.getElementById('auth-buttons'), | |
| userInfo: document.getElementById('user-info'), | |
| usernameDisplay: document.getElementById('username-display'), | |
| signoutBtn: document.getElementById('signout-btn'), | |
| signinBtnHeader: document.getElementById('signin-btn-header'), | |
| signupBtnHeader: document.getElementById('signup-btn-header'), | |
| sidebar: document.getElementById('sidebar'), | |
| mobileSidebar: document.getElementById('mobile-sidebar'), | |
| sidebarToggle: document.getElementById('sidebar-toggle'), | |
| sidebarToggleInline: document.getElementById('sidebar-toggle-inline'), | |
| closeMobileSidebar: document.getElementById('close-mobile-sidebar'), | |
| chatList: document.getElementById('chat-list'), | |
| mobileChatList: document.getElementById('mobile-chat-list'), | |
| newChatBtn: document.getElementById('new-chat-btn'), | |
| newChatBtnMobile: document.getElementById('new-chat-btn-mobile'), | |
| editChatsBtn: document.getElementById('edit-chats-btn'), | |
| editChatBtn: document.getElementById('edit-chat-btn'), | |
| contextBtn: document.getElementById('context-btn'), | |
| currentChatTitle: document.getElementById('current-chat-title'), | |
| messages: document.getElementById('messages'), | |
| messageForm: document.getElementById('message-form'), | |
| messageInput: document.getElementById('message-input'), | |
| sendBtn: document.getElementById('send-btn'), | |
| contextName: document.getElementById('context-name'), | |
| contextLocation: document.getElementById('context-location'), | |
| contextPets: document.getElementById('context-pets'), | |
| contextInterests: document.getElementById('context-interests'), | |
| contextInfo: document.getElementById('context-info'), | |
| contextInstructions: document.getElementById('context-instructions') | |
| }; | |
| // Initialize the app | |
| function init() { | |
| loadUserFromLocalStorage(); | |
| setupEventListeners(); | |
| renderUI(); | |
| } | |
| // Load user from localStorage | |
| function loadUserFromLocalStorage() { | |
| const userData = localStorage.getItem('qwenChatUser'); | |
| if (userData) { | |
| state.currentUser = JSON.parse(userData); | |
| loadUserChats(); | |
| } | |
| } | |
| // Load user's chats | |
| function loadUserChats() { | |
| if (!state.currentUser) return; | |
| const userChats = localStorage.getItem(`qwenChats_${state.currentUser.id}`); | |
| if (userChats) { | |
| state.chats = JSON.parse(userChats); | |
| // If there are chats but no current chat, set the first one as current | |
| if (state.chats.length > 0 && !state.currentChatId) { | |
| state.currentChatId = state.chats[0].id; | |
| } | |
| } else { | |
| // Create a default chat if none exists | |
| createNewChat(); | |
| } | |
| } | |
| // Save user's chats to localStorage | |
| function saveUserChats() { | |
| if (!state.currentUser) return; | |
| localStorage.setItem(`qwenChats_${state.currentUser.id}`, JSON.stringify(state.chats)); | |
| } | |
| // Setup event listeners | |
| function setupEventListeners() { | |
| // Auth modal | |
| elements.signinBtnHeader.addEventListener('click', () => showAuthModal('signin')); | |
| elements.signupBtnHeader.addEventListener('click', () => showAuthModal('signup')); | |
| elements.closeAuthModal.addEventListener('click', hideAuthModal); | |
| // Auth tabs | |
| elements.authTabsButtons.forEach(button => { | |
| button.addEventListener('click', () => { | |
| const tab = button.dataset.tab; | |
| showAuthTab(tab); | |
| }); | |
| }); | |
| // Auth forms | |
| elements.signinBtn.addEventListener('click', handleSignIn); | |
| elements.signupBtn.addEventListener('click', handleSignUp); | |
| elements.signoutBtn.addEventListener('click', handleSignOut); | |
| // Context modal | |
| elements.contextBtn.addEventListener('click', showContextModal); | |
| elements.closeContextModal.addEventListener('click', hideContextModal); | |
| elements.cancelContextBtn.addEventListener('click', hideContextModal); | |
| elements.saveContextBtn.addEventListener('click', saveContext); | |
| // Rename modal | |
| elements.editChatBtn.addEventListener('click', showRenameModal); | |
| elements.closeRenameModal.addEventListener('click', hideRenameModal); | |
| elements.cancelRenameBtn.addEventListener('click', hideRenameModal); | |
| elements.saveRenameBtn.addEventListener('click', renameCurrentChat); | |
| // Chat operations | |
| elements.newChatBtn.addEventListener('click', createNewChat); | |
| elements.newChatBtnMobile.addEventListener('click', createNewChat); | |
| elements.editChatsBtn.addEventListener('click', toggleEditMode); | |
| // Message form | |
| elements.messageForm.addEventListener('submit', handleMessageSubmit); | |
| // Sidebar toggles | |
| elements.sidebarToggle.addEventListener('click', toggleMobileSidebar); | |
| elements.sidebarToggleInline.addEventListener('click', toggleMobileSidebar); | |
| elements.closeMobileSidebar.addEventListener('click', toggleMobileSidebar); | |
| } | |
| // Show auth modal | |
| function showAuthModal(tab = 'signin') { | |
| elements.authModal.classList.remove('hidden'); | |
| showAuthTab(tab); | |
| } | |
| // Hide auth modal | |
| function hideAuthModal() { | |
| elements.authModal.classList.add('hidden'); | |
| elements.authMessage.classList.add('hidden'); | |
| elements.authMessage.textContent = ''; | |
| } | |
| // Show auth tab | |
| function showAuthTab(tab) { | |
| elements.authTabsButtons.forEach(button => { | |
| if (button.dataset.tab === tab) { | |
| button.classList.add('text-blue-500', 'border-blue-500'); | |
| button.classList.remove('text-gray-500'); | |
| } else { | |
| button.classList.remove('text-blue-500', 'border-blue-500'); | |
| button.classList.add('text-gray-500'); | |
| } | |
| }); | |
| if (tab === 'signin') { | |
| elements.signinForm.classList.remove('hidden'); | |
| elements.signupForm.classList.add('hidden'); | |
| elements.authModalTitle.textContent = 'Sign In'; | |
| } else { | |
| elements.signinForm.classList.add('hidden'); | |
| elements.signupForm.classList.remove('hidden'); | |
| elements.authModalTitle.textContent = 'Sign Up'; | |
| } | |
| } | |
| // Handle sign in | |
| function handleSignIn(e) { | |
| e.preventDefault(); | |
| const email = elements.signinEmail.value.trim(); | |
| const password = elements.signinPassword.value.trim(); | |
| if (!email || !password) { | |
| showAuthMessage('Please fill in all fields'); | |
| return; | |
| } | |
| // In a real app, this would be an API call to your backend | |
| const users = JSON.parse(localStorage.getItem('qwenUsers') || '[]'); | |
| const user = users.find(u => u.email === email && u.password === password); | |
| if (!user) { | |
| showAuthMessage('Invalid email or password'); | |
| return; | |
| } | |
| // Successfully signed in | |
| state.currentUser = user; | |
| localStorage.setItem('qwenChatUser', JSON.stringify(user)); | |
| hideAuthModal(); | |
| loadUserChats(); | |
| renderUI(); | |
| } | |
| // Handle sign up | |
| function handleSignUp(e) { | |
| e.preventDefault(); | |
| const name = elements.signupName.value.trim(); | |
| const email = elements.signupEmail.value.trim(); | |
| const password = elements.signupPassword.value.trim(); | |
| const confirmPassword = elements.signupConfirmPassword.value.trim(); | |
| if (!name || !email || !password || !confirmPassword) { | |
| showAuthMessage('Please fill in all fields'); | |
| return; | |
| } | |
| if (password !== confirmPassword) { | |
| showAuthMessage('Passwords do not match'); | |
| return; | |
| } | |
| if (password.length < 6) { | |
| showAuthMessage('Password must be at least 6 characters'); | |
| return; | |
| } | |
| // Check if email already exists | |
| const users = JSON.parse(localStorage.getItem('qwenUsers') || '[]'); | |
| if (users.some(u => u.email === email)) { | |
| showAuthMessage('Email already in use'); | |
| return; | |
| } | |
| // Create new user | |
| const newUser = { | |
| id: Date.now().toString(), | |
| name, | |
| email, | |
| password, | |
| createdAt: new Date().toISOString() | |
| }; | |
| // Save user | |
| users.push(newUser); | |
| localStorage.setItem('qwenUsers', JSON.stringify(users)); | |
| // Sign in the new user | |
| state.currentUser = newUser; | |
| localStorage.setItem('qwenChatUser', JSON.stringify(newUser)); | |
| hideAuthModal(); | |
| // Create first chat for the new user | |
| createNewChat(); | |
| renderUI(); | |
| } | |
| // Handle sign out | |
| function handleSignOut() { | |
| state.currentUser = null; | |
| state.currentChatId = null; | |
| state.chats = []; | |
| localStorage.removeItem('qwenChatUser'); | |
| renderUI(); | |
| } | |
| // Show auth message | |
| function showAuthMessage(message) { | |
| elements.authMessage.textContent = message; | |
| elements.authMessage.classList.remove('hidden'); | |
| } | |
| // Show context modal | |
| function showContextModal() { | |
| if (!state.currentUser || !state.currentChatId) return; | |
| const chat = state.chats.find(c => c.id === state.currentChatId); | |
| if (!chat) return; | |
| // Fill in the context form with existing data | |
| elements.contextName.value = chat.context?.name || ''; | |
| elements.contextLocation.value = chat.context?.location || ''; | |
| elements.contextPets.value = chat.context?.pets || ''; | |
| elements.contextInterests.value = chat.context?.interests || ''; | |
| elements.contextInfo.value = chat.context?.additionalInfo || ''; | |
| elements.contextInstructions.value = chat.context?.instructions || ''; | |
| elements.contextModal.classList.remove('hidden'); | |
| } | |
| // Hide context modal | |
| function hideContextModal() { | |
| elements.contextModal.classList.add('hidden'); | |
| } | |
| // Save context | |
| function saveContext() { | |
| if (!state.currentUser || !state.currentChatId) return; | |
| const chatIndex = state.chats.findIndex(c => c.id === state.currentChatId); | |
| if (chatIndex === -1) return; | |
| state.chats[chatIndex].context = { | |
| name: elements.contextName.value.trim(), | |
| location: elements.contextLocation.value.trim(), | |
| pets: elements.contextPets.value.trim(), | |
| interests: elements.contextInterests.value.trim(), | |
| additionalInfo: elements.contextInfo.value.trim(), | |
| instructions: elements.contextInstructions.value.trim() | |
| }; | |
| saveUserChats(); | |
| hideContextModal(); | |
| renderChatList(); | |
| } | |
| // Show rename modal | |
| function showRenameModal() { | |
| if (!state.currentUser || !state.currentChatId) return; | |
| const chat = state.chats.find(c => c.id === state.currentChatId); | |
| if (!chat) return; | |
| elements.newChatName.value = chat.title; | |
| elements.renameModal.classList.remove('hidden'); | |
| } | |
| // Hide rename modal | |
| function hideRenameModal() { | |
| elements.renameModal.classList.add('hidden'); | |
| } | |
| // Rename current chat | |
| function renameCurrentChat() { | |
| const newName = elements.newChatName.value.trim(); | |
| if (!newName || !state.currentUser || !state.currentChatId) { | |
| hideRenameModal(); | |
| return; | |
| } | |
| const chatIndex = state.chats.findIndex(c => c.id === state.currentChatId); | |
| if (chatIndex === -1) { | |
| hideRenameModal(); | |
| return; | |
| } | |
| state.chats[chatIndex].title = newName; | |
| saveUserChats(); | |
| hideRenameModal(); | |
| renderUI(); | |
| } | |
| // Toggle edit mode | |
| function toggleEditMode() { | |
| state.editMode = !state.editMode; | |
| renderChatList(); | |
| } | |
| // Create new chat | |
| function createNewChat() { | |
| if (!state.currentUser) { | |
| showAuthModal('signin'); | |
| return; | |
| } | |
| const newChat = { | |
| id: Date.now().toString(), | |
| title: `Chat ${state.chats.length + 1}`, | |
| createdAt: new Date().toISOString(), | |
| updatedAt: new Date().toISOString(), | |
| messages: [], | |
| context: {} | |
| }; | |
| state.chats.unshift(newChat); | |
| state.currentChatId = newChat.id; | |
| saveUserChats(); | |
| renderUI(); | |
| } | |
| // Handle message submission | |
| async function handleMessageSubmit(e) { | |
| e.preventDefault(); | |
| if (!state.currentUser || !state.currentChatId || state.isGenerating) return; | |
| const messageText = elements.messageInput.value.trim(); | |
| if (!messageText) return; | |
| // Add user message to chat | |
| const userMessage = { | |
| id: Date.now().toString(), | |
| role: 'user', | |
| content: messageText, | |
| timestamp: new Date().toISOString() | |
| }; | |
| addMessageToCurrentChat(userMessage); | |
| elements.messageInput.value = ''; | |
| // Show loading indicator | |
| state.isGenerating = true; | |
| elements.sendBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>'; | |
| try { | |
| // Get current chat | |
| const chatIndex = state.chats.findIndex(c => c.id === state.currentChatId); | |
| if (chatIndex === -1) return; | |
| const chat = state.chats[chatIndex]; | |
| // Prepare messages for API | |
| let messagesForApi = [ | |
| { | |
| role: 'system', | |
| content: buildSystemPrompt(chat.context) | |
| }, | |
| ...chat.messages.map(msg => ({ | |
| role: msg.role, | |
| content: msg.content | |
| })) | |
| ]; | |
| // Call Qwen API | |
| const response = await fetch(API_URL, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': `Bearer ${API_KEY}` | |
| }, | |
| body: JSON.stringify({ | |
| model_name: 'qwen1.5-14b-chat', | |
| messages: messagesForApi, | |
| max_tokens: 2048, | |
| temperature: 0.7 | |
| }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error('API request failed'); | |
| } | |
| const data = await response.json(); | |
| const aiMessage = data.choices[0].message; | |
| // Add AI response to chat | |
| const formattedAiMessage = { | |
| id: Date.now().toString(), | |
| role: 'assistant', | |
| content: aiMessage.content, | |
| timestamp: new Date().toISOString() | |
| }; | |
| addMessageToCurrentChat(formattedAiMessage); | |
| } catch (error) { | |
| console.error('Error:', error); | |
| // Show error message to user | |
| const errorMessage = { | |
| id: Date.now().toString(), | |
| role: 'assistant', | |
| content: 'Sorry, I encountered an error. Please try again.', | |
| timestamp: new Date().toISOString() | |
| }; | |
| addMessageToCurrentChat(errorMessage); | |
| } finally { | |
| state.isGenerating = false; | |
| elements.sendBtn.innerHTML = '<i class="fas fa-paper-plane"></i>'; | |
| } | |
| } | |
| // Build system prompt from context | |
| function buildSystemPrompt(context = {}) { | |
| let prompt = "You are Qwen, a helpful AI assistant. "; | |
| if (context.name) prompt += `The user's name is ${context.name}. `; | |
| if (context.location) prompt += `They are from ${context.location}. `; | |
| if (context.pets) prompt += `They have pets: ${context.pets}. `; | |
| if (context.interests) prompt += `Their interests include: ${context.interests}. `; | |
| if (context.additionalInfo) prompt += `Additional info about them: ${context.additionalInfo}. `; | |
| if (context.instructions) { | |
| prompt += `Follow these instructions when responding: ${context.instructions}`; | |
| } else { | |
| prompt += "Be helpful, friendly, and concise in your responses."; | |
| } | |
| return prompt; | |
| } | |
| // Add message to current chat | |
| function addMessageToCurrentChat(message) { | |
| if (!state.currentChatId) return; | |
| const chatIndex = state.chats.findIndex(c => c.id === state.currentChatId); | |
| if (chatIndex === -1) return; | |
| state.chats[chatIndex].messages.push(message); | |
| state.chats[chatIndex].updatedAt = new Date().toISOString(); | |
| saveUserChats(); | |
| renderMessages(); | |
| } | |
| // Toggle mobile sidebar | |
| function toggleMobileSidebar() { | |
| elements.mobileSidebar.classList.toggle('translate-x-full'); | |
| } | |
| // Render UI based on current state | |
| function renderUI() { | |
| if (state.currentUser) { | |
| // User is signed in | |
| elements.authButtons.classList.add('hidden'); | |
| elements.userInfo.classList.remove('hidden'); | |
| elements.usernameDisplay.textContent = state.currentUser.name; | |
| // Enable chat features | |
| elements.messageInput.disabled = false; | |
| elements.sendBtn.disabled = false; | |
| // Render chat list and messages | |
| renderChatList(); | |
| renderMessages(); | |
| } else { | |
| // User is not signed in | |
| elements.authButtons.classList.remove('hidden'); | |
| elements.userInfo.classList.add('hidden'); | |
| // Disable chat features | |
| elements.messageInput.disabled = true; | |
| elements.sendBtn.disabled = true; | |
| // Clear chat list and show sign in prompt | |
| elements.chatList.innerHTML = ''; | |
| elements.mobileChatList.innerHTML = ''; | |
| elements.messages.innerHTML = ` | |
| <div class="text-center py-8 text-gray-500"> | |
| <i class="fas fa-comments text-4xl mb-2"></i> | |
| <p>Please sign in to start chatting</p> | |
| <button class="mt-4 px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600" onclick="showAuthModal('signin')"> | |
| Sign In | |
| </button> | |
| </div> | |
| `; | |
| } | |
| } | |
| // Render chat list | |
| function renderChatList() { | |
| if (!state.currentUser) return; | |
| // Desktop chat list | |
| elements.chatList.innerHTML = state.chats.map(chat => ` | |
| <div class="group flex items-center justify-between p-2 rounded-md hover:bg-gray-100 ${chat.id === state.currentChatId ? 'bg-blue-50' : ''}"> | |
| <button | |
| class="flex-1 text-left truncate ${chat.id === state.currentChatId ? 'font-medium text-blue-600' : 'text-gray-700'}" | |
| onclick="switchToChat('${chat.id}')" | |
| > | |
| <i class="fas fa-comment-dots mr-2 text-gray-400"></i> | |
| ${chat.title} | |
| </button> | |
| ${state.editMode ? ` | |
| <button class="p-1 text-gray-400 hover:text-gray-700" onclick="deleteChat('${chat.id}', event)"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| ` : ''} | |
| </div> | |
| `).join(''); | |
| // Mobile chat list | |
| elements.mobileChatList.innerHTML = state.chats.map(chat => ` | |
| <div class="group flex items-center justify-between p-2 rounded-md hover:bg-gray-100 ${chat.id === state.currentChatId ? 'bg-blue-50' : ''}"> | |
| <button | |
| class="flex-1 text-left truncate ${chat.id === state.currentChatId ? 'font-medium text-blue-600' : 'text-gray-700'}" | |
| onclick="switchToChat('${chat.id}')" | |
| > | |
| <i class="fas fa-comment-dots mr-2 text-gray-400"></i> | |
| ${chat.title} | |
| </button> | |
| ${state.editMode ? ` | |
| <button class="p-1 text-gray-400 hover:text-gray-700" onclick="deleteChat('${chat.id}', event)"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| ` : ''} | |
| </div> | |
| `).join(''); | |
| } | |
| // Render messages for current chat | |
| function renderMessages() { | |
| if (!state.currentChatId) { | |
| elements.messages.innerHTML = ` | |
| <div class="text-center py-8 text-gray-500"> | |
| <i class="fas fa-comments text-4xl mb-2"></i> | |
| <p>Start a new conversation with Qwen AI</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| const chat = state.chats.find(c => c.id === state.currentChatId); | |
| if (!chat) return; | |
| elements.currentChatTitle.textContent = chat.title; | |
| if (chat.messages.length === 0) { | |
| elements.messages.innerHTML = ` | |
| <div class="text-center py-8 text-gray-500"> | |
| <i class="fas fa-comments text-4xl mb-2"></i> | |
| <p>Start a conversation with Qwen AI</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| elements.messages.innerHTML = chat.messages.map(message => ` | |
| <div class="chat-message ${message.role} p-4 max-w-3/4 ${message.role === 'user' ? 'ml-auto' : 'mr-auto'} fade-in"> | |
| <div class="flex items-start space-x-2"> | |
| <div class="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${message.role === 'user' ? 'bg-blue-100 text-blue-600' : 'bg-gray-200 text-gray-600'}"> | |
| ${message.role === 'user' ? '<i class="fas fa-user"></i>' : '<i class="fas fa-robot"></i>'} | |
| </div> | |
| <div class="flex-1"> | |
| <div class="text-sm font-medium mb-1 ${message.role === 'user' ? 'text-blue-100' : 'text-gray-500'}"> | |
| ${message.role === 'user' ? 'You' : 'Qwen AI'} | |
| </div> | |
| <div class="text-sm ${message.role === 'user' ? 'text-white' : 'text-gray-800'}"> | |
| ${message.content.replace(/\n/g, '<br>')} | |
| </div> | |
| <div class="text-xs mt-1 ${message.role === 'user' ? 'text-blue-100' : 'text-gray-500'}"> | |
| ${new Date(message.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| // Scroll to bottom | |
| elements.messages.scrollTop = elements.messages.scrollHeight; | |
| } | |
| // Switch to a different chat | |
| function switchToChat(chatId) { | |
| state.currentChatId = chatId; | |
| renderUI(); | |
| toggleMobileSidebar(); | |
| } | |
| // Delete a chat | |
| function deleteChat(chatId, event) { | |
| event.stopPropagation(); | |
| if (!confirm('Are you sure you want to delete this chat?')) return; | |
| const chatIndex = state.chats.findIndex(c => c.id === chatId); | |
| if (chatIndex === -1) return; | |
| state.chats.splice(chatIndex, 1); | |
| if (state.currentChatId === chatId) { | |
| state.currentChatId = state.chats.length > 0 ? state.chats[0].id : null; | |
| } | |
| saveUserChats(); | |
| renderUI(); | |
| } | |
| // Initialize the app | |
| init(); | |
| // Make functions available globally for HTML onclick handlers | |
| window.showAuthModal = showAuthModal; | |
| window.switchToChat = switchToChat; | |
| window.deleteChat = deleteChat; | |
| </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://huggingface.co/proxy/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://huggingface.co/proxy/enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://huggingface.co/proxy/enzostvs-deepsite.hf.space?remix=BarBar288/openchatai-qwen" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |