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
121 changes: 121 additions & 0 deletions apps/web/src/domains/settings/ai/call-site-helpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { describe, expect, test } from "bun:test";

import type { CallSiteOverrideDraft, ProfileEntry } from "@/domains/settings/ai/ai-types";
import { buildOrderedProfiles } from "@/domains/settings/ai/ai-utils";
import {
isDraftActive,
draftsEqual,
} from "@/domains/settings/ai/call-site-overrides-modal";

// ---------------------------------------------------------------------------
// isDraftActive
// ---------------------------------------------------------------------------

describe("isDraftActive", () => {
test("returns false for null, undefined, and empty draft", () => {
expect(isDraftActive(null)).toBe(false);
expect(isDraftActive(undefined)).toBe(false);
expect(isDraftActive({})).toBe(false);
});

test("returns true when any field is set", () => {
expect(isDraftActive({ profile: "fast" })).toBe(true);
expect(isDraftActive({ provider: "openai" })).toBe(true);
expect(isDraftActive({ model: "gpt-4o" })).toBe(true);
});

test("returns false when all fields are null", () => {
expect(isDraftActive({ profile: null, provider: null, model: null })).toBe(
false,
);
});
});

// ---------------------------------------------------------------------------
// draftsEqual
// ---------------------------------------------------------------------------

describe("draftsEqual", () => {
test("two inactive drafts are equal", () => {
expect(draftsEqual(null, undefined)).toBe(true);
expect(draftsEqual({}, null)).toBe(true);
expect(draftsEqual({}, {})).toBe(true);
});

test("active vs inactive are not equal", () => {
expect(draftsEqual({ profile: "fast" }, null)).toBe(false);
expect(draftsEqual(null, { profile: "fast" })).toBe(false);
});

test("identical active drafts are equal", () => {
const a: CallSiteOverrideDraft = {
profile: "fast",
provider: "openai",
model: "gpt-4o",
};
expect(draftsEqual(a, { ...a })).toBe(true);
});

test("differing fields are detected", () => {
const base: CallSiteOverrideDraft = {
profile: "fast",
provider: "openai",
model: "gpt-4o",
};
expect(draftsEqual(base, { ...base, profile: "slow" })).toBe(false);
expect(draftsEqual(base, { ...base, provider: "anthropic" })).toBe(false);
expect(draftsEqual(base, { ...base, model: "claude-sonnet-4-20250514" })).toBe(false);
});

test("null and undefined fields are treated as equivalent", () => {
expect(
draftsEqual(
{ profile: "fast", provider: null, model: null },
{ profile: "fast", provider: undefined, model: undefined },
),
).toBe(true);
});
});

// ---------------------------------------------------------------------------
// buildOrderedProfiles
// ---------------------------------------------------------------------------

describe("buildOrderedProfiles", () => {
const profiles: Record<string, ProfileEntry> = {
alpha: { label: "Alpha", status: "active" },
beta: { label: "Beta", status: "disabled" },
gamma: { label: "Gamma" },
};

test("returns profiles in profileOrder first, then extras", () => {
const result = buildOrderedProfiles(profiles, ["beta", "alpha"]);
expect(result.map((p) => p.name)).toEqual(["beta", "alpha", "gamma"]);
});

test("skips names in profileOrder that are not in profiles", () => {
const result = buildOrderedProfiles(profiles, [
"beta",
"missing",
"alpha",
]);
expect(result.map((p) => p.name)).toEqual(["beta", "alpha", "gamma"]);
});

test("returns all profiles when profileOrder is empty", () => {
const result = buildOrderedProfiles(profiles, []);
expect(result.map((p) => p.name)).toEqual(["alpha", "beta", "gamma"]);
});

test("attaches name and spreads entry fields", () => {
const result = buildOrderedProfiles(profiles, ["alpha"]);
const first = result[0]!;
expect(first.name).toBe("alpha");
expect(first.label).toBe("Alpha");
expect(first.status).toBe("active");
});

test("returns empty array when profiles is empty", () => {
expect(buildOrderedProfiles({}, ["alpha"])).toEqual([]);
});
});
45 changes: 18 additions & 27 deletions apps/web/src/domains/settings/ai/call-site-overrides-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { Input } from "@vellum/design-library/components/input";
import { Toggle } from "@vellum/design-library/components/toggle";
import { Modal } from "@vellum/design-library/components/modal";
import { toast } from "@vellum/design-library/components/toast";
import { configLlmCallsitesGet } from "@/generated/daemon/sdk.gen";
import type { ConfigLlmCallsitesGetResponse } from "@/generated/daemon/types.gen";
import { configLlmCallsitesGetOptions } from "@/generated/daemon/@tanstack/react-query.gen";
import { captureError } from "@/lib/sentry/capture-error";
import { useAssistantFeatureFlagStore } from "@/stores/assistant-feature-flag-store";
import {
Expand Down Expand Up @@ -59,12 +59,12 @@ export interface CallSiteOverridesModalProps {
// Helpers
// ---------------------------------------------------------------------------

function isDraftActive(d: CallSiteOverrideDraft | null | undefined): boolean {
export function isDraftActive(d: CallSiteOverrideDraft | null | undefined): boolean {
if (!d) return false;
return !!(d.profile || d.provider || d.model);
}

function draftsEqual(
export function draftsEqual(
a: CallSiteOverrideDraft | null | undefined,
b: CallSiteOverrideDraft | null | undefined,
): boolean {
Expand Down Expand Up @@ -148,14 +148,9 @@ function CallSiteOverridesModalInner({
isError,
refetch,
} = useQuery({
queryKey: ["call-site-catalog", assistantId],
queryFn: async () => {
const { data } = await configLlmCallsitesGet({
path: { assistant_id: assistantId },
throwOnError: true,
});
return data;
},
...configLlmCallsitesGetOptions({
path: { assistant_id: assistantId },
}),
enabled: !!assistantId,
staleTime: 60_000,
refetchOnWindowFocus: false,
Expand Down Expand Up @@ -220,13 +215,12 @@ function CallSiteOverridesModalInner({
);

const hasUnsavedDrafts = useMemo(() => {
// eslint-disable-next-line react-hooks/refs -- synchronous flag set before setState
if (!seeded.current) return false;
if (!isSeeded) return false;
for (const id of Object.keys(drafts)) {
if (!draftsEqual(drafts[id], persistedOverrides[id])) return true;
}
return false;
}, [drafts, persistedOverrides]);
}, [isSeeded, drafts, persistedOverrides]);

const hasValidationError = useMemo(
() =>
Expand Down Expand Up @@ -501,6 +495,11 @@ function CallSiteOverridesModalInner({
? null
: profileVal,
);
const defaultProfileLabel = cs.defaultProfile
? (orderedProfiles.find(
(op) => op.name === cs.defaultProfile,
)?.label ?? cs.defaultProfile)
: null;

return (
<div
Expand All @@ -516,19 +515,11 @@ function CallSiteOverridesModalInner({
{cs.description && (
<p className="mt-0.5 text-body-small-default text-[var(--content-tertiary)]">
{cs.description}
{cs.defaultProfile &&
(() => {
const p = orderedProfiles.find(
(op) => op.name === cs.defaultProfile,
);
const label =
p?.label ?? cs.defaultProfile;
return (
<span className="ml-1.5 text-body-small-default text-[var(--content-tertiary)] opacity-60">
&middot; Default: {label}
</span>
);
})()}
{defaultProfileLabel && (
<span className="ml-1.5 text-body-small-default text-[var(--content-tertiary)] opacity-60">
&middot; Default: {defaultProfileLabel}
</span>
)}
</p>
)}
</div>
Expand Down