mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-26 01:58:21 +03:00
clean up detectors section and i18n
This commit is contained in:
parent
d55b6226d0
commit
19acfe0f7c
@ -190,15 +190,15 @@ def generate_section_translation(config_class: type) -> Dict[str, Any]:
|
|||||||
|
|
||||||
def get_detector_translations(
|
def get_detector_translations(
|
||||||
config_schema: Dict[str, Any],
|
config_schema: Dict[str, Any],
|
||||||
) -> tuple[Dict[str, Any], Dict[str, Any]]:
|
) -> tuple[Dict[str, Any], set[str]]:
|
||||||
"""Build detector field and type translations based on schema definitions."""
|
"""Build detector type translations with nested fields based on schema definitions."""
|
||||||
defs = config_schema.get("$defs", {})
|
defs = config_schema.get("$defs", {})
|
||||||
detector_schema = defs.get("DetectorConfig", {})
|
detector_schema = defs.get("DetectorConfig", {})
|
||||||
discriminator = detector_schema.get("discriminator", {})
|
discriminator = detector_schema.get("discriminator", {})
|
||||||
mapping = discriminator.get("mapping", {})
|
mapping = discriminator.get("mapping", {})
|
||||||
|
|
||||||
type_translations: Dict[str, Any] = {}
|
type_translations: Dict[str, Any] = {}
|
||||||
field_translations: Dict[str, Any] = {}
|
nested_field_keys: set[str] = set()
|
||||||
for detector_type, ref in mapping.items():
|
for detector_type, ref in mapping.items():
|
||||||
if not isinstance(ref, str):
|
if not isinstance(ref, str):
|
||||||
continue
|
continue
|
||||||
@ -219,16 +219,18 @@ def get_detector_translations(
|
|||||||
if description:
|
if description:
|
||||||
type_entry["description"] = description
|
type_entry["description"] = description
|
||||||
|
|
||||||
if type_entry:
|
|
||||||
type_translations[detector_type] = type_entry
|
|
||||||
|
|
||||||
nested = extract_translations_from_schema(ref_schema, defs=defs)
|
nested = extract_translations_from_schema(ref_schema, defs=defs)
|
||||||
nested_without_root = {
|
nested_without_root = {
|
||||||
k: v for k, v in nested.items() if k not in ("label", "description")
|
k: v for k, v in nested.items() if k not in ("label", "description")
|
||||||
}
|
}
|
||||||
field_translations.update(nested_without_root)
|
if nested_without_root:
|
||||||
|
type_entry.update(nested_without_root)
|
||||||
|
nested_field_keys.update(nested_without_root.keys())
|
||||||
|
|
||||||
return field_translations, type_translations
|
if type_entry:
|
||||||
|
type_translations[detector_type] = type_entry
|
||||||
|
|
||||||
|
return type_translations, nested_field_keys
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -301,9 +303,14 @@ def main():
|
|||||||
section_data.update(nested_without_root)
|
section_data.update(nested_without_root)
|
||||||
|
|
||||||
if field_name == "detectors":
|
if field_name == "detectors":
|
||||||
detector_fields, detector_types = get_detector_translations(config_schema)
|
detector_types, detector_field_keys = get_detector_translations(
|
||||||
section_data.update(detector_fields)
|
config_schema
|
||||||
|
)
|
||||||
section_data.update(detector_types)
|
section_data.update(detector_types)
|
||||||
|
for key in detector_field_keys:
|
||||||
|
if key == "type":
|
||||||
|
continue
|
||||||
|
section_data.pop(key, None)
|
||||||
|
|
||||||
if not section_data:
|
if not section_data:
|
||||||
logger.warning(f"No translations found for section: {field_name}")
|
logger.warning(f"No translations found for section: {field_name}")
|
||||||
|
|||||||
@ -287,155 +287,791 @@
|
|||||||
"label": "Detector hardware",
|
"label": "Detector hardware",
|
||||||
"description": "Configuration for object detectors (CPU, GPU, ONNX backends) and any detector-specific model settings.",
|
"description": "Configuration for object detectors (CPU, GPU, ONNX backends) and any detector-specific model settings.",
|
||||||
"type": {
|
"type": {
|
||||||
"label": "Type"
|
"label": "Detector Type",
|
||||||
},
|
"description": "Type of detector to use for object detection (for example 'cpu', 'edgetpu', 'openvino')."
|
||||||
"model": {
|
|
||||||
"label": "Detector specific model configuration",
|
|
||||||
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
|
||||||
"path": {
|
|
||||||
"label": "Custom Object detection model path",
|
|
||||||
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
|
||||||
},
|
|
||||||
"labelmap_path": {
|
|
||||||
"label": "Label map for custom object detector",
|
|
||||||
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
|
||||||
},
|
|
||||||
"width": {
|
|
||||||
"label": "Object detection model input width",
|
|
||||||
"description": "Width of the model input tensor in pixels."
|
|
||||||
},
|
|
||||||
"height": {
|
|
||||||
"label": "Object detection model input height",
|
|
||||||
"description": "Height of the model input tensor in pixels."
|
|
||||||
},
|
|
||||||
"labelmap": {
|
|
||||||
"label": "Labelmap customization",
|
|
||||||
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
|
||||||
},
|
|
||||||
"attributes_map": {
|
|
||||||
"label": "Map of object labels to their attribute labels",
|
|
||||||
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
|
||||||
},
|
|
||||||
"input_tensor": {
|
|
||||||
"label": "Model Input Tensor Shape",
|
|
||||||
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
|
||||||
},
|
|
||||||
"input_pixel_format": {
|
|
||||||
"label": "Model Input Pixel Color Format",
|
|
||||||
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
|
||||||
},
|
|
||||||
"input_dtype": {
|
|
||||||
"label": "Model Input D Type",
|
|
||||||
"description": "Data type of the model input tensor (for example 'float32')."
|
|
||||||
},
|
|
||||||
"model_type": {
|
|
||||||
"label": "Object Detection Model Type",
|
|
||||||
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"model_path": {
|
|
||||||
"label": "Detector specific model path",
|
|
||||||
"description": "File path to the detector model binary if required by the chosen detector."
|
|
||||||
},
|
|
||||||
"num_threads": {
|
|
||||||
"label": "Number of detection threads",
|
|
||||||
"description": "The number of threads used for CPU-based inference."
|
|
||||||
},
|
|
||||||
"api_url": {
|
|
||||||
"label": "DeepStack API URL",
|
|
||||||
"description": "The URL of the DeepStack API."
|
|
||||||
},
|
|
||||||
"api_timeout": {
|
|
||||||
"label": "DeepStack API timeout (in seconds)",
|
|
||||||
"description": "Maximum time allowed for a DeepStack API request."
|
|
||||||
},
|
|
||||||
"api_key": {
|
|
||||||
"label": "DeepStack API key (if required)",
|
|
||||||
"description": "Optional API key for authenticated DeepStack services."
|
|
||||||
},
|
|
||||||
"location": {
|
|
||||||
"label": "Inference Location",
|
|
||||||
"description": "Location of the DeGirim inference engine (e.g. '@cloud', '127.0.0.1')."
|
|
||||||
},
|
|
||||||
"zoo": {
|
|
||||||
"label": "Model Zoo",
|
|
||||||
"description": "Path or URL to the DeGirum model zoo."
|
|
||||||
},
|
|
||||||
"token": {
|
|
||||||
"label": "DeGirum Cloud Token",
|
|
||||||
"description": "Token for DeGirum Cloud access."
|
|
||||||
},
|
|
||||||
"device": {
|
|
||||||
"label": "GPU Device Index",
|
|
||||||
"description": "The GPU device index to use."
|
|
||||||
},
|
|
||||||
"num_cores": {
|
|
||||||
"label": "Number of NPU cores to use.",
|
|
||||||
"description": "The number of NPU cores to use (0 for auto)."
|
|
||||||
},
|
|
||||||
"endpoint": {
|
|
||||||
"label": "ZMQ IPC endpoint",
|
|
||||||
"description": "The ZMQ endpoint to connect to."
|
|
||||||
},
|
|
||||||
"request_timeout_ms": {
|
|
||||||
"label": "ZMQ request timeout in milliseconds",
|
|
||||||
"description": "Timeout for ZMQ requests in milliseconds."
|
|
||||||
},
|
|
||||||
"linger_ms": {
|
|
||||||
"label": "ZMQ socket linger in milliseconds",
|
|
||||||
"description": "Socket linger period in milliseconds."
|
|
||||||
},
|
},
|
||||||
"cpu": {
|
"cpu": {
|
||||||
"label": "CPU",
|
"label": "CPU",
|
||||||
"description": "CPU TFLite detector that runs TensorFlow Lite models on the host CPU without hardware acceleration. Not recommended."
|
"description": "CPU TFLite detector that runs TensorFlow Lite models on the host CPU without hardware acceleration. Not recommended.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
},
|
||||||
|
"num_threads": {
|
||||||
|
"label": "Number of detection threads",
|
||||||
|
"description": "The number of threads used for CPU-based inference."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"deepstack": {
|
"deepstack": {
|
||||||
"label": "DeepStack",
|
"label": "DeepStack",
|
||||||
"description": "DeepStack/CodeProject.AI detector that sends images to a remote DeepStack HTTP API for inference. Not recommended."
|
"description": "DeepStack/CodeProject.AI detector that sends images to a remote DeepStack HTTP API for inference. Not recommended.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
},
|
||||||
|
"api_url": {
|
||||||
|
"label": "DeepStack API URL",
|
||||||
|
"description": "The URL of the DeepStack API."
|
||||||
|
},
|
||||||
|
"api_timeout": {
|
||||||
|
"label": "DeepStack API timeout (in seconds)",
|
||||||
|
"description": "Maximum time allowed for a DeepStack API request."
|
||||||
|
},
|
||||||
|
"api_key": {
|
||||||
|
"label": "DeepStack API key (if required)",
|
||||||
|
"description": "Optional API key for authenticated DeepStack services."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"degirum": {
|
"degirum": {
|
||||||
"label": "DeGirum",
|
"label": "DeGirum",
|
||||||
"description": "DeGirum detector for running models via DeGirum cloud or local inference services."
|
"description": "DeGirum detector for running models via DeGirum cloud or local inference services.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"label": "Inference Location",
|
||||||
|
"description": "Location of the DeGirim inference engine (e.g. '@cloud', '127.0.0.1')."
|
||||||
|
},
|
||||||
|
"zoo": {
|
||||||
|
"label": "Model Zoo",
|
||||||
|
"description": "Path or URL to the DeGirum model zoo."
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"label": "DeGirum Cloud Token",
|
||||||
|
"description": "Token for DeGirum Cloud access."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"edgetpu": {
|
"edgetpu": {
|
||||||
"label": "EdgeTPU",
|
"label": "EdgeTPU",
|
||||||
"description": "EdgeTPU detector that runs TensorFlow Lite models compiled for Coral EdgeTPU using the EdgeTPU delegate."
|
"description": "EdgeTPU detector that runs TensorFlow Lite models compiled for Coral EdgeTPU using the EdgeTPU delegate.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"label": "Device Type",
|
||||||
|
"description": "The device to use for EdgeTPU inference (e.g. 'usb', 'pci')."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"hailo8l": {
|
"hailo8l": {
|
||||||
"label": "Hailo-8/Hailo-8L",
|
"label": "Hailo-8/Hailo-8L",
|
||||||
"description": "Hailo-8/Hailo-8L detector using HEF models and the HailoRT SDK for inference on Hailo hardware."
|
"description": "Hailo-8/Hailo-8L detector using HEF models and the HailoRT SDK for inference on Hailo hardware.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"label": "Device Type",
|
||||||
|
"description": "The device to use for Hailo inference (e.g. 'PCIe', 'M.2')."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"memryx": {
|
"memryx": {
|
||||||
"label": "MemryX",
|
"label": "MemryX",
|
||||||
"description": "MemryX MX3 detector that runs compiled DFP models on MemryX accelerators."
|
"description": "MemryX MX3 detector that runs compiled DFP models on MemryX accelerators.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"label": "Device Path",
|
||||||
|
"description": "The device to use for MemryX inference (e.g. 'PCIe')."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"onnx": {
|
"onnx": {
|
||||||
"label": "ONNX",
|
"label": "ONNX",
|
||||||
"description": "ONNX detector for running ONNX models; will use available acceleration backends (CUDA/ROCm/OpenVINO) when available."
|
"description": "ONNX detector for running ONNX models; will use available acceleration backends (CUDA/ROCm/OpenVINO) when available.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"label": "Device Type",
|
||||||
|
"description": "The device to use for ONNX inference (e.g. 'AUTO', 'CPU', 'GPU')."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"openvino": {
|
"openvino": {
|
||||||
"label": "OpenVINO",
|
"label": "OpenVINO",
|
||||||
"description": "OpenVINO detector for AMD and Intel CPUs, Intel GPUs and Intel VPU hardware."
|
"description": "OpenVINO detector for AMD and Intel CPUs, Intel GPUs and Intel VPU hardware.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"label": "Device Type",
|
||||||
|
"description": "The device to use for OpenVINO inference (e.g. 'CPU', 'GPU', 'NPU')."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"rknn": {
|
"rknn": {
|
||||||
"label": "RKNN",
|
"label": "RKNN",
|
||||||
"description": "RKNN detector for Rockchip NPUs; runs compiled RKNN models on Rockchip hardware."
|
"description": "RKNN detector for Rockchip NPUs; runs compiled RKNN models on Rockchip hardware.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
},
|
||||||
|
"num_cores": {
|
||||||
|
"label": "Number of NPU cores to use.",
|
||||||
|
"description": "The number of NPU cores to use (0 for auto)."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"synaptics": {
|
"synaptics": {
|
||||||
"label": "Synaptics",
|
"label": "Synaptics",
|
||||||
"description": "Synaptics NPU detector for models in .synap format using the Synap SDK on Synaptics hardware."
|
"description": "Synaptics NPU detector for models in .synap format using the Synap SDK on Synaptics hardware.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"teflon_tfl": {
|
"teflon_tfl": {
|
||||||
"label": "Teflon",
|
"label": "Teflon",
|
||||||
"description": "Teflon delegate detector for TFLite using Mesa Teflon delegate library to accelerate inference on supported GPUs."
|
"description": "Teflon delegate detector for TFLite using Mesa Teflon delegate library to accelerate inference on supported GPUs.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tensorrt": {
|
"tensorrt": {
|
||||||
"label": "TensorRT",
|
"label": "TensorRT",
|
||||||
"description": "TensorRT detector for Nvidia Jetson devices using serialized TensorRT engines for accelerated inference."
|
"description": "TensorRT detector for Nvidia Jetson devices using serialized TensorRT engines for accelerated inference.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"label": "GPU Device Index",
|
||||||
|
"description": "The GPU device index to use."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"zmq": {
|
"zmq": {
|
||||||
"label": "ZMQ IPC",
|
"label": "ZMQ IPC",
|
||||||
"description": "ZMQ IPC detector that offloads inference to an external process via a ZeroMQ IPC endpoint."
|
"description": "ZMQ IPC detector that offloads inference to an external process via a ZeroMQ IPC endpoint.",
|
||||||
|
"type": {
|
||||||
|
"label": "Type"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"label": "Detector specific model configuration",
|
||||||
|
"description": "Detector-specific model configuration options (path, input size, etc.).",
|
||||||
|
"path": {
|
||||||
|
"label": "Custom Object detection model path",
|
||||||
|
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||||
|
},
|
||||||
|
"labelmap_path": {
|
||||||
|
"label": "Label map for custom object detector",
|
||||||
|
"description": "Path to a labelmap file that maps numeric classes to string labels for the detector."
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"label": "Object detection model input width",
|
||||||
|
"description": "Width of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"label": "Object detection model input height",
|
||||||
|
"description": "Height of the model input tensor in pixels."
|
||||||
|
},
|
||||||
|
"labelmap": {
|
||||||
|
"label": "Labelmap customization",
|
||||||
|
"description": "Overrides or remapping entries to merge into the standard labelmap."
|
||||||
|
},
|
||||||
|
"attributes_map": {
|
||||||
|
"label": "Map of object labels to their attribute labels",
|
||||||
|
"description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])."
|
||||||
|
},
|
||||||
|
"input_tensor": {
|
||||||
|
"label": "Model Input Tensor Shape",
|
||||||
|
"description": "Tensor format expected by the model: 'nhwc' or 'nchw'."
|
||||||
|
},
|
||||||
|
"input_pixel_format": {
|
||||||
|
"label": "Model Input Pixel Color Format",
|
||||||
|
"description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'."
|
||||||
|
},
|
||||||
|
"input_dtype": {
|
||||||
|
"label": "Model Input D Type",
|
||||||
|
"description": "Data type of the model input tensor (for example 'float32')."
|
||||||
|
},
|
||||||
|
"model_type": {
|
||||||
|
"label": "Object Detection Model Type",
|
||||||
|
"description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_path": {
|
||||||
|
"label": "Detector specific model path",
|
||||||
|
"description": "File path to the detector model binary if required by the chosen detector."
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"label": "ZMQ IPC endpoint",
|
||||||
|
"description": "The ZMQ endpoint to connect to."
|
||||||
|
},
|
||||||
|
"request_timeout_ms": {
|
||||||
|
"label": "ZMQ request timeout in milliseconds",
|
||||||
|
"description": "Timeout for ZMQ requests in milliseconds."
|
||||||
|
},
|
||||||
|
"linger_ms": {
|
||||||
|
"label": "ZMQ socket linger in milliseconds",
|
||||||
|
"description": "Socket linger period in milliseconds."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
|||||||
@ -1234,6 +1234,14 @@
|
|||||||
"detect": {
|
"detect": {
|
||||||
"title": "Detection Settings"
|
"title": "Detection Settings"
|
||||||
},
|
},
|
||||||
|
"detectors": {
|
||||||
|
"title": "Detector Settings",
|
||||||
|
"singleType": "Only one {{type}} detector is allowed.",
|
||||||
|
"keyRequired": "Detector name is required.",
|
||||||
|
"keyDuplicate": "Detector name already exists.",
|
||||||
|
"noSchema": "No detector schemas are available.",
|
||||||
|
"none": "No detector instances configured."
|
||||||
|
},
|
||||||
"record": {
|
"record": {
|
||||||
"title": "Recording Settings"
|
"title": "Recording Settings"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,17 +1,28 @@
|
|||||||
import type { SectionConfigOverrides } from "./types";
|
import type { SectionConfigOverrides } from "./types";
|
||||||
|
|
||||||
|
const detectorHiddenFields = [
|
||||||
|
"*.model.labelmap",
|
||||||
|
"*.model.attributes_map",
|
||||||
|
"*.model",
|
||||||
|
"*.model_path",
|
||||||
|
];
|
||||||
|
|
||||||
const detectors: SectionConfigOverrides = {
|
const detectors: SectionConfigOverrides = {
|
||||||
base: {
|
base: {
|
||||||
sectionDocs: "/configuration/object_detectors",
|
sectionDocs: "/configuration/object_detectors",
|
||||||
restartRequired: [],
|
restartRequired: [],
|
||||||
fieldOrder: [],
|
fieldOrder: [],
|
||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
hiddenFields: [
|
hiddenFields: detectorHiddenFields,
|
||||||
"*.model.labelmap",
|
uiSchema: {
|
||||||
"*.model.attributes_map",
|
"ui:field": "DetectorHardwareField",
|
||||||
"*.model",
|
"ui:options": {
|
||||||
"*.model_path",
|
multiInstanceTypes: ["cpu", "onnx", "openvino"],
|
||||||
],
|
typeOrder: ["onnx", "openvino", "edgetpu"],
|
||||||
|
hiddenByType: {},
|
||||||
|
hiddenFields: detectorHiddenFields,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,869 @@
|
|||||||
|
import type {
|
||||||
|
ErrorSchema,
|
||||||
|
FieldPathList,
|
||||||
|
FieldProps,
|
||||||
|
RJSFSchema,
|
||||||
|
UiSchema,
|
||||||
|
} from "@rjsf/utils";
|
||||||
|
import { toFieldPathId } from "@rjsf/utils";
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
LuChevronDown,
|
||||||
|
LuChevronRight,
|
||||||
|
LuPlus,
|
||||||
|
LuTrash2,
|
||||||
|
} from "react-icons/lu";
|
||||||
|
import { applySchemaDefaults } from "@/lib/config-schema";
|
||||||
|
import { cn, isJsonObject, mergeUiSchema } from "@/lib/utils";
|
||||||
|
import { ConfigFormContext, JsonObject } from "@/types/configForm";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Collapsible,
|
||||||
|
CollapsibleContent,
|
||||||
|
CollapsibleTrigger,
|
||||||
|
} from "@/components/ui/collapsible";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { humanizeKey } from "../utils/i18n";
|
||||||
|
|
||||||
|
type DetectorHardwareFieldOptions = {
|
||||||
|
multiInstanceTypes?: string[];
|
||||||
|
hiddenByType?: Record<string, string[]>;
|
||||||
|
hiddenFields?: string[];
|
||||||
|
typeOrder?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type DetectorSchemaEntry = {
|
||||||
|
type: string;
|
||||||
|
schema: RJSFSchema;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_MULTI_INSTANCE_TYPES = ["cpu", "onnx", "openvino"];
|
||||||
|
const EMPTY_HIDDEN_BY_TYPE: Record<string, string[]> = {};
|
||||||
|
const EMPTY_HIDDEN_FIELDS: string[] = [];
|
||||||
|
const EMPTY_TYPE_ORDER: string[] = [];
|
||||||
|
|
||||||
|
const isSchemaObject = (schema: unknown): schema is RJSFSchema =>
|
||||||
|
typeof schema === "object" && schema !== null;
|
||||||
|
|
||||||
|
const getUnionSchemas = (schema?: RJSFSchema): RJSFSchema[] => {
|
||||||
|
if (!schema) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaObj = schema as Record<string, unknown>;
|
||||||
|
const union = schemaObj.oneOf ?? schemaObj.anyOf;
|
||||||
|
if (Array.isArray(union)) {
|
||||||
|
return union.filter(isSchemaObject) as RJSFSchema[];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [schema];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTypeValues = (schema: RJSFSchema): string[] => {
|
||||||
|
const schemaObj = schema as Record<string, unknown>;
|
||||||
|
const properties = schemaObj.properties as
|
||||||
|
| Record<string, unknown>
|
||||||
|
| undefined;
|
||||||
|
const typeSchema = properties?.type as Record<string, unknown> | undefined;
|
||||||
|
const values: string[] = [];
|
||||||
|
|
||||||
|
if (typeof typeSchema?.const === "string") {
|
||||||
|
values.push(typeSchema.const);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(typeSchema?.enum)) {
|
||||||
|
typeSchema.enum.forEach((value) => {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildHiddenUiSchema = (paths: string[]): UiSchema => {
|
||||||
|
const result: UiSchema = {};
|
||||||
|
|
||||||
|
paths.forEach((path) => {
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const segments = path.split(".").filter(Boolean);
|
||||||
|
if (segments.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cursor = result;
|
||||||
|
segments.forEach((segment, index) => {
|
||||||
|
if (index === segments.length - 1) {
|
||||||
|
cursor[segment] = {
|
||||||
|
...(cursor[segment] as UiSchema | undefined),
|
||||||
|
"ui:widget": "hidden",
|
||||||
|
} as UiSchema;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = (cursor[segment] as UiSchema | undefined) ?? {};
|
||||||
|
cursor[segment] = existing;
|
||||||
|
cursor = existing;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInstanceType = (value: unknown): string | undefined => {
|
||||||
|
if (!isJsonObject(value)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeValue = value.type;
|
||||||
|
return typeof typeValue === "string" && typeValue.length > 0
|
||||||
|
? typeValue
|
||||||
|
: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DetectorHardwareField(props: FieldProps) {
|
||||||
|
const {
|
||||||
|
schema,
|
||||||
|
uiSchema,
|
||||||
|
registry,
|
||||||
|
fieldPathId,
|
||||||
|
formData: rawFormData,
|
||||||
|
errorSchema,
|
||||||
|
disabled,
|
||||||
|
readonly,
|
||||||
|
hideError,
|
||||||
|
onBlur,
|
||||||
|
onFocus,
|
||||||
|
onChange,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const formContext = registry.formContext as ConfigFormContext | undefined;
|
||||||
|
const configNamespace =
|
||||||
|
formContext?.i18nNamespace ??
|
||||||
|
(formContext?.level === "camera" ? "config/cameras" : "config/global");
|
||||||
|
const { t: fallbackT } = useTranslation(["common", configNamespace]);
|
||||||
|
const t = formContext?.t ?? fallbackT;
|
||||||
|
const sectionPrefix = formContext?.sectionI18nPrefix ?? "detectors";
|
||||||
|
|
||||||
|
const options =
|
||||||
|
(uiSchema?.["ui:options"] as DetectorHardwareFieldOptions | undefined) ??
|
||||||
|
{};
|
||||||
|
const multiInstanceTypes =
|
||||||
|
options.multiInstanceTypes ?? DEFAULT_MULTI_INSTANCE_TYPES;
|
||||||
|
const hiddenByType = options.hiddenByType ?? EMPTY_HIDDEN_BY_TYPE;
|
||||||
|
const hiddenFields = options.hiddenFields ?? EMPTY_HIDDEN_FIELDS;
|
||||||
|
const typeOrder = options.typeOrder ?? EMPTY_TYPE_ORDER;
|
||||||
|
const multiInstanceSet = useMemo(
|
||||||
|
() => new Set(multiInstanceTypes),
|
||||||
|
[multiInstanceTypes],
|
||||||
|
);
|
||||||
|
const globalHiddenFields = useMemo(
|
||||||
|
() =>
|
||||||
|
hiddenFields
|
||||||
|
.map((path) => (path.startsWith("*.") ? path.slice(2) : path))
|
||||||
|
.filter((path) => path.length > 0),
|
||||||
|
[hiddenFields],
|
||||||
|
);
|
||||||
|
|
||||||
|
const detectorConfigSchema = useMemo(() => {
|
||||||
|
const additional = (schema as RJSFSchema | undefined)?.additionalProperties;
|
||||||
|
if (isSchemaObject(additional)) {
|
||||||
|
return additional as RJSFSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootSchema = registry.rootSchema as Record<string, unknown>;
|
||||||
|
const defs =
|
||||||
|
(rootSchema?.$defs as Record<string, unknown> | undefined) ??
|
||||||
|
(rootSchema?.definitions as Record<string, unknown> | undefined);
|
||||||
|
const fallback = defs?.DetectorConfig;
|
||||||
|
|
||||||
|
return isSchemaObject(fallback) ? (fallback as RJSFSchema) : undefined;
|
||||||
|
}, [schema, registry.rootSchema]);
|
||||||
|
|
||||||
|
const detectorSchemas = useMemo<DetectorSchemaEntry[]>(() => {
|
||||||
|
const entries: DetectorSchemaEntry[] = [];
|
||||||
|
getUnionSchemas(detectorConfigSchema).forEach((schema) => {
|
||||||
|
const types = getTypeValues(schema);
|
||||||
|
types.forEach((type) => {
|
||||||
|
entries.push({ type, schema });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return entries;
|
||||||
|
}, [detectorConfigSchema]);
|
||||||
|
|
||||||
|
const detectorSchemaByType = useMemo(() => {
|
||||||
|
const map = new Map<string, RJSFSchema>();
|
||||||
|
detectorSchemas.forEach(({ type, schema }) => {
|
||||||
|
if (!map.has(type)) {
|
||||||
|
map.set(type, schema);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}, [detectorSchemas]);
|
||||||
|
|
||||||
|
const availableTypes = useMemo(
|
||||||
|
() => detectorSchemas.map((entry) => entry.type),
|
||||||
|
[detectorSchemas],
|
||||||
|
);
|
||||||
|
|
||||||
|
const orderedTypes = useMemo(() => {
|
||||||
|
if (!typeOrder.length) {
|
||||||
|
return availableTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const availableSet = new Set(availableTypes);
|
||||||
|
const ordered = typeOrder.filter((type) => availableSet.has(type));
|
||||||
|
const orderedSet = new Set(ordered);
|
||||||
|
const remaining = availableTypes.filter((type) => !orderedSet.has(type));
|
||||||
|
return [...ordered, ...remaining];
|
||||||
|
}, [availableTypes, typeOrder]);
|
||||||
|
|
||||||
|
const formData = isJsonObject(rawFormData) ? rawFormData : {};
|
||||||
|
const detectors = formData as JsonObject;
|
||||||
|
|
||||||
|
const [addType, setAddType] = useState<string | undefined>(orderedTypes[0]);
|
||||||
|
const [addError, setAddError] = useState<string | undefined>();
|
||||||
|
const [renameDrafts, setRenameDrafts] = useState<Record<string, string>>({});
|
||||||
|
const [renameErrors, setRenameErrors] = useState<Record<string, string>>({});
|
||||||
|
const [typeErrors, setTypeErrors] = useState<Record<string, string>>({});
|
||||||
|
const [openKeys, setOpenKeys] = useState<Set<string>>(
|
||||||
|
() => new Set(Object.keys(detectors)),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!orderedTypes.length) {
|
||||||
|
setAddType(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!addType || !orderedTypes.includes(addType)) {
|
||||||
|
setAddType(orderedTypes[0]);
|
||||||
|
}
|
||||||
|
}, [orderedTypes, addType]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOpenKeys((prev) => {
|
||||||
|
const next = new Set<string>();
|
||||||
|
Object.keys(detectors).forEach((key) => {
|
||||||
|
if (prev.has(key)) {
|
||||||
|
next.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
setRenameDrafts((prev) => {
|
||||||
|
const next: Record<string, string> = {};
|
||||||
|
Object.keys(detectors).forEach((key) => {
|
||||||
|
if (prev[key] !== undefined) {
|
||||||
|
next[key] = prev[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
setRenameErrors((prev) => {
|
||||||
|
const next: Record<string, string> = {};
|
||||||
|
Object.keys(detectors).forEach((key) => {
|
||||||
|
if (prev[key] !== undefined) {
|
||||||
|
next[key] = prev[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
setTypeErrors((prev) => {
|
||||||
|
const next: Record<string, string> = {};
|
||||||
|
Object.keys(detectors).forEach((key) => {
|
||||||
|
if (prev[key] !== undefined) {
|
||||||
|
next[key] = prev[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, [detectors]);
|
||||||
|
|
||||||
|
const updateDetectors = useCallback(
|
||||||
|
(nextDetectors: JsonObject) => {
|
||||||
|
onChange(nextDetectors as unknown, [] as FieldPathList);
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getTypeLabel = useCallback(
|
||||||
|
(type: string) =>
|
||||||
|
t(`${sectionPrefix}.${type}.label`, {
|
||||||
|
ns: configNamespace,
|
||||||
|
defaultValue: humanizeKey(type),
|
||||||
|
}),
|
||||||
|
[t, sectionPrefix, configNamespace],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getTypeDescription = useCallback(
|
||||||
|
(type: string) =>
|
||||||
|
t(`${sectionPrefix}.${type}.description`, {
|
||||||
|
ns: configNamespace,
|
||||||
|
defaultValue: "",
|
||||||
|
}),
|
||||||
|
[t, sectionPrefix, configNamespace],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isSingleInstanceType = useCallback(
|
||||||
|
(type: string) => !multiInstanceSet.has(type),
|
||||||
|
[multiInstanceSet],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getDetectorDefaults = useCallback(
|
||||||
|
(type: string) => {
|
||||||
|
const schema = detectorSchemaByType.get(type);
|
||||||
|
if (!schema) {
|
||||||
|
return { type };
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = { type } as Record<string, unknown>;
|
||||||
|
const withDefaults = applySchemaDefaults(schema, base);
|
||||||
|
return { ...withDefaults, type } as Record<string, unknown>;
|
||||||
|
},
|
||||||
|
[detectorSchemaByType],
|
||||||
|
);
|
||||||
|
|
||||||
|
const resolveDuplicateType = useCallback(
|
||||||
|
(targetType: string, excludeKey?: string) => {
|
||||||
|
return Object.entries(detectors).some(([key, value]) => {
|
||||||
|
if (excludeKey && key === excludeKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getInstanceType(value) === targetType;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[detectors],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAdd = useCallback(() => {
|
||||||
|
if (!addType) {
|
||||||
|
setAddError(
|
||||||
|
t("selectItem", {
|
||||||
|
ns: "common",
|
||||||
|
defaultValue: "Select {{item}}",
|
||||||
|
item: t("detectors.type.label", {
|
||||||
|
ns: configNamespace,
|
||||||
|
defaultValue: "Type",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSingleInstanceType(addType) && resolveDuplicateType(addType)) {
|
||||||
|
setAddError(
|
||||||
|
t("configForm.detectors.singleType", {
|
||||||
|
ns: "views/settings",
|
||||||
|
defaultValue: "Only one {{type}} detector is allowed.",
|
||||||
|
type: getTypeLabel(addType),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseKey = addType;
|
||||||
|
let nextKey = baseKey;
|
||||||
|
let index = 2;
|
||||||
|
while (Object.prototype.hasOwnProperty.call(detectors, nextKey)) {
|
||||||
|
nextKey = `${baseKey}${index}`;
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextDetectors = {
|
||||||
|
...detectors,
|
||||||
|
[nextKey]: getDetectorDefaults(addType),
|
||||||
|
} as JsonObject;
|
||||||
|
|
||||||
|
setAddError(undefined);
|
||||||
|
setOpenKeys((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.add(nextKey);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
updateDetectors(nextDetectors);
|
||||||
|
}, [
|
||||||
|
addType,
|
||||||
|
t,
|
||||||
|
configNamespace,
|
||||||
|
detectors,
|
||||||
|
getDetectorDefaults,
|
||||||
|
getTypeLabel,
|
||||||
|
isSingleInstanceType,
|
||||||
|
resolveDuplicateType,
|
||||||
|
updateDetectors,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleRemove = useCallback(
|
||||||
|
(key: string) => {
|
||||||
|
const { [key]: _, ...rest } = detectors;
|
||||||
|
updateDetectors(rest as JsonObject);
|
||||||
|
setOpenKeys((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.delete(key);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[detectors, updateDetectors],
|
||||||
|
);
|
||||||
|
|
||||||
|
const commitRename = useCallback(
|
||||||
|
(key: string, nextKey: string) => {
|
||||||
|
const trimmed = nextKey.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
setRenameErrors((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[key]: t("configForm.detectors.keyRequired", {
|
||||||
|
ns: "views/settings",
|
||||||
|
defaultValue: "Detector name is required.",
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed !== key && detectors[trimmed] !== undefined) {
|
||||||
|
setRenameErrors((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[key]: t("configForm.detectors.keyDuplicate", {
|
||||||
|
ns: "views/settings",
|
||||||
|
defaultValue: "Detector name already exists.",
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRenameErrors((prev) => {
|
||||||
|
const { [key]: _, ...rest } = prev;
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
|
|
||||||
|
setRenameDrafts((prev) => {
|
||||||
|
const { [key]: _, ...rest } = prev;
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (trimmed === key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { [key]: value, ...rest } = detectors;
|
||||||
|
const nextDetectors = { ...rest, [trimmed]: value } as JsonObject;
|
||||||
|
|
||||||
|
setOpenKeys((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.delete(key)) {
|
||||||
|
next.add(trimmed);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
updateDetectors(nextDetectors);
|
||||||
|
},
|
||||||
|
[detectors, t, updateDetectors],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTypeChange = useCallback(
|
||||||
|
(key: string, nextType: string) => {
|
||||||
|
const currentType = getInstanceType(detectors[key]);
|
||||||
|
if (!nextType || nextType === currentType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isSingleInstanceType(nextType) &&
|
||||||
|
resolveDuplicateType(nextType, key)
|
||||||
|
) {
|
||||||
|
setTypeErrors((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[key]: t("configForm.detectors.singleType", {
|
||||||
|
ns: "views/settings",
|
||||||
|
defaultValue: "Only one {{type}} detector is allowed.",
|
||||||
|
type: getTypeLabel(nextType),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTypeErrors((prev) => {
|
||||||
|
const { [key]: _, ...rest } = prev;
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextDetectors = {
|
||||||
|
...detectors,
|
||||||
|
[key]: getDetectorDefaults(nextType),
|
||||||
|
} as JsonObject;
|
||||||
|
|
||||||
|
updateDetectors(nextDetectors);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
detectors,
|
||||||
|
getDetectorDefaults,
|
||||||
|
getTypeLabel,
|
||||||
|
isSingleInstanceType,
|
||||||
|
resolveDuplicateType,
|
||||||
|
t,
|
||||||
|
updateDetectors,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getInstanceUiSchema = useCallback(
|
||||||
|
(type: string) => {
|
||||||
|
const baseUiSchema =
|
||||||
|
(uiSchema?.additionalProperties as UiSchema | undefined) ?? {};
|
||||||
|
const globalHidden = buildHiddenUiSchema(globalHiddenFields);
|
||||||
|
const hiddenOverrides = buildHiddenUiSchema(hiddenByType[type] ?? []);
|
||||||
|
const typeHidden = { type: { "ui:widget": "hidden" } } as UiSchema;
|
||||||
|
|
||||||
|
const withGlobalHidden = mergeUiSchema(baseUiSchema, globalHidden);
|
||||||
|
const withTypeHidden = mergeUiSchema(withGlobalHidden, hiddenOverrides);
|
||||||
|
return mergeUiSchema(withTypeHidden, typeHidden);
|
||||||
|
},
|
||||||
|
[globalHiddenFields, hiddenByType, uiSchema?.additionalProperties],
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderInstanceForm = useCallback(
|
||||||
|
(key: string, value: unknown) => {
|
||||||
|
const SchemaField = registry.fields.SchemaField;
|
||||||
|
const type = getInstanceType(value);
|
||||||
|
const schema = type ? detectorSchemaByType.get(type) : undefined;
|
||||||
|
|
||||||
|
if (!SchemaField || !schema || !type) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instanceUiSchema = getInstanceUiSchema(type);
|
||||||
|
const instanceFieldPathId = toFieldPathId(
|
||||||
|
key,
|
||||||
|
registry.globalFormOptions,
|
||||||
|
fieldPathId.path,
|
||||||
|
);
|
||||||
|
|
||||||
|
const instanceErrorSchema = (
|
||||||
|
errorSchema as Record<string, ErrorSchema> | undefined
|
||||||
|
)?.[key];
|
||||||
|
|
||||||
|
const handleInstanceChange = (
|
||||||
|
nextValue: unknown,
|
||||||
|
_path: FieldPathList,
|
||||||
|
_errors?: ErrorSchema,
|
||||||
|
_id?: string,
|
||||||
|
) => {
|
||||||
|
const nextDetectors = {
|
||||||
|
...detectors,
|
||||||
|
[key]: nextValue ?? {},
|
||||||
|
} as JsonObject;
|
||||||
|
updateDetectors(nextDetectors);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaField
|
||||||
|
name={key}
|
||||||
|
schema={schema}
|
||||||
|
uiSchema={instanceUiSchema}
|
||||||
|
fieldPathId={instanceFieldPathId}
|
||||||
|
formData={value}
|
||||||
|
errorSchema={instanceErrorSchema}
|
||||||
|
onChange={handleInstanceChange}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onFocus={onFocus}
|
||||||
|
registry={registry}
|
||||||
|
disabled={disabled}
|
||||||
|
readonly={readonly}
|
||||||
|
hideError={hideError}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
detectorSchemaByType,
|
||||||
|
detectors,
|
||||||
|
getInstanceUiSchema,
|
||||||
|
disabled,
|
||||||
|
errorSchema,
|
||||||
|
fieldPathId,
|
||||||
|
hideError,
|
||||||
|
onBlur,
|
||||||
|
onFocus,
|
||||||
|
readonly,
|
||||||
|
registry,
|
||||||
|
updateDetectors,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!availableTypes.length) {
|
||||||
|
return (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("configForm.detectors.noSchema", {
|
||||||
|
ns: "views/settings",
|
||||||
|
defaultValue: "No detector schemas are available.",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const detectorEntries = Object.entries(detectors);
|
||||||
|
const isDisabled = Boolean(disabled || readonly);
|
||||||
|
const addLabel = `${t("button.add", {
|
||||||
|
ns: "common",
|
||||||
|
defaultValue: "Add",
|
||||||
|
})} ${t("detectors.label", {
|
||||||
|
ns: configNamespace,
|
||||||
|
defaultValue: "Detector hardware",
|
||||||
|
})}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{detectorEntries.length === 0 ? (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("configForm.detectors.none", {
|
||||||
|
ns: "views/settings",
|
||||||
|
defaultValue: "No detector instances configured.",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{detectorEntries.map(([key, value]) => {
|
||||||
|
const type = getInstanceType(value) ?? "";
|
||||||
|
const typeLabel = type ? getTypeLabel(type) : key;
|
||||||
|
const typeDescription = type ? getTypeDescription(type) : "";
|
||||||
|
const isOpen = openKeys.has(key);
|
||||||
|
const renameDraft = renameDrafts[key] ?? key;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={key} className="rounded-lg border bg-card">
|
||||||
|
<Collapsible
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setOpenKeys((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (open) {
|
||||||
|
next.add(key);
|
||||||
|
} else {
|
||||||
|
next.delete(key);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between gap-4 p-4">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="xs"
|
||||||
|
className="mt-0.5"
|
||||||
|
>
|
||||||
|
{isOpen ? (
|
||||||
|
<LuChevronDown className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<LuChevronRight className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-medium">
|
||||||
|
{typeLabel}
|
||||||
|
<span className="ml-2 text-xs text-muted-foreground">
|
||||||
|
{key}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{typeDescription && (
|
||||||
|
<div className="text-xs text-muted-foreground">
|
||||||
|
{typeDescription}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => handleRemove(key)}
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
<LuTrash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<CollapsibleContent>
|
||||||
|
<div className="space-y-4 border-t p-4">
|
||||||
|
<div className="grid gap-4 md:grid-cols-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>
|
||||||
|
{t("label.ID", {
|
||||||
|
ns: "common",
|
||||||
|
defaultValue: "ID",
|
||||||
|
})}
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
value={renameDraft}
|
||||||
|
disabled={isDisabled}
|
||||||
|
onChange={(event) => {
|
||||||
|
setRenameDrafts((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[key]: event.target.value,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
onBlur={(event) =>
|
||||||
|
commitRename(key, event.target.value)
|
||||||
|
}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
commitRename(key, renameDraft);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t("field.internalID", {
|
||||||
|
ns: "common",
|
||||||
|
defaultValue:
|
||||||
|
"The Internal ID Frigate uses in the configuration and database",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
{renameErrors[key] && (
|
||||||
|
<p className="text-xs text-danger">
|
||||||
|
{renameErrors[key]}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="col-span-3 space-y-2">
|
||||||
|
<Label>
|
||||||
|
{t("detectors.type.label", {
|
||||||
|
ns: configNamespace,
|
||||||
|
defaultValue: "Type",
|
||||||
|
})}
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={type}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
handleTypeChange(key, value)
|
||||||
|
}
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t("selectItem", {
|
||||||
|
ns: "common",
|
||||||
|
defaultValue: "Select {{item}}",
|
||||||
|
item: t("detectors.type.label", {
|
||||||
|
ns: configNamespace,
|
||||||
|
defaultValue: "Type",
|
||||||
|
}),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{orderedTypes.map((option) => (
|
||||||
|
<SelectItem key={option} value={option}>
|
||||||
|
{getTypeLabel(option)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{typeErrors[key] && (
|
||||||
|
<p className="text-xs text-danger">
|
||||||
|
{typeErrors[key]}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"rounded-md border border-border/70 bg-background p-0",
|
||||||
|
readonly && "opacity-90",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{renderInstanceForm(key, value)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex justify-start">
|
||||||
|
<div className="w-full max-w-md rounded-lg border bg-card p-4">
|
||||||
|
<div className="text-sm font-medium text-muted-foreground">
|
||||||
|
{addLabel}
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 flex flex-col gap-3 md:flex-row md:items-end">
|
||||||
|
<div className="flex-1 space-y-2">
|
||||||
|
<Label>
|
||||||
|
{t("detectors.type.label", {
|
||||||
|
ns: configNamespace,
|
||||||
|
defaultValue: "Type",
|
||||||
|
})}
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={addType ?? ""}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setAddError(undefined);
|
||||||
|
setAddType(value);
|
||||||
|
}}
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t("selectItem", {
|
||||||
|
ns: "common",
|
||||||
|
defaultValue: "Select {{item}}",
|
||||||
|
item: t("detectors.type.label", {
|
||||||
|
ns: configNamespace,
|
||||||
|
defaultValue: "Type",
|
||||||
|
}),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{orderedTypes.map((type) => (
|
||||||
|
<SelectItem key={type} value={type}>
|
||||||
|
{getTypeLabel(type)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{addError && <p className="text-xs text-danger">{addError}</p>}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleAdd}
|
||||||
|
disabled={isDisabled}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<LuPlus className="h-4 w-4" />
|
||||||
|
{t("button.add", {
|
||||||
|
ns: "common",
|
||||||
|
defaultValue: "Add",
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,2 +1,3 @@
|
|||||||
// Custom RJSF Fields
|
// Custom RJSF Fields
|
||||||
export { LayoutGridField } from "./LayoutGridField";
|
export { LayoutGridField } from "./LayoutGridField";
|
||||||
|
export { DetectorHardwareField } from "./DetectorHardwareField";
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import { MultiSchemaFieldTemplate } from "./templates/MultiSchemaFieldTemplate";
|
|||||||
import { WrapIfAdditionalTemplate } from "./templates/WrapIfAdditionalTemplate";
|
import { WrapIfAdditionalTemplate } from "./templates/WrapIfAdditionalTemplate";
|
||||||
|
|
||||||
import { LayoutGridField } from "./fields/LayoutGridField";
|
import { LayoutGridField } from "./fields/LayoutGridField";
|
||||||
|
import { DetectorHardwareField } from "./fields/DetectorHardwareField";
|
||||||
|
|
||||||
export interface FrigateTheme {
|
export interface FrigateTheme {
|
||||||
widgets: RegistryWidgetsType;
|
widgets: RegistryWidgetsType;
|
||||||
@ -79,5 +80,6 @@ export const frigateTheme: FrigateTheme = {
|
|||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
LayoutGridField: LayoutGridField,
|
LayoutGridField: LayoutGridField,
|
||||||
|
DetectorHardwareField: DetectorHardwareField,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -126,7 +126,11 @@ export function FieldTemplate(props: FieldTemplateProps) {
|
|||||||
!isAdditionalProperty &&
|
!isAdditionalProperty &&
|
||||||
!isArrayItemInAdditionalProp;
|
!isArrayItemInAdditionalProp;
|
||||||
|
|
||||||
const translationPath = buildTranslationPath(pathSegments, sectionI18nPrefix);
|
const translationPath = buildTranslationPath(
|
||||||
|
pathSegments,
|
||||||
|
sectionI18nPrefix,
|
||||||
|
formContext,
|
||||||
|
);
|
||||||
const filterObjectLabel = getFilterObjectLabel(pathSegments);
|
const filterObjectLabel = getFilterObjectLabel(pathSegments);
|
||||||
const translatedFilterObjectLabel = filterObjectLabel
|
const translatedFilterObjectLabel = filterObjectLabel
|
||||||
? getTranslatedLabel(filterObjectLabel, "object")
|
? getTranslatedLabel(filterObjectLabel, "object")
|
||||||
|
|||||||
@ -88,7 +88,11 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
|||||||
? getTranslatedLabel(filterObjectLabel, "object")
|
? getTranslatedLabel(filterObjectLabel, "object")
|
||||||
: undefined;
|
: undefined;
|
||||||
if (path) {
|
if (path) {
|
||||||
translationPath = buildTranslationPath(path);
|
translationPath = buildTranslationPath(
|
||||||
|
path,
|
||||||
|
sectionI18nPrefix,
|
||||||
|
formContext,
|
||||||
|
);
|
||||||
// Also get the last property name for fallback label generation
|
// Also get the last property name for fallback label generation
|
||||||
for (let i = path.length - 1; i >= 0; i -= 1) {
|
for (let i = path.length - 1; i >= 0; i -= 1) {
|
||||||
const segment = path[i];
|
const segment = path[i];
|
||||||
|
|||||||
@ -5,6 +5,46 @@
|
|||||||
* for RJSF form fields.
|
* for RJSF form fields.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { ConfigFormContext } from "@/types/configForm";
|
||||||
|
|
||||||
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||||
|
typeof value === "object" && value !== null;
|
||||||
|
|
||||||
|
const resolveDetectorType = (
|
||||||
|
detectorConfig: unknown,
|
||||||
|
detectorKey?: string,
|
||||||
|
): string | undefined => {
|
||||||
|
if (!detectorKey || !isRecord(detectorConfig)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = detectorConfig[detectorKey];
|
||||||
|
if (!isRecord(entry)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeValue = entry.type;
|
||||||
|
return typeof typeValue === "string" && typeValue.length > 0
|
||||||
|
? typeValue
|
||||||
|
: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveDetectorTypeFromContext = (
|
||||||
|
formContext: ConfigFormContext | undefined,
|
||||||
|
detectorKey?: string,
|
||||||
|
): string | undefined => {
|
||||||
|
const formData = formContext?.formData;
|
||||||
|
if (!detectorKey || !isRecord(formData)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const detectorConfig = isRecord(formData.detectors)
|
||||||
|
? formData.detectors
|
||||||
|
: formData;
|
||||||
|
|
||||||
|
return resolveDetectorType(detectorConfig, detectorKey);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the i18n translation key path for nested fields using the field path
|
* Build the i18n translation key path for nested fields using the field path
|
||||||
* provided by RJSF. This avoids ambiguity with underscores in field names and
|
* provided by RJSF. This avoids ambiguity with underscores in field names and
|
||||||
@ -12,16 +52,18 @@
|
|||||||
*
|
*
|
||||||
* @param segments Array of path segments (strings and/or numbers)
|
* @param segments Array of path segments (strings and/or numbers)
|
||||||
* @param sectionI18nPrefix Optional section prefix for specialized sections
|
* @param sectionI18nPrefix Optional section prefix for specialized sections
|
||||||
|
* @param formContext Optional form context for resolving detector types
|
||||||
* @returns Normalized translation key path as a dot-separated string
|
* @returns Normalized translation key path as a dot-separated string
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* buildTranslationPath(["filters", "person", "threshold"]) => "filters.threshold"
|
* buildTranslationPath(["filters", "person", "threshold"]) => "filters.threshold"
|
||||||
* buildTranslationPath(["detectors", "ov1", "type"]) => "detectors.type"
|
* buildTranslationPath(["detectors", "ov1", "type"]) => "detectors.openvino.type"
|
||||||
* buildTranslationPath(["model", "type"], "detectors") => "type"
|
* buildTranslationPath(["ov1", "type"], "detectors") => "openvino.type"
|
||||||
*/
|
*/
|
||||||
export function buildTranslationPath(
|
export function buildTranslationPath(
|
||||||
segments: Array<string | number>,
|
segments: Array<string | number>,
|
||||||
sectionI18nPrefix?: string,
|
sectionI18nPrefix?: string,
|
||||||
|
formContext?: ConfigFormContext,
|
||||||
): string {
|
): string {
|
||||||
// Filter out numeric indices to get string segments only
|
// Filter out numeric indices to get string segments only
|
||||||
const stringSegments = segments.filter(
|
const stringSegments = segments.filter(
|
||||||
@ -39,10 +81,24 @@ export function buildTranslationPath(
|
|||||||
return normalized.join(".");
|
return normalized.join(".");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle detectors section - skip the dynamic detector name
|
// Handle detectors section - resolve the detector type when available
|
||||||
// Example: detectors.ov1.type -> detectors.type
|
// Example: detectors.ov1.type -> detectors.openvino.type
|
||||||
const detectorsIndex = stringSegments.indexOf("detectors");
|
const detectorsIndex = stringSegments.indexOf("detectors");
|
||||||
if (detectorsIndex !== -1 && stringSegments.length > detectorsIndex + 2) {
|
if (detectorsIndex !== -1 && stringSegments.length > detectorsIndex + 2) {
|
||||||
|
const detectorKey = stringSegments[detectorsIndex + 1];
|
||||||
|
const detectorType = resolveDetectorTypeFromContext(
|
||||||
|
formContext,
|
||||||
|
detectorKey,
|
||||||
|
);
|
||||||
|
if (detectorType) {
|
||||||
|
const normalized = [
|
||||||
|
...stringSegments.slice(0, detectorsIndex + 1),
|
||||||
|
detectorType,
|
||||||
|
...stringSegments.slice(detectorsIndex + 2),
|
||||||
|
];
|
||||||
|
return normalized.join(".");
|
||||||
|
}
|
||||||
|
|
||||||
const normalized = [
|
const normalized = [
|
||||||
...stringSegments.slice(0, detectorsIndex + 1),
|
...stringSegments.slice(0, detectorsIndex + 1),
|
||||||
...stringSegments.slice(detectorsIndex + 2),
|
...stringSegments.slice(detectorsIndex + 2),
|
||||||
@ -51,8 +107,17 @@ export function buildTranslationPath(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle specialized sections like detectors where the first segment is dynamic
|
// Handle specialized sections like detectors where the first segment is dynamic
|
||||||
// Example: (sectionI18nPrefix="detectors") "ov1.type" -> "type"
|
// Example: (sectionI18nPrefix="detectors") "ov1.type" -> "openvino.type"
|
||||||
if (sectionI18nPrefix === "detectors" && stringSegments.length > 1) {
|
if (sectionI18nPrefix === "detectors" && stringSegments.length > 1) {
|
||||||
|
const detectorKey = stringSegments[0];
|
||||||
|
const detectorType = resolveDetectorTypeFromContext(
|
||||||
|
formContext,
|
||||||
|
detectorKey,
|
||||||
|
);
|
||||||
|
if (detectorType) {
|
||||||
|
return [detectorType, ...stringSegments.slice(1)].join(".");
|
||||||
|
}
|
||||||
|
|
||||||
return stringSegments.slice(1).join(".");
|
return stringSegments.slice(1).join(".");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user