"use client";
import { useState, useRef, useEffect, useCallback } from "react";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Send,
Loader2,
Wrench,
Bot,
User,
RotateCcw,
ChevronDown,
ChevronUp,
Sparkles,
} from "lucide-react";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
interface Message {
role: "user" | "assistant";
content: string;
toolCalls?: string[];
turns?: number;
model?: string;
reasoning?: string | null;
error?: boolean;
}
interface OrchestratorChatProps {
projectId: string;
projectName?: string;
placeholder?: string;
}
// ---------------------------------------------------------------------------
// Suggestion chips shown before the first message
// ---------------------------------------------------------------------------
const SUGGESTIONS = [
"What's the current status of this project?",
"Check if there are any open issues or PRs",
"What was the last deployment?",
"Write a quick summary of what's been built so far",
];
// ---------------------------------------------------------------------------
// Single message bubble
// ---------------------------------------------------------------------------
function MessageBubble({ msg }: { msg: Message }) {
const [showReasoning, setShowReasoning] = useState(false);
const isUser = msg.role === "user";
return (
{/* Avatar */}
{isUser ? : }
{/* Bubble */}
{msg.content}
{/* Tool calls & meta */}
{!isUser && (
{msg.toolCalls && msg.toolCalls.length > 0 && (
{msg.toolCalls.map((t, i) => (
{t}
))}
)}
{msg.reasoning && (
)}
{msg.model && (
{msg.model}
)}
)}
{/* Reasoning panel */}
{!isUser && showReasoning && msg.reasoning && (
{msg.reasoning}
)}
);
}
// ---------------------------------------------------------------------------
// Typing indicator
// ---------------------------------------------------------------------------
function TypingIndicator() {
return (
{[0, 1, 2].map(i => (
))}
);
}
// ---------------------------------------------------------------------------
// Main component
// ---------------------------------------------------------------------------
export function OrchestratorChat({
projectId,
projectName,
placeholder = "Ask your AI team anything…",
}: OrchestratorChatProps) {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const bottomRef = useRef(null);
const textareaRef = useRef(null);
const hasMessages = messages.length > 0;
const scrollToBottom = useCallback(() => {
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
}, []);
useEffect(() => {
scrollToBottom();
}, [messages, loading]);
const sendMessage = useCallback(
async (text: string) => {
const trimmed = text.trim();
if (!trimmed || loading) return;
setInput("");
setMessages(prev => [...prev, { role: "user", content: trimmed }]);
setLoading(true);
try {
const res = await fetch(`/api/projects/${projectId}/agent-chat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: trimmed }),
});
const data = await res.json();
if (!res.ok) {
setMessages(prev => [
...prev,
{ role: "assistant", content: data.error ?? "Something went wrong.", error: true },
]);
} else {
setMessages(prev => [
...prev,
{
role: "assistant",
content: data.reply || "(no reply)",
toolCalls: data.toolCalls,
turns: data.turns,
model: data.model,
reasoning: data.reasoning,
},
]);
}
} catch (err) {
setMessages(prev => [
...prev,
{
role: "assistant",
content: err instanceof Error ? err.message : "Network error — is the agent runner online?",
error: true,
},
]);
} finally {
setLoading(false);
setTimeout(() => textareaRef.current?.focus(), 50);
}
},
[projectId, loading]
);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage(input);
}
};
const clearChat = async () => {
setMessages([]);
try {
await fetch(`/api/projects/${projectId}/agent-chat`, { method: "DELETE" });
} catch { /* best-effort */ }
};
return (
{/* Header */}
{projectName ? `${projectName} AI` : "Project AI"}
GLM-5
{hasMessages && (
)}
{/* Messages */}
{!hasMessages ? (
/* Empty state — Lovable-style centered prompt */
What should we build?
Your AI team is ready. Ask them anything about this project.
{SUGGESTIONS.map(s => (
))}
) : (
{messages.map((msg, i) => (
))}
{loading &&
}
)}
{/* Input */}
Enter to send · Shift+Enter for newline
);
}