"use client"; import React from "react"; import { motion } from "framer-motion"; import { Box, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, IconButton, Paper, Stack, TextField, Tooltip, Typography, alpha, useTheme, } from "@mui/material"; import CheckRounded from "@mui/icons-material/CheckRounded"; import CloseRounded from "@mui/icons-material/CloseRounded"; import EditRounded from "@mui/icons-material/EditRounded"; import EditNoteRounded from "@mui/icons-material/EditNoteRounded"; import DeleteOutlineRounded from "@mui/icons-material/DeleteOutlineRounded"; import ChatBubbleOutlineRounded from "@mui/icons-material/ChatBubbleOutlineRounded"; import SearchRounded from "@mui/icons-material/SearchRounded"; import WarningRounded from "@mui/icons-material/WarningRounded"; import type { ChatSessionSummary } from "./GlobalChatbox.types"; type AgentHistoryPanelProps = { sessions: ChatSessionSummary[]; activeSessionId?: string; isHydrating?: boolean; onNewSession: () => void; onRenameSession: (sessionId: string, title: string) => void; onSelectSession: (sessionId: string) => void; onDeleteSession: (sessionId: string) => void; }; const formatRelativeDate = (timestamp: number) => { const date = new Date(timestamp); const now = new Date(); const isSameDay = date.toDateString() === now.toDateString(); if (isSameDay) { return date.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", }); } return date.toLocaleDateString("zh-CN", { month: "numeric", day: "numeric", }); }; const getDayStart = (date: Date) => new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime(); const getSessionGroupLabel = (timestamp: number) => { const now = new Date(); const todayStart = getDayStart(now); const yesterdayStart = todayStart - 24 * 60 * 60 * 1000; const lastWeekStart = todayStart - 7 * 24 * 60 * 60 * 1000; if (timestamp >= todayStart) return "今天"; if (timestamp >= yesterdayStart) return "昨天"; if (timestamp >= lastWeekStart) return "过去 7 天"; return "更早"; }; export const AgentHistoryPanel = ({ sessions, activeSessionId, isHydrating = false, onNewSession, onRenameSession, onSelectSession, onDeleteSession, }: AgentHistoryPanelProps) => { const theme = useTheme(); const [keyword, setKeyword] = React.useState(""); const [editingSessionId, setEditingSessionId] = React.useState(null); const [draftTitle, setDraftTitle] = React.useState(""); const [pendingDeleteSessionId, setPendingDeleteSessionId] = React.useState(null); const filteredSessions = React.useMemo(() => { const normalizedKeyword = keyword.trim().toLowerCase(); if (!normalizedKeyword) return sessions; return sessions.filter((session) => session.title.toLowerCase().includes(normalizedKeyword)); }, [keyword, sessions]); const sortedFilteredSessions = React.useMemo( () => [...filteredSessions].sort((left, right) => { const createdAtDiff = right.createdAt - left.createdAt; if (createdAtDiff !== 0) return createdAtDiff; const updatedAtDiff = right.updatedAt - left.updatedAt; if (updatedAtDiff !== 0) return updatedAtDiff; return right.id.localeCompare(left.id); }), [filteredSessions], ); const groupedSessions = React.useMemo(() => { const groups = new Map(); sortedFilteredSessions.forEach((session) => { const label = getSessionGroupLabel(session.createdAt); const existing = groups.get(label); if (existing) { existing.push(session); } else { groups.set(label, [session]); } }); return Array.from(groups.entries()); }, [sortedFilteredSessions]); const pendingDeleteSession = filteredSessions.find( (session) => session.id === pendingDeleteSessionId, ); const handleStartRename = (sessionId: string, title: string) => { setEditingSessionId(sessionId); setDraftTitle(title); }; const handleCancelRename = () => { setEditingSessionId(null); setDraftTitle(""); }; const handleConfirmRename = (sessionId: string) => { const normalizedTitle = draftTitle.trim(); if (!normalizedTitle) return; onRenameSession(sessionId, normalizedTitle); handleCancelRename(); }; return ( <> 历史会话 setKeyword(event.target.value)} placeholder="搜索历史会话" size="small" fullWidth disabled={isHydrating} InputProps={{ startAdornment: , sx: { borderRadius: 3, bgcolor: alpha("#fff", 0.62), fontSize: "0.85rem", }, }} /> {sessions.length === 0 ? ( 暂无历史会话 新建对话后会自动出现在这里 ) : filteredSessions.length === 0 ? ( 未找到匹配会话 试试其他关键词 ) : ( {groupedSessions.map(([groupLabel, groupSessions]) => ( {groupLabel} {groupSessions.map((session) => { const isActive = session.id === activeSessionId; return ( { if (editingSessionId === session.id) return; onSelectSession(session.id); }} sx={{ px: 1.25, py: 1, borderRadius: 3, cursor: isHydrating ? "default" : "pointer", bgcolor: isActive ? alpha("#00acc1", 0.12) : alpha("#fff", 0.56), border: `1px solid ${isActive ? alpha("#00acc1", 0.25) : alpha("#fff", 0.72)}`, boxShadow: isActive ? `0 8px 20px ${alpha("#00acc1", 0.12)}` : `0 4px 12px ${alpha("#000", 0.03)}`, transition: "all 0.2s ease", pointerEvents: isHydrating ? "none" : "auto", "&:hover": { bgcolor: isActive ? alpha("#00acc1", 0.14) : alpha("#fff", 0.86), borderColor: alpha("#00acc1", 0.2), }, }} > {editingSessionId === session.id ? ( setDraftTitle(event.target.value)} size="small" autoFocus placeholder="请输入会话标题" onClick={(event) => event.stopPropagation()} onKeyDown={(event) => { if (event.key === "Enter") { event.preventDefault(); event.stopPropagation(); handleConfirmRename(session.id); } else if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); handleCancelRename(); } }} sx={{ flex: 1, minWidth: 0, "& .MuiOutlinedInput-root": { height: 32, bgcolor: alpha("#fff", 0.75), borderRadius: 1.5, transition: "all 0.2s ease-in-out", "& fieldset": { borderColor: alpha("#000", 0.08), }, "&:hover fieldset": { borderColor: alpha(theme.palette.primary.main, 0.4), }, "&.Mui-focused fieldset": { borderColor: theme.palette.primary.main, borderWidth: "1.5px", boxShadow: `0 0 0 3px ${alpha(theme.palette.primary.main, 0.1)}`, }, }, "& .MuiInputBase-input": { padding: "4px 10px", fontSize: "0.85rem", fontWeight: 700, color: theme.palette.text.primary, } }} /> { event.stopPropagation(); handleConfirmRename(session.id); }} disabled={!draftTitle.trim()} sx={{ width: 28, height: 28, color: "success.main", bgcolor: alpha(theme.palette.success.main, 0.1), "&:hover": { bgcolor: alpha(theme.palette.success.main, 0.2) }, }} > { event.stopPropagation(); handleCancelRename(); }} sx={{ width: 28, height: 28, color: "text.secondary", bgcolor: alpha("#000", 0.05), "&:hover": { bgcolor: alpha("#000", 0.1) }, }} > ) : pendingDeleteSessionId === session.id ? ( 确认删除此会话? ) : ( {session.title} {formatRelativeDate(session.createdAt)} )} {!(editingSessionId === session.id || pendingDeleteSessionId === session.id) && ( { event.stopPropagation(); handleStartRename(session.id, session.title); }} disabled={isHydrating || editingSessionId === session.id} sx={{ width: 28, height: 28, color: "text.secondary", "&:hover": { color: "primary.main", bgcolor: alpha("#00acc1", 0.08), }, }} > { event.stopPropagation(); setPendingDeleteSessionId(session.id); }} disabled={isHydrating} sx={{ width: 28, height: 28, color: "text.secondary", "&:hover": { color: "error.main", bgcolor: alpha("#ef5350", 0.08), }, }} > )} {pendingDeleteSessionId === session.id && ( { event.stopPropagation(); onDeleteSession(session.id); setPendingDeleteSessionId(null); }} sx={{ width: 28, height: 28, color: "error.main", bgcolor: alpha("#ef5350", 0.1), "&:hover": { bgcolor: alpha("#ef5350", 0.2) }, }} > { event.stopPropagation(); setPendingDeleteSessionId(null); }} sx={{ width: 28, height: 28, color: "text.secondary", bgcolor: alpha("#000", 0.05), "&:hover": { bgcolor: alpha("#000", 0.1) }, }} > )} ); })} ))} )} ); };