Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
84 changes: 84 additions & 0 deletions apps/web/src/pages/profile-select.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { describe, expect, test } from "bun:test";
import { renderToStaticMarkup } from "react-dom/server";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { ModelCatalog, Tiers } from "@corbits-builder/schemas/models";
import { keys } from "../queries/keys.ts";
import { NewProjectProfile } from "./profile-select.tsx";

function tiers(provider: string): Tiers {
const binding = { provider, model: "m", effort: "medium" as const };
return { low: binding, medium: binding, high: binding, max: binding };
}

function catalog(): ModelCatalog {
return {
providers: [],
roles: [],
tiers: [],
efforts: [],
customApis: [],
profiles: [
{
id: "xai_default",
label: "Grok",
hint: "Not connected",
tiers: tiers("xai"),
available: false,
},
{
id: "anthropic_default",
label: "Claude",
hint: "Anthropic",
tiers: tiers("anthropic"),
available: true,
},
{
id: "openai_default",
label: "GPT",
hint: "OpenAI",
tiers: tiers("openai"),
available: true,
},
],
};
}

function render(value: string | undefined, models = catalog()) {
const client = new QueryClient({
defaultOptions: { queries: { retry: false, staleTime: Infinity } },
});
client.setQueryData(keys.modelCatalog, models);
client.setQueryData(keys.modelChoice, null);
return renderToStaticMarkup(
<QueryClientProvider client={client}>
<NewProjectProfile value={value} onValueChange={() => {}} />
</QueryClientProvider>,
);
}

describe("NewProjectProfile", () => {
test("selects the first runnable profile when none is automatic", () => {
const initial = render(undefined);
expect(initial).toContain("Model profile, Claude");
expect(initial).not.toContain("Grok");

const picked = render("openai_default");
expect(picked).toContain("Model profile, GPT");
expect(picked).not.toContain("Model profile, Claude");
});

test("keeps the automatic profile when one is set", () => {
const models = catalog();
models.automaticProfile = "openai_default";
expect(render(undefined, models)).toContain("Model profile, GPT");
});

test("renders nothing when no profile can run", () => {
const models = catalog();
models.profiles = models.profiles.map((profile) => ({
...profile,
available: false,
}));
expect(render(undefined, models)).toBe("");
});
});
12 changes: 6 additions & 6 deletions apps/web/src/pages/profile-select.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* The model profile in the new-project prompt box. A new project starts on the one picked here
* (the person's active profile, else the only connected provider's, until they pick another); its
* bindings never change once it starts. With neither, nothing is picked and the hub says why.
* The model profile in the new-project prompt box. A new project starts on the one picked here:
* the person's active profile, else the only connected provider's, else the first profile that can
* run, until they pick another. Its bindings never change once it starts. With no runnable
* profile, nothing is picked and the hub says why.
*/
import {
PromptInputModelSelect,
Expand Down Expand Up @@ -74,9 +75,8 @@ export function NewProjectProfile({
const models =
catalog && choice !== undefined ? profileOptions(catalog, choice) : [];
const active = choice?.profile ?? catalog?.automaticProfile;
const shown = models.some((model) => model.id === active)
? active
: undefined;
const shown =
models.find((model) => model.id === active)?.id ?? models[0]?.id;
useEffect(() => {
if (value === undefined && shown !== undefined) onValueChange(shown);
}, [value, shown, onValueChange]);
Expand Down