"use client"; import { useEffect, useRef, useState } from "react"; import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; import type { UIMessage } from "ai"; import { Streamdown } from "streamdown"; import { ArrowUp, ChevronRight, Loader2, Sparkles, Wrench } from "lucide-react"; import { Card, CardHeader, CardTitle } from "@/components/ui/card"; import { cn } from "@/lib/utils"; type AnyPart = { type: string; text?: string; toolName?: string; input?: unknown; output?: unknown; state?: string; }; function ToolPart({ part }: { part: AnyPart }) { const [open, setOpen] = useState(false); const name = part.toolName ?? part.type.replace(/^tool-/, ""); const inputStr = part.input && typeof part.input === "object" ? (JSON.stringify(part.input).length > 80 ? JSON.stringify(part.input).slice(0, 80) + "…" : JSON.stringify(part.input)) : String(part.input ?? ""); const done = part.state === "output-available" || part.output != null; return (
{open && part.output != null && (
          {typeof part.output === "string" ? part.output : JSON.stringify(part.output, null, 2)}
        
)}
); } function MessageBubble({ message }: { message: UIMessage }) { const isUser = message.role === "user"; const parts = (message.parts ?? []) as AnyPart[]; return (
{parts.map((part, i) => { if (part.type === "text") { return isUser ? (

{part.text}

) : (
{part.text ?? ""}
); } if (part.type === "dynamic-tool" || part.type.startsWith("tool-")) { return ; } return null; })}
); } export function ChatDock({ projectId }: { projectId: string }) { const [input, setInput] = useState(""); const scrollRef = useRef(null); const { messages, sendMessage, status, setMessages } = useChat({ id: `chat-${projectId}`, transport: new DefaultChatTransport({ api: `/api/projects/${projectId}/chat` }), }); // Hydrate prior conversation on mount. useEffect(() => { let cancelled = false; fetch(`/api/projects/${projectId}/chat`) .then((r) => r.json()) .then((d: { messages?: UIMessage[] }) => { if (!cancelled && d.messages?.length) setMessages(d.messages); }) .catch(() => {}); return () => { cancelled = true; }; }, [projectId, setMessages]); useEffect(() => { const el = scrollRef.current; if (el) el.scrollTop = el.scrollHeight; }, [messages, status]); const busy = status === "submitted" || status === "streaming"; function submit(e: React.FormEvent) { e.preventDefault(); const text = input.trim(); if (!text || busy) return; sendMessage({ text }); setInput(""); } return (