diff --git a/refactor.md b/refactor.md index 9f74241..6230f0e 100644 --- a/refactor.md +++ b/refactor.md @@ -132,3 +132,12 @@ This branch captures local refactors focused on frontend UX polish, IPC call con ### 17. External gateway shutdown compatibility - Added capability cache for externally managed Gateway shutdown RPC. - If `shutdown` is unsupported (`unknown method: shutdown`), mark it unsupported and skip future shutdown RPC attempts to avoid repeated warnings. + +### 18. Chat history sidebar grouping (ChatGPT-style buckets) +- Updated chat session history display in sidebar to time buckets: + - Today / Yesterday / Within 1 Week / Within 2 Weeks / Within 1 Month / Older than 1 Month +- Added `historyBuckets` locale keys in EN/ZH/JA (`chat` namespace). +- Fixed i18n namespace usage for bucket labels in sidebar: + - explicitly resolves via `chat:historyBuckets.*` to avoid raw key fallback. +- Removed forced uppercase rendering for bucket headers to preserve localized casing. +- Grouping now applies to all sessions (including `:main`) for consistent bucket visibility and behavior. diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 19d6f49..f857ccd 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -27,6 +27,14 @@ import { ConfirmDialog } from '@/components/ui/confirm-dialog'; import { invokeIpc } from '@/lib/api-client'; import { useTranslation } from 'react-i18next'; +type SessionBucketKey = + | 'today' + | 'yesterday' + | 'withinWeek' + | 'withinTwoWeeks' + | 'withinMonth' + | 'older'; + interface NavItemProps { to: string; icon: React.ReactNode; @@ -67,6 +75,23 @@ function NavItem({ to, icon, label, badge, collapsed, onClick }: NavItemProps) { ); } +function getSessionBucket(activityMs: number, nowMs: number): SessionBucketKey { + if (!activityMs || activityMs <= 0) return 'older'; + + const now = new Date(nowMs); + const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + const startOfYesterday = startOfToday - 24 * 60 * 60 * 1000; + + if (activityMs >= startOfToday) return 'today'; + if (activityMs >= startOfYesterday) return 'yesterday'; + + const daysAgo = (startOfToday - activityMs) / (24 * 60 * 60 * 1000); + if (daysAgo <= 7) return 'withinWeek'; + if (daysAgo <= 14) return 'withinTwoWeeks'; + if (daysAgo <= 30) return 'withinMonth'; + return 'older'; +} + export function Sidebar() { const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed); const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed); @@ -83,9 +108,6 @@ export function Sidebar() { const navigate = useNavigate(); const isOnChat = useLocation().pathname === '/'; - const mainSessions = sessions.filter((s) => s.key.endsWith(':main')); - const otherSessions = sessions.filter((s) => !s.key.endsWith(':main')); - const getSessionLabel = (key: string, displayName?: string, label?: string) => sessionLabels[key] ?? label ?? displayName ?? key; @@ -106,8 +128,28 @@ export function Sidebar() { } }; - const { t } = useTranslation(); + const { t } = useTranslation(['common', 'chat']); const [sessionToDelete, setSessionToDelete] = useState<{ key: string; label: string } | null>(null); + const nowMs = Date.now(); + const sessionBuckets: Array<{ key: SessionBucketKey; label: string; sessions: typeof sessions }> = [ + { key: 'today', label: t('chat:historyBuckets.today'), sessions: [] }, + { key: 'yesterday', label: t('chat:historyBuckets.yesterday'), sessions: [] }, + { key: 'withinWeek', label: t('chat:historyBuckets.withinWeek'), sessions: [] }, + { key: 'withinTwoWeeks', label: t('chat:historyBuckets.withinTwoWeeks'), sessions: [] }, + { key: 'withinMonth', label: t('chat:historyBuckets.withinMonth'), sessions: [] }, + { key: 'older', label: t('chat:historyBuckets.older'), sessions: [] }, + ]; + const sessionBucketMap = Object.fromEntries(sessionBuckets.map((bucket) => [bucket.key, bucket])) as Record< + SessionBucketKey, + (typeof sessionBuckets)[number] + >; + + for (const session of [...sessions].sort((a, b) => + (sessionLastActivity[b.key] ?? 0) - (sessionLastActivity[a.key] ?? 0) + )) { + const bucketKey = getSessionBucket(sessionLastActivity[session.key] ?? 0, nowMs); + sessionBucketMap[bucketKey].sessions.push(session); + } const navItems = [ { to: '/cron', icon: , label: t('sidebar.cronTasks') }, @@ -154,43 +196,47 @@ export function Sidebar() { {/* Session list — below Settings, only when expanded */} {!sidebarCollapsed && sessions.length > 0 && (
- {[...mainSessions, ...[...otherSessions].sort((a, b) => - (sessionLastActivity[b.key] ?? 0) - (sessionLastActivity[a.key] ?? 0) - )].map((s) => ( -
- - {!s.key.endsWith(':main') && ( - - )} -
+ {sessionBuckets.map((bucket) => ( + bucket.sessions.length > 0 ? ( +
+
+ {bucket.label} +
+ {bucket.sessions.map((s) => ( +
+ + +
+ ))} +
+ ) : null ))}
)} diff --git a/src/i18n/locales/en/chat.json b/src/i18n/locales/en/chat.json index af9d58a..8d82e0d 100644 --- a/src/i18n/locales/en/chat.json +++ b/src/i18n/locales/en/chat.json @@ -14,5 +14,13 @@ "refresh": "Refresh chat", "showThinking": "Show thinking", "hideThinking": "Hide thinking" + }, + "historyBuckets": { + "today": "Today", + "yesterday": "Yesterday", + "withinWeek": "Within 1 Week", + "withinTwoWeeks": "Within 2 Weeks", + "withinMonth": "Within 1 Month", + "older": "Older than 1 Month" } -} \ No newline at end of file +} diff --git a/src/i18n/locales/ja/chat.json b/src/i18n/locales/ja/chat.json index 6904fa7..bdfd543 100644 --- a/src/i18n/locales/ja/chat.json +++ b/src/i18n/locales/ja/chat.json @@ -14,5 +14,13 @@ "refresh": "チャットを更新", "showThinking": "思考を表示", "hideThinking": "思考を非表示" + }, + "historyBuckets": { + "today": "今日", + "yesterday": "昨日", + "withinWeek": "1週間以内", + "withinTwoWeeks": "2週間以内", + "withinMonth": "1か月以内", + "older": "1か月より前" } -} \ No newline at end of file +} diff --git a/src/i18n/locales/zh/chat.json b/src/i18n/locales/zh/chat.json index 21a7024..84031e1 100644 --- a/src/i18n/locales/zh/chat.json +++ b/src/i18n/locales/zh/chat.json @@ -14,5 +14,13 @@ "refresh": "刷新聊天", "showThinking": "显示思考过程", "hideThinking": "隐藏思考过程" + }, + "historyBuckets": { + "today": "今天", + "yesterday": "昨天", + "withinWeek": "一周内", + "withinTwoWeeks": "两周内", + "withinMonth": "一个月内", + "older": "一个月之前" } -} \ No newline at end of file +}