mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-18 20:14:44 +08:00
108 lines
3.9 KiB
TypeScript
108 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import { MoreHorizontal, Trash2 } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { useParams, usePathname, useRouter } from "next/navigation";
|
|
import { useCallback } from "react";
|
|
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import {
|
|
SidebarGroup,
|
|
SidebarGroupContent,
|
|
SidebarGroupLabel,
|
|
SidebarMenu,
|
|
SidebarMenuAction,
|
|
SidebarMenuButton,
|
|
SidebarMenuItem,
|
|
} from "@/components/ui/sidebar";
|
|
import { useDeleteThread, useThreads } from "@/core/api";
|
|
import { pathOfThread, titleOfThread } from "@/core/threads/utils";
|
|
|
|
export function RecentChatList() {
|
|
const router = useRouter();
|
|
const pathname = usePathname();
|
|
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
|
|
const { data: threads = [] } = useThreads();
|
|
const { mutate: deleteThread } = useDeleteThread();
|
|
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],
|
|
);
|
|
if (threads.length === 0) {
|
|
return null;
|
|
}
|
|
return (
|
|
<SidebarGroup>
|
|
<SidebarGroupLabel>Recents</SidebarGroupLabel>
|
|
<SidebarGroupContent className="group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0">
|
|
<SidebarMenu>
|
|
<div className="flex w-full flex-col gap-1">
|
|
{threads.map((thread) => {
|
|
const isActive = pathOfThread(thread, false) === pathname;
|
|
return (
|
|
<SidebarMenuItem
|
|
key={thread.thread_id}
|
|
className="group/side-menu-item"
|
|
>
|
|
<SidebarMenuButton isActive={isActive} asChild>
|
|
<div>
|
|
<Link
|
|
className="text-muted-foreground block w-full whitespace-nowrap group-hover/side-menu-item:overflow-hidden"
|
|
href={pathOfThread(thread)}
|
|
>
|
|
{titleOfThread(thread)}
|
|
</Link>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<SidebarMenuAction
|
|
showOnHover
|
|
className="bg-background/50 hover:bg-background"
|
|
>
|
|
<MoreHorizontal />
|
|
<span className="sr-only">More</span>
|
|
</SidebarMenuAction>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
className="w-48 rounded-lg"
|
|
side={"right"}
|
|
align={"start"}
|
|
>
|
|
<DropdownMenuItem
|
|
onSelect={() => handleDelete(thread.thread_id)}
|
|
>
|
|
<Trash2 className="text-muted-foreground" />
|
|
<span>Delete</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
);
|
|
})}
|
|
</div>
|
|
</SidebarMenu>
|
|
</SidebarGroupContent>
|
|
</SidebarGroup>
|
|
);
|
|
}
|