feat: unread message count badges in sidebar sessions
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pinchchat",
|
"name": "pinchchat",
|
||||||
"version": "1.59.0",
|
"version": "1.60.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pinchchat",
|
"name": "pinchchat",
|
||||||
"version": "1.59.0",
|
"version": "1.60.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pinchchat",
|
"name": "pinchchat",
|
||||||
"version": "1.59.0",
|
"version": "1.60.0",
|
||||||
"description": "A sleek, dark-themed webchat UI for OpenClaw — monitor sessions, stream responses, and inspect tool calls in real-time.",
|
"description": "A sleek, dark-themed webchat UI for OpenClaw — monitor sessions, stream responses, and inspect tool calls in real-time.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -466,7 +466,9 @@ export function Sidebar({ sessions, activeSession, onSwitch, onDelete, onSplit,
|
|||||||
<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 && (
|
{s.hasUnread && !isActive && (
|
||||||
<span className="absolute -top-0.5 -left-0.5 h-2 w-2 rounded-full bg-[var(--pc-accent)] shadow-[0_0_8px_rgba(34,211,238,0.7)]" />
|
<span className="absolute -top-1.5 -left-1.5 min-w-[16px] h-4 flex items-center justify-center rounded-full bg-[var(--pc-accent)] text-[9px] font-bold text-zinc-900 leading-none px-1 shadow-[0_0_8px_rgba(34,211,238,0.5)]">
|
||||||
|
{(s.unreadCount || 1) > 99 ? '99+' : (s.unreadCount || 1)}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
|
|||||||
@@ -53,7 +53,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 [unreadSessions, setUnreadSessions] = useState<Map<string, number>>(new Map());
|
||||||
const [agentIdentity, setAgentIdentity] = useState<AgentIdentity | null>(null);
|
const [agentIdentity, setAgentIdentity] = useState<AgentIdentity | null>(null);
|
||||||
/** Map of runId → generation duration (ms), preserved across loadHistory reloads */
|
/** Map of runId → generation duration (ms), preserved across loadHistory reloads */
|
||||||
const generationTimesRef = useRef<Map<string, number>>(new Map());
|
const generationTimesRef = useRef<Map<string, number>>(new Map());
|
||||||
@@ -337,9 +337,8 @@ export function useGateway() {
|
|||||||
// Mark non-active sessions as unread when they receive a final message
|
// Mark non-active sessions as unread when they receive a final message
|
||||||
if (state === 'final' && evtSession) {
|
if (state === 'final' && evtSession) {
|
||||||
setUnreadSessions(prev => {
|
setUnreadSessions(prev => {
|
||||||
if (prev.has(evtSession)) return prev;
|
const next = new Map(prev);
|
||||||
const next = new Set(prev);
|
next.set(evtSession, (prev.get(evtSession) || 0) + 1);
|
||||||
next.add(evtSession);
|
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -483,7 +482,7 @@ export function useGateway() {
|
|||||||
setMessages([]);
|
setMessages([]);
|
||||||
setUnreadSessions(prev => {
|
setUnreadSessions(prev => {
|
||||||
if (!prev.has(key)) return prev;
|
if (!prev.has(key)) return prev;
|
||||||
const next = new Set(prev);
|
const next = new Map(prev);
|
||||||
next.delete(key);
|
next.delete(key);
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
@@ -538,6 +537,7 @@ export function useGateway() {
|
|||||||
...s,
|
...s,
|
||||||
isActive: activeSessions.has(s.key),
|
isActive: activeSessions.has(s.key),
|
||||||
hasUnread: unreadSessions.has(s.key),
|
hasUnread: unreadSessions.has(s.key),
|
||||||
|
unreadCount: unreadSessions.get(s.key) || 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const getClient = useCallback(() => clientRef.current, []);
|
const getClient = useCallback(() => clientRef.current, []);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export interface Session {
|
|||||||
messageCount?: number;
|
messageCount?: number;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
hasUnread?: boolean;
|
hasUnread?: boolean;
|
||||||
|
unreadCount?: number;
|
||||||
totalTokens?: number;
|
totalTokens?: number;
|
||||||
contextTokens?: number;
|
contextTokens?: number;
|
||||||
inputTokens?: number;
|
inputTokens?: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user