diff --git a/apps/web/src/domains/settings/ai/call-site-helpers.test.ts b/apps/web/src/domains/settings/ai/call-site-helpers.test.ts new file mode 100644 index 00000000000..c54981262b9 --- /dev/null +++ b/apps/web/src/domains/settings/ai/call-site-helpers.test.ts @@ -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 = { + 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([]); + }); +}); diff --git a/apps/web/src/domains/settings/ai/call-site-overrides-modal.tsx b/apps/web/src/domains/settings/ai/call-site-overrides-modal.tsx index cba39ae389f..5d0b2d0a62b 100644 --- a/apps/web/src/domains/settings/ai/call-site-overrides-modal.tsx +++ b/apps/web/src/domains/settings/ai/call-site-overrides-modal.tsx @@ -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 { @@ -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 { @@ -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, @@ -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( () => @@ -501,6 +495,11 @@ function CallSiteOverridesModalInner({ ? null : profileVal, ); + const defaultProfileLabel = cs.defaultProfile + ? (orderedProfiles.find( + (op) => op.name === cs.defaultProfile, + )?.label ?? cs.defaultProfile) + : null; return (
{cs.description} - {cs.defaultProfile && - (() => { - const p = orderedProfiles.find( - (op) => op.name === cs.defaultProfile, - ); - const label = - p?.label ?? cs.defaultProfile; - return ( - - · Default: {label} - - ); - })()} + {defaultProfileLabel && ( + + · Default: {defaultProfileLabel} + + )}

)}