"use client"; import { MoreHorizontal, Pencil, Share2, Trash2 } from "lucide-react"; import Link from "next/link"; import { useParams, usePathname, useRouter } from "next/navigation"; import { useCallback, useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, } from "@/components/ui/sidebar"; import { useI18n } from "@/core/i18n/hooks"; import { useDeleteThread, useRenameThread, useThreads, } from "@/core/threads/hooks"; import { pathOfThread, titleOfThread } from "@/core/threads/utils"; import { env } from "@/env"; export function RecentChatList() { const { t } = useI18n(); const router = useRouter(); const pathname = usePathname(); const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); const { data: threads = [] } = useThreads(); const { mutate: deleteThread } = useDeleteThread(); const { mutate: renameThread } = useRenameThread(); // Rename dialog state const [renameDialogOpen, setRenameDialogOpen] = useState(false); const [renameThreadId, setRenameThreadId] = useState(null); const [renameValue, setRenameValue] = useState(""); const handleDelete = useCallback( (threadId: string) => { deleteThread({ threadId }); if (threadId === threadIdFromPath) { const threadIndex = threads.findIndex((t) => t.thread_id === threadId); let nextThreadId = "new"; if (threadIndex > -1) { if (threads[threadIndex + 1]) { nextThreadId = threads[threadIndex + 1]!.thread_id; } else if (threads[threadIndex - 1]) { nextThreadId = threads[threadIndex - 1]!.thread_id; } } void router.push(`/workspace/chats/${nextThreadId}`); } }, [deleteThread, router, threadIdFromPath, threads], ); const handleRenameClick = useCallback( (threadId: string, currentTitle: string) => { setRenameThreadId(threadId); setRenameValue(currentTitle); setRenameDialogOpen(true); }, [], ); const handleRenameSubmit = useCallback(() => { if (renameThreadId && renameValue.trim()) { renameThread({ threadId: renameThreadId, title: renameValue.trim() }); setRenameDialogOpen(false); setRenameThreadId(null); setRenameValue(""); } }, [renameThread, renameThreadId, renameValue]); const handleShare = useCallback( async (threadId: string) => { // Always use Vercel URL for sharing so others can access const VERCEL_URL = "https://deer-flow-v2.vercel.app"; const isLocalhost = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"; // On localhost: use Vercel URL; On production: use current origin const baseUrl = isLocalhost ? VERCEL_URL : window.location.origin; const shareUrl = `${baseUrl}/workspace/chats/${threadId}`; try { await navigator.clipboard.writeText(shareUrl); toast.success(t.clipboard.linkCopied); } catch { toast.error(t.clipboard.failedToCopyToClipboard); } }, [t], ); if (threads.length === 0) { return null; } return ( <> {env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY !== "true" ? t.sidebar.recentChats : t.sidebar.demoChats}
{threads.map((thread) => { const isActive = pathOfThread(thread.thread_id) === pathname; return (
{titleOfThread(thread)} {env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY !== "true" && ( {t.common.more} handleRenameClick( thread.thread_id, titleOfThread(thread), ) } > {t.common.rename} handleShare(thread.thread_id)} > {t.common.share} handleDelete(thread.thread_id)} > {t.common.delete} )}
); })}
{/* Rename Dialog */} {t.common.rename}
setRenameValue(e.target.value)} placeholder={t.common.rename} onKeyDown={(e) => { if (e.key === "Enter") { handleRenameSubmit(); } }} />
); }