Spaces:
Running
Running
can you add a sidebar for more features such as cube root, greatest common factor and a lot more scientific features? and make it so that even pressing the sceietific feature like squre root not just directly becore like equal cause it will just perform the action right away. add peranthese too - Follow Up Deployment
Browse files- index.html +563 -1028
index.html
CHANGED
|
@@ -3,1133 +3,668 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 9 |
-
<script>
|
| 10 |
-
tailwind.config = {
|
| 11 |
-
theme: {
|
| 12 |
-
extend: {
|
| 13 |
-
colors: {
|
| 14 |
-
primary: {
|
| 15 |
-
50: '#f0f9ff',
|
| 16 |
-
100: '#e0f2fe',
|
| 17 |
-
200: '#bae6fd',
|
| 18 |
-
300: '#7dd3fc',
|
| 19 |
-
400: '#38bdf8',
|
| 20 |
-
500: '#0ea5e9',
|
| 21 |
-
600: '#0284c7',
|
| 22 |
-
700: '#0369a1',
|
| 23 |
-
800: '#075985',
|
| 24 |
-
900: '#0c4a6e',
|
| 25 |
-
},
|
| 26 |
-
dark: {
|
| 27 |
-
800: '#1e293b',
|
| 28 |
-
900: '#0f172a',
|
| 29 |
-
}
|
| 30 |
-
}
|
| 31 |
-
}
|
| 32 |
-
}
|
| 33 |
-
}
|
| 34 |
-
</script>
|
| 35 |
<style>
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
}
|
| 40 |
-
|
| 41 |
-
|
| 42 |
}
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
}
|
| 47 |
-
|
| 48 |
-
|
| 49 |
}
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
background: #1e293b;
|
| 54 |
-
}
|
| 55 |
-
.dark ::-webkit-scrollbar-thumb {
|
| 56 |
-
background: #64748b;
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
/* Chat message typing animation */
|
| 60 |
-
@keyframes typing {
|
| 61 |
-
from { width: 0 }
|
| 62 |
-
to { width: 100% }
|
| 63 |
}
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
overflow: hidden;
|
| 67 |
-
white-space: nowrap;
|
| 68 |
-
animation: typing 2s steps(40, end);
|
| 69 |
}
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
transition: all 0.3s ease;
|
| 74 |
}
|
| 75 |
</style>
|
| 76 |
</head>
|
| 77 |
-
<body class="bg-gray-
|
| 78 |
-
<div class="
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
<div class="p-4
|
| 82 |
-
<
|
| 83 |
-
<
|
| 84 |
-
<
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
<div class="p-4 border-t border-gray-200 dark:border-gray-700">
|
| 93 |
-
<div class="flex items-center gap-3 mb-4 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 p-2 rounded-lg transition-all">
|
| 94 |
-
<div class="w-8 h-8 rounded-full bg-primary-500 flex items-center justify-center text-white">
|
| 95 |
-
<i class="fas fa-user"></i>
|
| 96 |
</div>
|
| 97 |
-
<span class="font-medium">User Account</span>
|
| 98 |
</div>
|
| 99 |
|
| 100 |
-
<div
|
| 101 |
-
<div
|
| 102 |
-
|
| 103 |
-
</div>
|
| 104 |
-
<span>Settings</span>
|
| 105 |
-
</div>
|
| 106 |
-
</div>
|
| 107 |
-
</div>
|
| 108 |
-
|
| 109 |
-
<!-- Main Content -->
|
| 110 |
-
<div class="flex-1 flex flex-col overflow-hidden">
|
| 111 |
-
<!-- Header -->
|
| 112 |
-
<header class="bg-white dark:bg-dark-800 border-b border-gray-200 dark:border-gray-700 p-4 flex items-center justify-between">
|
| 113 |
-
<div class="flex items-center gap-2">
|
| 114 |
-
<button id="sidebarToggle" class="md:hidden text-gray-500 dark:text-gray-400">
|
| 115 |
-
<i class="fas fa-bars text-xl"></i>
|
| 116 |
-
</button>
|
| 117 |
-
<h1 class="text-xl font-bold">NexusChat</h1>
|
| 118 |
</div>
|
| 119 |
|
| 120 |
-
<div class="flex
|
| 121 |
-
<
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
<i class="fas fa-chevron-down text-xs"></i>
|
| 125 |
-
</button>
|
| 126 |
-
|
| 127 |
-
<div id="modelDropdown" class="hidden absolute right-0 mt-2 w-48 bg-white dark:bg-dark-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 z-10">
|
| 128 |
-
<div class="p-2">
|
| 129 |
-
<div class="text-xs uppercase text-gray-500 dark:text-gray-400 px-2 py-1">Default Models</div>
|
| 130 |
-
<div class="model-option cursor-pointer px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded" data-model="gpt-3.5">GPT-3.5</div>
|
| 131 |
-
<div class="model-option cursor-pointer px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded" data-model="gpt-4">GPT-4</div>
|
| 132 |
-
|
| 133 |
-
<div class="text-xs uppercase text-gray-500 dark:text-gray-400 px-2 py-1 mt-2">Custom Models</div>
|
| 134 |
-
<div id="customModelsList">
|
| 135 |
-
<!-- Custom models will be added here -->
|
| 136 |
-
</div>
|
| 137 |
-
|
| 138 |
-
<div class="border-t border-gray-200 dark:border-gray-700 mt-2 pt-2">
|
| 139 |
-
<div id="addModelBtn" class="cursor-pointer px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded flex items-center gap-2 text-primary-500">
|
| 140 |
-
<i class="fas fa-plus"></i>
|
| 141 |
-
<span>Add Custom Model</span>
|
| 142 |
-
</div>
|
| 143 |
-
</div>
|
| 144 |
-
</div>
|
| 145 |
-
</div>
|
| 146 |
-
</div>
|
| 147 |
-
|
| 148 |
-
<button id="darkModeToggle" class="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
|
| 149 |
-
<i class="fas fa-moon dark:hidden"></i>
|
| 150 |
-
<i class="fas fa-sun hidden dark:block"></i>
|
| 151 |
</button>
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
<!-- Chat Area -->
|
| 156 |
-
<div class="flex-1 overflow-y-auto p-4 bg-gray-50 dark:bg-dark-900" id="chatArea">
|
| 157 |
-
<div class="max-w-3xl mx-auto">
|
| 158 |
-
<!-- Welcome message -->
|
| 159 |
-
<div id="welcomeMessage" class="text-center py-10">
|
| 160 |
-
<div class="w-16 h-16 mx-auto mb-4 bg-primary-500 rounded-full flex items-center justify-center text-white">
|
| 161 |
-
<i class="fas fa-robot text-2xl"></i>
|
| 162 |
-
</div>
|
| 163 |
-
<h2 class="text-2xl font-bold mb-2">Welcome to NexusChat</h2>
|
| 164 |
-
<p class="text-gray-600 dark:text-gray-400 mb-6">Start a new conversation or select one from your history</p>
|
| 165 |
-
<button id="quickStartBtn" class="bg-primary-500 hover:bg-primary-600 text-white py-2 px-6 rounded-lg transition-all">
|
| 166 |
-
Quick Start
|
| 167 |
-
</button>
|
| 168 |
-
</div>
|
| 169 |
-
|
| 170 |
-
<!-- Chat messages will be added here dynamically -->
|
| 171 |
</div>
|
| 172 |
</div>
|
| 173 |
|
| 174 |
-
<!--
|
| 175 |
-
<div class="
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
<
|
| 197 |
-
<button id="
|
| 198 |
-
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
</div>
|
| 201 |
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
<
|
|
|
|
|
|
|
|
|
|
| 206 |
</div>
|
| 207 |
|
| 208 |
-
<div class="mb-4">
|
| 209 |
-
<
|
| 210 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
</div>
|
| 212 |
|
| 213 |
<div class="mb-4">
|
| 214 |
-
<
|
| 215 |
-
<
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
<input type="checkbox" id="modelStreaming" class="w-4 h-4 text-primary-600 rounded border-gray-300 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700">
|
| 220 |
-
<label for="modelStreaming" class="ml-2 text-sm font-medium text-gray-700 dark:text-gray-300">Supports Streaming</label>
|
| 221 |
</div>
|
| 222 |
-
<input type="hidden" id="editModelId" value="">
|
| 223 |
|
| 224 |
-
<div class="flex justify-end gap-2">
|
| 225 |
-
<button type="button" id="cancelAddModel" class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-all">
|
| 226 |
-
Cancel
|
| 227 |
-
</button>
|
| 228 |
-
<button type="submit" class="px-4 py-2 bg-primary-500 hover:bg-primary-600 text-white rounded transition-all">
|
| 229 |
-
Add Model
|
| 230 |
-
</button>
|
| 231 |
-
</div>
|
| 232 |
-
</form>
|
| 233 |
-
</div>
|
| 234 |
-
</div>
|
| 235 |
-
|
| 236 |
-
<!-- API Key Modal -->
|
| 237 |
-
<div id="apiKeyModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
| 238 |
-
<div class="bg-white dark:bg-dark-800 rounded-lg shadow-xl w-full max-w-md p-6">
|
| 239 |
-
<div class="flex justify-between items-center mb-4">
|
| 240 |
-
<h3 class="text-lg font-bold">OpenAI API Key Required</h3>
|
| 241 |
-
<button id="closeApiKeyModal" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
|
| 242 |
-
<i class="fas fa-times"></i>
|
| 243 |
-
</button>
|
| 244 |
-
</div>
|
| 245 |
-
|
| 246 |
-
<p class="mb-4">To use OpenAI models, please enter your API key:</p>
|
| 247 |
-
|
| 248 |
-
<form id="apiKeyForm">
|
| 249 |
<div class="mb-4">
|
| 250 |
-
<
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
Cancel
|
| 256 |
-
</button>
|
| 257 |
-
<button type="submit" class="px-4 py-2 bg-primary-500 hover:bg-primary-600 text-white rounded transition-all">
|
| 258 |
-
Save Key
|
| 259 |
-
</button>
|
| 260 |
</div>
|
| 261 |
-
</form>
|
| 262 |
-
</div>
|
| 263 |
-
</div>
|
| 264 |
-
|
| 265 |
-
<!-- Delete Chat Confirmation Modal -->
|
| 266 |
-
<div id="deleteChatModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
| 267 |
-
<div class="bg-white dark:bg-dark-800 rounded-lg shadow-xl w-full max-w-md p-6">
|
| 268 |
-
<div class="flex justify-between items-center mb-4">
|
| 269 |
-
<h3 class="text-lg font-bold">Delete Chat</h3>
|
| 270 |
-
<button id="closeDeleteModal" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
|
| 271 |
-
<i class="fas fa-times"></i>
|
| 272 |
-
</button>
|
| 273 |
</div>
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
</
|
| 281 |
-
<
|
| 282 |
-
Delete
|
| 283 |
-
</button>
|
| 284 |
</div>
|
| 285 |
</div>
|
| 286 |
</div>
|
| 287 |
|
| 288 |
<script>
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
});
|
| 335 |
|
| 336 |
-
//
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
// Render custom models in dropdown
|
| 356 |
-
renderCustomModels();
|
| 357 |
-
}
|
| 358 |
-
|
| 359 |
-
// Render chat list
|
| 360 |
-
function renderChatList() {
|
| 361 |
-
chatList.innerHTML = '';
|
| 362 |
-
|
| 363 |
-
if (chats.length === 0) {
|
| 364 |
-
chatList.innerHTML = `
|
| 365 |
-
<div class="p-4 text-center text-gray-500 dark:text-gray-400">
|
| 366 |
-
No chats yet
|
| 367 |
-
</div>
|
| 368 |
-
`;
|
| 369 |
-
return;
|
| 370 |
-
}
|
| 371 |
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
chatElement.className = `group relative flex items-center gap-3 p-2 rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 transition-all ${currentChatId === chat.id ? 'bg-gray-100 dark:bg-gray-700' : ''}`;
|
| 375 |
-
chatElement.dataset.id = chat.id;
|
| 376 |
-
|
| 377 |
-
chatElement.innerHTML = `
|
| 378 |
-
<div class="w-8 h-8 rounded-full bg-primary-100 dark:bg-primary-900 text-primary-500 dark:text-primary-300 flex items-center justify-center">
|
| 379 |
-
<i class="fas fa-comment"></i>
|
| 380 |
-
</div>
|
| 381 |
-
<div class="flex-1 min-w-0">
|
| 382 |
-
<p class="truncate font-medium">${chat.title || 'New Chat'}</p>
|
| 383 |
-
<p class="text-xs text-gray-500 dark:text-gray-400 truncate">${chat.lastMessage || ''}</p>
|
| 384 |
-
</div>
|
| 385 |
-
<button class="delete-chat opacity-0 group-hover:opacity-100 text-gray-400 hover:text-red-500 transition-all p-1">
|
| 386 |
-
<i class="fas fa-trash text-sm"></i>
|
| 387 |
-
</button>
|
| 388 |
-
`;
|
| 389 |
-
|
| 390 |
-
chatElement.addEventListener('click', () => loadChat(chat.id));
|
| 391 |
-
|
| 392 |
-
const deleteBtn = chatElement.querySelector('.delete-chat');
|
| 393 |
-
deleteBtn.addEventListener('click', (e) => {
|
| 394 |
-
e.stopPropagation();
|
| 395 |
-
showDeleteModal(chat.id);
|
| 396 |
-
});
|
| 397 |
-
|
| 398 |
-
chatList.appendChild(chatElement);
|
| 399 |
});
|
| 400 |
-
}
|
| 401 |
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
|
|
|
|
|
|
|
|
|
| 419 |
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
<button class="edit-model text-gray-400 hover:text-blue-500 transition-all p-1">
|
| 424 |
-
<i class="fas fa-edit text-xs"></i>
|
| 425 |
-
</button>
|
| 426 |
-
<button class="delete-model text-gray-400 hover:text-red-500 transition-all p-1">
|
| 427 |
-
<i class="fas fa-trash text-xs"></i>
|
| 428 |
-
</button>
|
| 429 |
-
</div>
|
| 430 |
-
`;
|
| 431 |
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
}
|
| 436 |
-
});
|
| 437 |
|
| 438 |
-
const
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
e.stopPropagation();
|
| 447 |
-
showEditModelModal(model);
|
| 448 |
});
|
| 449 |
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
title: 'New Chat',
|
| 459 |
-
model: selectedModel,
|
| 460 |
-
messages: [],
|
| 461 |
-
lastMessage: '',
|
| 462 |
-
createdAt: new Date().toISOString()
|
| 463 |
-
};
|
| 464 |
-
|
| 465 |
-
chats.unshift(newChat);
|
| 466 |
-
saveChats();
|
| 467 |
-
loadChat(newChat.id);
|
| 468 |
-
renderChatList();
|
| 469 |
-
}
|
| 470 |
-
|
| 471 |
-
// Load chat
|
| 472 |
-
function loadChat(chatId) {
|
| 473 |
-
currentChatId = chatId;
|
| 474 |
-
const chat = chats.find(c => c.id === chatId);
|
| 475 |
-
|
| 476 |
-
if (!chat) {
|
| 477 |
-
createNewChat();
|
| 478 |
-
return;
|
| 479 |
}
|
| 480 |
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
el.classList.toggle('dark:bg-gray-700', el.dataset.id === chatId);
|
| 485 |
-
});
|
| 486 |
-
|
| 487 |
-
// Render messages
|
| 488 |
-
renderMessages(chat.messages);
|
| 489 |
-
|
| 490 |
-
// Hide welcome message
|
| 491 |
-
welcomeMessage.classList.add('hidden');
|
| 492 |
-
}
|
| 493 |
-
|
| 494 |
-
// Render messages
|
| 495 |
-
function renderMessages(messages) {
|
| 496 |
-
chatArea.innerHTML = '';
|
| 497 |
-
|
| 498 |
-
if (messages.length === 0) {
|
| 499 |
-
welcomeMessage.classList.remove('hidden');
|
| 500 |
-
return;
|
| 501 |
}
|
| 502 |
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
const messageElement = document.createElement('div');
|
| 508 |
-
messageElement.className = `flex gap-4 ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`;
|
| 509 |
-
|
| 510 |
-
if (msg.role === 'assistant') {
|
| 511 |
-
messageElement.innerHTML = `
|
| 512 |
-
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-primary-500 text-white flex items-center justify-center">
|
| 513 |
-
<i class="fas fa-robot"></i>
|
| 514 |
-
</div>
|
| 515 |
-
<div class="max-w-[80%] bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm relative group">
|
| 516 |
-
<div class="prose dark:prose-invert">${msg.content}</div>
|
| 517 |
-
<div class="absolute right-2 top-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1">
|
| 518 |
-
<button class="message-action bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 p-1 rounded text-gray-700 dark:text-gray-300" data-action="regenerate" title="Regenerate">
|
| 519 |
-
<i class="fas fa-sync-alt text-xs"></i>
|
| 520 |
-
</button>
|
| 521 |
-
<button class="message-action bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 p-1 rounded text-gray-700 dark:text-gray-300" data-action="edit" title="Edit">
|
| 522 |
-
<i class="fas fa-edit text-xs"></i>
|
| 523 |
-
</button>
|
| 524 |
-
</div>
|
| 525 |
-
</div>
|
| 526 |
-
`;
|
| 527 |
} else {
|
| 528 |
-
|
| 529 |
-
<div class="max-w-[80%] bg-primary-500 text-white rounded-lg p-4 shadow-sm relative group">
|
| 530 |
-
<div class="prose prose-white">${msg.content}</div>
|
| 531 |
-
<div class="absolute right-2 top-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
| 532 |
-
<button class="message-action bg-primary-600 hover:bg-primary-700 p-1 rounded text-white" data-action="edit" title="Edit">
|
| 533 |
-
<i class="fas fa-edit text-xs"></i>
|
| 534 |
-
</button>
|
| 535 |
-
</div>
|
| 536 |
-
</div>
|
| 537 |
-
`;
|
| 538 |
}
|
| 539 |
-
|
| 540 |
-
messagesContainer.appendChild(messageElement);
|
| 541 |
-
});
|
| 542 |
-
|
| 543 |
-
chatArea.appendChild(messagesContainer);
|
| 544 |
-
chatArea.scrollTop = chatArea.scrollHeight;
|
| 545 |
-
}
|
| 546 |
-
|
| 547 |
-
// Send message
|
| 548 |
-
async function sendMessage() {
|
| 549 |
-
const message = messageInput.value.trim();
|
| 550 |
-
if (!message || !currentChatId) return;
|
| 551 |
-
|
| 552 |
-
// Get current chat
|
| 553 |
-
const chatIndex = chats.findIndex(c => c.id === currentChatId);
|
| 554 |
-
if (chatIndex === -1) return;
|
| 555 |
-
|
| 556 |
-
const chat = chats[chatIndex];
|
| 557 |
-
const model = models.find(m => m.id === chat.model);
|
| 558 |
-
if (!model) return;
|
| 559 |
-
|
| 560 |
-
if (model.type === 'default' && !localStorage.getItem('openaiApiKey')) {
|
| 561 |
-
showApiKeyModal();
|
| 562 |
-
return;
|
| 563 |
}
|
| 564 |
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
messageInput.value = '';
|
| 578 |
-
|
| 579 |
-
// Add loading indicator for assistant response
|
| 580 |
-
const loadingElement = document.createElement('div');
|
| 581 |
-
loadingElement.className = 'flex gap-4 justify-start';
|
| 582 |
-
loadingElement.innerHTML = `
|
| 583 |
-
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-primary-500 text-white flex items-center justify-center">
|
| 584 |
-
<i class="fas fa-robot"></i>
|
| 585 |
-
</div>
|
| 586 |
-
<div class="max-w-[80%] bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm w-full">
|
| 587 |
-
<div class="flex gap-2">
|
| 588 |
-
<div class="w-2 h-2 rounded-full bg-gray-300 animate-pulse"></div>
|
| 589 |
-
<div class="w-2 h-2 rounded-full bg-gray-300 animate-pulse delay-75"></div>
|
| 590 |
-
<div class="w-2 h-2 rounded-full bg-gray-300 animate-pulse delay-150"></div>
|
| 591 |
-
</div>
|
| 592 |
-
</div>
|
| 593 |
-
`;
|
| 594 |
|
| 595 |
-
|
| 596 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 597 |
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
|
|
|
| 603 |
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 613 |
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
}
|
| 622 |
|
| 623 |
-
//
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
role: 'assistant',
|
| 628 |
-
content: response,
|
| 629 |
-
timestamp: new Date().toISOString()
|
| 630 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
| 631 |
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
// Update chat list
|
| 637 |
-
renderChatList();
|
| 638 |
-
} catch (error) {
|
| 639 |
-
console.error('API Error:', error);
|
| 640 |
-
loadingElement.remove();
|
| 641 |
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
errorElement.className = 'flex gap-4 justify-start';
|
| 645 |
-
errorElement.innerHTML = `
|
| 646 |
-
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-red-500 text-white flex items-center justify-center">
|
| 647 |
-
<i class="fas fa-exclamation-triangle"></i>
|
| 648 |
-
</div>
|
| 649 |
-
<div class="max-w-[80%] bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm">
|
| 650 |
-
<div class="text-red-500">Error: ${error.message}</div>
|
| 651 |
-
</div>
|
| 652 |
-
`;
|
| 653 |
-
|
| 654 |
-
chatArea.appendChild(errorElement);
|
| 655 |
-
chatArea.scrollTop = chatArea.scrollHeight;
|
| 656 |
}
|
| 657 |
-
}
|
| 658 |
-
|
| 659 |
-
// Call OpenAI API
|
| 660 |
-
async function callOpenAIAPI(messages, model, apiKey) {
|
| 661 |
-
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
| 662 |
-
method: 'POST',
|
| 663 |
-
headers: {
|
| 664 |
-
'Content-Type': 'application/json',
|
| 665 |
-
'Authorization': `Bearer ${apiKey}`
|
| 666 |
-
},
|
| 667 |
-
body: JSON.stringify({
|
| 668 |
-
model: model === 'gpt-3.5' ? 'gpt-3.5-turbo' : 'gpt-4',
|
| 669 |
-
messages,
|
| 670 |
-
temperature: 0.7
|
| 671 |
-
})
|
| 672 |
-
});
|
| 673 |
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
|
|
|
|
|
|
| 677 |
}
|
| 678 |
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
async function callCustomModelAPI(messages, model) {
|
| 685 |
-
if (model.streaming) {
|
| 686 |
-
// Handle streaming response
|
| 687 |
-
const response = await fetch(model.endpoint, {
|
| 688 |
-
method: 'POST',
|
| 689 |
-
headers: {
|
| 690 |
-
'Content-Type': 'application/json',
|
| 691 |
-
...(model.key ? { 'Authorization': `Bearer ${model.key}` } : {})
|
| 692 |
-
},
|
| 693 |
-
body: JSON.stringify({
|
| 694 |
-
model: model.name,
|
| 695 |
-
messages,
|
| 696 |
-
temperature: 0.7,
|
| 697 |
-
stream: true
|
| 698 |
-
})
|
| 699 |
-
});
|
| 700 |
-
|
| 701 |
-
if (!response.ok) {
|
| 702 |
-
const errorText = await response.text();
|
| 703 |
-
throw new Error(errorText || 'Failed to call custom model API');
|
| 704 |
}
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
while (true) {
|
| 726 |
-
const { done, value } = await reader.read();
|
| 727 |
-
if (done) break;
|
| 728 |
-
|
| 729 |
-
const chunk = decoder.decode(value);
|
| 730 |
-
const lines = chunk.split('\n').filter(line => line.trim() !== '');
|
| 731 |
-
|
| 732 |
-
for (const line of lines) {
|
| 733 |
-
if (line.startsWith('data: ')) {
|
| 734 |
-
const data = line.substring(6);
|
| 735 |
-
if (data === '[DONE]') continue;
|
| 736 |
-
|
| 737 |
-
try {
|
| 738 |
-
const parsed = JSON.parse(data);
|
| 739 |
-
const content = parsed.choices?.[0]?.delta?.content || '';
|
| 740 |
-
result += content;
|
| 741 |
-
contentElement.innerHTML = result;
|
| 742 |
-
chatArea.scrollTop = chatArea.scrollHeight;
|
| 743 |
-
} catch (e) {
|
| 744 |
-
console.error('Error parsing stream data:', e);
|
| 745 |
-
}
|
| 746 |
-
}
|
| 747 |
-
}
|
| 748 |
-
}
|
| 749 |
-
} catch (e) {
|
| 750 |
-
console.error('Stream error:', e);
|
| 751 |
-
throw e;
|
| 752 |
}
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
const
|
| 758 |
-
|
| 759 |
-
headers: {
|
| 760 |
-
'Content-Type': 'application/json',
|
| 761 |
-
...(model.key ? { 'Authorization': `Bearer ${model.key}` } : {})
|
| 762 |
-
},
|
| 763 |
-
body: JSON.stringify({
|
| 764 |
-
model: model.id,
|
| 765 |
-
messages,
|
| 766 |
-
temperature: 0.7,
|
| 767 |
-
stream: false
|
| 768 |
-
})
|
| 769 |
-
});
|
| 770 |
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 779 |
}
|
| 780 |
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 787 |
}
|
|
|
|
|
|
|
|
|
|
| 788 |
}
|
| 789 |
-
}
|
| 790 |
-
|
| 791 |
-
// Select model
|
| 792 |
-
function selectModel(modelId) {
|
| 793 |
-
const model = models.find(m => m.id === modelId);
|
| 794 |
-
if (!model) return;
|
| 795 |
-
|
| 796 |
-
selectedModel = modelId;
|
| 797 |
-
currentModel.textContent = model.name;
|
| 798 |
-
modelDropdown.classList.add('hidden');
|
| 799 |
|
| 800 |
-
//
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 809 |
}
|
|
|
|
| 810 |
}
|
| 811 |
-
}
|
| 812 |
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
type: 'custom'
|
| 822 |
-
};
|
| 823 |
-
|
| 824 |
-
customModels.push(newModel);
|
| 825 |
-
models.push(newModel);
|
| 826 |
-
|
| 827 |
-
// Save to localStorage
|
| 828 |
-
localStorage.setItem('customModels', JSON.stringify(customModels));
|
| 829 |
-
|
| 830 |
-
// Update UI
|
| 831 |
-
renderCustomModels();
|
| 832 |
-
hideAddModelModal();
|
| 833 |
-
}
|
| 834 |
-
|
| 835 |
-
// Edit custom model
|
| 836 |
-
function editCustomModel(modelId, name, endpoint, key, streaming) {
|
| 837 |
-
const modelIndex = customModels.findIndex(m => m.id === modelId);
|
| 838 |
-
if (modelIndex === -1) return;
|
| 839 |
-
|
| 840 |
-
customModels[modelIndex] = {
|
| 841 |
-
...customModels[modelIndex],
|
| 842 |
-
name,
|
| 843 |
-
endpoint,
|
| 844 |
-
key,
|
| 845 |
-
streaming: streaming || false
|
| 846 |
-
};
|
| 847 |
-
|
| 848 |
-
// Update in models array
|
| 849 |
-
const globalModelIndex = models.findIndex(m => m.id === modelId);
|
| 850 |
-
if (globalModelIndex !== -1) {
|
| 851 |
-
models[globalModelIndex] = {
|
| 852 |
-
...models[globalModelIndex],
|
| 853 |
-
name,
|
| 854 |
-
endpoint,
|
| 855 |
-
key
|
| 856 |
-
};
|
| 857 |
}
|
| 858 |
-
|
| 859 |
-
// Save to localStorage
|
| 860 |
-
localStorage.setItem('customModels', JSON.stringify(customModels));
|
| 861 |
-
|
| 862 |
-
// Update UI
|
| 863 |
-
renderCustomModels();
|
| 864 |
-
hideAddModelModal();
|
| 865 |
-
}
|
| 866 |
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
// Update UI
|
| 876 |
-
renderCustomModels();
|
| 877 |
-
|
| 878 |
-
// If the deleted model was selected, switch to default
|
| 879 |
-
if (selectedModel === modelId) {
|
| 880 |
-
selectModel('gpt-4');
|
| 881 |
}
|
| 882 |
-
}
|
| 883 |
|
| 884 |
-
|
| 885 |
-
|
| 886 |
-
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
|
| 893 |
}
|
| 894 |
-
|
| 895 |
-
renderChatList();
|
| 896 |
-
hideDeleteModal();
|
| 897 |
-
}
|
| 898 |
-
|
| 899 |
-
// Show delete modal
|
| 900 |
-
function showDeleteModal(chatId) {
|
| 901 |
-
chatToDelete = chatId;
|
| 902 |
-
deleteChatModal.classList.remove('hidden');
|
| 903 |
-
}
|
| 904 |
-
|
| 905 |
-
// Hide delete modal
|
| 906 |
-
function hideDeleteModal() {
|
| 907 |
-
deleteChatModal.classList.add('hidden');
|
| 908 |
-
chatToDelete = null;
|
| 909 |
-
}
|
| 910 |
-
|
| 911 |
-
// Show add model modal
|
| 912 |
-
function showAddModelModal() {
|
| 913 |
-
addModelModal.classList.remove('hidden');
|
| 914 |
-
}
|
| 915 |
-
|
| 916 |
-
// Show edit model modal
|
| 917 |
-
function showEditModelModal(model) {
|
| 918 |
-
document.getElementById('modelName').value = model.name;
|
| 919 |
-
document.getElementById('modelEndpoint').value = model.endpoint;
|
| 920 |
-
document.getElementById('modelKey').value = model.key || '';
|
| 921 |
-
document.getElementById('modelStreaming').checked = model.streaming || false;
|
| 922 |
-
document.getElementById('editModelId').value = model.id;
|
| 923 |
-
|
| 924 |
-
// Change modal title and submit button text
|
| 925 |
-
document.querySelector('#addModelModal h3').textContent = 'Edit Custom Model';
|
| 926 |
-
document.querySelector('#addModelForm button[type="submit"]').textContent = 'Save Changes';
|
| 927 |
-
|
| 928 |
-
showAddModelModal();
|
| 929 |
-
}
|
| 930 |
-
|
| 931 |
-
// Hide add model modal
|
| 932 |
-
function hideAddModelModal() {
|
| 933 |
-
addModelModal.classList.add('hidden');
|
| 934 |
-
addModelForm.reset();
|
| 935 |
-
|
| 936 |
-
// Reset modal title and submit button text
|
| 937 |
-
document.querySelector('#addModelModal h3').textContent = 'Add Custom Model';
|
| 938 |
-
document.querySelector('#addModelForm button[type="submit"]').textContent = 'Add Model';
|
| 939 |
-
}
|
| 940 |
-
|
| 941 |
-
// Save chats to localStorage
|
| 942 |
-
function saveChats() {
|
| 943 |
-
localStorage.setItem('chats', JSON.stringify(chats));
|
| 944 |
-
}
|
| 945 |
-
|
| 946 |
-
// Toggle dark mode
|
| 947 |
-
function toggleDarkMode() {
|
| 948 |
-
const isDark = document.documentElement.classList.toggle('dark');
|
| 949 |
-
localStorage.setItem('darkMode', isDark);
|
| 950 |
-
}
|
| 951 |
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
|
| 955 |
-
|
| 956 |
-
|
| 957 |
-
|
| 958 |
-
|
| 959 |
-
|
| 960 |
-
|
| 961 |
-
|
| 962 |
-
|
| 963 |
-
// Hide API key modal
|
| 964 |
-
function hideApiKeyModal() {
|
| 965 |
-
apiKeyModal.classList.add('hidden');
|
| 966 |
-
apiKeyInput.value = '';
|
| 967 |
-
}
|
| 968 |
-
|
| 969 |
-
// Save API key
|
| 970 |
-
function saveApiKey(key) {
|
| 971 |
-
localStorage.setItem('openaiApiKey', key);
|
| 972 |
-
hideApiKeyModal();
|
| 973 |
-
}
|
| 974 |
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
|
| 982 |
-
|
| 983 |
-
|
| 984 |
-
|
| 985 |
-
|
| 986 |
-
|
| 987 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 988 |
}
|
| 989 |
-
});
|
| 990 |
-
sidebarToggle.addEventListener('click', toggleSidebar);
|
| 991 |
-
darkModeToggle.addEventListener('click', (e) => {
|
| 992 |
-
e.stopPropagation();
|
| 993 |
-
toggleDarkMode();
|
| 994 |
-
});
|
| 995 |
-
newChatBtn.addEventListener('click', (e) => {
|
| 996 |
-
e.preventDefault();
|
| 997 |
-
e.stopPropagation();
|
| 998 |
-
createNewChat();
|
| 999 |
-
|
| 1000 |
-
// Hide welcome message when new chat is created
|
| 1001 |
-
welcomeMessage.classList.add('hidden');
|
| 1002 |
|
| 1003 |
-
|
| 1004 |
-
|
| 1005 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1006 |
}
|
| 1007 |
-
});
|
| 1008 |
-
|
| 1009 |
-
quickStartBtn.addEventListener('click', (e) => {
|
| 1010 |
-
e.preventDefault();
|
| 1011 |
-
e.stopPropagation();
|
| 1012 |
-
createNewChat();
|
| 1013 |
|
| 1014 |
-
//
|
| 1015 |
-
|
| 1016 |
-
|
| 1017 |
-
|
| 1018 |
-
|
| 1019 |
-
|
| 1020 |
}
|
| 1021 |
-
});
|
| 1022 |
-
|
| 1023 |
-
messageForm.addEventListener('submit', (e) => {
|
| 1024 |
-
e.preventDefault();
|
| 1025 |
-
sendMessage();
|
| 1026 |
-
});
|
| 1027 |
-
|
| 1028 |
-
// Auto-resize textarea
|
| 1029 |
-
messageInput.addEventListener('input', () => {
|
| 1030 |
-
messageInput.style.height = 'auto';
|
| 1031 |
-
messageInput.style.height = `${messageInput.scrollHeight}px`;
|
| 1032 |
-
});
|
| 1033 |
-
|
| 1034 |
-
// Model dropdown
|
| 1035 |
-
modelDropdownBtn.addEventListener('click', (e) => {
|
| 1036 |
-
e.stopPropagation();
|
| 1037 |
-
modelDropdown.classList.toggle('hidden');
|
| 1038 |
-
});
|
| 1039 |
|
| 1040 |
-
|
| 1041 |
-
|
| 1042 |
-
if (!modelDropdown.contains(e.target) && e.target !== modelDropdownBtn && !e.target.closest('.model-option')) {
|
| 1043 |
-
modelDropdown.classList.add('hidden');
|
| 1044 |
}
|
| 1045 |
-
});
|
| 1046 |
|
| 1047 |
-
|
| 1048 |
-
|
| 1049 |
-
|
| 1050 |
-
|
| 1051 |
-
|
| 1052 |
-
|
| 1053 |
-
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
|
| 1064 |
-
|
| 1065 |
-
|
| 1066 |
-
const streaming = document.getElementById('modelStreaming').checked;
|
| 1067 |
-
const editModelId = document.getElementById('editModelId').value;
|
| 1068 |
-
|
| 1069 |
-
if (editModelId) {
|
| 1070 |
-
editCustomModel(editModelId, name, endpoint, key, streaming);
|
| 1071 |
-
} else {
|
| 1072 |
-
addCustomModel(name, endpoint, key);
|
| 1073 |
-
}
|
| 1074 |
-
});
|
| 1075 |
-
|
| 1076 |
-
// Delete chat
|
| 1077 |
-
closeDeleteModal.addEventListener('click', hideDeleteModal);
|
| 1078 |
-
cancelDelete.addEventListener('click', hideDeleteModal);
|
| 1079 |
-
confirmDelete.addEventListener('click', () => {
|
| 1080 |
-
if (chatToDelete) {
|
| 1081 |
-
deleteChat(chatToDelete);
|
| 1082 |
}
|
| 1083 |
-
});
|
| 1084 |
-
|
| 1085 |
-
// Settings button
|
| 1086 |
-
document.getElementById('settingsBtn').addEventListener('click', (e) => {
|
| 1087 |
-
e.stopPropagation();
|
| 1088 |
-
alert('Settings panel will be implemented here');
|
| 1089 |
-
// You can replace this with actual settings modal later
|
| 1090 |
-
});
|
| 1091 |
-
|
| 1092 |
-
// Handle message actions
|
| 1093 |
-
function handleMessageAction(action, messageIndex) {
|
| 1094 |
-
if (!currentChatId) return;
|
| 1095 |
|
| 1096 |
-
|
| 1097 |
-
|
| 1098 |
-
|
| 1099 |
-
|
| 1100 |
-
|
| 1101 |
-
if (
|
| 1102 |
-
|
| 1103 |
-
|
| 1104 |
-
|
| 1105 |
-
|
| 1106 |
-
|
| 1107 |
-
|
| 1108 |
-
|
| 1109 |
-
|
| 1110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1111 |
}
|
| 1112 |
-
} else if (action === 'edit') {
|
| 1113 |
-
// TODO: Implement edit functionality
|
| 1114 |
-
alert('Edit functionality will be implemented here');
|
| 1115 |
-
}
|
| 1116 |
-
}
|
| 1117 |
-
|
| 1118 |
-
// Initialize the app
|
| 1119 |
-
init();
|
| 1120 |
-
|
| 1121 |
-
// Add delegated event listener for message actions
|
| 1122 |
-
document.addEventListener('click', (e) => {
|
| 1123 |
-
const actionBtn = e.target.closest('.message-action');
|
| 1124 |
-
if (actionBtn) {
|
| 1125 |
-
e.preventDefault();
|
| 1126 |
-
e.stopPropagation();
|
| 1127 |
-
|
| 1128 |
-
const messageElement = actionBtn.closest('.flex.gap-4');
|
| 1129 |
-
const messageIndex = Array.from(messageElement.parentNode.children).indexOf(messageElement);
|
| 1130 |
-
const action = actionBtn.dataset.action;
|
| 1131 |
-
|
| 1132 |
-
handleMessageAction(action, messageIndex);
|
| 1133 |
}
|
| 1134 |
});
|
| 1135 |
</script>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Modern Scientific Calculator</title>
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
<style>
|
| 10 |
+
.sidebar {
|
| 11 |
+
transform: translateX(-100%);
|
| 12 |
+
transition: transform 0.3s ease;
|
| 13 |
}
|
| 14 |
+
.sidebar.open {
|
| 15 |
+
transform: translateX(0);
|
| 16 |
}
|
| 17 |
+
.calculator-btn {
|
| 18 |
+
transition: all 0.2s ease;
|
| 19 |
+
transform: scale(1);
|
| 20 |
}
|
| 21 |
+
.calculator-btn:active {
|
| 22 |
+
transform: scale(0.95);
|
| 23 |
}
|
| 24 |
+
.history-item:hover {
|
| 25 |
+
background-color: rgba(255, 255, 255, 0.1);
|
| 26 |
+
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
}
|
| 28 |
+
.scrollbar-hide::-webkit-scrollbar {
|
| 29 |
+
display: none;
|
|
|
|
|
|
|
|
|
|
| 30 |
}
|
| 31 |
+
.scrollbar-hide {
|
| 32 |
+
-ms-overflow-style: none;
|
| 33 |
+
scrollbar-width: none;
|
|
|
|
| 34 |
}
|
| 35 |
</style>
|
| 36 |
</head>
|
| 37 |
+
<body class="bg-gray-900 text-white min-h-screen flex items-center justify-center p-4">
|
| 38 |
+
<div class="w-full max-w-md">
|
| 39 |
+
<div class="bg-gray-800 rounded-2xl shadow-xl overflow-hidden">
|
| 40 |
+
<!-- Display Area -->
|
| 41 |
+
<div class="p-4">
|
| 42 |
+
<div class="flex justify-between items-center mb-2">
|
| 43 |
+
<h1 class="text-xl font-bold text-blue-400">Scientific Calculator</h1>
|
| 44 |
+
<div class="flex space-x-2">
|
| 45 |
+
<button id="sidebar-toggle" class="text-gray-300 hover:text-white">
|
| 46 |
+
<i class="fas fa-sliders-h"></i>
|
| 47 |
+
</button>
|
| 48 |
+
<button id="theme-toggle" class="text-gray-300 hover:text-white">
|
| 49 |
+
<i class="fas fa-moon"></i>
|
| 50 |
+
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
</div>
|
|
|
|
| 52 |
</div>
|
| 53 |
|
| 54 |
+
<div class="bg-gray-700 rounded-lg p-3 mb-2">
|
| 55 |
+
<div id="history-display" class="text-gray-400 text-sm h-6 overflow-x-auto whitespace-nowrap scrollbar-hide"></div>
|
| 56 |
+
<div id="display" class="text-right text-3xl font-semibold break-all">0</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
</div>
|
| 58 |
|
| 59 |
+
<div class="flex space-x-2 mb-2">
|
| 60 |
+
<button id="clear" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg flex-1 calculator-btn">AC</button>
|
| 61 |
+
<button id="backspace" class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded-lg calculator-btn">
|
| 62 |
+
<i class="fas fa-backspace"></i>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
</button>
|
| 64 |
+
<button id="percentage" class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded-lg calculator-btn">%</button>
|
| 65 |
+
<button id="divide" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg calculator-btn">÷</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
</div>
|
| 67 |
</div>
|
| 68 |
|
| 69 |
+
<!-- Main Calculator -->
|
| 70 |
+
<div class="grid grid-cols-4 gap-2 p-4">
|
| 71 |
+
<!-- Row 1 -->
|
| 72 |
+
<button data-value="7" class="bg-gray-700 hover:bg-gray-600 text-white text-xl p-3 rounded-lg calculator-btn">7</button>
|
| 73 |
+
<button data-value="8" class="bg-gray-700 hover:bg-gray-600 text-white text-xl p-3 rounded-lg calculator-btn">8</button>
|
| 74 |
+
<button data-value="9" class="bg-gray-700 hover:bg-gray-600 text-white text-xl p-3 rounded-lg calculator-btn">9</button>
|
| 75 |
+
<button id="multiply" class="bg-blue-500 hover:bg-blue-600 text-white text-xl p-3 rounded-lg calculator-btn">×</button>
|
| 76 |
+
|
| 77 |
+
<!-- Row 2 -->
|
| 78 |
+
<button data-value="4" class="bg-gray-700 hover:bg-gray-600 text-white text-xl p-3 rounded-lg calculator-btn">4</button>
|
| 79 |
+
<button data-value="5" class="bg-gray-700 hover:bg-gray-600 text-white text-xl p-3 rounded-lg calculator-btn">5</button>
|
| 80 |
+
<button data-value="6" class="bg-gray-700 hover:bg-gray-600 text-white text-xl p-3 rounded-lg calculator-btn">6</button>
|
| 81 |
+
<button id="subtract" class="bg-blue-500 hover:bg-blue-600 text-white text-xl p-3 rounded-lg calculator-btn">-</button>
|
| 82 |
+
|
| 83 |
+
<!-- Row 3 -->
|
| 84 |
+
<button data-value="1" class="bg-gray-700 hover:bg-gray-600 text-white text-xl p-3 rounded-lg calculator-btn">1</button>
|
| 85 |
+
<button data-value="2" class="bg-gray-700 hover:bg-gray-600 text-white text-xl p-3 rounded-lg calculator-btn">2</button>
|
| 86 |
+
<button data-value="3" class="bg-gray-700 hover:bg-gray-600 text-white text-xl p-3 rounded-lg calculator-btn">3</button>
|
| 87 |
+
<button id="add" class="bg-blue-500 hover:bg-blue-600 text-white text-xl p-3 rounded-lg calculator-btn">+</button>
|
| 88 |
+
|
| 89 |
+
<!-- Row 4 -->
|
| 90 |
+
<button id="decimal" class="bg-gray-700 hover:bg-gray-600 text-white text-xl p-3 rounded-lg calculator-btn">.</button>
|
| 91 |
+
<button data-value="0" class="bg-gray-700 hover:bg-gray-600 text-white text-xl p-3 rounded-lg calculator-btn">0</button>
|
| 92 |
+
<button id="equals" class="bg-green-500 hover:bg-green-600 text-white text-xl p-3 rounded-lg calculator-btn">=</button>
|
| 93 |
+
<button id="sqrt" class="bg-purple-500 hover:bg-purple-600 text-white text-xl p-3 rounded-lg calculator-btn">√</button>
|
| 94 |
+
|
| 95 |
+
<!-- Scientific Functions Row -->
|
| 96 |
+
<button id="power" class="bg-purple-500 hover:bg-purple-600 text-white text-xl p-3 rounded-lg calculator-btn">x^y</button>
|
| 97 |
+
<button id="sin" class="bg-purple-500 hover:bg-purple-600 text-white text-xl p-3 rounded-lg calculator-btn">sin</button>
|
| 98 |
+
<button id="cos" class="bg-purple-500 hover:bg-purple-600 text-white text-xl p-3 rounded-lg calculator-btn">cos</button>
|
| 99 |
+
<button id="tan" class="bg-purple-500 hover:bg-purple-600 text-white text-xl p-3 rounded-lg calculator-btn">tan</button>
|
| 100 |
+
|
| 101 |
+
<!-- More Functions Row -->
|
| 102 |
+
<button id="log" class="bg-purple-500 hover:bg-purple-600 text-white text-xl p-3 rounded-lg calculator-btn">log</button>
|
| 103 |
+
<button id="ln" class="bg-purple-500 hover:bg-purple-600 text-white text-xl p-3 rounded-lg calculator-btn">ln</button>
|
| 104 |
+
<button id="pi" class="bg-purple-500 hover:bg-purple-600 text-white text-xl p-3 rounded-lg calculator-btn">π</button>
|
| 105 |
+
<button id="factorial" class="bg-purple-500 hover:bg-purple-600 text-white text-xl p-3 rounded-lg calculator-btn">n!</button>
|
| 106 |
</div>
|
| 107 |
|
| 108 |
+
<!-- Scientific Sidebar -->
|
| 109 |
+
<div id="sidebar" class="sidebar absolute top-0 left-0 w-64 h-full bg-gray-800 z-10 p-4 overflow-y-auto">
|
| 110 |
+
<div class="flex justify-between items-center mb-4">
|
| 111 |
+
<h2 class="text-lg font-semibold text-blue-400">Scientific Functions</h2>
|
| 112 |
+
<button id="close-sidebar" class="text-gray-300 hover:text-white">
|
| 113 |
+
<i class="fas fa-times"></i>
|
| 114 |
+
</button>
|
| 115 |
</div>
|
| 116 |
|
| 117 |
+
<div class="grid grid-cols-2 gap-2 mb-4">
|
| 118 |
+
<button id="cube-root" class="bg-purple-500 hover:bg-purple-600 text-white p-2 rounded calculator-btn">∛</button>
|
| 119 |
+
<button id="nth-root" class="bg-purple-500 hover:bg-purple-600 text-white p-2 rounded calculator-btn">ⁿ√</button>
|
| 120 |
+
<button id="gcd" class="bg-purple-500 hover:bg-purple-600 text-white p-2 rounded calculator-btn">GCD</button>
|
| 121 |
+
<button id="lcm" class="bg-purple-500 hover:bg-purple-600 text-white p-2 rounded calculator-btn">LCM</button>
|
| 122 |
+
<button id="abs" class="bg-purple-500 hover:bg-purple-600 text-white p-2 rounded calculator-btn">|x|</button>
|
| 123 |
+
<button id="exp" class="bg-purple-500 hover:bg-purple-600 text-white p-2 rounded calculator-btn">e^x</button>
|
| 124 |
+
<button id="mod" class="bg-purple-500 hover:bg-purple-600 text-white p-2 rounded calculator-btn">mod</button>
|
| 125 |
+
<button id="rand" class="bg-purple-500 hover:bg-purple-600 text-white p-2 rounded calculator-btn">Rand</button>
|
| 126 |
</div>
|
| 127 |
|
| 128 |
<div class="mb-4">
|
| 129 |
+
<h3 class="text-md font-semibold text-blue-400 mb-2">Parentheses</h3>
|
| 130 |
+
<div class="grid grid-cols-2 gap-2">
|
| 131 |
+
<button id="open-paren" class="bg-blue-500 hover:bg-blue-600 text-white p-2 rounded calculator-btn">(</button>
|
| 132 |
+
<button id="close-paren" class="bg-blue-500 hover:bg-blue-600 text-white p-2 rounded calculator-btn">)</button>
|
| 133 |
+
</div>
|
|
|
|
|
|
|
| 134 |
</div>
|
|
|
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
<div class="mb-4">
|
| 137 |
+
<h3 class="text-md font-semibold text-blue-400 mb-2">Constants</h3>
|
| 138 |
+
<div class="grid grid-cols-2 gap-2">
|
| 139 |
+
<button id="e" class="bg-purple-500 hover:bg-purple-600 text-white p-2 rounded calculator-btn">e</button>
|
| 140 |
+
<button id="phi" class="bg-purple-500 hover:bg-purple-600 text-white p-2 rounded calculator-btn">φ</button>
|
| 141 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
</div>
|
| 144 |
+
|
| 145 |
+
<!-- History Panel -->
|
| 146 |
+
<div class="bg-gray-700 p-4 border-t border-gray-600">
|
| 147 |
+
<div class="flex justify-between items-center mb-2">
|
| 148 |
+
<h2 class="text-lg font-semibold text-blue-400">History</h2>
|
| 149 |
+
<button id="clear-history" class="text-gray-300 hover:text-white text-sm">Clear</button>
|
| 150 |
+
</div>
|
| 151 |
+
<div id="history-list" class="max-h-40 overflow-y-auto scrollbar-hide space-y-1"></div>
|
|
|
|
|
|
|
| 152 |
</div>
|
| 153 |
</div>
|
| 154 |
</div>
|
| 155 |
|
| 156 |
<script>
|
| 157 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 158 |
+
// Calculator state
|
| 159 |
+
let currentInput = '0';
|
| 160 |
+
let previousInput = '';
|
| 161 |
+
let operation = null;
|
| 162 |
+
let resetInput = false;
|
| 163 |
+
let calculationHistory = [];
|
| 164 |
+
|
| 165 |
+
// DOM elements
|
| 166 |
+
const display = document.getElementById('display');
|
| 167 |
+
const historyDisplay = document.getElementById('history-display');
|
| 168 |
+
const historyList = document.getElementById('history-list');
|
| 169 |
+
const themeToggle = document.getElementById('theme-toggle');
|
| 170 |
+
const clearHistoryBtn = document.getElementById('clear-history');
|
| 171 |
+
|
| 172 |
+
// Number buttons
|
| 173 |
+
const numberButtons = document.querySelectorAll('[data-value]');
|
| 174 |
+
|
| 175 |
+
// Operation buttons
|
| 176 |
+
const addButton = document.getElementById('add');
|
| 177 |
+
const subtractButton = document.getElementById('subtract');
|
| 178 |
+
const multiplyButton = document.getElementById('multiply');
|
| 179 |
+
const divideButton = document.getElementById('divide');
|
| 180 |
+
const equalsButton = document.getElementById('equals');
|
| 181 |
+
const clearButton = document.getElementById('clear');
|
| 182 |
+
const backspaceButton = document.getElementById('backspace');
|
| 183 |
+
const decimalButton = document.getElementById('decimal');
|
| 184 |
+
const percentageButton = document.getElementById('percentage');
|
| 185 |
+
|
| 186 |
+
// Scientific functions
|
| 187 |
+
const sqrtButton = document.getElementById('sqrt');
|
| 188 |
+
const powerButton = document.getElementById('power');
|
| 189 |
+
const sinButton = document.getElementById('sin');
|
| 190 |
+
const cosButton = document.getElementById('cos');
|
| 191 |
+
const tanButton = document.getElementById('tan');
|
| 192 |
+
const logButton = document.getElementById('log');
|
| 193 |
+
const lnButton = document.getElementById('ln');
|
| 194 |
+
const piButton = document.getElementById('pi');
|
| 195 |
+
const factorialButton = document.getElementById('factorial');
|
| 196 |
+
|
| 197 |
+
// Initialize
|
| 198 |
+
updateDisplay();
|
| 199 |
+
|
| 200 |
+
// Theme toggle
|
| 201 |
+
themeToggle.addEventListener('click', toggleTheme);
|
| 202 |
+
|
| 203 |
+
// Clear history
|
| 204 |
+
clearHistoryBtn.addEventListener('click', clearHistory);
|
| 205 |
+
|
| 206 |
+
// Number buttons
|
| 207 |
+
numberButtons.forEach(button => {
|
| 208 |
+
button.addEventListener('click', () => {
|
| 209 |
+
appendNumber(button.getAttribute('data-value'));
|
| 210 |
+
});
|
| 211 |
});
|
| 212 |
|
| 213 |
+
// Operation buttons
|
| 214 |
+
addButton.addEventListener('click', () => setOperation('+'));
|
| 215 |
+
subtractButton.addEventListener('click', () => setOperation('-'));
|
| 216 |
+
multiplyButton.addEventListener('click', () => setOperation('×'));
|
| 217 |
+
divideButton.addEventListener('click', () => setOperation('÷'));
|
| 218 |
+
equalsButton.addEventListener('click', compute);
|
| 219 |
+
clearButton.addEventListener('click', clear);
|
| 220 |
+
backspaceButton.addEventListener('click', backspace);
|
| 221 |
+
decimalButton.addEventListener('click', appendDecimal);
|
| 222 |
+
percentageButton.addEventListener('click', percentage);
|
| 223 |
+
|
| 224 |
+
// Sidebar toggle
|
| 225 |
+
const sidebarToggle = document.getElementById('sidebar-toggle');
|
| 226 |
+
const closeSidebar = document.getElementById('close-sidebar');
|
| 227 |
+
const sidebar = document.getElementById('sidebar');
|
| 228 |
+
|
| 229 |
+
sidebarToggle.addEventListener('click', () => {
|
| 230 |
+
sidebar.classList.add('open');
|
| 231 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
|
| 233 |
+
closeSidebar.addEventListener('click', () => {
|
| 234 |
+
sidebar.classList.remove('open');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
});
|
|
|
|
| 236 |
|
| 237 |
+
// Scientific functions
|
| 238 |
+
sqrtButton.addEventListener('click', () => appendFunction('√('));
|
| 239 |
+
powerButton.addEventListener('click', () => setOperation('^'));
|
| 240 |
+
sinButton.addEventListener('click', () => applyFunction('sin'));
|
| 241 |
+
cosButton.addEventListener('click', () => applyFunction('cos'));
|
| 242 |
+
tanButton.addEventListener('click', () => applyFunction('tan'));
|
| 243 |
+
logButton.addEventListener('click', () => applyFunction('log'));
|
| 244 |
+
lnButton.addEventListener('click', () => applyFunction('ln'));
|
| 245 |
+
piButton.addEventListener('click', appendPi);
|
| 246 |
+
factorialButton.addEventListener('click', factorial);
|
| 247 |
+
|
| 248 |
+
// Keyboard support
|
| 249 |
+
document.addEventListener('keydown', handleKeyboardInput);
|
| 250 |
+
|
| 251 |
+
// Functions
|
| 252 |
+
function toggleTheme() {
|
| 253 |
+
document.body.classList.toggle('bg-gray-100');
|
| 254 |
+
document.body.classList.toggle('text-gray-900');
|
| 255 |
+
document.body.classList.toggle('bg-gray-900');
|
| 256 |
+
document.body.classList.toggle('text-white');
|
| 257 |
|
| 258 |
+
const calculator = document.querySelector('.bg-gray-800');
|
| 259 |
+
calculator.classList.toggle('bg-gray-800');
|
| 260 |
+
calculator.classList.toggle('bg-white');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
+
const displayBg = document.querySelector('.bg-gray-700');
|
| 263 |
+
displayBg.classList.toggle('bg-gray-700');
|
| 264 |
+
displayBg.classList.toggle('bg-gray-100');
|
|
|
|
|
|
|
| 265 |
|
| 266 |
+
const buttons = document.querySelectorAll('.bg-gray-600, .bg-gray-700');
|
| 267 |
+
buttons.forEach(btn => {
|
| 268 |
+
btn.classList.toggle('bg-gray-600');
|
| 269 |
+
btn.classList.toggle('bg-gray-700');
|
| 270 |
+
btn.classList.toggle('bg-gray-200');
|
| 271 |
+
btn.classList.toggle('bg-gray-300');
|
| 272 |
+
btn.classList.toggle('text-white');
|
| 273 |
+
btn.classList.toggle('text-gray-900');
|
|
|
|
|
|
|
| 274 |
});
|
| 275 |
|
| 276 |
+
const icon = themeToggle.querySelector('i');
|
| 277 |
+
if (icon.classList.contains('fa-moon')) {
|
| 278 |
+
icon.classList.remove('fa-moon');
|
| 279 |
+
icon.classList.add('fa-sun');
|
| 280 |
+
} else {
|
| 281 |
+
icon.classList.remove('fa-sun');
|
| 282 |
+
icon.classList.add('fa-moon');
|
| 283 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
}
|
| 285 |
|
| 286 |
+
function clearHistory() {
|
| 287 |
+
calculationHistory = [];
|
| 288 |
+
updateHistoryList();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
}
|
| 290 |
|
| 291 |
+
function appendNumber(number) {
|
| 292 |
+
if (currentInput === '0' || resetInput) {
|
| 293 |
+
currentInput = number;
|
| 294 |
+
resetInput = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
} else {
|
| 296 |
+
currentInput += number;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
}
|
| 298 |
+
updateDisplay();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
}
|
| 300 |
|
| 301 |
+
function appendDecimal() {
|
| 302 |
+
if (resetInput) {
|
| 303 |
+
currentInput = '0.';
|
| 304 |
+
resetInput = false;
|
| 305 |
+
return;
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
if (currentInput.indexOf('.') === -1) {
|
| 309 |
+
currentInput += '.';
|
| 310 |
+
}
|
| 311 |
+
updateDisplay();
|
| 312 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
|
| 314 |
+
function appendPi() {
|
| 315 |
+
if (currentInput !== '0' && !resetInput) {
|
| 316 |
+
currentInput += Math.PI.toString();
|
| 317 |
+
} else {
|
| 318 |
+
currentInput = Math.PI.toString();
|
| 319 |
+
resetInput = false;
|
| 320 |
+
}
|
| 321 |
+
updateDisplay();
|
| 322 |
+
}
|
| 323 |
|
| 324 |
+
function setOperation(op) {
|
| 325 |
+
if (currentInput === '0' && previousInput && !resetInput) {
|
| 326 |
+
operation = op;
|
| 327 |
+
updateDisplay();
|
| 328 |
+
return;
|
| 329 |
+
}
|
| 330 |
|
| 331 |
+
if (operation && !resetInput) {
|
| 332 |
+
compute();
|
| 333 |
+
}
|
| 334 |
|
| 335 |
+
previousInput = currentInput;
|
| 336 |
+
operation = op;
|
| 337 |
+
resetInput = true;
|
| 338 |
+
updateDisplay();
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
function compute() {
|
| 342 |
+
try {
|
| 343 |
+
// Replace display symbols with JS-compatible ones
|
| 344 |
+
let expression = currentInput
|
| 345 |
+
.replace(/×/g, '*')
|
| 346 |
+
.replace(/÷/g, '/')
|
| 347 |
+
.replace(/√\(/g, 'Math.sqrt(')
|
| 348 |
+
.replace(/∛\(/g, 'Math.cbrt(')
|
| 349 |
+
.replace(/\^/g, '**')
|
| 350 |
+
.replace(/mod/g, '%')
|
| 351 |
+
.replace(/abs\(/g, 'Math.abs(')
|
| 352 |
+
.replace(/exp\(/g, 'Math.exp(')
|
| 353 |
+
.replace(/log\(/g, 'Math.log10(')
|
| 354 |
+
.replace(/ln\(/g, 'Math.log(')
|
| 355 |
+
.replace(/sin\(/g, 'Math.sin(')
|
| 356 |
+
.replace(/cos\(/g, 'Math.cos(')
|
| 357 |
+
.replace(/tan\(/g, 'Math.tan(')
|
| 358 |
+
.replace(/π/g, 'Math.PI')
|
| 359 |
+
.replace(/φ/g, '((1 + Math.sqrt(5)) / 2)')
|
| 360 |
+
.replace(/e/g, 'Math.E');
|
| 361 |
+
|
| 362 |
+
// Handle factorial (must be done after other replacements)
|
| 363 |
+
expression = expression.replace(/(\d+)!/g, function(match, num) {
|
| 364 |
+
let n = parseInt(num);
|
| 365 |
+
if (n < 0) return 'NaN';
|
| 366 |
+
let result = 1;
|
| 367 |
+
for (let i = 2; i <= n; i++) result *= i;
|
| 368 |
+
return result;
|
| 369 |
+
});
|
| 370 |
+
|
| 371 |
+
// Handle gcd and lcm (simple implementations)
|
| 372 |
+
expression = expression.replace(/gcd\(([^,]+),([^)]+)\)/g, function(match, a, b) {
|
| 373 |
+
a = parseFloat(a);
|
| 374 |
+
b = parseFloat(b);
|
| 375 |
+
while (b) [a, b] = [b, a % b];
|
| 376 |
+
return a;
|
| 377 |
+
});
|
| 378 |
+
|
| 379 |
+
expression = expression.replace(/lcm\(([^,]+),([^)]+)\)/g, function(match, a, b) {
|
| 380 |
+
a = parseFloat(a);
|
| 381 |
+
b = parseFloat(b);
|
| 382 |
+
return (a * b) / gcd(a, b);
|
| 383 |
+
});
|
| 384 |
+
|
| 385 |
+
// Evaluate the expression
|
| 386 |
+
const computation = eval(expression);
|
| 387 |
|
| 388 |
+
switch (operation) {
|
| 389 |
+
case '+':
|
| 390 |
+
computation = prev + current;
|
| 391 |
+
break;
|
| 392 |
+
case '-':
|
| 393 |
+
computation = prev - current;
|
| 394 |
+
break;
|
| 395 |
+
case '×':
|
| 396 |
+
computation = prev * current;
|
| 397 |
+
break;
|
| 398 |
+
case '÷':
|
| 399 |
+
computation = prev / current;
|
| 400 |
+
break;
|
| 401 |
+
case '^':
|
| 402 |
+
computation = Math.pow(prev, current);
|
| 403 |
+
break;
|
| 404 |
+
default:
|
| 405 |
+
return;
|
| 406 |
}
|
| 407 |
|
| 408 |
+
// Add to history
|
| 409 |
+
const historyEntry = {
|
| 410 |
+
expression: `${previousInput} ${operation} ${currentInput}`,
|
| 411 |
+
result: computation.toString()
|
|
|
|
|
|
|
|
|
|
| 412 |
};
|
| 413 |
+
calculationHistory.unshift(historyEntry);
|
| 414 |
+
if (calculationHistory.length > 10) {
|
| 415 |
+
calculationHistory.pop();
|
| 416 |
+
}
|
| 417 |
|
| 418 |
+
currentInput = computation.toString();
|
| 419 |
+
operation = null;
|
| 420 |
+
resetInput = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
|
| 422 |
+
updateDisplay();
|
| 423 |
+
updateHistoryList();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
|
| 426 |
+
function clear() {
|
| 427 |
+
currentInput = '0';
|
| 428 |
+
previousInput = '';
|
| 429 |
+
operation = null;
|
| 430 |
+
updateDisplay();
|
| 431 |
}
|
| 432 |
|
| 433 |
+
function backspace() {
|
| 434 |
+
if (currentInput.length === 1) {
|
| 435 |
+
currentInput = '0';
|
| 436 |
+
} else {
|
| 437 |
+
currentInput = currentInput.slice(0, -1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
}
|
| 439 |
+
updateDisplay();
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
function percentage() {
|
| 443 |
+
currentInput = (parseFloat(currentInput) / 100).toString();
|
| 444 |
+
updateDisplay();
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
function squareRoot() {
|
| 448 |
+
currentInput = Math.sqrt(parseFloat(currentInput)).toString();
|
| 449 |
+
updateDisplay();
|
| 450 |
+
|
| 451 |
+
// Add to history
|
| 452 |
+
const historyEntry = {
|
| 453 |
+
expression: `√(${currentInput})`,
|
| 454 |
+
result: currentInput
|
| 455 |
+
};
|
| 456 |
+
calculationHistory.unshift(historyEntry);
|
| 457 |
+
if (calculationHistory.length > 10) {
|
| 458 |
+
calculationHistory.pop();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
}
|
| 460 |
+
updateHistoryList();
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
function applyFunction(func) {
|
| 464 |
+
const value = parseFloat(currentInput);
|
| 465 |
+
let result;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 466 |
|
| 467 |
+
switch (func) {
|
| 468 |
+
case 'sin':
|
| 469 |
+
result = Math.sin(value);
|
| 470 |
+
break;
|
| 471 |
+
case 'cos':
|
| 472 |
+
result = Math.cos(value);
|
| 473 |
+
break;
|
| 474 |
+
case 'tan':
|
| 475 |
+
result = Math.tan(value);
|
| 476 |
+
break;
|
| 477 |
+
case 'log':
|
| 478 |
+
result = Math.log10(value);
|
| 479 |
+
break;
|
| 480 |
+
case 'ln':
|
| 481 |
+
result = Math.log(value);
|
| 482 |
+
break;
|
| 483 |
}
|
| 484 |
|
| 485 |
+
currentInput = result.toString();
|
| 486 |
+
|
| 487 |
+
// Add to history
|
| 488 |
+
const historyEntry = {
|
| 489 |
+
expression: `${func}(${value})`,
|
| 490 |
+
result: currentInput
|
| 491 |
+
};
|
| 492 |
+
calculationHistory.unshift(historyEntry);
|
| 493 |
+
if (calculationHistory.length > 10) {
|
| 494 |
+
calculationHistory.pop();
|
| 495 |
}
|
| 496 |
+
|
| 497 |
+
updateDisplay();
|
| 498 |
+
updateHistoryList();
|
| 499 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 500 |
|
| 501 |
+
// Sidebar buttons
|
| 502 |
+
document.getElementById('cube-root').addEventListener('click', () => appendFunction('∛('));
|
| 503 |
+
document.getElementById('nth-root').addEventListener('click', () => appendFunction('^(1/'));
|
| 504 |
+
document.getElementById('gcd').addEventListener('click', () => appendFunction('gcd('));
|
| 505 |
+
document.getElementById('lcm').addEventListener('click', () => appendFunction('lcm('));
|
| 506 |
+
document.getElementById('abs').addEventListener('click', () => appendFunction('abs('));
|
| 507 |
+
document.getElementById('exp').addEventListener('click', () => appendFunction('exp('));
|
| 508 |
+
document.getElementById('mod').addEventListener('click', () => appendFunction(' mod '));
|
| 509 |
+
document.getElementById('rand').addEventListener('click', randomNumber);
|
| 510 |
+
document.getElementById('open-paren').addEventListener('click', () => appendValue('('));
|
| 511 |
+
document.getElementById('close-paren').addEventListener('click', () => appendValue(')'));
|
| 512 |
+
document.getElementById('e').addEventListener('click', appendE);
|
| 513 |
+
document.getElementById('phi').addEventListener('click', appendPhi);
|
| 514 |
+
|
| 515 |
+
// New functions
|
| 516 |
+
function appendFunction(func) {
|
| 517 |
+
if (resetInput) {
|
| 518 |
+
currentInput = func;
|
| 519 |
+
resetInput = false;
|
| 520 |
+
} else {
|
| 521 |
+
currentInput += func;
|
| 522 |
}
|
| 523 |
+
updateDisplay();
|
| 524 |
}
|
|
|
|
| 525 |
|
| 526 |
+
function appendValue(value) {
|
| 527 |
+
if (currentInput === '0' || resetInput) {
|
| 528 |
+
currentInput = value;
|
| 529 |
+
resetInput = false;
|
| 530 |
+
} else {
|
| 531 |
+
currentInput += value;
|
| 532 |
+
}
|
| 533 |
+
updateDisplay();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 535 |
|
| 536 |
+
function appendE() {
|
| 537 |
+
if (currentInput !== '0' && !resetInput) {
|
| 538 |
+
currentInput += Math.E.toString();
|
| 539 |
+
} else {
|
| 540 |
+
currentInput = Math.E.toString();
|
| 541 |
+
resetInput = false;
|
| 542 |
+
}
|
| 543 |
+
updateDisplay();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 544 |
}
|
|
|
|
| 545 |
|
| 546 |
+
function appendPhi() {
|
| 547 |
+
const phi = (1 + Math.sqrt(5)) / 2;
|
| 548 |
+
if (currentInput !== '0' && !resetInput) {
|
| 549 |
+
currentInput += phi.toString();
|
| 550 |
+
} else {
|
| 551 |
+
currentInput = phi.toString();
|
| 552 |
+
resetInput = false;
|
| 553 |
+
}
|
| 554 |
+
updateDisplay();
|
| 555 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
|
| 557 |
+
function randomNumber() {
|
| 558 |
+
const random = Math.random();
|
| 559 |
+
if (currentInput !== '0' && !resetInput) {
|
| 560 |
+
currentInput += random.toString();
|
| 561 |
+
} else {
|
| 562 |
+
currentInput = random.toString();
|
| 563 |
+
resetInput = false;
|
| 564 |
+
}
|
| 565 |
+
updateDisplay();
|
| 566 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 567 |
|
| 568 |
+
function factorial() {
|
| 569 |
+
let num = parseInt(currentInput);
|
| 570 |
+
if (num < 0) {
|
| 571 |
+
currentInput = 'Error';
|
| 572 |
+
updateDisplay();
|
| 573 |
+
return;
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
let result = 1;
|
| 577 |
+
for (let i = 2; i <= num; i++) {
|
| 578 |
+
result *= i;
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
currentInput = result.toString();
|
| 582 |
+
|
| 583 |
+
// Add to history
|
| 584 |
+
const historyEntry = {
|
| 585 |
+
expression: `${num}!`,
|
| 586 |
+
result: currentInput
|
| 587 |
+
};
|
| 588 |
+
calculationHistory.unshift(historyEntry);
|
| 589 |
+
if (calculationHistory.length > 10) {
|
| 590 |
+
calculationHistory.pop();
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
updateDisplay();
|
| 594 |
+
updateHistoryList();
|
| 595 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 596 |
|
| 597 |
+
function updateDisplay() {
|
| 598 |
+
display.textContent = currentInput;
|
| 599 |
+
|
| 600 |
+
if (operation !== null) {
|
| 601 |
+
historyDisplay.textContent = `${previousInput} ${operation}`;
|
| 602 |
+
} else {
|
| 603 |
+
historyDisplay.textContent = '';
|
| 604 |
+
}
|
| 605 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 606 |
|
| 607 |
+
// Helper functions
|
| 608 |
+
function gcd(a, b) {
|
| 609 |
+
a = Math.abs(a);
|
| 610 |
+
b = Math.abs(b);
|
| 611 |
+
while (b) [a, b] = [b, a % b];
|
| 612 |
+
return a;
|
| 613 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
|
| 615 |
+
function lcm(a, b) {
|
| 616 |
+
return (a * b) / gcd(a, b);
|
|
|
|
|
|
|
| 617 |
}
|
|
|
|
| 618 |
|
| 619 |
+
function updateHistoryList() {
|
| 620 |
+
historyList.innerHTML = '';
|
| 621 |
+
|
| 622 |
+
calculationHistory.forEach((entry, index) => {
|
| 623 |
+
const historyItem = document.createElement('div');
|
| 624 |
+
historyItem.className = 'history-item bg-gray-600 rounded p-2 text-sm';
|
| 625 |
+
historyItem.innerHTML = `
|
| 626 |
+
<div class="text-gray-300">${entry.expression} =</div>
|
| 627 |
+
<div class="text-right font-semibold">${entry.result}</div>
|
| 628 |
+
`;
|
| 629 |
+
|
| 630 |
+
historyItem.addEventListener('click', () => {
|
| 631 |
+
currentInput = entry.result;
|
| 632 |
+
resetInput = true;
|
| 633 |
+
updateDisplay();
|
| 634 |
+
});
|
| 635 |
+
|
| 636 |
+
historyList.appendChild(historyItem);
|
| 637 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 638 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 639 |
|
| 640 |
+
function handleKeyboardInput(e) {
|
| 641 |
+
if (e.key >= '0' && e.key <= '9') {
|
| 642 |
+
appendNumber(e.key);
|
| 643 |
+
} else if (e.key === '.') {
|
| 644 |
+
appendDecimal();
|
| 645 |
+
} else if (e.key === '+') {
|
| 646 |
+
setOperation('+');
|
| 647 |
+
} else if (e.key === '-') {
|
| 648 |
+
setOperation('-');
|
| 649 |
+
} else if (e.key === '*') {
|
| 650 |
+
setOperation('×');
|
| 651 |
+
} else if (e.key === '/') {
|
| 652 |
+
setOperation('÷');
|
| 653 |
+
} else if (e.key === '^') {
|
| 654 |
+
setOperation('^');
|
| 655 |
+
} else if (e.key === '(') {
|
| 656 |
+
appendValue('(');
|
| 657 |
+
} else if (e.key === ')') {
|
| 658 |
+
appendValue(')');
|
| 659 |
+
} else if (e.key === 'Enter' || e.key === '=') {
|
| 660 |
+
compute();
|
| 661 |
+
} else if (e.key === 'Escape') {
|
| 662 |
+
clear();
|
| 663 |
+
} else if (e.key === 'Backspace') {
|
| 664 |
+
backspace();
|
| 665 |
+
} else if (e.key === '%') {
|
| 666 |
+
percentage();
|
| 667 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 668 |
}
|
| 669 |
});
|
| 670 |
</script>
|