"use client"; import { cn } from "@/lib/utils"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import { useTamboThread, useTamboThreadList } from "@tambo-ai/react"; import { ChevronDownIcon, PlusIcon } from "lucide-react"; import * as React from "react"; import { useCallback } from "react"; /** * Props for the ThreadDropdown component * @interface * @extends React.HTMLAttributes */ export interface ThreadDropdownProps extends React.HTMLAttributes { /** Optional context key for filtering threads */ contextKey?: string; /** Optional callback function called when the current thread changes */ onThreadChange?: () => void; } /** * A component that displays a dropdown menu for managing chat threads with keyboard shortcuts * @component * @example * ```tsx * console.log('Thread changed')} * className="custom-styles" * /> * ``` */ export const ThreadDropdown = React.forwardRef< HTMLDivElement, ThreadDropdownProps >(({ className, contextKey, onThreadChange, ...props }, ref) => { const { data: threads, isLoading, error, refetch, } = useTamboThreadList({ contextKey }); const { switchCurrentThread, startNewThread } = useTamboThread(); const isMac = typeof navigator !== "undefined" && navigator.platform.startsWith("Mac"); const modKey = isMac ? "⌥" : "Alt"; const handleNewThread = useCallback( async (e?: React.MouseEvent) => { if (e) { e.stopPropagation(); } try { await startNewThread(); await refetch(); onThreadChange?.(); } catch (error) { console.error("Failed to create new thread:", error); } }, [onThreadChange, startNewThread, refetch], ); React.useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.altKey && event.shiftKey && event.key === "n") { event.preventDefault(); handleNewThread(); } }; document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("keydown", handleKeyDown); }; }, [handleNewThread]); const handleSwitchThread = async (threadId: string, e?: React.MouseEvent) => { if (e) { e.stopPropagation(); } try { switchCurrentThread(threadId); onThreadChange?.(); } catch (error) { console.error("Failed to switch thread:", error); } }; return (
{ e.preventDefault(); handleNewThread(); }} >
New Thread
{modKey}+⇧+N
{isLoading ? ( Loading threads... ) : error ? ( Error loading threads ) : threads?.items.length === 0 ? ( No previous threads ) : ( threads?.items.map((thread) => ( { e.preventDefault(); handleSwitchThread(thread.id); }} > {`Thread ${thread.id.substring(0, 8)}`} )) )}
); }); ThreadDropdown.displayName = "ThreadDropdown";