mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-14 16:01:13 +03:00
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:
parent
01df9b15f2
commit
48d7ee4604
@ -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."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
web/src/components/config-form/LiveFormDataContext.ts
Normal file
13
web/src/components/config-form/LiveFormDataContext.ts
Normal 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,
|
||||||
|
);
|
||||||
@ -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" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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 && (
|
||||||
|
|||||||
@ -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: {
|
||||||
|
|||||||
@ -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} />;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user