Spaces:
Running
Running
remove all the logic of authentication and login now i just want any user should have to enter their openrouter api key to use this chatbot and after refreshing or reloading entered key should be remove and they should have to again enter the key
Browse files- index.html +39 -324
index.html
CHANGED
|
@@ -63,7 +63,6 @@
|
|
| 63 |
<button id="sidebarToggle" class="fixed left-4 top-4 z-40 md:hidden bg-gray-800 p-2 rounded-lg">
|
| 64 |
<i data-feather="menu"></i>
|
| 65 |
</button>
|
| 66 |
-
|
| 67 |
<header class="flex justify-between items-center mb-8 ml-12 md:ml-0">
|
| 68 |
<div class="flex items-center space-x-3">
|
| 69 |
<i data-feather="cpu" class="w-8 h-8 text-indigo-300"></i>
|
|
@@ -78,40 +77,9 @@
|
|
| 78 |
<i data-feather="settings" class="w-5 h-5"></i>
|
| 79 |
<span>Settings</span>
|
| 80 |
</button>
|
| 81 |
-
<button id="authBtn" class="flex items-center space-x-2 bg-indigo-800 hover:bg-indigo-700 px-4 py-2 rounded-lg transition-all">
|
| 82 |
-
<i data-feather="user" class="w-5 h-5"></i>
|
| 83 |
-
<span>Login</span>
|
| 84 |
-
</button>
|
| 85 |
</div>
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
<div id="authModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
| 89 |
-
<div class="bg-gray-800 rounded-xl p-6 w-full max-w-md">
|
| 90 |
-
<div class="flex justify-between items-center mb-4">
|
| 91 |
-
<h2 class="text-xl font-bold" id="authModalTitle">Login</h2>
|
| 92 |
-
<button id="closeAuth" class="text-gray-400 hover:text-white">
|
| 93 |
-
<i data-feather="x"></i>
|
| 94 |
-
</button>
|
| 95 |
-
</div>
|
| 96 |
-
<div id="authForm" class="space-y-4">
|
| 97 |
-
<div id="registerFields" class="hidden">
|
| 98 |
-
<label class="block text-sm font-medium mb-1">Username</label>
|
| 99 |
-
<input type="text" id="regUsername" placeholder="Choose a username" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
| 100 |
-
</div>
|
| 101 |
-
<div>
|
| 102 |
-
<label class="block text-sm font-medium mb-1">Password</label>
|
| 103 |
-
<input type="password" id="authPassword" placeholder="Enter your password" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
| 104 |
-
</div>
|
| 105 |
-
<button id="authActionBtn" class="w-full bg-indigo-600 hover:bg-indigo-500 text-white py-2 rounded-lg transition-colors">Login</button>
|
| 106 |
-
<div class="text-center text-sm">
|
| 107 |
-
<span id="authToggleText">Don't have an account? </span>
|
| 108 |
-
<button id="authToggleBtn" class="text-indigo-400 hover:text-indigo-300">Register</button>
|
| 109 |
-
</div>
|
| 110 |
-
</div>
|
| 111 |
-
</div>
|
| 112 |
-
</div>
|
| 113 |
-
|
| 114 |
-
<!-- Settings Modal -->
|
| 115 |
<div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
| 116 |
<div class="bg-gray-800 rounded-xl p-6 w-full max-w-md">
|
| 117 |
<div class="flex justify-between items-center mb-4">
|
|
@@ -215,16 +183,6 @@
|
|
| 215 |
const chatList = document.getElementById('chatList');
|
| 216 |
|
| 217 |
// DOM Elements - Original
|
| 218 |
-
const authBtn = document.getElementById('authBtn');
|
| 219 |
-
const authModal = document.getElementById('authModal');
|
| 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');
|
|
@@ -240,222 +198,25 @@ const settingsBtn = document.getElementById('settingsBtn');
|
|
| 240 |
const themeBtns = document.querySelectorAll('.theme-btn');
|
| 241 |
// User management
|
| 242 |
let isRegistering = false;
|
| 243 |
-
|
| 244 |
-
function updateAuthUI() {
|
| 245 |
-
const loggedInUser = localStorage.getItem('chatRouterUser');
|
| 246 |
-
if (loggedInUser) {
|
| 247 |
-
authBtn.innerHTML = `<i data-feather="log-out" class="w-5 h-5"></i><span>Logout</span>`;
|
| 248 |
-
authBtn.dataset.state = 'logout';
|
| 249 |
-
document.querySelector('#newChatBtn').disabled = false;
|
| 250 |
-
document.querySelector('#settingsBtn').disabled = false;
|
| 251 |
-
sendBtn.disabled = false;
|
| 252 |
-
} else {
|
| 253 |
-
authBtn.innerHTML = `<i data-feather="user" class="w-5 h-5"></i><span>Login</span>`;
|
| 254 |
-
authBtn.dataset.state = 'login';
|
| 255 |
-
document.querySelector('#newChatBtn').disabled = true;
|
| 256 |
-
document.querySelector('#settingsBtn').disabled = true;
|
| 257 |
-
sendBtn.disabled = true;
|
| 258 |
-
showAuthModal();
|
| 259 |
-
}
|
| 260 |
-
feather.replace();
|
| 261 |
-
}
|
| 262 |
-
|
| 263 |
-
function showAuthModal(register = false) {
|
| 264 |
-
isRegistering = register;
|
| 265 |
-
authModalTitle.textContent = register ? 'Register' : 'Login';
|
| 266 |
-
authActionBtn.textContent = register ? 'Register' : 'Login';
|
| 267 |
-
authToggleText.textContent = register ? 'Already have an account? ' : 'Don\'t have an account? ';
|
| 268 |
-
authToggleBtn.textContent = register ? 'Login' : 'Register';
|
| 269 |
-
registerFields.classList.toggle('hidden', !register);
|
| 270 |
-
authModal.classList.remove('hidden');
|
| 271 |
-
}
|
| 272 |
// Chat management
|
| 273 |
let currentChatId = null;
|
| 274 |
|
| 275 |
function generateChatId() {
|
| 276 |
return 'chat_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
|
| 277 |
}
|
| 278 |
-
|
| 279 |
-
const encoder = new TextEncoder();
|
| 280 |
-
const data = encoder.encode(password);
|
| 281 |
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
| 282 |
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
| 283 |
-
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
| 284 |
-
}
|
| 285 |
-
|
| 286 |
-
async function loginUser(username, password) {
|
| 287 |
-
const hashedPassword = await hashPassword(password);
|
| 288 |
-
const storedHash = localStorage.getItem(`chatRouterPass_${username}`);
|
| 289 |
-
|
| 290 |
-
if (storedHash !== hashedPassword) {
|
| 291 |
-
alert('Invalid username or password');
|
| 292 |
-
return false;
|
| 293 |
-
}
|
| 294 |
-
|
| 295 |
-
localStorage.setItem('chatRouterUser', username);
|
| 296 |
-
sessionStorage.setItem('chatRouterSession', username);
|
| 297 |
-
updateAuthUI();
|
| 298 |
-
|
| 299 |
-
createNewChat();
|
| 300 |
-
loadChatList(username);
|
| 301 |
-
return true;
|
| 302 |
-
}
|
| 303 |
-
function logoutUser() {
|
| 304 |
-
const username = localStorage.getItem('chatRouterUser');
|
| 305 |
-
localStorage.removeItem('chatRouterUser');
|
| 306 |
-
sessionStorage.removeItem('chatRouterSession');
|
| 307 |
-
updateAuthUI();
|
| 308 |
-
|
| 309 |
-
// Clear current chat and sidebar
|
| 310 |
-
chatContainer.innerHTML = '';
|
| 311 |
-
chatList.innerHTML = '';
|
| 312 |
-
currentChatId = null;
|
| 313 |
-
addMessage('system', 'Please log in to start chatting.');
|
| 314 |
-
|
| 315 |
-
// Clear all chat history for this session
|
| 316 |
-
for (let i = localStorage.length - 1; i >= 0; i--) {
|
| 317 |
-
const key = localStorage.key(i);
|
| 318 |
-
if (key.startsWith(`chatHistory_${username}_`) ||
|
| 319 |
-
key.startsWith(`chatTitle_${username}_`)) {
|
| 320 |
-
localStorage.removeItem(key);
|
| 321 |
-
}
|
| 322 |
-
}
|
| 323 |
-
}
|
| 324 |
-
function createNewChat() {
|
| 325 |
-
const username = localStorage.getItem('chatRouterUser');
|
| 326 |
-
if (!username) return;
|
| 327 |
-
|
| 328 |
currentChatId = generateChatId();
|
| 329 |
chatContainer.innerHTML = '';
|
| 330 |
addMessage('system', 'π New chat started.');
|
| 331 |
-
saveChatHistory(username, currentChatId);
|
| 332 |
-
addChatToList(username, currentChatId, 'New Chat');
|
| 333 |
}
|
| 334 |
-
|
| 335 |
-
function
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
chatContainer.innerHTML = history;
|
| 339 |
-
chatContainer.scrollTop = chatContainer.scrollHeight;
|
| 340 |
-
currentChatId = chatId;
|
| 341 |
-
}
|
| 342 |
-
}
|
| 343 |
-
|
| 344 |
-
function saveChatHistory(username, chatId) {
|
| 345 |
-
if (!chatId) chatId = currentChatId;
|
| 346 |
-
if (!username || !chatId) return;
|
| 347 |
-
|
| 348 |
-
localStorage.setItem(`chatHistory_${username}_${chatId}`, chatContainer.innerHTML);
|
| 349 |
-
|
| 350 |
-
// Update the chat title if it's the first message
|
| 351 |
-
const messages = chatContainer.querySelectorAll('.message-bubble');
|
| 352 |
-
if (messages.length === 1) {
|
| 353 |
-
updateChatTitle(username, chatId, messages[0].textContent.substring(0, 30));
|
| 354 |
-
}
|
| 355 |
-
}
|
| 356 |
-
|
| 357 |
-
function loadChatList(username) {
|
| 358 |
-
chatList.innerHTML = '';
|
| 359 |
-
const chats = [];
|
| 360 |
-
|
| 361 |
-
// Find all chats for this user
|
| 362 |
-
for (let i = 0; i < localStorage.length; i++) {
|
| 363 |
-
const key = localStorage.key(i);
|
| 364 |
-
if (key.startsWith(`chatHistory_${username}_`)) {
|
| 365 |
-
const chatId = key.split('_')[2];
|
| 366 |
-
const title = localStorage.getItem(`chatTitle_${username}_${chatId}`) || 'New Chat';
|
| 367 |
-
chats.push({ id: chatId, title });
|
| 368 |
-
}
|
| 369 |
-
}
|
| 370 |
-
|
| 371 |
-
// Sort by most recent first
|
| 372 |
-
chats.sort((a, b) => b.id.localeCompare(a.id));
|
| 373 |
-
|
| 374 |
-
// Add to sidebar
|
| 375 |
-
chats.forEach(chat => {
|
| 376 |
-
addChatToList(username, chat.id, chat.title);
|
| 377 |
-
});
|
| 378 |
-
}
|
| 379 |
-
|
| 380 |
-
function addChatToList(username, chatId, title) {
|
| 381 |
-
const chatItem = document.createElement('div');
|
| 382 |
-
chatItem.className = 'flex justify-between items-center p-2 hover:bg-gray-700 rounded-lg cursor-pointer';
|
| 383 |
-
chatItem.dataset.chatId = chatId;
|
| 384 |
-
|
| 385 |
-
const titleSpan = document.createElement('span');
|
| 386 |
-
titleSpan.className = 'truncate flex-1';
|
| 387 |
-
titleSpan.textContent = title;
|
| 388 |
-
|
| 389 |
-
const actionsDiv = document.createElement('div');
|
| 390 |
-
actionsDiv.className = 'flex space-x-2';
|
| 391 |
-
|
| 392 |
-
const renameBtn = document.createElement('button');
|
| 393 |
-
renameBtn.className = 'text-gray-400 hover:text-white';
|
| 394 |
-
renameBtn.innerHTML = '<i data-feather="edit-2" class="w-4 h-4"></i>';
|
| 395 |
-
renameBtn.onclick = (e) => {
|
| 396 |
-
e.stopPropagation();
|
| 397 |
-
renameChat(username, chatId, titleSpan);
|
| 398 |
-
};
|
| 399 |
-
|
| 400 |
-
const deleteBtn = document.createElement('button');
|
| 401 |
-
deleteBtn.className = 'text-gray-400 hover:text-white';
|
| 402 |
-
deleteBtn.innerHTML = '<i data-feather="trash-2" class="w-4 h-4"></i>';
|
| 403 |
-
deleteBtn.onclick = (e) => {
|
| 404 |
-
e.stopPropagation();
|
| 405 |
-
deleteChat(username, chatId, chatItem);
|
| 406 |
-
};
|
| 407 |
|
| 408 |
-
actionsDiv.appendChild(renameBtn);
|
| 409 |
-
actionsDiv.appendChild(deleteBtn);
|
| 410 |
-
chatItem.appendChild(titleSpan);
|
| 411 |
-
chatItem.appendChild(actionsDiv);
|
| 412 |
-
|
| 413 |
-
chatItem.addEventListener('click', () => {
|
| 414 |
-
loadChatHistory(username, chatId);
|
| 415 |
-
});
|
| 416 |
-
|
| 417 |
-
chatList.prepend(chatItem);
|
| 418 |
-
feather.replace();
|
| 419 |
-
}
|
| 420 |
-
|
| 421 |
-
function updateChatTitle(username, chatId, title) {
|
| 422 |
-
localStorage.setItem(`chatTitle_${username}_${chatId}`, title);
|
| 423 |
-
const chatItem = chatList.querySelector(`[data-chat-id="${chatId}"]`);
|
| 424 |
-
if (chatItem) {
|
| 425 |
-
chatItem.querySelector('span').textContent = title;
|
| 426 |
-
}
|
| 427 |
-
}
|
| 428 |
-
|
| 429 |
-
function renameChat(username, chatId, titleElement) {
|
| 430 |
-
const newTitle = prompt('Enter new chat title:', titleElement.textContent);
|
| 431 |
-
if (newTitle && newTitle.trim()) {
|
| 432 |
-
localStorage.setItem(`chatTitle_${username}_${chatId}`, newTitle.trim());
|
| 433 |
-
titleElement.textContent = newTitle.trim();
|
| 434 |
-
}
|
| 435 |
-
}
|
| 436 |
-
|
| 437 |
-
function deleteChat(username, chatId, element) {
|
| 438 |
-
if (confirm('Are you sure you want to delete this chat?')) {
|
| 439 |
-
localStorage.removeItem(`chatHistory_${username}_${chatId}`);
|
| 440 |
-
localStorage.removeItem(`chatTitle_${username}_${chatId}`);
|
| 441 |
-
element.remove();
|
| 442 |
-
|
| 443 |
-
if (currentChatId === chatId) {
|
| 444 |
-
createNewChat();
|
| 445 |
-
}
|
| 446 |
-
}
|
| 447 |
-
}
|
| 448 |
-
// Load saved settings
|
| 449 |
-
async function loadSettings() {
|
| 450 |
-
const savedApiKey = localStorage.getItem('chatRouterApiKey');
|
| 451 |
const savedModel = localStorage.getItem('chatRouterModel');
|
| 452 |
const savedTheme = localStorage.getItem('chatRouterTheme') || 'indigo';
|
| 453 |
|
| 454 |
-
if (savedApiKey) {
|
| 455 |
-
apiKeyInput.value = savedApiKey;
|
| 456 |
-
sendBtn.disabled = false;
|
| 457 |
-
await fetchModels(savedApiKey); // Load models when API key exists
|
| 458 |
-
}
|
| 459 |
if (savedModel) {
|
| 460 |
modelSelect.value = savedModel;
|
| 461 |
modelIndicator.textContent = `Using: ${savedModel.split('/').pop()}`;
|
|
@@ -464,7 +225,8 @@ async function loadSettings() {
|
|
| 464 |
// Apply theme
|
| 465 |
document.body.className = `bg-gradient-to-br from-${savedTheme}-900 to-purple-900 text-white`;
|
| 466 |
}
|
| 467 |
-
|
|
|
|
| 468 |
const freeModels = [
|
| 469 |
'alibaba/tongyi-deepresearch-30b-a3b:free',
|
| 470 |
'nvidia/nemotron-nano-9b-v2:free',
|
|
@@ -550,21 +312,20 @@ async function loadSettings() {
|
|
| 550 |
// Verify API key by fetching models
|
| 551 |
await fetchModels(apiKey);
|
| 552 |
|
| 553 |
-
//
|
| 554 |
-
localStorage.setItem('chatRouterApiKey', apiKey);
|
| 555 |
localStorage.setItem('chatRouterModel', model);
|
| 556 |
sendBtn.disabled = false;
|
| 557 |
modelIndicator.textContent = `Using: ${model.split('/').pop()}`;
|
| 558 |
settingsModal.classList.add('hidden');
|
| 559 |
|
| 560 |
// Add confirmation message to chat
|
| 561 |
-
addMessage('system', '
|
| 562 |
} catch (error) {
|
| 563 |
addMessage('system', `Error verifying API key: ${error.message}`);
|
| 564 |
}
|
| 565 |
});
|
| 566 |
|
| 567 |
-
|
| 568 |
apiKeyInput.addEventListener('blur', async () => {
|
| 569 |
const apiKey = apiKeyInput.value.trim();
|
| 570 |
if (apiKey) {
|
|
@@ -628,20 +389,23 @@ async function loadSettings() {
|
|
| 628 |
addMessage('system', `Error: ${error.message}`);
|
| 629 |
}
|
| 630 |
}
|
| 631 |
-
|
| 632 |
// Send message to OpenRouter
|
| 633 |
-
async function sendMessage() {
|
| 634 |
const message = messageInput.value.trim();
|
| 635 |
if (!message) return;
|
| 636 |
|
| 637 |
-
const apiKey =
|
| 638 |
const model = localStorage.getItem('chatRouterModel');
|
| 639 |
|
| 640 |
-
if (!apiKey
|
| 641 |
-
addMessage('system', 'Please
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 642 |
return;
|
| 643 |
}
|
| 644 |
-
|
| 645 |
// Add user message to chat
|
| 646 |
addMessage('user', message);
|
| 647 |
messageInput.value = '';
|
|
@@ -664,8 +428,10 @@ async function sendMessage() {
|
|
| 664 |
messages: [{ role: 'user', content: message }]
|
| 665 |
})
|
| 666 |
});
|
| 667 |
-
|
| 668 |
-
|
|
|
|
|
|
|
| 669 |
if (data.error) {
|
| 670 |
addMessage('system', `Error: ${data.error.message}`);
|
| 671 |
} else if (data.choices && data.choices[0].message.content) {
|
|
@@ -678,95 +444,44 @@ async function sendMessage() {
|
|
| 678 |
}
|
| 679 |
}
|
| 680 |
// Event listeners
|
| 681 |
-
|
| 682 |
-
if (authBtn.dataset.state === 'logout') {
|
| 683 |
-
logoutUser();
|
| 684 |
-
} else {
|
| 685 |
-
showAuthModal();
|
| 686 |
-
}
|
| 687 |
-
});
|
| 688 |
-
|
| 689 |
-
closeAuth.addEventListener('click', () => {
|
| 690 |
-
authModal.classList.add('hidden');
|
| 691 |
-
});
|
| 692 |
-
|
| 693 |
-
authToggleBtn.addEventListener('click', () => {
|
| 694 |
-
showAuthModal(!isRegistering);
|
| 695 |
-
});
|
| 696 |
-
authActionBtn.addEventListener('click', async () => {
|
| 697 |
-
const username = regUsername.value.trim();
|
| 698 |
-
const password = authPassword.value.trim();
|
| 699 |
-
|
| 700 |
-
if (!username || !password) {
|
| 701 |
-
alert('Please enter both username and password');
|
| 702 |
-
return;
|
| 703 |
-
}
|
| 704 |
-
|
| 705 |
-
if (isRegistering) {
|
| 706 |
-
const hashedPassword = await hashPassword(password);
|
| 707 |
-
|
| 708 |
-
if (localStorage.getItem(`chatRouterPass_${username}`)) {
|
| 709 |
-
alert('Username already exists');
|
| 710 |
-
return;
|
| 711 |
-
}
|
| 712 |
-
|
| 713 |
-
localStorage.setItem(`chatRouterPass_${username}`, hashedPassword);
|
| 714 |
-
if (await loginUser(username, password)) {
|
| 715 |
-
authModal.classList.add('hidden');
|
| 716 |
-
}
|
| 717 |
-
} else {
|
| 718 |
-
if (await loginUser(username, password)) {
|
| 719 |
-
authModal.classList.add('hidden');
|
| 720 |
-
}
|
| 721 |
-
}
|
| 722 |
-
});
|
| 723 |
-
newChatBtn.addEventListener('click', createNewChat);
|
| 724 |
newSidebarChat.addEventListener('click', createNewChat);
|
| 725 |
-
|
| 726 |
-
// Sidebar toggle for mobile
|
| 727 |
sidebarToggle.addEventListener('click', () => {
|
| 728 |
sidebar.classList.toggle('hidden');
|
| 729 |
});
|
| 730 |
-
messageInput.addEventListener('keydown', (e) => {
|
| 731 |
if (e.key === 'Enter' && !e.shiftKey) {
|
| 732 |
e.preventDefault();
|
| 733 |
sendMessage();
|
| 734 |
}
|
| 735 |
});
|
| 736 |
sendBtn.addEventListener('click', () => {
|
| 737 |
-
|
| 738 |
-
if (username) {
|
| 739 |
-
sendMessage();
|
| 740 |
-
saveChatHistory(username, currentChatId);
|
| 741 |
-
}
|
| 742 |
});
|
|
|
|
| 743 |
// Initialize
|
| 744 |
loadSettings();
|
| 745 |
|
| 746 |
-
//
|
| 747 |
-
const activeUser = sessionStorage.getItem('chatRouterSession');
|
| 748 |
-
if (activeUser && localStorage.getItem(`chatRouterPass_${activeUser}`)) {
|
| 749 |
-
localStorage.setItem('chatRouterUser', activeUser);
|
| 750 |
-
}
|
| 751 |
-
|
| 752 |
-
updateAuthUI();
|
| 753 |
-
// Welcome message
|
| 754 |
setTimeout(() => {
|
| 755 |
-
if (!localStorage.getItem('
|
| 756 |
-
addMessage('system', 'Welcome to ChatRouter! Please
|
| 757 |
-
} else if (!localStorage.getItem('chatRouterModel')) {
|
| 758 |
-
addMessage('system', 'Please select a free model from settings to begin.');
|
| 759 |
} else if (!currentChatId) {
|
| 760 |
createNewChat();
|
| 761 |
}
|
| 762 |
}, 1000);
|
| 763 |
-
|
| 764 |
// Close sidebar when clicking outside on mobile
|
| 765 |
document.addEventListener('click', (e) => {
|
| 766 |
if (window.innerWidth < 768 && !sidebar.contains(e.target) && e.target !== sidebarToggle) {
|
| 767 |
sidebar.classList.add('hidden');
|
| 768 |
}
|
| 769 |
});
|
| 770 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 771 |
</body>
|
| 772 |
</html>
|
|
|
|
| 63 |
<button id="sidebarToggle" class="fixed left-4 top-4 z-40 md:hidden bg-gray-800 p-2 rounded-lg">
|
| 64 |
<i data-feather="menu"></i>
|
| 65 |
</button>
|
|
|
|
| 66 |
<header class="flex justify-between items-center mb-8 ml-12 md:ml-0">
|
| 67 |
<div class="flex items-center space-x-3">
|
| 68 |
<i data-feather="cpu" class="w-8 h-8 text-indigo-300"></i>
|
|
|
|
| 77 |
<i data-feather="settings" class="w-5 h-5"></i>
|
| 78 |
<span>Settings</span>
|
| 79 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
</div>
|
| 81 |
+
</header>
|
| 82 |
+
<!-- Settings Modal -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
<div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
| 84 |
<div class="bg-gray-800 rounded-xl p-6 w-full max-w-md">
|
| 85 |
<div class="flex justify-between items-center mb-4">
|
|
|
|
| 183 |
const chatList = document.getElementById('chatList');
|
| 184 |
|
| 185 |
// DOM Elements - Original
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
const newChatBtn = document.getElementById('newChatBtn');
|
| 187 |
const settingsBtn = document.getElementById('settingsBtn');
|
| 188 |
const settingsModal = document.getElementById('settingsModal');
|
|
|
|
| 198 |
const themeBtns = document.querySelectorAll('.theme-btn');
|
| 199 |
// User management
|
| 200 |
let isRegistering = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
// Chat management
|
| 202 |
let currentChatId = null;
|
| 203 |
|
| 204 |
function generateChatId() {
|
| 205 |
return 'chat_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
|
| 206 |
}
|
| 207 |
+
function createNewChat() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
currentChatId = generateChatId();
|
| 209 |
chatContainer.innerHTML = '';
|
| 210 |
addMessage('system', 'π New chat started.');
|
|
|
|
|
|
|
| 211 |
}
|
| 212 |
+
// Load saved settings
|
| 213 |
+
async function loadSettings() {
|
| 214 |
+
// Clear any saved API key on load
|
| 215 |
+
localStorage.removeItem('chatRouterApiKey');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
const savedModel = localStorage.getItem('chatRouterModel');
|
| 218 |
const savedTheme = localStorage.getItem('chatRouterTheme') || 'indigo';
|
| 219 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
if (savedModel) {
|
| 221 |
modelSelect.value = savedModel;
|
| 222 |
modelIndicator.textContent = `Using: ${savedModel.split('/').pop()}`;
|
|
|
|
| 225 |
// Apply theme
|
| 226 |
document.body.className = `bg-gradient-to-br from-${savedTheme}-900 to-purple-900 text-white`;
|
| 227 |
}
|
| 228 |
+
|
| 229 |
+
// Free models from OpenRouter
|
| 230 |
const freeModels = [
|
| 231 |
'alibaba/tongyi-deepresearch-30b-a3b:free',
|
| 232 |
'nvidia/nemotron-nano-9b-v2:free',
|
|
|
|
| 312 |
// Verify API key by fetching models
|
| 313 |
await fetchModels(apiKey);
|
| 314 |
|
| 315 |
+
// Only save model (API key is not saved)
|
|
|
|
| 316 |
localStorage.setItem('chatRouterModel', model);
|
| 317 |
sendBtn.disabled = false;
|
| 318 |
modelIndicator.textContent = `Using: ${model.split('/').pop()}`;
|
| 319 |
settingsModal.classList.add('hidden');
|
| 320 |
|
| 321 |
// Add confirmation message to chat
|
| 322 |
+
addMessage('system', 'Model selected successfully. You can now start chatting!');
|
| 323 |
} catch (error) {
|
| 324 |
addMessage('system', `Error verifying API key: ${error.message}`);
|
| 325 |
}
|
| 326 |
});
|
| 327 |
|
| 328 |
+
// Load models when API key changes
|
| 329 |
apiKeyInput.addEventListener('blur', async () => {
|
| 330 |
const apiKey = apiKeyInput.value.trim();
|
| 331 |
if (apiKey) {
|
|
|
|
| 389 |
addMessage('system', `Error: ${error.message}`);
|
| 390 |
}
|
| 391 |
}
|
|
|
|
| 392 |
// Send message to OpenRouter
|
| 393 |
+
async function sendMessage() {
|
| 394 |
const message = messageInput.value.trim();
|
| 395 |
if (!message) return;
|
| 396 |
|
| 397 |
+
const apiKey = apiKeyInput.value.trim();
|
| 398 |
const model = localStorage.getItem('chatRouterModel');
|
| 399 |
|
| 400 |
+
if (!apiKey) {
|
| 401 |
+
addMessage('system', 'Please enter your OpenRouter API key in settings.');
|
| 402 |
+
return;
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
if (!model) {
|
| 406 |
+
addMessage('system', 'Please select a model in settings.');
|
| 407 |
return;
|
| 408 |
}
|
|
|
|
| 409 |
// Add user message to chat
|
| 410 |
addMessage('user', message);
|
| 411 |
messageInput.value = '';
|
|
|
|
| 428 |
messages: [{ role: 'user', content: message }]
|
| 429 |
})
|
| 430 |
});
|
| 431 |
+
|
| 432 |
+
// Clear API key after use
|
| 433 |
+
apiKeyInput.value = '';
|
| 434 |
+
const data = await response.json();
|
| 435 |
if (data.error) {
|
| 436 |
addMessage('system', `Error: ${data.error.message}`);
|
| 437 |
} else if (data.choices && data.choices[0].message.content) {
|
|
|
|
| 444 |
}
|
| 445 |
}
|
| 446 |
// Event listeners
|
| 447 |
+
newChatBtn.addEventListener('click', createNewChat);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
newSidebarChat.addEventListener('click', createNewChat);
|
| 449 |
+
// Sidebar toggle for mobile
|
|
|
|
| 450 |
sidebarToggle.addEventListener('click', () => {
|
| 451 |
sidebar.classList.toggle('hidden');
|
| 452 |
});
|
| 453 |
+
messageInput.addEventListener('keydown', (e) => {
|
| 454 |
if (e.key === 'Enter' && !e.shiftKey) {
|
| 455 |
e.preventDefault();
|
| 456 |
sendMessage();
|
| 457 |
}
|
| 458 |
});
|
| 459 |
sendBtn.addEventListener('click', () => {
|
| 460 |
+
sendMessage();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
});
|
| 462 |
+
|
| 463 |
// Initialize
|
| 464 |
loadSettings();
|
| 465 |
|
| 466 |
+
// Welcome message
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
setTimeout(() => {
|
| 468 |
+
if (!localStorage.getItem('chatRouterModel')) {
|
| 469 |
+
addMessage('system', 'Welcome to ChatRouter! Please enter your OpenRouter API key and select a model in settings to begin.');
|
|
|
|
|
|
|
| 470 |
} else if (!currentChatId) {
|
| 471 |
createNewChat();
|
| 472 |
}
|
| 473 |
}, 1000);
|
|
|
|
| 474 |
// Close sidebar when clicking outside on mobile
|
| 475 |
document.addEventListener('click', (e) => {
|
| 476 |
if (window.innerWidth < 768 && !sidebar.contains(e.target) && e.target !== sidebarToggle) {
|
| 477 |
sidebar.classList.add('hidden');
|
| 478 |
}
|
| 479 |
});
|
| 480 |
+
|
| 481 |
+
// Clear any saved API key on page refresh
|
| 482 |
+
window.addEventListener('beforeunload', () => {
|
| 483 |
+
localStorage.removeItem('chatRouterApiKey');
|
| 484 |
+
});
|
| 485 |
+
</script>
|
| 486 |
</body>
|
| 487 |
</html>
|