Skip to content

Commit a492f04

Browse files
committed
test(site/src/pages/AgentsPage/components/ChatModelAdminPanel): update attachment stories
1 parent b2c0285 commit a492f04

File tree

2 files changed

+266
-28
lines changed

2 files changed

+266
-28
lines changed

site/src/pages/AgentsPage/components/ChatModelAdminPanel/ChatModelAdminPanel.stories.tsx

Lines changed: 170 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Meta, StoryObj } from "@storybook/react-vite";
22
import { type ComponentProps, useState } from "react";
33
import { expect, fn, spyOn, userEvent, waitFor, within } from "storybook/test";
4+
import { reactRouterParameters } from "storybook-addon-remix-react-router";
45
import { API } from "#/api/api";
56
import type * as TypesGen from "#/api/typesGenerated";
67
import {
@@ -48,18 +49,39 @@ const createProviderConfig = (
4849
updated_at: overrides.updated_at ?? now,
4950
});
5051

52+
type ModelProviderAttachmentOverrides = Partial<
53+
Omit<TypesGen.ChatModelProviderAttachment, "provider_config_id">
54+
>;
55+
5156
const createModelProviderAttachment = (
5257
providerConfigId: string,
58+
overrides: ModelProviderAttachmentOverrides = {},
5359
): TypesGen.ChatModelProviderAttachment => ({
54-
id: `attachment-${providerConfigId}`,
60+
id: overrides.id ?? `attachment-${providerConfigId}`,
5561
provider_config_id: providerConfigId,
56-
provider: "openai",
57-
priority: 0,
58-
display_name: providerConfigId,
59-
enabled: true,
60-
has_api_key: false,
62+
provider: overrides.provider ?? "openai",
63+
priority: overrides.priority ?? 0,
64+
display_name: overrides.display_name ?? providerConfigId,
65+
enabled: overrides.enabled ?? true,
66+
has_api_key: overrides.has_api_key ?? false,
6167
});
6268

69+
const createModelProviderAttachments = (
70+
providerConfigs: readonly TypesGen.ChatProviderConfig[],
71+
): TypesGen.ChatModelProviderAttachment[] =>
72+
providerConfigs.map((providerConfig, priority) =>
73+
createModelProviderAttachment(providerConfig.id, {
74+
provider: providerConfig.provider,
75+
priority,
76+
display_name:
77+
providerConfig.display_name ||
78+
providerConfig.base_url ||
79+
providerConfig.id,
80+
enabled: providerConfig.enabled,
81+
has_api_key: providerConfig.has_api_key,
82+
}),
83+
);
84+
6385
const createModelConfig = (
6486
overrides: Partial<TypesGen.ChatModelConfig> &
6587
Pick<TypesGen.ChatModelConfig, "id" | "provider" | "model">,
@@ -78,6 +100,88 @@ const createModelConfig = (
78100
updated_at: overrides.updated_at ?? now,
79101
});
80102

103+
const createModelProviderAttachmentsFromIDs = (
104+
providerConfigIds: readonly string[],
105+
providerConfigs: readonly TypesGen.ChatProviderConfig[],
106+
fallbackProvider: string,
107+
): TypesGen.ChatModelProviderAttachment[] =>
108+
providerConfigIds.map((providerConfigId, priority) => {
109+
const providerConfig = providerConfigs.find(
110+
(config) => config.id === providerConfigId,
111+
);
112+
113+
return createModelProviderAttachment(providerConfigId, {
114+
provider: providerConfig?.provider ?? fallbackProvider,
115+
priority,
116+
display_name:
117+
providerConfig?.display_name ||
118+
providerConfig?.base_url ||
119+
providerConfigId,
120+
enabled: providerConfig?.enabled ?? true,
121+
has_api_key: providerConfig?.has_api_key ?? false,
122+
});
123+
});
124+
125+
const multiAttachmentPrimaryProviderConfig = createProviderConfig({
126+
id: "3f4f2e43-9c0b-4b22-a0cf-4f4f20f994f1",
127+
provider: "openai",
128+
display_name: "OpenAI Primary",
129+
has_api_key: true,
130+
has_effective_api_key: true,
131+
base_url: "https://api.openai.com/v1",
132+
});
133+
134+
const multiAttachmentSandboxProviderConfig = createProviderConfig({
135+
id: "55ed7e92-fad1-4d2e-9bba-78d27af5c949",
136+
provider: "openai",
137+
display_name: "OpenAI Sandbox",
138+
has_api_key: false,
139+
has_effective_api_key: false,
140+
allow_user_api_key: true,
141+
base_url: "https://sandbox.openai.example.com/v1",
142+
});
143+
144+
const multiAttachmentArchiveProviderConfig = createProviderConfig({
145+
id: "8e12d651-7430-4eb9-b2d6-d80a6107b7fb",
146+
provider: "openai",
147+
display_name: "OpenAI Archive",
148+
enabled: false,
149+
has_api_key: false,
150+
has_effective_api_key: false,
151+
allow_user_api_key: true,
152+
base_url: "https://archive.openai.example.com/v1",
153+
});
154+
155+
const multiAttachmentRecoveryProviderConfig = createProviderConfig({
156+
id: "9d0b27c9-4637-4e8f-8dcb-2bd1fb4e6c1f",
157+
provider: "openai",
158+
display_name: "OpenAI Disaster Recovery",
159+
has_api_key: true,
160+
has_effective_api_key: true,
161+
base_url: "https://dr.openai.example.com/v1",
162+
});
163+
164+
const multiAttachmentProviderConfigs = [
165+
multiAttachmentPrimaryProviderConfig,
166+
multiAttachmentSandboxProviderConfig,
167+
multiAttachmentArchiveProviderConfig,
168+
multiAttachmentRecoveryProviderConfig,
169+
];
170+
171+
const multiAttachmentModelConfig = createModelConfig({
172+
id: "6d447897-7a60-4209-9dfa-46f726726d46",
173+
provider: "openai",
174+
provider_configs: createModelProviderAttachments([
175+
multiAttachmentSandboxProviderConfig,
176+
multiAttachmentPrimaryProviderConfig,
177+
multiAttachmentArchiveProviderConfig,
178+
]),
179+
model: "gpt-4.1",
180+
display_name: "GPT-4.1 Router",
181+
enabled: true,
182+
context_limit: 128000,
183+
});
184+
81185
type ChatModelAdminPanelStoryProps = ComponentProps<typeof ChatModelAdminPanel>;
82186

83187
/**
@@ -170,8 +274,10 @@ const setupChatSpies = (state: {
170274
const created = createModelConfig({
171275
id: `model-${state.modelConfigs.length + 1}`,
172276
provider: req.provider,
173-
provider_configs: (req.provider_config_ids ?? []).map(
174-
createModelProviderAttachment,
277+
provider_configs: createModelProviderAttachmentsFromIDs(
278+
req.provider_config_ids ?? [],
279+
state.providerConfigs,
280+
req.provider,
175281
),
176282
model: req.model,
177283
display_name: req.display_name || req.model,
@@ -218,7 +324,11 @@ const setupChatSpies = (state: {
218324
...req,
219325
provider_configs:
220326
req.provider_config_ids !== undefined
221-
? req.provider_config_ids.map(createModelProviderAttachment)
327+
? createModelProviderAttachmentsFromIDs(
328+
req.provider_config_ids,
329+
state.providerConfigs,
330+
current.provider,
331+
)
222332
: current.provider_configs,
223333
id: current.id,
224334
provider: current.provider,
@@ -460,6 +570,57 @@ export const MultiConfigModelBinding: Story = {
460570
},
461571
};
462572

573+
export const MultiProviderAttachments: Story = {
574+
args: {
575+
section: "models" as ChatModelAdminSection,
576+
providerConfigsData: multiAttachmentProviderConfigs,
577+
modelConfigsData: [multiAttachmentModelConfig],
578+
modelCatalogData: { providers: [] },
579+
},
580+
parameters: {
581+
reactRouter: reactRouterParameters({
582+
location: {
583+
searchParams: { model: multiAttachmentModelConfig.id },
584+
},
585+
}),
586+
},
587+
play: async ({ canvasElement }) => {
588+
const body = within(canvasElement.ownerDocument.body);
589+
590+
await expect(
591+
await body.findByLabelText(/Model Identifier/i),
592+
).toBeInTheDocument();
593+
await expect(
594+
await body.findByText("Provider Configurations"),
595+
).toBeVisible();
596+
597+
const attachmentNames = body
598+
.getAllByText(/OpenAI (Sandbox|Primary|Archive)/)
599+
.map((element) => element.textContent);
600+
expect(attachmentNames).toEqual([
601+
"OpenAI Sandbox",
602+
"OpenAI Primary",
603+
"OpenAI Archive",
604+
]);
605+
606+
const moveUpButtons = body.getAllByRole("button", { name: "Move up" });
607+
const moveDownButtons = body.getAllByRole("button", { name: "Move down" });
608+
const removeButtons = body.getAllByRole("button", { name: "Remove" });
609+
610+
expect(moveUpButtons).toHaveLength(3);
611+
expect(moveUpButtons[0]).toBeDisabled();
612+
expect(moveDownButtons).toHaveLength(3);
613+
expect(moveDownButtons[2]).toBeDisabled();
614+
expect(removeButtons).toHaveLength(3);
615+
616+
expect(body.getAllByText("Enabled")).toHaveLength(2);
617+
expect(body.getAllByText("Disabled")).toHaveLength(1);
618+
expect(body.getAllByText("No API key")).toHaveLength(2);
619+
expect(body.getByText("API key set")).toBeInTheDocument();
620+
expect(body.getByText("Add configuration...")).toBeInTheDocument();
621+
},
622+
};
623+
463624
export const CreateAndUpdateProvider: Story = {
464625
render: function CreateAndUpdateProvider(args) {
465626
const [providerConfigsData, setProviderConfigsData] = useState(

site/src/pages/AgentsPage/components/ChatModelAdminPanel/ModelsSection.stories.tsx

Lines changed: 96 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,112 @@ import { TooltipProvider } from "#/components/Tooltip/Tooltip";
55
import type { ProviderState } from "./ChatModelAdminPanel";
66
import { ModelsSection } from "./ModelsSection";
77

8-
const existingProviderConfig: TypesGen.ChatProviderConfig = {
9-
id: "provider-config-id",
8+
const now = "2025-01-01T00:00:00Z";
9+
10+
const createProviderConfig = (
11+
overrides: Partial<TypesGen.ChatProviderConfig> &
12+
Pick<TypesGen.ChatProviderConfig, "id" | "provider">,
13+
): TypesGen.ChatProviderConfig => ({
14+
id: overrides.id,
15+
provider: overrides.provider,
16+
display_name: overrides.display_name ?? "",
17+
enabled: overrides.enabled ?? true,
18+
has_api_key: overrides.has_api_key ?? false,
19+
has_effective_api_key:
20+
overrides.has_effective_api_key ?? overrides.has_api_key ?? false,
21+
central_api_key_enabled: overrides.central_api_key_enabled ?? true,
22+
allow_user_api_key: overrides.allow_user_api_key ?? false,
23+
allow_central_api_key_fallback:
24+
overrides.allow_central_api_key_fallback ?? false,
25+
base_url: overrides.base_url ?? "",
26+
source: overrides.source ?? "database",
27+
created_at: overrides.created_at ?? now,
28+
updated_at: overrides.updated_at ?? now,
29+
});
30+
31+
const createAttachment = (
32+
providerConfig: TypesGen.ChatProviderConfig,
33+
priority: number,
34+
): TypesGen.ChatModelProviderAttachment => ({
35+
id: `attachment-${providerConfig.id}`,
36+
provider_config_id: providerConfig.id,
37+
provider: providerConfig.provider,
38+
priority,
39+
display_name:
40+
providerConfig.display_name || providerConfig.base_url || providerConfig.id,
41+
enabled: providerConfig.enabled,
42+
has_api_key: providerConfig.has_api_key,
43+
});
44+
45+
const primaryProviderConfig = createProviderConfig({
46+
id: "d889b26b-9d4e-4e1b-94de-d9a4f625bbf7",
1047
provider: "openai",
11-
display_name: "OpenAI",
12-
enabled: true,
48+
display_name: "OpenAI Primary",
1349
has_api_key: true,
1450
has_effective_api_key: true,
15-
central_api_key_enabled: true,
16-
allow_user_api_key: false,
17-
allow_central_api_key_fallback: false,
18-
base_url: undefined,
19-
source: "database",
20-
created_at: "2025-01-01T00:00:00Z",
21-
updated_at: "2025-01-01T00:00:00Z",
22-
};
51+
base_url: "https://api.openai.com/v1",
52+
});
53+
54+
const fallbackProviderConfig = createProviderConfig({
55+
id: "e03c44a3-91d0-4f08-8a95-14a0268cb2d5",
56+
provider: "openai",
57+
display_name: "OpenAI Fallback",
58+
has_api_key: true,
59+
has_effective_api_key: true,
60+
base_url: "https://fallback.openai.example.com/v1",
61+
});
62+
63+
const sandboxProviderConfig = createProviderConfig({
64+
id: "a19ad8d4-35ad-4e47-8243-5f7f14cc57f8",
65+
provider: "openai",
66+
display_name: "OpenAI Sandbox",
67+
has_api_key: false,
68+
has_effective_api_key: false,
69+
allow_user_api_key: true,
70+
base_url: "https://sandbox.openai.example.com/v1",
71+
});
2372

2473
const providerState: ProviderState = {
2574
provider: "openai",
2675
label: "OpenAI",
27-
providerConfig: existingProviderConfig,
28-
providerConfigs: [existingProviderConfig],
76+
providerConfig: primaryProviderConfig,
77+
providerConfigs: [
78+
primaryProviderConfig,
79+
fallbackProviderConfig,
80+
sandboxProviderConfig,
81+
],
2982
modelConfigs: [],
3083
catalogModelCount: 0,
3184
hasManagedAPIKey: true,
3285
hasCatalogAPIKey: true,
3386
hasEffectiveAPIKey: true,
3487
isEnvPreset: false,
35-
baseURL: "",
88+
baseURL: primaryProviderConfig.base_url ?? "",
3689
};
3790

3891
const baseModelConfig: TypesGen.ChatModelConfig = {
39-
id: "model-config-id",
92+
id: "f3f8f726-3a3f-4b85-bf5d-4ba4d427e5fe",
4093
provider: "openai",
94+
provider_configs: [],
4195
model: "gpt-4.1",
4296
display_name: "GPT-4.1",
4397
enabled: true,
4498
is_default: false,
4599
context_limit: 128000,
46100
compression_threshold: 80,
47-
created_at: "2025-01-01T00:00:00Z",
48-
updated_at: "2025-01-01T00:00:00Z",
101+
created_at: now,
102+
updated_at: now,
103+
};
104+
105+
const multiAttachmentModelConfig: TypesGen.ChatModelConfig = {
106+
...baseModelConfig,
107+
id: "a7b0e7f6-1cd6-4472-b1f7-6457f9f0b9d8",
108+
display_name: "GPT-4.1 Router",
109+
provider_configs: [
110+
createAttachment(sandboxProviderConfig, 0),
111+
createAttachment(fallbackProviderConfig, 1),
112+
createAttachment(primaryProviderConfig, 2),
113+
],
49114
};
50115

51116
const meta: Meta<typeof ModelsSection> = {
@@ -92,7 +157,7 @@ export const HidesPricingWarningForExplicitZeroPricing: Story = {
92157
modelConfigs: [
93158
{
94159
...baseModelConfig,
95-
id: "model-config-id-zero-pricing",
160+
id: "5304021d-6d9b-4d6a-a6f4-9fb504ea4a75",
96161
model_config: {
97162
cost: {
98163
output_price_per_million_tokens: "0",
@@ -108,3 +173,15 @@ export const HidesPricingWarningForExplicitZeroPricing: Story = {
108173
).not.toBeInTheDocument();
109174
},
110175
};
176+
177+
export const MultipleAttachments: Story = {
178+
args: {
179+
modelConfigs: [multiAttachmentModelConfig],
180+
},
181+
play: async ({ canvasElement }) => {
182+
const canvas = within(canvasElement);
183+
await expect(
184+
canvas.getByText("OpenAI Sandbox (+2 more)"),
185+
).toBeInTheDocument();
186+
},
187+
};

0 commit comments

Comments
 (0)