fix(webui): stabilize repeated model preset rows

This commit is contained in:
chengyongru
2026-07-27 18:11:10 +08:00
committed by chengyongru
parent 3f808d0a68
commit e819b7eea4
2 changed files with 85 additions and 7 deletions
+14 -7
View File
@@ -3171,13 +3171,20 @@ function ModelsSettings({
const namedPresets = settings.model_presets.filter((preset) => !preset.is_default);
const namedPresetsByName = new Map(namedPresets.map((preset) => [preset.name, preset]));
const unorderedPresets = namedPresets.filter((preset) => !callOrder.includes(preset.name));
const callOrderOccurrences = new Map<string, number>();
const presetRows = [
...callOrder.map((name, orderIndex) => ({
name,
orderIndex,
preset: namedPresetsByName.get(name),
})),
...callOrder.map((name, orderIndex) => {
const occurrence = callOrderOccurrences.get(name) ?? 0;
callOrderOccurrences.set(name, occurrence + 1);
return {
key: `ordered:${name}:${occurrence}`,
name,
orderIndex,
preset: namedPresetsByName.get(name),
};
}),
...unorderedPresets.map((preset) => ({
key: `disabled:${preset.name}`,
name: preset.name,
orderIndex: -1,
preset,
@@ -3321,7 +3328,7 @@ function ModelsSettings({
) : (
<>
<div role="list" className="divide-y divide-border/45">
{presetRows.map(({ name, orderIndex, preset }) => {
{presetRows.map(({ key, name, orderIndex, preset }) => {
const ordered = orderIndex >= 0;
const provider = preset
? modelPresetProviderKey(preset, settings)
@@ -3343,7 +3350,7 @@ function ModelsSettings({
draggedCallOrderIndex < orderIndex;
return (
<div
key={name}
key={key}
role="listitem"
tabIndex={ordered ? 0 : -1}
draggable={ordered && !callOrderBusy}
+71
View File
@@ -2130,6 +2130,77 @@ describe("SettingsView Apps catalog", () => {
expect(screen.getByRole("button", { name: "Save preset" })).toBeEnabled();
});
it("keeps repeated fallback preset rows stable when changing the primary preset", async () => {
const { payload, backupPreset } = settingsPayloadWithBackup();
const initialPayload: SettingsPayload = {
...payload,
agent: {
...payload.agent,
model: backupPreset.model,
provider: backupPreset.provider,
resolved_provider: backupPreset.resolved_provider,
model_preset: backupPreset.name,
},
model_presets: payload.model_presets.map((preset) => ({
...preset,
active: preset.name === backupPreset.name,
})),
model_call_order: ["backup", "primary", "backup"],
};
const updatedPayload: SettingsPayload = {
...initialPayload,
agent: {
...payload.agent,
model_preset: "primary",
},
model_presets: payload.model_presets.map((preset) => ({
...preset,
active: preset.name === "primary",
})),
model_call_order: ["primary", "backup", "backup"],
};
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url === "/api/settings") return jsonResponse(initialPayload);
if (url === "/api/settings/cli-apps") {
return jsonResponse({ apps: [], installed_count: 0 });
}
if (url === "/api/settings/mcp-presets") {
return jsonResponse({ presets: [], installed_count: 0 });
}
if (url.startsWith("/api/settings/model-call-order/update?")) {
return jsonResponse(updatedPayload);
}
return { ok: false, status: 404, json: async () => ({}) } as Response;
});
vi.stubGlobal("fetch", fetchMock);
renderSettingsView({ initialSection: "models", initialSettings: initialPayload });
const primaryRow = screen.getByTestId("model-call-order-row-primary");
const firstBackupRow = screen.getAllByTestId("model-call-order-row-backup")[0];
const dataTransfer = {
dropEffect: "move",
effectAllowed: "move",
setData: vi.fn(),
};
fireEvent.dragStart(primaryRow, { dataTransfer });
fireEvent.dragEnter(firstBackupRow, { dataTransfer });
fireEvent.drop(firstBackupRow, { dataTransfer });
await waitFor(() =>
expect(
screen
.getAllByTestId(/^model-call-order-row-/)
.map((row) => row.getAttribute("data-testid")),
).toEqual([
"model-call-order-row-primary",
"model-call-order-row-backup",
"model-call-order-row-backup",
]),
);
});
it("restores the model call order when immediate persistence fails", async () => {
const { payload } = settingsPayloadWithBackup();
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {