diff --git a/site/src/pages/AgentsPage/AgentDetail.stories.tsx b/site/src/pages/AgentsPage/AgentDetail.stories.tsx index 4bf310cc7c..79aac229c1 100644 --- a/site/src/pages/AgentsPage/AgentDetail.stories.tsx +++ b/site/src/pages/AgentsPage/AgentDetail.stories.tsx @@ -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 } /> diff --git a/site/src/pages/AgentsPage/AgentDetail.tsx b/site/src/pages/AgentsPage/AgentDetail.tsx index 9914b15404..a4a49bd1c4 100644 --- a/site/src/pages/AgentsPage/AgentDetail.tsx +++ b/site/src/pages/AgentsPage/AgentDetail.tsx @@ -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(null); const chatInputRef = useRef(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(); - 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(); - 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} /> ); }; diff --git a/site/src/pages/AgentsPage/AgentEmbedPage.tsx b/site/src/pages/AgentsPage/AgentEmbedPage.tsx index 2d44675938..c9f7235c6d 100644 --- a/site/src/pages/AgentsPage/AgentEmbedPage.tsx +++ b/site/src/pages/AgentsPage/AgentEmbedPage.tsx @@ -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, diff --git a/site/src/pages/AgentsPage/AgentsPage.tsx b/site/src/pages/AgentsPage/AgentsPage.tsx index 4149682311..3a3886df66 100644 --- a/site/src/pages/AgentsPage/AgentsPage.tsx +++ b/site/src/pages/AgentsPage/AgentsPage.tsx @@ -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; diff --git a/site/src/pages/AgentsPage/AgentsPageView.stories.tsx b/site/src/pages/AgentsPage/AgentsPageView.stories.tsx index f6547552fe..d8e95bd590 100644 --- a/site/src/pages/AgentsPage/AgentsPageView.stories.tsx +++ b/site/src/pages/AgentsPage/AgentsPageView.stories.tsx @@ -162,16 +162,13 @@ const meta: Meta = { 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 = { 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(), }, }; diff --git a/site/src/pages/AgentsPage/AgentsPageView.tsx b/site/src/pages/AgentsPage/AgentsPageView.tsx index 6a5516d315..7078860e9e 100644 --- a/site/src/pages/AgentsPage/AgentsPageView.tsx +++ b/site/src/pages/AgentsPage/AgentsPageView.tsx @@ -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; + modelIDByConfigID: ReadonlyMap; + 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; + 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; createError: unknown; @@ -58,6 +75,8 @@ interface AgentsPageViewProps { isModelCatalogLoading: boolean; isModelConfigsLoading: boolean; modelCatalogError: unknown; + modelConfigIDByModelID: ReadonlyMap; + desktopEnabled: boolean; hasNextPage: boolean | undefined; onLoadMore: () => void; isFetchingNextPage: boolean; @@ -82,7 +101,13 @@ export const AgentsPageView: FC = ({ onCollapseSidebar, isSidebarCollapsed, onExpandSidebar, - outletContext, + chatErrorReasons, + setChatErrorReason, + clearChatErrorReason, + requestArchiveAgent, + requestUnarchiveAgent, + requestArchiveAndDeleteWorkspace, + onToggleSidebarCollapsed, isAgentsAdmin, onCreateChat, createError, @@ -90,6 +115,8 @@ export const AgentsPageView: FC = ({ isModelCatalogLoading, isModelConfigsLoading, modelCatalogError, + modelConfigIDByModelID, + desktopEnabled, hasNextPage, onLoadMore, isFetchingNextPage, @@ -97,12 +124,6 @@ export const AgentsPageView: FC = ({ 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 = ({ [chatErrorReasons], ); - const outletContextValue = useMemo( - () => ({ ...outletContext, onOpenAnalytics: handleOpenAnalytics }), - [outletContext, handleOpenAnalytics], + const modelIDByConfigID = useMemo(() => { + const byConfigID = new Map(); + 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 (