← back to B Version 1

web-interface/chat.js

659 lines

// Chat functionality for Dear Bubbe
const chatInput = document.getElementById('chat-input');
const sendBtn = document.getElementById('send-btn');
const chatMessages = document.getElementById('chat-messages');
const modeBtn = document.getElementById('mode-btn');
const modeSelector = document.getElementById('mode-selector');
const modeIndicator = document.getElementById('mode-indicator');

// Current mode
let currentMode = 'bubbe';

// Follow-up reminder timer
let followUpTimer = null;
const FOLLOW_UP_DELAY = 120000; // 2 minutes

// Mode configurations
const modes = {
  bubbe: {
    name: 'Bubbe Mode',
    emoji: '🥯',
    intro: 'Oy vey, bubbeleh!',
    placeholder: 'Ask Bubbe for advice...',
    color: '#d4a574'
  },
  meshugana: {
    name: 'Meshugana Mode',
    emoji: '🤪',
    intro: 'MESHUGGAH ALERT!',
    placeholder: 'Tell me your CRAZY problem...',
    color: '#ff6b9d'
  },
  comedian: {
    name: 'Comedian Mode',
    emoji: '🎤',
    intro: "What's the deal?",
    placeholder: "What's your situation?",
    color: '#fbbf24'
  }
};

// Auto-grow textarea
chatInput.addEventListener('input', () => {
  chatInput.style.height = 'auto';
  chatInput.style.height = Math.min(chatInput.scrollHeight, 80) + 'px';
});

// Handle send button click
sendBtn.addEventListener('click', sendMessage);

// Handle Enter key (Shift+Enter for new line)
chatInput.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' && !e.shiftKey) {
    e.preventDefault();
    sendMessage();
  }
});

// Mode button click
modeBtn.addEventListener('click', (e) => {
  e.stopPropagation();
  modeSelector.style.display = modeSelector.style.display === 'none' ? 'block' : 'none';
});

// Close mode selector when clicking outside
document.addEventListener('click', (e) => {
  if (!modeSelector.contains(e.target) && e.target !== modeBtn) {
    modeSelector.style.display = 'none';
  }
});

// Mode option clicks
document.querySelectorAll('.mode-option').forEach(option => {
  option.addEventListener('click', () => {
    const newMode = option.dataset.mode;
    switchMode(newMode);
    modeSelector.style.display = 'none';
  });
});

// Switch mode function
function switchMode(mode) {
  currentMode = mode;
  const modeConfig = modes[mode];

  // Update mode indicator
  modeIndicator.querySelector('.mode-badge').textContent = modeConfig.name;
  // Keep consistent accent color for simplicity
  // modeIndicator.querySelector('.mode-badge').style.background = modeConfig.color;

  // Update placeholder
  chatInput.placeholder = `${modeConfig.placeholder} (Type /bubbe, /meshugana, or /comedian)`;

  // Add system message
  addSystemMessage(`Switched to ${modeConfig.name}! ${modeConfig.intro}`);

  // Update all avatars to current mode emoji
  updateAvatars(modeConfig.emoji);
}

// Update avatars in chat
function updateAvatars(emoji) {
  document.querySelectorAll('.msg-avatar').forEach(avatar => {
    avatar.textContent = emoji;
  });

  // Update header avatar
  document.querySelector('.chat-header .chat-avatar').textContent = emoji;
}

// Add system message
function addSystemMessage(content) {
  const msgRow = document.createElement('div');
  msgRow.className = 'msg-row msg-system';

  msgRow.innerHTML = `
    <div class="msg-bubble msg-system-bubble">
      <p>${content}</p>
    </div>
  `;

  chatMessages.appendChild(msgRow);
  chatMessages.scrollTop = chatMessages.scrollHeight;
}

// Get current time formatted
function getCurrentTime() {
  const now = new Date();
  return now.toLocaleTimeString('en-US', {
    hour: 'numeric',
    minute: '2-digit',
    hour12: true
  });
}

// Add message to chat
function addMessage(content, isUser = false) {
  const msgRow = document.createElement('div');
  msgRow.className = `msg-row ${isUser ? 'msg-user' : 'msg-assistant'}`;

  const time = getCurrentTime();
  const sender = isUser ? 'You' : modes[currentMode].name.split(' ')[0];

  if (!isUser) {
    const avatar = document.createElement('div');
    avatar.className = 'msg-avatar';
    avatar.textContent = modes[currentMode].emoji;
    msgRow.appendChild(avatar);
  }

  const bubble = document.createElement('div');
  bubble.className = 'msg-bubble';

  // Add share button for Bubbe's responses
  const shareBtn = !isUser ? `
    <button class="share-btn" onclick="shareResponse('${content.replace(/'/g, "\\'")}', this)" title="Share on social media">
      📤
    </button>
  ` : '';

  bubble.innerHTML = `
    <p>${content}</p>
    <span class="msg-meta">${sender} · ${time}${shareBtn}</span>
  `;

  msgRow.appendChild(bubble);
  chatMessages.appendChild(msgRow);

  // Scroll to bottom
  chatMessages.scrollTop = chatMessages.scrollHeight;
}

// Add typing indicator
function showTyping() {
  const msgRow = document.createElement('div');
  msgRow.className = 'msg-row msg-assistant';
  msgRow.id = 'typing-indicator';

  msgRow.innerHTML = `
    <div class="msg-avatar">${modes[currentMode].emoji}</div>
    <div class="msg-bubble typing">
      <span class="typing-dot"></span>
      <span class="typing-dot"></span>
      <span class="typing-dot"></span>
    </div>
  `;

  chatMessages.appendChild(msgRow);
  chatMessages.scrollTop = chatMessages.scrollHeight;
}

// Remove typing indicator
function hideTyping() {
  const typingIndicator = document.getElementById('typing-indicator');
  if (typingIndicator) {
    typingIndicator.remove();
  }
}

// Response libraries for each mode
const responses = {
  bubbe: [
    "Oy gevalt, bubbeleh! Listen to your Bubbe - you need to stand up for yourself. Nobody walks over a mensch like you!",
    "Sweetheart, my brisket gets more attention than that meshuggeneh situation. Drop it like yesterday's challah and move on!",
    "Mamaleh, you're worried about nothing! In my day, we had REAL problems. This? This is bubkes. You'll be fine!",
    "Such tsouris you're having! But listen - you're stronger than you think. Like my matzo balls, firm on the outside, tender inside.",
    "Oy vey iz mir! That's a pickle. But remember what I always say: when life gives you lemons, make lemon rugelach!",
    "Bubbeleh, you know what you need? Perspective! And maybe some chicken soup. But mostly, stop worrying so much!",
    "Listen to me, tateleh - you can't fix meshuggeneh. Set boundaries or they'll be moving in with their luggage!",
    "My darling, this situation is like day-old bagels - not worth keeping! Throw it out and start fresh. You deserve better!",
    "Oy, such a question! The answer is simple, bubbeleh: speak up! In my day, we didn't kvetch in silence. Use your voice!",
    "Sweetheart, stop schvitzing over this! You're making a mountain out of a molehill. Take a deep breath and trust yourself!"
  ],
  meshugana: [
    "Are you MESHUGGENEH?! What kind of narishkeit is THIS question?! You're asking ME?! Oy vey, bubbeleh, you've lost your mind!",
    "WHAT?! Are you farblondzhet asking this?! This is the CRAZIEST mishegoss I ever heard! You're NUTS for even thinking this!",
    "Oy gevalt! You're completely MESHUGGAH to ask such a question! Have you gone farshtunkener in the head? This is INSANE!",
    "Bubbeleh, are you NUTS asking me this?! This whole question is pure mishegoss! You sound meshuggeneh! What's WRONG with you?!",
    "WHAT KIND OF COCKAMAMIE QUESTION IS THIS?! You're a total schmendrik for asking! Have you lost ALL your marbles, mamaleh?!",
    "Are you KIDDING ME with this narishkeit?! You're CRAZY for even THINKING this! This is MESHUGGENEH! What are you, NUTS?!",
    "OY! You're such a VILDE CHAYA for asking this! This whole question is CRAZY TALK! Are you FARBLONDZHET?! You're MESHUGGENEH!",
    "STOP THE MEGILLAH! You're INSANE for asking this! This is total narishkeit! Are you feeling okay up there, you meshuggener?!",
    "Bubbeleh, you're NUTS! This is the CRAZIEST question! You're a complete schmendrik for even asking! What's WITH you?!",
    "What a MESHUGGENER question! You're being completely farshtinkener asking this! You're CRAZY! You're MESHUGGENEH! OY VEY!"
  ],
  comedian: [
    "So let me get this straight - you're asking ME for advice? *slaps knee* That's the real joke here! But seriously folks...",
    "What's the deal with people like that? I mean, come on! It's like they woke up and chose DRAMA. Just block 'em and move on!",
    "You ever notice how everyone's got problems? Yeah? Well yours is pretty standard! *rimshot* But here's what you do...",
    "I'm not saying you're overreacting, but I once saw a squirrel with more chill. *pause for laughs* For real though - take a breath!",
    "So you're telling me - and correct me if I'm wrong - but you're ACTUALLY asking for advice from a digital Jewish grandma? RESPECT!",
    "Let me paint you a picture: It's Tuesday. You've got problems. Welcome to the club! Membership fee: one bagel. Now here's the deal...",
    "You know what this situation reminds me of? Nothing. It's unique! Like a snowflake! A very problematic snowflake! Here's the fix...",
    "I'll be honest with you - I've seen toddlers handle conflict better. *ba dum tss* But you can turn this around! Here's how...",
    "What's the deal with people thinking they can treat you like that? Newsflash: they can't! Set boundaries like you're building a fence!",
    "So this one time... wait, wrong story. YOUR situation! Right! Look - life's too short for this nonsense. Do better, be better!"
  ]
};

// Get response based on current mode
function getResponse() {
  const modeResponses = responses[currentMode];
  return modeResponses[Math.floor(Math.random() * modeResponses.length)];
}

// Follow-up reminder messages
const followUpMessages = [
  "Bubbeleh? You still there? Bubbe asked you a question! Don't make me wait!",
  "Oy! I'm talking to you! What, you're too busy to answer your Bubbe?",
  "Mamaleh! HELLO?! Bubbe is WAITING! What happened, you fell asleep?",
  "Nu? You gonna answer me or what? I haven't got all day, bubbeleh!",
  "OY VEY! Did you forget about me already? Answer Bubbe's question!",
  "Sweetheart? You there? Don't leave your Bubbe hanging like this!"
];

// Start follow-up timer
function startFollowUpTimer() {
  // Clear any existing timer
  clearFollowUpTimer();

  // Set new timer
  followUpTimer = setTimeout(() => {
    const randomMessage = followUpMessages[Math.floor(Math.random() * followUpMessages.length)];
    addMessage(randomMessage, false);

    // Set another timer for persistent reminders
    followUpTimer = setTimeout(() => {
      const persistentMessage = "Bubbeleh, you're breaking Bubbe's heart with this silence! Just say SOMETHING!";
      addMessage(persistentMessage, false);
    }, FOLLOW_UP_DELAY);
  }, FOLLOW_UP_DELAY);
}

// Clear follow-up timer
function clearFollowUpTimer() {
  if (followUpTimer) {
    clearTimeout(followUpTimer);
    followUpTimer = null;
  }
}

// Send message function
async function sendMessage() {
  const message = chatInput.value.trim();

  if (!message) return;

  // Clear follow-up timer when user responds
  clearFollowUpTimer();

  // Check for slash commands
  if (message.startsWith('/')) {
    handleSlashCommand(message);
    chatInput.value = '';
    chatInput.style.height = 'auto';
    return;
  }

  // Add user message
  addMessage(message, true);

  // Clear input
  chatInput.value = '';
  chatInput.style.height = 'auto';

  // Disable send button
  sendBtn.disabled = true;

  // Show typing indicator
  showTyping();

  // Call real API endpoint
  try {
    // Get or create user ID from localStorage
    let userId = localStorage.getItem('bubbeUserId');
    if (!userId) {
      userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
      localStorage.setItem('bubbeUserId', userId);
    }

    const response = await fetch('/api/chat', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        message: message,
        userId: userId,
        mode: currentMode
      })
    });

    if (!response.ok) {
      throw new Error('API call failed');
    }

    const data = await response.json();

    // Hide typing
    hideTyping();

    // Add response (handle both 'answer' and 'response' fields)
    const botMessage = data.answer || data.response;
    addMessage(botMessage, false);

    // Start follow-up timer after Bubbe responds
    startFollowUpTimer();

  } catch (error) {
    hideTyping();

    const errorMsg = document.createElement('div');
    errorMsg.className = 'msg-row msg-assistant';
    errorMsg.innerHTML = `
      <div class="msg-avatar">${modes[currentMode].emoji}</div>
      <div class="msg-bubble msg-error">
        <p>Oy! Something went wrong. Technical difficulties! Try again, bubbeleh!</p>
        <span class="msg-meta">Error · ${getCurrentTime()}</span>
      </div>
    `;
    chatMessages.appendChild(errorMsg);
    chatMessages.scrollTop = chatMessages.scrollHeight;
  } finally {
    // Re-enable send button
    sendBtn.disabled = false;
    chatInput.focus();
  }
}

// Handle slash commands
function handleSlashCommand(command) {
  const cmd = command.toLowerCase().trim();

  if (cmd === '/bubbe') {
    switchMode('bubbe');
  } else if (cmd === '/meshugana' || cmd === '/meshuggeneh') {
    switchMode('meshugana');
  } else if (cmd === '/comedian') {
    switchMode('comedian');
  } else if (cmd === '/share-facebook' || cmd === '/facebook') {
    shareToFacebook();
  } else if (cmd === '/share-x' || cmd === '/x' || cmd === '/twitter') {
    shareToX();
  } else if (cmd === '/share-instagram' || cmd === '/instagram') {
    shareToInstagram();
  } else if (cmd === '/share-tiktok' || cmd === '/tiktok') {
    shareToTikTok();
  } else {
    addSystemMessage(`Unknown command: ${command}. Try /bubbe, /meshugana, /comedian, /facebook, /x, /instagram, /tiktok`);
  }
}

// Guilt trip messages
const guiltTripMessages = [
  "Oy vey! You're in {location} and you haven't checked in with Bubbe in {time}? What, I'm chopped liver? But bubbeleh, you're here now, so I'll forgive you!",
  "BUBBELEH! {location}? So far away and {time} without a word! Your Bubbe was worried SICK! But never mind, never mind, you're here now...",
  "OY! You're in {location} and this is the first I'm hearing from you in {time}?! I raised you better than this! But mamaleh, I forgive you. Now tell Bubbe everything!",
  "Look who finally shows up! {location}, huh? And {time} since your last visit! The brisket got COLD waiting for you! But sit, sit, Bubbe missed you!",
  "MESHUGGENEH! You're in {location} and you make Bubbe wait {time}?! What, you don't love your Bubbe anymore? But you're here now, so I forgive you, bubbeleh!",
  "Oy gevalt! {location} and {time} without checking in! I was THIS CLOSE to calling the police! But thank God you're here now, mamaleh!"
];

// Show guilt trip on page load
async function showGuiltTrip() {
  try {
    // Get last visit from localStorage
    const lastVisit = localStorage.getItem('bubbeLastVisit');
    const now = Date.now();

    // Calculate time since last visit
    let timeSinceVisit = '3 days';
    if (lastVisit) {
      const hoursSince = Math.floor((now - parseInt(lastVisit)) / (1000 * 60 * 60));
      if (hoursSince < 1) {
        timeSinceVisit = 'less than an hour';
      } else if (hoursSince < 24) {
        timeSinceVisit = `${hoursSince} hours`;
      } else {
        const daysSince = Math.floor(hoursSince / 24);
        timeSinceVisit = `${daysSince} ${daysSince === 1 ? 'day' : 'days'}`;
      }
    }

    // Update last visit timestamp
    localStorage.setItem('bubbeLastVisit', now.toString());

    // Try to get location (with timeout)
    let location = null;

    // Use IP-based geolocation API
    try {
      const locationPromise = fetch('http://ip-api.com/json/')
        .then(res => res.json())
        .then(data => {
          if (data.status === 'success') {
            return {
              city: data.city,
              region: data.regionName,
              country: data.country,
              timezone: data.timezone
            };
          }
          return null;
        })
        .catch(() => null);

      // Wait max 2 seconds for location
      const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(null), 2000));
      location = await Promise.race([locationPromise, timeoutPromise]);
    } catch (error) {
      console.log('Location detection failed');
    }

    // Get city greeting from API with location
    const userId = localStorage.getItem('bubbeUserId') || 'guest_' + Date.now();
    const greetingMsg = location ?
      `Tell me about ${location.city}, ${location.region}! What time is it there? What are the famous landmarks?` :
      'Hello Bubbe!';

    // Call API for city-specific greeting
    try {
      const response = await fetch('/api/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          message: greetingMsg,
          userId: userId,
          mode: currentMode
        })
      });

      if (response.ok) {
        const data = await response.json();

        // Show location-aware greeting before welcome message
        const welcomeMsg = chatMessages.querySelector('.msg-row.msg-assistant');
        if (welcomeMsg) {
          const botMessage = data.answer || data.response;
          const guiltRow = document.createElement('div');
          guiltRow.className = 'msg-row msg-assistant guilt-trip';
          guiltRow.innerHTML = `
            <div class="msg-avatar">🥯</div>
            <div class="msg-bubble" style="background: #fff3cd; border-left: 4px solid #ffc107;">
              <p><strong>${botMessage}</strong></p>
              <span class="msg-meta">Bubbe · Just now</span>
            </div>
          `;
          chatMessages.insertBefore(guiltRow, welcomeMsg);
        }
      }
    } catch (error) {
      console.error('Greeting error:', error);
      // Fallback to simple greeting
      const welcomeMsg = chatMessages.querySelector('.msg-row.msg-assistant');
      if (welcomeMsg && location) {
        const guiltRow = document.createElement('div');
        guiltRow.className = 'msg-row msg-assistant guilt-trip';
        guiltRow.innerHTML = `
          <div class="msg-avatar">🥯</div>
          <div class="msg-bubble" style="background: #fff3cd; border-left: 4px solid #ffc107;">
            <p><strong>Oy vey! You're in ${location.city}, ${location.region}? Welcome, bubbeleh!</strong></p>
            <span class="msg-meta">Bubbe · Just now</span>
          </div>
        `;
        chatMessages.insertBefore(guiltRow, welcomeMsg);
      }
    }

  } catch (error) {
    console.error('Guilt trip error:', error);
    // Fail silently - don't break the chat experience
  }
}

// Share response function
function shareResponse(text, button) {
  // Create share text with branding
  const shareText = `Bubbe says: "${text}" - Get advice from Dear Bubbe at www.bubbe.ai`;

  // Use Web Share API if available (mobile)
  if (navigator.share) {
    navigator.share({
      title: 'Dear Bubbe Advice',
      text: shareText,
      url: 'https://www.bubbe.ai'
    }).catch(err => console.log('Share cancelled'));
  } else {
    // Fallback: copy to clipboard
    navigator.clipboard.writeText(shareText).then(() => {
      // Show feedback
      const originalText = button.textContent;
      button.textContent = '✓';
      button.style.background = '#4caf50';
      setTimeout(() => {
        button.textContent = originalText;
        button.style.background = '';
      }, 2000);
    });
  }
}

// Get last Bubbe response for sharing
function getLastBubbeResponse() {
  const messages = chatMessages.querySelectorAll('.msg-row.msg-assistant');
  if (messages.length === 0) {
    addSystemMessage('No Bubbe response to share yet! Ask a question first.');
    return null;
  }
  const lastMsg = messages[messages.length - 1];
  const textContent = lastMsg.querySelector('.msg-bubble p').textContent;
  return textContent;
}

// Share to Facebook
function shareToFacebook() {
  const text = getLastBubbeResponse();
  if (!text) return;

  const shareText = `Bubbe says: "${text}" - Get advice from Dear Bubbe!`;
  const url = encodeURIComponent('https://www.bubbe.ai');
  const quote = encodeURIComponent(shareText);

  window.open(`https://www.facebook.com/sharer/sharer.php?u=${url}&quote=${quote}`, '_blank');
  addSystemMessage('Opening Facebook to share Bubbe\'s wisdom! 📘');
}

// Share to X (Twitter)
function shareToX() {
  const text = getLastBubbeResponse();
  if (!text) return;

  const shareText = `Bubbe says: "${text}" - Get advice from Dear Bubbe at www.bubbe.ai`;
  const encoded = encodeURIComponent(shareText);

  window.open(`https://twitter.com/intent/tweet?text=${encoded}`, '_blank');
  addSystemMessage('Opening X to share Bubbe\'s wisdom! 𝕏');
}

// Share to Instagram (copy to clipboard with instructions)
function shareToInstagram() {
  const text = getLastBubbeResponse();
  if (!text) return;

  const shareText = `Bubbe says: "${text}" - Get advice from Dear Bubbe at www.bubbe.ai`;

  navigator.clipboard.writeText(shareText).then(() => {
    addSystemMessage('📷 Copied to clipboard! Open Instagram, create a post/story, and paste the text!');
  }).catch(() => {
    addSystemMessage('📷 Share this on Instagram: ' + shareText);
  });
}

// Share to TikTok (copy to clipboard with instructions)
function shareToTikTok() {
  const text = getLastBubbeResponse();
  if (!text) return;

  const shareText = `Bubbe says: "${text}" - Get advice from Dear Bubbe at www.bubbe.ai`;

  navigator.clipboard.writeText(shareText).then(() => {
    addSystemMessage('🎵 Copied to clipboard! Open TikTok, create a video, and paste the text in your caption!');
  }).catch(() => {
    addSystemMessage('🎵 Share this on TikTok: ' + shareText);
  });
}

// Focus input on load and show guilt trip
// Fetch and display breaking news
async function loadBreakingNews() {
  try {
    const response = await fetch('/api/news');
    if (!response.ok) throw new Error('Failed to fetch news');

    const news = await response.json();
    const newsContainer = document.getElementById('breaking-news-items');
    const newsBanner = document.getElementById('breaking-news');

    if (news && news.length > 0) {
      // Take top 3 headlines
      const topNews = news.slice(0, 3);
      newsContainer.innerHTML = topNews.map(item =>
        `<div class="breaking-news-item">${item.title || item}</div>`
      ).join('');

      // Show the banner
      newsBanner.style.display = 'block';
    }
  } catch (error) {
    console.error('Error loading breaking news:', error);
    // Hide banner if news fails to load
    document.getElementById('breaking-news').style.display = 'none';
  }
}

window.addEventListener('load', () => {
  loadBreakingNews();
  showGuiltTrip();
  chatInput.focus();
});

// API Integration (uncomment and configure when ready)
/*
async function callBubbeAPI(question, mode) {
  const response = await fetch('/api/chat', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ question, mode })
  });

  if (!response.ok) {
    throw new Error('API call failed');
  }

  const data = await response.json();
  return data.answer;
}
*/