i / index.html
abdullahalioo's picture
Update index.html
bf1e6e8 verified
Raw
History Blame Contribute Delete
40.5 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nexus AI Assistant</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary: #6366f1;
--primary-dark: #4f46e5;
--secondary: #f43f5e;
--dark: #1e293b;
--light: #f8fafc;
--gray: #94a3b8;
--success: #10b981;
--warning: #f59e0b;
--error: #ef4444;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
}
body {
height: 100%;
margin: 0;
padding: 0;
background-color: #f1f5f9;
color: var(--dark);
min-height: 100vh;
display: flex;
flex-direction: column;
-ms-overflow-style: none;
scrollbar-width: none;
}
::-webkit-scrollbar{
display: none;
}
*{
-ms-overflow-style: none;
scrollbar-width: none;
}
.app-container {
max-width: 1250px;
width: 100%;
margin: 0 auto;
padding: 1rem;
flex: 1;
display: flex;
flex-direction: column;
}
header {
position: sticky;
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 0;
margin-bottom: 1rem;
border-bottom: 1px solid #e2e8f0;
}
.logo {
display: flex;
align-items: center;
gap: 0.75rem;
font-weight: 700;
font-size: 1.5rem;
color: var(--primary);
}
.logo i {
font-size: 1.75rem;
}
.settings-btn {
background: none;
border: 2px solid #f9f4f4;
padding: 0.5rem 1rem;
font-size: 1.25rem;
color: #272727;
cursor: pointer;
transition: color 0.2s;
}
.settings-btn:hover {
color: var(--primary);
}
.main-content {
display: flex;
flex: 1;
gap: 1rem;
}
.sidebar {
width: 250px;
background-color: white;
border-radius: 1rem;
padding: 1rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
transition: transform 0.3s ease;
}
.sidebar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.sidebar-title {
font-weight: 600;
font-size: 1rem;
}
.new-chat-btn {
background-color: var(--primary);
color: white;
border: none;
border-radius: 0.5rem;
padding: 0.5rem;
cursor: pointer;
transition: background-color 0.2s;
}
.new-chat-btn:hover {
background-color: var(--primary-dark);
}
.chat-list {
flex: 1;
overflow-y: auto;
margin-bottom: 1rem;
}
.chat-item {
padding: 0.75rem;
border-radius: 0.5rem;
cursor: pointer;
margin-bottom: 0.25rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: background-color 0.2s;
}
.chat-item:hover {
background-color: #f1f5f9;
}
.chat-item.active {
background-color: #e0e7ff;
color: var(--primary);
font-weight: 500;
}
.chat-container {
flex: 1;
display: flex;
flex-direction: column;
background-color: white;
border-radius: 1rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.chat-header {
padding: 1rem;
background-color: var(--primary);
color: white;
display: flex;
justify-content: space-between;
align-items: center;
display: none;
}
.chat-title {
font-weight: 600;
font-size: 1.25rem;
}
.chat-status {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
}
.status-indicator {
width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
background-color: var(--success);
}
.chat-messages {
flex: 1;
padding: 1.5rem;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 1.5rem;
-ms-overflow-style: none;
scrollbar-width: none;
}
.message {
display: flex;
gap: 1rem;
max-width: 80%;
}
.message-user {
align-self: flex-end;
flex-direction: row-reverse;
}
.message-ai {
align-self: flex-start;
}
.avatar {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 1.25rem;
}
.avatar-user {
background-color: var(--primary);
color: white;
}
.avatar-ai {
background-color: #e0e7ff;
color: var(--primary);
}
.message-content {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.message-text {
padding: 0.75rem 1rem;
border-radius: 1rem;
line-height: 1.5;
font-size: 0.95rem;
}
.message-user .message-text {
background-color: var(--primary);
color: white;
border-bottom-right-radius: 0.25rem;
}
.message-ai .message-text {
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-bottom-left-radius: 0.25rem;
}
.message-meta {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.75rem;
color: var(--gray);
}
.message-ai .message-meta {
justify-content: flex-start;
}
.message-user .message-meta {
justify-content: flex-end;
}
.typing-indicator {
display: flex;
gap: 0.25rem;
padding: 0.75rem 1rem;
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 1rem;
border-bottom-left-radius: 0.25rem;
align-self: flex-start;
}
.typing-dot {
width: 0.5rem;
height: 0.5rem;
background-color: var(--gray);
border-radius: 50%;
animation: typingAnimation 1.4s infinite ease-in-out;
}
.typing-dot:nth-child(1) {
animation-delay: 0s;
}
.typing-dot:nth-child(2) {
animation-delay: 0.2s;
}
.typing-dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typingAnimation {
0%, 60%, 100% {
transform: translateY(0);
opacity: 0.6;
}
30% {
transform: translateY(-0.25rem);
opacity: 1;
}
}
.input-container {
padding: 1rem;
border-top: 1px solid #e2e8f0;
background-color: white;
}
.input-box {
display: flex;
gap: 0.5rem;
}
.input-field {
flex: 1;
padding: 0.75rem 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
font-size: 1rem;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
.input-field:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
}
.send-btn {
padding: 0 1.25rem;
background-color: var(--primary);
color: white;
border: none;
border-radius: 0.75rem;
cursor: pointer;
transition: background-color 0.2s;
}
.send-btn:hover {
background-color: var(--primary-dark);
}
.send-btn:disabled {
background-color: var(--gray);
cursor: not-allowed;
}
.action-buttons {
display: flex;
gap: 0.5rem;
margin-top: 0.5rem;
}
.action-btn {
padding: 0.5rem 0.75rem;
background-color: #f1f5f9;
border: none;
border-radius: 0.5rem;
font-size: 0.875rem;
color: var(--dark);
cursor: pointer;
transition: background-color 0.2s;
display: flex;
align-items: center;
gap: 0.25rem;
}
.action-btn:hover {
background-color: #e2e8f0;
}
.action-btn i {
font-size: 0.875rem;
}
.chat-footer {
text-align: center;
padding: 0.5rem;
font-size: 1rem;
color: var(--dark);
background-color: white;
border-top: 1px solid #e2e8f0;
position: sticky;
bottom: 0;
z-index: 10;
}
.settings-panel {
margin-top: 0;
padding-top: 20px;
border-radius: 20px 20px 0 0;
position: fixed;
top: 0;
right: -350px;
width: 350px;
border-radius: 0;
height: 100vh;
background-color: white;
box-shadow: -4px 0 6px -1px rgba(0, 0, 0, 0.1);
transition: right 0.3s ease;
z-index: 100;
padding: 1.5rem;
display: flex;
flex-direction: column;
}
.settings-panel.open {
right: 0;
}
.settings-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e2e8f0;
}
.settings-title {
font-weight: 600;
font-size: 1.25rem;
}
.close-settings {
background: none;
border: none;
font-size: 1.25rem;
color: var(--gray);
cursor: pointer;
}
.settings-content {
flex: 1;
overflow-y: auto;
}
.settings-section {
margin-bottom: 0rem;
}
.settings-section-title {
font-weight: 600;
margin-bottom: 0.75rem;
color: var(--dark);
}
.setting-item {
margin-bottom: 1rem;
}
.setting-label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
color: var(--dark);
}
.setting-select, .setting-input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
font-size: 0.875rem;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: var(--primary);
}
input:checked + .slider:before {
transform: translateX(26px);
}
.toggle-label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 99;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.overlay.open {
opacity: 1;
pointer-events: all;
}
.markdown-content a {
color: var(--primary);
text-decoration: none;
}
.markdown-content a:hover {
text-decoration: underline;
}
.markdown-content code {
background-color: #f1f5f9;
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-family: monospace;
font-size: 0.9em;
}
.markdown-content pre {
background-color: #f8fafc;
padding: 0.75rem;
border-radius: 0.5rem;
overflow-x: auto;
margin: 0.5rem 0;
border: 1px solid #e2e8f0;
}
.markdown-content pre code {
background-color: transparent;
padding: 0;
}
.markdown-content ul, .markdown-content ol {
padding-left: 1.5rem;
margin: 0.5rem 0;
}
.markdown-content blockquote {
border-left: 3px solid var(--gray);
padding-left: 1rem;
margin: 0.5rem 0;
color: var(--gray);
}
.suggestions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1rem;
}
.suggestion-chip {
padding: 0.5rem 0.75rem;
background-color: #f1f5f9;
border-radius: 1rem;
font-size: 0.875rem;
cursor: pointer;
transition: background-color 0.2s;
}
.suggestion-chip:hover {
background-color: #e2e8f0;
}
/* Mobile menu button */
.mobile-menu-btn {
display: none;
background: none;
border: 2px solid #f9f4f4;
border-radius: 4px;
font-size: 1.5rem;
color: var(--dark);
cursor: pointer;
padding: 0.5rem 1rem;
margin-right: 1rem;
}
@media (max-width: 768px) {
.app-container {
padding: 0rem;
}
.main-content {
flex-direction: column;
}
.sidebar {
position: fixed;
top: 0;
left: -100%;
width: 80%;
max-width: 300px;
height: 100vh;
z-index: 101;
border-radius: 0;
transition: left 0.3s ease;
}
.sidebar.open {
left: 0;
}
.chat-container {
border-radius: 0;
}
.message {
max-width: 90%;
}
.settings-panel {
width: 100%;
right: -100%;
}
.settings-panel.open {
right: 0;
}
.mobile-menu-btn {
display: block;
}
.mobile-menu-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 100;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.mobile-menu-overlay.open {
opacity: 1;
pointer-events: all;
}
}
/* Animation for new messages */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message {
animation: fadeIn 0.3s ease-out;
}
/* General panel styling */
.settings-panel {
max-width: 500px;
margin: 30px auto;
padding: 24px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
font-family: 'Segoe UI', sans-serif;
color: #333;
}
/* Content layout */
.settings-content {
display: flex;
flex-direction: column;
gap: 32px;
}
/* Section titles */
.settings-section-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
color: #222;
}
/* Section spacing */
.settings-section {
border-top: 1px solid #e0e0e0;
padding-top: 0px;
}
.settings-section:first-child {
border-top: none;
padding-top: 0;
}
/* Individual setting item */
.setting-item {
margin-bottom: 20px;
}
/* Label styles */
.setting-label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #555;
}
/* Dropdown select */
.setting-select {
width: 100%;
padding: 10px 14px;
font-size: 15px;
border-radius: 8px;
border: 1px solid #ccc;
background-color: #fff;
transition: border-color 0.3s;
}
.setting-select:focus {
border-color: #4caf50;
outline: none;
}
/* Toggle switch */
.toggle-label {
display: flex;
align-items: center;
gap: 12px;
font-weight: 500;
cursor: pointer;
color: #555;
}
/* Hide checkbox */
.toggle-label input[type="checkbox"] {
display: none;
}
/* Switch slider style */
.slider {
width: 44px;
height: 22px;
background-color: #ccc;
border-radius: 22px;
position: relative;
transition: background-color 0.3s ease;
}
.slider::before {
content: "";
position: absolute;
width: 18px;
height: 18px;
left: 2px;
top: 2px;
background-color: white;
border-radius: 50%;
transition: transform 0.3s ease;
}
/* Toggle ON */
.toggle-label input:checked + .slider {
background-color: #4caf50;
}
.toggle-label input:checked + .slider::before {
transform: translateX(22px);
}
</style>
</head>
<body>
<div class="app-container">
<header>
<button class="mobile-menu-btn" id="mobileMenuBtn">
</button>
<div class="logo">
<i class="fas fa-robot"></i>
<span>Orion AI</span>
</div>
<button class="settings-btn" id="settingsBtn">
<i class="fas fa-cog"></i>
</button>
</header>
<div class="main-content">
<div class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="sidebar-title">Chats</div>
<button class="new-chat-btn" id="newChatBtn">
<i class="fas fa-plus"></i>
</button>
</div>
<div class="chat-list" id="chatList">
<!-- Chat history items will be added here -->
</div>
<div class="chat-footer">
Created by Abdullah Ali
</div>
</div>
<div class="chat-container">
<div class="chat-header">
<div class="chat-title" id="currentChatTitle">New Chat</div>
<div class="chat-status">
<div class="status-indicator"></div>
<span>hi</span>
</div>
</div>
<div class="chat-messages" id="chatMessages">
<!-- Messages will be inserted here -->
</div>
<div class="input-container">
<div class="suggestions">
<div class="suggestion-chip">What can you do?</div>
<div class="suggestion-chip">Explain AI in simple terms</div>
</div>
<div class="input-box">
<input type="text" class="input-field" id="messageInput" placeholder="Ask me anything..." autocomplete="off">
<button class="send-btn" id="sendBtn">
<i class="fas fa-paper-plane"></i>
</button>
</div>
<div class="action-buttons">
<button class="action-btn">
<i class="fas fa-microphone"></i>
Voice
</button>
<button class="action-btn">
<i class="fas fa-image"></i>
Image
</button>
<button class="action-btn" id="clearChatBtn">
<i class="fas fa-trash-alt"></i>
Clear
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Mobile Menu Overlay -->
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
<!-- Settings Panel -->
<div class="settings-panel" id="settingsPanel">
<div class="settings-header">
<div class="settings-title">Settings</div>
<button class="close-settings" id="closeSettingsBtn">
<i class="fas fa-times"></i>
</button>
</div>
<div class="settings-content">
<div class="settings-section">
<div class="settings-section-title">AI Configuration</div>
<div class="setting-item">
<label class="setting-label">AI Model</label>
<select class="setting-select" id="aiModelSelect">
<option value="gpt-4">GPT-4 (Most capable)</option>
<option value="gpt-3.5">GPT-3.5 (Faster)</option>
<option value="claude-2">Claude 2</option>
<option value="llama-2">Llama 2</option>
</select>
</div>
<div class="setting-item">
<label class="setting-label">Response Style</label>
<select class="setting-select" id="responseStyleSelect">
<option value="default">Default</option>
<option value="concise">Concise</option>
<option value="detailed">Detailed</option>
<option value="creative">Creative</option>
</select>
</div>
</div>
<div class="settings-section">
<div class="settings-section-title">Appearance</div>
<div class="setting-item">
<label class="setting-label">Theme</label>
<select class="setting-select" id="themeSelect">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="system">System</option>
</select>
</div>
<div class="setting-item">
<label class="toggle-label">
<input type="checkbox" id="animationsToggle" checked>
<span class="slider"></span>
Animations
</label>
</div>
</div>
<div class="settings-section">
<div class="settings-section-title">Privacy</div>
<div class="setting-item">
<label class="toggle-label">
<input type="checkbox" id="historyToggle" checked>
<span class="slider"></span>
Save chat history
</label>
</div>
<div class="setting-item">
<label class="toggle-label">
<input type="checkbox" id="analyticsToggle">
<span class="slider"></span>
Share usage analytics
</label>
</div>
</div>
</div>
</div>
<div class="overlay" id="overlay"></div>
<script>
// DOM Elements
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
const chatMessages = document.getElementById('chatMessages');
const settingsBtn = document.getElementById('settingsBtn');
const settingsPanel = document.getElementById('settingsPanel');
const closeSettingsBtn = document.getElementById('closeSettingsBtn');
const overlay = document.getElementById('overlay');
const clearChatBtn = document.getElementById('clearChatBtn');
const suggestionChips = document.querySelectorAll('.suggestion-chip');
const newChatBtn = document.getElementById('newChatBtn');
const chatList = document.getElementById('chatList');
const currentChatTitle = document.getElementById('currentChatTitle');
// Corrected code for toggling the sidebar visibility
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
const mobileMenuOverlay = document.getElementById('mobileMenuOverlay');
// Toggle mobile menu
mobileMenuBtn.addEventListener('click', () => {
sidebar.classList.toggle('open');
mobileMenuOverlay.classList.toggle('open');
});
// Close mobile menu when overlay is clicked
mobileMenuOverlay.addEventListener('click', () => {
sidebar.classList.remove('open');
mobileMenuOverlay.classList.remove('open');
});
// Configuration
const PROXY_URL = "https://abdullahalioo-severr.hf.space/ask";
let lastController = null;
let isWaitingForResponse = false;
// Chat state
let chats = [];
let currentChatId = null;
// Initialize the app
function initApp() {
loadChats();
if (chats.length === 0) {
createNewChat();
} else {
loadChat(chats[0].id);
}
renderChatList();
}
// Load chats from localStorage
function loadChats() {
const savedChats = localStorage.getItem('orionAI_chats');
if (savedChats) {
chats = JSON.parse(savedChats);
}
}
// Save chats to localStorage
function saveChats() {
localStorage.setItem('orionAI_chats', JSON.stringify(chats));
}
// Create a new chat
function createNewChat() {
const newChat = {
id: Date.now().toString(),
title: 'New Chat',
messages: [], // Ensure this is an empty array
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
chats.unshift(newChat);
saveChats();
loadChat(newChat.id); // This will trigger the welcome message
renderChatList();
}
// Load a chat by ID
function loadChat(chatId) {
const chat = chats.find(c => c.id === chatId);
if (!chat) return;
currentChatId = chatId;
currentChatTitle.textContent = chat.title;
// Clear current messages
chatMessages.innerHTML = '';
// Load messages
if (chat.messages.length === 0) {
addWelcomeMessage(); // Add welcome message if no messages exist
} else {
chat.messages.forEach(msg => {
addMessage(msg.sender, msg.text, msg.isMarkdown, false);
});
}
// Update active state in chat list
document.querySelectorAll('.chat-item').forEach(item => {
item.classList.toggle('active', item.dataset.chatId === chatId);
});
scrollToBottom();
}
// Add welcome message
function addWelcomeMessage() {
addMessage('ai', `Hello! I'm Orion AI, your advanced assistant. How can I help you today?`);
}
// Render chat list
function renderChatList() {
chatList.innerHTML = '';
chats.forEach(chat => {
const chatItem = document.createElement('div');
chatItem.className = `chat-item ${chat.id === currentChatId ? 'active' : ''}`;
chatItem.dataset.chatId = chat.id;
chatItem.textContent = chat.title;
chatItem.addEventListener('click', () => {
loadChat(chat.id);
closeMobileMenu();
});
chatList.appendChild(chatItem);
});
}
// Update chat title based on first message
function updateChatTitle(messageText) {
if (!currentChatId) return;
const chat = chats.find(c => c.id === currentChatId);
if (!chat) return;
// Only update if it's still the default title
if (chat.title === 'New Chat') {
// Take first 30 characters of the message
const newTitle = messageText.length > 30
? messageText.substring(0, 30) + '...'
: messageText;
chat.title = newTitle;
currentChatTitle.textContent = newTitle;
saveChats();
renderChatList();
}
}
// Add a message to the current chat
function addMessage(sender, text, isMarkdown = true, saveToHistory = true) {
const messageDiv = document.createElement('div');
messageDiv.className = `message message-${sender}`;
const avatarDiv = document.createElement('div');
avatarDiv.className = `avatar avatar-${sender}`;
avatarDiv.innerHTML = sender === 'ai' ? '<i class="fas fa-robot"></i>' : '<i class="fas fa-user"></i>';
const contentDiv = document.createElement('div');
contentDiv.className = 'message-content';
const textDiv = document.createElement('div');
textDiv.className = 'message-text';
if (isMarkdown) {
textDiv.classList.add('markdown-content');
textDiv.innerHTML = text;
} else {
textDiv.textContent = text;
}
const metaDiv = document.createElement('div');
metaDiv.className = 'message-meta';
const timeSpan = document.createElement('span');
timeSpan.textContent = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
if (sender === 'ai') {
const modelSpan = document.createElement('span');
modelSpan.textContent = 'GPT-4';
metaDiv.appendChild(modelSpan);
}
metaDiv.appendChild(timeSpan);
contentDiv.appendChild(textDiv);
contentDiv.appendChild(metaDiv);
messageDiv.appendChild(avatarDiv);
messageDiv.appendChild(contentDiv);
chatMessages.appendChild(messageDiv);
scrollToBottom();
// Save to chat history
if (saveToHistory && currentChatId) {
const chat = chats.find(c => c.id === currentChatId);
if (chat) {
chat.messages.push({
sender,
text,
isMarkdown,
timestamp: new Date().toISOString()
});
chat.updatedAt = new Date().toISOString();
saveChats();
if (sender === 'user') {
updateChatTitle(text);
}
}
}
}
// Show typing indicator
function showTypingIndicator() {
const typingDiv = document.createElement('div');
typingDiv.className = 'typing-indicator';
typingDiv.id = 'typingIndicator';
for (let i = 0; i < 3; i++) {
const dot = document.createElement('div');
dot.className = 'typing-dot';
typingDiv.appendChild(dot);
}
chatMessages.appendChild(typingDiv);
scrollToBottom();
}
// Hide typing indicator
function hideTypingIndicator() {
const typingIndicator = document.getElementById('typingIndicator');
if (typingIndicator) {
typingIndicator.remove();
}
}
// Scroll to bottom of chat
function scrollToBottom() {
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// Send message to AI
async function sendMessage() {
const message = messageInput.value.trim();
if (!message || isWaitingForResponse) return;
// Add user message to chat
addMessage('user', message, false);
messageInput.value = '';
// Show typing indicator
showTypingIndicator();
isWaitingForResponse = true;
sendBtn.disabled = true;
// Abort previous request if exists
if (lastController) lastController.abort();
const controller = new AbortController();
lastController = controller;
try {
const startTime = performance.now();
const response = await fetch(PROXY_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question: message }),
signal: controller.signal
});
if (!response.ok) throw new Error(await response.text());
const data = await response.json();
const latency = performance.now() - startTime;
// Hide typing indicator
hideTypingIndicator();
// Add AI response to chat
addMessage('ai', data.answer);
} catch (err) {
if (err.name !== 'AbortError') {
hideTypingIndicator();
addMessage('ai', `<span style="color:var(--error)">Error: ${err.message || 'Failed to get response'}</span>`, false);
}
} finally {
isWaitingForResponse = false;
sendBtn.disabled = false;
}
}
// Clear current chat
function clearCurrentChat() {
if (!currentChatId) return;
const chat = chats.find(c => c.id === currentChatId);
if (chat) {
chat.messages = [];
chat.updatedAt = new Date().toISOString();
saveChats();
}
chatMessages.innerHTML = '';
addWelcomeMessage();
}
// Toggle mobile menu
function toggleMobileMenu() {
sidebarElement.classList.toggle('open');
mobileMenuOverlay.classList.toggle('open');
}
// Close mobile menu
function closeMobileMenu() {
sidebarElement.classList.remove('open');
mobileMenuOverlay.classList.remove('open');
}
// Event Listeners
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// Settings panel toggle
settingsBtn.addEventListener('click', () => {
settingsPanel.classList.add('open');
overlay.classList.add('open');
});
closeSettingsBtn.addEventListener('click', () => {
settingsPanel.classList.remove('open');
overlay.classList.remove('open');
});
overlay.addEventListener('click', () => {
settingsPanel.classList.remove('open');
overlay.classList.remove('open');
});
// Clear chat
clearChatBtn.addEventListener('click', clearCurrentChat);
// New chat button
newChatBtn.addEventListener('click', createNewChat);
// Suggestion chips
suggestionChips.forEach(chip => {
chip.addEventListener('click', () => {
messageInput.value = chip.textContent;
messageInput.focus();
});
});
// DOM Elements
const menuBtn = document.getElementById('menuBtn');
const sidebarElement = document.querySelector('.sidebar');
// Toggle sidebar visibility
menuBtn.addEventListener('click', () => {
sidebar.classList.toggle('open');
});
// Initialize app
function initApp() {
loadChats();
if (chats.length === 0) {
createNewChat(); // This should create a new chat and trigger the welcome message
} else {
loadChat(chats[0].id);
}
renderChatList();
}
messageInput.focus();
</script>
</body>
</html>