openchatai-qwen / index.html
BarBar288's picture
Add 3 files
e01ff2c verified
<!DOCTYPE html>
<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>