Skip to content

Commit 24faaa4

Browse files
committed
♻️ Repeated calls in chat page (Agent list & conversation list) #2082
[Specification Details] 1.Remove redundant `/conversation/list` API calls and move the `/agent/list` API call to the parent component, passing it to the child component as a parameter.
1 parent b87f15c commit 24faaa4

File tree

9 files changed

+105
-192
lines changed

9 files changed

+105
-192
lines changed

frontend/app/[locale]/chat/components/chatAgentSelector.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function ChatAgentSelector({
1515
onAgentSelect,
1616
disabled = false,
1717
isInitialMode = false,
18-
initialAgents,
18+
agents: propAgents,
1919
}: ChatAgentSelectorProps) {
2020
const [agents, setAgents] = useState<Agent[]>([]);
2121
const [isOpen, setIsOpen] = useState(false);
@@ -95,15 +95,9 @@ export function ChatAgentSelector({
9595
};
9696

9797
useEffect(() => {
98-
// If parent provided initialAgents, reuse them to avoid duplicate network calls.
99-
if (initialAgents && initialAgents.length > 0) {
100-
setAgents(initialAgents);
101-
setIsLoading(false);
102-
return;
103-
}
104-
105-
loadAgents();
106-
}, []);
98+
// Only load agents if not provided via props or if props is explicitly empty
99+
setAgents(propAgents || []);
100+
}, [propAgents]);
107101

108102
// Execute auto-selection logic when agents are loaded
109103
useEffect(() => {

frontend/app/[locale]/chat/components/chatInput.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import { chatConfig } from "@/const/chatConfig";
3131
import { FilePreview } from "@/types/chat";
3232

3333
import { ChatAgentSelector } from "./chatAgentSelector";
34-
import type { Agent } from "@/types/chat";
3534

3635
// Image viewer component
3736
function ImageViewer({
@@ -311,8 +310,7 @@ interface ChatInputProps {
311310
onAttachmentsChange?: (attachments: FilePreview[]) => void;
312311
selectedAgentId?: number | null;
313312
onAgentSelect?: (agentId: number | null) => void;
314-
// Optional agents list passed from parent to avoid redundant fetching
315-
initialAgents?: Agent[];
313+
cachedAgents?: Agent[]; // Optional cached agents to avoid repeated API calls
316314
}
317315

318316
export function ChatInput({
@@ -325,13 +323,13 @@ export function ChatInput({
325323
onStop,
326324
onKeyDown,
327325
onRecordingStatusChange,
326+
cachedAgents,
328327
onFileUpload,
329328
onImageUpload,
330329
attachments = [],
331330
onAttachmentsChange,
332331
selectedAgentId = null,
333332
onAgentSelect,
334-
initialAgents,
335333
}: ChatInputProps) {
336334
const [isRecording, setIsRecording] = useState(false);
337335
const [recordingStatus, setRecordingStatus] = useState<
@@ -1030,7 +1028,7 @@ export function ChatInput({
10301028
onAgentSelect={onAgentSelect || (() => {})}
10311029
disabled={isLoading || isStreaming}
10321030
isInitialMode={isInitialMode}
1033-
initialAgents={initialAgents}
1031+
agents={cachedAgents}
10341032
/>
10351033
</div>
10361034

frontend/app/[locale]/chat/internal/chatInterface.tsx

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { conversationService } from "@/services/conversationService";
1616
import { storageService, convertImageUrlToApiUrl } from "@/services/storageService";
1717
import { useConversationManagement } from "@/hooks/chat/useConversationManagement";
1818
import { fetchAllAgents } from "@/services/agentConfigService";
19-
import type { Agent } from "@/types/chat";
2019

2120
import { ChatSidebar } from "../components/chatLeftSidebar";
2221
import { FilePreview } from "@/types/chat";
@@ -139,7 +138,11 @@ export function ChatInterface() {
139138

140139
// Add agent selection state
141140
const [selectedAgentId, setSelectedAgentId] = useState<number | null>(null);
142-
const [agents, setAgents] = useState<Agent[]>([]);
141+
142+
// Add global cache states to avoid repeated API calls
143+
const [cachedAgents, setCachedAgents] = useState<any[]>([]);
144+
const [agentsLoaded, setAgentsLoaded] = useState(false);
145+
const [deploymentVersionLoaded, setDeploymentVersionLoaded] = useState(false);
143146

144147
// Reset scroll to bottom state
145148
useEffect(() => {
@@ -181,6 +184,24 @@ export function ChatInterface() {
181184
setSidebarOpen(!sidebarOpen);
182185
};
183186

187+
// Load agents only once and cache the result
188+
const loadAgentsOnce = async () => {
189+
if (agentsLoaded) return; // Already loaded, skip
190+
191+
try {
192+
const result = await fetchAllAgents();
193+
if (result.success) {
194+
setCachedAgents(result.data);
195+
setAgentsLoaded(true);
196+
log.log("Agent list loaded and cached successfully");
197+
} else {
198+
log.error("Failed to load agent list:", result.message);
199+
}
200+
} catch (error) {
201+
log.error("Failed to load agent list:", error);
202+
}
203+
};
204+
184205
// Handle right panel toggle - keep it simple and clear
185206
const toggleRightPanel = () => {
186207
setShowRightPanel(!showRightPanel);
@@ -190,6 +211,11 @@ export function ChatInterface() {
190211
if (!conversationManagement.initialized.current) {
191212
conversationManagement.initialized.current = true;
192213

214+
// Load agent list only once when component initializes
215+
if (!agentsLoaded) {
216+
loadAgentsOnce();
217+
}
218+
193219
// Get conversation history list, but don't auto-select the latest conversation
194220
conversationManagement.fetchConversationList()
195221
.then((dialogData) => {
@@ -204,24 +230,6 @@ export function ChatInterface() {
204230
}
205231
}, [appConfig]); // Add appConfig as dependency
206232

207-
// Load agent list once and reuse across child components to avoid duplicate /agent/list calls.
208-
useEffect(() => {
209-
let mounted = true;
210-
(async () => {
211-
try {
212-
const res = await fetchAllAgents();
213-
if (mounted && res?.success) {
214-
setAgents(res.data || []);
215-
}
216-
} catch (e) {
217-
log.error("Failed to fetch agents for chat page:", e);
218-
}
219-
})();
220-
return () => {
221-
mounted = false;
222-
};
223-
}, []);
224-
225233
// Add useEffect to listen for conversationId changes, ensure right sidebar is always closed when conversation switches
226234
useEffect(() => {
227235
// Ensure right sidebar is reset to closed state whenever conversation ID changes
@@ -914,13 +922,8 @@ export function ChatInterface() {
914922
setShouldScrollToBottom(false);
915923
}, 1000);
916924

917-
// Refresh history list
918-
conversationManagement.fetchConversationList().catch((err) => {
919-
log.error(
920-
t("chatInterface.refreshDialogListFailedButContinue"),
921-
err
922-
);
923-
});
925+
// Note: Removed unnecessary conversation list refresh when loading historical messages
926+
// Only refresh when creating, deleting, or renaming conversations
924927
} else {
925928
// No longer empty cache, only prompt no history messages
926929
conversationManagement.setConversationLoadErrorForId(
@@ -1046,13 +1049,8 @@ export function ChatInterface() {
10461049
setShouldScrollToBottom(false);
10471050
}, 1000);
10481051

1049-
// Refresh history list
1050-
conversationManagement.fetchConversationList().catch((err) => {
1051-
log.error(
1052-
t("chatInterface.refreshDialogListFailedButContinue"),
1053-
err
1054-
);
1055-
});
1052+
// Note: Removed unnecessary conversation list refresh when loading historical messages
1053+
// Only refresh when creating, deleting, or renaming conversations
10561054
} else {
10571055
// No longer empty cache, only prompt no history messages
10581056
conversationManagement.setConversationLoadErrorForId(
@@ -1480,9 +1478,9 @@ export function ChatInterface() {
14801478
shouldScrollToBottom={shouldScrollToBottom}
14811479
selectedAgentId={selectedAgentId}
14821480
onAgentSelect={setSelectedAgentId}
1483-
initialAgents={agents}
14841481
onCitationHover={clearCompletedIndicator}
14851482
onScroll={clearCompletedIndicator}
1483+
cachedAgents={cachedAgents}
14861484
/>
14871485
</div>
14881486

frontend/app/[locale]/chat/streaming/chatStreamMain.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function ChatStreamMain({
4040
onAgentSelect,
4141
onCitationHover,
4242
onScroll,
43-
initialAgents,
43+
cachedAgents,
4444
}: ChatStreamMainProps) {
4545
const { t } = useTranslation();
4646
// Animation variants for ChatInput
@@ -376,7 +376,7 @@ export function ChatStreamMain({
376376
onImageUpload={onImageUpload}
377377
selectedAgentId={selectedAgentId}
378378
onAgentSelect={onAgentSelect}
379-
initialAgents={initialAgents}
379+
cachedAgents={cachedAgents}
380380
/>
381381
</motion.div>
382382
</AnimatePresence>
@@ -474,7 +474,7 @@ export function ChatStreamMain({
474474
onImageUpload={onImageUpload}
475475
selectedAgentId={selectedAgentId}
476476
onAgentSelect={onAgentSelect}
477-
initialAgents={initialAgents}
477+
cachedAgents={cachedAgents}
478478
/>
479479
</motion.div>
480480
</AnimatePresence>

frontend/hooks/useAuth.ts

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,32 +48,19 @@ export function AuthProvider({ children }: { children: (value: AuthContextType)
4848
}, [isLoginModalOpen, isRegisterModalOpen])
4949

5050
// Check deployment version and handle speed mode
51-
// This function deduplicates network requests across the app instance using a module/global promise.
5251
const checkDeploymentVersion = async () => {
5352
try {
5453
setIsReady(false);
55-
if (!(globalThis as any).__deploymentVersionPromise) {
56-
(globalThis as any).__deploymentVersionPromise = (async () => {
57-
try {
58-
const resp = await fetch(API_ENDPOINTS.tenantConfig.deploymentVersion);
59-
if (!resp.ok) return null;
60-
const data = await resp.json();
61-
return data.content?.deployment_version || data.deployment_version || null;
62-
} catch (e) {
63-
return null;
64-
}
65-
})();
66-
}
67-
68-
const version = await (globalThis as any).__deploymentVersionPromise;
69-
if (version) {
70-
setIsSpeedMode(version === "speed");
71-
} else {
72-
setIsSpeedMode(false);
54+
const response = await fetch(API_ENDPOINTS.tenantConfig.deploymentVersion);
55+
if (response.ok) {
56+
const data = await response.json();
57+
const version = data.content?.deployment_version || data.deployment_version;
58+
59+
setIsSpeedMode(version === 'speed');
60+
// In speed mode, do not perform any auto login; UI should not depend on login
7361
}
74-
// In speed mode, do not perform any auto login; UI should not depend on login
7562
} catch (error) {
76-
log.error("Failed to check deployment version:", error);
63+
log.error('Failed to check deployment version:', error);
7764
setIsSpeedMode(false);
7865
} finally {
7966
setIsReady(true);

frontend/services/agentConfigService.ts

Lines changed: 30 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -646,52 +646,38 @@ export const searchAgentInfo = async (agentId: number) => {
646646
* @returns list of available agents with agent_id, name, description, is_available
647647
*/
648648
export const fetchAllAgents = async () => {
649-
// Deduplicate concurrent fetches: reuse the in-flight promise if present.
650-
// Use module-level promise variable.
651-
if ((fetchAllAgents as any).__promise) {
652-
return (fetchAllAgents as any).__promise;
653-
}
654-
655-
const p = (async () => {
656-
try {
657-
const response = await fetch(API_ENDPOINTS.agent.list, {
658-
headers: getAuthHeaders(),
659-
});
660-
if (!response.ok) {
661-
throw new Error(`Request failed: ${response.status}`);
662-
}
663-
const data = await response.json();
664-
665-
// convert backend data to frontend format
666-
const formattedAgents = data.map((agent: any) => ({
667-
agent_id: agent.agent_id,
668-
name: agent.name,
669-
display_name: agent.display_name || agent.name,
670-
description: agent.description,
671-
author: agent.author,
672-
is_available: agent.is_available,
673-
}));
674-
675-
return {
676-
success: true,
677-
data: formattedAgents,
678-
message: "",
679-
};
680-
} catch (error) {
681-
log.error("Failed to get all Agent list:", error);
682-
return {
683-
success: false,
684-
data: [],
685-
message: "agentConfig.agents.listFetchFailed",
686-
};
687-
} finally {
688-
// clear the in-flight promise after completion so future calls can refetch
689-
delete (fetchAllAgents as any).__promise;
649+
try {
650+
const response = await fetch(API_ENDPOINTS.agent.list, {
651+
headers: getAuthHeaders(),
652+
});
653+
if (!response.ok) {
654+
throw new Error(`Request failed: ${response.status}`);
690655
}
691-
})();
656+
const data = await response.json();
692657

693-
(fetchAllAgents as any).__promise = p;
694-
return p;
658+
// convert backend data to frontend format
659+
const formattedAgents = data.map((agent: any) => ({
660+
agent_id: agent.agent_id,
661+
name: agent.name,
662+
display_name: agent.display_name || agent.name,
663+
description: agent.description,
664+
author: agent.author,
665+
is_available: agent.is_available,
666+
}));
667+
668+
return {
669+
success: true,
670+
data: formattedAgents,
671+
message: "",
672+
};
673+
} catch (error) {
674+
log.error("Failed to get all Agent list:", error);
675+
return {
676+
success: false,
677+
data: [],
678+
message: "agentConfig.agents.listFetchFailed",
679+
};
680+
}
695681
};
696682

697683
/**

0 commit comments

Comments
 (0)