feat: add unread message indicators on sidebar sessions
Show a cyan dot on sessions that received new messages while viewing a different session. The dot clears when switching to that session.
This commit is contained in:
@@ -147,6 +147,9 @@ export function Sidebar({ sessions, activeSession, onSwitch, open, onClose }: Pr
|
|||||||
{s.isActive && (
|
{s.isActive && (
|
||||||
<span className="absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full bg-violet-400 shadow-[0_0_8px_rgba(168,85,247,0.7)] animate-pulse" />
|
<span className="absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full bg-violet-400 shadow-[0_0_8px_rgba(168,85,247,0.7)] animate-pulse" />
|
||||||
)}
|
)}
|
||||||
|
{s.hasUnread && !isActive && (
|
||||||
|
<span className="absolute -top-0.5 -left-0.5 h-2 w-2 rounded-full bg-cyan-400 shadow-[0_0_8px_rgba(34,211,238,0.7)]" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export function useGateway() {
|
|||||||
useEffect(() => { activeSessionRef.current = activeSession; }, [activeSession]);
|
useEffect(() => { activeSessionRef.current = activeSession; }, [activeSession]);
|
||||||
const currentRunIdRef = useRef<string | null>(null);
|
const currentRunIdRef = useRef<string | null>(null);
|
||||||
const [activeSessions, setActiveSessions] = useState<Set<string>>(new Set());
|
const [activeSessions, setActiveSessions] = useState<Set<string>>(new Set());
|
||||||
|
const [unreadSessions, setUnreadSessions] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const handleAgentEvent = useCallback((payload: JsonPayload) => {
|
const handleAgentEvent = useCallback((payload: JsonPayload) => {
|
||||||
if (payload?.stream !== 'tool') return;
|
if (payload?.stream !== 'tool') return;
|
||||||
@@ -241,7 +242,18 @@ export function useGateway() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evtSession !== activeSessionRef.current) return;
|
if (evtSession !== activeSessionRef.current) {
|
||||||
|
// Mark non-active sessions as unread when they receive a final message
|
||||||
|
if (state === 'final' && evtSession) {
|
||||||
|
setUnreadSessions(prev => {
|
||||||
|
if (prev.has(evtSession)) return prev;
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.add(evtSession);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (state === 'delta') {
|
if (state === 'delta') {
|
||||||
const text = extractText(message);
|
const text = extractText(message);
|
||||||
@@ -358,6 +370,12 @@ export function useGateway() {
|
|||||||
setActiveSession(key);
|
setActiveSession(key);
|
||||||
activeSessionRef.current = key;
|
activeSessionRef.current = key;
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
|
setUnreadSessions(prev => {
|
||||||
|
if (!prev.has(key)) return prev;
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.delete(key);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
loadHistory(key);
|
loadHistory(key);
|
||||||
}, [loadHistory]);
|
}, [loadHistory]);
|
||||||
|
|
||||||
@@ -388,6 +406,7 @@ export function useGateway() {
|
|||||||
const enrichedSessions = sessions.map(s => ({
|
const enrichedSessions = sessions.map(s => ({
|
||||||
...s,
|
...s,
|
||||||
isActive: activeSessions.has(s.key),
|
isActive: activeSessions.has(s.key),
|
||||||
|
hasUnread: unreadSessions.has(s.key),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export interface Session {
|
|||||||
label?: string;
|
label?: string;
|
||||||
messageCount?: number;
|
messageCount?: number;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
|
hasUnread?: boolean;
|
||||||
totalTokens?: number;
|
totalTokens?: number;
|
||||||
contextTokens?: number;
|
contextTokens?: number;
|
||||||
inputTokens?: number;
|
inputTokens?: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user