import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Menu, LayoutDashboard, Plus, MoreVertical, User, Search, Wrench, Settings, Brain, Trash2, Pencil } from "lucide-react";
import { useState, useRef, useEffect } from "react";
import { Link, useNavigate, useLocation } from "react-router-dom";
import { useAuthStore } from "@/store/authStore";
import { api } from "@/lib/api";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
interface SessionInfo {
key: string;
created_at: string;
updated_at: string;
metadata?: {
title?: string;
};
}
function Section({
title,
count,
items,
onSelect,
onDelete,
onRename,
activeKey
}: {
title: string;
count: number;
items: SessionInfo[];
onSelect: (key: string) => void;
onDelete: (key: string) => void;
onRename: (key: string, currentTitle: string) => void;
activeKey: string | null;
}) {
return (
{items.map((item) => {
const displayTitle = item.metadata?.title || item.key.replace("api:", "");
const isActive = activeKey === item.key;
return (
onSelect(item.key)}
>
{displayTitle}
e.stopPropagation()} className="h-6 w-6 flex items-center justify-center rounded hover:bg-zinc-200 text-zinc-400 opacity-0 group-hover:opacity-100 transition-opacity outline-none">
{ e.stopPropagation(); onRename(item.key, displayTitle); }}>
重命名
{ e.stopPropagation(); onDelete(item.key); }} className="text-red-600 focus:text-red-600 focus:bg-red-50">
删除会话
);
})}
);
}
function SidebarBody() {
const navigate = useNavigate();
const location = useLocation();
const { user, logout } = useAuthStore();
const [showUserMenu, setShowUserMenu] = useState(false);
const menuRef = useRef(null);
// Session management state
const [sessions, setSessions] = useState([]);
const [renameDialogOpen, setRenameDialogOpen] = useState(false);
const [sessionToRename, setSessionToRename] = useState<{key: string, title: string} | null>(null);
const [newTitle, setNewTitle] = useState("");
// Try to parse active session from URL query
const queryParams = new URLSearchParams(location.search);
const activeSessionKey = queryParams.get("session") || "api:default";
const fetchSessions = async () => {
try {
const data = await api.get("/nanobot/sessions");
setSessions(data);
} catch (e) {
console.error("Failed to fetch sessions", e);
}
};
useEffect(() => {
fetchSessions();
// Set up polling to refresh session list
const interval = setInterval(fetchSessions, 5000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
setShowUserMenu(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const handleLogout = () => {
logout();
navigate("/login");
};
const handleSelectSession = (key: string) => {
navigate(`/?session=${encodeURIComponent(key)}`);
};
const handleNewThread = () => {
const newSessionId = `api:${Date.now()}`;
navigate(`/?session=${encodeURIComponent(newSessionId)}`);
};
const handleDeleteSession = async (key: string) => {
if (!window.confirm("确定要删除这个会话吗?")) return;
try {
await api.delete(`/nanobot/sessions/${key}`);
if (activeSessionKey === key) {
navigate("/");
}
fetchSessions();
} catch (e) {
console.error("Failed to delete session", e);
}
};
const openRenameDialog = (key: string, currentTitle: string) => {
setSessionToRename({ key, title: currentTitle });
setNewTitle(currentTitle);
setRenameDialogOpen(true);
};
const handleRename = async () => {
if (!sessionToRename || !newTitle.trim()) return;
try {
await api.put(`/nanobot/sessions/${sessionToRename.key}`, { title: newTitle.trim() });
setRenameDialogOpen(false);
fetchSessions();
} catch (e) {
console.error("Failed to rename session", e);
}
};
return (
{/* Header */}
{/* User Settings Popover Menu */}
{showUserMenu && (
{user?.username}
{user?.email}
{user?.is_admin && (
<>
>
)}
)}
);
}
export function Sidebar() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
} />
>
);
}