Skip to content

Commit 1d1bae2

Browse files
committed
Adding context provider for Agent Chat front end to persist the chat while navigating away from the agent chat page
1 parent 28e29cf commit 1d1bae2

File tree

4 files changed

+221
-66
lines changed

4 files changed

+221
-66
lines changed

src/ui/src/App.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import '@aws-amplify/ui-react/styles.css';
88

99
import { AppContext } from './contexts/app';
1010
import { AnalyticsProvider } from './contexts/analytics';
11+
import { AgentChatProvider } from './contexts/agentChat';
1112
import useAwsConfig from './hooks/use-aws-config';
1213
import useCurrentSessionCreds from './hooks/use-current-session-creds';
1314

@@ -37,14 +38,16 @@ const AppContent = () => {
3738
setNavigationOpen,
3839
};
3940
logger.debug('appContextValue', appContextValue);
40-
41+
// TODO: Remove the AnalyticsProvider once we migrate full to Agent Chat
4142
return (
4243
<div className="App">
4344
<AppContext.Provider value={appContextValue}>
4445
<AnalyticsProvider>
45-
<HashRouter>
46-
<Routes />
47-
</HashRouter>
46+
<AgentChatProvider>
47+
<HashRouter>
48+
<Routes />
49+
</HashRouter>
50+
</AgentChatProvider>
4851
</AnalyticsProvider>
4952
</AppContext.Provider>
5053
</div>

src/ui/src/components/agent-chat/AgentChatLayout.jsx

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import remarkGfm from 'remark-gfm';
2020
import rehypeRaw from 'rehype-raw';
2121
import useAgentChat from '../../hooks/use-agent-chat';
2222
import useAppContext from '../../contexts/app';
23+
import { useAgentChatContext } from '../../contexts/agentChat';
2324
import PlotDisplay from '../document-agents-layout/PlotDisplay';
2425
import TableDisplay from '../document-agents-layout/TableDisplay';
2526
import AgentChatHistoryDropdown from './AgentChatHistoryDropdown';
@@ -33,12 +34,14 @@ const AgentChatLayout = ({
3334
showHeader = true,
3435
customStyles = {},
3536
}) => {
36-
const [inputValue, setInputValue] = useState('');
37-
const [expandedSections, setExpandedSections] = useState(new Set());
3837
const [welcomeAnimated, setWelcomeAnimated] = useState(false);
39-
const [lastMessageCount, setLastMessageCount] = useState(0);
40-
const [enableCodeIntelligence, setEnableCodeIntelligence] = useState(true);
38+
const [isLoadingSession, setIsLoadingSession] = useState(false);
4139
const chatMessagesRef = useRef(null);
40+
41+
// Get persistent state from context
42+
const { agentChatState, updateAgentChatState } = useAgentChatContext();
43+
const { inputValue, expandedSections, lastMessageCount, enableCodeIntelligence } = agentChatState;
44+
4245
const { messages, isLoading, waitingForResponse, error, sendMessage, clearError, clearChat, loadChatSession } = useAgentChat(agentConfig);
4346
const { user } = useAppContext();
4447

@@ -59,15 +62,15 @@ const AgentChatLayout = ({
5962
useEffect(() => {
6063
const handleSampleQueryInsert = (event) => {
6164
const { query } = event.detail;
62-
setInputValue(query);
65+
updateAgentChatState({ inputValue: query });
6366
};
6467

6568
window.addEventListener('insertSampleQuery', handleSampleQueryInsert);
6669

6770
return () => {
6871
window.removeEventListener('insertSampleQuery', handleSampleQueryInsert);
6972
};
70-
}, []);
73+
}, [updateAgentChatState]);
7174

7275
// Track new messages and scroll to new assistant messages (but not while streaming)
7376
useEffect(() => {
@@ -88,15 +91,15 @@ const AgentChatLayout = ({
8891
}, 100);
8992
}
9093

91-
setLastMessageCount(messages.length);
94+
updateAgentChatState({ lastMessageCount: messages.length });
9295
}
93-
}, [messages, lastMessageCount]);
96+
}, [messages, lastMessageCount, updateAgentChatState]);
9497

9598
const handlePromptSubmit = async () => {
9699
const prompt = inputValue;
97100
if (!prompt.trim()) return;
98101

99-
setInputValue('');
102+
updateAgentChatState({ inputValue: '' });
100103
try {
101104
await sendMessage(prompt, { enableCodeIntelligence });
102105
// Scroll to the latest user message after sending
@@ -115,7 +118,7 @@ const AgentChatLayout = ({
115118
};
116119

117120
const handleInputChange = (event) => {
118-
setInputValue(event.detail.value);
121+
updateAgentChatState({ inputValue: event.detail.value });
119122
};
120123

121124
const handleKeyDown = (event) => {
@@ -127,26 +130,22 @@ const AgentChatLayout = ({
127130

128131
// Handle expandable section state changes
129132
const handleExpandedChange = (messageId, expanded) => {
130-
setExpandedSections((prev) => {
131-
const newSet = new Set(prev);
132-
if (expanded) {
133-
newSet.add(messageId);
134-
} else {
135-
newSet.delete(messageId);
136-
}
137-
return newSet;
138-
});
133+
const newSet = new Set(expandedSections);
134+
if (expanded) {
135+
newSet.add(messageId);
136+
} else {
137+
newSet.delete(messageId);
138+
}
139+
updateAgentChatState({ expandedSections: newSet });
139140
};
140141

141142
// Handle session selection from dropdown
142143
const handleSessionSelect = async (session, sessionMessages) => {
143144
try {
145+
setIsLoadingSession(true);
144146
console.log('Loading chat session:', session.sessionId);
145-
await loadChatSession(session.sessionId, sessionMessages);
146147

147-
// Reset UI state for loaded session
148-
setExpandedSections(new Set());
149-
setLastMessageCount(sessionMessages.length);
148+
await loadChatSession(session.sessionId, sessionMessages);
150149

151150
// Scroll to bottom after loading
152151
setTimeout(() => {
@@ -156,6 +155,8 @@ const AgentChatLayout = ({
156155
}, 100);
157156
} catch (err) {
158157
console.error('Failed to load chat session:', err);
158+
} finally {
159+
setIsLoadingSession(false);
159160
}
160161
};
161162

@@ -281,7 +282,41 @@ const AgentChatLayout = ({
281282
</Alert>
282283
)}
283284

284-
<div ref={chatMessagesRef} className="chat-messages">
285+
<div
286+
ref={chatMessagesRef}
287+
className="chat-messages"
288+
style={{
289+
position: 'relative',
290+
opacity: isLoadingSession ? 0.5 : 1,
291+
pointerEvents: isLoadingSession ? 'none' : 'auto',
292+
transition: 'opacity 0.3s ease',
293+
}}
294+
>
295+
{isLoadingSession && (
296+
<div
297+
style={{
298+
position: 'absolute',
299+
top: '50%',
300+
left: '50%',
301+
transform: 'translate(-50%, -50%)',
302+
zIndex: 1000,
303+
display: 'flex',
304+
flexDirection: 'column',
305+
alignItems: 'center',
306+
gap: '12px',
307+
backgroundColor: 'rgba(255, 255, 255, 0.9)',
308+
padding: '20px',
309+
borderRadius: '8px',
310+
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
311+
}}
312+
>
313+
<Spinner size="large" />
314+
<Box fontSize="body-m" color="text-body-secondary">
315+
Loading chat history...
316+
</Box>
317+
</div>
318+
)}
319+
285320
{messages.length === 0 ? (
286321
<div className={`welcome-text ${welcomeAnimated ? 'animate-in' : ''}`}>
287322
<h2>
@@ -311,7 +346,7 @@ const AgentChatLayout = ({
311346
onItemClick={async ({ detail }) => {
312347
const selectedPrompt = supportPrompts.find((prompt) => prompt.id === detail.id);
313348
if (selectedPrompt) {
314-
setInputValue(selectedPrompt.prompt);
349+
updateAgentChatState({ inputValue: selectedPrompt.prompt });
315350
}
316351
}}
317352
/>{' '}
@@ -325,7 +360,7 @@ const AgentChatLayout = ({
325360
onChange={handleInputChange}
326361
onKeyDown={handleKeyDown}
327362
placeholder={placeholder}
328-
disabled={isLoading}
363+
disabled={isLoading || isLoadingSession}
329364
actionButtonIconName="send"
330365
onAction={handlePromptSubmit}
331366
minRows={3}
@@ -336,7 +371,7 @@ const AgentChatLayout = ({
336371
</Box>
337372
<Checkbox
338373
checked={enableCodeIntelligence}
339-
onChange={({ detail }) => setEnableCodeIntelligence(detail.checked)}
374+
onChange={({ detail }) => updateAgentChatState({ enableCodeIntelligence: detail.checked })}
340375
disabled={waitingForResponse}
341376
>
342377
<Box fontSize="body-s">Enable Code Intelligence Agent</Box>
@@ -349,7 +384,7 @@ const AgentChatLayout = ({
349384
<AgentChatHistoryDropdown
350385
onSessionSelect={handleSessionSelect}
351386
onSessionDeleted={handleSessionDeleted}
352-
disabled={waitingForResponse}
387+
disabled={waitingForResponse || isLoadingSession}
353388
/>
354389
</Box>
355390
{messages.length > 0 && (
@@ -358,14 +393,12 @@ const AgentChatLayout = ({
358393
iconName="refresh"
359394
onClick={() => {
360395
clearChat();
361-
setExpandedSections(new Set());
362396
setWelcomeAnimated(false);
363-
setLastMessageCount(0);
364397
setTimeout(() => {
365398
setWelcomeAnimated(true);
366399
}, 100);
367400
}}
368-
disabled={waitingForResponse}
401+
disabled={waitingForResponse || isLoadingSession}
369402
>
370403
Clear chat
371404
</Button>

src/ui/src/contexts/agentChat.jsx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React, { createContext, useContext, useState, useCallback, useMemo } from 'react';
4+
import PropTypes from 'prop-types';
5+
import { v4 as uuidv4 } from 'uuid';
6+
7+
const AgentChatContext = createContext(null);
8+
9+
export const AgentChatProvider = ({ children }) => {
10+
// State for the agent chat
11+
const [agentChatState, setAgentChatState] = useState({
12+
messages: [], // Current chat messages
13+
sessionId: uuidv4(), // Current session ID
14+
isLoading: false,
15+
waitingForResponse: false,
16+
error: null,
17+
expandedSections: new Set(),
18+
lastMessageCount: 0,
19+
enableCodeIntelligence: true,
20+
inputValue: '',
21+
});
22+
23+
// Function to update agent chat state
24+
const updateAgentChatState = useCallback((updates) => {
25+
setAgentChatState((prevState) => ({
26+
...prevState,
27+
...updates,
28+
}));
29+
}, []);
30+
31+
// Function to reset agent chat state (new session)
32+
const resetAgentChatState = useCallback(() => {
33+
setAgentChatState({
34+
messages: [],
35+
sessionId: uuidv4(),
36+
isLoading: false,
37+
waitingForResponse: false,
38+
error: null,
39+
expandedSections: new Set(),
40+
lastMessageCount: 0,
41+
enableCodeIntelligence: true,
42+
inputValue: '',
43+
});
44+
}, []);
45+
46+
// Function to load a specific session
47+
const loadAgentChatSession = useCallback((sessionId, messages) => {
48+
setAgentChatState((prevState) => ({
49+
...prevState,
50+
messages,
51+
sessionId,
52+
expandedSections: new Set(),
53+
lastMessageCount: messages.length,
54+
error: null,
55+
waitingForResponse: false,
56+
isLoading: false,
57+
}));
58+
}, []);
59+
60+
// Function to add a message to the current session
61+
const addMessageToSession = useCallback((message) => {
62+
setAgentChatState((prevState) => ({
63+
...prevState,
64+
messages: [...prevState.messages, message],
65+
}));
66+
}, []);
67+
68+
// Function to update messages (for streaming updates)
69+
const updateMessages = useCallback((updaterFunction) => {
70+
setAgentChatState((prevState) => ({
71+
...prevState,
72+
messages: updaterFunction(prevState.messages),
73+
}));
74+
}, []);
75+
76+
const contextValue = useMemo(
77+
() => ({
78+
agentChatState,
79+
updateAgentChatState,
80+
resetAgentChatState,
81+
loadAgentChatSession,
82+
addMessageToSession,
83+
updateMessages,
84+
}),
85+
[agentChatState, updateAgentChatState, resetAgentChatState, loadAgentChatSession, addMessageToSession, updateMessages],
86+
);
87+
88+
return <AgentChatContext.Provider value={contextValue}>{children}</AgentChatContext.Provider>;
89+
};
90+
91+
AgentChatProvider.propTypes = {
92+
children: PropTypes.node.isRequired,
93+
};
94+
95+
export const useAgentChatContext = () => {
96+
const context = useContext(AgentChatContext);
97+
if (!context) {
98+
throw new Error('useAgentChatContext must be used within an AgentChatProvider');
99+
}
100+
return context;
101+
};
102+
103+
export default AgentChatContext;

0 commit comments

Comments
 (0)