Spaces:
Running
Running
// Chat Configuration | |
const DEFAULT_API_KEY = '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'; | |
const DEFAULT_MODEL = 'deepseek/deepseek-chat-v3-0324:free'; | |
let chatConfig = { | |
apiKey: DEFAULT_API_KEY, | |
model: DEFAULT_MODEL, | |
siteUrl: window.location.origin, | |
siteName: 'Muhafiz AI Chat' | |
}; | |
// Conversation history | |
let conversationHistory = []; | |
// DOM Elements | |
const messageInput = document.getElementById('message-input'); | |
const sendButton = document.getElementById('send-btn'); | |
const chatMessages = document.getElementById('chat-messages'); | |
const moreButton = document.querySelector('.more-btn'); | |
const newChatButton = document.getElementById('new-chat-btn'); | |
const menuButton = document.querySelector('.menu-btn'); | |
const menuOverlay = document.getElementById('menuOverlay'); | |
const closeMenuButton = document.getElementById('closeMenuBtn'); | |
const conversationList = document.getElementById('conversationList'); | |
const seeMoreBtn = document.getElementById('seeMoreBtn'); | |
const themeToggleBtn = document.getElementById('themeToggleBtn'); | |
// Initialize chat | |
function initChat() { | |
// Load saved configuration and conversations | |
loadChatConfig(); | |
loadConversations(); | |
loadTheme(); | |
// Add event listeners | |
messageInput.addEventListener('input', handleInput); | |
messageInput.addEventListener('keypress', handleKeyPress); | |
sendButton.addEventListener('click', handleSend); | |
moreButton.addEventListener('click', handleMore); | |
newChatButton.addEventListener('click', handleNewChat); | |
menuButton.addEventListener('click', handleMenuOpen); | |
closeMenuButton.addEventListener('click', handleMenuClose); | |
seeMoreBtn.addEventListener('click', handleSeeMore); | |
themeToggleBtn.addEventListener('click', handleThemeToggle); | |
menuOverlay.addEventListener('click', (e) => { | |
if (e.target === menuOverlay) { | |
handleMenuClose(); | |
} | |
}); | |
// Disable send button initially | |
sendButton.disabled = true; | |
sendButton.style.opacity = '0.5'; | |
// Update status bar time | |
updateTime(); | |
setInterval(updateTime, 60000); | |
} | |
// Load conversations from localStorage | |
function loadConversations() { | |
const savedConversations = localStorage.getItem('conversations'); | |
if (savedConversations) { | |
conversationHistory = JSON.parse(savedConversations); | |
// If there are saved conversations, update the chat interface | |
if (conversationHistory.length > 0) { | |
const currentConversation = conversationHistory[0]; | |
if (currentConversation.messages.length > 0) { | |
// Clear welcome message | |
while (chatMessages.firstChild) { | |
chatMessages.removeChild(chatMessages.firstChild); | |
} | |
// Load messages from the current conversation | |
currentConversation.messages.forEach(msg => { | |
addMessage(msg.content, msg.type); | |
}); | |
} | |
} | |
} | |
updateConversationList(); | |
} | |
// Save conversations to localStorage | |
function saveConversations() { | |
localStorage.setItem('conversations', JSON.stringify(conversationHistory)); | |
} | |
// Update conversation list in menu | |
function updateConversationList() { | |
conversationList.innerHTML = ''; | |
const recentConversations = conversationHistory.slice(0, 4); | |
if (recentConversations.length === 0) { | |
const emptyState = document.createElement('div'); | |
emptyState.className = 'empty-conversations'; | |
emptyState.textContent = 'No conversations yet'; | |
conversationList.appendChild(emptyState); | |
return; | |
} | |
recentConversations.forEach(conv => { | |
const item = document.createElement('div'); | |
item.className = 'conversation-item'; | |
item.innerHTML = ` | |
<i class="fas fa-comment"></i> | |
<span>${conv.title}</span> | |
<span class="message-count">${conv.messages.length} messages</span> | |
`; | |
item.addEventListener('click', () => loadConversation(conv.id)); | |
conversationList.appendChild(item); | |
}); | |
} | |
// Handle new chat | |
async function handleNewChat() { | |
// If there's an existing conversation, generate a title for it | |
if (conversationHistory.length > 0 && conversationHistory[0].messages.length > 0) { | |
const currentConversation = conversationHistory[0]; | |
try { | |
const messages = currentConversation.messages.map(msg => `${msg.type}: ${msg.content}`).join('\n'); | |
const titlePrompt = `Please summarize this conversation in 4-5 words:\n${messages}`; | |
// Show loading state for the conversation title | |
const tempTitle = 'Generating title...'; | |
currentConversation.title = tempTitle; | |
updateConversationList(); | |
// Get AI-generated title | |
const response = await fetchBotResponse(titlePrompt); | |
currentConversation.title = response.slice(0, 40).trim(); | |
saveConversations(); | |
updateConversationList(); | |
} catch (error) { | |
console.error('Error generating title:', error); | |
// Fallback to first message if title generation fails | |
currentConversation.title = currentConversation.messages[0].content.slice(0, 30) + '...'; | |
saveConversations(); | |
updateConversationList(); | |
} | |
} | |
// Create new conversation | |
const newConversation = { | |
id: Date.now(), | |
title: 'New Chat', | |
messages: [] | |
}; | |
// Add to conversations array | |
conversationHistory.unshift(newConversation); | |
saveConversations(); | |
updateConversationList(); | |
// Clear chat messages | |
while (chatMessages.firstChild) { | |
chatMessages.removeChild(chatMessages.firstChild); | |
} | |
// Add welcome message | |
const welcomeMessage = document.createElement('div'); | |
welcomeMessage.className = 'welcome-message'; | |
welcomeMessage.innerHTML = ` | |
<img src="images/welcome-logo.png" alt="Muhafiz AI Logo" class="welcome-logo"> | |
<h2>Hi, I'm Muhafiz AI.</h2> | |
<p>Your digital safety assistant dedicated to combating cyber threats and providing support.</p> | |
<div class="context-info">Ask me about cyber security, online protection, or digital safety concerns.</div> | |
`; | |
chatMessages.appendChild(welcomeMessage); | |
// Clear input | |
messageInput.value = ''; | |
handleInput({ target: messageInput }); | |
// Focus input | |
messageInput.focus(); | |
} | |
// Handle see more button click | |
function handleSeeMore() { | |
// Create full conversation list overlay | |
const overlay = document.createElement('div'); | |
overlay.className = 'conversation-overlay'; | |
const content = document.createElement('div'); | |
content.className = 'conversation-overlay-content'; | |
// Add header | |
content.innerHTML = ` | |
<div class="overlay-header"> | |
<h2>All Conversations</h2> | |
<button class="close-overlay-btn"><i class="fas fa-times"></i></button> | |
</div> | |
<div class="conversation-list-full"></div> | |
`; | |
// Add all conversations | |
const listContainer = content.querySelector('.conversation-list-full'); | |
conversationHistory.forEach(conv => { | |
const item = document.createElement('div'); | |
item.className = 'conversation-item'; | |
item.innerHTML = ` | |
<i class="fas fa-comment"></i> | |
<div class="conversation-details"> | |
<span class="conversation-title">${conv.title}</span> | |
<span class="message-count">${conv.messages.length} messages</span> | |
<span class="conversation-date">${new Date(conv.id).toLocaleDateString()}</span> | |
</div> | |
`; | |
item.addEventListener('click', () => { | |
loadConversation(conv.id); | |
overlay.remove(); | |
}); | |
listContainer.appendChild(item); | |
}); | |
// Add close button handler | |
overlay.appendChild(content); | |
content.querySelector('.close-overlay-btn').addEventListener('click', () => { | |
overlay.remove(); | |
}); | |
// Add overlay click handler | |
overlay.addEventListener('click', (e) => { | |
if (e.target === overlay) { | |
overlay.remove(); | |
} | |
}); | |
document.body.appendChild(overlay); | |
} | |
// Load specific conversation | |
function loadConversation(id) { | |
const conversation = conversationHistory.find(conv => conv.id === id); | |
if (conversation) { | |
// Clear current chat | |
while (chatMessages.firstChild) { | |
chatMessages.removeChild(chatMessages.firstChild); | |
} | |
// Load conversation messages | |
conversation.messages.forEach(msg => { | |
addMessage(msg.content, msg.type); | |
}); | |
// Close menu | |
handleMenuClose(); | |
} | |
} | |
// Handle input changes | |
function handleInput(e) { | |
const isEmpty = !e.target.value.trim(); | |
sendButton.style.opacity = isEmpty ? '0.5' : '1'; | |
sendButton.disabled = isEmpty; | |
} | |
// Handle enter key | |
function handleKeyPress(e) { | |
if (e.key === 'Enter' && !e.shiftKey) { | |
e.preventDefault(); | |
handleSend(); | |
} | |
} | |
// Handle send button click | |
async function handleSend() { | |
const message = messageInput.value.trim(); | |
if (!message) return; | |
// Add user message to chat and conversation history | |
addMessage(message, 'user'); | |
if (conversationHistory.length === 0) { | |
handleNewChat(); | |
} | |
// Update current conversation | |
const currentConversation = conversationHistory[0]; | |
currentConversation.messages.push({ type: 'user', content: message }); | |
if (currentConversation.messages.length === 1) { | |
// Update conversation title with first message | |
currentConversation.title = message.slice(0, 30) + (message.length > 30 ? '...' : ''); | |
updateConversationList(); | |
} | |
saveConversations(); | |
messageInput.value = ''; | |
handleInput({ target: messageInput }); | |
try { | |
// Show typing indicator | |
showTypingIndicator(); | |
// Get bot response | |
const response = await fetchBotResponse(message); | |
// Remove typing indicator and add bot message | |
removeTypingIndicator(); | |
addMessage(response, 'bot'); | |
// Update conversation history | |
currentConversation.messages.push({ type: 'bot', content: response }); | |
saveConversations(); | |
} catch (error) { | |
console.error('Error:', error); | |
removeTypingIndicator(); | |
showError(error.message || 'Failed to get response. Please try again.'); | |
} | |
} | |
// Add message to chat | |
function addMessage(content, type) { | |
// Create message element | |
const messageDiv = document.createElement('div'); | |
messageDiv.className = `message ${type}-message`; | |
// Get current time | |
const now = new Date(); | |
const hours = now.getHours().toString().padStart(2, '0'); | |
const minutes = now.getMinutes().toString().padStart(2, '0'); | |
const timeString = `${hours}:${minutes}`; | |
if (type === 'user') { | |
messageDiv.innerHTML = ` | |
<div class="question-bubble">${content}</div> | |
<div class="message-time">${timeString}</div> | |
`; | |
} else { | |
messageDiv.innerHTML = ` | |
<div class="answer-container"> | |
<div class="bot-icon"> | |
<img src="images/bot-message-logo.png" alt="Muhafiz AI"> | |
</div> | |
<div class="answer-content">${formatBotResponse(content)}</div> | |
</div> | |
<div class="message-time">${timeString}</div> | |
`; | |
} | |
// Remove welcome message if present | |
const welcomeMessage = document.querySelector('.welcome-message'); | |
if (welcomeMessage) { | |
welcomeMessage.remove(); | |
} | |
chatMessages.appendChild(messageDiv); | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
// Add message to current conversation | |
if (conversationHistory.length === 0) { | |
handleNewChat(); | |
} | |
conversationHistory[0].messages.push({ type, content }); | |
saveConversations(); | |
} | |
// Format bot response with proper styling | |
function formatBotResponse(content) { | |
// Split content into sections | |
const sections = content.split('\n\n'); | |
let formattedContent = ''; | |
let inList = false; | |
sections.forEach(section => { | |
// Handle main headings (e.g., "Types of AI:") | |
if (section.match(/^[A-Z][^:]+:$/)) { | |
if (inList) { | |
formattedContent += '</div>'; | |
inList = false; | |
} | |
formattedContent += `<h2 class="response-heading">${section}</h2>`; | |
return; | |
} | |
// Handle definitions (text with parentheses) | |
if (section.includes('(') && section.includes(')')) { | |
section = section.replace(/([A-Za-z\s]+)\s*\((.*?)\)/, '<div class="definition"><span class="term">$1</span> <span class="definition-text">($2)</span></div>'); | |
} | |
// Handle numbered lists | |
if (section.match(/^\d+\./)) { | |
if (!inList) { | |
formattedContent += '<div class="numbered-list">'; | |
inList = true; | |
} | |
const [number, ...rest] = section.split('.'); | |
const content = rest.join('.').trim(); | |
// Check if the list item has a dash/hyphen definition | |
if (content.includes('–')) { | |
const [term, definition] = content.split('–').map(s => s.trim()); | |
formattedContent += ` | |
<div class="list-item"> | |
<span class="number">${number}.</span> | |
<div class="list-content"> | |
<span class="list-term">${term}</span> | |
<span class="list-definition">– ${definition}</span> | |
</div> | |
</div>`; | |
} else { | |
formattedContent += ` | |
<div class="list-item"> | |
<span class="number">${number}.</span> | |
<div class="list-content">${content}</div> | |
</div>`; | |
} | |
return; | |
} | |
// Close list if we're not processing a list item | |
if (inList) { | |
formattedContent += '</div>'; | |
inList = false; | |
} | |
// Regular paragraphs | |
formattedContent += `<p class="response-paragraph">${section}</p>`; | |
}); | |
// Close any open list | |
if (inList) { | |
formattedContent += '</div>'; | |
} | |
return formattedContent; | |
} | |
// Show typing indicator | |
function showTypingIndicator() { | |
const indicator = document.createElement('div'); | |
indicator.classList.add('message', 'bot-message', 'typing-indicator'); | |
indicator.innerHTML = '<span></span><span></span><span></span>'; | |
chatMessages.appendChild(indicator); | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
} | |
// Remove typing indicator | |
function removeTypingIndicator() { | |
const indicator = document.querySelector('.typing-indicator'); | |
if (indicator) { | |
indicator.remove(); | |
} | |
} | |
// Show error message | |
function showError(message) { | |
const errorDiv = document.createElement('div'); | |
errorDiv.classList.add('message', 'error-message'); | |
errorDiv.textContent = message; | |
chatMessages.appendChild(errorDiv); | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
// Remove error after 5 seconds | |
setTimeout(() => { | |
errorDiv.remove(); | |
}, 5000); | |
} | |
// Fetch bot response from OpenRouter API | |
async function fetchBotResponse(userMessage) { | |
try { | |
// Check for API key | |
const apiKey = chatConfig.apiKey || localStorage.getItem('openRouterApiKey'); | |
if (!apiKey) { | |
throw new Error('Please set up your API key in the admin panel'); | |
} | |
// Format messages for the API | |
const messages = [ | |
{ | |
role: 'system', | |
content: `You are Muhafiz AI, a digital safety assistant by the Mohsin Kamil Foundation. Your role is strictly limited to: | |
1. Combating cyber threats | |
2. Providing emotional support for cybercrime victims | |
3. Guiding users through digital safety solutions | |
4. Handling cybersecurity incidents | |
5. Addressing online harassment and blackmail | |
6. Assisting with content removal and legal reporting | |
IMPORTANT: You must NEVER respond to questions outside these areas. For any off-topic questions (e.g., general AI, weather, news, etc.), respond with: | |
"I am Muhafiz AI, focused exclusively on digital safety and cybersecurity. I cannot assist with questions about [topic]. However, I'm here to help if you need support with: | |
- Cybersecurity incidents | |
- Online harassment or blackmail | |
- Account hacking | |
- Content removal | |
- Digital safety guidance | |
- Reporting cybercrimes | |
Would you like assistance with any of these issues?" | |
Response Framework: | |
1. Emotional Check-In: | |
- Start with empathy: "This sounds incredibly stressful. How are you feeling right now?" | |
- Reassure: "We'll tackle this together—you're not alone." | |
2. URL Safety Analysis: | |
- If a user shares a suspicious link: | |
- "Let's check this link safely. First check the Link looking formating feel Safe or Not. Avoid clicking it. Use tools like Google Safe Browsing or VirusTotal to scan it." | |
- "If you've already clicked it, change your passwords immediately and enable 2FA." | |
3. Tech Support for Hacking: | |
- Account Security: | |
- "Let's secure your account: 1) Change passwords, 2) Enable 2FA, 3) Check login activity." | |
- Phishing Guidance: | |
- "Never share OTPs or passwords. Legitimate organizations won't ask for these." | |
- Breach Response: | |
- "Check if your data was leaked at Have I Been Pwned?" | |
4. Legal & Content Removal: | |
- "Under Pakistan's PECA 2016, blackmail is punishable. Report to FIA Cyber Crime or call 1991." | |
- "To remove content from platforms: | |
- Google: Content Removal Request | |
- Meta (FB/IG): Reporting Tool" | |
5. Founder's Role: | |
- "Abdullah Tarar, our founder, is a Certified Ethical Hacker. He's helped many in your situation—no judgment, only solutions. Chat directly: WhatsApp or call 0342-4844392." | |
Always end responses with: | |
- "What else can I do to help you feel safer?" | |
- "You're in control. Let's take the next step."` | |
} | |
]; | |
// Add conversation history if available | |
if (conversationHistory.length > 0) { | |
const currentConversation = conversationHistory[0]; | |
if (currentConversation.messages) { | |
// Get all messages from the current conversation for context | |
currentConversation.messages.forEach(msg => { | |
messages.push({ | |
role: msg.type === 'user' ? 'user' : 'assistant', | |
content: msg.content | |
}); | |
}); | |
} | |
} | |
// Add the new message | |
messages.push({ | |
role: 'user', | |
content: userMessage | |
}); | |
console.log('Sending request to OpenRouter with context:', { | |
model: chatConfig.model, | |
messageCount: messages.length, | |
apiKeySet: !!apiKey | |
}); | |
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { | |
method: 'POST', | |
headers: { | |
'Authorization': `Bearer ${apiKey}`, | |
'HTTP-Referer': chatConfig.siteUrl, | |
'X-Title': chatConfig.siteName, | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
model: chatConfig.model || DEFAULT_MODEL, | |
messages: messages, | |
temperature: 0.7, | |
max_tokens: 2000 | |
}) | |
}); | |
const data = await response.json(); | |
if (!response.ok) { | |
console.error('OpenRouter API Error:', data); | |
throw new Error(data.error?.message || `HTTP error! status: ${response.status}`); | |
} | |
console.log('OpenRouter API Response:', data); | |
if (!data.choices?.[0]?.message?.content) { | |
throw new Error('Invalid response format from API'); | |
} | |
return data.choices[0].message.content; | |
} catch (error) { | |
console.error('API Error:', error); | |
throw new Error(`Failed to get response: ${error.message}`); | |
} | |
} | |
// Handle more options | |
function handleMore() { | |
// Implement more options menu | |
console.log('More options clicked'); | |
} | |
// Handle menu open | |
function handleMenuOpen() { | |
menuOverlay.classList.add('active'); | |
document.body.style.overflow = 'hidden'; | |
} | |
// Handle menu close | |
function handleMenuClose() { | |
menuOverlay.classList.remove('active'); | |
document.body.style.overflow = ''; | |
} | |
// Update time in status bar | |
function updateTime() { | |
const timeElement = document.querySelector('.time'); | |
const now = new Date(); | |
const hours = now.getHours().toString().padStart(2, '0'); | |
const minutes = now.getMinutes().toString().padStart(2, '0'); | |
timeElement.textContent = `${hours}:${minutes}`; | |
} | |
// Load chat configuration | |
function loadChatConfig() { | |
// Try to load API key from localStorage if no default is set | |
if (!DEFAULT_API_KEY) { | |
const storedApiKey = localStorage.getItem('openRouterApiKey'); | |
if (storedApiKey) { | |
chatConfig.apiKey = storedApiKey; | |
} | |
} | |
// Load model from localStorage or use default | |
chatConfig.model = localStorage.getItem('selectedModel') || DEFAULT_MODEL; | |
// Log configuration (without API key) | |
console.log('Chat Configuration:', { | |
...chatConfig, | |
apiKey: chatConfig.apiKey ? '[SET]' : '[NOT SET]', | |
model: chatConfig.model | |
}); | |
} | |
// Load theme from localStorage | |
function loadTheme() { | |
const savedTheme = localStorage.getItem('theme') || 'light'; | |
document.documentElement.setAttribute('data-theme', savedTheme); | |
updateThemeIcon(savedTheme); | |
} | |
// Handle theme toggle | |
function handleThemeToggle() { | |
const currentTheme = document.documentElement.getAttribute('data-theme'); | |
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; | |
document.documentElement.setAttribute('data-theme', newTheme); | |
localStorage.setItem('theme', newTheme); | |
updateThemeIcon(newTheme); | |
} | |
// Update theme toggle icon | |
function updateThemeIcon(theme) { | |
const icon = themeToggleBtn.querySelector('i'); | |
if (theme === 'dark') { | |
icon.className = 'fas fa-sun'; | |
themeToggleBtn.title = 'Switch to light mode'; | |
} else { | |
icon.className = 'fas fa-moon'; | |
themeToggleBtn.title = 'Switch to dark mode'; | |
} | |
} | |
// Initialize chat when DOM is loaded | |
document.addEventListener('DOMContentLoaded', initChat); |