import { useState, useRef, useEffect, useCallback } from 'react' import { motion, AnimatePresence } from 'framer-motion' import AgentPanel from './AgentPanel' import MemoryManager from '../services/memoryManager' /* ── Slide-up ease ──────────────────────────────────────── */ const EASE_OUT = [0.25, 0.46, 0.45, 0.94] /* ══════════════════════════════════════════════════════════ Typing dots ══════════════════════════════════════════════════════════ */ function TypingIndicator() { return (
{[0, 1, 2].map((i) => ( ))}
) } /* ══════════════════════════════════════════════════════════ Message bubble ══════════════════════════════════════════════════════════ */ function Message({ message, index, onViewInsights }) { const isUser = message.role === 'user' return ( {/* Avatar */}
{isUser ? '›' : '✦'}
{/* Content column */}
{message.content}
{/* Insights link */} {!isUser && message.metadata && ( onViewInsights(message.metadata)} className="flex items-center gap-1.5 px-2 py-0.5 rounded-md text-[10px] text-text-dim hover:text-text-muted transition-colors duration-250 group" whileHover={{ x: 1.5 }} > insights {message.metadata.confidence && ( )} )}
) } /* ══════════════════════════════════════════════════════════ Empty state ══════════════════════════════════════════════════════════ */ function EmptyState({ onPromptClick }) { const prompts = [ 'Latest AI breakthroughs', 'Explain quantum computing', 'Compare React vs Vue', ] return ( {/* Logo */}

What can I help you with?

Search the web, reason through topics, and build on past conversations.

{/* Quick prompts */}
{prompts.map((prompt) => ( onPromptClick(prompt)} > {prompt} ))}
) } /* ══════════════════════════════════════════════════════════ Chat UI — main component ══════════════════════════════════════════════════════════ */ export default function ChatUI() { const [messages, setMessages] = useState([]) const [input, setInput] = useState('') const [isLoading, setIsLoading] = useState(false) const [panelOpen, setPanelOpen] = useState(false) const [panelMeta, setPanelMeta] = useState(null) const messagesEndRef = useRef(null) const inputRef = useRef(null) useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages, isLoading]) useEffect(() => { inputRef.current?.focus() }, []) const handleViewInsights = useCallback((metadata) => { setPanelMeta(metadata) setPanelOpen(true) }, []) const handlePromptClick = useCallback((prompt) => { setInput(prompt) inputRef.current?.focus() }, []) const handleSubmit = async (e) => { e.preventDefault() const query = input.trim() if (!query || isLoading) return setMessages((prev) => [...prev, { role: 'user', content: query }]) setInput('') setIsLoading(true) try { // Inject stored memory context into the request const profileContext = MemoryManager.getProfileForInjection() const sessionContext = MemoryManager.getSessionForInjection() const API_BASE = import.meta.env.VITE_API_URL || '/api' const res = await fetch(`${API_BASE}/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, profile_context: profileContext.length > 0 ? profileContext : null, session_context: sessionContext.length > 0 ? sessionContext : null, }), }) if (!res.ok) throw new Error(`HTTP ${res.status}`) const data = await res.json() // Process memory extraction from the response if (data.memory_extraction) { MemoryManager.processExtraction(data.memory_extraction) } setMessages((prev) => [ ...prev, { role: 'agent', content: data.response, metadata: { source: data.source, tools_used: data.tools_used, steps_taken: data.steps_taken, plan: data.plan, confidence: data.confidence, refinements: data.refinements, memory_used: data.memory_used, memory_hits: data.memory_hits, decision: data.decision, llm_calls: data.llm_calls, steps_skipped: data.steps_skipped, early_stopped: data.early_stopped, cache_hits: data.cache_hits, }, }, ]) } catch (err) { setMessages((prev) => [ ...prev, { role: 'agent', content: `Something went wrong — ${err.message}. Make sure the backend is running.`, metadata: null, }, ]) } finally { setIsLoading(false) inputRef.current?.focus() } } const isEmpty = messages.length === 0 && !isLoading return ( <>
{/* ── Header ── */}

Agent

Autonomous assistant

Online
{/* ── Messages ── */}
{isEmpty && }
{messages.map((msg, i) => ( ))}
{isLoading && (
)}
{/* ── Input area ── */}
setInput(e.target.value)} placeholder="Ask anything…" disabled={isLoading} className="flex-1 bg-transparent text-[14px] text-text-primary placeholder:text-text-ghost outline-none py-2.5 disabled:opacity-40" autoComplete="off" />

Groq · llama-3.1-8b-instant

setPanelOpen(false)} metadata={panelMeta} /> ) }