"use client"; import { v4 as uuidv4 } from "uuid"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Textarea } from "@/components/ui/textarea"; import { ArrowLeft, GitBranch, Send, User, Bot, Copy, CopyCheck, } from "lucide-react"; import { TooltipIconButton } from "@/components/ui/tooltip-icon-button"; import { getMessageContentString } from "@open-swe/shared/messages"; import { AnimatePresence, motion } from "framer-motion"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ThreadSwitcher } from "./thread-switcher"; import { ThreadDisplayInfo } from "./types"; import { useStream } from "@langchain/langgraph-sdk/react"; import { ManagerGraphState } from "@open-swe/shared/open-swe/manager/types"; import { PlannerGraphState } from "@open-swe/shared/open-swe/planner/types"; import { ActionsRenderer } from "./actions-renderer"; import { ThemeToggle } from "../theme-toggle"; import { HumanMessage } from "@langchain/core/messages"; import { DO_NOT_RENDER_ID_PREFIX } from "@open-swe/shared/constants"; const PROGRAMMER_ASSISTANT_ID = process.env.NEXT_PUBLIC_PROGRAMMER_ASSISTANT_ID; const PLANNER_ASSISTANT_ID = process.env.NEXT_PUBLIC_PLANNER_ASSISTANT_ID; function MessageCopyButton({ content }: { content: string }) { const [copied, setCopied] = useState(false); const handleCopy = (e: React.MouseEvent) => { e.stopPropagation(); navigator.clipboard.writeText(content); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return ( handleCopy(e)} variant="ghost" tooltip="Copy content" className="size-6 p-1" > {copied ? ( ) : ( )} ); } interface ThreadViewProps { stream: ReturnType>; displayThread: ThreadDisplayInfo; allDisplayThreads: ThreadDisplayInfo[]; onBackToHome: () => void; } export function ThreadView({ stream, displayThread, allDisplayThreads, onBackToHome, }: ThreadViewProps) { const [chatInput, setChatInput] = useState(""); const [selectedTab, setSelectedTab] = useState<"planner" | "programmer">( "planner", ); const plannerThreadId = stream.values?.plannerSession?.threadId; const plannerRunId = stream.values?.plannerSession?.runId; const [programmerSession, setProgrammerSession] = useState(); if (!stream.messages?.length) { return null; } const handleSendMessage = () => { if (chatInput.trim()) { const newHumanMessage = new HumanMessage({ id: uuidv4(), content: chatInput, }); stream.submit( { messages: [newHumanMessage], }, { streamResumable: true, optimisticValues: (prev) => ({ ...prev, messages: [...(prev.messages ?? []), newHumanMessage], }), }, ); setChatInput(""); } }; const filteredMessages = stream.messages.filter((message) => { return !message.id?.startsWith(DO_NOT_RENDER_ID_PREFIX); }); return (
{/* Header */}
{displayThread.title} {displayThread.repository && ( <> • {displayThread.repository} )}
{/* Main Content - Split Layout */}
{/* Left Side - Chat Interface */}
{/* Chat Messages */}
{filteredMessages.map((message) => (
{message.type === "human" ? (
) : (
)}
{message.type === "human" ? "You" : "Agent"}
{getMessageContentString(message.content)}
))}
{/* Chat Input - Fixed at bottom */}