fix(site): hoist model queries out of AgentDetail (#23324)

This commit is contained in:
Danielle Maywood
2026-03-19 21:41:00 +00:00
committed by GitHub
parent 4da273ba3c
commit cf0c4d0dcf
6 changed files with 198 additions and 128 deletions
@@ -51,6 +51,49 @@ const AgentDetailLayout: FC = () => {
requestUnarchiveAgent: () => {},
isSidebarCollapsed: false,
onToggleSidebarCollapsed: () => {},
modelOptions: [
{
id: "openai:gpt-4o",
provider: "openai",
model: "gpt-4o",
displayName: "GPT-4o",
},
],
modelConfigIDByModelID: new Map([["openai:gpt-4o", "config-1"]]),
modelIDByConfigID: new Map([["config-1", "openai:gpt-4o"]]),
modelConfigs: [
{
id: "config-1",
provider: "openai",
model: "gpt-4o",
display_name: "GPT-4o",
enabled: true,
is_default: false,
context_limit: 200000,
compression_threshold: 70,
created_at: "2026-01-01T00:00:00Z",
updated_at: "2026-01-01T00:00:00Z",
},
],
modelCatalog: {
providers: [
{
provider: "openai",
available: true,
models: [
{
id: "openai:gpt-4o",
provider: "openai",
model: "gpt-4o",
display_name: "GPT-4o",
},
],
},
],
},
isModelCatalogLoading: false,
modelCatalogError: null,
desktopEnabled: false,
} satisfies AgentsOutletContext
}
/>
+16 -58
View File
@@ -2,10 +2,7 @@ import { API, watchWorkspace } from "api/api";
import { isApiError } from "api/errors";
import {
chat,
chatDesktopEnabled,
chatMessagesForInfiniteScroll,
chatModelConfigs,
chatModels,
chats,
createChatMessage,
deleteChatQueuedMessage,
@@ -55,9 +52,7 @@ import {
import type { AgentsOutletContext } from "./AgentsPage";
import {
getModelCatalogStatusMessage,
getModelOptionsFromCatalog,
getModelSelectorPlaceholder,
getNormalizedModelRef,
hasConfiguredModelsInCatalog,
} from "./modelOptions";
import { parsePullRequestUrl } from "./pullRequest";
@@ -258,6 +253,14 @@ const AgentDetail: FC = () => {
onOpenAnalytics,
isSidebarCollapsed,
onToggleSidebarCollapsed,
modelOptions,
modelConfigIDByModelID,
modelIDByConfigID,
modelConfigs,
modelCatalog,
isModelCatalogLoading,
modelCatalogError,
desktopEnabled,
} = outletContext;
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
const chatInputRef = useRef<ChatMessageInputRef | null>(null);
@@ -318,9 +321,6 @@ const AgentDetail: FC = () => {
});
return () => socket.close();
}, [workspaceId, queryClient]);
const chatModelsQuery = useQuery(chatModels());
const chatModelConfigsQuery = useQuery(chatModelConfigs());
const desktopEnabledQuery = useQuery(chatDesktopEnabled());
const sshConfigQuery = useQuery(deploymentSSHConfig());
const workspace = workspaceQuery.data;
const workspaceAgent = getWorkspaceAgent(workspace, undefined);
@@ -385,42 +385,6 @@ const AgentDetail: FC = () => {
const isArchived = chatRecord?.archived ?? false;
const chatLastModelConfigID = chatRecord?.last_model_config_id;
const modelOptions = useMemo(
() =>
getModelOptionsFromCatalog(
chatModelsQuery.data,
chatModelConfigsQuery.data,
),
[chatModelsQuery.data, chatModelConfigsQuery.data],
);
const modelConfigIDByModelID = useMemo(() => {
const byModelID = new Map<string, string>();
for (const config of chatModelConfigsQuery.data ?? []) {
const { provider, model } = getNormalizedModelRef(config);
if (!provider || !model) {
continue;
}
const colonRef = `${provider}:${model}`;
if (!byModelID.has(colonRef)) {
byModelID.set(colonRef, config.id);
}
const slashRef = `${provider}/${model}`;
if (!byModelID.has(slashRef)) {
byModelID.set(slashRef, config.id);
}
}
return byModelID;
}, [chatModelConfigsQuery.data]);
const modelIDByConfigID = useMemo(() => {
const byConfigID = new Map<string, string>();
for (const [modelID, configID] of modelConfigIDByModelID.entries()) {
if (!byConfigID.has(configID)) {
byConfigID.set(configID, modelID);
}
}
return byConfigID;
}, [modelConfigIDByModelID]);
const sendMutation = useMutation(
createChatMessage(queryClient, agentId ?? ""),
);
@@ -501,25 +465,21 @@ const AgentDetail: FC = () => {
if (!chatLastModelConfigID) {
return undefined;
}
const config = chatModelConfigsQuery.data?.find(
(c) => c.id === chatLastModelConfigID,
);
const config = modelConfigs.find((c) => c.id === chatLastModelConfigID);
return config?.compression_threshold;
}, [chatLastModelConfigID, chatModelConfigsQuery.data]);
}, [chatLastModelConfigID, modelConfigs]);
const hasModelOptions = modelOptions.length > 0;
const hasConfiguredModels = hasConfiguredModelsInCatalog(
chatModelsQuery.data,
);
const hasConfiguredModels = hasConfiguredModelsInCatalog(modelCatalog);
const modelSelectorPlaceholder = getModelSelectorPlaceholder(
modelOptions,
chatModelsQuery.isLoading,
isModelCatalogLoading,
hasConfiguredModels,
);
const modelCatalogStatusMessage = getModelCatalogStatusMessage(
chatModelsQuery.data,
modelCatalog,
modelOptions,
chatModelsQuery.isLoading,
Boolean(chatModelsQuery.error),
isModelCatalogLoading,
Boolean(modelCatalogError),
);
const inputStatusText = hasModelOptions
? null
@@ -913,9 +873,7 @@ const AgentDetail: FC = () => {
hasMoreMessages={chatMessagesQuery.hasNextPage ?? false}
isFetchingMoreMessages={chatMessagesQuery.isFetchingNextPage}
onFetchMoreMessages={chatMessagesQuery.fetchNextPage}
desktopChatId={
desktopEnabledQuery.data?.enable_desktop ? agentId : undefined
}
desktopChatId={desktopEnabled ? agentId : undefined}
/>
);
};
@@ -130,6 +130,14 @@ const AgentEmbedPage: FC = () => {
requestArchiveAndDeleteWorkspace,
isSidebarCollapsed,
onToggleSidebarCollapsed,
modelOptions: [],
modelConfigIDByModelID: new Map(),
modelIDByConfigID: new Map(),
modelConfigs: [],
modelCatalog: undefined,
isModelCatalogLoading: false,
modelCatalogError: null,
desktopEnabled: false,
}),
[
chatErrorReasons,
+11 -25
View File
@@ -2,6 +2,7 @@ import { API, watchChats } from "api/api";
import { getErrorMessage } from "api/errors";
import {
archiveChat,
chatDesktopEnabled,
chatDiffContentsKey,
chatKey,
chatModelConfigs,
@@ -41,7 +42,6 @@ import {
emptyInputStorageKey,
} from "./AgentCreateForm";
import { maybePlayChime } from "./AgentDetail/useAgentChime";
import type { AgentsOutletContext } from "./AgentsPageView";
import { AgentsPageView } from "./AgentsPageView";
import { resolveArchiveAndDeleteAction } from "./agentWorkspaceUtils";
import {
@@ -134,6 +134,7 @@ const AgentsPage: FC = () => {
);
const chatModelsQuery = useQuery(chatModels());
const chatModelConfigsQuery = useQuery(chatModelConfigs());
const desktopEnabledQuery = useQuery(chatDesktopEnabled());
const createMutation = useMutation(createChat(queryClient));
const archiveChatBase = archiveChat(queryClient);
const archiveAgentMutation = useMutation({
@@ -319,28 +320,6 @@ const AgentsPage: FC = () => {
() => setIsSidebarCollapsed((prev) => !prev),
[],
);
const outletContext: AgentsOutletContext = useMemo(
() => ({
chatErrorReasons,
setChatErrorReason,
clearChatErrorReason,
requestArchiveAgent,
requestUnarchiveAgent,
requestArchiveAndDeleteWorkspace,
isSidebarCollapsed,
onToggleSidebarCollapsed: handleToggleSidebarCollapsed,
}),
[
chatErrorReasons,
setChatErrorReason,
clearChatErrorReason,
requestArchiveAgent,
requestUnarchiveAgent,
requestArchiveAndDeleteWorkspace,
isSidebarCollapsed,
handleToggleSidebarCollapsed,
],
);
const handleCreateChat = async (options: CreateChatOptions) => {
const { message, fileIDs, workspaceId, model } = options;
const modelConfigID =
@@ -555,13 +534,21 @@ const AgentsPage: FC = () => {
onCollapseSidebar={() => setIsSidebarCollapsed(true)}
isSidebarCollapsed={isSidebarCollapsed}
onExpandSidebar={() => setIsSidebarCollapsed(false)}
outletContext={outletContext}
chatErrorReasons={chatErrorReasons}
setChatErrorReason={setChatErrorReason}
clearChatErrorReason={clearChatErrorReason}
requestArchiveAgent={requestArchiveAgent}
requestUnarchiveAgent={requestUnarchiveAgent}
requestArchiveAndDeleteWorkspace={requestArchiveAndDeleteWorkspace}
onToggleSidebarCollapsed={handleToggleSidebarCollapsed}
onCreateChat={handleCreateChat}
createError={createMutation.error}
modelCatalog={chatModelsQuery.data}
isModelCatalogLoading={chatModelsQuery.isLoading}
isModelConfigsLoading={chatModelConfigsQuery.isLoading}
modelCatalogError={chatModelsQuery.error}
modelConfigIDByModelID={modelConfigIDByModelID}
desktopEnabled={desktopEnabledQuery.data?.enable_desktop ?? false}
isAgentsAdmin={isAgentsAdmin}
hasNextPage={chatsQuery.hasNextPage}
onLoadMore={() => void chatsQuery.fetchNextPage()}
@@ -584,5 +571,4 @@ const AgentsPage: FC = () => {
</>
);
};
export default AgentsPage;
@@ -162,16 +162,13 @@ const meta: Meta<typeof AgentsPageView> = {
onCollapseSidebar: fn(),
isSidebarCollapsed: false,
onExpandSidebar: fn(),
outletContext: {
chatErrorReasons: {},
setChatErrorReason: fn(),
clearChatErrorReason: fn(),
requestArchiveAgent: fn(),
requestUnarchiveAgent: fn(),
requestArchiveAndDeleteWorkspace: fn(),
isSidebarCollapsed: false,
onToggleSidebarCollapsed: fn(),
},
chatErrorReasons: {},
setChatErrorReason: fn(),
clearChatErrorReason: fn(),
requestArchiveAgent: fn(),
requestUnarchiveAgent: fn(),
requestArchiveAndDeleteWorkspace: fn(),
onToggleSidebarCollapsed: fn(),
isAgentsAdmin: false,
analyticsNow: fixedAnalyticsNow,
archivedFilter: "active" as const,
@@ -181,10 +178,27 @@ const meta: Meta<typeof AgentsPageView> = {
isFetchingNextPage: false,
onCreateChat: fn(),
createError: undefined,
modelCatalog: undefined,
modelCatalog: {
providers: [
{
provider: "openai",
available: true,
models: [
{
id: "openai:gpt-4o",
provider: "openai",
model: "gpt-4o",
display_name: "GPT-4o",
},
],
},
],
},
isModelCatalogLoading: false,
isModelConfigsLoading: false,
modelCatalogError: undefined,
modelConfigIDByModelID: new Map(),
desktopEnabled: false,
},
beforeEach: () => {
spyOn(API, "getWorkspaces").mockResolvedValue({
@@ -278,16 +292,13 @@ export const SidebarCollapsed: Story = {
updated_at: todayTimestamp,
}),
],
outletContext: {
chatErrorReasons: {},
setChatErrorReason: fn(),
clearChatErrorReason: fn(),
requestArchiveAgent: fn(),
requestUnarchiveAgent: fn(),
requestArchiveAndDeleteWorkspace: fn(),
isSidebarCollapsed: true,
onToggleSidebarCollapsed: fn(),
},
chatErrorReasons: {},
setChatErrorReason: fn(),
clearChatErrorReason: fn(),
requestArchiveAgent: fn(),
requestUnarchiveAgent: fn(),
requestArchiveAndDeleteWorkspace: fn(),
onToggleSidebarCollapsed: fn(),
},
};
@@ -434,19 +445,16 @@ export const WithErrorReasons: Story = {
updated_at: todayTimestamp,
}),
],
outletContext: {
chatErrorReasons: {
"chat-1": { kind: "generic", message: "Model rate limited" },
"chat-3": { kind: "generic", message: "Context window exceeded" },
},
setChatErrorReason: fn(),
clearChatErrorReason: fn(),
requestArchiveAgent: fn(),
requestUnarchiveAgent: fn(),
requestArchiveAndDeleteWorkspace: fn(),
isSidebarCollapsed: false,
onToggleSidebarCollapsed: fn(),
chatErrorReasons: {
"chat-1": { kind: "generic", message: "Model rate limited" },
"chat-3": { kind: "generic", message: "Context window exceeded" },
},
setChatErrorReason: fn(),
clearChatErrorReason: fn(),
requestArchiveAgent: fn(),
requestUnarchiveAgent: fn(),
requestArchiveAndDeleteWorkspace: fn(),
onToggleSidebarCollapsed: fn(),
},
};
+79 -12
View File
@@ -29,9 +29,17 @@ export interface AgentsOutletContext {
chatId: string,
workspaceId: string,
) => void;
onOpenAnalytics?: () => void;
isSidebarCollapsed: boolean;
onToggleSidebarCollapsed: () => void;
onOpenAnalytics?: () => void;
modelOptions: readonly ModelSelectorOption[];
modelConfigIDByModelID: ReadonlyMap<string, string>;
modelIDByConfigID: ReadonlyMap<string, string>;
modelConfigs: readonly TypesGen.ChatModelConfig[];
modelCatalog: TypesGen.ChatModelsResponse | null | undefined;
isModelCatalogLoading: boolean;
modelCatalogError: unknown;
desktopEnabled: boolean;
}
interface AgentsPageViewProps {
@@ -50,7 +58,16 @@ interface AgentsPageViewProps {
onCollapseSidebar: () => void;
isSidebarCollapsed: boolean;
onExpandSidebar: () => void;
outletContext: AgentsOutletContext;
chatErrorReasons: Record<string, ChatDetailError>;
setChatErrorReason: (chatId: string, reason: ChatDetailError) => void;
clearChatErrorReason: (chatId: string) => void;
requestArchiveAgent: (chatId: string) => void;
requestUnarchiveAgent: (chatId: string) => void;
requestArchiveAndDeleteWorkspace: (
chatId: string,
workspaceId: string,
) => void;
onToggleSidebarCollapsed: () => void;
isAgentsAdmin: boolean;
onCreateChat: (options: CreateChatOptions) => Promise<void>;
createError: unknown;
@@ -58,6 +75,8 @@ interface AgentsPageViewProps {
isModelCatalogLoading: boolean;
isModelConfigsLoading: boolean;
modelCatalogError: unknown;
modelConfigIDByModelID: ReadonlyMap<string, string>;
desktopEnabled: boolean;
hasNextPage: boolean | undefined;
onLoadMore: () => void;
isFetchingNextPage: boolean;
@@ -82,7 +101,13 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
onCollapseSidebar,
isSidebarCollapsed,
onExpandSidebar,
outletContext,
chatErrorReasons,
setChatErrorReason,
clearChatErrorReason,
requestArchiveAgent,
requestUnarchiveAgent,
requestArchiveAndDeleteWorkspace,
onToggleSidebarCollapsed,
isAgentsAdmin,
onCreateChat,
createError,
@@ -90,6 +115,8 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
isModelCatalogLoading,
isModelConfigsLoading,
modelCatalogError,
modelConfigIDByModelID,
desktopEnabled,
hasNextPage,
onLoadMore,
isFetchingNextPage,
@@ -97,12 +124,6 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
onArchivedFilterChange,
analyticsNow,
}) => {
const {
chatErrorReasons,
requestArchiveAgent,
requestUnarchiveAgent,
requestArchiveAndDeleteWorkspace,
} = outletContext;
const location = useLocation();
const navigate = useNavigate();
const sidebarView = sidebarViewFromPath(location.pathname);
@@ -124,9 +145,55 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
[chatErrorReasons],
);
const outletContextValue = useMemo(
() => ({ ...outletContext, onOpenAnalytics: handleOpenAnalytics }),
[outletContext, handleOpenAnalytics],
const modelIDByConfigID = useMemo(() => {
const byConfigID = new Map<string, string>();
for (const [modelID, configID] of modelConfigIDByModelID.entries()) {
if (!byConfigID.has(configID)) {
byConfigID.set(configID, modelID);
}
}
return byConfigID;
}, [modelConfigIDByModelID]);
const outletContextValue: AgentsOutletContext = useMemo(
() => ({
chatErrorReasons,
setChatErrorReason,
clearChatErrorReason,
requestArchiveAgent,
requestUnarchiveAgent,
requestArchiveAndDeleteWorkspace,
isSidebarCollapsed,
onToggleSidebarCollapsed,
onOpenAnalytics: handleOpenAnalytics,
modelOptions: catalogModelOptions,
modelConfigIDByModelID,
modelIDByConfigID,
modelConfigs,
modelCatalog,
isModelCatalogLoading,
modelCatalogError,
desktopEnabled,
}),
[
chatErrorReasons,
setChatErrorReason,
clearChatErrorReason,
requestArchiveAgent,
requestUnarchiveAgent,
requestArchiveAndDeleteWorkspace,
isSidebarCollapsed,
onToggleSidebarCollapsed,
handleOpenAnalytics,
catalogModelOptions,
modelConfigIDByModelID,
modelIDByConfigID,
modelConfigs,
modelCatalog,
isModelCatalogLoading,
modelCatalogError,
desktopEnabled,
],
);
return (