"use client"; import { useState, useEffect, useRef } from "react"; interface CooMessage { id: string; role: "user" | "assistant"; content: string; streaming?: boolean; } const WELCOME: CooMessage = { id: "welcome", role: "assistant", content: "Hi. I'm your product COO — I know your codebase, your goals, and what's been built. What do you need?", }; export function CooChat({ projectId }: { projectId: string }) { const [messages, setMessages] = useState([WELCOME]); const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); const bottomRef = useRef(null); const textareaRef = useRef(null); useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); const send = async () => { const text = input.trim(); if (!text || loading) return; setInput(""); const userMsg: CooMessage = { id: Date.now().toString(), role: "user", content: text }; const assistantId = (Date.now() + 1).toString(); const assistantMsg: CooMessage = { id: assistantId, role: "assistant", content: "", streaming: true }; setMessages(prev => [...prev, userMsg, assistantMsg]); setLoading(true); const history = messages .filter(m => m.id !== "welcome" && m.content) .map(m => ({ role: m.role === "assistant" ? "model" as const : "user" as const, content: m.content })); try { const res = await fetch(`/api/projects/${projectId}/advisor`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: text, history }), }); if (!res.ok || !res.body) { setMessages(prev => prev.map(m => m.id === assistantId ? { ...m, content: "Something went wrong. Please try again.", streaming: false } : m)); return; } const reader = res.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); setMessages(prev => prev.map(m => m.id === assistantId ? { ...m, content: m.content + chunk } : m)); } setMessages(prev => prev.map(m => m.id === assistantId ? { ...m, streaming: false } : m)); } catch { setMessages(prev => prev.map(m => m.id === assistantId ? { ...m, content: "Connection error. Please try again.", streaming: false } : m)); } finally { setLoading(false); textareaRef.current?.focus(); } }; return (
{/* Messages */}
{messages.map(msg => (
{msg.role === "assistant" && ( )}
{msg.content} {msg.streaming && msg.content === "" && ( {[0, 1, 2].map(i => ( ))} )} {msg.streaming && msg.content !== "" && ( )}
))}
{/* Input */}