[object Object]

← back to Dear Bubbe Nextjs

auto-save: 2026-07-16T17:43:55 (3 files) — app/api/social-post/route.ts components/SimpleConversation.tsx components/SimplifiedChat.tsx

0dbdfa0019c3caf0a3aebac2baabac433e23c3a3 · 2026-07-16 17:43:58 -0700 · Steve Abrams

Files touched

Diff

commit 0dbdfa0019c3caf0a3aebac2baabac433e23c3a3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 16 17:43:58 2026 -0700

    auto-save: 2026-07-16T17:43:55 (3 files) — app/api/social-post/route.ts components/SimpleConversation.tsx components/SimplifiedChat.tsx
---
 app/api/social-post/route.ts      |   7 +-
 components/SimpleConversation.tsx | 371 ------------------------------
 components/SimplifiedChat.tsx     | 461 --------------------------------------
 3 files changed, 5 insertions(+), 834 deletions(-)

diff --git a/app/api/social-post/route.ts b/app/api/social-post/route.ts
index a55a468..ad9085e 100644
--- a/app/api/social-post/route.ts
+++ b/app/api/social-post/route.ts
@@ -39,8 +39,11 @@ export async function POST(request: NextRequest) {
     console.log('[SOCIAL-POST] User message:', userMessage.substring(0, 50))
     console.log('[SOCIAL-POST] Bubbe response:', bubbeResponse.substring(0, 50))
 
-    // Save to chat history for the auto-poster to process
-    const chatHistoryPath = '/root/Projects/dear-bubbe-nextjs/logs/chat-history.json'
+    // Save to chat history for the auto-poster to process.
+    // Use cwd (not a hardcoded absolute path) so it works on any host, and
+    // ensure the logs/ dir exists — writeFile can't create parent dirs (was ENOENT).
+    const chatHistoryPath = path.join(process.cwd(), 'logs', 'chat-history.json')
+    await fs.mkdir(path.dirname(chatHistoryPath), { recursive: true })
     let chats = []
     
     try {
diff --git a/components/SimpleConversation.tsx b/components/SimpleConversation.tsx
deleted file mode 100644
index c04d07b..0000000
--- a/components/SimpleConversation.tsx
+++ /dev/null
@@ -1,371 +0,0 @@
-'use client'
-
-import { useState, useEffect, useRef } from 'react'
-import { Mic, MessageSquare, Send, Volume2, VolumeX } from 'lucide-react'
-
-interface Message {
-  id: string
-  role: 'user' | 'assistant'
-  content: string
-  timestamp: Date
-}
-
-interface SimpleConversationProps {
-  currentMode: string
-  className?: string
-}
-
-export default function SimpleConversation({ currentMode, className = '' }: SimpleConversationProps) {
-  const [messages, setMessages] = useState<Message[]>([])
-  const [isListening, setIsListening] = useState(false)
-  const [isSpeaking, setIsSpeaking] = useState(false)
-  const [isLoading, setIsLoading] = useState(false)
-  const [textInput, setTextInput] = useState('')
-  const [voiceEnabled, setVoiceEnabled] = useState(true)
-  const [conversationStarted, setConversationStarted] = useState(false)
-  const [inputMethod, setInputMethod] = useState<'text' | 'voice' | null>(null)
-  
-  const messagesEndRef = useRef<HTMLDivElement>(null)
-  const recognitionRef = useRef<any>(null)
-  const synthRef = useRef<SpeechSynthesis | null>(null)
-
-  useEffect(() => {
-    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
-  }, [messages])
-
-  useEffect(() => {
-    // Initialize speech synthesis
-    if (typeof window !== 'undefined' && 'speechSynthesis' in window) {
-      synthRef.current = window.speechSynthesis
-    }
-
-    // Initialize speech recognition
-    if (typeof window !== 'undefined' && ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)) {
-      const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition
-      recognitionRef.current = new SpeechRecognition()
-      recognitionRef.current.continuous = false
-      recognitionRef.current.interimResults = false
-      recognitionRef.current.lang = 'en-US'
-
-      recognitionRef.current.onresult = (event: any) => {
-        const transcript = event.results[0][0].transcript
-        handleUserMessage(transcript, 'voice')
-        setIsListening(false)
-      }
-
-      recognitionRef.current.onerror = (event: any) => {
-        console.error('Speech recognition error:', event.error)
-        setIsListening(false)
-        if (event.error === 'not-allowed') {
-          alert('Microphone permission denied. Please allow microphone access.')
-        }
-      }
-
-      recognitionRef.current.onend = () => {
-        setIsListening(false)
-      }
-    }
-
-    return () => {
-      if (recognitionRef.current) {
-        recognitionRef.current.abort()
-      }
-      if (synthRef.current) {
-        synthRef.current.cancel()
-      }
-    }
-  }, [])
-
-  const handleUserMessage = async (content: string, source: 'voice' | 'text') => {
-    if (!content.trim()) return
-
-    // Add user message
-    const userMessage: Message = {
-      id: Date.now().toString(),
-      role: 'user',
-      content: content.trim(),
-      timestamp: new Date()
-    }
-    setMessages(prev => [...prev, userMessage])
-    setTextInput('')
-    setIsLoading(true)
-
-    try {
-      // Call API
-      const response = await fetch('/api/chat', {
-        method: 'POST',
-        headers: { 'Content-Type': 'application/json' },
-        body: JSON.stringify({
-          message: content.trim(),
-          mode: currentMode,
-          conversationHistory: messages.slice(-5)
-        })
-      })
-
-      if (!response.ok) throw new Error('Failed to get response')
-
-      const data = await response.json()
-      
-      // Add assistant message
-      const assistantMessage: Message = {
-        id: (Date.now() + 1).toString(),
-        role: 'assistant',
-        content: data.response,
-        timestamp: new Date()
-      }
-      setMessages(prev => [...prev, assistantMessage])
-
-      // Speak response if voice is enabled
-      if (voiceEnabled && synthRef.current) {
-        speakText(data.response)
-      }
-
-      // If using voice, automatically start listening again after speaking
-      if (source === 'voice' && inputMethod === 'voice') {
-        setTimeout(() => {
-          startListening()
-        }, 500)
-      }
-
-    } catch (error) {
-      console.error('Error:', error)
-      const errorMessage: Message = {
-        id: (Date.now() + 1).toString(),
-        role: 'assistant',
-        content: "Oy vey! Something's meshuga with the computer. Try again!",
-        timestamp: new Date()
-      }
-      setMessages(prev => [...prev, errorMessage])
-    } finally {
-      setIsLoading(false)
-    }
-  }
-
-  const speakText = (text: string) => {
-    if (!synthRef.current || !voiceEnabled) return
-
-    synthRef.current.cancel()
-    setIsSpeaking(true)
-
-    const utterance = new SpeechSynthesisUtterance(text)
-    utterance.rate = 0.9
-    utterance.pitch = 1.1
-    utterance.onend = () => {
-      setIsSpeaking(false)
-      // Auto-listen after speaking if in voice mode
-      if (inputMethod === 'voice') {
-        setTimeout(() => startListening(), 300)
-      }
-    }
-
-    synthRef.current.speak(utterance)
-  }
-
-  const startListening = () => {
-    if (!recognitionRef.current || isListening) return
-    
-    try {
-      setIsListening(true)
-      recognitionRef.current.start()
-    } catch (error: any) {
-      console.error('Error starting recognition:', error)
-      setIsListening(false)
-      
-      if (error.message?.includes('already started')) {
-        recognitionRef.current.abort()
-        setTimeout(() => {
-          try {
-            setIsListening(true)
-            recognitionRef.current.start()
-          } catch (e) {
-            console.error('Error restarting:', e)
-            setIsListening(false)
-          }
-        }, 100)
-      }
-    }
-  }
-
-  const stopListening = () => {
-    if (recognitionRef.current) {
-      recognitionRef.current.abort()
-    }
-    setIsListening(false)
-  }
-
-  const handleMethodSelect = (method: 'text' | 'voice') => {
-    setInputMethod(method)
-    setConversationStarted(true)
-    
-    if (method === 'voice') {
-      // Start listening immediately for voice
-      setTimeout(() => startListening(), 500)
-    }
-  }
-
-  // Initial selection screen
-  if (!conversationStarted) {
-    return (
-      <div className="flex flex-col items-center justify-center h-full p-4 sm:p-8">
-        <div className="text-5xl sm:text-4xl mb-4 sm:mb-6">🥯</div>
-        <h2 className="text-xl sm:text-2xl font-bold text-white mb-2 text-center">Nu, how do you want to kvetch?</h2>
-        <p className="text-white/70 mb-6 sm:mb-8 text-center">Choose how to talk to Bubbe</p>
-        
-        <div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4 w-full max-w-md">
-          <button
-            onClick={() => handleMethodSelect('text')}
-            className="p-5 sm:p-6 bg-white/10 hover:bg-white/20 active:bg-white/30 rounded-xl border border-white/20 transition-all hover:scale-105 active:scale-95 group touch-manipulation"
-            style={{ WebkitTapHighlightColor: 'transparent' }}
-          >
-            <MessageSquare className="w-10 h-10 sm:w-12 sm:h-12 text-white mb-2 sm:mb-3 mx-auto" />
-            <h3 className="text-base sm:text-lg font-semibold text-white mb-1">Type</h3>
-            <p className="text-xs sm:text-sm text-white/70">Type your messages</p>
-          </button>
-          
-          <button
-            onClick={() => handleMethodSelect('voice')}
-            className="p-5 sm:p-6 bg-white/10 hover:bg-white/20 active:bg-white/30 rounded-xl border border-white/20 transition-all hover:scale-105 active:scale-95 group touch-manipulation"
-            style={{ WebkitTapHighlightColor: 'transparent' }}
-          >
-            <Mic className="w-10 h-10 sm:w-12 sm:h-12 text-white mb-2 sm:mb-3 mx-auto" />
-            <h3 className="text-base sm:text-lg font-semibold text-white mb-1">Talk</h3>
-            <p className="text-xs sm:text-sm text-white/70">Have a conversation</p>
-          </button>
-        </div>
-      </div>
-    )
-  }
-
-  // Conversation interface
-  return (
-    <div className={`flex flex-col h-full ${className}`}>
-      {/* Header */}
-      <div className="p-3 sm:p-4 bg-black/20 border-b border-white/10">
-        <div className="flex items-center justify-between">
-          <div className="flex items-center space-x-2 sm:space-x-3">
-            <span className="text-xl sm:text-2xl">🥯</span>
-            <div>
-              <h3 className="text-white font-semibold text-sm sm:text-base">Dear Bubbe</h3>
-              <p className="text-white/60 text-xs sm:text-sm">
-                {inputMethod === 'voice' ? 'Speaking mode' : 'Typing mode'}
-              </p>
-            </div>
-          </div>
-          
-          <button
-            onClick={() => setVoiceEnabled(!voiceEnabled)}
-            className={`p-2 rounded-lg transition-colors touch-manipulation ${
-              voiceEnabled ? 'bg-green-600 hover:bg-green-700 active:bg-green-800' : 'bg-gray-600 hover:bg-gray-700 active:bg-gray-800'
-            }`}
-            style={{ WebkitTapHighlightColor: 'transparent' }}
-          >
-            {voiceEnabled ? <Volume2 className="w-4 h-4 sm:w-5 sm:h-5 text-white" /> : <VolumeX className="w-4 h-4 sm:w-5 sm:h-5 text-white" />}
-          </button>
-        </div>
-      </div>
-
-      {/* Messages */}
-      <div className="flex-1 overflow-y-auto p-3 sm:p-4 space-y-3 sm:space-y-4 -webkit-overflow-scrolling-touch">
-        {messages.length === 0 ? (
-          <div className="text-center text-white/60 mt-6 sm:mt-8 px-4">
-            <p className="text-base sm:text-lg mb-2">
-              {inputMethod === 'voice' 
-                ? "I'm listening... Nu, what's on your mind?"
-                : "Type already! What are you waiting for, an invitation?"}
-            </p>
-          </div>
-        ) : (
-          messages.map((message) => (
-            <div
-              key={message.id}
-              className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
-            >
-              <div
-                className={`max-w-[85%] sm:max-w-[80%] rounded-2xl px-3 sm:px-4 py-2 sm:py-3 ${
-                  message.role === 'user'
-                    ? 'bg-purple-600 text-white'
-                    : 'bg-white/20 text-white'
-                }`}
-              >
-                <p className="text-sm sm:text-sm leading-relaxed break-words">{message.content}</p>
-                <p className="text-xs opacity-70 mt-1">
-                  {message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
-                </p>
-              </div>
-            </div>
-          ))
-        )}
-        {isLoading && (
-          <div className="flex justify-start">
-            <div className="bg-white/20 rounded-2xl px-4 py-3">
-              <div className="flex space-x-2">
-                <div className="w-2 h-2 bg-white/60 rounded-full animate-bounce" />
-                <div className="w-2 h-2 bg-white/60 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }} />
-                <div className="w-2 h-2 bg-white/60 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }} />
-              </div>
-            </div>
-          </div>
-        )}
-        <div ref={messagesEndRef} />
-      </div>
-
-      {/* Input area */}
-      <div className="p-3 sm:p-4 bg-black/20 border-t border-white/10 safe-area-inset-bottom">
-        {inputMethod === 'text' ? (
-          // Text input
-          <div className="flex space-x-2 sm:space-x-3">
-            <input
-              type="text"
-              value={textInput}
-              onChange={(e) => setTextInput(e.target.value)}
-              onKeyPress={(e) => {
-                if (e.key === 'Enter' && !e.shiftKey) {
-                  e.preventDefault()
-                  if (textInput.trim()) {
-                    handleUserMessage(textInput, 'text')
-                  }
-                }
-              }}
-              placeholder="Type your message..."
-              className="flex-1 bg-white/10 border border-white/20 rounded-lg px-3 sm:px-4 py-2.5 sm:py-3 text-white placeholder-white/50 focus:outline-none focus:border-white/40 text-base"
-              style={{ fontSize: '16px' }} // Prevent iOS zoom on focus
-              autoComplete="off"
-              autoCorrect="on"
-              autoCapitalize="sentences"
-            />
-            <button
-              onClick={() => textInput.trim() && handleUserMessage(textInput, 'text')}
-              disabled={!textInput.trim() || isLoading}
-              className="p-2.5 sm:p-3 bg-purple-600 hover:bg-purple-700 active:bg-purple-800 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed transition-colors touch-manipulation"
-              style={{ WebkitTapHighlightColor: 'transparent' }}
-            >
-              <Send className="w-5 h-5 text-white" />
-            </button>
-          </div>
-        ) : (
-          // Voice controls
-          <div className="flex flex-col items-center py-2">
-            <button
-              onClick={isListening ? stopListening : startListening}
-              disabled={isLoading || isSpeaking}
-              className={`p-5 sm:p-6 rounded-full transition-all transform touch-manipulation ${
-                isListening 
-                  ? 'bg-red-600 hover:bg-red-700 active:bg-red-800 scale-110 animate-pulse' 
-                  : 'bg-purple-600 hover:bg-purple-700 active:bg-purple-800 hover:scale-105 active:scale-95'
-              } disabled:opacity-50 disabled:cursor-not-allowed`}
-              style={{ WebkitTapHighlightColor: 'transparent' }}
-            >
-              <Mic className="w-7 h-7 sm:w-8 sm:h-8 text-white" />
-            </button>
-            <p className="text-white/70 mt-2 sm:mt-3 text-sm">
-              {isListening ? 'Listening...' :
-               isSpeaking ? 'Bubbe is talking...' :
-               isLoading ? 'Thinking...' :
-               'Tap to speak'}
-            </p>
-          </div>
-        )}
-      </div>
-    </div>
-  )
-}
\ No newline at end of file
diff --git a/components/SimplifiedChat.tsx b/components/SimplifiedChat.tsx
deleted file mode 100644
index 72e6b2c..0000000
--- a/components/SimplifiedChat.tsx
+++ /dev/null
@@ -1,461 +0,0 @@
-'use client'
-
-import { useState, useEffect, useRef } from 'react'
-import { Mic, MicOff, Send, Volume2, VolumeX } from 'lucide-react'
-
-interface Message {
-  id: string
-  role: 'user' | 'assistant'
-  content: string
-  timestamp: Date
-}
-
-interface SimplifiedChatProps {
-  currentMode: string
-  className?: string
-}
-
-export default function SimplifiedChat({ currentMode, className = '' }: SimplifiedChatProps) {
-  const [messages, setMessages] = useState<Message[]>([])
-  const [isListening, setIsListening] = useState(false)
-  const [isSpeaking, setIsSpeaking] = useState(false)
-  const [isLoading, setIsLoading] = useState(false)
-  const [textInput, setTextInput] = useState('')
-  const [voiceEnabled, setVoiceEnabled] = useState(true)
-  const [inputMode, setInputMode] = useState<'voice' | 'text'>('voice')
-  const messagesEndRef = useRef<HTMLDivElement>(null)
-  const recognitionRef = useRef<any>(null)
-  const synthRef = useRef<SpeechSynthesis | null>(null)
-
-  useEffect(() => {
-    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
-  }, [messages])
-
-  useEffect(() => {
-    // Initialize speech synthesis
-    if (typeof window !== 'undefined' && 'speechSynthesis' in window) {
-      synthRef.current = window.speechSynthesis
-    }
-
-    // Initialize speech recognition
-    if (typeof window !== 'undefined' && ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)) {
-      const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition
-      recognitionRef.current = new SpeechRecognition()
-      recognitionRef.current.continuous = false
-      recognitionRef.current.interimResults = false
-      recognitionRef.current.lang = 'en-US'
-
-      recognitionRef.current.onresult = (event: any) => {
-        const transcript = event.results[0][0].transcript
-        handleUserMessage(transcript, 'voice')
-        setIsListening(false)
-      }
-
-      recognitionRef.current.onerror = (event: any) => {
-        console.error('Speech recognition error:', event.error)
-        setIsListening(false)
-        // Show user-friendly error message
-        if (event.error === 'not-allowed') {
-          alert('Microphone permission denied. Please allow microphone access and try again.')
-        } else if (event.error === 'no-speech') {
-          // Silently handle no-speech errors
-        } else {
-          console.log('Speech recognition error:', event.error)
-        }
-      }
-
-      recognitionRef.current.onend = () => {
-        setIsListening(false)
-      }
-    }
-
-    return () => {
-      if (recognitionRef.current) {
-        recognitionRef.current.abort()
-      }
-      if (synthRef.current) {
-        synthRef.current.cancel()
-      }
-    }
-  }, [])
-
-  const handleUserMessage = async (content: string, source: 'voice' | 'text') => {
-    if (!content.trim()) return
-
-    // Add user message
-    const userMessage: Message = {
-      id: Date.now().toString(),
-      role: 'user',
-      content: content.trim(),
-      timestamp: new Date()
-    }
-    setMessages(prev => [...prev, userMessage])
-    setTextInput('')
-    setIsLoading(true)
-
-    try {
-      // Call API with current mode
-      const response = await fetch('/api/chat', {
-        method: 'POST',
-        headers: { 'Content-Type': 'application/json' },
-        body: JSON.stringify({
-          message: content.trim(),
-          mode: currentMode,
-          conversationHistory: messages.slice(-5) // Send last 5 messages for context
-        })
-      })
-
-      if (!response.ok) throw new Error('Failed to get response')
-
-      const data = await response.json()
-      
-      // Add assistant message
-      const assistantMessage: Message = {
-        id: (Date.now() + 1).toString(),
-        role: 'assistant',
-        content: data.response,
-        timestamp: new Date()
-      }
-      setMessages(prev => [...prev, assistantMessage])
-
-      // Speak response if voice is enabled
-      if (voiceEnabled && synthRef.current) {
-        speakText(data.response)
-      }
-
-    } catch (error) {
-      console.error('Error getting response:', error)
-      const errorMessage: Message = {
-        id: (Date.now() + 1).toString(),
-        role: 'assistant',
-        content: "Oy vey! Something's wrong with the schmuck computer. Try again!",
-        timestamp: new Date()
-      }
-      setMessages(prev => [...prev, errorMessage])
-    } finally {
-      setIsLoading(false)
-    }
-  }
-
-  const speakText = (text: string) => {
-    if (!synthRef.current || !voiceEnabled) return
-
-    // Cancel any ongoing speech
-    synthRef.current.cancel()
-    setIsSpeaking(true)
-
-    const utterance = new SpeechSynthesisUtterance(text)
-    utterance.rate = 0.9
-    utterance.pitch = 1.1
-    utterance.volume = 0.8
-
-    utterance.onend = () => {
-      setIsSpeaking(false)
-    }
-
-    utterance.onerror = () => {
-      setIsSpeaking(false)
-    }
-
-    synthRef.current.speak(utterance)
-  }
-
-  const startListening = async () => {
-    if (!recognitionRef.current) {
-      alert('Speech recognition not supported in your browser. Please use Chrome, Edge, or Safari.')
-      return
-    }
-    
-    if (isListening) return
-    
-    try {
-      // Check for microphone permissions with proper fallback
-      if (navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
-        try {
-          const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
-          // Stop the stream immediately as we just needed to check permissions
-          stream.getTracks().forEach(track => track.stop())
-        } catch (permError) {
-          console.log('Microphone permission check failed:', permError)
-          // Continue anyway - the speech recognition API will handle permissions
-        }
-      }
-      
-      setIsListening(true)
-      recognitionRef.current.start()
-    } catch (error: any) {
-      console.error('Error starting speech recognition:', error)
-      setIsListening(false)
-      
-      if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
-        alert('Microphone permission denied. Please allow microphone access and try again.')
-      } else if (error.message && error.message.includes('already started')) {
-        // If already started, stop and restart
-        recognitionRef.current.abort()
-        setTimeout(() => {
-          try {
-            setIsListening(true)
-            recognitionRef.current.start()
-          } catch (e) {
-            console.error('Error restarting recognition:', e)
-            setIsListening(false)
-          }
-        }, 100)
-      } else {
-        alert('Error accessing microphone. Please check your settings and try again.')
-      }
-    }
-  }
-
-  const stopListening = () => {
-    if (recognitionRef.current) {
-      recognitionRef.current.abort()
-    }
-    setIsListening(false)
-  }
-
-  const stopSpeaking = () => {
-    if (synthRef.current) {
-      synthRef.current.cancel()
-    }
-    setIsSpeaking(false)
-  }
-
-  const handleTextSubmit = () => {
-    if (textInput.trim()) {
-      handleUserMessage(textInput, 'text')
-    }
-  }
-
-  const handleKeyPress = (e: React.KeyboardEvent) => {
-    if (e.key === 'Enter' && !e.shiftKey) {
-      e.preventDefault()
-      handleTextSubmit()
-    }
-  }
-
-  const getModeTheme = () => {
-    switch (currentMode) {
-      case 'bubbe':
-        return {
-          gradient: 'from-purple-600 to-pink-600',
-          accent: 'bg-purple-500',
-          userBubble: 'bg-purple-600',
-          assistantBubble: 'bg-white/20'
-        }
-      case 'meshugana':
-        return {
-          gradient: 'from-orange-500 to-red-500',
-          accent: 'bg-orange-500',
-          userBubble: 'bg-orange-600',
-          assistantBubble: 'bg-white/20'
-        }
-      case 'comedian':
-        return {
-          gradient: 'from-blue-500 to-indigo-600',
-          accent: 'bg-blue-500',
-          userBubble: 'bg-blue-600',
-          assistantBubble: 'bg-white/20'
-        }
-      default:
-        return {
-          gradient: 'from-purple-600 to-pink-600',
-          accent: 'bg-purple-500',
-          userBubble: 'bg-purple-600',
-          assistantBubble: 'bg-white/20'
-        }
-    }
-  }
-
-  const theme = getModeTheme()
-
-  return (
-    <div className={`flex flex-col h-full ${className}`}>
-      {/* Messages Container */}
-      <div className="flex-1 overflow-y-auto p-4 space-y-4 min-h-0">
-        {messages.length === 0 ? (
-          <div className="flex-1 flex items-center justify-center text-center">
-            <div className="max-w-sm">
-              <div className="text-4xl mb-4">
-                {currentMode === 'bubbe' ? '🥯' : currentMode === 'meshugana' ? '🤪' : '🎤'}
-              </div>
-              <p className="text-white/80 text-lg font-medium mb-2">
-                {currentMode === 'bubbe' ? 'Nu, so what\'s your question?' :
-                 currentMode === 'meshugana' ? 'You think I got all day?' :
-                 'Ready for some laughs?'}
-              </p>
-              <p className="text-white/60 text-sm">
-                {inputMode === 'voice' ? 'Tap the mic and start talking!' : 'Type your message below'}
-              </p>
-            </div>
-          </div>
-        ) : (
-          messages.map((message) => (
-            <div
-              key={message.id}
-              className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
-            >
-              <div
-                className={`
-                  max-w-[85%] p-3 rounded-2xl
-                  ${message.role === 'user'
-                    ? `${theme.userBubble} text-white rounded-br-md`
-                    : `${theme.assistantBubble} text-white rounded-bl-md backdrop-blur-sm`
-                  }
-                `}
-              >
-                <p className="text-sm leading-relaxed whitespace-pre-wrap">
-                  {message.content}
-                </p>
-                <p className="text-xs opacity-70 mt-1">
-                  {message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
-                </p>
-              </div>
-            </div>
-          ))
-        )}
-        
-        {/* Loading indicator */}
-        {isLoading && (
-          <div className="flex justify-start">
-            <div className={`${theme.assistantBubble} backdrop-blur-sm p-3 rounded-2xl rounded-bl-md`}>
-              <div className="flex space-x-1">
-                <div className="w-2 h-2 bg-white rounded-full animate-bounce"></div>
-                <div className="w-2 h-2 bg-white rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
-                <div className="w-2 h-2 bg-white rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
-              </div>
-            </div>
-          </div>
-        )}
-        
-        <div ref={messagesEndRef} />
-      </div>
-
-      {/* Input Controls */}
-      <div className="p-4 bg-black/20 backdrop-blur-sm">
-        {/* Mode Toggle */}
-        <div className="flex justify-center mb-3">
-          <div className="flex bg-black/30 rounded-full p-1">
-            <button
-              onClick={() => setInputMode('voice')}
-              className={`
-                px-4 py-2 rounded-full text-sm font-medium transition-all duration-200
-                ${inputMode === 'voice'
-                  ? `bg-gradient-to-r ${theme.gradient} text-white`
-                  : 'text-white/70 hover:text-white'
-                }
-              `}
-            >
-              <Mic className="w-4 h-4 inline mr-2" />
-              Voice
-            </button>
-            <button
-              onClick={() => setInputMode('text')}
-              className={`
-                px-4 py-2 rounded-full text-sm font-medium transition-all duration-200
-                ${inputMode === 'text'
-                  ? `bg-gradient-to-r ${theme.gradient} text-white`
-                  : 'text-white/70 hover:text-white'
-                }
-              `}
-            >
-              <Send className="w-4 h-4 inline mr-2" />
-              Text
-            </button>
-          </div>
-        </div>
-
-        {/* Voice Controls */}
-        {inputMode === 'voice' && (
-          <div className="flex items-center justify-center space-x-4">
-            <button
-              onClick={() => setVoiceEnabled(!voiceEnabled)}
-              className={`
-                p-3 rounded-full transition-all duration-200
-                ${voiceEnabled ? 'bg-green-600 hover:bg-green-700' : 'bg-gray-600 hover:bg-gray-700'}
-              `}
-            >
-              {voiceEnabled ? <Volume2 className="w-5 h-5 text-white" /> : <VolumeX className="w-5 h-5 text-white" />}
-            </button>
-
-            <button
-              onClick={isSpeaking ? stopSpeaking : (isListening ? stopListening : startListening)}
-              disabled={isLoading}
-              className={`
-                p-6 rounded-full transition-all duration-200 transform
-                ${isListening
-                  ? 'bg-red-600 hover:bg-red-700 scale-110 shadow-lg shadow-red-600/30'
-                  : isSpeaking
-                  ? 'bg-blue-600 hover:bg-blue-700 animate-pulse'
-                  : `bg-gradient-to-r ${theme.gradient} hover:scale-105`
-                }
-                ${isLoading ? 'opacity-50 cursor-not-allowed' : 'hover:shadow-lg'}
-                disabled:hover:scale-100 disabled:hover:shadow-none
-              `}
-            >
-              {isSpeaking ? (
-                <Volume2 className="w-7 h-7 text-white" />
-              ) : isListening ? (
-                <div className="relative">
-                  <Mic className="w-7 h-7 text-white" />
-                  <div className="absolute inset-0 bg-red-500 rounded-full animate-ping opacity-30"></div>
-                </div>
-              ) : (
-                <Mic className="w-7 h-7 text-white" />
-              )}
-            </button>
-
-            <div className="text-center min-w-[80px]">
-              <p className="text-white/80 text-sm font-medium">
-                {isSpeaking ? 'Speaking...' :
-                 isListening ? 'Listening...' :
-                 isLoading ? 'Thinking...' :
-                 'Tap to talk'}
-              </p>
-            </div>
-          </div>
-        )}
-
-        {/* Text Input */}
-        {inputMode === 'text' && (
-          <div className="flex space-x-3">
-            <div className="flex-1 relative">
-              <textarea
-                value={textInput}
-                onChange={(e) => setTextInput(e.target.value)}
-                onKeyDown={handleKeyPress}
-                placeholder={
-                  currentMode === 'bubbe' ? 'Nu, what\'s your question?' :
-                  currentMode === 'meshugana' ? 'Spit it out already!' :
-                  'What\'s the deal with...?'
-                }
-                disabled={isLoading}
-                className="
-                  w-full bg-white/10 backdrop-blur-sm border border-white/20 rounded-2xl
-                  px-4 py-3 text-white placeholder-white/50 resize-none
-                  focus:outline-none focus:ring-2 focus:ring-white/30 focus:border-transparent
-                  min-h-[50px] max-h-[120px]
-                  disabled:opacity-50 disabled:cursor-not-allowed
-                "
-                rows={1}
-              />
-            </div>
-            <button
-              onClick={handleTextSubmit}
-              disabled={!textInput.trim() || isLoading}
-              className={`
-                p-3 rounded-full transition-all duration-200
-                ${textInput.trim() && !isLoading
-                  ? `bg-gradient-to-r ${theme.gradient} hover:scale-105 shadow-lg`
-                  : 'bg-white/20 cursor-not-allowed opacity-50'
-                }
-              `}
-            >
-              <Send className="w-5 h-5 text-white" />
-            </button>
-          </div>
-        )}
-      </div>
-    </div>
-  )
-}
\ No newline at end of file

← 84b91bc fix(csp): allow Cloudflare Insights beacon (+ GA4 regional)  ·  back to Dear Bubbe Nextjs  ·  debug+refactor(bubbe): fix social-post ENOENT, extract share 6ac4af5 →