"use client"; import { MessageInput, MessageInputTextarea, MessageInputToolbar, MessageInputSubmitButton, MessageInputError, // MessageInputMcpConfigButton, } from "@/components/ui/message-input"; import { MessageSuggestions, MessageSuggestionsStatus, MessageSuggestionsList, } from "@/components/ui/message-suggestions"; import { ThreadHistory, ThreadHistoryHeader, ThreadHistoryNewButton, ThreadHistorySearch, ThreadHistoryList, } from "@/components/ui/thread-history"; import { ThreadContent, ThreadContentMessages, } from "@/components/ui/thread-content"; import type { messageVariants } from "@/components/ui/message"; import { ScrollableMessageContainer } from "@/components/ui/scrollable-message-container"; import { cn } from "@/lib/utils"; import { useMergedRef, useCanvasDetection, usePositioning, } from "@/lib/thread-hooks"; import type { VariantProps } from "class-variance-authority"; import * as React from "react"; import { useRef } from "react"; import type { Suggestion } from "@tambo-ai/react"; /** * Props for the MessageThreadPanel component * @interface */ export interface MessageThreadPanelProps extends React.HTMLAttributes { /** * Optional key to identify the context of the thread * Used to maintain separate thread histories for different contexts */ contextKey?: string; /** Optional content to render in the left panel of the grid */ children?: React.ReactNode; /** * Controls the visual styling of messages in the thread. * Possible values include: "default", "compact", etc. * These values are defined in messageVariants from "@/components/ui/message". * @example variant="compact" */ variant?: VariantProps["variant"]; } /** * Props for the ResizablePanel component */ interface ResizablePanelProps extends React.HTMLAttributes { /** Children elements to render inside the container */ children: React.ReactNode; /** Whether the panel should be positioned on the left (true) or right (false) */ isLeftPanel: boolean; } /** * A resizable panel component with a draggable divider */ const ResizablePanel = React.forwardRef( ({ className, children, isLeftPanel, ...props }, ref) => { const [width, setWidth] = React.useState(956); const isResizing = React.useRef(false); const lastUpdateRef = React.useRef(0); const handleMouseMove = React.useCallback( (e: MouseEvent) => { if (!isResizing.current) return; const now = Date.now(); if (now - lastUpdateRef.current < 16) return; lastUpdateRef.current = now; const windowWidth = window.innerWidth; requestAnimationFrame(() => { let newWidth; if (isLeftPanel) { newWidth = Math.round(e.clientX); } else { newWidth = Math.round(windowWidth - e.clientX); } // Ensure minimum width of 300px const clampedWidth = Math.max( 300, Math.min(windowWidth - 300, newWidth), ); setWidth(clampedWidth); // Update both panel and canvas widths using the same divider position if (isLeftPanel) { document.documentElement.style.setProperty( "--panel-left-width", `${clampedWidth}px`, ); } else { document.documentElement.style.setProperty( "--panel-right-width", `${clampedWidth}px`, ); } }); }, [isLeftPanel], ); return (
{/* Always show resize handle */}
{ e.preventDefault(); isResizing.current = true; document.body.style.cursor = "ew-resize"; document.body.style.userSelect = "none"; document.addEventListener("mousemove", handleMouseMove); document.addEventListener( "mouseup", () => { isResizing.current = false; document.body.style.cursor = ""; document.body.style.userSelect = ""; document.removeEventListener("mousemove", handleMouseMove); }, { once: true }, ); }} /> {children}
); }, ); ResizablePanel.displayName = "ResizablePanel"; /** * A resizable panel component that displays a chat thread with message history, input, and suggestions * @component * @example * ```tsx * // Default left positioning * * * // Explicit right positioning * * ``` */ export const MessageThreadPanel = React.forwardRef< HTMLDivElement, MessageThreadPanelProps >(({ className, contextKey, variant, ...props }, ref) => { const panelRef = useRef(null); const { hasCanvasSpace, canvasIsOnLeft } = useCanvasDetection(panelRef); const { isLeftPanel, historyPosition } = usePositioning( className, canvasIsOnLeft, hasCanvasSpace, ); const mergedRef = useMergedRef(ref, panelRef); const defaultSuggestions: Suggestion[] = [ { id: "suggestion-1", title: "Get started", detailedSuggestion: "What can you help me with?", messageId: "welcome-query", }, { id: "suggestion-2", title: "Learn more", detailedSuggestion: "Tell me about your capabilities.", messageId: "capabilities-query", }, { id: "suggestion-3", title: "Examples", detailedSuggestion: "Show me some example queries I can try.", messageId: "examples-query", }, ]; return (
{historyPosition === "left" && (
)}
{/* Message thread content */} {/* Message Suggestions Status */} {/* Message input */}
{/* Uncomment this to enable client-side MCP config modal button */} {/* */}
{/* Message suggestions */}
{historyPosition === "right" && (
)}
); }); MessageThreadPanel.displayName = "MessageThreadPanel";