| "use client"; |
| import { useEffect, useRef, useState } from "react"; |
| import MessageList from "./MessageList"; |
| import Composer from "./Composer"; |
| function ExpertActivity({ stage }) { |
| if (stage === "idle") |
| return null; |
| const isParsing = stage === "parsing"; |
| return (<div style={{ |
| background: "#080808", |
| borderRadius: 16, |
| padding: "16px 18px", |
| marginBottom: 14, |
| border: "1px solid rgba(60,60,70,0.35)", |
| color: "#ddd", |
| }}> |
| <style>{` |
| @keyframes hpDotBounce { |
| 0%, 80%, 100% { transform: translateY(0); opacity: 0.7; } |
| 40% { transform: translateY(-6px); opacity: 1; } |
| } |
| @keyframes hpGrokPulse { |
| 0% { background: #222; transform: scale(1); opacity: .75; box-shadow: 0 0 2px rgba(255,255,255,.1); } |
| 8% { background: #fff; transform: scale(1.55); opacity: 1; box-shadow: 0 0 8px rgba(255,255,255,.9), 0 0 14px rgba(180,220,255,.6); } |
| 18% { background: #aaa; transform: scale(1.15); opacity: .9; box-shadow: 0 0 4px rgba(255,255,255,.4); } |
| 28%, 100% { background: #222; transform: scale(1); opacity: .75; box-shadow: 0 0 2px rgba(255,255,255,.08); } |
| } |
| `}</style> |
| <div style={{ display: "flex", alignItems: "center", gap: 14, marginBottom: 10 }}> |
| {isParsing ? (<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 5px)", gridTemplateRows: "repeat(3, 5px)", gap: 7 }}> |
| {Array.from({ length: 9 }).map((_, i) => (<span key={i} style={{ |
| width: 5, |
| height: 5, |
| borderRadius: "50%", |
| animation: "hpGrokPulse 2.4s infinite ease-in-out", |
| animationDelay: `${i * 0.2}s`, |
| }}/>))} |
| </div>) : (<div style={{ display: "flex", alignItems: "center", gap: 5 }}> |
| {[0, 1, 2].map((i) => (<span key={i} style={{ |
| width: 6, |
| height: 6, |
| borderRadius: "50%", |
| background: "#888", |
| animation: "hpDotBounce 1.3s infinite ease-in-out", |
| animationDelay: `${i * 0.15}s`, |
| }}/>))} |
| </div>)} |
| <div style={{ color: "#7aaae0", fontSize: 15 }}> |
| {isParsing ? "Parsing question · sending to backend" : "Thinking"} |
| </div> |
| </div> |
| <div style={{ color: "#8e8e9a", fontSize: 13 }}> |
| {isParsing |
| ? "Analyzing query structure and preparing tool context." |
| : "Processing reasoning step-by-step."} |
| </div> |
| </div>); |
| } |
| export default function ChatWindow() { |
| const [messages, setMessages] = useState([]); |
| const [stage, setStage] = useState("idle"); |
| const parseTimer = useRef(null); |
| useEffect(() => { |
| return () => { |
| if (parseTimer.current) |
| clearTimeout(parseTimer.current); |
| }; |
| }, []); |
| async function sendMessage(text) { |
| setStage("parsing"); |
| parseTimer.current = setTimeout(() => setStage("thinking"), 1200); |
| try { |
| const resp = await fetch("http://localhost:8000/api/chat", { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ session_id: "default", message: text }), |
| }); |
| const data = await resp.json(); |
| if (parseTimer.current) |
| clearTimeout(parseTimer.current); |
| setStage("thinking"); |
| setMessages((prev) => [...prev, `User: ${text}`, `Expert: ${data.answer}`]); |
| } |
| catch (err) { |
| if (parseTimer.current) |
| clearTimeout(parseTimer.current); |
| setMessages((prev) => [...prev, `User: ${text}`, `Expert: Failed to get response: ${String(err)}`]); |
| } |
| finally { |
| setStage("idle"); |
| } |
| } |
| return (<div style={{ padding: 24 }}> |
| <MessageList messages={messages}/> |
| <ExpertActivity stage={stage}/> |
| <Composer onSend={sendMessage}/> |
| </div>); |
| } |
|
|