From 2b095278c7bc4d7639ee88fe3b0d4626296c1409 Mon Sep 17 00:00:00 2001 From: Nicolas Varrot Date: Fri, 13 Feb 2026 21:54:45 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20session=20filter=20chips=20in=20sidebar?= =?UTF-8?q?=20=E2=80=94=20filter=20by=20channel=20type,=20active=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sidebar.tsx | 105 +++++++++++++++++++++++++++++++++++-- src/lib/i18n.ts | 4 ++ 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 069a8b4..efaa924 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,5 +1,5 @@ import { useState, useMemo, useRef, useEffect, useCallback } from 'react'; -import { X, Sparkles, Search, Pin, Trash2, Columns2 } from 'lucide-react'; +import { X, Sparkles, Search, Pin, Trash2, Columns2, Clock, Bot, MessageSquare, Globe, Zap } from 'lucide-react'; import type { Session } from '../types'; import { useT } from '../hooks/useLocale'; import { SessionIcon } from './SessionIcon'; @@ -9,6 +9,42 @@ import { relativeTime } from '../lib/relativeTime'; const PINNED_KEY = 'pinchchat-pinned-sessions'; const WIDTH_KEY = 'pinchchat-sidebar-width'; const ORDER_KEY = 'pinchchat-session-order'; +const FILTER_KEY = 'pinchchat-session-filter'; + +/** Detect the category of a session for filtering */ +function sessionCategory(s: Session): string { + if (s.key.includes(':cron:')) return 'cron'; + if (s.key.includes(':spawn:') || s.key.includes(':sub:')) return 'agent'; + const ch = s.channel?.toLowerCase(); + if (ch && ch !== 'webchat') return ch; + return 'other'; +} + +/** Get unique categories present in sessions */ +function getAvailableCategories(sessions: Session[]): string[] { + const cats = new Set(); + for (const s of sessions) cats.add(sessionCategory(s)); + return Array.from(cats).sort(); +} + +/** Icons for filter chips */ +function FilterChipIcon({ cat, size = 12 }: { cat: string; size?: number }) { + switch (cat) { + case 'cron': return ; + case 'agent': return ; + case 'discord': return ; + case 'telegram': return ; + default: return ; + } +} + +/** Pretty label for category */ +function categoryLabel(cat: string): string { + if (cat === 'cron') return 'Cron'; + if (cat === 'agent') return 'Agents'; + if (cat === 'other') return 'Chat'; + return cat.charAt(0).toUpperCase() + cat.slice(1); +} const MIN_WIDTH = 220; const MAX_WIDTH = 480; const DEFAULT_WIDTH = 288; // w-72 @@ -72,6 +108,9 @@ export function Sidebar({ sessions, activeSession, onSwitch, onDelete, onSplit, const [dragging, setDragging] = useState(false); const [confirmDelete, setConfirmDelete] = useState(null); const [customOrder, setCustomOrder] = useState(getSavedOrder); + const [channelFilter, setChannelFilter] = useState(() => { + try { return localStorage.getItem(FILTER_KEY); } catch { return null; } + }); const [dragKey, setDragKey] = useState(null); const [dropTarget, setDropTarget] = useState(null); const searchRef = useRef(null); @@ -143,11 +182,30 @@ export function Sidebar({ sessions, activeSession, onSwitch, onDelete, onSplit, setFocusIdx(-1); }, []); + const availableCategories = useMemo(() => getAvailableCategories(sessions), [sessions]); + + const toggleChannelFilter = useCallback((cat: string) => { + setChannelFilter(prev => { + const next = prev === cat ? null : cat; + try { + if (next) localStorage.setItem(FILTER_KEY, next); + else localStorage.removeItem(FILTER_KEY); + } catch { /* noop */ } + return next; + }); + }, []); + const filtered = useMemo(() => { let list = sessions; + // Apply channel filter + if (channelFilter === 'active') { + list = list.filter(s => s.isActive); + } else if (channelFilter) { + list = list.filter(s => sessionCategory(s) === channelFilter); + } if (filter.trim()) { const q = filter.toLowerCase(); - list = sessions.filter(s => sessionDisplayName(s).toLowerCase().includes(q)); + list = list.filter(s => sessionDisplayName(s).toLowerCase().includes(q)); } // Sort pinned sessions to top (preserving relative order within each group) const pinnedList = list.filter(s => pinned.has(s.key)); @@ -165,7 +223,7 @@ export function Sidebar({ sessions, activeSession, onSwitch, onDelete, onSplit, pinnedList.sort(byCustomThenRecent); unpinnedList.sort(byCustomThenRecent); return [...pinnedList, ...unpinnedList]; - }, [sessions, filter, pinned, customOrder]); + }, [sessions, filter, pinned, customOrder, channelFilter]); return ( <> @@ -213,6 +271,47 @@ export function Sidebar({ sessions, activeSession, onSwitch, onDelete, onSplit, )} + {/* Channel filter chips */} + {availableCategories.length > 1 && ( +
+ + + {availableCategories.map(cat => ( + + ))} +
+ )} +
= { 'sidebar.openSplit': 'Ouvrir en vue scindée', 'sidebar.close': 'Fermer la barre latérale', 'sidebar.clearSearch': 'Effacer la recherche', + 'sidebar.filterAll': 'Tout', + 'sidebar.filterActive': 'Actives', 'split.close': 'Fermer la vue scindée', 'app.mainChat': 'Chat principal', 'app.splitPane': 'Volet scindé',