import { parse } from "best-effort-json-parser"; import { motion } from "framer-motion"; import { useCallback, useMemo } from "react"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardFooter, CardHeader, CardTitle, } from "~/components/ui/card"; import type { Message, Option } from "~/core/messages"; import { openResearch, sendMessage, useMessage, useResearchTitle, useStore, } from "~/core/store"; import { cn } from "~/lib/utils"; import { LoadingAnimation } from "./loading-animation"; import { Markdown } from "./markdown"; import { RainbowText } from "./rainbow-text"; import { RollingText } from "./rolling-text"; import { ScrollContainer } from "./scroll-container"; export function MessageListView({ className, onFeedback, }: { className?: string; onFeedback?: (feedback: { option: Option }) => void; }) { const messageIds = useStore((state) => state.messageIds); const interruptMessage = useStore((state) => { if (messageIds.length >= 2) { const lastMessage = state.messages.get( messageIds[messageIds.length - 1]!, ); return lastMessage?.finishReason === "interrupt" ? lastMessage : null; } return null; }); const waitingForFeedbackMessageId = useStore((state) => { if (messageIds.length >= 2) { const lastMessage = state.messages.get( messageIds[messageIds.length - 1]!, ); if (lastMessage && lastMessage.finishReason === "interrupt") { return state.messageIds[state.messageIds.length - 2]; } } return null; }); const responding = useStore((state) => state.responding); const noOngoingResearch = useStore( (state) => state.ongoingResearchId === null, ); const ongoingResearchIsOpen = useStore( (state) => state.ongoingResearchId === state.openResearchId, ); return ( {responding && (noOngoingResearch || !ongoingResearchIsOpen) && ( )} ); } function MessageListItem({ className, messageId, waitForFeedback, onFeedback, interruptMessage, }: { className?: string; messageId: string; waitForFeedback?: boolean; onFeedback?: (feedback: { option: Option }) => void; interruptMessage?: Message | null; }) { const message = useMessage(messageId); const startOfResearch = useStore((state) => state.researchIds.includes(messageId), ); if (message) { if ( message.role === "user" || message.agent === "coordinator" || message.agent === "planner" || startOfResearch ) { let content: React.ReactNode; if (message.agent === "planner") { content = (
); } else if (startOfResearch) { content = (
); } else { content = message.content ? (
{message?.content}
) : null; } if (content) { return ( {content} ); } } return null; } function MessageBubble({ className, message, children, }: { className?: string; message: Message; children: React.ReactNode; }) { return (
{children}
); } function ResearchCard({ className, researchId, }: { className?: string; researchId: string; }) { const reportId = useStore((state) => state.researchReportIds.get(researchId), ); const hasReport = useStore((state) => state.researchReportIds.has(researchId), ); const reportGenerating = useStore( (state) => hasReport && state.messages.get(reportId!)!.isStreaming, ); const openResearchId = useStore((state) => state.openResearchId); const state = useMemo(() => { if (hasReport) { return reportGenerating ? "Generating report..." : "Report generated"; } return "Researching..."; }, [hasReport, reportGenerating]); const title = useResearchTitle(researchId); const handleOpen = useCallback(() => { if (openResearchId === researchId) { openResearch(null); } else { openResearch(researchId); } }, [openResearchId, researchId]); return ( {title !== undefined && title !== "" ? title : "Deep Research"}
{state}
); } } const GREETINGS = ["Cool", "Sounds great", "Looks good", "Great", "Awesome"]; function PlanCard({ className, message, interruptMessage, onFeedback, waitForFeedback, }: { className?: string; message: Message; interruptMessage?: Message | null; onFeedback?: (feedback: { option: Option }) => void; waitForFeedback?: boolean; }) { const plan = useMemo<{ title?: string; thought?: string; steps?: { title?: string; description?: string }[]; }>(() => { return parse(message.content ?? ""); }, [message.content]); const handleAccept = useCallback(async () => { await sendMessage( `${GREETINGS[Math.floor(Math.random() * GREETINGS.length)]}! ${Math.random() > 0.5 ? "Let's get started." : "Let's start."}`, { interruptFeedback: "accepted", }, ); }, []); return (

{plan.title !== undefined && plan.title !== "" ? plan.title : "Deep Research"}

{plan.thought} {plan.steps && ( )} {!message.isStreaming && interruptMessage?.options?.length && ( {interruptMessage?.options.map((option) => ( ))} )}
); }