diff --git a/src/views/AgentChat/components/LeftSidebar/DateGroup.js b/src/views/AgentChat/components/LeftSidebar/DateGroup.js new file mode 100644 index 00000000..be574218 --- /dev/null +++ b/src/views/AgentChat/components/LeftSidebar/DateGroup.js @@ -0,0 +1,127 @@ +// src/views/AgentChat/components/LeftSidebar/DateGroup.js +// 可折叠的日期分组组件 + +import React, { useState } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Box, Text, HStack, VStack, Badge } from '@chakra-ui/react'; +import { ChevronDown, ChevronRight, Calendar } from 'lucide-react'; +import SessionCard from './SessionCard'; + +/** + * DateGroup - 可折叠的日期分组组件 + * + * @param {Object} props + * @param {string} props.label - 日期标签(如"今天"、"昨天"、"11月28日") + * @param {Array} props.sessions - 该日期下的会话列表 + * @param {string|null} props.currentSessionId - 当前选中的会话 ID + * @param {Function} props.onSessionSwitch - 切换会话回调 + * @param {boolean} props.defaultExpanded - 默认是否展开 + * @param {number} props.index - 分组索引(用于动画延迟) + * @returns {JSX.Element} + */ +const DateGroup = ({ + label, + sessions, + currentSessionId, + onSessionSwitch, + defaultExpanded = true, + index = 0, +}) => { + const [isExpanded, setIsExpanded] = useState(defaultExpanded); + const hasActiveSession = sessions.some((s) => s.session_id === currentSessionId); + + return ( + + + {/* 分组标题 - 可点击折叠 */} + setIsExpanded(!isExpanded)} + borderRadius="md" + bg={hasActiveSession ? 'rgba(139, 92, 246, 0.1)' : 'transparent'} + _hover={{ + bg: 'rgba(255, 255, 255, 0.05)', + }} + transition="all 0.2s" + > + {/* 折叠图标 */} + + + + + {/* 日历图标 */} + + + {/* 日期标签 */} + + {label} + + + {/* 会话数量徽章 */} + + {sessions.length} + + + + {/* 会话列表 - 折叠动画 */} + + {isExpanded && ( + + + {sessions.map((session, idx) => ( + + onSessionSwitch(session.session_id)} + /> + + ))} + + + )} + + + + ); +}; + +export default DateGroup; diff --git a/src/views/AgentChat/components/LeftSidebar/index.js b/src/views/AgentChat/components/LeftSidebar/index.js index a893021b..2f359323 100644 --- a/src/views/AgentChat/components/LeftSidebar/index.js +++ b/src/views/AgentChat/components/LeftSidebar/index.js @@ -1,7 +1,7 @@ // src/views/AgentChat/components/LeftSidebar/index.js // 左侧栏组件 - 对话历史列表 -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Box, @@ -15,11 +15,12 @@ import { HStack, VStack, Flex, + Button, } from '@chakra-ui/react'; -import { MessageSquare, Plus, Search, ChevronLeft } from 'lucide-react'; +import { MessageSquare, Plus, Search, ChevronLeft, ChevronDown, MoreHorizontal } from 'lucide-react'; import { animations } from '../../constants/animations'; import { groupSessionsByDate } from '../../utils/sessionUtils'; -import SessionCard from './SessionCard'; +import DateGroup from './DateGroup'; /** * LeftSidebar - 左侧栏组件 @@ -35,6 +36,9 @@ import SessionCard from './SessionCard'; * @param {Object} props.user - 用户信息 * @returns {JSX.Element|null} */ +// 最多显示的日期分组数量 +const MAX_VISIBLE_GROUPS = 10; + const LeftSidebar = ({ isOpen, onClose, @@ -46,18 +50,33 @@ const LeftSidebar = ({ user, }) => { const [searchQuery, setSearchQuery] = useState(''); - - // 按日期分组会话 - const sessionGroups = groupSessionsByDate(sessions); + const [showAllGroups, setShowAllGroups] = useState(false); // 搜索过滤 - const filteredSessions = searchQuery - ? sessions.filter( - (s) => - s.title?.toLowerCase().includes(searchQuery.toLowerCase()) || - s.session_id?.toLowerCase().includes(searchQuery.toLowerCase()) - ) - : sessions; + const filteredSessions = useMemo(() => { + if (!searchQuery) return sessions; + return sessions.filter( + (s) => + s.title?.toLowerCase().includes(searchQuery.toLowerCase()) || + s.session_id?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + }, [sessions, searchQuery]); + + // 按日期分组会话(新版本返回数组) + const sessionGroups = useMemo(() => { + return groupSessionsByDate(filteredSessions); + }, [filteredSessions]); + + // 控制显示的分组数量 + const visibleGroups = useMemo(() => { + if (showAllGroups || sessionGroups.length <= MAX_VISIBLE_GROUPS) { + return sessionGroups; + } + return sessionGroups.slice(0, MAX_VISIBLE_GROUPS); + }, [sessionGroups, showAllGroups]); + + const hasMoreGroups = sessionGroups.length > MAX_VISIBLE_GROUPS; + const hiddenGroupsCount = sessionGroups.length - MAX_VISIBLE_GROUPS; return ( @@ -170,86 +189,97 @@ const LeftSidebar = ({ - {/* 会话列表 */} - + {/* 会话列表 - 滚动容器 */} + {/* 按日期分组显示会话 */} - {sessionGroups.today.length > 0 && ( - - - 今天 - - - {sessionGroups.today.map((session, idx) => ( - - onSessionSwitch(session.session_id)} - /> - - ))} - - + {visibleGroups.map((group, index) => ( + + ))} + + {/* 查看更多按钮 */} + {hasMoreGroups && !showAllGroups && ( + + + )} - {sessionGroups.yesterday.length > 0 && ( - - - 昨天 - - - {sessionGroups.yesterday.map((session) => ( - onSessionSwitch(session.session_id)} - /> - ))} - - - )} - - {sessionGroups.thisWeek.length > 0 && ( - - - 本周 - - - {sessionGroups.thisWeek.map((session) => ( - onSessionSwitch(session.session_id)} - /> - ))} - - - )} - - {sessionGroups.older.length > 0 && ( - - - 更早 - - - {sessionGroups.older.map((session) => ( - onSessionSwitch(session.session_id)} - /> - ))} - - + {/* 收起按钮 */} + {showAllGroups && hasMoreGroups && ( + + + )} {/* 加载状态 */} @@ -273,6 +303,15 @@ const LeftSidebar = ({ 开始一个新对话吧! )} + + {/* 搜索无结果 */} + {searchQuery && filteredSessions.length === 0 && sessions.length > 0 && ( + + + 未找到匹配的对话 + 尝试其他关键词 + + )} {/* 用户信息卡片 */} diff --git a/src/views/AgentChat/utils/sessionUtils.js b/src/views/AgentChat/utils/sessionUtils.js index 588f0ab5..9f04532b 100644 --- a/src/views/AgentChat/utils/sessionUtils.js +++ b/src/views/AgentChat/utils/sessionUtils.js @@ -2,17 +2,113 @@ // 会话管理工具函数 /** - * 按日期分组会话列表 + * 格式化日期为显示标签 + * @param {Date} date - 日期对象 + * @param {Date} today - 今天的日期 + * @returns {string} 格式化后的日期标签 + */ +const formatDateLabel = (date, today) => { + const daysDiff = Math.floor((today - date) / (1000 * 60 * 60 * 24)); + + if (daysDiff === 0) { + return '今天'; + } else if (daysDiff === 1) { + return '昨天'; + } else if (daysDiff < 7) { + const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; + return weekDays[date.getDay()]; + } else { + // 超过一周,显示具体日期 + return `${date.getMonth() + 1}月${date.getDate()}日`; + } +}; + +/** + * 获取日期的纯日期字符串(用于分组 key) + * @param {Date} date - 日期对象 + * @returns {string} YYYY-MM-DD 格式的日期字符串 + */ +const getDateKey = (date) => { + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; +}; + +/** + * 按日期分组会话列表(新版本 - 按具体日期分组) * * @param {Array} sessions - 会话列表 - * @returns {Object} 分组后的会话对象 { today, yesterday, thisWeek, older } + * @returns {Array} 分组后的会话数组 [{ dateKey, label, sessions, date }] * * @example * const groups = groupSessionsByDate(sessions); - * console.log(groups.today); // 今天的会话 - * console.log(groups.yesterday); // 昨天的会话 + * // 返回: [ + * // { dateKey: '2025-11-30', label: '今天', sessions: [...], date: Date }, + * // { dateKey: '2025-11-29', label: '昨天', sessions: [...], date: Date }, + * // ... + * // ] */ export const groupSessionsByDate = (sessions) => { + if (!sessions || sessions.length === 0) { + return []; + } + + const today = new Date(); + today.setHours(0, 0, 0, 0); + + // 按日期分组到 Map + const groupMap = new Map(); + + sessions.forEach((session) => { + const sessionDate = new Date(session.created_at || session.timestamp); + if (isNaN(sessionDate.getTime())) { + // 无效日期,归到今天 + const todayKey = getDateKey(today); + if (!groupMap.has(todayKey)) { + groupMap.set(todayKey, { + dateKey: todayKey, + label: '今天', + sessions: [], + date: today, + }); + } + groupMap.get(todayKey).sessions.push(session); + return; + } + + const dateOnly = new Date(sessionDate); + dateOnly.setHours(0, 0, 0, 0); + const dateKey = getDateKey(dateOnly); + + if (!groupMap.has(dateKey)) { + groupMap.set(dateKey, { + dateKey, + label: formatDateLabel(dateOnly, today), + sessions: [], + date: dateOnly, + }); + } + groupMap.get(dateKey).sessions.push(session); + }); + + // 转换为数组并按日期降序排序 + const groups = Array.from(groupMap.values()).sort((a, b) => b.date - a.date); + + // 每个分组内部按时间降序排序 + groups.forEach((group) => { + group.sessions.sort((a, b) => { + const dateA = new Date(a.created_at || a.timestamp); + const dateB = new Date(b.created_at || b.timestamp); + return dateB - dateA; + }); + }); + + return groups; +}; + +/** + * 旧版分组函数(保留兼容性) + * @deprecated 请使用 groupSessionsByDate 替代 + */ +export const groupSessionsByDateLegacy = (sessions) => { const today = new Date(); const yesterday = new Date(today); yesterday.setDate(yesterday.getDate() - 1);