HomePilot / frontend /components /ChatWindow.tsx
HomePilot Deploy Bot
chore(hf): sync HomePilot to HF Space
921d377
Raw
History Blame Contribute Delete
4.25 kB
"use client";
import { useEffect, useRef, useState } from "react";
import MessageList from "./MessageList";
import Composer from "./Composer";
type ActivityStage = "idle" | "parsing" | "thinking";
function ExpertActivity({ stage }: { stage: ActivityStage }) {
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<string[]>([]);
const [stage, setStage] = useState<ActivityStage>("idle");
const parseTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
return () => {
if (parseTimer.current) clearTimeout(parseTimer.current);
};
}, []);
async function sendMessage(text: string) {
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>
);
}