ButterM40's picture
Deploy actual React-style frontend with FastAPI backend
c2db707
// WebSocket connection
let ws = null;
let clientId = generateClientId();
let currentCharacter = 'moses';
let conversationHistory = [];
let voiceEnabled = true;
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
initializeWebSocket();
setupCharacterSelection();
setupEventListeners();
loadSettings();
});
function generateClientId() {
return 'client_' + Math.random().toString(36).substr(2, 9);
}
function initializeWebSocket() {
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${wsProtocol}//${window.location.host}/ws/${clientId}`;
ws = new WebSocket(wsUrl);
ws.onopen = function() {
console.log('WebSocket connected');
updateConnectionStatus(true);
};
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
handleWebSocketMessage(data);
};
ws.onclose = function() {
console.log('WebSocket disconnected');
updateConnectionStatus(false);
// Attempt to reconnect after 3 seconds
setTimeout(initializeWebSocket, 3000);
};
ws.onerror = function(error) {
console.error('WebSocket error:', error);
updateConnectionStatus(false);
};
}
function updateConnectionStatus(connected) {
// You could add a connection indicator in the UI
const sendBtn = document.getElementById('send-btn');
sendBtn.disabled = !connected;
}
function handleWebSocketMessage(data) {
switch(data.type) {
case 'character_switched':
console.log(`Switched to character: ${data.character_id}`);
break;
case 'chat_response':
hideTypingIndicator();
displayMessage(data.response, 'assistant', data.character_id);
// Play audio if available
if (data.audio && voiceEnabled) {
playAudio(data.audio);
}
break;
}
}
function setupCharacterSelection() {
const characterCards = document.querySelectorAll('.character-card');
characterCards.forEach(card => {
card.addEventListener('click', function() {
const characterId = this.dataset.character;
switchCharacter(characterId);
// Show enhancement notification
showEnhancementNotification(characterId);
});
});
// Set initial character as active
setActiveCharacter(currentCharacter);
}
function showEnhancementNotification(character) {
const notifications = {
'moses': 'Moses enhanced with 70 examples of divine wisdom and biblical knowledge',
'samsung_employee': 'Samsung Employee enhanced with 60 examples of technical expertise',
'jinx': 'Jinx enhanced with 60 examples of chaotic personality and emotional depth'
};
const message = notifications[character] || 'Character enhanced with 5x training data';
showNotification(message, 'enhancement');
}
function showNotification(message, type = 'info') {
// Remove existing notifications
document.querySelectorAll('.notification').forEach(n => n.remove());
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.innerHTML = `
<i class="fas fa-star"></i>
<span>${message}</span>
<button onclick="this.parentElement.remove()">×</button>
`;
document.body.appendChild(notification);
// Auto remove after 5 seconds
setTimeout(() => {
notification.remove();
}, 5000);
}
function setupEventListeners() {
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
// Send message on button click
sendBtn.addEventListener('click', sendMessage);
// Auto-resize textarea
messageInput.addEventListener('input', function() {
autoResize(this);
});
}
function handleKeyPress(event) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
sendMessage();
}
}
function autoResize(textarea) {
textarea.style.height = 'auto';
textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
}
function switchCharacter(characterId) {
if (currentCharacter === characterId) return;
currentCharacter = characterId;
setActiveCharacter(characterId);
updateChatHeader(characterId);
// Send switch message via WebSocket
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'switch_character',
character_id: characterId
}));
}
// Clear current chat and show welcome message
clearChat();
showWelcomeMessage(characterId);
}
function setActiveCharacter(characterId) {
const characterCards = document.querySelectorAll('.character-card');
characterCards.forEach(card => {
card.classList.remove('active');
if (card.dataset.character === characterId) {
card.classList.add('active');
}
});
}
function updateChatHeader(characterId) {
const characters = {
'moses': {
name: 'Moses',
description: 'Biblical Prophet and Lawgiver',
avatar: 'static/avatars/moses.svg'
},
'samsung_employee': {
name: 'Samsung Employee',
description: 'Tech Expert and Product Specialist',
avatar: 'static/avatars/samsung.svg'
},
'jinx': {
name: 'Jinx',
description: 'Chaotic Genius from Arcane',
avatar: 'static/avatars/jinx.svg'
}
};
const character = characters[characterId];
if (character) {
document.getElementById('current-character-name').textContent = character.name;
document.getElementById('current-character-desc').textContent = character.description;
document.getElementById('current-avatar').src = character.avatar;
document.getElementById('message-input').placeholder = `Message ${character.name}...`;
document.getElementById('typing-character').textContent = character.name;
}
}
function sendMessage() {
const messageInput = document.getElementById('message-input');
const message = messageInput.value.trim();
if (!message || !ws || ws.readyState !== WebSocket.OPEN) return;
// Display user message
displayMessage(message, 'user');
// Add to conversation history
conversationHistory.push({
role: 'user',
content: message
});
// Show typing indicator
showTypingIndicator();
// Send via WebSocket
ws.send(JSON.stringify({
type: 'chat_message',
text: message,
history: conversationHistory.slice(-10), // Keep last 10 messages
include_audio: voiceEnabled,
timestamp: Date.now()
}));
// Clear input
messageInput.value = '';
autoResize(messageInput);
}
function displayMessage(content, sender, characterId = null) {
const chatMessages = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}`;
let avatarSrc = '';
if (sender === 'user') {
avatarSrc = 'static/avatars/user.svg';
} else {
const avatars = {
'moses': 'static/avatars/moses.svg',
'samsung_employee': 'static/avatars/samsung.svg',
'jinx': 'static/avatars/jinx.svg'
};
avatarSrc = avatars[characterId || currentCharacter];
}
messageDiv.innerHTML = `
<div class="message-avatar">
<img src="${avatarSrc}" alt="${sender}" onerror="this.src='static/avatars/default.svg'">
</div>
<div class="message-content">
<p>${escapeHtml(content)}</p>
</div>
`;
chatMessages.appendChild(messageDiv);
scrollToBottom();
// Add to conversation history for assistant messages
if (sender === 'assistant') {
conversationHistory.push({
role: 'assistant',
content: content
});
}
}
function showWelcomeMessage(characterId) {
const welcomeMessages = {
'moses': "Peace be with you, my child. I am Moses, prophet and lawgiver. My character-focused training allows me to embody the wisdom of the Almighty. How may I guide you in righteousness?",
'samsung_employee': "Hello! I'm your Samsung technology expert, trained with character-focused learning to provide authentic product knowledge and enthusiasm. What amazing Galaxy features can I share with you today?",
'jinx': "*spins around excitedly* Hey there! I'm Jinx - the real me, not some AI pretending! My character-focused training means pure chaotic genius with no boring assistant stuff. Ready for some explosive fun?"
};
const message = welcomeMessages[characterId];
if (message) {
displayMessage(message, 'assistant', characterId);
}
}
function showTypingIndicator() {
document.getElementById('typing-indicator').style.display = 'flex';
scrollToBottom();
}
function hideTypingIndicator() {
document.getElementById('typing-indicator').style.display = 'none';
}
function scrollToBottom() {
const chatMessages = document.getElementById('chat-messages');
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function playAudio(audioData) {
if (!voiceEnabled || !audioData) return;
try {
const audio = new Audio(audioData);
audio.play().catch(error => {
console.error('Error playing audio:', error);
});
} catch (error) {
console.error('Error creating audio:', error);
}
}
function toggleVoice() {
voiceEnabled = !voiceEnabled;
const voiceIcon = document.getElementById('voice-icon');
const voiceToggle = document.querySelector('.voice-toggle');
if (voiceEnabled) {
voiceIcon.className = 'fas fa-volume-up';
voiceToggle.classList.add('active');
} else {
voiceIcon.className = 'fas fa-volume-mute';
voiceToggle.classList.remove('active');
}
saveSettings();
}
function clearChat() {
const chatMessages = document.getElementById('chat-messages');
chatMessages.innerHTML = '';
conversationHistory = [];
}
function startNewChat() {
clearChat();
showWelcomeMessage(currentCharacter);
}
function showResources() {
const modal = document.getElementById('resources-modal');
modal.classList.add('active');
}
function showSettings() {
const modal = document.getElementById('settings-modal');
modal.classList.add('active');
}
function closeModal(modalId) {
const modal = document.getElementById(modalId);
modal.classList.remove('active');
}
function updateVoiceSetting() {
const voiceCheckbox = document.getElementById('voice-enabled');
voiceEnabled = voiceCheckbox.checked;
const voiceToggle = document.querySelector('.voice-toggle');
const voiceIcon = document.getElementById('voice-icon');
if (voiceEnabled) {
voiceToggle.classList.add('active');
voiceIcon.className = 'fas fa-volume-up';
} else {
voiceToggle.classList.remove('active');
voiceIcon.className = 'fas fa-volume-mute';
}
saveSettings();
}
function changeTheme() {
const themeSelect = document.getElementById('theme-select');
const theme = themeSelect.value;
document.documentElement.setAttribute('data-theme', theme);
saveSettings();
}
function saveSettings() {
const settings = {
voiceEnabled: voiceEnabled,
theme: document.documentElement.getAttribute('data-theme') || 'dark',
responseSpeed: document.getElementById('response-speed')?.value || '0.7'
};
localStorage.setItem('roleplayChatSettings', JSON.stringify(settings));
}
function loadSettings() {
try {
const settings = JSON.parse(localStorage.getItem('roleplayChatSettings')) || {};
// Load voice setting
if (settings.voiceEnabled !== undefined) {
voiceEnabled = settings.voiceEnabled;
document.getElementById('voice-enabled').checked = voiceEnabled;
updateVoiceSetting();
}
// Load theme
if (settings.theme) {
document.documentElement.setAttribute('data-theme', settings.theme);
document.getElementById('theme-select').value = settings.theme;
}
// Load response speed
if (settings.responseSpeed) {
document.getElementById('response-speed').value = settings.responseSpeed;
}
} catch (error) {
console.error('Error loading settings:', error);
}
}
// Close modals when clicking outside
window.addEventListener('click', function(event) {
if (event.target.classList.contains('modal')) {
event.target.classList.remove('active');
}
});
// Handle escape key to close modals
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
const activeModal = document.querySelector('.modal.active');
if (activeModal) {
activeModal.classList.remove('active');
}
}
});