mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
Fix Matrix notification system and add loading states
- Fix Goose assistant response spam: suppress role='assistant' notifications - Fix stale tab state: use fresh shouldSuppressNotification calls - Remove buggy legacy /pair suppression logic that checked sender ID - Fix 'Open Chat' to check existing tabs before navigating - Add Matrix room ID display in SessionListView - Optimize MatrixSessionService to defer heavy loading until needed - Add loading state for Matrix chat initialization - Improve loading indicator visibility in BaseChat2 Fixes notification spam, incorrect suppression, and blank chat states.
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
# Loading State for Matrix Chat Initialization
|
||||
|
||||
## Summary
|
||||
Added a prominent loading state when opening Matrix chats from notifications to provide visual feedback while the conversation is being initialized and message history is being loaded.
|
||||
|
||||
## Problem
|
||||
When clicking "Open Chat" on a Matrix notification, the chat would appear empty for a few seconds while:
|
||||
1. Backend session was being created or retrieved
|
||||
2. Matrix room history was being loaded
|
||||
3. Session mapping was being established
|
||||
|
||||
This made it look like the chat was broken or empty, even though it was just loading.
|
||||
|
||||
## Solution
|
||||
Implemented a two-phase loading state:
|
||||
|
||||
### Phase 1: Create Temporary Tab with Loading State
|
||||
When `openMatrixChat` is called, immediately create a tab with:
|
||||
- `loadingChat: true` - Shows loading indicator
|
||||
- Temporary session ID (`temp_matrix_${timestamp}`)
|
||||
- Tab is immediately visible to user
|
||||
|
||||
### Phase 2: Update with Real Session
|
||||
Asynchronously:
|
||||
- Get or create backend session mapping
|
||||
- Update tab with real backend session ID
|
||||
- Set `loadingChat: false` - Removes loading indicator
|
||||
- Message history loads via `useChatStream`
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. TabContext.tsx - Add Loading State to `openMatrixChat`
|
||||
|
||||
**File**: `ui/desktop/src/contexts/TabContext.tsx`
|
||||
|
||||
**Change**: Modified `openMatrixChat` to create a temporary loading tab immediately, then update it with the real session ID asynchronously.
|
||||
|
||||
**Before**:
|
||||
```typescript
|
||||
const openMatrixChat = useCallback(async (roomId: string, senderId: string) => {
|
||||
// Check for existing tab...
|
||||
|
||||
// Get or create backend session (blocking)
|
||||
let backendSessionId = sessionMappingService.getGooseSessionId(roomId);
|
||||
// ... create session if needed ...
|
||||
|
||||
// Create tab with real session ID
|
||||
const newTab = createNewTab({ sessionId: backendSessionId, ... });
|
||||
const newTabState = { tab: newTab, chat: {...}, loadingChat: false };
|
||||
|
||||
setTabStates(prev => [...prev, newTabState]);
|
||||
setActiveTabId(newTab.id);
|
||||
}, [tabStates]);
|
||||
```
|
||||
|
||||
**After**:
|
||||
```typescript
|
||||
const openMatrixChat = useCallback(async (roomId: string, senderId: string) => {
|
||||
// Check for existing tab...
|
||||
|
||||
// Create temporary tab with loading state IMMEDIATELY
|
||||
const tempTab = createNewTab({
|
||||
sessionId: `temp_matrix_${Date.now()}`,
|
||||
title: `Chat with ${senderName}`,
|
||||
type: 'matrix',
|
||||
matrixRoomId: roomId,
|
||||
matrixRecipientId: senderId,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
const tempTabState = {
|
||||
tab: tempTab,
|
||||
chat: {...},
|
||||
loadingChat: true // Show loading indicator
|
||||
};
|
||||
|
||||
// Add loading tab immediately for instant feedback
|
||||
setTabStates(prev => [...prev, tempTabState]);
|
||||
setActiveTabId(tempTab.id);
|
||||
|
||||
// Get or create backend session (async, doesn't block UI)
|
||||
let backendSessionId = sessionMappingService.getGooseSessionId(roomId);
|
||||
// ... create session if needed ...
|
||||
|
||||
// Update tab with real session ID and remove loading state
|
||||
setTabStates(prev => prev.map(ts =>
|
||||
ts.tab.id === tempTab.id
|
||||
? {
|
||||
...ts,
|
||||
tab: { ...ts.tab, sessionId: backendSessionId },
|
||||
chat: { ...ts.chat, sessionId: backendSessionId },
|
||||
loadingChat: false // Remove loading indicator
|
||||
}
|
||||
: ts
|
||||
));
|
||||
}, [tabStates]);
|
||||
```
|
||||
|
||||
### 2. BaseChat2.tsx - Improve Loading Indicator Visibility
|
||||
|
||||
**File**: `ui/desktop/src/components/BaseChat2.tsx`
|
||||
|
||||
**Change**: Made the loading indicator more prominent and centered.
|
||||
|
||||
**Before**:
|
||||
```typescript
|
||||
{loadingChat && (
|
||||
<div className="px-6 py-4">
|
||||
<LoadingGoose
|
||||
message="loading conversation..."
|
||||
chatState={ChatState.Idle}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
**After**:
|
||||
```typescript
|
||||
{loadingChat && (
|
||||
<div className="flex items-center justify-center h-full min-h-[400px]">
|
||||
<div className="text-center">
|
||||
<LoadingGoose
|
||||
message="Loading conversation..."
|
||||
chatState={ChatState.Idle}
|
||||
/>
|
||||
<p className="text-text-muted text-sm mt-4">
|
||||
Fetching message history...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
## User Experience Flow
|
||||
|
||||
### Before:
|
||||
1. User clicks "Open Chat" on notification
|
||||
2. **[2-3 second delay with empty chat]**
|
||||
3. Messages suddenly appear
|
||||
|
||||
### After:
|
||||
1. User clicks "Open Chat" on notification
|
||||
2. **Tab opens immediately with loading indicator**
|
||||
3. Loading message: "Loading conversation... Fetching message history..."
|
||||
4. Messages load and loading indicator disappears
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Instant Feedback**: Tab opens immediately, no perceived delay
|
||||
2. **Clear Communication**: User knows the app is working, not frozen
|
||||
3. **Better UX**: Loading state prevents confusion about empty chats
|
||||
4. **Non-Blocking**: Backend session creation doesn't block UI rendering
|
||||
5. **Smooth Transition**: Loading indicator smoothly transitions to loaded messages
|
||||
|
||||
## Testing
|
||||
|
||||
1. **Test New Matrix Chat**:
|
||||
- Click "Open Chat" on a notification for a room you haven't opened yet
|
||||
- ✅ Tab should open immediately with loading indicator
|
||||
- ✅ Loading message should be visible and centered
|
||||
- ✅ After 1-2 seconds, messages should load and loading indicator disappears
|
||||
|
||||
2. **Test Existing Matrix Chat**:
|
||||
- Click "Open Chat" on a notification for a room already open in a tab
|
||||
- ✅ Should switch to existing tab immediately (no loading state)
|
||||
|
||||
3. **Test Slow Network**:
|
||||
- Simulate slow network conditions
|
||||
- ✅ Loading indicator should remain visible until session is ready
|
||||
- ✅ User should never see an empty chat without explanation
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Loading State Prop**: `loadingChat: boolean` in `TabState`
|
||||
- **Temporary Session ID**: `temp_matrix_${timestamp}` format
|
||||
- **Session Update**: Uses `setTabStates` to update specific tab without re-creating
|
||||
- **Loading Component**: `LoadingGoose` with custom message
|
||||
- **Minimum Height**: `min-h-[400px]` ensures loading indicator is visible even in small windows
|
||||
@@ -0,0 +1,163 @@
|
||||
# Notification System Fixes
|
||||
|
||||
## Summary
|
||||
Fixed four major issues with the Matrix notification system:
|
||||
1. **Goose assistant responses were triggering notifications** (spam)
|
||||
2. **Notifications continued after closing Matrix tabs** (stale state)
|
||||
3. **Wrong room notifications were suppressed** (legacy pair view bug)
|
||||
4. **"Open Chat" created blank sessions** instead of opening existing chats
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. MessageNotification.tsx - Suppress Goose Assistant Responses
|
||||
|
||||
**Problem**: Goose's AI responses in collaborative Matrix sessions were showing as notifications because they come from other users' Goose instances.
|
||||
|
||||
**Solution**: Parse the `goose-session-message:` content and check the `role` field. Only show notifications for `role: "user"` (human messages), suppress `role: "assistant"` (AI responses).
|
||||
|
||||
**Code Added** (lines ~108-135):
|
||||
```typescript
|
||||
// IMPORTANT: Don't show notifications for Goose assistant responses!
|
||||
// These are AI responses, not human messages that need notification
|
||||
// Parse the goose-session-message content to check the role
|
||||
try {
|
||||
if (content.startsWith('goose-session-message:')) {
|
||||
const jsonContent = content.substring('goose-session-message:'.length);
|
||||
const parsed = JSON.parse(jsonContent);
|
||||
|
||||
// Suppress notifications for assistant messages (Goose's responses)
|
||||
if (parsed.role === 'assistant') {
|
||||
console.log('🔕 Suppressing Goose assistant message notification (AI response, not human):', {
|
||||
roomId,
|
||||
sender,
|
||||
role: parsed.role
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Only show notifications for user messages (human messages in collaborative sessions)
|
||||
console.log('🦆 Goose user message detected (human message in collab session):', {
|
||||
roomId,
|
||||
sender,
|
||||
role: parsed.role
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to parse goose-session-message content:', error);
|
||||
// If parsing fails, fall through to normal notification logic
|
||||
}
|
||||
```
|
||||
|
||||
### 2. MessageNotification.tsx - Fix Stale Tab State in Notification Suppression
|
||||
|
||||
**Problem**: When a Matrix tab is closed, the notification listener still had the old tab state captured in its closure, so it continued suppressing notifications for that room.
|
||||
|
||||
**Solution**: Changed from destructuring `shouldSuppressNotification` to storing the entire `activeSessionHook` object, then calling the method fresh each time to get current tab state.
|
||||
|
||||
**Code Changed** (lines ~34-35, ~59, ~144):
|
||||
```typescript
|
||||
// OLD:
|
||||
const { shouldSuppressNotification } = useActiveSession();
|
||||
const shouldSuppress = shouldSuppressNotification(roomId, sender);
|
||||
|
||||
// NEW:
|
||||
const activeSessionHook = useActiveSession();
|
||||
// CRITICAL: Call shouldSuppressNotification fresh each time to get current tab state
|
||||
// Don't capture it in closure - this ensures we always check against current tabs
|
||||
const shouldSuppress = activeSessionHook.shouldSuppressNotification(roomId, sender);
|
||||
```
|
||||
|
||||
This ensures that every time a message arrives, the suppression check uses the **current** tab state from `TabContext`, not stale state from when the listener was created.
|
||||
|
||||
### 3. useActiveSession.ts - Remove Buggy Legacy Pair View Suppression
|
||||
|
||||
**Problem**: The legacy `/pair` view suppression was checking if `messageSenderId === currentRecipientId`, which incorrectly suppressed notifications from **different rooms** when the sender ID matched.
|
||||
|
||||
**Example Bug**:
|
||||
- You're viewing room A with recipient `@spence:tchncs.de`
|
||||
- Message arrives from room B, sent by `@spence:tchncs.de` (you)
|
||||
- Legacy logic suppressed it because sender matched recipient, even though it's a different room!
|
||||
|
||||
**Solution**: Removed the buggy legacy `/pair` view suppression logic entirely. The Matrix room check (line ~169) is the correct way to suppress notifications.
|
||||
|
||||
**Code Removed** (lines ~210-220):
|
||||
```typescript
|
||||
// REMOVED:
|
||||
if (currentView.path.startsWith('/pair') &&
|
||||
currentView.matrixRecipientId &&
|
||||
messageSenderId === currentView.matrixRecipientId) {
|
||||
console.log('🔕 Suppressing notification: message from current pair recipient (legacy)');
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOW: Only suppress based on room ID match, not sender ID
|
||||
```
|
||||
|
||||
### 4. App.tsx - Fix "Open Chat" to Use Existing Tabs
|
||||
|
||||
**Problem**: Clicking "Open Chat" on a notification always navigated to `/pair`, which could cause the tab system to re-initialize and lose track of existing tabs. This resulted in creating a blank new session instead of opening the existing chat.
|
||||
|
||||
**Solution**: Check if we're already on the `/pair` route before navigating. If we are, dispatch the `create-matrix-tab` event immediately without navigation.
|
||||
|
||||
**Code Changed** (lines ~368-395):
|
||||
```typescript
|
||||
// OLD:
|
||||
navigate('/pair');
|
||||
setTimeout(() => {
|
||||
const event = new CustomEvent('create-matrix-tab', { detail: { roomId, senderId } });
|
||||
window.dispatchEvent(event);
|
||||
}, 100);
|
||||
|
||||
// NEW:
|
||||
const isOnPairRoute = location.pathname === '/pair' || location.pathname === '/tabs';
|
||||
|
||||
if (isOnPairRoute) {
|
||||
// Already on pair route - just dispatch the event immediately
|
||||
const event = new CustomEvent('create-matrix-tab', { detail: { roomId, senderId } });
|
||||
window.dispatchEvent(event);
|
||||
} else {
|
||||
// Navigate to pair view first, then dispatch event
|
||||
navigate('/pair');
|
||||
setTimeout(() => {
|
||||
const event = new CustomEvent('create-matrix-tab', { detail: { roomId, senderId } });
|
||||
window.dispatchEvent(event);
|
||||
}, 100);
|
||||
}
|
||||
```
|
||||
|
||||
This ensures that `openMatrixChat` in `TabContext.tsx` can properly check for existing tabs and switch to them instead of creating duplicates.
|
||||
|
||||
## Testing
|
||||
|
||||
1. **Test Goose Assistant Suppression**:
|
||||
- Open a Matrix collaborative session
|
||||
- Send a message and wait for Goose to respond
|
||||
- ✅ You should NOT see a notification for Goose's response
|
||||
- ✅ You should only see notifications for human messages from collaborators
|
||||
|
||||
2. **Test Tab Close Behavior**:
|
||||
- Open a Matrix chat tab
|
||||
- While viewing it, messages should be suppressed (no notifications)
|
||||
- Close the tab
|
||||
- Send a message to that room from another device
|
||||
- ✅ You should now see a notification (not suppressed)
|
||||
|
||||
3. **Test Multi-Room Suppression**:
|
||||
- Open Matrix room A in a tab
|
||||
- Receive a message in room B (different room)
|
||||
- ✅ You should see a notification for room B
|
||||
- ✅ Room B notification should NOT be suppressed just because you're viewing room A
|
||||
|
||||
4. **Test "Open Chat" from Notification**:
|
||||
- Receive a notification for a Matrix room
|
||||
- Click "Open Chat" on the notification
|
||||
- ✅ If the room is already open in a tab, it should switch to that tab
|
||||
- ✅ If the room is not open, it should create a new tab with the existing conversation loaded
|
||||
- ✅ Should NOT create a blank new session
|
||||
|
||||
## Impact
|
||||
|
||||
- **Reduced notification spam**: No more constant Goose assistant response notifications
|
||||
- **Correct suppression behavior**: Notifications work correctly after closing tabs and across multiple rooms
|
||||
- **Better UX**: Users only get notified for actual human messages that need attention
|
||||
- **Proper navigation**: "Open Chat" correctly opens existing conversations instead of blank sessions
|
||||
@@ -0,0 +1,180 @@
|
||||
# SessionListView Matrix Room ID Display - Changes Summary
|
||||
|
||||
## Overview
|
||||
Fixed Matrix sessions not appearing in chat history and added Matrix room ID display to make it clearer which tiles are Matrix collaborative sessions.
|
||||
|
||||
## 🐛 Critical Bug Fix
|
||||
|
||||
### Issue Found
|
||||
Matrix sessions were being synced but not appearing in the UI because of a connection status check bug:
|
||||
- **Symptom**: Console showed `matrix: 0` in session counts despite Matrix messages being synced
|
||||
- **Root Cause**: `MatrixSessionService.getMatrixSessions()` was checking `connectionStatus.connected` which was `false` even though Matrix was actively syncing
|
||||
- **Impact**: All Matrix rooms were being filtered out before processing
|
||||
|
||||
### Fix Applied
|
||||
**File**: `ui/desktop/src/services/MatrixSessionService.ts` (lines 42-56)
|
||||
|
||||
Changed the connection check to also accept `SYNCING` and `PREPARED` sync states:
|
||||
|
||||
```typescript
|
||||
// OLD CODE (buggy):
|
||||
if (!connectionStatus.connected) {
|
||||
console.log('📋 Matrix service not connected, skipping Matrix sessions');
|
||||
return [];
|
||||
}
|
||||
|
||||
// NEW CODE (fixed):
|
||||
const isUsable = connectionStatus.connected ||
|
||||
connectionStatus.syncState === 'SYNCING' ||
|
||||
connectionStatus.syncState === 'PREPARED';
|
||||
|
||||
if (!isUsable) {
|
||||
console.log('📋 Matrix service not ready (state:', connectionStatus.syncState, '), skipping Matrix sessions');
|
||||
return [];
|
||||
}
|
||||
```
|
||||
|
||||
**Why This Works**: Matrix can be in `SYNCING` state before `isConnected` is set to `true`, but it still has access to rooms and can process them.
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Added Matrix Room ID Display
|
||||
**File**: `ui/desktop/src/components/sessions/SessionListView.tsx`
|
||||
|
||||
**Location**: In the `SessionItem` component, after the participants count display
|
||||
|
||||
**Code Added**:
|
||||
```tsx
|
||||
{/* Show Matrix Room ID for Matrix sessions */}
|
||||
{isMatrix && session.extension_data?.matrix?.roomId && (
|
||||
<div className="flex items-center text-text-muted text-xs mb-1">
|
||||
<Hash className="w-3 h-3 mr-1 flex-shrink-0" />
|
||||
<span className="font-mono text-[10px] truncate opacity-70" title={session.extension_data.matrix.roomId}>
|
||||
{session.extension_data.matrix.roomId}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
**Visual Design**:
|
||||
- Small monospace font (10px) for technical appearance
|
||||
- Hash icon (#) to indicate room/channel identifier
|
||||
- Truncated text with full room ID in tooltip on hover
|
||||
- 70% opacity to keep it subtle
|
||||
- Only displays for Matrix sessions
|
||||
|
||||
### 2. Added Debug Logging
|
||||
**Purpose**: To help diagnose why Matrix session icons might not be appearing
|
||||
|
||||
**Code Added**:
|
||||
```tsx
|
||||
// Debug logging to see what's happening
|
||||
if (session.extension_data?.matrix) {
|
||||
console.log('🔍 Matrix session detected:', {
|
||||
sessionId: session.id,
|
||||
displayInfoType: displayInfo.type,
|
||||
isMatrix,
|
||||
isCollaborative,
|
||||
hasMatrixData: !!session.extension_data?.matrix,
|
||||
roomId: session.extension_data?.matrix?.roomId,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**What to Look For in Console**:
|
||||
- Check if Matrix sessions are being detected
|
||||
- Verify `displayInfoType` is 'matrix' or 'collaborative'
|
||||
- Confirm `isMatrix` is `true` for Matrix sessions
|
||||
- Validate `roomId` is present
|
||||
|
||||
## Existing Matrix Session Features (Already in Code)
|
||||
|
||||
The SessionListView already has these features for differentiating Matrix sessions:
|
||||
|
||||
### Visual Indicators:
|
||||
1. **Icons** (top-right corner):
|
||||
- 💬 Green `MessageCircle` for Direct Messages
|
||||
- 👥 Purple `Users` for Collaborative Sessions
|
||||
- # Blue `Hash` for Group Chats
|
||||
|
||||
2. **Styling**:
|
||||
- Purple left border for collaborative sessions
|
||||
- Purple gradient background for collaborative sessions
|
||||
- "Collaborative" badge with Users icon
|
||||
|
||||
3. **Metadata**:
|
||||
- Participant count display
|
||||
- Participant avatars at bottom
|
||||
- Special working directory labels ("Direct Message", "Collaborative AI Session", "Group Chat")
|
||||
|
||||
4. **Actions**:
|
||||
- ✨ AI title regeneration button (only for Matrix sessions)
|
||||
- Edit and delete buttons
|
||||
|
||||
## How Matrix Sessions Work
|
||||
|
||||
### Data Flow:
|
||||
1. **MatrixSessionService** converts Matrix rooms to Session format
|
||||
2. **UnifiedSessionService** combines regular and Matrix sessions
|
||||
3. **SessionListView** displays them in a unified list
|
||||
|
||||
### Session Identification:
|
||||
- Matrix sessions have `session.extension_data.matrix` populated
|
||||
- Room ID is stored in `session.extension_data.matrix.roomId`
|
||||
- Session ID equals the Matrix room ID for Matrix sessions
|
||||
|
||||
### Display Info:
|
||||
- `displayInfo.type` can be: 'regular', 'matrix', or 'collaborative'
|
||||
- `isMatrix` = true when type is 'matrix' or 'collaborative'
|
||||
- `isCollaborative` = true when type is 'collaborative'
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### If Icons Don't Appear:
|
||||
1. Check console for "🔍 Matrix session detected" logs
|
||||
2. Verify Matrix service is connected (console: "📋 Matrix service not connected")
|
||||
3. Check if `isMatrix` is true in debug logs
|
||||
4. Verify `displayInfo.type` is 'matrix' or 'collaborative'
|
||||
|
||||
### If Room ID Doesn't Display:
|
||||
1. Check if `session.extension_data?.matrix?.roomId` exists
|
||||
2. Verify `isMatrix` is true
|
||||
3. Look for the Hash icon in the session tile
|
||||
|
||||
### Console Messages to Monitor:
|
||||
- `📋 Loaded unified sessions:` - Shows regular vs Matrix session counts
|
||||
- `📋 Matrix service not connected` - Matrix integration unavailable
|
||||
- `🚫 Message participants temporarily disabled` - Known debug message
|
||||
- `🔍 Matrix session detected:` - Debug info for each Matrix session
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
1. **Create a Matrix collaborative session** via the UI
|
||||
2. **Navigate to Chat History** view
|
||||
3. **Look for**:
|
||||
- Icon in top-right corner (should be 💬, 👥, or #)
|
||||
- Matrix room ID below participants count
|
||||
- Purple border/gradient for collaborative sessions
|
||||
- Participant avatars at bottom
|
||||
|
||||
4. **Check console** for:
|
||||
- Matrix session detection logs
|
||||
- Session type information
|
||||
- Any error messages
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Consider these improvements:
|
||||
1. **Clickable room ID** - Copy to clipboard on click
|
||||
2. **Room ID formatting** - Shorten display (e.g., "!abc...xyz:server")
|
||||
3. **Color coding** - Different colors for DM vs Group vs Collaborative
|
||||
4. **Filter by type** - Add filter buttons for Regular/Matrix/Collaborative sessions
|
||||
5. **Matrix status indicator** - Show if Matrix is connected/disconnected
|
||||
|
||||
## Related Files
|
||||
|
||||
- `ui/desktop/src/components/sessions/SessionListView.tsx` - Main view component
|
||||
- `ui/desktop/src/services/UnifiedSessionService.ts` - Session management
|
||||
- `ui/desktop/src/services/MatrixSessionService.ts` - Matrix room conversion
|
||||
- `ui/desktop/src/services/SessionMappingService.ts` - Room ID mapping
|
||||
- `ui/desktop/src/contexts/MatrixContext.tsx` - Matrix connection state
|
||||
+21
-9
@@ -365,23 +365,35 @@ export function AppInner() {
|
||||
|
||||
// Handle opening chat from message notifications
|
||||
const handleOpenChat = useCallback((roomId: string, senderId: string) => {
|
||||
console.log('📱 Opening chat for room:', roomId, 'sender:', senderId);
|
||||
console.log('📱 App: Opening chat for room:', roomId, 'sender:', senderId);
|
||||
|
||||
// For Matrix rooms (starting with !), navigate to pair view and let the tabbed system handle it
|
||||
if (roomId.startsWith('!')) {
|
||||
console.log('📱 Opening Matrix shared session for room:', roomId);
|
||||
console.log('📱 App: Opening Matrix chat for room:', roomId);
|
||||
|
||||
// Navigate to pair view first (this will ensure we're in the tabbed system)
|
||||
navigate('/pair');
|
||||
// Check if we're already on the /pair route
|
||||
const isOnPairRoute = location.pathname === '/pair' || location.pathname === '/tabs';
|
||||
|
||||
// Dispatch a custom event to create a Matrix tab
|
||||
// This will be handled by a component that has access to TabContext
|
||||
setTimeout(() => {
|
||||
if (isOnPairRoute) {
|
||||
// Already on pair route - just dispatch the event immediately
|
||||
console.log('📱 App: Already on pair route, dispatching create-matrix-tab event immediately');
|
||||
const event = new CustomEvent('create-matrix-tab', {
|
||||
detail: { roomId, senderId }
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}, 100); // Small delay to ensure navigation completes
|
||||
} else {
|
||||
// Navigate to pair view first, then dispatch event
|
||||
console.log('📱 App: Navigating to /pair first, then will dispatch event');
|
||||
navigate('/pair');
|
||||
|
||||
// Dispatch event after navigation completes
|
||||
setTimeout(() => {
|
||||
const event = new CustomEvent('create-matrix-tab', {
|
||||
detail: { roomId, senderId }
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}, 100);
|
||||
}
|
||||
} else {
|
||||
// For non-Matrix rooms, navigate to peers view
|
||||
navigate('/peers', {
|
||||
@@ -392,7 +404,7 @@ export function AppInner() {
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [navigate]);
|
||||
}, [navigate, location.pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Sending reactReady signal to Electron');
|
||||
|
||||
@@ -482,11 +482,16 @@ function BaseChatContent({
|
||||
|
||||
{/* Loading indicator for initial chat loading */}
|
||||
{loadingChat && (
|
||||
<div className="px-6 py-4">
|
||||
<LoadingGoose
|
||||
message="loading conversation..."
|
||||
chatState={ChatState.Idle}
|
||||
/>
|
||||
<div className="flex items-center justify-center h-full min-h-[400px]">
|
||||
<div className="text-center">
|
||||
<LoadingGoose
|
||||
message="Loading conversation..."
|
||||
chatState={ChatState.Idle}
|
||||
/>
|
||||
<p className="text-text-muted text-sm mt-4">
|
||||
Fetching message history...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@ const MessageNotification: React.FC<MessageNotificationProps> = ({
|
||||
} = useMatrix();
|
||||
|
||||
const location = useLocation();
|
||||
const { shouldSuppressNotification } = useActiveSession();
|
||||
const activeSessionHook = useActiveSession();
|
||||
|
||||
const [notifications, setNotifications] = useState<MessageNotificationData[]>([]);
|
||||
const [dismissedIds, setDismissedIds] = useState<Set<string>>(new Set());
|
||||
@@ -50,8 +50,9 @@ const MessageNotification: React.FC<MessageNotificationProps> = ({
|
||||
// Only show notifications for messages from others
|
||||
if (sender === currentUser.userId) return;
|
||||
|
||||
// Use the enhanced notification suppression logic
|
||||
const shouldSuppress = shouldSuppressNotification(roomId, sender);
|
||||
// CRITICAL: Call shouldSuppressNotification fresh each time to get current tab state
|
||||
// Don't capture it in closure - this ensures we always check against current tabs
|
||||
const shouldSuppress = activeSessionHook.shouldSuppressNotification(roomId, sender);
|
||||
console.log('🔍 MessageNotification suppression check:', {
|
||||
roomId,
|
||||
sender,
|
||||
@@ -105,8 +106,39 @@ const MessageNotification: React.FC<MessageNotificationProps> = ({
|
||||
// Only show notifications for messages from others
|
||||
if (metadata?.isFromSelf) return;
|
||||
|
||||
// Use the enhanced notification suppression logic
|
||||
if (shouldSuppressNotification(roomId, sender)) {
|
||||
// IMPORTANT: Don't show notifications for Goose assistant responses!
|
||||
// These are AI responses, not human messages that need notification
|
||||
// Parse the goose-session-message content to check the role
|
||||
try {
|
||||
if (content.startsWith('goose-session-message:')) {
|
||||
const jsonContent = content.substring('goose-session-message:'.length);
|
||||
const parsed = JSON.parse(jsonContent);
|
||||
|
||||
// Suppress notifications for assistant messages (Goose's responses)
|
||||
if (parsed.role === 'assistant') {
|
||||
console.log('🔕 Suppressing Goose assistant message notification (AI response, not human):', {
|
||||
roomId,
|
||||
sender,
|
||||
role: parsed.role
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Only show notifications for user messages (human messages in collaborative sessions)
|
||||
console.log('🦆 Goose user message detected (human message in collab session):', {
|
||||
roomId,
|
||||
sender,
|
||||
role: parsed.role
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to parse goose-session-message content:', error);
|
||||
// If parsing fails, fall through to normal notification logic
|
||||
}
|
||||
|
||||
// CRITICAL: Call shouldSuppressNotification fresh each time to get current tab state
|
||||
// Don't capture it in closure - this ensures we always check against current tabs
|
||||
if (activeSessionHook.shouldSuppressNotification(roomId, sender)) {
|
||||
console.log('🔕 Suppressing Goose message notification for active session:', roomId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -440,7 +440,7 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
||||
// The issue appears to be that Matrix sessions are sharing conversation data
|
||||
// or there's cross-contamination in the MatrixSessionService
|
||||
|
||||
console.log('🚫 Message participants temporarily disabled for debugging');
|
||||
// Silently return empty array without logging to avoid console spam
|
||||
return [];
|
||||
|
||||
// Only show message participants for Matrix sessions that have conversation data
|
||||
@@ -659,6 +659,18 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
||||
const isMatrix = displayInfo.type === 'matrix' || displayInfo.type === 'collaborative';
|
||||
const isMatrixDM = isMatrix && session.extension_data?.matrix?.isDirectMessage;
|
||||
const isCollaborative = displayInfo.type === 'collaborative';
|
||||
|
||||
// Debug logging to see what's happening
|
||||
if (session.extension_data?.matrix) {
|
||||
console.log('🔍 Matrix session detected:', {
|
||||
sessionId: session.id,
|
||||
displayInfoType: displayInfo.type,
|
||||
isMatrix,
|
||||
isCollaborative,
|
||||
hasMatrixData: !!session.extension_data?.matrix,
|
||||
roomId: session.extension_data?.matrix?.roomId,
|
||||
});
|
||||
}
|
||||
|
||||
// Enhanced styling for collaborative sessions
|
||||
const borderStyle = isCollaborative
|
||||
@@ -750,6 +762,16 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Show Matrix Room ID for Matrix sessions */}
|
||||
{isMatrix && session.extension_data?.matrix?.roomId && (
|
||||
<div className="flex items-center text-text-muted text-xs mb-1">
|
||||
<Hash className="w-3 h-3 mr-1 flex-shrink-0" />
|
||||
<span className="font-mono text-[10px] truncate opacity-70" title={session.extension_data.matrix.roomId}>
|
||||
{session.extension_data.matrix.roomId}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Show recent message participants for all sessions */}
|
||||
<div className="mb-1">
|
||||
<RecentMessageParticipants session={session} />
|
||||
|
||||
@@ -849,13 +849,44 @@ export const TabProvider: React.FC<TabProviderProps> = ({ children }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get or create the backend session for this Matrix room
|
||||
// Create a temporary tab with loading state first for immediate feedback
|
||||
const senderName = senderId.split(':')[0].substring(1);
|
||||
const tabTitle = `Chat with ${senderName}`;
|
||||
|
||||
const tempTab = createNewTab({
|
||||
sessionId: `temp_matrix_${Date.now()}`, // Temporary ID until we get the real one
|
||||
title: tabTitle,
|
||||
type: 'matrix',
|
||||
matrixRoomId: roomId,
|
||||
matrixRecipientId: senderId,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
const tempTabState: TabState = {
|
||||
tab: tempTab,
|
||||
chat: {
|
||||
sessionId: tempTab.sessionId,
|
||||
title: tabTitle,
|
||||
messages: [],
|
||||
messageHistoryIndex: 0,
|
||||
recipeConfig: null,
|
||||
aiEnabled: false,
|
||||
},
|
||||
loadingChat: true // Show loading state
|
||||
};
|
||||
|
||||
// Add the loading tab immediately
|
||||
setTabStates(prev => [...prev, tempTabState]);
|
||||
setActiveTabId(tempTab.id);
|
||||
|
||||
console.log('📱 Created temporary loading tab:', tempTab.id);
|
||||
|
||||
// Get or create the backend session for this Matrix room (async)
|
||||
let backendSessionId = sessionMappingService.getGooseSessionId(roomId);
|
||||
|
||||
if (!backendSessionId) {
|
||||
console.log('📱 No existing mapping found, creating new Matrix session mapping');
|
||||
try {
|
||||
const senderName = senderId.split(':')[0].substring(1);
|
||||
const roomTitle = `DM with ${senderName}`;
|
||||
|
||||
// Create a backend session for this Matrix room
|
||||
@@ -869,58 +900,31 @@ export const TabProvider: React.FC<TabProviderProps> = ({ children }) => {
|
||||
console.log('✅ Created new backend session for Matrix room:', backendSessionId);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to create backend session for Matrix room:', error);
|
||||
// Fallback to a temporary session ID - this won't have backend persistence
|
||||
backendSessionId = `temp_matrix_${Date.now()}`;
|
||||
// Keep the temporary session ID - this won't have backend persistence
|
||||
backendSessionId = tempTab.sessionId;
|
||||
}
|
||||
} else {
|
||||
console.log('📱 Found existing backend session for Matrix room:', backendSessionId);
|
||||
}
|
||||
|
||||
// Create a new Matrix tab using the actual backend session ID
|
||||
const senderName = senderId.split(':')[0].substring(1);
|
||||
const tabTitle = `Chat with ${senderName}`;
|
||||
|
||||
console.log('📱 Creating new Matrix tab with backend session:', {
|
||||
// Update the tab with the real backend session ID and remove loading state
|
||||
console.log('📱 Updating tab with backend session:', {
|
||||
tabId: tempTab.id,
|
||||
backendSessionId,
|
||||
tabTitle,
|
||||
roomId,
|
||||
senderId,
|
||||
type: 'matrix'
|
||||
});
|
||||
|
||||
const newTab = createNewTab({
|
||||
sessionId: backendSessionId, // Use actual backend session ID
|
||||
title: tabTitle,
|
||||
type: 'matrix',
|
||||
matrixRoomId: roomId,
|
||||
matrixRecipientId: senderId,
|
||||
isActive: true
|
||||
senderId
|
||||
});
|
||||
|
||||
const newTabState: TabState = {
|
||||
tab: newTab,
|
||||
chat: {
|
||||
sessionId: backendSessionId, // Use actual backend session ID
|
||||
title: tabTitle,
|
||||
messages: [],
|
||||
messageHistoryIndex: 0,
|
||||
recipeConfig: null,
|
||||
aiEnabled: false, // Matrix chats have AI disabled by default
|
||||
},
|
||||
loadingChat: false
|
||||
};
|
||||
|
||||
console.log('📱 Creating new Matrix tab state:', {
|
||||
tabId: newTab.id,
|
||||
backendSessionId,
|
||||
title: tabTitle,
|
||||
roomId,
|
||||
senderId,
|
||||
type: 'matrix'
|
||||
});
|
||||
|
||||
setTabStates(prev => [...prev, newTabState]);
|
||||
setActiveTabId(newTab.id);
|
||||
setTabStates(prev => prev.map(ts =>
|
||||
ts.tab.id === tempTab.id
|
||||
? {
|
||||
...ts,
|
||||
tab: { ...ts.tab, sessionId: backendSessionId },
|
||||
chat: { ...ts.chat, sessionId: backendSessionId },
|
||||
loadingChat: false // Remove loading state
|
||||
}
|
||||
: ts
|
||||
));
|
||||
}, [tabStates]);
|
||||
|
||||
// Create a backend session for a tab (converts new_ session to real backend session)
|
||||
|
||||
@@ -210,20 +210,18 @@ export const useActiveSession = () => {
|
||||
// For now, we rely on the Matrix room check above
|
||||
}
|
||||
|
||||
// Legacy: If we're in pair view with a specific recipient and the message is from that recipient, suppress it
|
||||
if (currentView.path.startsWith('/pair') &&
|
||||
currentView.matrixRecipientId &&
|
||||
messageSenderId === currentView.matrixRecipientId) {
|
||||
console.log('🔕 Suppressing notification: message from current pair recipient (legacy)', {
|
||||
messageSenderId,
|
||||
currentRecipientId: currentView.matrixRecipientId,
|
||||
path: currentView.path
|
||||
});
|
||||
return true;
|
||||
}
|
||||
// REMOVED BUGGY LEGACY LOGIC: The old /pair view suppression was checking sender ID
|
||||
// instead of room ID, causing messages from other rooms to be suppressed incorrectly.
|
||||
// The Matrix room check above (line ~169) is the correct way to suppress notifications.
|
||||
// Legacy /pair view is deprecated in favor of tabbed architecture.
|
||||
|
||||
// Don't suppress - show the notification
|
||||
console.log('✅ Not suppressing notification: no active session match');
|
||||
console.log('✅ Not suppressing notification: no active session match', {
|
||||
messageRoomId,
|
||||
currentMatrixRoomId: currentView.matrixRoomId,
|
||||
roomsMatch: messageRoomId === currentView.matrixRoomId,
|
||||
isMatrixMode: currentView.isMatrixMode
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -42,12 +42,22 @@ export class MatrixSessionService {
|
||||
*/
|
||||
public async getMatrixSessions(): Promise<Session[]> {
|
||||
try {
|
||||
// Only return Matrix sessions if Matrix service is connected
|
||||
// Check if Matrix service is connected OR syncing (both states mean we have rooms)
|
||||
const connectionStatus = matrixService.getConnectionStatus();
|
||||
if (!connectionStatus.connected) {
|
||||
console.log('📋 Matrix service not connected, skipping Matrix sessions');
|
||||
console.log('🔍 MatrixSessionService.getMatrixSessions() called - connection status:', {
|
||||
connected: connectionStatus.connected,
|
||||
syncState: connectionStatus.syncState,
|
||||
});
|
||||
|
||||
const isUsable = connectionStatus.connected || connectionStatus.syncState === 'SYNCING' || connectionStatus.syncState === 'PREPARED';
|
||||
|
||||
if (!isUsable) {
|
||||
console.log('❌ Matrix service not ready (state:', connectionStatus.syncState, '), skipping Matrix sessions');
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log('✅ Matrix service ready (connected:', connectionStatus.connected, ', syncState:', connectionStatus.syncState, ')');
|
||||
|
||||
|
||||
// Check if we have valid cached sessions
|
||||
const now = Date.now();
|
||||
@@ -98,8 +108,11 @@ export class MatrixSessionService {
|
||||
}
|
||||
|
||||
try {
|
||||
// Get room history to calculate message count and create conversation
|
||||
const history = await matrixService.getRoomHistoryAsGooseMessages(room.roomId, 100);
|
||||
// OPTIMIZATION: Don't load full history for list view - it's too slow!
|
||||
// Just get a rough message count from the room object
|
||||
// Full history will be loaded when user clicks into the session
|
||||
const history: any[] = []; // Empty for list view
|
||||
const messageCount = room.lastActivity ? 1 : 0; // Rough estimate
|
||||
|
||||
// Sync room history to backend session if we have a backend session ID
|
||||
if (mapping.gooseSessionId && history.length > 0) {
|
||||
@@ -218,6 +231,8 @@ export class MatrixSessionService {
|
||||
sessionId: session.id,
|
||||
messageCount: session.message_count,
|
||||
participants: room.members.length,
|
||||
hasExtensionData: !!session.extension_data?.matrix,
|
||||
extensionDataKeys: session.extension_data?.matrix ? Object.keys(session.extension_data.matrix) : [],
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('📋 Failed to convert Matrix room to session:', room.roomId, error);
|
||||
|
||||
@@ -42,6 +42,18 @@ export class UnifiedSessionService {
|
||||
|
||||
const regularSessions = regularSessionsResponse.data?.sessions || [];
|
||||
|
||||
// Debug: Check if Matrix sessions have extension_data
|
||||
const matrixSessionsWithExtData = matrixSessions.filter(s => s.extension_data?.matrix);
|
||||
console.log('🔍 Matrix sessions extension_data check:', {
|
||||
totalMatrixSessions: matrixSessions.length,
|
||||
withExtensionData: matrixSessionsWithExtData.length,
|
||||
sampleSession: matrixSessions[0] ? {
|
||||
id: matrixSessions[0].id,
|
||||
hasExtData: !!matrixSessions[0].extension_data?.matrix,
|
||||
roomId: matrixSessions[0].extension_data?.matrix?.roomId,
|
||||
} : null,
|
||||
});
|
||||
|
||||
// Combine sessions and sort by updated_at (most recent first)
|
||||
const allSessions = [...regularSessions, ...matrixSessions].sort((a, b) => {
|
||||
const dateA = new Date(a.updated_at).getTime();
|
||||
|
||||
Reference in New Issue
Block a user