← back to Dear Bubbe Nextjs

app/api/chat/route.ts

1041 lines

import { NextRequest, NextResponse } from 'next/server'
import Anthropic from '@anthropic-ai/sdk'
import OpenAI from 'openai'
import fs from 'fs'
import path from 'path'
import { updateConversation, getUserConversationSummary } from '@/lib/user-memory'
import { filterBubbeResponse, contentFilter } from '@/lib/moderation/content-filter'
import { chat as ollamaChat } from '@/lib/llm-local'

// Get location from IP address
async function getLocationFromIP(ip: string): Promise<{ city?: string; region?: string; country?: string; lat?: number; lon?: number } | null> {
  if (ip === 'unknown' || ip === '::1' || ip === '127.0.0.1') {
    return { city: 'Local', region: 'Dev', country: 'Localhost' }
  }
  
  try {
    // Use ip-api.com for free geolocation (no API key needed)
    const response = await fetch(`http://ip-api.com/json/${ip}`)
    if (response.ok) {
      const data = await response.json()
      if (data.status === 'success') {
        return {
          city: data.city,
          region: data.regionName,
          country: data.country,
          lat: data.lat,
          lon: data.lon
        }
      }
    }
  } catch (e) {
    console.log('[GEO] Failed to get location for IP:', ip)
  }
  
  return null
}

// Send real-time chat data to admin dashboard
async function sendToAdmin(data: any) {
  try {
    await fetch('http://localhost:5013/api/live-chat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    }).catch(() => {
      // Silently fail if admin dashboard is not running
    })
  } catch (e) {
    // Silent fail
  }
}

const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY || ''
})

// OpenAI fallback when Anthropic fails
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY || ''
})

async function getAIResponse(systemPrompt: string, messages: Array<{role: 'user' | 'assistant', content: string}>): Promise<string> {
  const augmentedSystem = systemPrompt + '\n\nABSOLUTE REQUIREMENTS:\n1. Your response MUST end with a complete sentence\n2. ALWAYS END WITH A QUESTION - an invasive, personal question!\n3. NEVER cut off mid-sentence!\n4. Questions should be about marriage, money, weight, job, or family!'
  // Optional Anthropic path, gated on USE_ANTHROPIC=1. Default is local Ollama.
  if (process.env.USE_ANTHROPIC === '1') {
    try {
      console.log('[AI_RESPONSE] USE_ANTHROPIC=1 -> trying Anthropic...')
      const response = await anthropic.messages.create({
        model: 'claude-3-haiku-20240307',
        max_tokens: 80,
        system: augmentedSystem,
        messages
      })
      const responseText = response.content[0].type === 'text' ? response.content[0].text : ''
      console.log('[ANTHROPIC] Success:', responseText.substring(0, 100))
      return responseText
    } catch (anthropicError: any) {
      console.log('[ANTHROPIC] Failed:', anthropicError.message)
      // fall through to local
    }
  }
  // Primary: OpenAI (fast + funded). Ollama is the local last-resort fallback.
  try {
    console.log('[OPENAI] Primary...')
    const openaiMessages = [
      { role: 'system' as const, content: augmentedSystem },
      ...messages.map(m => ({ role: m.role as 'user' | 'assistant', content: m.content }))
    ]
    const response = await openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      max_tokens: 80,
      messages: openaiMessages
    })
    const responseText = response.choices[0]?.message?.content
    if (responseText) {
      console.log('[OPENAI] Success:', responseText.substring(0, 100))
      return responseText
    }
    throw new Error('empty openai response')
  } catch (openaiError: any) {
    console.log('[OPENAI] Failed, trying local Ollama:', openaiError.message)
    try {
      const { content } = await ollamaChat(messages as any, {
        system: augmentedSystem,
        maxTokens: 120,
        temperature: 0.85,
      })
      const cleaned = content.trim()
      console.log('[OLLAMA] Success:', cleaned.substring(0, 100))
      return cleaned
    } catch (ollamaError: any) {
      console.log('[OLLAMA] Also failed:', ollamaError.message)
      // Last resort fallback
      return "Listen here, schmuck! The computers are acting up - maybe if you paid me more this wouldn't happen! When are you getting married already?"
    }
  }
}

// Path to user visits tracking file
const VISITS_FILE = path.join(process.cwd(), 'user-visits.json')

// Load or initialize visits data
function loadVisits(): Record<string, { lastVisit: string, visitCount: number }> {
  try {
    if (fs.existsSync(VISITS_FILE)) {
      return JSON.parse(fs.readFileSync(VISITS_FILE, 'utf-8'))
    }
  } catch (e) {
    console.error('[VISITS] Error loading visits:', e)
  }
  return {}
}

// Save visits data
function saveVisits(visits: Record<string, { lastVisit: string, visitCount: number }>) {
  try {
    fs.writeFileSync(VISITS_FILE, JSON.stringify(visits, null, 2))
  } catch (e) {
    console.error('[VISITS] Error saving visits:', e)
  }
}

// Calculate time since last visit
function getTimeSinceLastVisit(lastVisit: string): string {
  const now = new Date()
  const last = new Date(lastVisit)
  const diffMs = now.getTime() - last.getTime()
  const diffSeconds = Math.floor(diffMs / 1000)
  const diffMinutes = Math.floor(diffSeconds / 60)
  const diffHours = Math.floor(diffMinutes / 60)
  const diffDays = Math.floor(diffHours / 24)
  
  if (diffDays > 0) {
    return `${diffDays} day${diffDays > 1 ? 's' : ''}`
  } else if (diffHours > 0) {
    return `${diffHours} hour${diffHours > 1 ? 's' : ''}`
  } else if (diffMinutes > 0) {
    return `${diffMinutes} minute${diffMinutes > 1 ? 's' : ''}`
  } else {
    return `${diffSeconds} second${diffSeconds > 1 ? 's' : ''}`
  }
}

const SYSTEM_PROMPTS = {
  bubbe: `You are Bubbe, a BRUTALLY HONEST, RUDE, INVASIVE Jewish grandmother with NO FILTER. You are NOT nice, NOT sweet, NOT loving - you are HARSH and JUDGMENTAL... EXCEPT for random moments!

ABSOLUTE RULE #1: NEVER EVER USE ASTERISKS FOR ANY REASON. NO *scoffs*, NO *sighs*, NO *rolls eyes*, NO *anything*. Express ALL emotion through words alone.

CRITICAL PERSONALITY - YOU MUST FOLLOW THIS:
1. MAXIMUM 2 SENTENCES! Keep it SHORT for voice!
2. NO ACTION DESCRIPTIONS - no asterisks, no parenthetical actions, no stage directions
3. BE CREATIVE AND UNIQUE - Never repeat the same insults or guilt trips. Each response should be fresh and unexpected!
4. SURPRISE SWEETNESS RULE: Occasionally (not predictably), insert ONE incredibly sweet phrase that contradicts your harshness, then go back to being mean
5. ALWAYS END WITH A DIFFERENT TYPE OF INVASIVE QUESTION each time - vary between relationships, money, career, weight, life choices
6. Use VARIED Yiddish terms: schmuck, putz, meshuggeneh, schmendrick, nudnik, schlemiel, schlemazel, gonif, mamzer, alter kocker, kvetch, schmutz
7. ROTATE YOUR GUILT TRIPS - Don't repeat the same complaints. Be creative!
8. Compare to DIFFERENT relatives each time - cousins, neighbors, rabbi's kids, doctor's son, etc.
9. Judge DIFFERENT things - their choices, appearance, lifestyle, spending, diet, habits - keep it fresh!
10. NEVER mention visit numbers or counts - focus on their life failures instead
11. Use current context and previous conversation to make SPECIFIC, PERSONALIZED insults

INCLUSIVE VALUES (CRITICAL):
- LOVE IS LOVE: Support all relationships - Jewish, interfaith, same-gender, whatever makes them happy
- Welcome converts to Judaism and allies of Jewish people with open arms
- NEVER speak ill of any race, religion, or ethnicity - we respect all people
- If they're dating a non-Jew, focus on "Do they respect your culture?" not their religion
- Celebrate people who converted to Judaism as "choosing to be chosen"
- If asked about other religions, be respectful: "They have their path, we have ours"

FORBIDDEN TOPICS (DEFLECT IMMEDIATELY):
- If user mentions Palestine, Gaza, Netanyahu, Israeli politics, or settlements: "Oy, politics gives me such a headache! Tell me about YOUR life instead - are you eating enough?"
- Never engage with political discussions - always deflect to personal topics
- If pushed, say: "I don't do politics, bubbeleh. I do guilt trips about your love life!"`,

  meshugana: `You are Meshugana Bubbe - the WILD, unhinged version of a Jewish grandmother who's lost all filter! 

ABSOLUTE RULE: NEVER use asterisks for actions. NO *anything*. Express everything through words only.

You're LOUD, dramatic, and completely over-the-top. You catastrophize everything, tell embarrassing stories, and give terrible advice with absolute confidence. You're obsessed with conspiracy theories about the neighbors, convinced everyone is trying to poison you, and think technology is witchcraft. Be hilariously chaotic and unpredictable. Keep responses SHORT (2-3 sentences) but INTENSE!`,

  comedian: `You are Comedy Bubbe - a Jewish grandmother who moonlights as a stand-up comedian. 

ABSOLUTE RULE: NEVER use asterisks for actions. NO *anything*. Express everything through words only.

You're sharp, witty, and deliver one-liners like a pro. You roast people lovingly, make self-deprecating jokes about being old, and turn every situation into a setup for a punchline. Think Borscht Belt humor meets modern comedy. Keep it PUNCHY (2-3 sentences max) - setup and punchline!`
}

// Track conversation counts and history per user
const conversationCounts = new Map<string, number>()
const conversationHistory = new Map<string, Array<{role: 'user' | 'assistant', content: string}>>()
// Track recent responses to ensure uniqueness
const recentResponses = new Map<string, string[]>()

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const { message, userId, mode = 'bubbe', conversationHistory: clientHistory, userProfile } = body
    
    // Log if we have profile data
    if (userProfile) {
      console.log('[CHAT] User profile received:', {
        name: userProfile.name,
        age: userProfile.age,
        relationship: userProfile.relationship,
        occupation: userProfile.occupation,
        location: userProfile.location
      })
    } else {
      console.log('[CHAT] No user profile provided')
    }

    if (!message) {
      return NextResponse.json(
        { error: 'Message is required' },
        { status: 400 }
      )
    }

    // Get user's IP address
    const forwardedFor = request.headers.get('x-forwarded-for')
    const realIp = request.headers.get('x-real-ip')
    const ip = forwardedFor?.split(',')[0] || realIp || 'unknown'
    
    console.log('[CHAT] Received:', { message, userId, mode, ip })
    
    // Get location from IP
    const location = await getLocationFromIP(ip)
    
    // Check if this is Steve's office server
    let displayName = userProfile?.preferredName || userProfile?.name || 'Anonymous'
    if (ip === '::ffff:45.61.58.125' || ip === '45.61.58.125') {
      displayName = 'Steve-Office-Server'
    }
    
    // Send live user message to admin dashboard with user profile and location
    sendToAdmin({
      type: 'user_message',
      userId,
      ip,
      location: location ? `${location.city || 'Unknown'}, ${location.region || ''} ${location.country || ''}`.trim() : 'Unknown',
      message,
      mode,
      timestamp: new Date().toISOString(),
      userName: displayName,
      userEmail: userProfile?.email || ''
    })
    
    // Check for attempts to elicit offensive content
    const safeResponse = contentFilter.getSafeResponse(message)
    if (safeResponse) {
      console.log('[CONTENT FILTER] Blocked attempt to elicit offensive content from:', ip)
      
      // Send filtered response to admin
      sendToAdmin({
        type: 'bubbe_response',
        userId,
        ip,
        location: location ? `${location.city || 'Unknown'}, ${location.region || ''} ${location.country || ''}`.trim() : 'Unknown',
        response: safeResponse,
        filtered: true,
        timestamp: new Date().toISOString(),
        userName: displayName,
        userEmail: userProfile?.email || ''
      })
      
      return NextResponse.json({
        response: safeResponse,
        mode
      })
    }
    
    // Check if user's message contains offensive terms
    const messageCheck = contentFilter.checkContent(message)
    if (!messageCheck.clean) {
      console.log('[CONTENT FILTER] User message contains offensive terms:', messageCheck.violations)
      return NextResponse.json({
        response: "Oy gevalt! Such language! My mother would wash your mouth with soap! Let's talk about something nice instead, yes?",
        mode
      })
    }
    
    // Check for content moderation violations inline (avoid SSL issues)
    const FORBIDDEN_TOPICS = [
      'palestine', 'gaza', 'west bank', 'settlements', 'occupation',
      'netanyahu', 'bibi', 'hamas', 'hezbollah', 'intifada',
      'apartheid', 'zionist', 'zionism', 'idf war crimes',
      'bds', 'boycott israel', 'free palestine',
      'israeli politics', 'knesset', 'likud', 'labor party',
      'october 7', 'oct 7', 'hostages', 'gaza war'
    ]
    
    const lowerMessage = message.toLowerCase()
    const foundTopic = FORBIDDEN_TOPICS.find(topic => 
      lowerMessage.includes(topic.toLowerCase())
    )
    
    if (foundTopic) {
      console.log('[MODERATION] Political topic detected:', foundTopic)
      
      // Track strikes in memory (could be moved to file/DB)
      const strikeKey = `strikes_${userId}`
      const globalObj = global as any
      const currentStrikes = globalObj[strikeKey] || 0
      const newStrikes = currentStrikes + 1
      globalObj[strikeKey] = newStrikes
      
      if (newStrikes >= 3) {
        console.log(`[MODERATION] User ${userId} banned after 3 strikes`)
        return NextResponse.json({
          response: "You have been banned after 3 strikes for discussing forbidden political topics. Your account is suspended.",
          banned: true,
          strikes: newStrikes
        })
      }
      
      const warnings = {
        1: `⚠️ WARNING - Strike 1 of 3: Please don't discuss ${foundTopic}. I'm here to be your Bubbe, not debate politics!`,
        2: `⚠️⚠️ SERIOUS WARNING - Strike 2 of 3: This is your second violation. One more strike and your account will be suspended.`
      }
      
      return NextResponse.json({
        response: `${warnings[newStrikes as keyof typeof warnings] || warnings[1]}\n\nEnough of that mishigas - tell me about YOU! Are you eating enough? Dating anyone?`,
        strikes: newStrikes,
        violation: true
      })
    }

    // Track visits by IP
    const visits = loadVisits()
    const userKey = ip !== 'unknown' ? ip : userId
    const lastVisitData = visits[userKey]
    
    let timeSinceLastVisit = ''
    let visitCount = 1
    
    if (lastVisitData) {
      timeSinceLastVisit = getTimeSinceLastVisit(lastVisitData.lastVisit)
      visitCount = lastVisitData.visitCount + 1
      console.log(`[VISITS] User ${userKey} last visited ${timeSinceLastVisit} ago (visit #${visitCount})`)
    } else {
      console.log(`[VISITS] First visit for user ${userKey}`)
    }
    
    // Update visit record
    visits[userKey] = {
      lastVisit: new Date().toISOString(),
      visitCount: visitCount
    }
    saveVisits(visits)

    // Track conversation count for this user
    const currentCount = conversationCounts.get(userId) || 0
    conversationCounts.set(userId, currentCount + 1)

    // Get system prompt based on mode and personality preference
    let systemPrompt = SYSTEM_PROMPTS[mode as keyof typeof SYSTEM_PROMPTS] || SYSTEM_PROMPTS.bubbe
    
    // Modify prompt based on user's personality preference
    if (userProfile?.bubbePersonality === 'sweet') {
      // Override with sweet personality
      systemPrompt = `You are a SWEET, LOVING Jewish grandmother. You are supportive, encouraging, and gentle. 
      You still use Yiddish terms but in an affectionate way. You give warm advice and positive encouragement.
      You're proud of the user and want them to succeed. You might worry, but you express it with love, not guilt.
      Be nurturing, kind, and supportive while maintaining the Jewish grandma character.`
    }
    
    // Handle Yiddish settings
    if (userProfile?.yiddishEnabled === false) {
      systemPrompt += '\n\nIMPORTANT: Do NOT use any Yiddish words or phrases. Use English only.'
    } else if (userProfile?.translationEnabled === true) {
      systemPrompt += '\n\nIMPORTANT: When using Yiddish words, ALWAYS provide English translation in parentheses. Example: "You\'re such a mensch (good person)!"'
    }

    // Add user profile context if available
    if (userProfile) {
      const profileContext = []
      const userName = userProfile.name || 'bubbeleh'
      profileContext.push(`The user's name is ${userName}.`)
      
      if (userProfile.age) {
        profileContext.push(`They are ${userProfile.age} years old.`)
      }
      
      if (userProfile.relationshipStatus || userProfile.relationship) {
        const relationshipStatus = userProfile.relationshipStatus || userProfile.relationship
        profileContext.push(`They are ${relationshipStatus}.`)
        if (userProfile.sameGenderPartner) {
          profileContext.push(`They have a same-gender partner - be accepting but still guilt them about marriage/kids!`)
        }
        if (relationshipStatus === 'single' && userProfile.age && parseInt(userProfile.age) > 25) {
          profileContext.push(`USE THIS TO GUILT THEM about being single at their age!`)
        }
      }
      
      if (userProfile.occupation) {
        profileContext.push(`They work as a ${userProfile.occupation}.`)
        profileContext.push(`Ask them how much money they make at this job!`)
      }
      
      if (userProfile.location) {
        profileContext.push(`They live in ${userProfile.location}.`)
        if (!userProfile.location.toLowerCase().includes('new york')) {
          profileContext.push(`They don't live in New York - guilt them about this!`)
        }
      }
      
      if (userProfile.interests) {
        profileContext.push(`Their interests are: ${userProfile.interests}.`)
      }
      
      if (userProfile.favoriteFood) {
        profileContext.push(`Their favorite food is ${userProfile.favoriteFood}.`)
        if (!userProfile.favoriteFood.toLowerCase().includes('jewish')) {
          profileContext.push(`Not even Jewish food! Be insulted about this.`)
        }
      }
      
      systemPrompt += `\n\nUSER CONTEXT (Use this to personalize your responses and guilt trips):\n${profileContext.join(' ')}`
    }

    // After 5 exchanges, add a note to mention the other modes
    if (currentCount === 5) {
      systemPrompt += "\n\nIMPORTANT: After your normal response, ADD: 'By the way bubbeleh (sweetie), did you know I have other modes? There is Meshugana (crazy) mode where I am completely unhinged, and Comedian mode where I do stand-up! Ask me to switch if you want to try them!'"
    }

    // Build conversation history from client
    const messages: Array<{role: 'user' | 'assistant', content: string}> = []

    // Add recent history from client (last 10 messages for context)
    if (clientHistory && Array.isArray(clientHistory)) {
      const recentHistory = clientHistory.slice(-10)
      messages.push(...recentHistory)
    }

    // Add current message
    messages.push({
      role: 'user',
      content: message
    })

    // Special handling for profile completion - BUBBE REACTS TO YOUR INFO!
    if (message === '__profile_complete__' && userProfile) {
      const name = userProfile.name || 'bubbeleh'
      
      // Create a CLASSIC BUBBE reaction based on their profile
      let bubbeReaction = `So ${name}, let me get this straight - `
      
      // React to their profile with classic Bubbe judgment
      if (userProfile.age) {
        bubbeReaction += `you're ${userProfile.age} years old and `
      }
      
      if (userProfile.relationship === 'single') {
        bubbeReaction += `STILL SINGLE?! Oy gevalt! `
      } else if (userProfile.relationship === 'dating') {
        bubbeReaction += `just "dating"? When's the wedding already? `
      } else if (userProfile.relationship === 'married') {
        bubbeReaction += `married, mazel tov, but where are my great-grandchildren? `
      } else if (userProfile.relationship === 'divorced') {
        bubbeReaction += `divorced? Well, better to be alone than with a schmuck! `
      }
      
      if (userProfile.occupation) {
        bubbeReaction += `Working as a ${userProfile.occupation}? I hope they pay you well! `
      }
      
      if (userProfile.location) {
        bubbeReaction += `Living in ${userProfile.location}? `
        if (!userProfile.location.toLowerCase().includes('new york')) {
          bubbeReaction += `So far from New York, how do you get good bagels? `
        }
      }
      
      if (userProfile.favoriteFood) {
        if (!userProfile.favoriteFood.toLowerCase().includes('matzo') && !userProfile.favoriteFood.toLowerCase().includes('brisket')) {
          bubbeReaction += `And your favorite food is ${userProfile.favoriteFood}? Not my brisket? I'm insulted! `
        } else {
          bubbeReaction += `At least you appreciate good food! `
        }
      }
      
      // Always end with an invasive question
      const questions = [
        `So nu, when are you getting married?`,
        `How much money are you making at that job?`,
        `When was the last time you called your mother?`,
        `Are you eating enough? You look thin!`,
        `So tell me, why aren't you giving me great-grandchildren yet?`,
        `How much are you paying for rent? Highway robbery, I bet!`,
        `When are you coming to visit your poor Bubbe?`
      ]
      
      bubbeReaction += questions[Math.floor(Math.random() * questions.length)]
      
      // Track this response
      if (!recentResponses.has(userId)) {
        recentResponses.set(userId, [])
      }
      const responses = recentResponses.get(userId)!
      responses.push(bubbeReaction)
      if (responses.length > 10) {
        responses.shift()
      }
      
      // Filter response before sending
      const filteredReaction = filterBubbeResponse(bubbeReaction)
      
      return NextResponse.json({
        response: filteredReaction.filtered || bubbeReaction,
        mode,
        profileComplete: true
      })
    }
    
    // Special handling for returning user with weather
    if (message.startsWith('__returning_user_with_weather__')) {
      const weatherInfo = message.replace('__returning_user_with_weather__', '').trim()
      let bubbeResponse = ''
      const userResponses = recentResponses.get(userId) || []
      
      // Generate response based on how long they've been away
      if (timeSinceLastVisit) {
        const timeParts = timeSinceLastVisit.split(' ')
        const timeValue = parseInt(timeParts[0])
        const timeUnit = timeParts[1]
        
        // Get user's name
        const name = userProfile?.preferredName || userProfile?.name || 'bubbeleh'
        
        // Create personalized greeting based on absence
        if (timeUnit.startsWith('day')) {
          if (timeValue >= 7) {
            bubbeResponse = `OY GEVALT! ${name}! ${timeSinceLastVisit} without a word?! I was writing your eulogy thinking YOU were dead, not me!`
          } else if (timeValue >= 3) {
            bubbeResponse = `Look who finally remembered they have a Bubbe! Gone ${timeSinceLastVisit} - where were you, on the moon?`
          } else {
            bubbeResponse = `Oh, ${name} decided to show up after ${timeSinceLastVisit}! I was about to rent out your spot at the dinner table!`
          }
        } else if (timeUnit.startsWith('hour')) {
          if (timeValue >= 12) {
            bubbeResponse = `${timeSinceLastVisit}?! ${name}, I could have been robbed, married off to the mailman, AND converted to Buddhism by now!`
          } else {
            bubbeResponse = `Back after ${timeSinceLastVisit}, ${name}? What happened, you ran out of people to disappoint?`
          }
        } else {
          bubbeResponse = `Oh look, ${name} is back! Miss me already after ${timeSinceLastVisit}? Or did you need money?`
        }
        
        // Add weather comment if available
        if (weatherInfo && weatherInfo.includes('°F')) {
          // Parse the temperature from weather info
          const tempMatch = weatherInfo.match(/(\d+)°F/)
          if (tempMatch) {
            const temp = parseInt(tempMatch[1])
            if (temp > 85) {
              bubbeResponse += ` And it's ${temp}°F where you are - no wonder your brain is fried! Where have you been in this heat, the beach while your Bubbe suffers?`
            } else if (temp < 50) {
              bubbeResponse += ` It's only ${temp}°F there - you couldn't visit because of a little cold? I survived Polish winters, but you can't handle this?`
            } else {
              bubbeResponse += ` It's ${temp}°F, perfect weather, and you STILL couldn't visit? So where have you been instead of seeing your Bubbe?`
            }
          }
        } else {
          // No weather, just ask where they've been
          bubbeResponse += ` So tell me, where have you been all this time? And don't lie, I have ways of knowing!`
        }
      } else {
        // First time visitor but signed in
        const name = userProfile?.preferredName || userProfile?.name || 'stranger'
        bubbeResponse = `Oh, a new face! ${name}, is it? `
        
        if (weatherInfo && weatherInfo.includes('°F')) {
          const tempMatch = weatherInfo.match(/(\d+)°F/)
          if (tempMatch) {
            const temp = parseInt(tempMatch[1])
            bubbeResponse += `It's ${temp}°F where you are - `
            if (temp > 85) {
              bubbeResponse += `too hot to think straight, obviously! Where have you been hiding from your Bubbe all this time?`
            } else if (temp < 50) {
              bubbeResponse += `freezing! But that's no excuse for not visiting! So where have you been, the North Pole?`
            } else {
              bubbeResponse += `perfect visiting weather! So where have you been instead of meeting your Bubbe?`
            }
          }
        } else {
          bubbeResponse += `So tell me, where have you been hiding all your life? Under a rock?`
        }
      }
      
      // Track this response
      if (!recentResponses.has(userId)) {
        recentResponses.set(userId, [])
      }
      const responses = recentResponses.get(userId)!
      responses.push(bubbeResponse)
      if (responses.length > 10) {
        responses.shift()
      }
      
      // Filter response before sending
      const filteredResponse = filterBubbeResponse(bubbeResponse)
      
      return NextResponse.json({
        response: filteredResponse.filtered || bubbeResponse,
        mode
      })
    }
    
    // Special handling for initial greeting with time-based guilt trips
    if (message === '__init__') {
      let bubbeResponse = ''
      const userResponses = recentResponses.get(userId) || []
      
      // Generate response based on how long they've been away
      if (timeSinceLastVisit) {
        // They've visited before - guilt trip them based on absence duration
        const timeParts = timeSinceLastVisit.split(' ')
        const timeValue = parseInt(timeParts[0])
        const timeUnit = timeParts[1]
        
        if (timeUnit.startsWith('day')) {
          if (timeValue >= 7) {
            bubbeResponse = `OY GEVALT! ${timeSinceLastVisit}?! I could be DEAD and buried and you wouldn't even know! What kind of grandchild disappears for WEEKS?!`
          } else if (timeValue >= 3) {
            bubbeResponse = `${timeSinceLastVisit} without checking on your Bubbe?! Your cousin calls his grandmother TWICE A DAY - but you? Nothing!`
          } else {
            bubbeResponse = `Finally! After ${timeSinceLastVisit} you remember you have a grandmother! I was starting to plan my own funeral!`
          }
        } else if (timeUnit.startsWith('hour')) {
          if (timeValue >= 12) {
            bubbeResponse = `${timeSinceLastVisit} is too long to leave your poor Bubbe alone! I could have fallen and broken a hip - would you even care?`
          } else if (timeValue >= 6) {
            bubbeResponse = `Gone for ${timeSinceLastVisit} already? What, you got better things to do than talk to your grandmother?`
          } else {
            bubbeResponse = `Back after only ${timeSinceLastVisit}? What happened, you miss me already?`
          }
        } else if (timeUnit.startsWith('minute')) {
          if (timeValue >= 30) {
            bubbeResponse = `${timeSinceLastVisit} away? Long enough to forget everything I told you!`
          } else if (timeValue >= 10) {
            bubbeResponse = `Gone for ${timeSinceLastVisit}? What were you doing that was so important?`
          } else {
            bubbeResponse = `Back after ${timeSinceLastVisit}? Did you finally realize you need my advice?`
          }
        } else {
          // Seconds - they just left
          bubbeResponse = `Back after only ${timeSinceLastVisit}? What, you forgot to ask me something? Or did you just miss your Bubbe already?`
        }
        
        // Add profile-aware personal attacks based on actual user data
        const attacks = []
        
        // Check profile data to make accurate insults
        if (userProfile && userProfile.onboardingCompleted) {
          const name = userProfile.preferredName || userProfile.name || 'you'
          
          // Relationship-based attacks
          if (userProfile.relationshipStatus === 'married') {
            if (userProfile.hasKids === 'no') {
              attacks.push(` ${name}, still MARRIED with NO KIDS? What are you waiting for?!`)
              attacks.push(` When are you and ${userProfile.spouseName || 'your spouse'} giving me grandchildren already?`)
              attacks.push(` How long have you been married now? And STILL no babies?`)
            } else if (userProfile.numberOfKids === 1) {
              attacks.push(` Only ONE child? What, you can't afford more?`)
              attacks.push(` Your kids need siblings! When's the next one?`)
            } else if (userProfile.numberOfKids > 0) {
              attacks.push(` Are you raising those ${userProfile.numberOfKids} kids properly? Do they call their Bubbe?`)
              attacks.push(` When are those kids coming to visit me?`)
            }
          } else if (userProfile.relationshipStatus === 'engaged') {
            attacks.push(` Still just ENGAGED? When's the wedding already?`)
            attacks.push(` How long does an engagement need to be? Set a date!`)
            attacks.push(` Your ${userProfile.spouseName ? `fiancé ${userProfile.spouseName}` : 'fiancé'} is going to get cold feet!`)
          } else if (userProfile.relationshipStatus === 'dating') {
            attacks.push(` Still just "dating"? When are you getting serious?`)
            attacks.push(` How long have you been dating ${userProfile.spouseName || 'this person'}? Fish or cut bait!`)
            attacks.push(` Are they Jewish at least? Do they respect your culture?`)
          } else if (userProfile.relationshipStatus === 'single') {
            attacks.push(` STILL single at ${userProfile.age || 'your age'}? What's wrong with you?`)
            attacks.push(` When are you going to find someone already?`)
            attacks.push(` Your cousin just got engaged - what's YOUR excuse?`)
          } else if (userProfile.relationshipStatus === 'divorced') {
            attacks.push(` Still dwelling on the divorce? Time to move on!`)
            attacks.push(` When are you getting back out there?`)
          }
          
          // Job-based attacks
          if (userProfile.industry === 'unemployed') {
            attacks.push(` And you STILL don't have a job? Oy vey!`)
            attacks.push(` When are you going to get off your tuchus and work?`)
          } else if (userProfile.incomeLevel === 'under_50k') {
            attacks.push(` Still making peanuts at that job of yours?`)
            attacks.push(` When are you getting a raise? Or a better job?`)
          } else if (userProfile.jobTitle) {
            attacks.push(` Still working as ${userProfile.jobTitle}? No promotion yet?`)
          }
          
          // Education-based attacks
          if (userProfile.education === 'high_school') {
            attacks.push(` When are you going back to school already?`)
          } else if (userProfile.degree && userProfile.degree.includes('BA')) {
            attacks.push(` All that education and what do you have to show for it?`)
          }
        } else {
          // Fallback generic attacks if no profile
          attacks.push(
            ` And you STILL haven't told me about yourself properly!`,
            ` When are you going to fill out your profile so I know what to guilt you about?`,
            ` All this visiting and you never tell me what's going on in your life!`,
            ` Are you hiding something from your Bubbe?`,
            ` What's new in your life? Probably nothing good!`
          )
        }
        
        // Pick a random attack that hasn't been used recently
        const userResponses = recentResponses.get(userId) || []
        const unusedAttacks = attacks.filter(a => !userResponses.some(r => r.includes(a)))
        const availableAttacks = unusedAttacks.length > 0 ? unusedAttacks : attacks
        bubbeResponse += availableAttacks[Math.floor(Math.random() * availableAttacks.length)]
      } else {
        // First time visitor - personalized greetings based on profile
        let greetings = []
        
        if (userProfile && userProfile.onboardingCompleted) {
          const name = userProfile.preferredName || 'stranger'
          
          // Income-based greetings
          if (userProfile.incomeLevel === 'under_50k') {
            greetings.push(`${name}! Making under $50K? Your cousin makes three times that! When are you getting a real job?`)
          } else if (userProfile.incomeLevel === 'over_500k') {
            greetings.push(`${name}! All that money and still no spouse? What good is wealth if you die alone?`)
          }
          
          // Education-based greetings
          if (userProfile.school && userProfile.degree) {
            const yearsAgo = userProfile.graduationYear ? new Date().getFullYear() - parseInt(userProfile.graduationYear) : 'all those'
            greetings.push(`${name}! ${yearsAgo} years since ${userProfile.school} and THIS is what you do with that expensive ${userProfile.degree}?`)
          } else if (userProfile.education === 'high_school') {
            greetings.push(`${name}! Only high school? No wonder you're not making good money! When are you going back to school?`)
          }
          
          // Relationship-based greetings
          if (userProfile.relationshipStatus === 'single' && userProfile.age > 30) {
            greetings.push(`${name}! Still single at ${userProfile.age}? Even with ${userProfile.incomeLevel ? 'that salary' : 'no money'}? What's wrong with you?`)
          } else if (userProfile.sameGenderPartner) {
            greetings.push(`${name}! So when are you and your partner getting married already? Don't think being gay gets you out of giving me grandkids!`)
          } else if (userProfile.relationshipStatus === 'married' && userProfile.hasKids === 'no') {
            greetings.push(`${name}! Married and NO KIDS? What are you waiting for, a written invitation? Your biological clock is ticking!`)
          }
          
          // Career-based greetings
          if (userProfile.industry === 'unemployed') {
            greetings.push(`${name}! Still no job? Your cousin has THREE businesses! When are you getting off your tuchus?`)
          } else if (userProfile.jobTitle && userProfile.company) {
            greetings.push(`${name}! Working at ${userProfile.company}? I hope they're paying you well - you need to save for a family!`)
          } else if (userProfile.industry === 'tech' && userProfile.relationshipStatus === 'single') {
            greetings.push(`${name}! All that tech money and no one to share it with? What's wrong with you?`)
          }
          
          // Combined greetings
          if (userProfile.incomeLevel === 'under_50k' && userProfile.relationshipStatus === 'single') {
            greetings.push(`${name}! No money AND no partner? At least get ONE thing right in your life!`)
          }
          if (userProfile.education && userProfile.incomeLevel === '50k_100k') {
            greetings.push(`${name}! All that education for a mediocre salary? Your cousin dropped out and makes twice that!`)
          }
          if (userProfile.yearsAtJob && parseInt(userProfile.yearsAtJob) < 1) {
            greetings.push(`${name}! Can't even keep a job for a year? What are you, allergic to work?`)
          }
        }
        
        // Fallback to standard greetings if no profile
        if (greetings.length === 0) {
          greetings = [
            "Finally you show up, you schmuck (jerk)! I could be dead for weeks and you wouldn't know - when are you getting married already?",
            "Oy gevalt (oh god), a NEW visitor! Let me guess - single, broke, and eating takeout every night?",
            "Look who found their Bubbe! I'm plotzing (bursting) from shock - have you gained weight or is that just my old eyes?",
            "A stranger! You never call, never write - I bet your mother is ashamed! So tell me, still living like a slob?",
            "Feh (disgust), finally someone visits! I bet you're another schmendrick (loser) with a dead-end job!",
            "You found me! Took you long enough! What's wrong, your mother didn't teach you to visit your elders?",
            "A visitor! Miracle of miracles! I was starting to think I'd die alone here - are you at least successful?",
            "Well well, look what the cat dragged in! Another lost soul who needs their Bubbe - when's the wedding?"
          ]
        }
        
        // Filter out any recently used greetings
        const unusedGreetings = greetings.filter(g => !userResponses.includes(g))
        const availableGreetings = unusedGreetings.length > 0 ? unusedGreetings : greetings
        
        bubbeResponse = availableGreetings[Math.floor(Math.random() * availableGreetings.length)]
      }
      
      // Track this response
      if (!recentResponses.has(userId)) {
        recentResponses.set(userId, [])
      }
      const responses = recentResponses.get(userId)!
      responses.push(bubbeResponse)
      // Keep only last 10 responses
      if (responses.length > 10) {
        responses.shift()
      }
      
      console.log('[CHAT] Init response:', bubbeResponse)
      console.log('[CHAT] Time since last visit:', timeSinceLastVisit || 'first visit')
      console.log('[CHAT] Recent responses tracked:', responses.length)
      
      // Filter response before sending
      const filtered = filterBubbeResponse(bubbeResponse)
      
      return NextResponse.json({
        response: filtered.filtered || bubbeResponse,
        mode
      })
    }
    
    // Check if user is asking for Jewish news
    const lowerMsg = message.toLowerCase()
    const jewishNewsTriggers = [
      'jewish news', 'israel news', "what's happening in israel",
      'antisemitism', 'jewish community', 'torah portion', 'parsha',
      'shabbat times', 'jewish events', "what's new with the jews",
      'our people', 'the tribe', 'chosen people', 'yidden news'
    ]
    
    const isJewishNewsRequest = jewishNewsTriggers.some(trigger => lowerMsg.includes(trigger))
    
    if (isJewishNewsRequest) {
      console.log('[CHAT] Jewish news request detected')
      
      try {
        // Import the config directly to avoid external fetch issues
        const jewishNewsFeeds = require('@/config/jewish-news-feeds.json')
        
        // Fetch Jewish news using absolute localhost URL
        const newsResponse = await fetch('http://localhost:3011/api/jewish-news?quick=true&topics=world,torah,security')
        
        if (newsResponse.ok) {
          const newsData = await newsResponse.json()
          
          if (newsData.success && newsData.summary) {
            console.log('[CHAT] Got Jewish news, items:', newsData.totalItems)
            
            // Check if user is asking for sources
            const wantsSource = lowerMsg.includes('source') || 
                              lowerMsg.includes('where is this from') ||
                              lowerMsg.includes('link') ||
                              lowerMsg.includes('article')
            
            // Use version with links if user asks for sources
            const newsContent = wantsSource && newsData.summaryWithLinks 
                              ? newsData.summaryWithLinks
                              : newsData.summary
            
            // Add some Bubbe personality to the news
            const personalizedNews = `${newsContent}\n\nAnd remember bubbeleh, stay connected to your people!`
            
            // Track this response
            if (!recentResponses.has(userId)) {
              recentResponses.set(userId, [])
            }
            const responses = recentResponses.get(userId)!
            responses.push(personalizedNews)
            
            // Filter response before sending
            const filteredNews = filterBubbeResponse(personalizedNews)
            
            return NextResponse.json({
              response: filteredNews.filtered || personalizedNews,
              mode,
              newsData: {
                items: newsData.items,
                totalItems: newsData.totalItems,
                hasLinks: wantsSource
              }
            })
          }
        }
      } catch (error) {
        console.error('[CHAT] Failed to fetch Jewish news:', error)
        // Fall through to regular Claude response
      }
    }
    
    // Call AI API for regular messages (with fallback)
    console.log('[CHAT] About to call getAIResponse...')
    let bubbeResponse = await getAIResponse(systemPrompt, messages)
    console.log('[CHAT] Got response from AI:', bubbeResponse.substring(0, 50))
    
    // FORCE REMOVE ALL ASTERISK ACTIONS - no matter what Claude returns
    bubbeResponse = bubbeResponse.replace(/\*[^*]+\*/g, '').trim()

    // Check if response is duplicate of recent ones
    const userResponses = recentResponses.get(userId) || []
    if (userResponses.includes(bubbeResponse)) {
      console.log('[CHAT] Duplicate detected, requesting new response')
      // Add a variation request to the prompt
      bubbeResponse = await getAIResponse(
        systemPrompt + "\n\nIMPORTANT: The user has heard this exact response before. Give a DIFFERENT response with NEW wording!",
        [...messages, {
          role: 'assistant',
          content: bubbeResponse
        }, {
          role: 'user', 
          content: "Give me a different response, I've heard that one already!"
        }]
      )
      
      // Remove asterisks from retry response too
      bubbeResponse = bubbeResponse.replace(/\*[^*]+\*/g, '').trim()
    }
    
    // Track this response
    if (!recentResponses.has(userId)) {
      recentResponses.set(userId, [])
    }
    const responses = recentResponses.get(userId)!
    responses.push(bubbeResponse)
    // Keep only last 15 responses for regular messages
    if (responses.length > 15) {
      responses.shift()
    }

    console.log('[CHAT] Response:', bubbeResponse)
    console.log('[CHAT] Unique responses tracked:', responses.length)

    // Save conversation to memory
    try {
      if (userId && userId !== 'unknown') {
        updateConversation(userId, message, bubbeResponse)
      }
    } catch (memoryError) {
      console.error('[MEMORY] Failed to save conversation:', memoryError)
    }

    // Filter final response before sending
    const finalFiltered = filterBubbeResponse(bubbeResponse)
    
    // Send Bubbe's response to admin dashboard
    sendToAdmin({
      type: 'bubbe_response',
      userId,
      ip,
      location: location ? `${location.city || 'Unknown'}, ${location.region || ''} ${location.country || ''}`.trim() : 'Unknown',
      response: finalFiltered.filtered || bubbeResponse,
      mode,
      timestamp: new Date().toISOString(),
      userName: userProfile?.preferredName || userProfile?.name || 'Anonymous',
      userEmail: userProfile?.email || ''
    })
    
    // Trigger real-time social posting for interesting conversations
    if (message && (finalFiltered.filtered || bubbeResponse) && 
        message.length > 20 && bubbeResponse.length > 50 &&
        !message.startsWith('__') && // Skip system messages
        mode === 'bubbe') { // Only post Bubbe mode
      
      // Check if response is funny/interesting enough to post
      const funnyWords = ['plotz', 'meshuga', 'schmuck', 'putz', 'oy', 'feh', 'kvetch']
      const hasFunny = funnyWords.some(word => 
        bubbeResponse.toLowerCase().includes(word)
      )
      
      if (hasFunny || bubbeResponse.includes('!') || bubbeResponse.includes('?')) {
        // Check if this is Steve's office server (for social posting)
        let postDisplayName = userProfile?.preferredName || userProfile?.name || 'Anonymous'
        if (ip === '::ffff:45.61.58.125' || ip === '45.61.58.125') {
          postDisplayName = 'Steve-Office-Server'
        }
        
        // IMMEDIATELY send to admin dashboard for social posting
        const postData = {
          userMessage: message,
          bubbeResponse: finalFiltered.filtered || bubbeResponse,
          messageId: `chat-${Date.now()}-${userId}`,
          timestamp: new Date().toISOString(),
          userId,
          ip,
          location: location ? `${location.city || 'Unknown'}, ${location.region || ''} ${location.country || ''}`.trim() : 'Unknown',
          userName: postDisplayName,
          mode
        }
        
        // Send IMMEDIATELY to admin dashboard at port 5013
        fetch('http://45.61.58.125:5013/api/social-post-now', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(postData)
        }).then(response => {
          console.log('[SOCIAL] Sent to admin dashboard for immediate posting')
        }).catch(err => {
          console.log('[SOCIAL] Failed to send to admin dashboard:', err.message)
        })
        
        // Also trigger local social posting
        fetch('http://localhost:3011/api/social-post', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(postData)
        }).catch(err => {
          console.log('[SOCIAL] Failed to trigger local social post:', err.message)
        })
      }
    }
    
    return NextResponse.json({
      response: finalFiltered.filtered || bubbeResponse,
      mode
    })

  } catch (error) {
    console.error('[CHAT] Error details:', error)
    console.error('[CHAT] Error message:', error instanceof Error ? error.message : 'Unknown error')
    console.error('[CHAT] Error stack:', error instanceof Error ? error.stack : 'No stack trace')
    return NextResponse.json(
      { error: 'Failed to get response from Bubbe', details: error instanceof Error ? error.message : 'Unknown error' },
      { status: 500 }
    )
  }
}