diff --git a/webui/src/components/settings/SettingsView.tsx b/webui/src/components/settings/SettingsView.tsx index b23efd66f..648222712 100644 --- a/webui/src/components/settings/SettingsView.tsx +++ b/webui/src/components/settings/SettingsView.tsx @@ -1467,21 +1467,10 @@ export function SettingsView({ ) { return; } - const presetName = modelPresetPendingDelete.name; - const nextOrder = modelCallOrder.includes(presetName) - ? modelCallOrder.filter((name) => name !== presetName) - : null; - if (nextOrder?.length === 0) return; setSaving(true); try { - if (nextOrder) { - const orderedPayload = await updateModelCallOrder(token, nextOrder); - applyPayload(orderedPayload); - onModelNameChange(orderedPayload.agent.model || null); - } - const payload = await deleteModelConfiguration(token, presetName); + const payload = await deleteModelConfiguration(token, modelPresetPendingDelete.name); applyPayload(payload); - onModelNameChange(payload.agent.model || null); setModelPresetPendingDelete(null); setError(null); } catch (err) { @@ -3384,11 +3373,6 @@ function ModelsSettings({ const selectedPresetReferenced = Boolean( selectedPreset && callOrder.includes(selectedPreset.name), ); - const selectedPresetOnlyActive = Boolean( - selectedPresetReferenced && - selectedPreset && - !callOrder.some((name) => name !== selectedPreset.name), - ); const callOrderBusy = orderSaving || saving; const selectPreset = (preset: SettingsPayload["model_presets"][number]) => { const toggleCurrentPreset = !creating && selectedPreset?.name === preset.name; @@ -3453,7 +3437,7 @@ function ModelsSettings({ key={key} id="model-preset-editor" data-testid="model-preset-editor" - className="divide-y divide-border/45 bg-muted/10 motion-reduce:animate-none animate-in fade-in-0 slide-in-from-top-1 duration-200" + className="divide-y divide-border/45 bg-muted/30 motion-reduce:animate-none animate-in fade-in-0 slide-in-from-top-1 duration-200" > {creating ? (
@@ -3582,16 +3566,16 @@ function ModelsSettings({ size="sm" variant="ghost" className="rounded-full text-muted-foreground hover:text-destructive" - disabled={selectedPresetOnlyActive || saving || orderSaving} + disabled={selectedPresetReferenced || saving || orderSaving} aria-describedby={ - selectedPresetOnlyActive ? "model-preset-delete-hint" : undefined + selectedPresetReferenced ? "model-preset-delete-hint" : undefined } onClick={() => onDeleteConfiguration(selectedPreset)} > {tx("settings.actions.delete", "Delete")} - {selectedPresetOnlyActive ? ( + {selectedPresetReferenced ? ( { expect(trigger).toHaveAttribute("aria-expanded", "true"); expect(trigger).toHaveAttribute("aria-controls", "model-preset-editor"); expect(row.nextElementSibling).toBe(editor); - expect(editor).toHaveClass("slide-in-from-top-1", "bg-muted/10"); + expect(editor).toHaveClass("slide-in-from-top-1", "bg-muted/30"); expect(within(editor).getByDisplayValue("Primary")).toBeInTheDocument(); const deleteButton = within(editor).getByRole("button", { name: "Delete" }); expect(deleteButton).toBeDisabled(); @@ -2182,74 +2182,6 @@ describe("SettingsView Apps catalog", () => { expect(screen.queryByTestId("model-preset-editor")).not.toBeInTheDocument(); }); - it("removes an active preset from the call order before deleting it", async () => { - const { payload, backupPreset } = settingsPayloadWithBackup(); - const orderedPayload: 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: [backupPreset.name], - }; - const deletedPayload: SettingsPayload = { - ...orderedPayload, - model_presets: orderedPayload.model_presets.filter( - (preset) => preset.name !== "primary", - ), - }; - const fetchMock = vi.fn(async (input: RequestInfo | URL) => { - const url = String(input); - if (url === "/api/settings") return jsonResponse(payload); - 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(orderedPayload); - } - if (url.startsWith("/api/settings/model-configurations/delete?")) { - return jsonResponse(deletedPayload); - } - return { ok: false, status: 404, json: async () => ({}) } as Response; - }); - vi.stubGlobal("fetch", fetchMock); - - renderSettingsView({ initialSection: "models", initialSettings: payload }); - - await togglePresetEditor(); - const deleteButton = screen.getByRole("button", { name: "Delete" }); - expect(deleteButton).toBeEnabled(); - fireEvent.click(deleteButton); - const dialog = await screen.findByRole("dialog", { name: "Delete model preset?" }); - fireEvent.click(within(dialog).getByRole("button", { name: "Delete" })); - - await waitFor(() => { - const urls = fetchMock.mock.calls.map(([input]) => String(input)); - const orderCallIndex = urls.findIndex((url) => - url.startsWith("/api/settings/model-call-order/update?"), - ); - const deleteCallIndex = urls.findIndex((url) => - url.startsWith("/api/settings/model-configurations/delete?"), - ); - expect(orderCallIndex).toBeGreaterThanOrEqual(0); - expect(deleteCallIndex).toBeGreaterThan(orderCallIndex); - const orderUrl = new URL(urls[orderCallIndex], "http://nanobot.test"); - expect(JSON.parse(orderUrl.searchParams.get("order") ?? "[]")).toEqual(["backup"]); - }); - expect(screen.queryByTestId("model-call-order-row-primary")).not.toBeInTheDocument(); - expect(screen.getByTestId("model-call-order-row-backup")).toBeInTheDocument(); - }); - it("drags model presets to reorder and saves the model call order immediately", async () => { const { payload, backupPreset } = settingsPayloadWithBackup(); const updatedPayload: SettingsPayload = {