<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rabbit AI Beta</title>
<script defer src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="style.css" />
<!-- Firebase SDK -->
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js";
import { getAuth, GoogleAuthProvider, signInWithPopup, signOut, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-auth.js";
import { getFirestore, doc, setDoc, getDoc, collection, addDoc, deleteDoc, onSnapshot, Timestamp } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-firestore.js";
import { firebaseConfig } from "./config.js";
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const provider = new GoogleAuthProvider();
// Array to store chat tabs
let chatTabs = [];
let currentTabIndex = 0;
let unsubscribeFromTabs = null;
let autoSaveInterval = null;
// Function to save tabs to Firestore
async function saveTabsToFirestore(userId) {
if (!userId) return;
try {
const userDoc = doc(db, 'users', userId);
// Ensure each tab has a unique ID and proper structure
const tabsToSave = chatTabs.map(tab => ({
id: tab.id || crypto.randomUUID(), // Generate unique ID if not exists
title: tab.title || 'New Chat',
messages: tab.messages || [],
createdAt: tab.createdAt || Timestamp.now(),
lastUpdated: Timestamp.now()
}));
await setDoc(userDoc, {
tabs: tabsToSave,
lastUpdated: Timestamp.now()
});
} catch (error) {
console.error('Error saving tabs:', error);
}
}
// Function to start auto-save
function startAutoSave(userId) {
if (autoSaveInterval) {
clearInterval(autoSaveInterval);
}
autoSaveInterval = setInterval(() => {
if (auth.currentUser) {
saveTabsToFirestore(userId);
}
}, 60000); // Save every minute
}
// Function to stop auto-save
function stopAutoSave() {
if (autoSaveInterval) {
clearInterval(autoSaveInterval);
autoSaveInterval = null;
}
}
// Function to load tabs from Firestore
async function loadTabsFromFirestore(userId) {
if (!userId) return;
if (unsubscribeFromTabs) {
unsubscribeFromTabs();
}
try {
const userDoc = doc(db, 'users', userId);
// First, get the current state
const docSnap = await getDoc(userDoc);
if (docSnap.exists()) {
const data = docSnap.data();
chatTabs = data.tabs || [];
updateChatList();
if (chatTabs.length > 0) {
loadChatMessages(currentTabIndex);
}
}
// Start auto-save
startAutoSave(userId);
// Set up real-time listener
unsubscribeFromTabs = onSnapshot(userDoc, (docSnap) => {
if (docSnap.exists()) {
const data = docSnap.data();
const newTabs = data.tabs || [];
if (JSON.stringify(chatTabs) !== JSON.stringify(newTabs)) {
chatTabs = newTabs;
updateChatList();
if (chatTabs.length > 0) {
loadChatMessages(currentTabIndex);
}
}
}
});
} catch (error) {
console.error('Error loading tabs:', error);
}
}
// Function to clear all tabs
function clearAllTabs() {
chatTabs = [];
currentTabIndex = 0;
updateChatList();
document.getElementById('chat').innerHTML = '';
// Unsubscribe from real-time updates when clearing tabs
if (unsubscribeFromTabs) {
unsubscribeFromTabs();
unsubscribeFromTabs = null;
}
// Stop auto-save
stopAutoSave();
}
// Function to update the chat list UI
function updateChatList() {
const chatList = document.getElementById('chatList');
chatList.innerHTML = '';
if (!auth.currentUser) {
// Show message when not logged in
const message = document.createElement('div');
message.className = 'text-center text-gray-500 p-4';
message.textContent = 'Log in to see your chats';
chatList.appendChild(message);
return;
}
chatTabs.forEach((tab, index) => {
const tabElement = document.createElement('div');
tabElement.className = `flex items-center justify-between p-2 hover:bg-gray-100 rounded cursor-pointer ${index === currentTabIndex ? 'bg-gray-200' : ''}`;
tabElement.innerHTML = `
<span>${tab.title || 'New Chat'}</span>
<button onclick="deleteTab(${index})" class="text-red-500 hover:text-red-700">×</button>
`;
tabElement.onclick = () => switchTab(index);
chatList.appendChild(tabElement);
});
}
// Function to load chat messages for a specific tab
function loadChatMessages(tabIndex) {
if (!auth.currentUser || !chatTabs[tabIndex]) return;
const chatContainer = document.getElementById('chat');
chatContainer.innerHTML = '';
const messages = chatTabs[tabIndex].messages || [];
messages.forEach(msg => {
const messageElement = document.createElement('div');
messageElement.className = `p-4 mb-2 rounded ${msg.role === 'user' ? 'bg-blue-100 ml-4' : 'bg-gray-100 mr-4'}`;
messageElement.textContent = msg.content;
chatContainer.appendChild(messageElement);
});
chatContainer.scrollTop = chatContainer.scrollHeight;
}
// Function to add a new message to the current tab
async function addMessage(content, role) {
if (!auth.currentUser || !chatTabs[currentTabIndex]) return;
try {
const message = {
id: crypto.randomUUID(),
content,
role,
timestamp: Timestamp.now()
};
if (!chatTabs[currentTabIndex].messages) {
chatTabs[currentTabIndex].messages = [];
}
chatTabs[currentTabIndex].messages.push(message);
await saveTabsToFirestore(auth.currentUser.uid);
} catch (error) {
console.error('Error adding message:', error);
}
}
// Handle login button click
document.getElementById('loginBtn').addEventListener('click', () => {
const modal = document.getElementById('loginModal');
modal.classList.remove('hidden');
});
// Handle close modal button
document.getElementById('closeModal').addEventListener('click', () => {
const modal = document.getElementById('loginModal');
modal.classList.add('hidden');
});
// Handle Google login
document.getElementById('googleLoginBtn').addEventListener('click', () => {
signInWithPopup(auth, provider)
.then((result) => {
const modal = document.getElementById('loginModal');
modal.classList.add('hidden');
loadTabsFromFirestore(result.user.uid);
})
.catch((error) => {
console.error('Error during Google sign-in:', error.message);
alert('Error during sign-in: ' + error.message);
});
});
// Handle logout
document.getElementById('logoutBtn').addEventListener('click', () => {
const user = auth.currentUser;
if (user) {
saveTabsToFirestore(user.uid).then(() => {
signOut(auth).then(() => {
updateAuthUI();
clearAllTabs();
});
});
} else {
signOut(auth);
}
});
// Update UI based on auth state
function updateAuthUI() {
const loginBtn = document.getElementById('loginBtn');
const logoutBtn = document.getElementById('logoutBtn');
const userDisplay = document.getElementById('userDisplay');
if (auth.currentUser) {
loginBtn.classList.add('hidden');
logoutBtn.classList.remove('hidden');
userDisplay.textContent = auth.currentUser.displayName || 'User';
userDisplay.classList.remove('hidden');
} else {
loginBtn.classList.remove('hidden');
logoutBtn.classList.add('hidden');
userDisplay.classList.add('hidden');
}
}
// Listen for auth state changes
onAuthStateChanged(auth, (user) => {
updateAuthUI();
if (user) {
console.log('User logged in:', user.uid);
loadTabsFromFirestore(user.uid);
} else {
console.log('User logged out');
clearAllTabs();
}
});
// Expose functions to window for use in other scripts
window.createNewChat = function() {
if (!auth.currentUser) {
const modal = document.getElementById('loginModal');
modal.classList.remove('hidden');
return;
}
try {
const newTab = {
id: crypto.randomUUID(),
title: 'New Chat',
messages: [],
createdAt: Timestamp.now(),
lastUpdated: Timestamp.now()
};
chatTabs.push(newTab);
currentTabIndex = chatTabs.length - 1;
updateChatList();
loadChatMessages(currentTabIndex);
saveTabsToFirestore(auth.currentUser.uid);
} catch (error) {
console.error('Error creating new chat:', error);
}
};
window.deleteTab = function(index) {
if (!auth.currentUser) return;
try {
chatTabs.splice(index, 1);
if (currentTabIndex >= chatTabs.length) {
currentTabIndex = Math.max(0, chatTabs.length - 1);
}
updateChatList();
if (chatTabs.length > 0) {
loadChatMessages(currentTabIndex);
} else {
document.getElementById('chat').innerHTML = '';
}
saveTabsToFirestore(auth.currentUser.uid);
console.log('Tab deleted');
} catch (error) {
console.error('Error deleting tab:', error);
}
};
window.switchTab = function(index) {
if (!auth.currentUser) return;
currentTabIndex = index;
updateChatList();
loadChatMessages(index);
};
// Handle sending messages
window.sendMessage = async function() {
if (!auth.currentUser) return;
const input = document.getElementById('userInput');
const message = input.value.trim();
if (!message) return;
// Add user message
await addMessage(message, 'user');
input.value = '';
// Here you would typically add the AI response
// For now, we'll just echo the message
await addMessage(`AI: ${message}`, 'assistant');
};
</script>
<script defer src="script.js"></script>
</head>
<body class="bg-white text-black h-screen flex transition-colors" id="body">
<!-- Login Modal -->
<div id="loginModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white p-8 rounded-lg shadow-lg max-w-md w-full">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold">Sign in to RabbitAI</h2>
<button id="closeModal" class="text-gray-500 hover:text-gray-700">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<button id="googleLoginBtn" class="w-full bg-white border border-gray-300 rounded-lg px-4 py-2 flex items-center justify-center gap-2 hover:bg-gray-50">
<img src="https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/google.svg" alt="Google logo" width="18" height="18">
Sign in with Google
</button>
</div>
</div>
<!-- Sidebar -->
<div id="sidebar" class="w-64 bg-white border-r border-gray-200 flex flex-col p-4">
<div class="flex justify-between items-center mb-6">
<h2 class="text-lg font-bold">Tabs</h2>
<button onclick="createNewChat()" class="bg-pink-500 hover:bg-pink-600 p-2 rounded text-sm text-white" style="color: #ffffff;">
+
</button>
</div>
<div id="chatList" class="flex-1 space-y-2 overflow-y-auto">
<!-- Dynamisch geladen chats komen hier -->
</div>
<div class="mt-4 text-sm text-gray-400">
<button onclick="openSettings()" class="hover:underline">Settings</button>
</div>
</div>
<!-- Main Content -->
<div class="flex flex-col flex-1 h-screen">
<header class="text-center py-4 border-b border-gray-200 flex justify-between px-4 items-center">
<div class="flex gap-2 items-center">
<button onclick="toggleSidebar()" class="text-xl font-bold px-2 py-1">☰</button>
<h1 class="text-xl font-bold">Rabbit AI</h1>
</div>
<div class="flex gap-2 items-center">
<span id="userDisplay" class="hidden text-sm text-gray-600"></span>
<button id="loginBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-800 px-3 py-1 rounded text-sm">Guest</button>
<button id="logoutBtn" class="hidden bg-gray-100 hover:bg-gray-200 text-gray-800 px-3 py-1 rounded text-sm">Log out</button>
<button onclick="resetChat()" class="bg-pink-500 hover:bg-pink-600 text-white px-3 py-1 rounded text-sm">Reset</button>
</div>
</header>
<main id="chat" class="flex-1 overflow-y-auto p-4 space-y-2"></main>
<footer class="p-4 border-t border-gray-200">
<div class="flex gap-2">
<select id="modelSelect" class="px-3 py-2 rounded bg-gray-100 text-black outline-none border border-gray-300">
<option value="mistralai/mistral-7b-instruct:free">Censored RabbitAI</option>
<option value="cognitivecomputations/dolphin3.0-mistral-24b:free">Uncensored RabbitAI</option>
</select>
<input id="userInput" type="text" placeholder="Aks a question privately..." class="flex-1 px-4 py-2 rounded bg-gray-100 text-black outline-none">
<button onclick="sendMessage()" class="bg-pink-500 hover:bg-pink-600 text-white px-4 py-2 rounded">Send</button>
<a href="#" class="flex items-center gap-1 font-semibold text-pink-500 hover:underline px-3 py-2 rounded"><span class="text-lg">✨</span>Upgrade to Pro</a>
</div>
</footer>
</div>
</body>
</html>