Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/native/src/components/widget/settings/ai-models-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export function AiModelsTab({
selector={(state: any) => [
state.values.evolveProvider,
state.values.openaiApiKey,
state.values.vllmApiBaseUrl,
state.values.openaiCompatibleApiBaseUrl,
]}
>
{([evolveProvider]: any[]) => (
Expand Down Expand Up @@ -292,7 +292,7 @@ export function AiModelsTab({
selector={(state: any) => [
state.values.summaryProvider,
state.values.openaiApiKey,
state.values.vllmApiBaseUrl,
state.values.openaiCompatibleApiBaseUrl,
]}
>
{([summaryProvider]: any[]) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { act, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { tauriAPI } from "@/ipc/api";
import { RouterProvider, nav, router } from "@/router";

type ApiKeysTabSaveProps = {
onSaveOpenaiCompatibleUrl: (url: string) => Promise<void>;
onSaveOpenaiCompatibleKey: (key: string) => Promise<void>;
};

const apiKeysTab = vi.hoisted(() => ({ props: null as ApiKeysTabSaveProps | null }));

// The root layout mounts DarwinWidget alongside the settings overlay; its
// side-effect hooks are irrelevant to this test.
vi.mock("@/components/widget/widget", () => ({
Expand All @@ -12,7 +20,12 @@ vi.mock("@/components/widget/widget", () => ({
// module-level imports don't drag in the IPC layer.
vi.mock("@/components/widget/settings/account-tab", () => ({ AccountTab: () => null }));
vi.mock("@/components/widget/settings/ai-models-tab", () => ({ AiModelsTab: () => null }));
vi.mock("@/components/widget/settings/api-keys-tab", () => ({ ApiKeysTab: () => null }));
vi.mock("@/components/widget/settings/api-keys-tab", () => ({
ApiKeysTab: (props: ApiKeysTabSaveProps) => {
apiKeysTab.props = props;
return null;
},
}));
vi.mock("@/components/widget/settings/developer-tab", () => ({ DeveloperTab: () => null }));
vi.mock("@/components/widget/settings/general-tab", () => ({ GeneralTab: () => null }));
vi.mock("@/components/widget/settings/permissions-tab", () => ({ PermissionsTab: () => null }));
Expand Down Expand Up @@ -75,4 +88,30 @@ describe("SettingsDialog", () => {
expect(screen.getByRole("button", { name: "Log back in" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Select another model" })).toBeInTheDocument();
});

// Regression: the model picker caches OpenAI-compatible models under the
// provider id `openai_compatible`, but saving the endpoint or key cleared a
// `vllm` entry instead, so the picker kept showing the old server's models.
it("clears the OpenAI-compatible model cache when its endpoint or key is saved", async () => {
render(<RouterProvider router={router} />);
await act(async () => {
await nav.openSettings("api-keys");
});

const props = await vi.waitFor(() => {
if (!apiKeysTab.props) throw new Error("API keys tab not rendered yet");
return apiKeysTab.props;
});
vi.mocked(tauriAPI.models.clearCached).mockClear();

await act(async () => {
await props.onSaveOpenaiCompatibleUrl("http://localhost:8000/v1");
await props.onSaveOpenaiCompatibleKey("sk-test");
});

expect(vi.mocked(tauriAPI.models.clearCached).mock.calls).toEqual([
["openai_compatible"],
["openai_compatible"],
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,15 @@ export function SettingsDialog() {
await tauriAPI.ui.setPrefs({ openaiCompatibleApiBaseUrl: url });
// Clear cached OpenAI-compatible models when the endpoint changes.
// deprecated(orpc): replace with client/orpc from @/lib/orpc
await tauriAPI.models.clearCached("vllm");
await tauriAPI.models.clearCached("openai_compatible");
Comment thread
czxtm marked this conversation as resolved.
Resolved
};

const saveOpenaiCompatibleKey = async (key: string) => {
// deprecated(orpc): replace with client/orpc from @/lib/orpc
await tauriAPI.ui.setPrefs({ openaiCompatibleApiKey: key });
// Clear cached OpenAI-compatible models because auth can change visible models.
// deprecated(orpc): replace with client/orpc from @/lib/orpc
await tauriAPI.models.clearCached("vllm");
await tauriAPI.models.clearCached("openai_compatible");
};

const form = useForm({
Expand Down
Loading