From d832f656599b153be8826bc43e1832209b2a1bf6 Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Sun, 08 Mar 2026 07:10:12 +0100
Subject: [PATCH] feat: per-session typing, incoming toast, remove switched-to messages
---
contexts/ChatContext.tsx | 56 ++++++++++++++++++++++++++++++++++++++------------------
1 files changed, 38 insertions(+), 18 deletions(-)
diff --git a/contexts/ChatContext.tsx b/contexts/ChatContext.tsx
index 5269516..b109bb7 100644
--- a/contexts/ChatContext.tsx
+++ b/contexts/ChatContext.tsx
@@ -116,6 +116,12 @@
// --- Context ---
+interface IncomingToast {
+ sessionId: string;
+ sessionName: string;
+ preview: string;
+}
+
interface ChatContextValue {
messages: Message[];
sendTextMessage: (text: string) => void;
@@ -136,6 +142,8 @@
loadMoreMessages: () => void;
hasMoreMessages: boolean;
unreadCounts: Record<string, number>;
+ incomingToast: IncomingToast | null;
+ dismissToast: () => void;
latestScreenshot: string | null;
requestScreenshot: () => void;
sendNavKey: (key: string) => void;
@@ -156,8 +164,11 @@
const [messages, setMessages] = useState<Message[]>([]);
// Unread counts for non-active sessions
const [unreadCounts, setUnreadCounts] = useState<Record<string, number>>({});
- // Typing indicator from server
+ // Per-session typing indicator (sessionId → boolean)
+ const typingMapRef = useRef<Record<string, boolean>>({});
const [isTyping, setIsTyping] = useState(false);
+ // Toast for other-session incoming messages
+ const [incomingToast, setIncomingToast] = useState<{ sessionId: string; sessionName: string; preview: string } | null>(null);
// PAI projects list
const [projects, setProjects] = useState<PaiProject[]>([]);
// Pagination: does the active session have more messages in storage?
@@ -199,6 +210,9 @@
delete next[active.id];
return next;
});
+ // Sync typing indicator for the new active session
+ const activeTyping = typingMapRef.current[active.id] ?? false;
+ setIsTyping(activeTyping);
}
activeSessionIdRef.current = active.id;
return active.id;
@@ -251,6 +265,16 @@
...u,
[sessionId]: (u[sessionId] ?? 0) + 1,
}));
+ // Show toast for other-session messages (assistant only, skip system noise)
+ if (msg.role === "assistant") {
+ setSessions((prev) => {
+ const session = prev.find((s) => s.id === sessionId);
+ const name = session?.name ?? sessionId.slice(0, 8);
+ const preview = msg.type === "voice" ? "🎤 Voice note" : msg.type === "image" ? "📷 Image" : (msg.content ?? "").slice(0, 60);
+ setIncomingToast({ sessionId, sessionName: name, preview });
+ return prev;
+ });
+ }
}
}, []);
@@ -359,26 +383,12 @@
break;
}
case "session_switched": {
- const msg: Message = {
- id: generateId(),
- role: "system",
- type: "text",
- content: `Switched to ${data.name}`,
- timestamp: Date.now(),
- };
- addMessageToActive(msg);
+ // Just refresh session list — no system message needed
sendCommand("sessions");
break;
}
case "session_renamed": {
- const msg: Message = {
- id: generateId(),
- role: "system",
- type: "text",
- content: `Renamed to ${data.name}`,
- timestamp: Date.now(),
- };
- addMessageToActive(msg);
+ // Just refresh session list — no system message needed
sendCommand("sessions");
break;
}
@@ -388,7 +398,11 @@
break;
}
case "typing": {
- setIsTyping(data.typing);
+ const typingSession = (data.sessionId as string) || activeSessionIdRef.current || "_global";
+ typingMapRef.current[typingSession] = !!data.typing;
+ // Only show typing indicator if it's for the active session
+ const activeTyping = typingMapRef.current[activeSessionIdRef.current ?? ""] ?? false;
+ setIsTyping(activeTyping);
break;
}
case "status": {
@@ -545,6 +559,10 @@
sendCommand("projects");
}, [sendCommand]);
+ const dismissToast = useCallback(() => {
+ setIncomingToast(null);
+ }, []);
+
const loadMoreMessages = useCallback(() => {
const sessId = activeSessionIdRef.current;
if (!sessId) return;
@@ -595,6 +613,8 @@
loadMoreMessages,
hasMoreMessages,
unreadCounts,
+ incomingToast,
+ dismissToast,
latestScreenshot,
requestScreenshot,
sendNavKey,
--
Gitblit v1.3.1