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 && (
+
+ }
+ onClick={() => setShowAllGroups(true)}
+ bg="rgba(255, 255, 255, 0.05)"
+ color="gray.400"
+ border="1px dashed"
+ borderColor="rgba(255, 255, 255, 0.1)"
+ _hover={{
+ bg: 'rgba(139, 92, 246, 0.1)',
+ borderColor: 'purple.400',
+ color: 'purple.300',
+ }}
+ mt={2}
+ >
+ 查看更多 ({hiddenGroupsCount} 个日期)
+
+
)}
- {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 && (
+
+ }
+ onClick={() => setShowAllGroups(false)}
+ bg="rgba(255, 255, 255, 0.05)"
+ color="gray.400"
+ border="1px dashed"
+ borderColor="rgba(255, 255, 255, 0.1)"
+ _hover={{
+ bg: 'rgba(139, 92, 246, 0.1)',
+ borderColor: 'purple.400',
+ color: 'purple.300',
+ }}
+ mt={2}
+ >
+ 收起
+
+
)}
{/* 加载状态 */}
@@ -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);