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://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=BarBar288/openchatai-qwen" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |