diff --git a/.gitattributes b/.gitattributes index 78a8e2963d..2ecaacf0f1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -40,4 +40,4 @@ site/src/api/typesGenerated.ts linguist-generated=true # AI Gateway prices coderd/aibridge/prices/data/prices.json linguist-generated=true -site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.json linguist-generated=true +site/src/modules/aiModels/knownModels/knownModelsGenerated.json linguist-generated=true diff --git a/.github/workflows/aigateway-prices-refresh.yaml b/.github/workflows/aigateway-prices-refresh.yaml index 9e5ff52fba..946ad90c18 100644 --- a/.github/workflows/aigateway-prices-refresh.yaml +++ b/.github/workflows/aigateway-prices-refresh.yaml @@ -29,7 +29,7 @@ concurrency: env: REFRESH_BRANCH: bot/aigateway-prices-refresh PRICES_FILE: coderd/aibridge/prices/data/prices.json - CATALOG_FILE: site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.json + CATALOG_FILE: site/src/modules/aiModels/knownModels/knownModelsGenerated.json jobs: refresh: diff --git a/CODEOWNERS b/CODEOWNERS index 2c50db3b79..31c502dca1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -46,4 +46,4 @@ codersdk/aiproviders_bedrock.go @ibetitsmike @johnstcn # models.dev data, and they carry customer-visible cost numbers, so every # refresh needs a review from someone who owns them. coderd/aibridge/prices/data/prices.json @evgeniy-scherbina @ssncferreira @johnstcn -site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.json @evgeniy-scherbina @ssncferreira @johnstcn +site/src/modules/aiModels/knownModels/knownModelsGenerated.json @evgeniy-scherbina @ssncferreira @johnstcn diff --git a/Makefile b/Makefile index 4070f89241..9357b5668a 100644 --- a/Makefile +++ b/Makefile @@ -1063,13 +1063,13 @@ coderd/aibridge/prices/data/prices.json: _gen/bin/aibridgepricesgen _gen/models- # snapshot joined with the editorial curation in # scripts/aibridgepricesgen/curation.json. Kept out of `make gen` for the # same live-upstream-data reason as prices.json. -site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.json: _gen/bin/aibridgepricesgen _gen/models-dev.json | _gen +site/src/modules/aiModels/knownModels/knownModelsGenerated.json: _gen/bin/aibridgepricesgen _gen/models-dev.json | _gen $(call atomic_write,_gen/bin/aibridgepricesgen -format=catalog -upstream _gen/models-dev.json,./scripts/biome_format.sh) -.PHONY: site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.json +.PHONY: site/src/modules/aiModels/knownModels/knownModelsGenerated.json gen/aibridge-prices: \ coderd/aibridge/prices/data/prices.json \ - site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.json + site/src/modules/aiModels/knownModels/knownModelsGenerated.json .PHONY: gen/aibridge-prices gen/golden-files: \ diff --git a/coderd/aibridge/prices/data/README.md b/coderd/aibridge/prices/data/README.md index f92025c8f7..dedce37bd7 100644 --- a/coderd/aibridge/prices/data/README.md +++ b/coderd/aibridge/prices/data/README.md @@ -7,7 +7,7 @@ next regeneration will overwrite any changes. The Make target fetches models.dev once into `_gen/models-dev.json`, applying the upstream corrections in `scripts/aibridgepricesgen/overrides.jq`. Both `prices.json` and the frontend known-models catalog at -`site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.json` +`site/src/modules/aiModels/knownModels/knownModelsGenerated.json` are generated from that single patched snapshot; the catalog is additionally joined with the editorial curation in `scripts/aibridgepricesgen/curation.json`. diff --git a/scripts/aibridgepricesgen/catalog_test.go b/scripts/aibridgepricesgen/catalog_test.go index cc8c5affa4..eddbfdcb35 100644 --- a/scripts/aibridgepricesgen/catalog_test.go +++ b/scripts/aibridgepricesgen/catalog_test.go @@ -272,7 +272,7 @@ func TestCurationMatchesGeneratedCatalog(t *testing.T) { curation := embeddedCuration(t) - data, err := os.ReadFile("../../site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.json") + data, err := os.ReadFile("../../site/src/modules/aiModels/knownModels/knownModelsGenerated.json") require.NoError(t, err) var generated map[string][]catalogEntry require.NoError(t, json.Unmarshal(data, &generated)) diff --git a/site/AGENTS.md b/site/AGENTS.md index 1471db301b..b283faa005 100644 --- a/site/AGENTS.md +++ b/site/AGENTS.md @@ -78,7 +78,7 @@ Some end-to-end tests require a license. The Storybook MCP at `http://localhost: ## Performance -- `src/pages/AgentsPage/`, including `components/ChatElements/`, uses React Compiler. Do not add `useMemo`, `useCallback`, or `memo()` there. +- `src/pages/AgentsPage/`, including `components/ChatElements/`, and `src/modules/aiModels/` use React Compiler. Do not add `useMemo`, `useCallback`, or `memo()` there. - `memo()` remains valid for list-item components rendered in a map because compiler memoization does not cross component boundaries. - Isolate frequently changing state in a small child component instead of rerendering a large parent subtree. - Throttle high-frequency handlers that set state with `requestAnimationFrame` or an established throttle utility. diff --git a/site/scripts/check-compiler.mjs b/site/scripts/check-compiler.mjs index b89f75e903..af64cddf3f 100644 --- a/site/scripts/check-compiler.mjs +++ b/site/scripts/check-compiler.mjs @@ -19,6 +19,7 @@ const siteDir = new URL("..", import.meta.url).pathname; // Directories opted in to React Compiler. Keep this list in sync with // the include filter in vite.config.mts. const targetDirs = [ + "src/modules/aiModels", "src/pages/AgentsPage", "src/pages/AIBridgePage", "src/pages/TemplateBuilder", diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/ModelConfigFields.tsx b/site/src/modules/aiModels/ModelConfigFields.tsx similarity index 99% rename from site/src/pages/AgentsPage/components/ChatModelAdminPanel/ModelConfigFields.tsx rename to site/src/modules/aiModels/ModelConfigFields.tsx index adc3ddbe51..3883e72457 100644 --- a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/ModelConfigFields.tsx +++ b/site/src/modules/aiModels/ModelConfigFields.tsx @@ -41,7 +41,7 @@ import { findKnownModelByCanonicalId, findKnownModelByExactAlias, formatPricePerMillionTokens, -} from "./knownModels"; +} from "./knownModels/catalog"; import { isFieldConflictDisabled, isVisibleWhenSatisfied, diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/ModelIdentifierField.tsx b/site/src/modules/aiModels/ModelIdentifierField.tsx similarity index 99% rename from site/src/pages/AgentsPage/components/ChatModelAdminPanel/ModelIdentifierField.tsx rename to site/src/modules/aiModels/ModelIdentifierField.tsx index 1ba8739dfa..e0cd862d0b 100644 --- a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/ModelIdentifierField.tsx +++ b/site/src/modules/aiModels/ModelIdentifierField.tsx @@ -13,15 +13,15 @@ import { Input } from "#/components/Input/Input"; import { Label } from "#/components/Label/Label"; import { normalizeProvider } from "#/modules/aiModels/helpers"; import type { FormHelpers } from "#/utils/formUtils"; +import { applyKnownModelDefaults } from "./knownModels/applyKnownModelDefaults"; import { findKnownModelByCanonicalId, findKnownModelByExactAlias, formatContextBadge, getKnownModelsForProvider, - type KnownModel, searchKnownModels, -} from "./knownModels"; -import { applyKnownModelDefaults } from "./knownModels/applyKnownModelDefaults"; +} from "./knownModels/catalog"; +import type { KnownModel } from "./knownModels/types"; import { deepGet, deepSet, type ModelFormValues } from "./modelConfigFormLogic"; type ModelFormMode = "add" | "edit" | "duplicate"; diff --git a/site/src/pages/AgentsPage/components/ChatElements/ModelSelector.stories.tsx b/site/src/modules/aiModels/ModelSelector.stories.tsx similarity index 99% rename from site/src/pages/AgentsPage/components/ChatElements/ModelSelector.stories.tsx rename to site/src/modules/aiModels/ModelSelector.stories.tsx index d7e336e998..bb18f95207 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/ModelSelector.stories.tsx +++ b/site/src/modules/aiModels/ModelSelector.stories.tsx @@ -76,7 +76,7 @@ const longNameModel: ModelSelectorOption = { }; const meta: Meta = { - title: "pages/AgentsPage/ChatElements/ModelSelector", + title: "modules/aiModels/ModelSelector", component: ModelSelector, args: { options: openAIModels, diff --git a/site/src/pages/AgentsPage/components/ChatElements/ModelSelector.tsx b/site/src/modules/aiModels/ModelSelector.tsx similarity index 98% rename from site/src/pages/AgentsPage/components/ChatElements/ModelSelector.tsx rename to site/src/modules/aiModels/ModelSelector.tsx index 626d72e390..644c607c91 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/ModelSelector.tsx +++ b/site/src/modules/aiModels/ModelSelector.tsx @@ -22,9 +22,9 @@ import { TooltipContent, TooltipTrigger, } from "#/components/Tooltip/Tooltip"; -import { ProviderIcon } from "#/pages/AISettingsPage/ProvidersPage/components/ProviderIcon"; +import { formatReasoningEffort } from "#/modules/aiModels/helpers"; +import { ProviderIcon } from "#/modules/aiModels/ProviderIcon"; import { formatProviderLabel as defaultFormatProviderLabel } from "#/utils/aiProviders"; -import { formatReasoningEffort } from "../../utils/reasoningEffort"; export interface ModelSelectorOption { id: string; diff --git a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderIcon.stories.tsx b/site/src/modules/aiModels/ProviderIcon.stories.tsx similarity index 95% rename from site/src/pages/AISettingsPage/ProvidersPage/components/ProviderIcon.stories.tsx rename to site/src/modules/aiModels/ProviderIcon.stories.tsx index 1c3b04d28f..62d18247f8 100644 --- a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderIcon.stories.tsx +++ b/site/src/modules/aiModels/ProviderIcon.stories.tsx @@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { ProviderIcon } from "./ProviderIcon"; const meta: Meta = { - title: "pages/AISettingsPage/ProviderIcon", + title: "modules/aiModels/ProviderIcon", component: ProviderIcon, }; diff --git a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderIcon.tsx b/site/src/modules/aiModels/ProviderIcon.tsx similarity index 90% rename from site/src/pages/AISettingsPage/ProvidersPage/components/ProviderIcon.tsx rename to site/src/modules/aiModels/ProviderIcon.tsx index 8f10820c31..25bbbc42b1 100644 --- a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderIcon.tsx +++ b/site/src/modules/aiModels/ProviderIcon.tsx @@ -1,5 +1,6 @@ import { Building2Icon } from "lucide-react"; import { ExternalImage } from "#/components/ExternalImage/ExternalImage"; +import { normalizeProvider } from "#/modules/aiModels/helpers"; type ProviderIconProps = { provider: string; @@ -8,7 +9,7 @@ type ProviderIconProps = { }; export const getProviderIcon = (provider: string): string | undefined => { - switch (provider) { + switch (normalizeProvider(provider)) { case "openai": return "/icon/openai.svg"; case "anthropic": diff --git a/site/src/modules/aiModels/helpers.ts b/site/src/modules/aiModels/helpers.ts index d4bd03e52f..ee76679990 100644 --- a/site/src/modules/aiModels/helpers.ts +++ b/site/src/modules/aiModels/helpers.ts @@ -1,3 +1,7 @@ export function normalizeProvider(provider: string): string { return provider.trim().toLowerCase(); } + +/** Display label for an effort value, e.g. "xhigh" renders as "Xhigh". */ +export const formatReasoningEffort = (value: string): string => + value.charAt(0).toUpperCase() + value.slice(1); diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/applyKnownModelDefaults.test.ts b/site/src/modules/aiModels/knownModels/applyKnownModelDefaults.test.ts similarity index 99% rename from site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/applyKnownModelDefaults.test.ts rename to site/src/modules/aiModels/knownModels/applyKnownModelDefaults.test.ts index 0b12187021..07871678d9 100644 --- a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/applyKnownModelDefaults.test.ts +++ b/site/src/modules/aiModels/knownModels/applyKnownModelDefaults.test.ts @@ -5,7 +5,8 @@ import { type ApplyKnownModelDefaultsResult, applyKnownModelDefaults, } from "./applyKnownModelDefaults"; -import { findKnownModelByCanonicalId, type KnownModel } from "./index"; +import { findKnownModelByCanonicalId } from "./catalog"; +import type { KnownModel } from "./types"; const requireKnownModel = ( provider: string, diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/applyKnownModelDefaults.ts b/site/src/modules/aiModels/knownModels/applyKnownModelDefaults.ts similarity index 100% rename from site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/applyKnownModelDefaults.ts rename to site/src/modules/aiModels/knownModels/applyKnownModelDefaults.ts diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/index.test.ts b/site/src/modules/aiModels/knownModels/catalog.test.ts similarity index 99% rename from site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/index.test.ts rename to site/src/modules/aiModels/knownModels/catalog.test.ts index b45f9c7d7c..e3553190e6 100644 --- a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/index.test.ts +++ b/site/src/modules/aiModels/knownModels/catalog.test.ts @@ -6,7 +6,7 @@ import { formatPricePerMillionTokens, getKnownModelsForProvider, searchKnownModels, -} from "./index"; +} from "./catalog"; const modelIds = (provider: string): readonly string[] => getKnownModelsForProvider(provider).map( diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/index.ts b/site/src/modules/aiModels/knownModels/catalog.ts similarity index 99% rename from site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/index.ts rename to site/src/modules/aiModels/knownModels/catalog.ts index 1e918138e3..3d29c508d2 100644 --- a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/index.ts +++ b/site/src/modules/aiModels/knownModels/catalog.ts @@ -2,8 +2,6 @@ import { normalizeProvider } from "#/modules/aiModels/helpers"; import knownModelsGenerated from "./knownModelsGenerated.json"; import type { KnownModel } from "./types"; -export type { KnownModel }; - // knownModelsGenerated.json is produced by `make gen/aibridge-prices` from // models.dev joined with the editorial curation in // scripts/aibridgepricesgen/curation.json. Do not edit it manually. JSON diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.json b/site/src/modules/aiModels/knownModels/knownModelsGenerated.json similarity index 100% rename from site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.json rename to site/src/modules/aiModels/knownModels/knownModelsGenerated.json diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.test.ts b/site/src/modules/aiModels/knownModels/knownModelsGenerated.test.ts similarity index 100% rename from site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.test.ts rename to site/src/modules/aiModels/knownModels/knownModelsGenerated.test.ts diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/types.ts b/site/src/modules/aiModels/knownModels/types.ts similarity index 100% rename from site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/types.ts rename to site/src/modules/aiModels/knownModels/types.ts diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/modelConfigFormLogic.test.ts b/site/src/modules/aiModels/modelConfigFormLogic.test.ts similarity index 100% rename from site/src/pages/AgentsPage/components/ChatModelAdminPanel/modelConfigFormLogic.test.ts rename to site/src/modules/aiModels/modelConfigFormLogic.test.ts diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/modelConfigFormLogic.ts b/site/src/modules/aiModels/modelConfigFormLogic.ts similarity index 100% rename from site/src/pages/AgentsPage/components/ChatModelAdminPanel/modelConfigFormLogic.ts rename to site/src/modules/aiModels/modelConfigFormLogic.ts diff --git a/site/src/pages/AgentsPage/components/ChatElements/modelSelectorFixtures.ts b/site/src/modules/aiModels/modelSelectorFixtures.ts similarity index 100% rename from site/src/pages/AgentsPage/components/ChatElements/modelSelectorFixtures.ts rename to site/src/modules/aiModels/modelSelectorFixtures.ts diff --git a/site/src/pages/AISettingsPage/CoderAgentsPage/components/SubagentModelOverrideSettings.tsx b/site/src/pages/AISettingsPage/CoderAgentsPage/components/SubagentModelOverrideSettings.tsx index ca8505adcc..af5fbdf842 100644 --- a/site/src/pages/AISettingsPage/CoderAgentsPage/components/SubagentModelOverrideSettings.tsx +++ b/site/src/pages/AISettingsPage/CoderAgentsPage/components/SubagentModelOverrideSettings.tsx @@ -3,7 +3,7 @@ import type { FC, ReactNode } from "react"; import type * as TypesGen from "#/api/typesGenerated"; import { Button } from "#/components/Button/Button"; import { useTemporarySavedState } from "#/components/TemporarySavedState/TemporarySavedState"; -import { ModelSelector } from "#/pages/AgentsPage/components/ChatElements/ModelSelector"; +import { ModelSelector } from "#/modules/aiModels/ModelSelector"; import { ModelOverrideAlerts } from "#/pages/AgentsPage/components/ModelOverrideAlerts"; import type { ProviderInfo } from "#/pages/AgentsPage/utils/modelOptions"; import { pickReasoningEffort } from "#/pages/AgentsPage/utils/reasoningEffort"; diff --git a/site/src/pages/AISettingsPage/ModelsPage/ModelsPageView.tsx b/site/src/pages/AISettingsPage/ModelsPage/ModelsPageView.tsx index 8fa019927f..7498c8583e 100644 --- a/site/src/pages/AISettingsPage/ModelsPage/ModelsPageView.tsx +++ b/site/src/pages/AISettingsPage/ModelsPage/ModelsPageView.tsx @@ -38,11 +38,11 @@ import { } from "#/components/Table/Table"; import { TableEmpty } from "#/components/TableEmpty/TableEmpty"; import { TableLoader } from "#/components/TableLoader/TableLoader"; +import { ProviderIcon } from "#/modules/aiModels/ProviderIcon"; import { canManageProviderModels, type ProviderState, } from "#/modules/aiModels/providerStates"; -import { ProviderIcon } from "#/pages/AISettingsPage/ProvidersPage/components/ProviderIcon"; import { paginateItems } from "#/utils/paginateItems"; import { ModelRow } from "./components/ModelRow"; import { diff --git a/site/src/pages/AISettingsPage/ModelsPage/components/ModelForm.tsx b/site/src/pages/AISettingsPage/ModelsPage/components/ModelForm.tsx index 14a9c68f14..0e9e8c6270 100644 --- a/site/src/pages/AISettingsPage/ModelsPage/components/ModelForm.tsx +++ b/site/src/pages/AISettingsPage/ModelsPage/components/ModelForm.tsx @@ -6,17 +6,17 @@ import type * as TypesGen from "#/api/typesGenerated"; import { OrganizationField } from "#/components/OrganizationAutocomplete/OrganizationAutocomplete"; import { SettingsHeaderTitle } from "#/components/SettingsHeader/SettingsHeader"; import { useUnsavedChangesPrompt } from "#/hooks/useUnsavedChangesPrompt"; -import { - canManageProviderModels, - type ProviderState, -} from "#/modules/aiModels/providerStates"; import { buildInitialModelFormValues, buildModelConfigFromForm, type ModelFormValues, parsePositiveInteger, parseThresholdInteger, -} from "#/pages/AgentsPage/components/ChatModelAdminPanel/modelConfigFormLogic"; +} from "#/modules/aiModels/modelConfigFormLogic"; +import { + canManageProviderModels, + type ProviderState, +} from "#/modules/aiModels/providerStates"; import { getFormHelpers } from "#/utils/formUtils"; import { creatableModelOrganizations, diff --git a/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormDialogs.tsx b/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormDialogs.tsx index 09de858bce..d5d58b9773 100644 --- a/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormDialogs.tsx +++ b/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormDialogs.tsx @@ -11,7 +11,7 @@ import { DialogHeader, DialogTitle, } from "#/components/Dialog/Dialog"; -import type { ModelFormValues } from "#/pages/AgentsPage/components/ChatModelAdminPanel/modelConfigFormLogic"; +import type { ModelFormValues } from "#/modules/aiModels/modelConfigFormLogic"; export const ModelFormDialogs: FC<{ editingModel?: TypesGen.ChatModel; diff --git a/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormFields.tsx b/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormFields.tsx index 8eeb80155b..09685f7102 100644 --- a/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormFields.tsx +++ b/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormFields.tsx @@ -32,18 +32,18 @@ import { TooltipContent, TooltipTrigger, } from "#/components/Tooltip/Tooltip"; -import type { ProviderState } from "#/modules/aiModels/providerStates"; import { GeneralModelConfigFields, ModelConfigFields, PricingEstimateFields, ReasoningEffortConfigFields, -} from "#/pages/AgentsPage/components/ChatModelAdminPanel/ModelConfigFields"; -import { ModelIdentifierField } from "#/pages/AgentsPage/components/ChatModelAdminPanel/ModelIdentifierField"; +} from "#/modules/aiModels/ModelConfigFields"; +import { ModelIdentifierField } from "#/modules/aiModels/ModelIdentifierField"; import type { ModelConfigFormBuildResult, ModelFormValues, -} from "#/pages/AgentsPage/components/ChatModelAdminPanel/modelConfigFormLogic"; +} from "#/modules/aiModels/modelConfigFormLogic"; +import type { ProviderState } from "#/modules/aiModels/providerStates"; import { docs } from "#/utils/docs"; import type { FormHelpers } from "#/utils/formUtils"; import { diff --git a/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormHeader.tsx b/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormHeader.tsx index 93c64c870d..fe093d4ee4 100644 --- a/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormHeader.tsx +++ b/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormHeader.tsx @@ -26,8 +26,8 @@ import { TooltipContent, TooltipTrigger, } from "#/components/Tooltip/Tooltip"; +import { getProviderIcon } from "#/modules/aiModels/ProviderIcon"; import type { ProviderState } from "#/modules/aiModels/providerStates"; -import { getProviderIcon } from "#/pages/AISettingsPage/ProvidersPage/components/ProviderIcon"; import { useOrganizationModelsPath } from "../organizationModels"; export const ModelFormBackLink: FC = () => { diff --git a/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormProviderSelect.tsx b/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormProviderSelect.tsx index 2340ae1b95..4b83459f1a 100644 --- a/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormProviderSelect.tsx +++ b/site/src/pages/AISettingsPage/ModelsPage/components/ModelFormProviderSelect.tsx @@ -7,8 +7,8 @@ import { SelectTrigger, SelectValue, } from "#/components/Select/Select"; +import { ProviderIcon } from "#/modules/aiModels/ProviderIcon"; import type { ProviderState } from "#/modules/aiModels/providerStates"; -import { ProviderIcon } from "#/pages/AISettingsPage/ProvidersPage/components/ProviderIcon"; export const ModelFormProviderSelect: FC<{ providerStates: readonly ProviderState[]; diff --git a/site/src/pages/AISettingsPage/ModelsPage/components/ModelRow.tsx b/site/src/pages/AISettingsPage/ModelsPage/components/ModelRow.tsx index b68762cb5a..d812989942 100644 --- a/site/src/pages/AISettingsPage/ModelsPage/components/ModelRow.tsx +++ b/site/src/pages/AISettingsPage/ModelsPage/components/ModelRow.tsx @@ -11,7 +11,7 @@ import { TooltipTrigger, } from "#/components/Tooltip/Tooltip"; import { useClickableTableRow } from "#/hooks/useClickableTableRow"; -import { ProviderIcon } from "#/pages/AISettingsPage/ProvidersPage/components/ProviderIcon"; +import { ProviderIcon } from "#/modules/aiModels/ProviderIcon"; type ModelRowProps = { model: ChatModel; diff --git a/site/src/pages/AISettingsPage/ProvidersPage/AddProviderPage/AddProviderPageView.tsx b/site/src/pages/AISettingsPage/ProvidersPage/AddProviderPage/AddProviderPageView.tsx index 39553155ed..b621edd07b 100644 --- a/site/src/pages/AISettingsPage/ProvidersPage/AddProviderPage/AddProviderPageView.tsx +++ b/site/src/pages/AISettingsPage/ProvidersPage/AddProviderPage/AddProviderPageView.tsx @@ -8,9 +8,9 @@ import { createAIProviderMutation } from "#/api/queries/aiProviders"; import { Avatar } from "#/components/Avatar/Avatar"; import { Button } from "#/components/Button/Button"; import { SettingsHeaderTitle } from "#/components/SettingsHeader/SettingsHeader"; +import { getProviderIcon } from "#/modules/aiModels/ProviderIcon"; import type { AddableProvider } from "../components/addableProviderTypes"; import { ProviderForm } from "../components/ProviderForm"; -import { getProviderIcon } from "../components/ProviderIcon"; import { providerFormValuesToCreate } from "../components/providerFormApiMap"; interface AddProviderPageViewProps { diff --git a/site/src/pages/AISettingsPage/ProvidersPage/ProvidersPageView.tsx b/site/src/pages/AISettingsPage/ProvidersPage/ProvidersPageView.tsx index e756bba7b8..7c8eb25645 100644 --- a/site/src/pages/AISettingsPage/ProvidersPage/ProvidersPageView.tsx +++ b/site/src/pages/AISettingsPage/ProvidersPage/ProvidersPageView.tsx @@ -24,8 +24,8 @@ import { } from "#/components/Table/Table"; import { TableEmpty } from "#/components/TableEmpty/TableEmpty"; import { TableLoader } from "#/components/TableLoader/TableLoader"; +import { ProviderIcon } from "#/modules/aiModels/ProviderIcon"; import { addableProviders } from "#/pages/AISettingsPage/ProvidersPage/components/addableProviderTypes"; -import { ProviderIcon } from "#/pages/AISettingsPage/ProvidersPage/components/ProviderIcon"; import { ProviderRow } from "#/pages/AISettingsPage/ProvidersPage/components/ProviderRow"; import { docs } from "#/utils/docs"; diff --git a/site/src/pages/AISettingsPage/ProvidersPage/UpdateProviderPage/UpdateProviderPageView.tsx b/site/src/pages/AISettingsPage/ProvidersPage/UpdateProviderPage/UpdateProviderPageView.tsx index 5a0b242d24..45355a03a5 100644 --- a/site/src/pages/AISettingsPage/ProvidersPage/UpdateProviderPage/UpdateProviderPageView.tsx +++ b/site/src/pages/AISettingsPage/ProvidersPage/UpdateProviderPage/UpdateProviderPageView.tsx @@ -18,9 +18,9 @@ import { DeleteDialog } from "#/components/Dialog/DeleteDialog/DeleteDialog"; import { Loader } from "#/components/Loader/Loader"; import { SettingsHeaderTitle } from "#/components/SettingsHeader/SettingsHeader"; import { Switch } from "#/components/Switch/Switch"; +import { getProviderIcon } from "#/modules/aiModels/ProviderIcon"; import { pageTitle } from "#/utils/page"; import { ProviderForm } from "../components/ProviderForm"; -import { getProviderIcon } from "../components/ProviderIcon"; import { aiProviderToFormValues, bedrockExternalId, diff --git a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.tsx b/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.tsx index 7cdfc435a9..80703630c5 100644 --- a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.tsx +++ b/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.tsx @@ -14,7 +14,7 @@ import { TooltipTrigger, } from "#/components/Tooltip/Tooltip"; import { useClickableTableRow } from "#/hooks/useClickableTableRow"; -import { ProviderIcon } from "./ProviderIcon"; +import { ProviderIcon } from "#/modules/aiModels/ProviderIcon"; import { getProviderDisplayType } from "./providerFormApiMap"; type ProviderRowProps = { diff --git a/site/src/pages/AISettingsPage/ProvidersPage/components/providerFormApiMap.ts b/site/src/pages/AISettingsPage/ProvidersPage/components/providerFormApiMap.ts index 434ff14ca7..3fe493f205 100644 --- a/site/src/pages/AISettingsPage/ProvidersPage/components/providerFormApiMap.ts +++ b/site/src/pages/AISettingsPage/ProvidersPage/components/providerFormApiMap.ts @@ -8,12 +8,12 @@ import type { CreateAIProviderRequest, UpdateAIProviderRequest, } from "#/api/typesGenerated"; +import { getProviderIcon } from "#/modules/aiModels/ProviderIcon"; import { type ProviderFormValues, parseBedrockRegionFromBaseUrl, SAVED_CREDENTIAL_MASK, } from "./ProviderForm"; -import { getProviderIcon } from "./ProviderIcon"; /** Drop placeholder masks so they don't round-trip back to the API. */ const sanitizeCredential = ( diff --git a/site/src/pages/AgentsPage/AgentChatPageView.stories.tsx b/site/src/pages/AgentsPage/AgentChatPageView.stories.tsx index 33f0bc2e08..ebd2ee3512 100644 --- a/site/src/pages/AgentsPage/AgentChatPageView.stories.tsx +++ b/site/src/pages/AgentsPage/AgentChatPageView.stories.tsx @@ -15,6 +15,7 @@ import { reactRouterParameters } from "storybook-addon-remix-react-router"; import { API } from "#/api/api"; import type * as TypesGen from "#/api/typesGenerated"; import type { ChatDiffStatus, ChatMessagePart } from "#/api/typesGenerated"; +import type { ModelSelectorOption } from "#/modules/aiModels/ModelSelector"; import { AGENT_BROWSER_APP_SLUG } from "#/modules/apps/apps"; import { MockChat } from "#/testHelpers/chatEntities"; import { @@ -43,7 +44,6 @@ import { import type { ChatDetailError } from "./components/ChatConversation/chatError"; import { createChatStore } from "./components/ChatConversation/chatStore"; import { buildLongConversation } from "./components/ChatConversation/storyFixtures"; -import type { ModelSelectorOption } from "./components/ChatElements/ModelSelector"; import { lastActiveSidebarTabStorageKeyPrefix } from "./utils/sidebarTabStorage"; // --------------------------------------------------------------------------- diff --git a/site/src/pages/AgentsPage/AgentChatPageView.tsx b/site/src/pages/AgentsPage/AgentChatPageView.tsx index 1ec0747e87..38c6a9c9e9 100644 --- a/site/src/pages/AgentsPage/AgentChatPageView.tsx +++ b/site/src/pages/AgentsPage/AgentChatPageView.tsx @@ -18,6 +18,7 @@ import type { import { ErrorAlert } from "#/components/Alert/ErrorAlert"; import { useProxy } from "#/contexts/ProxyContext"; import { useAuthenticated } from "#/hooks/useAuthenticated"; +import type { ModelSelectorOption } from "#/modules/aiModels/ModelSelector"; import { getAgentBrowserApp, isWorkspaceAppEmbeddable, @@ -39,7 +40,6 @@ import type { ChatDetailError } from "./components/ChatConversation/chatError"; import { getParentChatID } from "./components/ChatConversation/chatHelpers"; import type { useChatStore } from "./components/ChatConversation/chatStore"; import { QueuedForCapacityCallout } from "./components/ChatConversation/QueuedForCapacityCallout"; -import type { ModelSelectorOption } from "./components/ChatElements/ModelSelector"; import { DesktopPanelContext } from "./components/ChatElements/tools/DesktopPanelContext"; import type { SkillMetadata } from "./components/ChatMessageInput/SkillsTriggerMenu"; import type { PendingAttachment } from "./components/ChatPageContent"; diff --git a/site/src/pages/AgentsPage/AgentSettingsUserAgentsPageView.stories.tsx b/site/src/pages/AgentsPage/AgentSettingsUserAgentsPageView.stories.tsx index ac41d65ff0..271ce6f760 100644 --- a/site/src/pages/AgentsPage/AgentSettingsUserAgentsPageView.stories.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsUserAgentsPageView.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { useState } from "react"; import { expect, fn, screen, userEvent, waitFor, within } from "storybook/test"; import type * as TypesGen from "#/api/typesGenerated"; +import type { ModelSelectorOption } from "#/modules/aiModels/ModelSelector"; import { MockChatModel } from "#/testHelpers/chatModels"; import { MockDefaultOrganization, @@ -11,7 +12,6 @@ import { AgentSettingsUserAgentsPageView, type AgentSettingsUserAgentsPageViewProps, } from "./AgentSettingsUserAgentsPageView"; -import type { ModelSelectorOption } from "./components/ChatElements/ModelSelector"; const UNAVAILABLE_WARNING = "The saved model is unavailable and will be ignored until you choose a valid model override."; diff --git a/site/src/pages/AgentsPage/AgentSettingsUserAgentsPageView.tsx b/site/src/pages/AgentsPage/AgentSettingsUserAgentsPageView.tsx index 53033f8ce0..f8033b36b1 100644 --- a/site/src/pages/AgentsPage/AgentSettingsUserAgentsPageView.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsUserAgentsPageView.tsx @@ -7,7 +7,7 @@ import { getOrganizationLabel, OrganizationAutocomplete, } from "#/components/OrganizationAutocomplete/OrganizationAutocomplete"; -import type { ModelSelectorOption } from "./components/ChatElements/ModelSelector"; +import type { ModelSelectorOption } from "#/modules/aiModels/ModelSelector"; import { PersonalModelOverrideRow, type SavePersonalOverride, diff --git a/site/src/pages/AgentsPage/components/AgentChatInput.tsx b/site/src/pages/AgentsPage/components/AgentChatInput.tsx index 2bee6831e7..a83d4ea5ac 100644 --- a/site/src/pages/AgentsPage/components/AgentChatInput.tsx +++ b/site/src/pages/AgentsPage/components/AgentChatInput.tsx @@ -58,6 +58,10 @@ import { TooltipContent, TooltipTrigger, } from "#/components/Tooltip/Tooltip"; +import { + ModelSelector, + type ModelSelectorOption, +} from "#/modules/aiModels/ModelSelector"; import { countInvisibleCharacters } from "#/utils/invisibleUnicode"; import { isBelowMdViewport, isMobileViewport } from "#/utils/mobile"; import { chatWidthClass, useChatFullWidth } from "../hooks/useChatFullWidth"; @@ -79,10 +83,6 @@ import { isUploadInProgress, type UploadState, } from "./AttachmentPreview"; -import { - ModelSelector, - type ModelSelectorOption, -} from "./ChatElements/ModelSelector"; import { ChatMessageInput, type ChatMessageInputRef, diff --git a/site/src/pages/AgentsPage/components/ChatConversation/chatHelpers.test.ts b/site/src/pages/AgentsPage/components/ChatConversation/chatHelpers.test.ts index b5df5fa1a0..2c06e33f54 100644 --- a/site/src/pages/AgentsPage/components/ChatConversation/chatHelpers.test.ts +++ b/site/src/pages/AgentsPage/components/ChatConversation/chatHelpers.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import type * as TypesGen from "#/api/typesGenerated"; +import type { ModelSelectorOption } from "#/modules/aiModels/ModelSelector"; import { MockChatMessage } from "#/testHelpers/chatEntities"; -import type { ModelSelectorOption } from "../ChatElements/ModelSelector"; import { extractContextUsageFromMessage, getLatestContextUsage, diff --git a/site/src/pages/AgentsPage/components/ChatConversation/chatHelpers.ts b/site/src/pages/AgentsPage/components/ChatConversation/chatHelpers.ts index e9b1a193a8..ed9462b11a 100644 --- a/site/src/pages/AgentsPage/components/ChatConversation/chatHelpers.ts +++ b/site/src/pages/AgentsPage/components/ChatConversation/chatHelpers.ts @@ -1,7 +1,7 @@ import type * as TypesGen from "#/api/typesGenerated"; +import type { ModelSelectorOption } from "#/modules/aiModels/ModelSelector"; import { findWorkspaceAgent } from "#/utils/workspace"; import type { AgentContextUsage } from "../AgentChatInput"; -import type { ModelSelectorOption } from "../ChatElements/ModelSelector"; import { asString } from "../ChatElements/runtimeTypeUtils"; import { asNonEmptyString } from "./blockUtils"; diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ListSubagentModelsTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ListSubagentModelsTool.tsx index 114e2f0831..11986e7c52 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/tools/ListSubagentModelsTool.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ListSubagentModelsTool.tsx @@ -1,8 +1,8 @@ import type React from "react"; import { ScrollArea } from "#/components/ScrollArea/ScrollArea"; -import { ProviderIcon } from "#/pages/AISettingsPage/ProvidersPage/components/ProviderIcon"; +import { formatContextLimit } from "#/modules/aiModels/ModelSelector"; +import { ProviderIcon } from "#/modules/aiModels/ProviderIcon"; import { formatProviderLabel } from "#/utils/aiProviders"; -import { formatContextLimit } from "../ModelSelector"; import { ToolCall } from "./ToolCall"; import { asNumber, asRecord, asString, type ToolStatus } from "./utils"; diff --git a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/ProviderIcon.tsx b/site/src/pages/AgentsPage/components/ChatModelAdminPanel/ProviderIcon.tsx deleted file mode 100644 index 7d9f303f08..0000000000 --- a/site/src/pages/AgentsPage/components/ChatModelAdminPanel/ProviderIcon.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { cn } from "cn"; -import { Building2Icon } from "lucide-react"; -import type { FC } from "react"; -import { ExternalImage } from "#/components/ExternalImage/ExternalImage"; -import { normalizeProvider } from "#/modules/aiModels/helpers"; -import { getProviderIcon } from "#/pages/AISettingsPage/ProvidersPage/components/ProviderIcon"; - -interface ProviderIconProps { - provider: string; - className?: string; -} - -export const ProviderIcon: FC = ({ - provider, - className, -}) => { - const iconPath = getProviderIcon(normalizeProvider(provider)); - return ( -
- {iconPath ? ( - - ) : ( - - )} -
- ); -}; diff --git a/site/src/pages/AgentsPage/components/ChatPageContent.tsx b/site/src/pages/AgentsPage/components/ChatPageContent.tsx index 8978e28472..7268532154 100644 --- a/site/src/pages/AgentsPage/components/ChatPageContent.tsx +++ b/site/src/pages/AgentsPage/components/ChatPageContent.tsx @@ -6,6 +6,7 @@ import type { UrlTransform } from "streamdown"; import { chatPromptsQuery, refreshChatContext } from "#/api/queries/chats"; import type * as TypesGen from "#/api/typesGenerated"; import type { AgentChatSendShortcut } from "#/api/typesGenerated"; +import type { ModelSelectorOption } from "#/modules/aiModels/ModelSelector"; import { useChatDraftAttachments } from "../hooks/useChatDraftAttachments"; import { chatWidthClass, useChatFullWidth } from "../hooks/useChatFullWidth"; import { useFileAttachments } from "../hooks/useFileAttachments"; @@ -49,7 +50,6 @@ import { } from "./ChatConversation/messageParsing"; import { buildStreamTools } from "./ChatConversation/streamState"; import { useOnRenderProfiler } from "./ChatConversation/useOnRenderProfiler"; -import type { ModelSelectorOption } from "./ChatElements/ModelSelector"; import type { SkillMetadata } from "./ChatMessageInput/SkillsTriggerMenu"; import { ChatMessageScroller } from "./ChatMessageScroller"; diff --git a/site/src/pages/AgentsPage/components/PersonalModelOverrideRow.tsx b/site/src/pages/AgentsPage/components/PersonalModelOverrideRow.tsx index ff6217c172..549fede416 100644 --- a/site/src/pages/AgentsPage/components/PersonalModelOverrideRow.tsx +++ b/site/src/pages/AgentsPage/components/PersonalModelOverrideRow.tsx @@ -3,11 +3,11 @@ import type { FC } from "react"; import type * as TypesGen from "#/api/typesGenerated"; import { Alert, AlertDescription } from "#/components/Alert/Alert"; import { Button } from "#/components/Button/Button"; -import { pickReasoningEffort } from "../utils/reasoningEffort"; import { ModelSelector, type ModelSelectorOption, -} from "./ChatElements/ModelSelector"; +} from "#/modules/aiModels/ModelSelector"; +import { pickReasoningEffort } from "../utils/reasoningEffort"; import { ModelOverrideAlerts } from "./ModelOverrideAlerts"; import { SectionHeader } from "./SectionHeader"; diff --git a/site/src/pages/AgentsPage/components/UserCompactionThresholdSettings.tsx b/site/src/pages/AgentsPage/components/UserCompactionThresholdSettings.tsx index 4faeb2ba3e..5c0c0a8e23 100644 --- a/site/src/pages/AgentsPage/components/UserCompactionThresholdSettings.tsx +++ b/site/src/pages/AgentsPage/components/UserCompactionThresholdSettings.tsx @@ -29,8 +29,8 @@ import { TooltipContent, TooltipTrigger, } from "#/components/Tooltip/Tooltip"; +import { ProviderIcon } from "#/modules/aiModels/ProviderIcon"; import { formatProviderLabel } from "#/utils/aiProviders"; -import { ProviderIcon } from "./ChatModelAdminPanel/ProviderIcon"; interface UserCompactionThresholdSettingsProps { models: readonly TypesGen.ChatModel[]; @@ -371,7 +371,12 @@ export const UserCompactionThresholdSettings: FC< className="w-fit" aria-label={`${providerLabel} ${modelName} in ${organizationName}`} > - + + + {modelName} {rowError && ( diff --git a/site/src/pages/AgentsPage/utils/modelOptions.ts b/site/src/pages/AgentsPage/utils/modelOptions.ts index a4fb05764c..844fbeca65 100644 --- a/site/src/pages/AgentsPage/utils/modelOptions.ts +++ b/site/src/pages/AgentsPage/utils/modelOptions.ts @@ -1,6 +1,6 @@ import type * as TypesGen from "#/api/typesGenerated"; import { normalizeProvider } from "#/modules/aiModels/helpers"; -import type { ModelSelectorOption } from "../components/ChatElements/ModelSelector"; +import type { ModelSelectorOption } from "#/modules/aiModels/ModelSelector"; import { asNumber, asString, diff --git a/site/src/pages/AgentsPage/utils/reasoningEffort.test.ts b/site/src/pages/AgentsPage/utils/reasoningEffort.test.ts index a84e5eb174..d9ddba5bc5 100644 --- a/site/src/pages/AgentsPage/utils/reasoningEffort.test.ts +++ b/site/src/pages/AgentsPage/utils/reasoningEffort.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { formatReasoningEffort } from "#/modules/aiModels/helpers"; import { - formatReasoningEffort, getReasoningEffortForModel, pickReasoningEffort, saveReasoningEffortForModel, diff --git a/site/src/pages/AgentsPage/utils/reasoningEffort.ts b/site/src/pages/AgentsPage/utils/reasoningEffort.ts index f07a807b0d..d53012203d 100644 --- a/site/src/pages/AgentsPage/utils/reasoningEffort.ts +++ b/site/src/pages/AgentsPage/utils/reasoningEffort.ts @@ -28,10 +28,6 @@ export const saveReasoningEffortForModel = ( } }; -/** Display label for an effort value, e.g. "xhigh" renders as "Xhigh". */ -export const formatReasoningEffort = (value: string): string => - value.charAt(0).toUpperCase() + value.slice(1); - /** Chooses requested effort, then default effort, then the last selectable effort. */ export const pickReasoningEffort = ( value: string | undefined, diff --git a/site/vite.config.mts b/site/vite.config.mts index 2e293bbfc1..7afe13d933 100644 --- a/site/vite.config.mts +++ b/site/vite.config.mts @@ -20,6 +20,7 @@ compilerPreset.rolldown.filter = { id: { // Keep in sync with targetDirs in scripts/check-compiler.mjs. include: [ + /src\/modules\/aiModels\//, /src\/pages\/AgentsPage\//, /src\/pages\/AIBridgePage\//, /src\/pages\/TemplateBuilder\//,