add semantic search model size widget

disables model_size select with n/a text when an embeddings genai provider is selected
This commit is contained in:
Josh Hawkins 2026-05-22 12:38:47 -05:00
parent 01df9b15f2
commit 48d7ee4604
6 changed files with 135 additions and 71 deletions

View File

@ -1543,6 +1543,9 @@
"builtIn": "Built-in Models", "builtIn": "Built-in Models",
"genaiProviders": "GenAI Providers" "genaiProviders": "GenAI Providers"
}, },
"semanticSearchModelSize": {
"notApplicable": "Not applicable for GenAI providers"
},
"review": { "review": {
"title": "Review Settings" "title": "Review Settings"
}, },
@ -1820,8 +1823,7 @@
"mixedTypesSuggestion": "All detectors must use the same type. Remove existing detectors or select {{type}}." "mixedTypesSuggestion": "All detectors must use the same type. Remove existing detectors or select {{type}}."
}, },
"semanticSearch": { "semanticSearch": {
"jinav2SmallModelSize": "The 'small' size with the Jina V2 model has high RAM and inference cost. The 'large' model with a discrete GPU is recommended.", "jinav2SmallModelSize": "The 'small' size with the Jina V2 model has high RAM and inference cost. The 'large' model with a discrete GPU is recommended."
"modelSizeIgnoredForProvider": "Model size only applies to the built-in Jina models. This value will be ignored when using a GenAI embedding provider."
} }
} }
} }

View File

@ -0,0 +1,13 @@
import { createContext } from "react";
import type { ConfigSectionData } from "@/types/configForm";
// Mirrors the current section's in-flight form data so widgets can react
// to changes that RJSF wouldn't otherwise re-render them for. RJSF's
// Form memoizes SchemaField via deep equality and, in some transitions
// (notably reverting a field to its saved value), can skip re-rendering
// a widget even though the form data it depends on changed. useContext
// re-runs consumers directly on every provider value update, sidestepping
// that.
export const LiveFormDataContext = createContext<ConfigSectionData | null>(
null,
);

View File

@ -29,28 +29,13 @@ const semanticSearch: SectionConfigOverrides = {
ctx.formData?.model === "jinav2" && ctx.formData?.model === "jinav2" &&
ctx.formData?.model_size === "small", ctx.formData?.model_size === "small",
}, },
{
key: "model-size-ignored-for-provider",
field: "model_size",
messageKey: "configMessages.semanticSearch.modelSizeIgnoredForProvider",
severity: "info",
position: "after",
condition: (ctx) => {
const model = ctx.formData?.model;
return (
typeof model === "string" &&
model !== "" &&
model !== "jinav1" &&
model !== "jinav2"
);
},
},
], ],
uiSchema: { uiSchema: {
model: { model: {
"ui:widget": "semanticSearchModel", "ui:widget": "semanticSearchModel",
}, },
model_size: { model_size: {
"ui:widget": "semanticSearchModelSize",
"ui:options": { size: "xs", enumI18nPrefix: "modelSize" }, "ui:options": { size: "xs", enumI18nPrefix: "modelSize" },
}, },
}, },

View File

@ -87,6 +87,7 @@ import type {
import { useConfigMessages } from "@/hooks/use-config-messages"; import { useConfigMessages } from "@/hooks/use-config-messages";
import { ConfigMessageBanner } from "../ConfigMessageBanner"; import { ConfigMessageBanner } from "../ConfigMessageBanner";
import { FieldMessagesContext } from "../FieldMessagesContext"; import { FieldMessagesContext } from "../FieldMessagesContext";
import { LiveFormDataContext } from "../LiveFormDataContext";
export interface SectionConfig { export interface SectionConfig {
/** Field ordering within the section */ /** Field ordering within the section */
@ -998,6 +999,9 @@ export function ConfigSection({
<div className="space-y-6"> <div className="space-y-6">
<ConfigMessageBanner messages={activeMessages} /> <ConfigMessageBanner messages={activeMessages} />
<FieldMessagesContext.Provider value={activeFieldMessages}> <FieldMessagesContext.Provider value={activeFieldMessages}>
<LiveFormDataContext.Provider
value={(currentFormData as ConfigSectionData | null) ?? null}
>
<ConfigForm <ConfigForm
key={formKey} key={formKey}
schema={modifiedSchema} schema={modifiedSchema}
@ -1051,6 +1055,7 @@ export function ConfigSection({
isProfile: !!profileName, isProfile: !!profileName,
}} }}
/> />
</LiveFormDataContext.Provider>
</FieldMessagesContext.Provider> </FieldMessagesContext.Provider>
{!embedded && ( {!embedded && (

View File

@ -31,6 +31,7 @@ import { TimezoneSelectWidget } from "./widgets/TimezoneSelectWidget";
import { CameraPathWidget } from "./widgets/CameraPathWidget"; import { CameraPathWidget } from "./widgets/CameraPathWidget";
import { OptionalFieldWidget } from "./widgets/OptionalFieldWidget"; import { OptionalFieldWidget } from "./widgets/OptionalFieldWidget";
import { SemanticSearchModelWidget } from "./widgets/SemanticSearchModelWidget"; import { SemanticSearchModelWidget } from "./widgets/SemanticSearchModelWidget";
import { SemanticSearchModelSizeWidget } from "./widgets/SemanticSearchModelSizeWidget";
import { OnvifProfileWidget } from "./widgets/OnvifProfileWidget"; import { OnvifProfileWidget } from "./widgets/OnvifProfileWidget";
import { FieldTemplate } from "./templates/FieldTemplate"; import { FieldTemplate } from "./templates/FieldTemplate";
@ -86,6 +87,7 @@ export const frigateTheme: FrigateTheme = {
timezoneSelect: TimezoneSelectWidget, timezoneSelect: TimezoneSelectWidget,
optionalField: OptionalFieldWidget, optionalField: OptionalFieldWidget,
semanticSearchModel: SemanticSearchModelWidget, semanticSearchModel: SemanticSearchModelWidget,
semanticSearchModelSize: SemanticSearchModelSizeWidget,
onvifProfile: OnvifProfileWidget, onvifProfile: OnvifProfileWidget,
}, },
templates: { templates: {

View File

@ -0,0 +1,57 @@
// Disables model_size and shows "N/A" when a GenAI provider is selected.
// Reads model via LiveFormDataContext so it re-runs even when RJSF's
// SchemaField memoization would skip this widget.
import type { WidgetProps } from "@rjsf/utils";
import { useContext, useEffect } from "react";
import { useTranslation } from "react-i18next";
import {
Select,
SelectContent,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { LiveFormDataContext } from "../../LiveFormDataContext";
import { getSizedFieldClassName } from "../utils";
import { SelectWidget } from "./SelectWidget";
export function SemanticSearchModelSizeWidget(props: WidgetProps) {
const { t } = useTranslation(["views/settings"]);
const liveFormData = useContext(LiveFormDataContext);
const model = liveFormData?.model;
const isProvider =
typeof model === "string" &&
model !== "" &&
model !== "jinav1" &&
model !== "jinav2";
// Clear model_size while on a provider (buildOverrides converts to ""
// which the backend treats as "remove"). Restore the schema default
// when returning to a Jina model so the field isn't left empty.
const { value, onChange, schema } = props;
const schemaDefault = schema?.default as string | undefined;
useEffect(() => {
if (isProvider && value !== undefined) {
onChange(undefined);
} else if (!isProvider && value === undefined && schemaDefault) {
onChange(schemaDefault);
}
}, [isProvider, value, onChange, schemaDefault]);
if (isProvider) {
const fieldClassName = getSizedFieldClassName(props.options ?? {}, "sm");
return (
<Select value="" disabled>
<SelectTrigger className={fieldClassName}>
<SelectValue
placeholder={t("configForm.semanticSearchModelSize.notApplicable", {
defaultValue: "Not applicable for GenAI providers",
})}
/>
</SelectTrigger>
<SelectContent />
</Select>
);
}
return <SelectWidget {...props} />;
}