"use client"; import React, { useState, useRef, useEffect } from "react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import Image from "next/image"; import { Message } from "../lib/types"; import { Send, Copy, Check, ChevronDown, ChevronRight, Sparkles, Terminal, PhoneCall, ArrowRight, Activity, Cpu, Volume2, Square, Loader2, Mic, MicOff, RotateCcw, Briefcase, HeartPulse, Layers, Scale } from "lucide-react"; const getApiBaseUrl = (): string => { if (process.env.NEXT_PUBLIC_API_URL) { return process.env.NEXT_PUBLIC_API_URL.replace(/\/$/, ""); } if (typeof window !== "undefined") { if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") { return "http://localhost:8000"; } return window.location.origin; } return "http://localhost:8000"; }; const API_BASE_URL = getApiBaseUrl(); interface ChatPanelProps { messages: Message[]; onSendMessage: (text: string) => void; isStreaming: boolean; onResetSession: () => void; activePrompt: string; setActivePrompt: (prompt: string) => void; openHandoffModal: () => void; isAdminMode?: boolean; onSendAdminMessage?: (text: string) => void; tutorConfig?: any; } export const ChatPanel: React.FC = ({ messages, onSendMessage, isStreaming, onResetSession, activePrompt, setActivePrompt, openHandoffModal, isAdminMode, onSendAdminMessage, tutorConfig, }) => { const [inputText, setInputText] = useState(""); const [adminInputText, setAdminInputText] = useState(""); const [expandedThoughts, setExpandedThoughts] = useState>({}); const [copiedId, setCopiedId] = useState(null); const [playingMessageId, setPlayingMessageId] = useState(null); const [isLoadingAudio, setIsLoadingAudio] = useState(null); const [isListening, setIsListening] = useState(false); const tutorName = tutorConfig?.frontend_ui_dictionary?.chat_panel?.hero_card?.assistant_title || tutorConfig?.title || "Arun's AI Assistant"; const tutorRole = tutorConfig?.frontend_ui_dictionary?.chat_panel?.hero_card?.role_subtitle || tutorConfig?.subtitle || tutorConfig?.role || "AI Systems Architect • Healthcare & Education"; const tutorAvatar = tutorConfig?.client_metadata?.avatar_url || tutorConfig?.avatar || "/profile_photo.png"; const tutorWelcome = tutorConfig?.frontend_ui_dictionary?.chat_panel?.hero_card?.welcome_paragraph || tutorConfig?.welcome_message || "Hi! I'm Arun's AI Assistant. I can walk you through his production systems, architecture decisions, healthcare & education AI builds, or help you get in direct touch with him. Plus, the real Arun monitors this channel live and can jump in to converse with you directly!"; const tutorCta = tutorConfig?.frontend_ui_dictionary?.chat_panel?.hero_card?.cta_button_text || tutorConfig?.cta_text || "Consult Arun"; const inputPlaceholder = tutorConfig?.frontend_ui_dictionary?.chat_panel?.input_bar?.placeholder || (tutorConfig?.name ? `Ask ${tutorConfig.name}'s AI Assistant...` : "Ask Arun's AI Assistant..."); const customQuestions = tutorConfig?.frontend_ui_dictionary?.chat_panel?.suggested_questions_section?.chips?.map((c: any) => c.query) || tutorConfig?.suggested_questions; const audioRef = useRef(null); const recognitionRef = useRef(null); const messagesEndRef = useRef(null); const textareaRef = useRef(null); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages, isStreaming]); // Focus text input on initial page load / mount useEffect(() => { const timer = setTimeout(() => { textareaRef.current?.focus(); }, 100); return () => clearTimeout(timer); }, []); // Re-focus text input whenever AI finishes streaming a response useEffect(() => { if (!isStreaming) { textareaRef.current?.focus(); } }, [isStreaming]); useEffect(() => { if (activePrompt) { setInputText(activePrompt); setActivePrompt(""); textareaRef.current?.focus(); } }, [activePrompt, setActivePrompt]); // Clean up audio & speech recognition on unmount useEffect(() => { return () => { if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } if (recognitionRef.current) { try { recognitionRef.current.stop(); } catch (e) {} } if (typeof window !== "undefined" && "speechSynthesis" in window) { window.speechSynthesis.cancel(); } }; }, []); const handleInputChange = (e: React.ChangeEvent) => { setInputText(e.target.value); if (textareaRef.current) { textareaRef.current.style.height = "auto"; textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 120)}px`; } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend(); } }; const handleSend = () => { if (!inputText.trim() || isStreaming) return; if (isListening && recognitionRef.current) { try { recognitionRef.current.stop(); } catch (e) {} setIsListening(false); } onSendMessage(inputText.trim()); setInputText(""); if (textareaRef.current) { textareaRef.current.style.height = "auto"; textareaRef.current.focus(); } }; const copyToClipboard = (id: string, text: string) => { navigator.clipboard.writeText(text); setCopiedId(id); setTimeout(() => setCopiedId(null), 2000); }; const baseTextRef = useRef(""); // Speech-To-Text (STT) Microphone Handler const handleToggleListening = () => { if (typeof window === "undefined") return; const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; if (!SpeechRecognition) { alert("Speech recognition is not supported in your browser. Please try Chrome or Edge."); return; } if (isListening) { if (recognitionRef.current) { try { recognitionRef.current.stop(); } catch (e) {} } setIsListening(false); return; } try { baseTextRef.current = inputText.trim(); const recognition = new SpeechRecognition(); recognition.continuous = false; recognition.interimResults = true; recognition.lang = "en-US"; recognition.onresult = (event: any) => { let currentSpeech = ""; for (let i = 0; i < event.results.length; i++) { currentSpeech += event.results[i][0].transcript; } const base = baseTextRef.current; const newText = base ? `${base} ${currentSpeech.trim()}` : currentSpeech.trim(); setInputText(newText); if (textareaRef.current) { textareaRef.current.style.height = "auto"; textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 120)}px`; } }; recognition.onerror = (event: any) => { if (event.error !== "no-speech" && event.error !== "aborted") { console.warn("Speech recognition notice:", event.error); } setIsListening(false); }; recognition.onend = () => { setIsListening(false); }; recognitionRef.current = recognition; recognition.start(); setIsListening(true); } catch (e) { console.error("Failed to start speech recognition:", e); setIsListening(false); } }; // Text-To-Speech (TTS) Voice Synthesis const handleToggleSpeech = async (msgId: string, text: string) => { if (playingMessageId === msgId) { if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } if (typeof window !== "undefined" && "speechSynthesis" in window) { window.speechSynthesis.cancel(); } setPlayingMessageId(null); setIsLoadingAudio(null); return; } if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } if (typeof window !== "undefined" && "speechSynthesis" in window) { window.speechSynthesis.cancel(); } setIsLoadingAudio(msgId); const cleanText = text .replace(/[*_#`~\[\]()]/g, " ") .replace(/https?:\/\/\S+/g, "") .replace(/\n+/g, ". ") .trim(); if (!cleanText) { setIsLoadingAudio(null); return; } try { const res = await fetch(`${API_BASE_URL}/tts`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: cleanText, voice: "alloy" }), }); if (res.ok) { const blob = await res.blob(); const url = URL.createObjectURL(blob); const audio = new Audio(url); audioRef.current = audio; audio.onended = () => { setPlayingMessageId(null); setIsLoadingAudio(null); }; audio.onerror = () => { fallbackBrowserSpeech(msgId, cleanText); }; setIsLoadingAudio(null); setPlayingMessageId(msgId); await audio.play(); return; } } catch (e) { console.warn("Neural TTS endpoint fallback:", e); } fallbackBrowserSpeech(msgId, cleanText); }; const fallbackBrowserSpeech = (msgId: string, cleanText: string) => { if (typeof window === "undefined" || !("speechSynthesis" in window)) { setIsLoadingAudio(null); return; } const utterance = new SpeechSynthesisUtterance(cleanText); utterance.rate = 0.95; utterance.pitch = 1.05; const voices = window.speechSynthesis.getVoices(); const naturalVoice = voices.find( (v) => v.name.includes("Google") || v.name.includes("Natural") || v.name.includes("Samantha") || v.name.includes("Daniel") || v.lang.startsWith("en") ); if (naturalVoice) utterance.voice = naturalVoice; utterance.onend = () => { setPlayingMessageId(null); setIsLoadingAudio(null); }; utterance.onerror = () => { setPlayingMessageId(null); setIsLoadingAudio(null); }; setIsLoadingAudio(null); setPlayingMessageId(msgId); window.speechSynthesis.speak(utterance); }; const starterPrompts = [ { title: "Healthcare Systems & Automation", query: "How can your AI systems automate clinical workflows and reduce operational costs?", }, { title: "Zero-Hallucination RAG Architecture", query: "How do you build zero-hallucination RAG engines for trusted knowledge search?", }, { title: "Recent Work & Live Repositories", query: "What has Arun been working on recently? Fetch his latest GitHub activity and commits!", }, { title: "Consulting & Project Collaboration", query: "How can our team hire or consult with Arun to build custom AI software?", }, ]; return (
{/* 1. Main Scrollable Content (Landing Card + Suggested Questions or Messages) */}
{messages.length === 0 ? ( /* Landing Hero Card + Suggested Questions */
{/* AI Twin Card (Placed immediately below header) */} {/* Header Identity Card */}
{tutorName}

{tutorName}

Online

{tutorRole}

{tutorWelcome}

{/* Suggested Questions Section */}
Suggested Questions
{/* ChatGPT Style Chips Grid */}
{(customQuestions ? customQuestions.map((q: string, idx: number) => ({ icon: idx === 0 ? : idx === 1 ? : idx === 2 ? : , badgeClass: idx === 0 ? "badge-coral" : idx === 1 ? "badge-amber" : idx === 2 ? "badge-indigo" : "badge-emerald", label: q.length > 32 ? q.substring(0, 30) + "..." : q, query: q, })) : [ { icon: , badgeClass: "badge-coral", label: "Healthcare AI", query: starterPrompts[0].query, }, { icon: , badgeClass: "badge-amber", label: "Zero-Hallucination RAG", query: starterPrompts[1].query, }, { icon: , badgeClass: "badge-indigo", label: "Legal AI", query: starterPrompts[2].query, }, { icon: , badgeClass: "badge-emerald", label: "Consult with Arun", query: starterPrompts[3].query, }, ] ).map((item: any, idx: number) => ( ))}
) : ( /* Active Conversation View Messages */
{messages.map((msg) => (
{/* Sender & Real-Time Stream Status Bar */}
{msg.sender === "user" ? ( "You" ) : msg.sender === "human_arun" ? ( 👨‍💻 Arun Yadav VERIFIED HUMAN 🟢 ) : ( <> Arun's AI Assistant {msg.isStreaming && ( STREAMING RESPONSE BUFFER )} )} {msg.timestamp}
{/* Reasoning steps trace */} {msg.sender === "twin" && msg.thoughts && msg.thoughts.length > 0 && (
{(expandedThoughts[msg.id] || msg.isStreaming) && (
{msg.thoughts.map((th, idx) => (
[{idx + 1}] {th}
))}
)}
)} {/* Real-time Streaming Content */} {msg.sender === "user" ? (

{msg.text}

) : (
{msg.isStreaming && !msg.text ? (
Retrieving vector embeddings & initializing token buffer...
) : ( <> ( ), }} > {msg.text} {msg.isStreaming && ( )} )}
)} {/* Action Bar: High Quality Speech & Copy Button */} {msg.sender === "twin" && msg.text && !msg.isStreaming && (
|
)}
))}
)}
{/* 2. Permanently Fixed Bottom Chat Input Bar (Docked above bottom navigation on mobile, aligned to main panel on desktop) */}
{isListening && (
Listening to your voice... Speak now!
)}