mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-08 02:37:45 +02:00
chat: dismissable MCP suggestion banner + auto-clear on activate + jump to /actions on unknown_server
Adds an × button to the integration-suggestion banner so it doesn't linger after the user dismisses it. On successful MCP activate, clears the banner so the user gets visual confirmation the click did something. On unknown_server response (not yet connected), navigate to /actions so the user can finish OAuth there.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useRef, useMemo, useState, useCallback } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import Box from '@mui/material/Box';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Chip from '@mui/material/Chip';
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
fetchSession,
|
||||
AgentMessage,
|
||||
clearSessionMessages,
|
||||
clearMcpSuggestions,
|
||||
} from '@/shared/state/agentsSlice';
|
||||
import { fetchModes } from '@/shared/state/modesSlice';
|
||||
import { createSessionWs } from '@/shared/ws/WebSocketManager';
|
||||
@@ -167,6 +168,7 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
};
|
||||
const { id: routeId } = useParams<{ id: string }>();
|
||||
const id = sessionIdProp || routeId;
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const session = useAppSelector((state) => (id ? state.agents.sessions[id] : undefined));
|
||||
const modesMap = useAppSelector((state) => state.modes.items);
|
||||
@@ -1015,8 +1017,32 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
borderRadius: 1.5,
|
||||
border: `1px solid ${c.border.medium}`,
|
||||
bgcolor: c.bg.secondary,
|
||||
position: 'relative',
|
||||
}}>
|
||||
<Typography variant="body2" sx={{ color: c.text.primary, fontWeight: 500, mb: 0.5 }}>
|
||||
<Box
|
||||
role="button"
|
||||
aria-label="Dismiss integration suggestion"
|
||||
onClick={() => id && dispatch(clearMcpSuggestions({ sessionId: id }))}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 6,
|
||||
right: 8,
|
||||
width: 20,
|
||||
height: 20,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '1rem',
|
||||
lineHeight: 1,
|
||||
color: c.text.muted,
|
||||
cursor: 'pointer',
|
||||
borderRadius: 0.75,
|
||||
'&:hover': { color: c.text.primary, bgcolor: c.bg.elevated },
|
||||
}}
|
||||
>
|
||||
×
|
||||
</Box>
|
||||
<Typography variant="body2" sx={{ color: c.text.primary, fontWeight: 500, mb: 0.5, pr: 3 }}>
|
||||
Looks like this might need an integration
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: c.text.secondary, display: 'block', mb: 1 }}>
|
||||
@@ -1056,8 +1082,18 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
parent_session_id: session.id,
|
||||
}),
|
||||
});
|
||||
const body = await r.json().catch(() => ({} as any));
|
||||
if (!r.ok) {
|
||||
setActivateError(`Activation failed (${r.status})`);
|
||||
} else if (body?.status === 'unknown_server') {
|
||||
// Not yet connected; jump straight to Actions
|
||||
// so the user can finish OAuth. Nothing here
|
||||
// can do it on their behalf.
|
||||
navigate('/actions');
|
||||
} else if (id) {
|
||||
// Activation succeeded; clear the banner so the user
|
||||
// gets visual confirmation the click did something.
|
||||
dispatch(clearMcpSuggestions({ sessionId: id }));
|
||||
}
|
||||
} catch (e: any) {
|
||||
setActivateError(e?.message || 'Activation failed');
|
||||
|
||||
@@ -1068,6 +1068,8 @@ const agentsSlice = createSlice({
|
||||
? existing.pending_approvals
|
||||
: s.pending_approvals ?? [],
|
||||
tool_group_meta: { ...existing?.tool_group_meta, ...s.tool_group_meta },
|
||||
mcp_suggestions: existing?.mcp_suggestions ?? [],
|
||||
mcp_suggestions_is_vague: existing?.mcp_suggestions_is_vague ?? false,
|
||||
};
|
||||
if (activeStatuses.has(s.status) && !state.trackedNotificationIds.includes(s.id)) {
|
||||
state.trackedNotificationIds.push(s.id);
|
||||
@@ -1257,6 +1259,12 @@ const agentsSlice = createSlice({
|
||||
...session,
|
||||
pending_approvals: session.pending_approvals ?? existing?.pending_approvals ?? [],
|
||||
tool_group_meta: session.tool_group_meta ?? existing?.tool_group_meta ?? {},
|
||||
// mcp_suggestions live in client state only (the backend never
|
||||
// returns them in the session payload). Preserve them across
|
||||
// refresh so the suggestion banner stays put until the user
|
||||
// dismisses it or activates one.
|
||||
mcp_suggestions: existing?.mcp_suggestions ?? [],
|
||||
mcp_suggestions_is_vague: existing?.mcp_suggestions_is_vague ?? false,
|
||||
};
|
||||
})
|
||||
.addCase(fetchSession.rejected, (state, action) => {
|
||||
|
||||
Reference in New Issue
Block a user