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: