mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-26 05:39:04 +03:00
Miscellaneous fixes (0.18 beta) (#23716)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
* resolve zone friendly names against the correct camera * Improve handling of zone names in chat prompt * show a numeric keyboard for numeric config form fields on mobile * Specify english only for semantic search tool when model is JinaV1 * resolve export hwaccel args global value against the correct config path --------- Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
co-authored by
Nicolas Mowen
parent
c2e739b4bc
commit
81b53b7835
@@ -169,6 +169,13 @@ const detect: SectionConfigOverrides = {
|
||||
resolution: ["width", "height", "fps"],
|
||||
tracking: ["min_initialized", "max_disappeared"],
|
||||
},
|
||||
uiSchema: {
|
||||
annotation_offset: {
|
||||
"ui:options": {
|
||||
signed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
hiddenFields: ["enabled_in_config"],
|
||||
advancedFields: [
|
||||
"min_initialized",
|
||||
|
||||
@@ -57,6 +57,7 @@ const record: SectionConfigOverrides = {
|
||||
"ui:options": {
|
||||
suppressMultiSchema: true,
|
||||
ffmpegPresetField: "hwaccel_args",
|
||||
ffmpegGlobalFieldPath: "export.hwaccel_args",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -17,3 +17,4 @@ export {
|
||||
isSubtreeModified,
|
||||
} from "./overrides";
|
||||
export { getSizedFieldClassName } from "./fieldSizing";
|
||||
export { getNumericInputMode } from "./inputMode";
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { RJSFSchema } from "@rjsf/utils";
|
||||
|
||||
type NumericInputOptions = {
|
||||
signed?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Derive the on-screen keyboard hint for a schema field.
|
||||
*
|
||||
* Numeric config fields render as text inputs because RJSF's NumberField
|
||||
* relies on the widget echoing raw strings back, so that trailing "." and "0"
|
||||
* characters survive while a value is being typed. That means the numeric
|
||||
* keypad has to be requested explicitly. Desktop browsers ignore inputMode, so
|
||||
* this only affects virtual keyboards.
|
||||
*
|
||||
* Fields accepting negative values opt out, since the iOS numeric and decimal
|
||||
* keypads have no minus key. Most numeric fields declare no minimum even
|
||||
* though they are non-negative, so signed fields are marked explicitly with
|
||||
* ui:options.signed.
|
||||
*
|
||||
* Args:
|
||||
* schema: The JSON schema for the field being rendered
|
||||
* options: The resolved ui:options for the field
|
||||
*
|
||||
* Returns:
|
||||
* The inputMode to apply, or undefined to leave the keyboard alone
|
||||
*/
|
||||
export function getNumericInputMode(
|
||||
schema: RJSFSchema,
|
||||
options: unknown,
|
||||
): "numeric" | "decimal" | undefined {
|
||||
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
||||
const isInteger = types.includes("integer");
|
||||
|
||||
if (!isInteger && !types.includes("number")) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const numericOptions =
|
||||
typeof options === "object" && options !== null
|
||||
? (options as NumericInputOptions)
|
||||
: undefined;
|
||||
|
||||
const minimum = schema.minimum ?? schema.exclusiveMinimum;
|
||||
|
||||
if (numericOptions?.signed || (minimum ?? 0) < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return isInteger ? "numeric" : "decimal";
|
||||
}
|
||||
@@ -120,6 +120,12 @@ export function FfmpegArgsWidget(props: WidgetProps) {
|
||||
id,
|
||||
} = props;
|
||||
const presetField = options?.ffmpegPresetField as PresetField | undefined;
|
||||
// Path to this field within its config section. This is usually the same as
|
||||
// the preset field, but the two diverge when the field sits below the
|
||||
// section root: record.export.hwaccel_args uses the hwaccel_args preset list
|
||||
// while living at export.hwaccel_args inside the record section.
|
||||
const globalFieldPath =
|
||||
(options?.ffmpegGlobalFieldPath as string | undefined) ?? presetField;
|
||||
const allowInherit = options?.allowInherit === true;
|
||||
const hideDescription = options?.hideDescription === true;
|
||||
const useSplitLayout = options?.splitLayout !== false;
|
||||
@@ -131,11 +137,18 @@ export function FfmpegArgsWidget(props: WidgetProps) {
|
||||
|
||||
// Extract the global value for this specific field to detect inheritance
|
||||
const globalFieldValue = useMemo(() => {
|
||||
if (!showUseGlobalSetting || !formContext?.globalValue || !presetField) {
|
||||
if (
|
||||
!showUseGlobalSetting ||
|
||||
!formContext?.globalValue ||
|
||||
!globalFieldPath
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return get(formContext.globalValue as Record<string, unknown>, presetField);
|
||||
}, [showUseGlobalSetting, formContext?.globalValue, presetField]);
|
||||
return get(
|
||||
formContext.globalValue as Record<string, unknown>,
|
||||
globalFieldPath,
|
||||
);
|
||||
}, [showUseGlobalSetting, formContext?.globalValue, globalFieldPath]);
|
||||
|
||||
const { data } = useSWR<FfmpegPresetResponse>("ffmpeg/presets");
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { WidgetProps } from "@rjsf/utils";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { getSizedFieldClassName } from "../utils";
|
||||
import { getNumericInputMode, getSizedFieldClassName } from "../utils";
|
||||
|
||||
export function TextWidget(props: WidgetProps) {
|
||||
const {
|
||||
@@ -28,6 +28,7 @@ export function TextWidget(props: WidgetProps) {
|
||||
id={id}
|
||||
className={cn(fieldClassName)}
|
||||
type="text"
|
||||
inputMode={getNumericInputMode(schema, options)}
|
||||
value={value ?? ""}
|
||||
disabled={disabled || readonly}
|
||||
placeholder={placeholder || (options.placeholder as string) || ""}
|
||||
|
||||
@@ -127,8 +127,12 @@ export default function ObjectTrackOverlay({
|
||||
},
|
||||
);
|
||||
|
||||
const getZonesFriendlyNames = (zones: string[], config: FrigateConfig) => {
|
||||
return zones?.map((zone) => resolveZoneName(config, zone)) ?? [];
|
||||
const getZonesFriendlyNames = (
|
||||
zones: string[],
|
||||
config: FrigateConfig,
|
||||
cameraId?: string,
|
||||
) => {
|
||||
return zones?.map((zone) => resolveZoneName(config, zone, cameraId)) ?? [];
|
||||
};
|
||||
|
||||
const timelineResults = useMemo(() => {
|
||||
@@ -151,7 +155,7 @@ export default function ObjectTrackOverlay({
|
||||
data: {
|
||||
...event.data,
|
||||
zones_friendly_names: config
|
||||
? getZonesFriendlyNames(event.data?.zones, config)
|
||||
? getZonesFriendlyNames(event.data?.zones, config, event.camera)
|
||||
: [],
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -61,7 +61,11 @@ export function ObjectPath({
|
||||
...pos.lifecycle_item?.data,
|
||||
zones_friendly_names: pos.lifecycle_item?.data.zones.map(
|
||||
(zone) => {
|
||||
return resolveZoneName(config, zone);
|
||||
return resolveZoneName(
|
||||
config,
|
||||
zone,
|
||||
pos.lifecycle_item?.camera,
|
||||
);
|
||||
},
|
||||
),
|
||||
},
|
||||
|
||||
@@ -301,11 +301,19 @@ export function TrackingDetails({
|
||||
[recordings, actualVideoStart],
|
||||
);
|
||||
|
||||
eventSequence?.map((event) => {
|
||||
event.data.zones_friendly_names = event.data?.zones?.map((zone) => {
|
||||
return resolveZoneName(config, zone);
|
||||
});
|
||||
});
|
||||
const sequence = useMemo(
|
||||
() =>
|
||||
eventSequence?.map((item) => ({
|
||||
...item,
|
||||
data: {
|
||||
...item.data,
|
||||
zones_friendly_names: item.data?.zones?.map((zone) =>
|
||||
resolveZoneName(config, zone, item.camera),
|
||||
),
|
||||
},
|
||||
})),
|
||||
[eventSequence, config],
|
||||
);
|
||||
|
||||
// Use manualOverride (set when seeking in image mode) if present so
|
||||
// lifecycle rows and overlays follow image-mode seeks. Otherwise fall
|
||||
@@ -849,9 +857,9 @@ export function TrackingDetails({
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
{!eventSequence ? (
|
||||
{!sequence ? (
|
||||
<ActivityIndicator className="size-2" size={2} />
|
||||
) : eventSequence.length === 0 ? (
|
||||
) : sequence.length === 0 ? (
|
||||
<div className="py-2 text-muted-foreground">
|
||||
{t("detail.noObjectDetailData", { ns: "views/events" })}
|
||||
</div>
|
||||
@@ -871,7 +879,7 @@ export function TrackingDetails({
|
||||
/>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
{eventSequence.map((item, idx) => {
|
||||
{sequence.map((item, idx) => {
|
||||
return (
|
||||
<div
|
||||
key={`${item.timestamp}-${item.source_id ?? ""}-${idx}`}
|
||||
|
||||
@@ -1032,7 +1032,7 @@ function ObjectTimeline({
|
||||
data: {
|
||||
...event.data,
|
||||
zones_friendly_names: event.data?.zones?.map((zone) =>
|
||||
resolveZoneName(config, zone),
|
||||
resolveZoneName(config, zone, event.camera),
|
||||
),
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user