Spaces:
Running
Running
now i want to add proper authentication where user can login only with their google account and Users should be able to sign up, log in, and log out securely Once logged in, users should automatically be taken to the chat interface. Each user should have their own chat history — so when they log in again later, they can see their previous conversations in the sidebar
Browse files- .env +16 -0
- auth.js +93 -0
- index.html +128 -168
.env
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
```env
|
| 2 |
+
# Google OAuth
|
| 3 |
+
GOOGLE_CLIENT_ID=your_google_client_id.apps.googleusercontent.com
|
| 4 |
+
GOOGLE_CLIENT_SECRET=your_google_client_secret
|
| 5 |
+
GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback
|
| 6 |
+
|
| 7 |
+
# Session
|
| 8 |
+
SESSION_SECRET=your_session_secret_here
|
| 9 |
+
|
| 10 |
+
# OpenRouter
|
| 11 |
+
OPENROUTER_API_KEY=your_openrouter_api_key
|
| 12 |
+
|
| 13 |
+
# Server
|
| 14 |
+
PORT=3000
|
| 15 |
+
NODE_ENV=development
|
| 16 |
+
```
|
auth.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
```javascript
|
| 2 |
+
const { OAuth2Client } = require('google-auth-library');
|
| 3 |
+
const express = require('express');
|
| 4 |
+
const session = require('express-session');
|
| 5 |
+
const dotenv = require('dotenv');
|
| 6 |
+
const path = require('path');
|
| 7 |
+
|
| 8 |
+
dotenv.config();
|
| 9 |
+
|
| 10 |
+
const app = express();
|
| 11 |
+
const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
|
| 12 |
+
|
| 13 |
+
// Middleware
|
| 14 |
+
app.use(express.json());
|
| 15 |
+
app.use(express.static(path.join(__dirname, 'public')));
|
| 16 |
+
app.use(session({
|
| 17 |
+
secret: process.env.SESSION_SECRET,
|
| 18 |
+
resave: false,
|
| 19 |
+
saveUninitialized: false,
|
| 20 |
+
cookie: { secure: process.env.NODE_ENV === 'production', maxAge: 24 * 60 * 60 * 1000 }
|
| 21 |
+
}));
|
| 22 |
+
|
| 23 |
+
// Google OAuth routes
|
| 24 |
+
app.get('/auth/google', (req, res) => {
|
| 25 |
+
const url = client.generateAuthUrl({
|
| 26 |
+
access_type: 'online',
|
| 27 |
+
scope: ['profile', 'email'],
|
| 28 |
+
redirect_uri: process.env.GOOGLE_REDIRECT_URI
|
| 29 |
+
});
|
| 30 |
+
res.redirect(url);
|
| 31 |
+
});
|
| 32 |
+
|
| 33 |
+
app.get('/auth/google/callback', async (req, res) => {
|
| 34 |
+
const { code } = req.query;
|
| 35 |
+
|
| 36 |
+
try {
|
| 37 |
+
const { tokens } = await client.getToken({
|
| 38 |
+
code,
|
| 39 |
+
redirect_uri: process.env.GOOGLE_REDIRECT_URI
|
| 40 |
+
});
|
| 41 |
+
|
| 42 |
+
const ticket = await client.verifyIdToken({
|
| 43 |
+
idToken: tokens.id_token,
|
| 44 |
+
audience: process.env.GOOGLE_CLIENT_ID
|
| 45 |
+
});
|
| 46 |
+
|
| 47 |
+
const payload = ticket.getPayload();
|
| 48 |
+
req.session.user = {
|
| 49 |
+
id: payload.sub,
|
| 50 |
+
email: payload.email,
|
| 51 |
+
name: payload.name,
|
| 52 |
+
picture: payload.picture
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
res.redirect('/');
|
| 56 |
+
} catch (error) {
|
| 57 |
+
console.error('Auth error:', error);
|
| 58 |
+
res.redirect('/?auth_error=1');
|
| 59 |
+
}
|
| 60 |
+
});
|
| 61 |
+
|
| 62 |
+
app.get('/auth/logout', (req, res) => {
|
| 63 |
+
req.session.destroy();
|
| 64 |
+
res.redirect('/');
|
| 65 |
+
});
|
| 66 |
+
|
| 67 |
+
app.get('/auth/status', (req, res) => {
|
| 68 |
+
res.json({ isAuthenticated: !!req.session.user, user: req.session.user });
|
| 69 |
+
});
|
| 70 |
+
|
| 71 |
+
// Chat history routes
|
| 72 |
+
app.get('/api/chats', async (req, res) => {
|
| 73 |
+
if (!req.session.user) return res.sendStatus(401);
|
| 74 |
+
|
| 75 |
+
// In a real app, you'd fetch from a database
|
| 76 |
+
const chats = JSON.parse(localStorage.getItem(`chats_${req.session.user.id}`) || '[]');
|
| 77 |
+
res.json(chats);
|
| 78 |
+
});
|
| 79 |
+
|
| 80 |
+
app.post('/api/chats', async (req, res) => {
|
| 81 |
+
if (!req.session.user) return res.sendStatus(401);
|
| 82 |
+
|
| 83 |
+
// In a real app, you'd save to a database
|
| 84 |
+
const chats = JSON.parse(localStorage.getItem(`chats_${req.session.user.id}`) || '[]');
|
| 85 |
+
chats.push(req.body);
|
| 86 |
+
localStorage.setItem(`chats_${req.session.user.id}`, JSON.stringify(chats));
|
| 87 |
+
|
| 88 |
+
res.sendStatus(201);
|
| 89 |
+
});
|
| 90 |
+
|
| 91 |
+
const PORT = process.env.PORT || 3000;
|
| 92 |
+
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
|
| 93 |
+
```
|
index.html
CHANGED
|
@@ -205,7 +205,7 @@
|
|
| 205 |
<p class="mt-1">Your conversations stay private and secure</p>
|
| 206 |
</footer>
|
| 207 |
</div>
|
| 208 |
-
|
| 209 |
<script>
|
| 210 |
feather.replace();
|
| 211 |
// DOM Elements - New Sidebar Elements
|
|
@@ -215,17 +215,8 @@
|
|
| 215 |
const chatList = document.getElementById('chatList');
|
| 216 |
|
| 217 |
// DOM Elements - Original
|
| 218 |
-
const authBtn = document.getElementById('authBtn');
|
| 219 |
-
|
| 220 |
-
const authModalTitle = document.getElementById('authModalTitle');
|
| 221 |
-
const closeAuth = document.getElementById('closeAuth');
|
| 222 |
-
const regUsername = document.getElementById('regUsername');
|
| 223 |
-
const authPassword = document.getElementById('authPassword');
|
| 224 |
-
const authActionBtn = document.getElementById('authActionBtn');
|
| 225 |
-
const authToggleBtn = document.getElementById('authToggleBtn');
|
| 226 |
-
const authToggleText = document.getElementById('authToggleText');
|
| 227 |
-
const registerFields = document.getElementById('registerFields');
|
| 228 |
-
const newChatBtn = document.getElementById('newChatBtn');
|
| 229 |
const settingsBtn = document.getElementById('settingsBtn');
|
| 230 |
const settingsModal = document.getElementById('settingsModal');
|
| 231 |
const closeSettings = document.getElementById('closeSettings');
|
|
@@ -238,36 +229,49 @@ const settingsBtn = document.getElementById('settingsBtn');
|
|
| 238 |
const typingIndicator = document.getElementById('typingIndicator');
|
| 239 |
const modelIndicator = document.getElementById('modelIndicator');
|
| 240 |
const themeBtns = document.querySelectorAll('.theme-btn');
|
| 241 |
-
//
|
| 242 |
-
let
|
| 243 |
|
| 244 |
-
function
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
}
|
| 260 |
-
feather.replace();
|
| 261 |
}
|
| 262 |
|
| 263 |
-
function
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
}
|
| 272 |
// Chat management
|
| 273 |
let currentChatId = null;
|
|
@@ -276,30 +280,23 @@ const settingsBtn = document.getElementById('settingsBtn');
|
|
| 276 |
return 'chat_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
|
| 277 |
}
|
| 278 |
|
| 279 |
-
function
|
| 280 |
-
|
| 281 |
-
localStorage.setItem('chatRouterUser', username);
|
| 282 |
-
localStorage.setItem(`chatRouterPass_${username}`, password); // Not secure for production!
|
| 283 |
-
updateAuthUI();
|
| 284 |
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
// Clear current chat and sidebar
|
| 297 |
-
chatContainer.innerHTML = '';
|
| 298 |
-
chatList.innerHTML = '';
|
| 299 |
-
currentChatId = null;
|
| 300 |
-
addMessage('system', 'Please log in to start chatting.');
|
| 301 |
}
|
| 302 |
-
|
|
|
|
| 303 |
const username = localStorage.getItem('chatRouterUser');
|
| 304 |
if (!username) return;
|
| 305 |
|
|
@@ -309,54 +306,44 @@ const settingsBtn = document.getElementById('settingsBtn');
|
|
| 309 |
saveChatHistory(username, currentChatId);
|
| 310 |
addChatToList(username, currentChatId, 'New Chat');
|
| 311 |
}
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
|
|
|
|
|
|
|
|
|
| 317 |
chatContainer.scrollTop = chatContainer.scrollHeight;
|
| 318 |
currentChatId = chatId;
|
|
|
|
|
|
|
| 319 |
}
|
| 320 |
}
|
| 321 |
|
| 322 |
-
function saveChatHistory(
|
| 323 |
if (!chatId) chatId = currentChatId;
|
| 324 |
-
if (!
|
| 325 |
-
|
| 326 |
-
localStorage.setItem(`chatHistory_${username}_${chatId}`, chatContainer.innerHTML);
|
| 327 |
-
|
| 328 |
-
// Update the chat title if it's the first message
|
| 329 |
-
const messages = chatContainer.querySelectorAll('.message-bubble');
|
| 330 |
-
if (messages.length === 1) {
|
| 331 |
-
updateChatTitle(username, chatId, messages[0].textContent.substring(0, 30));
|
| 332 |
-
}
|
| 333 |
-
}
|
| 334 |
-
|
| 335 |
-
function loadChatList(username) {
|
| 336 |
-
chatList.innerHTML = '';
|
| 337 |
-
const chats = [];
|
| 338 |
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
}
|
| 348 |
-
|
| 349 |
-
// Sort by most recent first
|
| 350 |
-
chats.sort((a, b) => b.id.localeCompare(a.id));
|
| 351 |
-
|
| 352 |
-
// Add to sidebar
|
| 353 |
-
chats.forEach(chat => {
|
| 354 |
-
addChatToList(username, chat.id, chat.title);
|
| 355 |
-
});
|
| 356 |
}
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
const chatItem = document.createElement('div');
|
| 360 |
chatItem.className = 'flex justify-between items-center p-2 hover:bg-gray-700 rounded-lg cursor-pointer';
|
| 361 |
chatItem.dataset.chatId = chatId;
|
| 362 |
|
|
@@ -395,37 +382,58 @@ const settingsBtn = document.getElementById('settingsBtn');
|
|
| 395 |
chatList.prepend(chatItem);
|
| 396 |
feather.replace();
|
| 397 |
}
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
}
|
| 405 |
}
|
| 406 |
|
| 407 |
-
function renameChat(
|
| 408 |
-
|
| 409 |
if (newTitle && newTitle.trim()) {
|
| 410 |
localStorage.setItem(`chatTitle_${username}_${chatId}`, newTitle.trim());
|
| 411 |
titleElement.textContent = newTitle.trim();
|
| 412 |
}
|
| 413 |
}
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
element.remove();
|
| 420 |
|
| 421 |
if (currentChatId === chatId) {
|
| 422 |
createNewChat();
|
| 423 |
}
|
|
|
|
|
|
|
| 424 |
}
|
| 425 |
}
|
| 426 |
-
//
|
| 427 |
-
|
| 428 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
const savedModel = localStorage.getItem('chatRouterModel');
|
| 430 |
const savedTheme = localStorage.getItem('chatRouterTheme') || 'indigo';
|
| 431 |
|
|
@@ -655,54 +663,7 @@ async function sendMessage() {
|
|
| 655 |
typingIndicator.classList.add('hidden');
|
| 656 |
}
|
| 657 |
}
|
| 658 |
-
|
| 659 |
-
authBtn.addEventListener('click', () => {
|
| 660 |
-
if (authBtn.dataset.state === 'logout') {
|
| 661 |
-
logoutUser();
|
| 662 |
-
} else {
|
| 663 |
-
showAuthModal();
|
| 664 |
-
}
|
| 665 |
-
});
|
| 666 |
-
|
| 667 |
-
closeAuth.addEventListener('click', () => {
|
| 668 |
-
authModal.classList.add('hidden');
|
| 669 |
-
});
|
| 670 |
-
|
| 671 |
-
authToggleBtn.addEventListener('click', () => {
|
| 672 |
-
showAuthModal(!isRegistering);
|
| 673 |
-
});
|
| 674 |
-
|
| 675 |
-
authActionBtn.addEventListener('click', () => {
|
| 676 |
-
if (isRegistering) {
|
| 677 |
-
const username = regUsername.value.trim();
|
| 678 |
-
const password = authPassword.value.trim();
|
| 679 |
-
|
| 680 |
-
if (!username || !password) {
|
| 681 |
-
alert('Please enter both username and password');
|
| 682 |
-
return;
|
| 683 |
-
}
|
| 684 |
-
|
| 685 |
-
if (localStorage.getItem(`chatRouterPass_${username}`)) {
|
| 686 |
-
alert('Username already exists');
|
| 687 |
-
return;
|
| 688 |
-
}
|
| 689 |
-
|
| 690 |
-
loginUser(username, password);
|
| 691 |
-
} else {
|
| 692 |
-
const username = regUsername.value.trim();
|
| 693 |
-
const password = authPassword.value.trim();
|
| 694 |
-
const storedPass = localStorage.getItem(`chatRouterPass_${username}`);
|
| 695 |
-
|
| 696 |
-
if (!storedPass || storedPass !== password) {
|
| 697 |
-
alert('Invalid username or password');
|
| 698 |
-
return;
|
| 699 |
-
}
|
| 700 |
-
|
| 701 |
-
loginUser(username, password);
|
| 702 |
-
}
|
| 703 |
-
authModal.classList.add('hidden');
|
| 704 |
-
});
|
| 705 |
-
newChatBtn.addEventListener('click', createNewChat);
|
| 706 |
newSidebarChat.addEventListener('click', createNewChat);
|
| 707 |
|
| 708 |
// Sidebar toggle for mobile
|
|
@@ -724,20 +685,19 @@ messageInput.addEventListener('keydown', (e) => {
|
|
| 724 |
});
|
| 725 |
// Initialize
|
| 726 |
loadSettings();
|
| 727 |
-
|
| 728 |
|
| 729 |
// Welcome message
|
| 730 |
setTimeout(() => {
|
| 731 |
-
if (!
|
| 732 |
-
addMessage('system', 'Welcome to ChatRouter! Please
|
| 733 |
} else if (!localStorage.getItem('chatRouterModel')) {
|
| 734 |
addMessage('system', 'Please select a free model from settings to begin.');
|
| 735 |
} else if (!currentChatId) {
|
| 736 |
createNewChat();
|
| 737 |
}
|
| 738 |
}, 1000);
|
| 739 |
-
|
| 740 |
-
// Close sidebar when clicking outside on mobile
|
| 741 |
document.addEventListener('click', (e) => {
|
| 742 |
if (window.innerWidth < 768 && !sidebar.contains(e.target) && e.target !== sidebarToggle) {
|
| 743 |
sidebar.classList.add('hidden');
|
|
|
|
| 205 |
<p class="mt-1">Your conversations stay private and secure</p>
|
| 206 |
</footer>
|
| 207 |
</div>
|
| 208 |
+
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
| 209 |
<script>
|
| 210 |
feather.replace();
|
| 211 |
// DOM Elements - New Sidebar Elements
|
|
|
|
| 215 |
const chatList = document.getElementById('chatList');
|
| 216 |
|
| 217 |
// DOM Elements - Original
|
| 218 |
+
const authBtn = document.getElementById('authBtn');
|
| 219 |
+
const newChatBtn = document.getElementById('newChatBtn');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
const settingsBtn = document.getElementById('settingsBtn');
|
| 221 |
const settingsModal = document.getElementById('settingsModal');
|
| 222 |
const closeSettings = document.getElementById('closeSettings');
|
|
|
|
| 229 |
const typingIndicator = document.getElementById('typingIndicator');
|
| 230 |
const modelIndicator = document.getElementById('modelIndicator');
|
| 231 |
const themeBtns = document.querySelectorAll('.theme-btn');
|
| 232 |
+
// Auth state management
|
| 233 |
+
let currentUser = null;
|
| 234 |
|
| 235 |
+
async function checkAuth() {
|
| 236 |
+
try {
|
| 237 |
+
const response = await fetch('/auth/status');
|
| 238 |
+
const data = await response.json();
|
| 239 |
+
|
| 240 |
+
if (data.isAuthenticated) {
|
| 241 |
+
currentUser = data.user;
|
| 242 |
+
authBtn.innerHTML = `<img src="${data.user.picture}" class="w-6 h-6 rounded-full mr-2"><span>Logout</span>`;
|
| 243 |
+
document.querySelector('#newChatBtn').disabled = false;
|
| 244 |
+
document.querySelector('#settingsBtn').disabled = false;
|
| 245 |
+
sendBtn.disabled = false;
|
| 246 |
+
loadChatList();
|
| 247 |
+
} else {
|
| 248 |
+
currentUser = null;
|
| 249 |
+
authBtn.innerHTML = `<i data-feather="user" class="w-5 h-5"></i><span>Login with Google</span>`;
|
| 250 |
+
document.querySelector('#newChatBtn').disabled = true;
|
| 251 |
+
document.querySelector('#settingsBtn').disabled = true;
|
| 252 |
+
sendBtn.disabled = true;
|
| 253 |
+
}
|
| 254 |
+
feather.replace();
|
| 255 |
+
} catch (error) {
|
| 256 |
+
console.error('Auth check failed:', error);
|
| 257 |
}
|
|
|
|
| 258 |
}
|
| 259 |
|
| 260 |
+
function handleGoogleSignIn() {
|
| 261 |
+
window.location.href = '/auth/google';
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
async function handleLogout() {
|
| 265 |
+
try {
|
| 266 |
+
await fetch('/auth/logout');
|
| 267 |
+
currentUser = null;
|
| 268 |
+
chatList.innerHTML = '';
|
| 269 |
+
chatContainer.innerHTML = '';
|
| 270 |
+
checkAuth();
|
| 271 |
+
addMessage('system', 'Please sign in to start chatting.');
|
| 272 |
+
} catch (error) {
|
| 273 |
+
console.error('Logout failed:', error);
|
| 274 |
+
}
|
| 275 |
}
|
| 276 |
// Chat management
|
| 277 |
let currentChatId = null;
|
|
|
|
| 280 |
return 'chat_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
|
| 281 |
}
|
| 282 |
|
| 283 |
+
async function loadChatList() {
|
| 284 |
+
if (!currentUser) return;
|
|
|
|
|
|
|
|
|
|
| 285 |
|
| 286 |
+
try {
|
| 287 |
+
const response = await fetch('/api/chats');
|
| 288 |
+
const chats = await response.json();
|
| 289 |
+
|
| 290 |
+
chatList.innerHTML = '';
|
| 291 |
+
chats.forEach(chat => {
|
| 292 |
+
addChatToList(chat.id, chat.title);
|
| 293 |
+
});
|
| 294 |
+
} catch (error) {
|
| 295 |
+
console.error('Failed to load chats:', error);
|
| 296 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
}
|
| 298 |
+
|
| 299 |
+
function createNewChat() {
|
| 300 |
const username = localStorage.getItem('chatRouterUser');
|
| 301 |
if (!username) return;
|
| 302 |
|
|
|
|
| 306 |
saveChatHistory(username, currentChatId);
|
| 307 |
addChatToList(username, currentChatId, 'New Chat');
|
| 308 |
}
|
| 309 |
+
async function loadChatHistory(chatId) {
|
| 310 |
+
if (!currentUser) return;
|
| 311 |
+
|
| 312 |
+
try {
|
| 313 |
+
const response = await fetch(`/api/chats/${chatId}`);
|
| 314 |
+
const chat = await response.json();
|
| 315 |
+
|
| 316 |
+
chatContainer.innerHTML = chat.history;
|
| 317 |
chatContainer.scrollTop = chatContainer.scrollHeight;
|
| 318 |
currentChatId = chatId;
|
| 319 |
+
} catch (error) {
|
| 320 |
+
console.error('Failed to load chat history:', error);
|
| 321 |
}
|
| 322 |
}
|
| 323 |
|
| 324 |
+
async function saveChatHistory(chatId) {
|
| 325 |
if (!chatId) chatId = currentChatId;
|
| 326 |
+
if (!currentUser || !chatId) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
|
| 328 |
+
try {
|
| 329 |
+
await fetch('/api/chats', {
|
| 330 |
+
method: 'POST',
|
| 331 |
+
headers: {
|
| 332 |
+
'Content-Type': 'application/json'
|
| 333 |
+
},
|
| 334 |
+
body: JSON.stringify({
|
| 335 |
+
id: chatId,
|
| 336 |
+
title: chatContainer.querySelector('.message-bubble')?.textContent.substring(0, 30) || 'New Chat',
|
| 337 |
+
history: chatContainer.innerHTML,
|
| 338 |
+
updatedAt: new Date().toISOString()
|
| 339 |
+
})
|
| 340 |
+
});
|
| 341 |
+
} catch (error) {
|
| 342 |
+
console.error('Failed to save chat:', error);
|
| 343 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
}
|
| 345 |
+
function addChatToList(chatId, title) {
|
| 346 |
+
const chatItem = document.createElement('div');
|
|
|
|
| 347 |
chatItem.className = 'flex justify-between items-center p-2 hover:bg-gray-700 rounded-lg cursor-pointer';
|
| 348 |
chatItem.dataset.chatId = chatId;
|
| 349 |
|
|
|
|
| 382 |
chatList.prepend(chatItem);
|
| 383 |
feather.replace();
|
| 384 |
}
|
| 385 |
+
async function updateChatTitle(chatId, title) {
|
| 386 |
+
try {
|
| 387 |
+
await fetch(`/api/chats/${chatId}`, {
|
| 388 |
+
method: 'PUT',
|
| 389 |
+
headers: {
|
| 390 |
+
'Content-Type': 'application/json'
|
| 391 |
+
},
|
| 392 |
+
body: JSON.stringify({ title })
|
| 393 |
+
});
|
| 394 |
+
|
| 395 |
+
const chatItem = chatList.querySelector(`[data-chat-id="${chatId}"]`);
|
| 396 |
+
if (chatItem) {
|
| 397 |
+
chatItem.querySelector('span').textContent = title;
|
| 398 |
+
}
|
| 399 |
+
} catch (error) {
|
| 400 |
+
console.error('Failed to update chat title:', error);
|
| 401 |
}
|
| 402 |
}
|
| 403 |
|
| 404 |
+
async function renameChat(chatId, titleElement) {
|
| 405 |
+
const newTitle = prompt('Enter new chat title:', titleElement.textContent);
|
| 406 |
if (newTitle && newTitle.trim()) {
|
| 407 |
localStorage.setItem(`chatTitle_${username}_${chatId}`, newTitle.trim());
|
| 408 |
titleElement.textContent = newTitle.trim();
|
| 409 |
}
|
| 410 |
}
|
| 411 |
+
async function deleteChat(chatId, element) {
|
| 412 |
+
if (!confirm('Are you sure you want to delete this chat?')) return;
|
| 413 |
+
|
| 414 |
+
try {
|
| 415 |
+
await fetch(`/api/chats/${chatId}`, { method: 'DELETE' });
|
| 416 |
element.remove();
|
| 417 |
|
| 418 |
if (currentChatId === chatId) {
|
| 419 |
createNewChat();
|
| 420 |
}
|
| 421 |
+
} catch (error) {
|
| 422 |
+
console.error('Failed to delete chat:', error);
|
| 423 |
}
|
| 424 |
}
|
| 425 |
+
// Event listeners
|
| 426 |
+
authBtn.addEventListener('click', () => {
|
| 427 |
+
if (currentUser) {
|
| 428 |
+
handleLogout();
|
| 429 |
+
} else {
|
| 430 |
+
handleGoogleSignIn();
|
| 431 |
+
}
|
| 432 |
+
});
|
| 433 |
+
|
| 434 |
+
// Load saved settings
|
| 435 |
+
async function loadSettings() {
|
| 436 |
+
const savedApiKey = localStorage.getItem('chatRouterApiKey');
|
| 437 |
const savedModel = localStorage.getItem('chatRouterModel');
|
| 438 |
const savedTheme = localStorage.getItem('chatRouterTheme') || 'indigo';
|
| 439 |
|
|
|
|
| 663 |
typingIndicator.classList.add('hidden');
|
| 664 |
}
|
| 665 |
}
|
| 666 |
+
newChatBtn.addEventListener('click', createNewChat);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 667 |
newSidebarChat.addEventListener('click', createNewChat);
|
| 668 |
|
| 669 |
// Sidebar toggle for mobile
|
|
|
|
| 685 |
});
|
| 686 |
// Initialize
|
| 687 |
loadSettings();
|
| 688 |
+
checkAuth();
|
| 689 |
|
| 690 |
// Welcome message
|
| 691 |
setTimeout(() => {
|
| 692 |
+
if (!currentUser) {
|
| 693 |
+
addMessage('system', 'Welcome to ChatRouter! Please sign in with Google to start chatting.');
|
| 694 |
} else if (!localStorage.getItem('chatRouterModel')) {
|
| 695 |
addMessage('system', 'Please select a free model from settings to begin.');
|
| 696 |
} else if (!currentChatId) {
|
| 697 |
createNewChat();
|
| 698 |
}
|
| 699 |
}, 1000);
|
| 700 |
+
// Close sidebar when clicking outside on mobile
|
|
|
|
| 701 |
document.addEventListener('click', (e) => {
|
| 702 |
if (window.innerWidth < 768 && !sidebar.contains(e.target) && e.target !== sidebarToggle) {
|
| 703 |
sidebar.classList.add('hidden');
|